diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index f5e15f1985a..9a9f9b55bef 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -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
  -->
@@ -1870,6 +1870,27 @@
       <entry>If a foreign key, list of the referenced columns</entry>
      </row>
 
+     <row>
+      <entry><structfield>conpfeqop</structfield></entry>
+      <entry><type>oid[]</type></entry>
+      <entry><literal><link linkend="catalog-pg-operator"><structname>pg_operator</structname></link>.oid</></entry>
+      <entry>If a foreign key, list of the equality operators for PK = FK comparisons</entry>
+     </row>
+
+     <row>
+      <entry><structfield>conppeqop</structfield></entry>
+      <entry><type>oid[]</type></entry>
+      <entry><literal><link linkend="catalog-pg-operator"><structname>pg_operator</structname></link>.oid</></entry>
+      <entry>If a foreign key, list of the equality operators for PK = PK comparisons</entry>
+     </row>
+
+     <row>
+      <entry><structfield>conffeqop</structfield></entry>
+      <entry><type>oid[]</type></entry>
+      <entry><literal><link linkend="catalog-pg-operator"><structname>pg_operator</structname></link>.oid</></entry>
+      <entry>If a foreign key, list of the equality operators for FK = FK comparisons</entry>
+     </row>
+
      <row>
       <entry><structfield>conbin</structfield></entry>
       <entry><type>text</type></entry>
@@ -1899,8 +1920,8 @@
   <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>
 
@@ -4166,35 +4187,42 @@
       <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>
@@ -4221,10 +4249,22 @@
    </tgroup>
   </table>
 
+  <note>
+   <para>
+    When <structfield>tgconstraint</> is nonzero,
+    <structfield>tgisconstraint</> must be true, and
+    <structfield>tgconstrname</>, <structfield>tgconstrrelid</>,
+    <structfield>tgdeferrable</>, <structfield>tginitdeferred</> are redundant
+    with the referenced <structname>pg_constraint</> entry.  The reason we
+    keep these fields is that we support <quote>stand-alone</> constraint
+    triggers with no corresponding <structname>pg_constraint</> entry.
+   </para>
+  </note>
+
   <note>
    <para>
     <literal>pg_class.reltriggers</literal> needs to agree with the
-    number of triggers found in this table for the given relation.
+    number of triggers found in this table for each relation.
    </para>
   </note>
 
diff --git a/doc/src/sgml/trigger.sgml b/doc/src/sgml/trigger.sgml
index 0479f57b0d2..193b8173974 100644
--- a/doc/src/sgml/trigger.sgml
+++ b/doc/src/sgml/trigger.sgml
@@ -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;
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index b1210841fff..a2b64c78b3f 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/catalog/aclchk.c,v 1.136 2007/02/01 19:10:25 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/catalog/aclchk.c,v 1.137 2007/02/14 01:58:56 tgl Exp $
  *
  * NOTES
  *	  See acl.h.
@@ -2314,7 +2314,7 @@ pg_conversion_ownercheck(Oid conv_oid, Oid roleid)
 	if (superuser_arg(roleid))
 		return true;
 
-	tuple = SearchSysCache(CONOID,
+	tuple = SearchSysCache(CONVOID,
 						   ObjectIdGetDatum(conv_oid),
 						   0, 0, 0);
 	if (!HeapTupleIsValid(tuple))
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 2041125d4d0..40591fd3680 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -8,7 +8,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/catalog/dependency.c,v 1.63 2007/02/01 19:10:25 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/catalog/dependency.c,v 1.64 2007/02/14 01:58:56 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1758,29 +1758,16 @@ getObjectDescription(const ObjectAddress *object)
 
 		case OCLASS_CONSTRAINT:
 			{
-				Relation	conDesc;
-				ScanKeyData skey[1];
-				SysScanDesc rcscan;
-				HeapTuple	tup;
+				HeapTuple	conTup;
 				Form_pg_constraint con;
 
-				conDesc = heap_open(ConstraintRelationId, AccessShareLock);
-
-				ScanKeyInit(&skey[0],
-							ObjectIdAttributeNumber,
-							BTEqualStrategyNumber, F_OIDEQ,
-							ObjectIdGetDatum(object->objectId));
-
-				rcscan = systable_beginscan(conDesc, ConstraintOidIndexId, true,
-											SnapshotNow, 1, skey);
-
-				tup = systable_getnext(rcscan);
-
-				if (!HeapTupleIsValid(tup))
-					elog(ERROR, "could not find tuple for constraint %u",
+				conTup = SearchSysCache(CONSTROID,
+										ObjectIdGetDatum(object->objectId),
+										0, 0, 0);
+				if (!HeapTupleIsValid(conTup))
+					elog(ERROR, "cache lookup failed for constraint %u",
 						 object->objectId);
-
-				con = (Form_pg_constraint) GETSTRUCT(tup);
+				con = (Form_pg_constraint) GETSTRUCT(conTup);
 
 				if (OidIsValid(con->conrelid))
 				{
@@ -1794,8 +1781,7 @@ getObjectDescription(const ObjectAddress *object)
 									 NameStr(con->conname));
 				}
 
-				systable_endscan(rcscan);
-				heap_close(conDesc, AccessShareLock);
+				ReleaseSysCache(conTup);
 				break;
 			}
 
@@ -1803,7 +1789,7 @@ getObjectDescription(const ObjectAddress *object)
 			{
 				HeapTuple	conTup;
 
-				conTup = SearchSysCache(CONOID,
+				conTup = SearchSysCache(CONVOID,
 										ObjectIdGetDatum(object->objectId),
 										0, 0, 0);
 				if (!HeapTupleIsValid(conTup))
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 1d0639bcf0c..d29a6df21d3 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/catalog/heap.c,v 1.316 2007/01/05 22:19:24 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/catalog/heap.c,v 1.317 2007/02/14 01:58:56 tgl Exp $
  *
  *
  * INTERFACE ROUTINES
@@ -1463,6 +1463,9 @@ StoreRelCheck(Relation rel, char *ccname, char *ccbin)
 						  InvalidOid,	/* not a domain constraint */
 						  InvalidOid,	/* Foreign key fields */
 						  NULL,
+						  NULL,
+						  NULL,
+						  NULL,
 						  0,
 						  ' ',
 						  ' ',
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 52554c3b21d..ae8965e730a 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/catalog/index.c,v 1.278 2007/02/01 19:10:25 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/catalog/index.c,v 1.279 2007/02/14 01:58:56 tgl Exp $
  *
  *
  * INTERFACE ROUTINES
@@ -680,6 +680,9 @@ index_create(Oid heapRelationId,
 										   InvalidOid,	/* no domain */
 										   InvalidOid,	/* no foreign key */
 										   NULL,
+										   NULL,
+										   NULL,
+										   NULL,
 										   0,
 										   ' ',
 										   ' ',
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 7c2cd4b8a49..2213192f782 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -13,7 +13,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/catalog/namespace.c,v 1.91 2007/02/01 19:10:25 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/catalog/namespace.c,v 1.92 2007/02/14 01:58:56 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1199,7 +1199,7 @@ ConversionIsVisible(Oid conid)
 	Oid			connamespace;
 	bool		visible;
 
-	contup = SearchSysCache(CONOID,
+	contup = SearchSysCache(CONVOID,
 							ObjectIdGetDatum(conid),
 							0, 0, 0);
 	if (!HeapTupleIsValid(contup))
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index 53249facb7d..ede6607b851 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/catalog/pg_constraint.c,v 1.34 2007/01/05 22:19:25 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/catalog/pg_constraint.c,v 1.35 2007/02/14 01:58:56 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -19,8 +19,7 @@
 #include "catalog/dependency.h"
 #include "catalog/indexing.h"
 #include "catalog/pg_constraint.h"
-#include "catalog/pg_depend.h"
-#include "catalog/pg_trigger.h"
+#include "catalog/pg_operator.h"
 #include "catalog/pg_type.h"
 #include "commands/defrem.h"
 #include "utils/array.h"
@@ -49,6 +48,9 @@ CreateConstraintEntry(const char *constraintName,
 					  Oid domainId,
 					  Oid foreignRelId,
 					  const int16 *foreignKey,
+					  const Oid *pfEqOp,
+					  const Oid *ppEqOp,
+					  const Oid *ffEqOp,
 					  int foreignNKeys,
 					  char foreignUpdateType,
 					  char foreignDeleteType,
@@ -65,6 +67,9 @@ CreateConstraintEntry(const char *constraintName,
 	Datum		values[Natts_pg_constraint];
 	ArrayType  *conkeyArray;
 	ArrayType  *confkeyArray;
+	ArrayType  *conpfeqopArray;
+	ArrayType  *conppeqopArray;
+	ArrayType  *conffeqopArray;
 	NameData	cname;
 	int			i;
 	ObjectAddress conobject;
@@ -92,16 +97,33 @@ CreateConstraintEntry(const char *constraintName,
 
 	if (foreignNKeys > 0)
 	{
-		Datum	   *confkey;
+		Datum	   *fkdatums;
 
-		confkey = (Datum *) palloc(foreignNKeys * sizeof(Datum));
+		fkdatums = (Datum *) palloc(foreignNKeys * sizeof(Datum));
 		for (i = 0; i < foreignNKeys; i++)
-			confkey[i] = Int16GetDatum(foreignKey[i]);
-		confkeyArray = construct_array(confkey, foreignNKeys,
+			fkdatums[i] = Int16GetDatum(foreignKey[i]);
+		confkeyArray = construct_array(fkdatums, foreignNKeys,
 									   INT2OID, 2, true, 's');
+		for (i = 0; i < foreignNKeys; i++)
+			fkdatums[i] = ObjectIdGetDatum(pfEqOp[i]);
+		conpfeqopArray = construct_array(fkdatums, foreignNKeys,
+										 OIDOID, sizeof(Oid), true, 'i');
+		for (i = 0; i < foreignNKeys; i++)
+			fkdatums[i] = ObjectIdGetDatum(ppEqOp[i]);
+		conppeqopArray = construct_array(fkdatums, foreignNKeys,
+										 OIDOID, sizeof(Oid), true, 'i');
+		for (i = 0; i < foreignNKeys; i++)
+			fkdatums[i] = ObjectIdGetDatum(ffEqOp[i]);
+		conffeqopArray = construct_array(fkdatums, foreignNKeys,
+										 OIDOID, sizeof(Oid), true, 'i');
 	}
 	else
+	{
 		confkeyArray = NULL;
+		conpfeqopArray = NULL;
+		conppeqopArray = NULL;
+		conffeqopArray = NULL;
+	}
 
 	/* initialize nulls and values */
 	for (i = 0; i < Natts_pg_constraint; i++)
@@ -132,6 +154,21 @@ CreateConstraintEntry(const char *constraintName,
 	else
 		nulls[Anum_pg_constraint_confkey - 1] = 'n';
 
+	if (conpfeqopArray)
+		values[Anum_pg_constraint_conpfeqop - 1] = PointerGetDatum(conpfeqopArray);
+	else
+		nulls[Anum_pg_constraint_conpfeqop - 1] = 'n';
+
+	if (conppeqopArray)
+		values[Anum_pg_constraint_conppeqop - 1] = PointerGetDatum(conppeqopArray);
+	else
+		nulls[Anum_pg_constraint_conppeqop - 1] = 'n';
+
+	if (conffeqopArray)
+		values[Anum_pg_constraint_conffeqop - 1] = PointerGetDatum(conffeqopArray);
+	else
+		nulls[Anum_pg_constraint_conffeqop - 1] = 'n';
+
 	/*
 	 * initialize the binary form of the check constraint.
 	 */
@@ -246,6 +283,36 @@ CreateConstraintEntry(const char *constraintName,
 		recordDependencyOn(&conobject, &relobject, DEPENDENCY_NORMAL);
 	}
 
+	if (foreignNKeys > 0)
+	{
+		/*
+		 * Register normal dependencies on the equality operators that
+		 * support a foreign-key constraint.  If the PK and FK types
+		 * are the same then all three operators for a column are the
+		 * same; otherwise they are different.
+		 */
+		ObjectAddress oprobject;
+
+		oprobject.classId = OperatorRelationId;
+		oprobject.objectSubId = 0;
+
+		for (i = 0; i < foreignNKeys; i++)
+		{
+			oprobject.objectId = pfEqOp[i];
+			recordDependencyOn(&conobject, &oprobject, DEPENDENCY_NORMAL);
+			if (ppEqOp[i] != pfEqOp[i])
+			{
+				oprobject.objectId = ppEqOp[i];
+				recordDependencyOn(&conobject, &oprobject, DEPENDENCY_NORMAL);
+			}
+			if (ffEqOp[i] != pfEqOp[i])
+			{
+				oprobject.objectId = ffEqOp[i];
+				recordDependencyOn(&conobject, &oprobject, DEPENDENCY_NORMAL);
+			}
+		}
+	}
+
 	if (conExpr != NULL)
 	{
 		/*
@@ -419,24 +486,16 @@ void
 RemoveConstraintById(Oid conId)
 {
 	Relation	conDesc;
-	ScanKeyData skey[1];
-	SysScanDesc conscan;
 	HeapTuple	tup;
 	Form_pg_constraint con;
 
 	conDesc = heap_open(ConstraintRelationId, RowExclusiveLock);
 
-	ScanKeyInit(&skey[0],
-				ObjectIdAttributeNumber,
-				BTEqualStrategyNumber, F_OIDEQ,
-				ObjectIdGetDatum(conId));
-
-	conscan = systable_beginscan(conDesc, ConstraintOidIndexId, true,
-								 SnapshotNow, 1, skey);
-
-	tup = systable_getnext(conscan);
-	if (!HeapTupleIsValid(tup))
-		elog(ERROR, "could not find tuple for constraint %u", conId);
+	tup = SearchSysCache(CONSTROID,
+						 ObjectIdGetDatum(conId),
+						 0, 0, 0);
+	if (!HeapTupleIsValid(tup)) /* should not happen */
+		elog(ERROR, "cache lookup failed for constraint %u", conId);
 	con = (Form_pg_constraint) GETSTRUCT(tup);
 
 	/*
@@ -505,97 +564,10 @@ RemoveConstraintById(Oid conId)
 	simple_heap_delete(conDesc, &tup->t_self);
 
 	/* Clean up */
-	systable_endscan(conscan);
+	ReleaseSysCache(tup);
 	heap_close(conDesc, RowExclusiveLock);
 }
 
-/*
- * GetConstraintNameForTrigger
- *		Get the name of the constraint owning a trigger, if any
- *
- * Returns a palloc'd string, or NULL if no constraint can be found
- */
-char *
-GetConstraintNameForTrigger(Oid triggerId)
-{
-	char	   *result;
-	Oid			constraintId = InvalidOid;
-	Relation	depRel;
-	Relation	conRel;
-	ScanKeyData key[2];
-	SysScanDesc scan;
-	HeapTuple	tup;
-
-	/*
-	 * We must grovel through pg_depend to find the owning constraint. Perhaps
-	 * pg_trigger should have a column for the owning constraint ... but right
-	 * now this is not performance-critical code.
-	 */
-	depRel = heap_open(DependRelationId, AccessShareLock);
-
-	ScanKeyInit(&key[0],
-				Anum_pg_depend_classid,
-				BTEqualStrategyNumber, F_OIDEQ,
-				ObjectIdGetDatum(TriggerRelationId));
-	ScanKeyInit(&key[1],
-				Anum_pg_depend_objid,
-				BTEqualStrategyNumber, F_OIDEQ,
-				ObjectIdGetDatum(triggerId));
-	/* assume we can ignore objsubid for a trigger */
-
-	scan = systable_beginscan(depRel, DependDependerIndexId, true,
-							  SnapshotNow, 2, key);
-
-	while (HeapTupleIsValid(tup = systable_getnext(scan)))
-	{
-		Form_pg_depend foundDep = (Form_pg_depend) GETSTRUCT(tup);
-
-		if (foundDep->refclassid == ConstraintRelationId &&
-			foundDep->deptype == DEPENDENCY_INTERNAL)
-		{
-			constraintId = foundDep->refobjid;
-			break;
-		}
-	}
-
-	systable_endscan(scan);
-
-	heap_close(depRel, AccessShareLock);
-
-	if (!OidIsValid(constraintId))
-		return NULL;			/* no owning constraint found */
-
-	conRel = heap_open(ConstraintRelationId, AccessShareLock);
-
-	ScanKeyInit(&key[0],
-				ObjectIdAttributeNumber,
-				BTEqualStrategyNumber, F_OIDEQ,
-				ObjectIdGetDatum(constraintId));
-
-	scan = systable_beginscan(conRel, ConstraintOidIndexId, true,
-							  SnapshotNow, 1, key);
-
-	tup = systable_getnext(scan);
-
-	if (HeapTupleIsValid(tup))
-	{
-		Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(tup);
-
-		result = pstrdup(NameStr(con->conname));
-	}
-	else
-	{
-		/* This arguably should be an error, but we'll just return NULL */
-		result = NULL;
-	}
-
-	systable_endscan(scan);
-
-	heap_close(conRel, AccessShareLock);
-
-	return result;
-}
-
 /*
  * AlterConstraintNamespaces
  *		Find any constraints belonging to the specified object,
diff --git a/src/backend/catalog/pg_conversion.c b/src/backend/catalog/pg_conversion.c
index c11f337eed8..63779452850 100644
--- a/src/backend/catalog/pg_conversion.c
+++ b/src/backend/catalog/pg_conversion.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/catalog/pg_conversion.c,v 1.34 2007/01/05 22:19:25 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/catalog/pg_conversion.c,v 1.35 2007/02/14 01:58:56 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -146,7 +146,7 @@ ConversionDrop(Oid conversionOid, DropBehavior behavior)
 	HeapTuple	tuple;
 	ObjectAddress object;
 
-	tuple = SearchSysCache(CONOID,
+	tuple = SearchSysCache(CONVOID,
 						   ObjectIdGetDatum(conversionOid),
 						   0, 0, 0);
 	if (!HeapTupleIsValid(tuple))
@@ -313,7 +313,7 @@ pg_convert_using(PG_FUNCTION_ARGS)
 				 errmsg("conversion \"%s\" does not exist",
 						NameListToString(parsed_name))));
 
-	tuple = SearchSysCache(CONOID,
+	tuple = SearchSysCache(CONVOID,
 						   ObjectIdGetDatum(convoid),
 						   0, 0, 0);
 	if (!HeapTupleIsValid(tuple))
diff --git a/src/backend/commands/conversioncmds.c b/src/backend/commands/conversioncmds.c
index 349eadf24f0..37cc0e6f571 100644
--- a/src/backend/commands/conversioncmds.c
+++ b/src/backend/commands/conversioncmds.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/commands/conversioncmds.c,v 1.30 2007/01/05 22:19:25 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/commands/conversioncmds.c,v 1.31 2007/02/14 01:58:56 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -146,7 +146,7 @@ RenameConversion(List *name, const char *newname)
 				 errmsg("conversion \"%s\" does not exist",
 						NameListToString(name))));
 
-	tup = SearchSysCacheCopy(CONOID,
+	tup = SearchSysCacheCopy(CONVOID,
 							 ObjectIdGetDatum(conversionOid),
 							 0, 0, 0);
 	if (!HeapTupleIsValid(tup)) /* should not happen */
@@ -236,7 +236,7 @@ AlterConversionOwner_internal(Relation rel, Oid conversionOid, Oid newOwnerId)
 
 	Assert(RelationGetRelid(rel) == ConversionRelationId);
 
-	tup = SearchSysCacheCopy(CONOID,
+	tup = SearchSysCacheCopy(CONVOID,
 							 ObjectIdGetDatum(conversionOid),
 							 0, 0, 0);
 	if (!HeapTupleIsValid(tup)) /* should not happen */
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index ae9839641e5..9ea32d7707a 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1994-5, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/commands/explain.c,v 1.153 2007/01/05 22:19:26 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/commands/explain.c,v 1.154 2007/02/14 01:58:56 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -327,8 +327,8 @@ ExplainOnePlan(QueryDesc *queryDesc, ExplainStmt *stmt,
 				if (instr->ntuples == 0)
 					continue;
 
-				if (trig->tgisconstraint &&
-				(conname = GetConstraintNameForTrigger(trig->tgoid)) != NULL)
+				if (OidIsValid(trig->tgconstraint) &&
+					(conname = get_constraint_name(trig->tgconstraint)) != NULL)
 				{
 					appendStringInfo(&buf, "Trigger for constraint %s",
 									 conname);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 001bc571548..cea8ddeabf9 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.213 2007/02/02 00:07:02 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.214 2007/02/14 01:58:56 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -28,6 +28,7 @@
 #include "catalog/pg_depend.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
+#include "catalog/pg_opclass.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
 #include "catalog/toasting.h"
@@ -139,6 +140,7 @@ typedef struct NewConstraint
 	char	   *name;			/* Constraint name, or NULL if none */
 	ConstrType	contype;		/* CHECK or FOREIGN */
 	Oid			refrelid;		/* PK rel, if FOREIGN */
+	Oid			conid;			/* OID of pg_constraint entry, if FOREIGN */
 	Node	   *qual;			/* Check expr or FkConstraint struct */
 	List	   *qualstate;		/* Execution state for CHECK */
 } NewConstraint;
@@ -186,10 +188,9 @@ static Oid transformFkeyCheckAttrs(Relation pkrel,
 						int numattrs, int16 *attnums,
 						Oid *opclasses);
 static void validateForeignKeyConstraint(FkConstraint *fkconstraint,
-							 Relation rel, Relation pkrel);
+							 Relation rel, Relation pkrel, Oid constraintOid);
 static void createForeignKeyTriggers(Relation rel, FkConstraint *fkconstraint,
-						 Oid constrOid);
-static char *fkMatchTypeToString(char match_type);
+						 Oid constraintOid);
 static void ATController(Relation rel, List *cmds, bool recurse);
 static void ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
 		  bool recurse, bool recursing);
@@ -256,11 +257,6 @@ static void ATExecEnableDisableTrigger(Relation rel, char *trigname,
 static void ATExecAddInherit(Relation rel, RangeVar *parent);
 static void ATExecDropInherit(Relation rel, RangeVar *parent);
 static void copy_relation_data(Relation rel, SMgrRelation dst);
-static void update_ri_trigger_args(Oid relid,
-					   const char *oldname,
-					   const char *newname,
-					   bool fk_scan,
-					   bool update_relname);
 
 
 /* ----------------------------------------------------------------
@@ -1615,21 +1611,6 @@ renameatt(Oid myrelid,
 
 	heap_close(attrelation, RowExclusiveLock);
 
-	/*
-	 * Update att name in any RI triggers associated with the relation.
-	 */
-	if (targetrelation->rd_rel->reltriggers > 0)
-	{
-		/* update tgargs column reference where att is primary key */
-		update_ri_trigger_args(RelationGetRelid(targetrelation),
-							   oldattname, newattname,
-							   false, false);
-		/* update tgargs column reference where att is foreign key */
-		update_ri_trigger_args(RelationGetRelid(targetrelation),
-							   oldattname, newattname,
-							   true, false);
-	}
-
 	relation_close(targetrelation, NoLock);		/* close rel but keep lock */
 }
 
@@ -1708,226 +1689,12 @@ renamerel(Oid myrelid, const char *newrelname)
 	if (relkind != RELKIND_INDEX)
 		TypeRename(oldrelname, namespaceId, newrelname);
 
-	/*
-	 * Update rel name in any RI triggers associated with the relation.
-	 */
-	if (relhastriggers)
-	{
-		/* update tgargs where relname is primary key */
-		update_ri_trigger_args(myrelid,
-							   oldrelname,
-							   newrelname,
-							   false, true);
-		/* update tgargs where relname is foreign key */
-		update_ri_trigger_args(myrelid,
-							   oldrelname,
-							   newrelname,
-							   true, true);
-	}
-
 	/*
 	 * Close rel, but keep exclusive lock!
 	 */
 	relation_close(targetrelation, NoLock);
 }
 
-/*
- * Scan pg_trigger for RI triggers that are on the specified relation
- * (if fk_scan is false) or have it as the tgconstrrel (if fk_scan
- * is true).  Update RI trigger args fields matching oldname to contain
- * newname instead.  If update_relname is true, examine the relname
- * fields; otherwise examine the attname fields.
- */
-static void
-update_ri_trigger_args(Oid relid,
-					   const char *oldname,
-					   const char *newname,
-					   bool fk_scan,
-					   bool update_relname)
-{
-	Relation	tgrel;
-	ScanKeyData skey[1];
-	SysScanDesc trigscan;
-	HeapTuple	tuple;
-	Datum		values[Natts_pg_trigger];
-	char		nulls[Natts_pg_trigger];
-	char		replaces[Natts_pg_trigger];
-
-	tgrel = heap_open(TriggerRelationId, RowExclusiveLock);
-	if (fk_scan)
-	{
-		ScanKeyInit(&skey[0],
-					Anum_pg_trigger_tgconstrrelid,
-					BTEqualStrategyNumber, F_OIDEQ,
-					ObjectIdGetDatum(relid));
-		trigscan = systable_beginscan(tgrel, TriggerConstrRelidIndexId,
-									  true, SnapshotNow,
-									  1, skey);
-	}
-	else
-	{
-		ScanKeyInit(&skey[0],
-					Anum_pg_trigger_tgrelid,
-					BTEqualStrategyNumber, F_OIDEQ,
-					ObjectIdGetDatum(relid));
-		trigscan = systable_beginscan(tgrel, TriggerRelidNameIndexId,
-									  true, SnapshotNow,
-									  1, skey);
-	}
-
-	while ((tuple = systable_getnext(trigscan)) != NULL)
-	{
-		Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple);
-		bytea	   *val;
-		bytea	   *newtgargs;
-		bool		isnull;
-		int			tg_type;
-		bool		examine_pk;
-		bool		changed;
-		int			tgnargs;
-		int			i;
-		int			newlen;
-		const char *arga[RI_MAX_ARGUMENTS];
-		const char *argp;
-
-		tg_type = RI_FKey_trigger_type(pg_trigger->tgfoid);
-		if (tg_type == RI_TRIGGER_NONE)
-		{
-			/* Not an RI trigger, forget it */
-			continue;
-		}
-
-		/*
-		 * It is an RI trigger, so parse the tgargs bytea.
-		 *
-		 * NB: we assume the field will never be compressed or moved out of
-		 * line; so does trigger.c ...
-		 */
-		tgnargs = pg_trigger->tgnargs;
-		val = DatumGetByteaP(fastgetattr(tuple,
-										Anum_pg_trigger_tgargs,
-										tgrel->rd_att, &isnull));
-		if (isnull || tgnargs < RI_FIRST_ATTNAME_ARGNO ||
-			tgnargs > RI_MAX_ARGUMENTS)
-		{
-			/* This probably shouldn't happen, but ignore busted triggers */
-			continue;
-		}
-		argp = (const char *) VARDATA(val);
-		for (i = 0; i < tgnargs; i++)
-		{
-			arga[i] = argp;
-			argp += strlen(argp) + 1;
-		}
-
-		/*
-		 * Figure out which item(s) to look at.  If the trigger is primary-key
-		 * type and attached to my rel, I should look at the PK fields; if it
-		 * is foreign-key type and attached to my rel, I should look at the FK
-		 * fields.	But the opposite rule holds when examining triggers found
-		 * by tgconstrrel search.
-		 */
-		examine_pk = (tg_type == RI_TRIGGER_PK) == (!fk_scan);
-
-		changed = false;
-		if (update_relname)
-		{
-			/* Change the relname if needed */
-			i = examine_pk ? RI_PK_RELNAME_ARGNO : RI_FK_RELNAME_ARGNO;
-			if (strcmp(arga[i], oldname) == 0)
-			{
-				arga[i] = newname;
-				changed = true;
-			}
-		}
-		else
-		{
-			/* Change attname(s) if needed */
-			i = examine_pk ? RI_FIRST_ATTNAME_ARGNO + RI_KEYPAIR_PK_IDX :
-				RI_FIRST_ATTNAME_ARGNO + RI_KEYPAIR_FK_IDX;
-			for (; i < tgnargs; i += 2)
-			{
-				if (strcmp(arga[i], oldname) == 0)
-				{
-					arga[i] = newname;
-					changed = true;
-				}
-			}
-		}
-
-		if (!changed)
-		{
-			/* Don't need to update this tuple */
-			continue;
-		}
-
-		/*
-		 * Construct modified tgargs bytea.
-		 */
-		newlen = VARHDRSZ;
-		for (i = 0; i < tgnargs; i++)
-			newlen += strlen(arga[i]) + 1;
-		newtgargs = (bytea *) palloc(newlen);
-		VARATT_SIZEP(newtgargs) = newlen;
-		newlen = VARHDRSZ;
-		for (i = 0; i < tgnargs; i++)
-		{
-			strcpy(((char *) newtgargs) + newlen, arga[i]);
-			newlen += strlen(arga[i]) + 1;
-		}
-
-		/*
-		 * Build modified tuple.
-		 */
-		for (i = 0; i < Natts_pg_trigger; i++)
-		{
-			values[i] = (Datum) 0;
-			replaces[i] = ' ';
-			nulls[i] = ' ';
-		}
-		values[Anum_pg_trigger_tgargs - 1] = PointerGetDatum(newtgargs);
-		replaces[Anum_pg_trigger_tgargs - 1] = 'r';
-
-		tuple = heap_modifytuple(tuple, RelationGetDescr(tgrel), values, nulls, replaces);
-
-		/*
-		 * Update pg_trigger and its indexes
-		 */
-		simple_heap_update(tgrel, &tuple->t_self, tuple);
-
-		CatalogUpdateIndexes(tgrel, tuple);
-
-		/*
-		 * Invalidate trigger's relation's relcache entry so that other
-		 * backends (and this one too!) are sent SI message to make them
-		 * rebuild relcache entries.  (Ideally this should happen
-		 * automatically...)
-		 *
-		 * We can skip this for triggers on relid itself, since that relcache
-		 * flush will happen anyway due to the table or column rename.	We
-		 * just need to catch the far ends of RI relationships.
-		 */
-		pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple);
-		if (pg_trigger->tgrelid != relid)
-			CacheInvalidateRelcacheByRelid(pg_trigger->tgrelid);
-
-		/* free up our scratch memory */
-		pfree(newtgargs);
-		heap_freetuple(tuple);
-	}
-
-	systable_endscan(trigscan);
-
-	heap_close(tgrel, RowExclusiveLock);
-
-	/*
-	 * Increment cmd counter to make updates visible; this is needed in case
-	 * the same tuple has to be updated again by next pass (can happen in case
-	 * of a self-referential FK relationship).
-	 */
-	CommandCounterIncrement();
-}
-
 /*
  * AlterTable
  *		Execute ALTER TABLE, which can be a list of subcommands
@@ -2552,7 +2319,8 @@ ATRewriteTables(List **wqueue)
 
 				refrel = heap_open(con->refrelid, RowShareLock);
 
-				validateForeignKeyConstraint(fkconstraint, rel, refrel);
+				validateForeignKeyConstraint(fkconstraint, rel, refrel,
+											 con->conid);
 
 				heap_close(refrel, NoLock);
 			}
@@ -4061,6 +3829,9 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
 	Oid			pktypoid[INDEX_MAX_KEYS];
 	Oid			fktypoid[INDEX_MAX_KEYS];
 	Oid			opclasses[INDEX_MAX_KEYS];
+	Oid			pfeqoperators[INDEX_MAX_KEYS];
+	Oid			ppeqoperators[INDEX_MAX_KEYS];
+	Oid			ffeqoperators[INDEX_MAX_KEYS];
 	int			i;
 	int			numfks,
 				numpks;
@@ -4138,6 +3909,9 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
 	MemSet(pktypoid, 0, sizeof(pktypoid));
 	MemSet(fktypoid, 0, sizeof(fktypoid));
 	MemSet(opclasses, 0, sizeof(opclasses));
+	MemSet(pfeqoperators, 0, sizeof(pfeqoperators));
+	MemSet(ppeqoperators, 0, sizeof(ppeqoperators));
+	MemSet(ffeqoperators, 0, sizeof(ffeqoperators));
 
 	numfks = transformColumnNameList(RelationGetRelid(rel),
 									 fkconstraint->fk_attrs,
@@ -4166,7 +3940,15 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
 										   opclasses);
 	}
 
-	/* Be sure referencing and referenced column types are comparable */
+	/*
+	 * Look up the equality operators to use in the constraint.
+	 *
+	 * Note that we look for operator(s) with the PK type on the left; when
+	 * the types are different this is the right choice because the PK index
+	 * will need operators with the indexkey on the left.  Also, we take the
+	 * PK type as being the declared input type of the opclass, which might be
+	 * binary-compatible but not identical to the PK column type.
+	 */
 	if (numfks != numpks)
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_FOREIGN_KEY),
@@ -4174,24 +3956,71 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
 
 	for (i = 0; i < numpks; i++)
 	{
-		/*
-		 * pktypoid[i] is the primary key table's i'th key's type fktypoid[i]
-		 * is the foreign key table's i'th key's type
-		 *
-		 * Note that we look for an operator with the PK type on the left;
-		 * when the types are different this is critical because the PK index
-		 * will need operators with the indexkey on the left. (Ordinarily both
-		 * commutator operators will exist if either does, but we won't get
-		 * the right answer from the test below on opclass membership unless
-		 * we select the proper operator.)
-		 */
-		Operator	o = oper(NULL, list_make1(makeString("=")),
-							 pktypoid[i], fktypoid[i],
-							 true, -1);
+		HeapTuple	cla_ht;
+		Form_pg_opclass cla_tup;
+		Oid			amid;
+		Oid			opfamily;
+		Oid			pktype;
+		Oid			fktype;
+		Oid			pfeqop;
+		Oid			ppeqop;
+		Oid			ffeqop;
+		int16		eqstrategy;
 
-		if (o == NULL)
+		/* We need several fields out of the pg_opclass entry */
+		cla_ht = SearchSysCache(CLAOID,
+								ObjectIdGetDatum(opclasses[i]),
+								0, 0, 0);
+		if (!HeapTupleIsValid(cla_ht))
+			elog(ERROR, "cache lookup failed for opclass %u", opclasses[i]);
+		cla_tup = (Form_pg_opclass) GETSTRUCT(cla_ht);
+		amid = cla_tup->opcmethod;
+		opfamily = cla_tup->opcfamily;
+		pktype = cla_tup->opcintype;
+		ReleaseSysCache(cla_ht);
+
+		/*
+		 * Check it's a btree; currently this can never fail since no other
+		 * index AMs support unique indexes.  If we ever did have other
+		 * types of unique indexes, we'd need a way to determine which
+		 * operator strategy number is equality.  (Is it reasonable to
+		 * insist that every such index AM use btree's number for equality?)
+		 */
+		if (amid != BTREE_AM_OID)
+			elog(ERROR, "only b-tree indexes are supported for foreign keys");
+		eqstrategy = BTEqualStrategyNumber;
+
+		/*
+		 * There had better be a PK = PK operator for the index.
+		 */
+		ppeqop = get_opfamily_member(opfamily, pktype, pktype, eqstrategy);
+
+		if (!OidIsValid(ppeqop))
+			elog(ERROR, "missing operator %d(%u,%u) in opfamily %u",
+				 eqstrategy, pktype, pktype, opfamily);
+
+		/*
+		 * Are there equality operators that take exactly the FK type?
+		 * Assume we should look through any domain here.
+		 */
+		fktype = getBaseType(fktypoid[i]);
+
+		pfeqop = get_opfamily_member(opfamily, pktype, fktype, eqstrategy);
+		ffeqop = get_opfamily_member(opfamily, fktype, fktype, eqstrategy);
+
+		if (!(OidIsValid(pfeqop) && OidIsValid(ffeqop)))
+		{
+			/*
+			 * Otherwise, look for an implicit cast from the FK type to
+			 * the PK type, and if found, use the PK type's equality operator.
+			 */
+			if (can_coerce_type(1, &fktype, &pktype, COERCION_IMPLICIT))
+				pfeqop = ffeqop = ppeqop;
+		}
+
+		if (!(OidIsValid(pfeqop) && OidIsValid(ffeqop)))
 			ereport(ERROR,
-					(errcode(ERRCODE_UNDEFINED_FUNCTION),
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
 					 errmsg("foreign key constraint \"%s\" "
 							"cannot be implemented",
 							fkconstraint->constr_name),
@@ -4202,41 +4031,9 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
 							   format_type_be(fktypoid[i]),
 							   format_type_be(pktypoid[i]))));
 
-		/*
-		 * Check that the found operator is compatible with the PK index, and
-		 * generate a warning if not, since otherwise costly seqscans will be
-		 * incurred to check FK validity.
-		 */
-		if (!op_in_opfamily(oprid(o), get_opclass_family(opclasses[i])))
-			ereport(WARNING,
-					(errmsg("foreign key constraint \"%s\" "
-							"will require costly sequential scans",
-							fkconstraint->constr_name),
-					 errdetail("Key columns \"%s\" and \"%s\" "
-							   "are of different types: %s and %s.",
-							   strVal(list_nth(fkconstraint->fk_attrs, i)),
-							   strVal(list_nth(fkconstraint->pk_attrs, i)),
-							   format_type_be(fktypoid[i]),
-							   format_type_be(pktypoid[i]))));
-
-		ReleaseSysCache(o);
-	}
-
-	/*
-	 * Tell Phase 3 to check that the constraint is satisfied by existing rows
-	 * (we can skip this during table creation).
-	 */
-	if (!fkconstraint->skip_validation)
-	{
-		NewConstraint *newcon;
-
-		newcon = (NewConstraint *) palloc0(sizeof(NewConstraint));
-		newcon->name = fkconstraint->constr_name;
-		newcon->contype = CONSTR_FOREIGN;
-		newcon->refrelid = RelationGetRelid(pkrel);
-		newcon->qual = (Node *) fkconstraint;
-
-		tab->constraints = lappend(tab->constraints, newcon);
+		pfeqoperators[i] = pfeqop;
+		ppeqoperators[i] = ppeqop;
+		ffeqoperators[i] = ffeqop;
 	}
 
 	/*
@@ -4254,6 +4051,9 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
 														 * constraint */
 									  RelationGetRelid(pkrel),
 									  pkattnum,
+									  pfeqoperators,
+									  ppeqoperators,
+									  ffeqoperators,
 									  numpks,
 									  fkconstraint->fk_upd_action,
 									  fkconstraint->fk_del_action,
@@ -4268,6 +4068,24 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
 	 */
 	createForeignKeyTriggers(rel, fkconstraint, constrOid);
 
+	/*
+	 * Tell Phase 3 to check that the constraint is satisfied by existing rows
+	 * (we can skip this during table creation).
+	 */
+	if (!fkconstraint->skip_validation)
+	{
+		NewConstraint *newcon;
+
+		newcon = (NewConstraint *) palloc0(sizeof(NewConstraint));
+		newcon->name = fkconstraint->constr_name;
+		newcon->contype = CONSTR_FOREIGN;
+		newcon->refrelid = RelationGetRelid(pkrel);
+		newcon->conid = constrOid;
+		newcon->qual = (Node *) fkconstraint;
+
+		tab->constraints = lappend(tab->constraints, newcon);
+	}
+
 	/*
 	 * Close pk table, but keep lock until we've committed.
 	 */
@@ -4526,25 +4344,15 @@ transformFkeyCheckAttrs(Relation pkrel,
 static void
 validateForeignKeyConstraint(FkConstraint *fkconstraint,
 							 Relation rel,
-							 Relation pkrel)
+							 Relation pkrel,
+							 Oid constraintOid)
 {
 	HeapScanDesc scan;
 	HeapTuple	tuple;
 	Trigger		trig;
-	ListCell   *list;
-	int			count;
 
 	/*
-	 * See if we can do it with a single LEFT JOIN query.  A FALSE result
-	 * indicates we must proceed with the fire-the-trigger method.
-	 */
-	if (RI_Initial_Check(fkconstraint, rel, pkrel))
-		return;
-
-	/*
-	 * Scan through each tuple, calling RI_FKey_check_ins (insert trigger) as
-	 * if that tuple had just been inserted.  If any of those fail, it should
-	 * ereport(ERROR) and that's that.
+	 * Build a trigger call structure; we'll need it either way.
 	 */
 	MemSet(&trig, 0, sizeof(trig));
 	trig.tgoid = InvalidOid;
@@ -4552,35 +4360,23 @@ validateForeignKeyConstraint(FkConstraint *fkconstraint,
 	trig.tgenabled = TRUE;
 	trig.tgisconstraint = TRUE;
 	trig.tgconstrrelid = RelationGetRelid(pkrel);
+	trig.tgconstraint = constraintOid;
 	trig.tgdeferrable = FALSE;
 	trig.tginitdeferred = FALSE;
+	/* we needn't fill in tgargs */
 
-	trig.tgargs = (char **) palloc(sizeof(char *) *
-								   (4 + list_length(fkconstraint->fk_attrs)
-									+ list_length(fkconstraint->pk_attrs)));
-
-	trig.tgargs[0] = trig.tgname;
-	trig.tgargs[1] = RelationGetRelationName(rel);
-	trig.tgargs[2] = RelationGetRelationName(pkrel);
-	trig.tgargs[3] = fkMatchTypeToString(fkconstraint->fk_matchtype);
-	count = 4;
-	foreach(list, fkconstraint->fk_attrs)
-	{
-		char	   *fk_at = strVal(lfirst(list));
-
-		trig.tgargs[count] = fk_at;
-		count += 2;
-	}
-	count = 5;
-	foreach(list, fkconstraint->pk_attrs)
-	{
-		char	   *pk_at = strVal(lfirst(list));
-
-		trig.tgargs[count] = pk_at;
-		count += 2;
-	}
-	trig.tgnargs = count - 1;
+	/*
+	 * See if we can do it with a single LEFT JOIN query.  A FALSE result
+	 * indicates we must proceed with the fire-the-trigger method.
+	 */
+	if (RI_Initial_Check(&trig, rel, pkrel))
+		return;
 
+	/*
+	 * Scan through each tuple, calling RI_FKey_check_ins (insert trigger) as
+	 * if that tuple had just been inserted.  If any of those fail, it should
+	 * ereport(ERROR) and that's that.
+	 */
 	scan = heap_beginscan(rel, SnapshotNow, 0, NULL);
 
 	while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
@@ -4613,18 +4409,13 @@ validateForeignKeyConstraint(FkConstraint *fkconstraint,
 	}
 
 	heap_endscan(scan);
-
-	pfree(trig.tgargs);
 }
 
 static void
 CreateFKCheckTrigger(RangeVar *myRel, FkConstraint *fkconstraint,
-					 ObjectAddress *constrobj, ObjectAddress *trigobj,
-					 bool on_insert)
+					 Oid constraintOid, bool on_insert)
 {
 	CreateTrigStmt *fk_trigger;
-	ListCell   *fk_attr;
-	ListCell   *pk_attr;
 
 	fk_trigger = makeNode(CreateTrigStmt);
 	fk_trigger->trigname = fkconstraint->constr_name;
@@ -4649,32 +4440,9 @@ CreateFKCheckTrigger(RangeVar *myRel, FkConstraint *fkconstraint,
 	fk_trigger->deferrable = fkconstraint->deferrable;
 	fk_trigger->initdeferred = fkconstraint->initdeferred;
 	fk_trigger->constrrel = fkconstraint->pktable;
-
 	fk_trigger->args = NIL;
-	fk_trigger->args = lappend(fk_trigger->args,
-							   makeString(fkconstraint->constr_name));
-	fk_trigger->args = lappend(fk_trigger->args,
-							   makeString(myRel->relname));
-	fk_trigger->args = lappend(fk_trigger->args,
-							   makeString(fkconstraint->pktable->relname));
-	fk_trigger->args = lappend(fk_trigger->args,
-				makeString(fkMatchTypeToString(fkconstraint->fk_matchtype)));
-	if (list_length(fkconstraint->fk_attrs) != list_length(fkconstraint->pk_attrs))
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_FOREIGN_KEY),
-				 errmsg("number of referencing and referenced columns for foreign key disagree")));
 
-	forboth(fk_attr, fkconstraint->fk_attrs,
-			pk_attr, fkconstraint->pk_attrs)
-	{
-		fk_trigger->args = lappend(fk_trigger->args, lfirst(fk_attr));
-		fk_trigger->args = lappend(fk_trigger->args, lfirst(pk_attr));
-	}
-
-	trigobj->objectId = CreateTrigger(fk_trigger, true);
-
-	/* Register dependency from trigger to constraint */
-	recordDependencyOn(trigobj, constrobj, DEPENDENCY_INTERNAL);
+	(void) CreateTrigger(fk_trigger, constraintOid);
 
 	/* Make changes-so-far visible */
 	CommandCounterIncrement();
@@ -4685,14 +4453,10 @@ CreateFKCheckTrigger(RangeVar *myRel, FkConstraint *fkconstraint,
  */
 static void
 createForeignKeyTriggers(Relation rel, FkConstraint *fkconstraint,
-						 Oid constrOid)
+						 Oid constraintOid)
 {
 	RangeVar   *myRel;
 	CreateTrigStmt *fk_trigger;
-	ListCell   *fk_attr;
-	ListCell   *pk_attr;
-	ObjectAddress trigobj,
-				constrobj;
 
 	/*
 	 * Reconstruct a RangeVar for my relation (not passed in, unfortunately).
@@ -4700,15 +4464,6 @@ createForeignKeyTriggers(Relation rel, FkConstraint *fkconstraint,
 	myRel = makeRangeVar(get_namespace_name(RelationGetNamespace(rel)),
 						 pstrdup(RelationGetRelationName(rel)));
 
-	/*
-	 * Preset objectAddress fields
-	 */
-	constrobj.classId = ConstraintRelationId;
-	constrobj.objectId = constrOid;
-	constrobj.objectSubId = 0;
-	trigobj.classId = TriggerRelationId;
-	trigobj.objectSubId = 0;
-
 	/* Make changes-so-far visible */
 	CommandCounterIncrement();
 
@@ -4716,8 +4471,8 @@ createForeignKeyTriggers(Relation rel, FkConstraint *fkconstraint,
 	 * Build and execute a CREATE CONSTRAINT TRIGGER statement for the CHECK
 	 * action for both INSERTs and UPDATEs on the referencing table.
 	 */
-	CreateFKCheckTrigger(myRel, fkconstraint, &constrobj, &trigobj, true);
-	CreateFKCheckTrigger(myRel, fkconstraint, &constrobj, &trigobj, false);
+	CreateFKCheckTrigger(myRel, fkconstraint, constraintOid, true);
+	CreateFKCheckTrigger(myRel, fkconstraint, constraintOid, false);
 
 	/*
 	 * Build and execute a CREATE CONSTRAINT TRIGGER statement for the ON
@@ -4765,27 +4520,9 @@ createForeignKeyTriggers(Relation rel, FkConstraint *fkconstraint,
 				 (int) fkconstraint->fk_del_action);
 			break;
 	}
-
 	fk_trigger->args = NIL;
-	fk_trigger->args = lappend(fk_trigger->args,
-							   makeString(fkconstraint->constr_name));
-	fk_trigger->args = lappend(fk_trigger->args,
-							   makeString(myRel->relname));
-	fk_trigger->args = lappend(fk_trigger->args,
-							   makeString(fkconstraint->pktable->relname));
-	fk_trigger->args = lappend(fk_trigger->args,
-				makeString(fkMatchTypeToString(fkconstraint->fk_matchtype)));
-	forboth(fk_attr, fkconstraint->fk_attrs,
-			pk_attr, fkconstraint->pk_attrs)
-	{
-		fk_trigger->args = lappend(fk_trigger->args, lfirst(fk_attr));
-		fk_trigger->args = lappend(fk_trigger->args, lfirst(pk_attr));
-	}
 
-	trigobj.objectId = CreateTrigger(fk_trigger, true);
-
-	/* Register dependency from trigger to constraint */
-	recordDependencyOn(&trigobj, &constrobj, DEPENDENCY_INTERNAL);
+	(void) CreateTrigger(fk_trigger, constraintOid);
 
 	/* Make changes-so-far visible */
 	CommandCounterIncrement();
@@ -4835,49 +4572,9 @@ createForeignKeyTriggers(Relation rel, FkConstraint *fkconstraint,
 				 (int) fkconstraint->fk_upd_action);
 			break;
 	}
-
 	fk_trigger->args = NIL;
-	fk_trigger->args = lappend(fk_trigger->args,
-							   makeString(fkconstraint->constr_name));
-	fk_trigger->args = lappend(fk_trigger->args,
-							   makeString(myRel->relname));
-	fk_trigger->args = lappend(fk_trigger->args,
-							   makeString(fkconstraint->pktable->relname));
-	fk_trigger->args = lappend(fk_trigger->args,
-				makeString(fkMatchTypeToString(fkconstraint->fk_matchtype)));
-	forboth(fk_attr, fkconstraint->fk_attrs,
-			pk_attr, fkconstraint->pk_attrs)
-	{
-		fk_trigger->args = lappend(fk_trigger->args, lfirst(fk_attr));
-		fk_trigger->args = lappend(fk_trigger->args, lfirst(pk_attr));
-	}
 
-	trigobj.objectId = CreateTrigger(fk_trigger, true);
-
-	/* Register dependency from trigger to constraint */
-	recordDependencyOn(&trigobj, &constrobj, DEPENDENCY_INTERNAL);
-}
-
-/*
- * fkMatchTypeToString -
- *	  convert FKCONSTR_MATCH_xxx code to string to use in trigger args
- */
-static char *
-fkMatchTypeToString(char match_type)
-{
-	switch (match_type)
-	{
-		case FKCONSTR_MATCH_FULL:
-			return pstrdup("FULL");
-		case FKCONSTR_MATCH_PARTIAL:
-			return pstrdup("PARTIAL");
-		case FKCONSTR_MATCH_UNSPECIFIED:
-			return pstrdup("UNSPECIFIED");
-		default:
-			elog(ERROR, "unrecognized match type: %d",
-				 (int) match_type);
-	}
-	return NULL;				/* can't get here */
+	(void) CreateTrigger(fk_trigger, constraintOid);
 }
 
 /*
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index cc7dfc895b8..c08525c2e04 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/commands/trigger.c,v 1.212 2007/01/25 04:17:46 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/commands/trigger.c,v 1.213 2007/02/14 01:58:56 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -19,6 +19,7 @@
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/indexing.h"
+#include "catalog/pg_constraint.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -57,13 +58,12 @@ static void AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event,
 /*
  * Create a trigger.  Returns the OID of the created trigger.
  *
- * forConstraint, if true, says that this trigger is being created to
- * implement a constraint.	The caller will then be expected to make
- * a pg_depend entry linking the trigger to that constraint (and thereby
- * to the owning relation(s)).
+ * constraintOid, if nonzero, says that this trigger is being created to
+ * implement that constraint.  A suitable pg_depend entry will be made
+ * to link the trigger to that constraint.
  */
 Oid
-CreateTrigger(CreateTrigStmt *stmt, bool forConstraint)
+CreateTrigger(CreateTrigStmt *stmt, Oid constraintOid)
 {
 	int16		tgtype;
 	int2vector *tgattr;
@@ -91,51 +91,6 @@ CreateTrigger(CreateTrigStmt *stmt, bool forConstraint)
 
 	rel = heap_openrv(stmt->relation, AccessExclusiveLock);
 
-	if (stmt->constrrel != NULL)
-		constrrelid = RangeVarGetRelid(stmt->constrrel, false);
-	else if (stmt->isconstraint)
-	{
-		/*
-		 * If this trigger is a constraint (and a foreign key one) then we
-		 * really need a constrrelid.  Since we don't have one, we'll try to
-		 * generate one from the argument information.
-		 *
-		 * This is really just a workaround for a long-ago pg_dump bug that
-		 * omitted the FROM clause in dumped CREATE CONSTRAINT TRIGGER
-		 * commands.  We don't want to bomb out completely here if we can't
-		 * determine the correct relation, because that would prevent loading
-		 * the dump file.  Instead, NOTICE here and ERROR in the trigger.
-		 */
-		bool		needconstrrelid = false;
-		void	   *elem = NULL;
-
-		if (strncmp(strVal(lfirst(list_tail((stmt->funcname)))), "RI_FKey_check_", 14) == 0)
-		{
-			/* A trigger on FK table. */
-			needconstrrelid = true;
-			if (list_length(stmt->args) > RI_PK_RELNAME_ARGNO)
-				elem = list_nth(stmt->args, RI_PK_RELNAME_ARGNO);
-		}
-		else if (strncmp(strVal(lfirst(list_tail((stmt->funcname)))), "RI_FKey_", 8) == 0)
-		{
-			/* A trigger on PK table. */
-			needconstrrelid = true;
-			if (list_length(stmt->args) > RI_FK_RELNAME_ARGNO)
-				elem = list_nth(stmt->args, RI_FK_RELNAME_ARGNO);
-		}
-		if (elem != NULL)
-		{
-			RangeVar   *rel = makeRangeVar(NULL, strVal(elem));
-
-			constrrelid = RangeVarGetRelid(rel, true);
-		}
-		if (needconstrrelid && constrrelid == InvalidOid)
-			ereport(NOTICE,
-					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-					 errmsg("could not determine referenced table for constraint \"%s\"",
-							stmt->trigname)));
-	}
-
 	if (rel->rd_rel->relkind != RELKIND_RELATION)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -152,15 +107,17 @@ CreateTrigger(CreateTrigStmt *stmt, bool forConstraint)
 
 	if (stmt->isconstraint)
 	{
-		/* foreign key constraint trigger */
-
+		/* constraint trigger */
 		aclresult = pg_class_aclcheck(RelationGetRelid(rel), GetUserId(),
 									  ACL_REFERENCES);
 		if (aclresult != ACLCHECK_OK)
 			aclcheck_error(aclresult, ACL_KIND_CLASS,
 						   RelationGetRelationName(rel));
-		if (constrrelid != InvalidOid)
+
+		if (stmt->constrrel != NULL)
 		{
+			constrrelid = RangeVarGetRelid(stmt->constrrel, false);
+
 			aclresult = pg_class_aclcheck(constrrelid, GetUserId(),
 										  ACL_REFERENCES);
 			if (aclresult != ACLCHECK_OK)
@@ -170,7 +127,7 @@ CreateTrigger(CreateTrigStmt *stmt, bool forConstraint)
 	}
 	else
 	{
-		/* real trigger */
+		/* regular trigger */
 		aclresult = pg_class_aclcheck(RelationGetRelid(rel), GetUserId(),
 									  ACL_TRIGGER);
 		if (aclresult != ACLCHECK_OK)
@@ -187,23 +144,31 @@ CreateTrigger(CreateTrigStmt *stmt, bool forConstraint)
 	trigoid = GetNewOid(tgrel);
 
 	/*
-	 * If trigger is an RI constraint, use specified trigger name as
-	 * constraint name and build a unique trigger name instead. This is mainly
-	 * for backwards compatibility with CREATE CONSTRAINT TRIGGER commands.
+	 * If trigger is for an RI constraint, the passed-in name is the
+	 * constraint name; save that and build a unique trigger name to avoid
+	 * collisions with user-selected trigger names.
 	 */
-	if (stmt->isconstraint)
+	if (OidIsValid(constraintOid))
 	{
 		snprintf(constrtrigname, sizeof(constrtrigname),
 				 "RI_ConstraintTrigger_%u", trigoid);
 		trigname = constrtrigname;
 		constrname = stmt->trigname;
 	}
+	else if (stmt->isconstraint)
+	{
+		/* constraint trigger: trigger name is also constraint name */
+		trigname = stmt->trigname;
+		constrname = stmt->trigname;
+	}
 	else
 	{
+		/* regular trigger: use empty constraint name */
 		trigname = stmt->trigname;
 		constrname = "";
 	}
 
+	/* Compute tgtype */
 	TRIGGER_CLEAR_TYPE(tgtype);
 	if (stmt->before)
 		TRIGGER_SETT_BEFORE(tgtype);
@@ -298,7 +263,7 @@ CreateTrigger(CreateTrigStmt *stmt, bool forConstraint)
 	/*
 	 * Build the new pg_trigger tuple.
 	 */
-	MemSet(nulls, ' ', Natts_pg_trigger * sizeof(char));
+	memset(nulls, ' ', Natts_pg_trigger * sizeof(char));
 
 	values[Anum_pg_trigger_tgrelid - 1] = ObjectIdGetDatum(RelationGetRelid(rel));
 	values[Anum_pg_trigger_tgname - 1] = DirectFunctionCall1(namein,
@@ -310,6 +275,7 @@ CreateTrigger(CreateTrigStmt *stmt, bool forConstraint)
 	values[Anum_pg_trigger_tgconstrname - 1] = DirectFunctionCall1(namein,
 												CStringGetDatum(constrname));
 	values[Anum_pg_trigger_tgconstrrelid - 1] = ObjectIdGetDatum(constrrelid);
+	values[Anum_pg_trigger_tgconstraint - 1] = ObjectIdGetDatum(constraintOid);
 	values[Anum_pg_trigger_tgdeferrable - 1] = BoolGetDatum(stmt->deferrable);
 	values[Anum_pg_trigger_tginitdeferred - 1] = BoolGetDatum(stmt->initdeferred);
 
@@ -372,10 +338,6 @@ CreateTrigger(CreateTrigStmt *stmt, bool forConstraint)
 
 	CatalogUpdateIndexes(tgrel, tuple);
 
-	myself.classId = TriggerRelationId;
-	myself.objectId = trigoid;
-	myself.objectSubId = 0;
-
 	heap_freetuple(tuple);
 	heap_close(tgrel, RowExclusiveLock);
 
@@ -412,20 +374,36 @@ CreateTrigger(CreateTrigStmt *stmt, bool forConstraint)
 
 	/*
 	 * Record dependencies for trigger.  Always place a normal dependency on
-	 * the function.  If we are doing this in response to an explicit CREATE
-	 * TRIGGER command, also make trigger be auto-dropped if its relation is
-	 * dropped or if the FK relation is dropped.  (Auto drop is compatible
-	 * with our pre-7.3 behavior.)	If the trigger is being made for a
-	 * constraint, we can skip the relation links; the dependency on the
-	 * constraint will indirectly depend on the relations.
+	 * the function.
 	 */
+	myself.classId = TriggerRelationId;
+	myself.objectId = trigoid;
+	myself.objectSubId = 0;
+
 	referenced.classId = ProcedureRelationId;
 	referenced.objectId = funcoid;
 	referenced.objectSubId = 0;
 	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 
-	if (!forConstraint)
+	if (OidIsValid(constraintOid))
 	{
+		/*
+		 * It's for a constraint, so make it an internal dependency of the
+		 * constraint.  We can skip depending on the relations, as there'll
+		 * be an indirect dependency via the constraint.
+		 */
+		referenced.classId = ConstraintRelationId;
+		referenced.objectId = constraintOid;
+		referenced.objectSubId = 0;
+		recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL);
+	}
+	else
+	{
+		/*
+		 * Regular CREATE TRIGGER, so place dependencies.  We make trigger be
+		 * auto-dropped if its relation is dropped or if the FK relation is
+		 * dropped.  (Auto drop is compatible with our pre-7.3 behavior.)
+		 */
 		referenced.classId = RelationRelationId;
 		referenced.objectId = RelationGetRelid(rel);
 		referenced.objectSubId = 0;
@@ -773,7 +751,7 @@ EnableDisableTrigger(Relation rel, const char *tgname,
 	{
 		Form_pg_trigger oldtrig = (Form_pg_trigger) GETSTRUCT(tuple);
 
-		if (oldtrig->tgisconstraint)
+		if (OidIsValid(oldtrig->tgconstraint))
 		{
 			/* system trigger ... ok to process? */
 			if (skip_system)
@@ -886,6 +864,7 @@ RelationBuildTriggers(Relation relation)
 		build->tgenabled = pg_trigger->tgenabled;
 		build->tgisconstraint = pg_trigger->tgisconstraint;
 		build->tgconstrrelid = pg_trigger->tgconstrrelid;
+		build->tgconstraint = pg_trigger->tgconstraint;
 		build->tgdeferrable = pg_trigger->tgdeferrable;
 		build->tginitdeferred = pg_trigger->tginitdeferred;
 		build->tgnargs = pg_trigger->tgnargs;
@@ -1220,6 +1199,8 @@ equalTriggerDescs(TriggerDesc *trigdesc1, TriggerDesc *trigdesc2)
 				return false;
 			if (trig1->tgconstrrelid != trig2->tgconstrrelid)
 				return false;
+			if (trig1->tgconstraint != trig2->tgconstraint)
+				return false;
 			if (trig1->tgdeferrable != trig2->tgdeferrable)
 				return false;
 			if (trig1->tginitdeferred != trig2->tginitdeferred)
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 4f88e9561cd..99f964db74d 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/commands/typecmds.c,v 1.99 2007/01/05 22:19:26 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/commands/typecmds.c,v 1.100 2007/02/14 01:58:57 tgl Exp $
  *
  * DESCRIPTION
  *	  The "DefineFoo" routines take the parse tree and pick out the
@@ -1966,6 +1966,9 @@ domainAddConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid,
 						  domainOid,	/* domain constraint */
 						  InvalidOid,	/* Foreign key fields */
 						  NULL,
+						  NULL,
+						  NULL,
+						  NULL,
 						  0,
 						  ' ',
 						  ' ',
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 613ef653d19..5cabec2970b 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -10,7 +10,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/tcop/utility.c,v 1.271 2007/01/23 05:07:18 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/tcop/utility.c,v 1.272 2007/02/14 01:58:57 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -973,7 +973,7 @@ ProcessUtility(Node *parsetree,
 			break;
 
 		case T_CreateTrigStmt:
-			CreateTrigger((CreateTrigStmt *) parsetree, false);
+			CreateTrigger((CreateTrigStmt *) parsetree, InvalidOid);
 			break;
 
 		case T_DropPropertyStmt:
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index c21d827d0c7..4ba2bb7a98f 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -17,7 +17,7 @@
  *
  * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
  *
- * $PostgreSQL: pgsql/src/backend/utils/adt/ri_triggers.c,v 1.90 2007/01/05 22:19:42 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/utils/adt/ri_triggers.c,v 1.91 2007/02/14 01:58:57 tgl Exp $
  *
  * ----------
  */
@@ -32,13 +32,17 @@
 
 #include "postgres.h"
 
+#include "catalog/pg_constraint.h"
+#include "catalog/pg_operator.h"
 #include "commands/trigger.h"
 #include "executor/spi_priv.h"
+#include "parser/parse_coerce.h"
+#include "parser/parse_relation.h"
+#include "miscadmin.h"
 #include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/lsyscache.h"
-#include "utils/typcache.h"
-#include "miscadmin.h"
+#include "utils/memutils.h"
 
 
 /* ----------
@@ -46,11 +50,9 @@
  * ----------
  */
 
-#define RI_INIT_QUERYHASHSIZE			128
+#define RI_MAX_NUMKEYS					INDEX_MAX_KEYS
 
-#define RI_MATCH_TYPE_UNSPECIFIED		0
-#define RI_MATCH_TYPE_FULL				1
-#define RI_MATCH_TYPE_PARTIAL			2
+#define RI_INIT_QUERYHASHSIZE			128
 
 #define RI_KEYS_ALL_NULL				0
 #define RI_KEYS_SOME_NULL				1
@@ -72,21 +74,51 @@
 #define MAX_QUOTED_NAME_LEN  (NAMEDATALEN*2+3)
 #define MAX_QUOTED_REL_NAME_LEN  (MAX_QUOTED_NAME_LEN*2)
 
+#define RIAttName(rel, attnum)  NameStr(*attnumAttName(rel, attnum))
+#define RIAttType(rel, attnum)  SPI_gettypeid(RelationGetDescr(rel), attnum)
+
 #define RI_TRIGTYPE_INSERT 1
 #define RI_TRIGTYPE_UPDATE 2
 #define RI_TRIGTYPE_INUP   3
 #define RI_TRIGTYPE_DELETE 4
 
+#define RI_KEYPAIR_FK_IDX	0
+#define RI_KEYPAIR_PK_IDX	1
+
+
+/* ----------
+ * RI_ConstraintInfo
+ *
+ *	Information extracted from an FK pg_constraint entry.
+ * ----------
+ */
+typedef struct RI_ConstraintInfo
+{
+	Oid			constraint_id;	/* OID of pg_constraint entry */
+	NameData	conname;		/* name of the FK constraint */
+	Oid			pk_relid;		/* referenced relation */
+	Oid			fk_relid;		/* referencing relation */
+	char		confupdtype;	/* foreign key's ON UPDATE action */
+	char		confdeltype;	/* foreign key's ON DELETE action */
+	char		confmatchtype;	/* foreign key's match type */
+	int			nkeys;			/* number of key columns */
+	int16		pk_attnums[RI_MAX_NUMKEYS];	/* attnums of referenced cols */
+	int16		fk_attnums[RI_MAX_NUMKEYS];	/* attnums of referencing cols */
+	Oid			pf_eq_oprs[RI_MAX_NUMKEYS];	/* equality operators (PK = FK) */
+	Oid			pp_eq_oprs[RI_MAX_NUMKEYS];	/* equality operators (PK = PK) */
+	Oid			ff_eq_oprs[RI_MAX_NUMKEYS];	/* equality operators (FK = FK) */
+} RI_ConstraintInfo;
+
 
 /* ----------
  * RI_QueryKey
  *
- *	The key identifying a prepared SPI plan in our private hashtable
+ *	The key identifying a prepared SPI plan in our query hashtable
  * ----------
  */
 typedef struct RI_QueryKey
 {
-	int32		constr_type;
+	char		constr_type;
 	Oid			constr_id;
 	int32		constr_queryno;
 	Oid			fk_relid;
@@ -107,11 +139,38 @@ typedef struct RI_QueryHashEntry
 } RI_QueryHashEntry;
 
 
+/* ----------
+ * RI_CompareKey
+ *
+ *	The key identifying an entry showing how to compare two values
+ * ----------
+ */
+typedef struct RI_CompareKey
+{
+	Oid			eq_opr;			/* the equality operator to apply */
+	Oid			typeid;			/* the data type to apply it to */
+} RI_CompareKey;
+
+
+/* ----------
+ * RI_CompareHashEntry
+ * ----------
+ */
+typedef struct RI_CompareHashEntry
+{
+	RI_CompareKey key;
+	bool		valid;				/* successfully initialized? */
+	FmgrInfo	eq_opr_finfo;		/* call info for equality fn */
+	FmgrInfo	cast_func_finfo;	/* in case we must coerce input */
+} RI_CompareHashEntry;
+
+
 /* ----------
  * Local data
  * ----------
  */
 static HTAB *ri_query_cache = NULL;
+static HTAB *ri_compare_cache = NULL;
 
 
 /* ----------
@@ -120,35 +179,41 @@ static HTAB *ri_query_cache = NULL;
  */
 static void quoteOneName(char *buffer, const char *name);
 static void quoteRelationName(char *buffer, Relation rel);
-static int	ri_DetermineMatchType(char *str);
+static void ri_GenerateQual(StringInfo buf,
+							const char *sep,
+							const char *leftop, Oid leftoptype,
+							Oid opoid,
+							const char *rightop, Oid rightoptype);
 static int ri_NullCheck(Relation rel, HeapTuple tup,
 			 RI_QueryKey *key, int pairidx);
-static void ri_BuildQueryKeyFull(RI_QueryKey *key, Oid constr_id,
-					 int32 constr_queryno,
-					 Relation fk_rel, Relation pk_rel,
-					 int argc, char **argv);
-static void ri_BuildQueryKeyPkCheck(RI_QueryKey *key, Oid constr_id,
-						int32 constr_queryno,
-						Relation pk_rel,
-						int argc, char **argv);
+static void ri_BuildQueryKeyFull(RI_QueryKey *key,
+								 const RI_ConstraintInfo *riinfo,
+								 int32 constr_queryno);
+static void ri_BuildQueryKeyPkCheck(RI_QueryKey *key,
+									const RI_ConstraintInfo *riinfo,
+									int32 constr_queryno);
 static bool ri_KeysEqual(Relation rel, HeapTuple oldtup, HeapTuple newtup,
-			 RI_QueryKey *key, int pairidx);
+						 const RI_ConstraintInfo *riinfo, bool rel_is_pk);
 static bool ri_AllKeysUnequal(Relation rel, HeapTuple oldtup, HeapTuple newtup,
-				  RI_QueryKey *key, int pairidx);
-static bool ri_OneKeyEqual(Relation rel, int column, HeapTuple oldtup,
-			   HeapTuple newtup, RI_QueryKey *key, int pairidx);
-static bool ri_AttributesEqual(Oid typeid, Datum oldvalue, Datum newvalue);
+				  const RI_ConstraintInfo *riinfo, bool rel_is_pk);
+static bool ri_OneKeyEqual(Relation rel, int column,
+			   HeapTuple oldtup, HeapTuple newtup,
+			   const RI_ConstraintInfo *riinfo, bool rel_is_pk);
+static bool ri_AttributesEqual(Oid eq_opr, Oid typeid,
+							   Datum oldvalue, Datum newvalue);
 static bool ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel,
 				  HeapTuple old_row,
-				  Oid tgoid, int match_type,
-				  int tgnargs, char **tgargs);
+				  const RI_ConstraintInfo *riinfo);
 
 static void ri_InitHashTables(void);
 static void *ri_FetchPreparedPlan(RI_QueryKey *key);
 static void ri_HashPreparedPlan(RI_QueryKey *key, void *plan);
+static RI_CompareHashEntry *ri_HashCompareOp(Oid eq_opr, Oid typeid);
 
 static void ri_CheckTrigger(FunctionCallInfo fcinfo, const char *funcname,
 				int tgkind);
+static void ri_FetchConstraintInfo(RI_ConstraintInfo *riinfo,
+					   Trigger *trigger, Relation trig_rel, bool rel_is_pk);
 static void *ri_PlanCheck(const char *querystr, int nargs, Oid *argtypes,
 			 RI_QueryKey *qkey, Relation fk_rel, Relation pk_rel,
 			 bool cache_plan);
@@ -176,8 +241,7 @@ static Datum
 RI_FKey_check(PG_FUNCTION_ARGS)
 {
 	TriggerData *trigdata = (TriggerData *) fcinfo->context;
-	int			tgnargs;
-	char	  **tgargs;
+	RI_ConstraintInfo riinfo;
 	Relation	fk_rel;
 	Relation	pk_rel;
 	HeapTuple	new_row;
@@ -186,7 +250,6 @@ RI_FKey_check(PG_FUNCTION_ARGS)
 	RI_QueryKey qkey;
 	void	   *qplan;
 	int			i;
-	int			match_type;
 
 	/*
 	 * Check that this is a valid trigger call on the right time and event.
@@ -196,8 +259,9 @@ RI_FKey_check(PG_FUNCTION_ARGS)
 	/*
 	 * Get arguments.
 	 */
-	tgnargs = trigdata->tg_trigger->tgnargs;
-	tgargs = trigdata->tg_trigger->tgargs;
+	ri_FetchConstraintInfo(&riinfo,
+						   trigdata->tg_trigger, trigdata->tg_relation, false);
+
 	if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
 	{
 		old_row = trigdata->tg_trigtuple;
@@ -237,7 +301,7 @@ RI_FKey_check(PG_FUNCTION_ARGS)
 	 * SELECT FOR SHARE will get on it.
 	 */
 	fk_rel = trigdata->tg_relation;
-	pk_rel = heap_open(trigdata->tg_trigger->tgconstrrelid, RowShareLock);
+	pk_rel = heap_open(riinfo.pk_relid, RowShareLock);
 
 	/* ----------
 	 * SQL3 11.9 <referential constraint definition>
@@ -250,12 +314,9 @@ RI_FKey_check(PG_FUNCTION_ARGS)
 	 *		future enhancements.
 	 * ----------
 	 */
-	if (tgnargs == 4)
+	if (riinfo.nkeys == 0)
 	{
-		ri_BuildQueryKeyFull(&qkey, trigdata->tg_trigger->tgoid,
-							 RI_PLAN_CHECK_LOOKUPPK_NOCOLS,
-							 fk_rel, pk_rel,
-							 tgnargs, tgargs);
+		ri_BuildQueryKeyFull(&qkey, &riinfo, RI_PLAN_CHECK_LOOKUPPK_NOCOLS);
 
 		if (SPI_connect() != SPI_OK_CONNECT)
 			elog(ERROR, "SPI_connect failed");
@@ -271,7 +332,8 @@ RI_FKey_check(PG_FUNCTION_ARGS)
 			 * ----------
 			 */
 			quoteRelationName(pkrelname, pk_rel);
-			snprintf(querystr, sizeof(querystr), "SELECT 1 FROM ONLY %s x FOR SHARE OF x",
+			snprintf(querystr, sizeof(querystr),
+					 "SELECT 1 FROM ONLY %s x FOR SHARE OF x",
 					 pkrelname);
 
 			/* Prepare and save the plan */
@@ -287,7 +349,7 @@ RI_FKey_check(PG_FUNCTION_ARGS)
 						NULL, NULL,
 						false,
 						SPI_OK_SELECT,
-						tgargs[RI_CONSTRAINT_NAME_ARGNO]);
+						NameStr(riinfo.conname));
 
 		if (SPI_finish() != SPI_OK_FINISH)
 			elog(ERROR, "SPI_finish failed");
@@ -295,19 +357,14 @@ RI_FKey_check(PG_FUNCTION_ARGS)
 		heap_close(pk_rel, RowShareLock);
 
 		return PointerGetDatum(NULL);
-
 	}
 
-	match_type = ri_DetermineMatchType(tgargs[RI_MATCH_TYPE_ARGNO]);
-
-	if (match_type == RI_MATCH_TYPE_PARTIAL)
+	if (riinfo.confmatchtype == FKCONSTR_MATCH_PARTIAL)
 		ereport(ERROR,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("MATCH PARTIAL not yet implemented")));
 
-	ri_BuildQueryKeyFull(&qkey, trigdata->tg_trigger->tgoid,
-						 RI_PLAN_CHECK_LOOKUPPK, fk_rel, pk_rel,
-						 tgnargs, tgargs);
+	ri_BuildQueryKeyFull(&qkey, &riinfo, RI_PLAN_CHECK_LOOKUPPK);
 
 	switch (ri_NullCheck(fk_rel, new_row, &qkey, RI_KEYPAIR_FK_IDX))
 	{
@@ -329,9 +386,9 @@ RI_FKey_check(PG_FUNCTION_ARGS)
 			 * This is the only case that differs between the three kinds of
 			 * MATCH.
 			 */
-			switch (match_type)
+			switch (riinfo.confmatchtype)
 			{
-				case RI_MATCH_TYPE_FULL:
+				case FKCONSTR_MATCH_FULL:
 
 					/*
 					 * Not allowed - MATCH FULL says either all or none of the
@@ -341,12 +398,12 @@ RI_FKey_check(PG_FUNCTION_ARGS)
 							(errcode(ERRCODE_FOREIGN_KEY_VIOLATION),
 							 errmsg("insert or update on table \"%s\" violates foreign key constraint \"%s\"",
 							  RelationGetRelationName(trigdata->tg_relation),
-									tgargs[RI_CONSTRAINT_NAME_ARGNO]),
+									NameStr(riinfo.conname)),
 							 errdetail("MATCH FULL does not allow mixing of null and nonnull key values.")));
 					heap_close(pk_rel, RowShareLock);
 					return PointerGetDatum(NULL);
 
-				case RI_MATCH_TYPE_UNSPECIFIED:
+				case FKCONSTR_MATCH_UNSPECIFIED:
 
 					/*
 					 * MATCH <unspecified> - if ANY column is null, we have a
@@ -355,7 +412,7 @@ RI_FKey_check(PG_FUNCTION_ARGS)
 					heap_close(pk_rel, RowShareLock);
 					return PointerGetDatum(NULL);
 
-				case RI_MATCH_TYPE_PARTIAL:
+				case FKCONSTR_MATCH_PARTIAL:
 
 					/*
 					 * MATCH PARTIAL - all non-null columns must match. (not
@@ -387,39 +444,43 @@ RI_FKey_check(PG_FUNCTION_ARGS)
 	 */
 	if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
 	{
-		char		querystr[MAX_QUOTED_REL_NAME_LEN + 100 +
-								(MAX_QUOTED_NAME_LEN + 32) * RI_MAX_NUMKEYS];
+		StringInfoData querybuf;
 		char		pkrelname[MAX_QUOTED_REL_NAME_LEN];
 		char		attname[MAX_QUOTED_NAME_LEN];
+		char		paramname[16];
 		const char *querysep;
 		Oid			queryoids[RI_MAX_NUMKEYS];
 
 		/* ----------
 		 * The query string built is
-		 *	SELECT 1 FROM ONLY <pktable> WHERE pkatt1 = $1 [AND ...]
+		 *	SELECT 1 FROM ONLY <pktable> WHERE pkatt1 = $1 [AND ...] FOR SHARE
 		 * The type id's for the $ parameters are those of the
-		 * corresponding FK attributes. Thus, ri_PlanCheck could
-		 * eventually fail if the parser cannot identify some way
-		 * how to compare these two types by '='.
+		 * corresponding FK attributes.
 		 * ----------
 		 */
+		initStringInfo(&querybuf);
 		quoteRelationName(pkrelname, pk_rel);
-		snprintf(querystr, sizeof(querystr), "SELECT 1 FROM ONLY %s x", pkrelname);
+		appendStringInfo(&querybuf, "SELECT 1 FROM ONLY %s x", pkrelname);
 		querysep = "WHERE";
-		for (i = 0; i < qkey.nkeypairs; i++)
+		for (i = 0; i < riinfo.nkeys; i++)
 		{
+			Oid		pk_type = RIAttType(pk_rel, riinfo.pk_attnums[i]);
+			Oid		fk_type = RIAttType(fk_rel, riinfo.fk_attnums[i]);
+
 			quoteOneName(attname,
-				 tgargs[RI_FIRST_ATTNAME_ARGNO + i * 2 + RI_KEYPAIR_PK_IDX]);
-			snprintf(querystr + strlen(querystr), sizeof(querystr) - strlen(querystr), " %s %s = $%d",
-					 querysep, attname, i + 1);
+						 RIAttName(pk_rel, riinfo.pk_attnums[i]));
+			sprintf(paramname, "$%d", i + 1);
+			ri_GenerateQual(&querybuf, querysep,
+							attname, pk_type,
+							riinfo.pf_eq_oprs[i],
+							paramname, fk_type);
 			querysep = "AND";
-			queryoids[i] = SPI_gettypeid(fk_rel->rd_att,
-										 qkey.keypair[i][RI_KEYPAIR_FK_IDX]);
+			queryoids[i] = fk_type;
 		}
-		strcat(querystr, " FOR SHARE OF x");
+		appendStringInfo(&querybuf, " FOR SHARE OF x");
 
 		/* Prepare and save the plan */
-		qplan = ri_PlanCheck(querystr, qkey.nkeypairs, queryoids,
+		qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys, queryoids,
 							 &qkey, fk_rel, pk_rel, true);
 	}
 
@@ -431,7 +492,7 @@ RI_FKey_check(PG_FUNCTION_ARGS)
 					NULL, new_row,
 					false,
 					SPI_OK_SELECT,
-					tgargs[RI_CONSTRAINT_NAME_ARGNO]);
+					NameStr(riinfo.conname));
 
 	if (SPI_finish() != SPI_OK_FINISH)
 		elog(ERROR, "SPI_finish failed");
@@ -471,7 +532,7 @@ RI_FKey_check_upd(PG_FUNCTION_ARGS)
 /* ----------
  * ri_Check_Pk_Match
  *
- *	Check for matching value of old pk row in current state for
+ * Check for matching value of old pk row in current state for
  * noaction triggers. Returns false if no row was found and a fk row
  * could potentially be referencing this row, true otherwise.
  * ----------
@@ -479,17 +540,14 @@ RI_FKey_check_upd(PG_FUNCTION_ARGS)
 static bool
 ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel,
 				  HeapTuple old_row,
-				  Oid tgoid, int match_type,
-				  int tgnargs, char **tgargs)
+				  const RI_ConstraintInfo *riinfo)
 {
 	void	   *qplan;
 	RI_QueryKey qkey;
 	int			i;
 	bool		result;
 
-	ri_BuildQueryKeyPkCheck(&qkey, tgoid,
-							RI_PLAN_CHECK_LOOKUPPK, pk_rel,
-							tgnargs, tgargs);
+	ri_BuildQueryKeyPkCheck(&qkey, riinfo, RI_PLAN_CHECK_LOOKUPPK);
 
 	switch (ri_NullCheck(pk_rel, old_row, &qkey, RI_KEYPAIR_PK_IDX))
 	{
@@ -506,10 +564,10 @@ ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel,
 			 * This is the only case that differs between the three kinds of
 			 * MATCH.
 			 */
-			switch (match_type)
+			switch (riinfo->confmatchtype)
 			{
-				case RI_MATCH_TYPE_FULL:
-				case RI_MATCH_TYPE_UNSPECIFIED:
+				case FKCONSTR_MATCH_FULL:
+				case FKCONSTR_MATCH_UNSPECIFIED:
 
 					/*
 					 * MATCH <unspecified>/FULL  - if ANY column is null, we
@@ -517,7 +575,7 @@ ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel,
 					 */
 					return true;
 
-				case RI_MATCH_TYPE_PARTIAL:
+				case FKCONSTR_MATCH_PARTIAL:
 
 					/*
 					 * MATCH PARTIAL - all non-null columns must match. (not
@@ -548,39 +606,42 @@ ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel,
 	 */
 	if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
 	{
-		char		querystr[MAX_QUOTED_REL_NAME_LEN + 100 +
-								(MAX_QUOTED_NAME_LEN + 32) * RI_MAX_NUMKEYS];
+		StringInfoData querybuf;
 		char		pkrelname[MAX_QUOTED_REL_NAME_LEN];
 		char		attname[MAX_QUOTED_NAME_LEN];
+		char		paramname[16];
 		const char *querysep;
 		Oid			queryoids[RI_MAX_NUMKEYS];
 
 		/* ----------
 		 * The query string built is
-		 *	SELECT 1 FROM ONLY <pktable> WHERE pkatt1 = $1 [AND ...]
+		 *	SELECT 1 FROM ONLY <pktable> WHERE pkatt1 = $1 [AND ...] FOR SHARE
 		 * The type id's for the $ parameters are those of the
-		 * corresponding FK attributes. Thus, ri_PlanCheck could
-		 * eventually fail if the parser cannot identify some way
-		 * how to compare these two types by '='.
+		 * PK attributes themselves.
 		 * ----------
 		 */
+		initStringInfo(&querybuf);
 		quoteRelationName(pkrelname, pk_rel);
-		snprintf(querystr, sizeof(querystr), "SELECT 1 FROM ONLY %s x", pkrelname);
+		appendStringInfo(&querybuf, "SELECT 1 FROM ONLY %s x", pkrelname);
 		querysep = "WHERE";
-		for (i = 0; i < qkey.nkeypairs; i++)
+		for (i = 0; i < riinfo->nkeys; i++)
 		{
+			Oid		pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]);
+
 			quoteOneName(attname,
-				 tgargs[RI_FIRST_ATTNAME_ARGNO + i * 2 + RI_KEYPAIR_PK_IDX]);
-			snprintf(querystr + strlen(querystr), sizeof(querystr) - strlen(querystr), " %s %s = $%d",
-					 querysep, attname, i + 1);
+						 RIAttName(pk_rel, riinfo->pk_attnums[i]));
+			sprintf(paramname, "$%d", i + 1);
+			ri_GenerateQual(&querybuf, querysep,
+							attname, pk_type,
+							riinfo->pp_eq_oprs[i],
+							paramname, pk_type);
 			querysep = "AND";
-			queryoids[i] = SPI_gettypeid(pk_rel->rd_att,
-										 qkey.keypair[i][RI_KEYPAIR_PK_IDX]);
+			queryoids[i] = pk_type;
 		}
-		strcat(querystr, " FOR SHARE OF x");
+		appendStringInfo(&querybuf, " FOR SHARE OF x");
 
 		/* Prepare and save the plan */
-		qplan = ri_PlanCheck(querystr, qkey.nkeypairs, queryoids,
+		qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids,
 							 &qkey, fk_rel, pk_rel, true);
 	}
 
@@ -612,28 +673,29 @@ Datum
 RI_FKey_noaction_del(PG_FUNCTION_ARGS)
 {
 	TriggerData *trigdata = (TriggerData *) fcinfo->context;
-	int			tgnargs;
-	char	  **tgargs;
+	RI_ConstraintInfo riinfo;
 	Relation	fk_rel;
 	Relation	pk_rel;
 	HeapTuple	old_row;
 	RI_QueryKey qkey;
 	void	   *qplan;
 	int			i;
-	int			match_type;
 
 	/*
 	 * Check that this is a valid trigger call on the right time and event.
 	 */
 	ri_CheckTrigger(fcinfo, "RI_FKey_noaction_del", RI_TRIGTYPE_DELETE);
 
-	tgnargs = trigdata->tg_trigger->tgnargs;
-	tgargs = trigdata->tg_trigger->tgargs;
+	/*
+	 * Get arguments.
+	 */
+	ri_FetchConstraintInfo(&riinfo,
+						   trigdata->tg_trigger, trigdata->tg_relation, true);
 
 	/*
 	 * Nothing to do if no column names to compare given
 	 */
-	if (tgnargs == 4)
+	if (riinfo.nkeys == 0)
 		return PointerGetDatum(NULL);
 
 	/*
@@ -642,14 +704,11 @@ RI_FKey_noaction_del(PG_FUNCTION_ARGS)
 	 * fk_rel is opened in RowShareLock mode since that's what our eventual
 	 * SELECT FOR SHARE will get on it.
 	 */
-	fk_rel = heap_open(trigdata->tg_trigger->tgconstrrelid, RowShareLock);
+	fk_rel = heap_open(riinfo.fk_relid, RowShareLock);
 	pk_rel = trigdata->tg_relation;
 	old_row = trigdata->tg_trigtuple;
 
-	match_type = ri_DetermineMatchType(tgargs[RI_MATCH_TYPE_ARGNO]);
-	if (ri_Check_Pk_Match(pk_rel, fk_rel,
-						  old_row, trigdata->tg_trigger->tgoid,
-						  match_type, tgnargs, tgargs))
+	if (ri_Check_Pk_Match(pk_rel, fk_rel, old_row, &riinfo))
 	{
 		/*
 		 * There's either another row, or no row could match this one.  In
@@ -659,7 +718,7 @@ RI_FKey_noaction_del(PG_FUNCTION_ARGS)
 		return PointerGetDatum(NULL);
 	}
 
-	switch (match_type)
+	switch (riinfo.confmatchtype)
 	{
 			/* ----------
 			 * SQL3 11.9 <referential constraint definition>
@@ -668,12 +727,10 @@ RI_FKey_noaction_del(PG_FUNCTION_ARGS)
 			 *			... ON DELETE CASCADE
 			 * ----------
 			 */
-		case RI_MATCH_TYPE_UNSPECIFIED:
-		case RI_MATCH_TYPE_FULL:
-			ri_BuildQueryKeyFull(&qkey, trigdata->tg_trigger->tgoid,
-								 RI_PLAN_NOACTION_DEL_CHECKREF,
-								 fk_rel, pk_rel,
-								 tgnargs, tgargs);
+		case FKCONSTR_MATCH_UNSPECIFIED:
+		case FKCONSTR_MATCH_FULL:
+			ri_BuildQueryKeyFull(&qkey, &riinfo,
+								 RI_PLAN_NOACTION_DEL_CHECKREF);
 
 			switch (ri_NullCheck(pk_rel, old_row, &qkey, RI_KEYPAIR_PK_IDX))
 			{
@@ -704,39 +761,44 @@ RI_FKey_noaction_del(PG_FUNCTION_ARGS)
 			 */
 			if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
 			{
-				char		querystr[MAX_QUOTED_REL_NAME_LEN + 100 +
-								(MAX_QUOTED_NAME_LEN + 32) * RI_MAX_NUMKEYS];
+				StringInfoData querybuf;
 				char		fkrelname[MAX_QUOTED_REL_NAME_LEN];
 				char		attname[MAX_QUOTED_NAME_LEN];
+				char		paramname[16];
 				const char *querysep;
 				Oid			queryoids[RI_MAX_NUMKEYS];
 
 				/* ----------
 				 * The query string built is
-				 *	SELECT 1 FROM ONLY <fktable> WHERE fkatt1 = $1 [AND ...]
+				 *	SELECT 1 FROM ONLY <fktable> WHERE $1 = fkatt1 [AND ...]
 				 * The type id's for the $ parameters are those of the
-				 * corresponding PK attributes. Thus, ri_PlanCheck could
-				 * eventually fail if the parser cannot identify some way
-				 * how to compare these two types by '='.
+				 * corresponding PK attributes.
 				 * ----------
 				 */
+				initStringInfo(&querybuf);
 				quoteRelationName(fkrelname, fk_rel);
-				snprintf(querystr, sizeof(querystr), "SELECT 1 FROM ONLY %s x", fkrelname);
+				appendStringInfo(&querybuf, "SELECT 1 FROM ONLY %s x",
+								 fkrelname);
 				querysep = "WHERE";
-				for (i = 0; i < qkey.nkeypairs; i++)
+				for (i = 0; i < riinfo.nkeys; i++)
 				{
+					Oid		pk_type = RIAttType(pk_rel, riinfo.pk_attnums[i]);
+					Oid		fk_type = RIAttType(fk_rel, riinfo.fk_attnums[i]);
+
 					quoteOneName(attname,
-								 tgargs[RI_FIRST_ATTNAME_ARGNO + i * 2 + RI_KEYPAIR_FK_IDX]);
-					snprintf(querystr + strlen(querystr), sizeof(querystr) - strlen(querystr), " %s %s = $%d",
-							 querysep, attname, i + 1);
+								 RIAttName(fk_rel, riinfo.fk_attnums[i]));
+					sprintf(paramname, "$%d", i + 1);
+					ri_GenerateQual(&querybuf, querysep,
+									paramname, pk_type,
+									riinfo.pf_eq_oprs[i],
+									attname, fk_type);
 					querysep = "AND";
-					queryoids[i] = SPI_gettypeid(pk_rel->rd_att,
-										 qkey.keypair[i][RI_KEYPAIR_PK_IDX]);
+					queryoids[i] = pk_type;
 				}
-				strcat(querystr, " FOR SHARE OF x");
+				appendStringInfo(&querybuf, " FOR SHARE OF x");
 
 				/* Prepare and save the plan */
-				qplan = ri_PlanCheck(querystr, qkey.nkeypairs, queryoids,
+				qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys, queryoids,
 									 &qkey, fk_rel, pk_rel, true);
 			}
 
@@ -748,7 +810,7 @@ RI_FKey_noaction_del(PG_FUNCTION_ARGS)
 							old_row, NULL,
 							true,		/* must detect new rows */
 							SPI_OK_SELECT,
-							tgargs[RI_CONSTRAINT_NAME_ARGNO]);
+							NameStr(riinfo.conname));
 
 			if (SPI_finish() != SPI_OK_FINISH)
 				elog(ERROR, "SPI_finish failed");
@@ -760,7 +822,7 @@ RI_FKey_noaction_del(PG_FUNCTION_ARGS)
 			/*
 			 * Handle MATCH PARTIAL restrict delete.
 			 */
-		case RI_MATCH_TYPE_PARTIAL:
+		case FKCONSTR_MATCH_PARTIAL:
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("MATCH PARTIAL not yet implemented")));
@@ -770,7 +832,7 @@ RI_FKey_noaction_del(PG_FUNCTION_ARGS)
 	/*
 	 * Never reached
 	 */
-	elog(ERROR, "invalid match_type");
+	elog(ERROR, "invalid confmatchtype");
 	return PointerGetDatum(NULL);
 }
 
@@ -787,8 +849,7 @@ Datum
 RI_FKey_noaction_upd(PG_FUNCTION_ARGS)
 {
 	TriggerData *trigdata = (TriggerData *) fcinfo->context;
-	int			tgnargs;
-	char	  **tgargs;
+	RI_ConstraintInfo riinfo;
 	Relation	fk_rel;
 	Relation	pk_rel;
 	HeapTuple	new_row;
@@ -796,20 +857,22 @@ RI_FKey_noaction_upd(PG_FUNCTION_ARGS)
 	RI_QueryKey qkey;
 	void	   *qplan;
 	int			i;
-	int			match_type;
 
 	/*
 	 * Check that this is a valid trigger call on the right time and event.
 	 */
 	ri_CheckTrigger(fcinfo, "RI_FKey_noaction_upd", RI_TRIGTYPE_UPDATE);
 
-	tgnargs = trigdata->tg_trigger->tgnargs;
-	tgargs = trigdata->tg_trigger->tgargs;
+	/*
+	 * Get arguments.
+	 */
+	ri_FetchConstraintInfo(&riinfo,
+						   trigdata->tg_trigger, trigdata->tg_relation, true);
 
 	/*
 	 * Nothing to do if no column names to compare given
 	 */
-	if (tgnargs == 4)
+	if (riinfo.nkeys == 0)
 		return PointerGetDatum(NULL);
 
 	/*
@@ -819,14 +882,12 @@ RI_FKey_noaction_upd(PG_FUNCTION_ARGS)
 	 * fk_rel is opened in RowShareLock mode since that's what our eventual
 	 * SELECT FOR SHARE will get on it.
 	 */
-	fk_rel = heap_open(trigdata->tg_trigger->tgconstrrelid, RowShareLock);
+	fk_rel = heap_open(riinfo.fk_relid, RowShareLock);
 	pk_rel = trigdata->tg_relation;
 	new_row = trigdata->tg_newtuple;
 	old_row = trigdata->tg_trigtuple;
 
-	match_type = ri_DetermineMatchType(tgargs[RI_MATCH_TYPE_ARGNO]);
-
-	switch (match_type)
+	switch (riinfo.confmatchtype)
 	{
 			/* ----------
 			 * SQL3 11.9 <referential constraint definition>
@@ -835,12 +896,10 @@ RI_FKey_noaction_upd(PG_FUNCTION_ARGS)
 			 *			... ON DELETE CASCADE
 			 * ----------
 			 */
-		case RI_MATCH_TYPE_UNSPECIFIED:
-		case RI_MATCH_TYPE_FULL:
-			ri_BuildQueryKeyFull(&qkey, trigdata->tg_trigger->tgoid,
-								 RI_PLAN_NOACTION_UPD_CHECKREF,
-								 fk_rel, pk_rel,
-								 tgnargs, tgargs);
+		case FKCONSTR_MATCH_UNSPECIFIED:
+		case FKCONSTR_MATCH_FULL:
+			ri_BuildQueryKeyFull(&qkey, &riinfo,
+								 RI_PLAN_NOACTION_UPD_CHECKREF);
 
 			switch (ri_NullCheck(pk_rel, old_row, &qkey, RI_KEYPAIR_PK_IDX))
 			{
@@ -865,16 +924,13 @@ RI_FKey_noaction_upd(PG_FUNCTION_ARGS)
 			/*
 			 * No need to check anything if old and new keys are equal
 			 */
-			if (ri_KeysEqual(pk_rel, old_row, new_row, &qkey,
-							 RI_KEYPAIR_PK_IDX))
+			if (ri_KeysEqual(pk_rel, old_row, new_row, &riinfo, true))
 			{
 				heap_close(fk_rel, RowShareLock);
 				return PointerGetDatum(NULL);
 			}
 
-			if (ri_Check_Pk_Match(pk_rel, fk_rel,
-								  old_row, trigdata->tg_trigger->tgoid,
-								  match_type, tgnargs, tgargs))
+			if (ri_Check_Pk_Match(pk_rel, fk_rel, old_row, &riinfo))
 			{
 				/*
 				 * There's either another row, or no row could match this one.
@@ -893,39 +949,44 @@ RI_FKey_noaction_upd(PG_FUNCTION_ARGS)
 			 */
 			if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
 			{
-				char		querystr[MAX_QUOTED_REL_NAME_LEN + 100 +
-								(MAX_QUOTED_NAME_LEN + 32) * RI_MAX_NUMKEYS];
+				StringInfoData querybuf;
 				char		fkrelname[MAX_QUOTED_REL_NAME_LEN];
 				char		attname[MAX_QUOTED_NAME_LEN];
+				char		paramname[16];
 				const char *querysep;
 				Oid			queryoids[RI_MAX_NUMKEYS];
 
 				/* ----------
 				 * The query string built is
-				 *	SELECT 1 FROM ONLY <fktable> WHERE fkatt1 = $1 [AND ...]
+				 *	SELECT 1 FROM ONLY <fktable> WHERE $1 = fkatt1 [AND ...]
 				 * The type id's for the $ parameters are those of the
-				 * corresponding PK attributes. Thus, ri_PlanCheck could
-				 * eventually fail if the parser cannot identify some way
-				 * how to compare these two types by '='.
+				 * corresponding PK attributes.
 				 * ----------
 				 */
+				initStringInfo(&querybuf);
 				quoteRelationName(fkrelname, fk_rel);
-				snprintf(querystr, sizeof(querystr), "SELECT 1 FROM ONLY %s x", fkrelname);
+				appendStringInfo(&querybuf, "SELECT 1 FROM ONLY %s x",
+								 fkrelname);
 				querysep = "WHERE";
-				for (i = 0; i < qkey.nkeypairs; i++)
+				for (i = 0; i < riinfo.nkeys; i++)
 				{
+					Oid		pk_type = RIAttType(pk_rel, riinfo.pk_attnums[i]);
+					Oid		fk_type = RIAttType(fk_rel, riinfo.fk_attnums[i]);
+
 					quoteOneName(attname,
-								 tgargs[RI_FIRST_ATTNAME_ARGNO + i * 2 + RI_KEYPAIR_FK_IDX]);
-					snprintf(querystr + strlen(querystr), sizeof(querystr) - strlen(querystr), " %s %s = $%d",
-							 querysep, attname, i + 1);
+								 RIAttName(fk_rel, riinfo.fk_attnums[i]));
+					sprintf(paramname, "$%d", i + 1);
+					ri_GenerateQual(&querybuf, querysep,
+									paramname, pk_type,
+									riinfo.pf_eq_oprs[i],
+									attname, fk_type);
 					querysep = "AND";
-					queryoids[i] = SPI_gettypeid(pk_rel->rd_att,
-										 qkey.keypair[i][RI_KEYPAIR_PK_IDX]);
+					queryoids[i] = pk_type;
 				}
-				strcat(querystr, " FOR SHARE OF x");
+				appendStringInfo(&querybuf, " FOR SHARE OF x");
 
 				/* Prepare and save the plan */
-				qplan = ri_PlanCheck(querystr, qkey.nkeypairs, queryoids,
+				qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys, queryoids,
 									 &qkey, fk_rel, pk_rel, true);
 			}
 
@@ -937,7 +998,7 @@ RI_FKey_noaction_upd(PG_FUNCTION_ARGS)
 							old_row, NULL,
 							true,		/* must detect new rows */
 							SPI_OK_SELECT,
-							tgargs[RI_CONSTRAINT_NAME_ARGNO]);
+							NameStr(riinfo.conname));
 
 			if (SPI_finish() != SPI_OK_FINISH)
 				elog(ERROR, "SPI_finish failed");
@@ -949,7 +1010,7 @@ RI_FKey_noaction_upd(PG_FUNCTION_ARGS)
 			/*
 			 * Handle MATCH PARTIAL noaction update.
 			 */
-		case RI_MATCH_TYPE_PARTIAL:
+		case FKCONSTR_MATCH_PARTIAL:
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("MATCH PARTIAL not yet implemented")));
@@ -959,7 +1020,7 @@ RI_FKey_noaction_upd(PG_FUNCTION_ARGS)
 	/*
 	 * Never reached
 	 */
-	elog(ERROR, "invalid match_type");
+	elog(ERROR, "invalid confmatchtype");
 	return PointerGetDatum(NULL);
 }
 
@@ -974,8 +1035,7 @@ Datum
 RI_FKey_cascade_del(PG_FUNCTION_ARGS)
 {
 	TriggerData *trigdata = (TriggerData *) fcinfo->context;
-	int			tgnargs;
-	char	  **tgargs;
+	RI_ConstraintInfo riinfo;
 	Relation	fk_rel;
 	Relation	pk_rel;
 	HeapTuple	old_row;
@@ -988,13 +1048,16 @@ RI_FKey_cascade_del(PG_FUNCTION_ARGS)
 	 */
 	ri_CheckTrigger(fcinfo, "RI_FKey_cascade_del", RI_TRIGTYPE_DELETE);
 
-	tgnargs = trigdata->tg_trigger->tgnargs;
-	tgargs = trigdata->tg_trigger->tgargs;
+	/*
+	 * Get arguments.
+	 */
+	ri_FetchConstraintInfo(&riinfo,
+						   trigdata->tg_trigger, trigdata->tg_relation, true);
 
 	/*
 	 * Nothing to do if no column names to compare given
 	 */
-	if (tgnargs == 4)
+	if (riinfo.nkeys == 0)
 		return PointerGetDatum(NULL);
 
 	/*
@@ -1003,11 +1066,11 @@ RI_FKey_cascade_del(PG_FUNCTION_ARGS)
 	 * fk_rel is opened in RowExclusiveLock mode since that's what our
 	 * eventual DELETE will get on it.
 	 */
-	fk_rel = heap_open(trigdata->tg_trigger->tgconstrrelid, RowExclusiveLock);
+	fk_rel = heap_open(riinfo.fk_relid, RowExclusiveLock);
 	pk_rel = trigdata->tg_relation;
 	old_row = trigdata->tg_trigtuple;
 
-	switch (ri_DetermineMatchType(tgargs[RI_MATCH_TYPE_ARGNO]))
+	switch (riinfo.confmatchtype)
 	{
 			/* ----------
 			 * SQL3 11.9 <referential constraint definition>
@@ -1016,12 +1079,10 @@ RI_FKey_cascade_del(PG_FUNCTION_ARGS)
 			 *			... ON DELETE CASCADE
 			 * ----------
 			 */
-		case RI_MATCH_TYPE_UNSPECIFIED:
-		case RI_MATCH_TYPE_FULL:
-			ri_BuildQueryKeyFull(&qkey, trigdata->tg_trigger->tgoid,
-								 RI_PLAN_CASCADE_DEL_DODELETE,
-								 fk_rel, pk_rel,
-								 tgnargs, tgargs);
+		case FKCONSTR_MATCH_UNSPECIFIED:
+		case FKCONSTR_MATCH_FULL:
+			ri_BuildQueryKeyFull(&qkey, &riinfo,
+								 RI_PLAN_CASCADE_DEL_DODELETE);
 
 			switch (ri_NullCheck(pk_rel, old_row, &qkey, RI_KEYPAIR_PK_IDX))
 			{
@@ -1051,38 +1112,42 @@ RI_FKey_cascade_del(PG_FUNCTION_ARGS)
 			 */
 			if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
 			{
-				char		querystr[MAX_QUOTED_REL_NAME_LEN + 100 +
-								(MAX_QUOTED_NAME_LEN + 32) * RI_MAX_NUMKEYS];
+				StringInfoData querybuf;
 				char		fkrelname[MAX_QUOTED_REL_NAME_LEN];
 				char		attname[MAX_QUOTED_NAME_LEN];
+				char		paramname[16];
 				const char *querysep;
 				Oid			queryoids[RI_MAX_NUMKEYS];
 
 				/* ----------
 				 * The query string built is
-				 *	DELETE FROM ONLY <fktable> WHERE fkatt1 = $1 [AND ...]
+				 *	DELETE FROM ONLY <fktable> WHERE $1 = fkatt1 [AND ...]
 				 * The type id's for the $ parameters are those of the
-				 * corresponding PK attributes. Thus, ri_PlanCheck could
-				 * eventually fail if the parser cannot identify some way
-				 * how to compare these two types by '='.
+				 * corresponding PK attributes.
 				 * ----------
 				 */
+				initStringInfo(&querybuf);
 				quoteRelationName(fkrelname, fk_rel);
-				snprintf(querystr, sizeof(querystr), "DELETE FROM ONLY %s", fkrelname);
+				appendStringInfo(&querybuf, "DELETE FROM ONLY %s", fkrelname);
 				querysep = "WHERE";
-				for (i = 0; i < qkey.nkeypairs; i++)
+				for (i = 0; i < riinfo.nkeys; i++)
 				{
+					Oid		pk_type = RIAttType(pk_rel, riinfo.pk_attnums[i]);
+					Oid		fk_type = RIAttType(fk_rel, riinfo.fk_attnums[i]);
+
 					quoteOneName(attname,
-								 tgargs[RI_FIRST_ATTNAME_ARGNO + i * 2 + RI_KEYPAIR_FK_IDX]);
-					snprintf(querystr + strlen(querystr), sizeof(querystr) - strlen(querystr), " %s %s = $%d",
-							 querysep, attname, i + 1);
+								 RIAttName(fk_rel, riinfo.fk_attnums[i]));
+					sprintf(paramname, "$%d", i + 1);
+					ri_GenerateQual(&querybuf, querysep,
+									paramname, pk_type,
+									riinfo.pf_eq_oprs[i],
+									attname, fk_type);
 					querysep = "AND";
-					queryoids[i] = SPI_gettypeid(pk_rel->rd_att,
-										 qkey.keypair[i][RI_KEYPAIR_PK_IDX]);
+					queryoids[i] = pk_type;
 				}
 
 				/* Prepare and save the plan */
-				qplan = ri_PlanCheck(querystr, qkey.nkeypairs, queryoids,
+				qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys, queryoids,
 									 &qkey, fk_rel, pk_rel, true);
 			}
 
@@ -1095,7 +1160,7 @@ RI_FKey_cascade_del(PG_FUNCTION_ARGS)
 							old_row, NULL,
 							true,		/* must detect new rows */
 							SPI_OK_DELETE,
-							tgargs[RI_CONSTRAINT_NAME_ARGNO]);
+							NameStr(riinfo.conname));
 
 			if (SPI_finish() != SPI_OK_FINISH)
 				elog(ERROR, "SPI_finish failed");
@@ -1107,7 +1172,7 @@ RI_FKey_cascade_del(PG_FUNCTION_ARGS)
 			/*
 			 * Handle MATCH PARTIAL cascaded delete.
 			 */
-		case RI_MATCH_TYPE_PARTIAL:
+		case FKCONSTR_MATCH_PARTIAL:
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("MATCH PARTIAL not yet implemented")));
@@ -1117,7 +1182,7 @@ RI_FKey_cascade_del(PG_FUNCTION_ARGS)
 	/*
 	 * Never reached
 	 */
-	elog(ERROR, "invalid match_type");
+	elog(ERROR, "invalid confmatchtype");
 	return PointerGetDatum(NULL);
 }
 
@@ -1132,8 +1197,7 @@ Datum
 RI_FKey_cascade_upd(PG_FUNCTION_ARGS)
 {
 	TriggerData *trigdata = (TriggerData *) fcinfo->context;
-	int			tgnargs;
-	char	  **tgargs;
+	RI_ConstraintInfo riinfo;
 	Relation	fk_rel;
 	Relation	pk_rel;
 	HeapTuple	new_row;
@@ -1148,13 +1212,16 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS)
 	 */
 	ri_CheckTrigger(fcinfo, "RI_FKey_cascade_upd", RI_TRIGTYPE_UPDATE);
 
-	tgnargs = trigdata->tg_trigger->tgnargs;
-	tgargs = trigdata->tg_trigger->tgargs;
+	/*
+	 * Get arguments.
+	 */
+	ri_FetchConstraintInfo(&riinfo,
+						   trigdata->tg_trigger, trigdata->tg_relation, true);
 
 	/*
 	 * Nothing to do if no column names to compare given
 	 */
-	if (tgnargs == 4)
+	if (riinfo.nkeys == 0)
 		return PointerGetDatum(NULL);
 
 	/*
@@ -1164,12 +1231,12 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS)
 	 * fk_rel is opened in RowExclusiveLock mode since that's what our
 	 * eventual UPDATE will get on it.
 	 */
-	fk_rel = heap_open(trigdata->tg_trigger->tgconstrrelid, RowExclusiveLock);
+	fk_rel = heap_open(riinfo.fk_relid, RowExclusiveLock);
 	pk_rel = trigdata->tg_relation;
 	new_row = trigdata->tg_newtuple;
 	old_row = trigdata->tg_trigtuple;
 
-	switch (ri_DetermineMatchType(tgargs[RI_MATCH_TYPE_ARGNO]))
+	switch (riinfo.confmatchtype)
 	{
 			/* ----------
 			 * SQL3 11.9 <referential constraint definition>
@@ -1178,12 +1245,10 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS)
 			 *			... ON UPDATE CASCADE
 			 * ----------
 			 */
-		case RI_MATCH_TYPE_UNSPECIFIED:
-		case RI_MATCH_TYPE_FULL:
-			ri_BuildQueryKeyFull(&qkey, trigdata->tg_trigger->tgoid,
-								 RI_PLAN_CASCADE_UPD_DOUPDATE,
-								 fk_rel, pk_rel,
-								 tgnargs, tgargs);
+		case FKCONSTR_MATCH_UNSPECIFIED:
+		case FKCONSTR_MATCH_FULL:
+			ri_BuildQueryKeyFull(&qkey, &riinfo,
+								 RI_PLAN_CASCADE_UPD_DOUPDATE);
 
 			switch (ri_NullCheck(pk_rel, old_row, &qkey, RI_KEYPAIR_PK_IDX))
 			{
@@ -1208,8 +1273,7 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS)
 			/*
 			 * No need to do anything if old and new keys are equal
 			 */
-			if (ri_KeysEqual(pk_rel, old_row, new_row, &qkey,
-							 RI_KEYPAIR_PK_IDX))
+			if (ri_KeysEqual(pk_rel, old_row, new_row, &riinfo, true))
 			{
 				heap_close(fk_rel, RowExclusiveLock);
 				return PointerGetDatum(NULL);
@@ -1224,11 +1288,11 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS)
 			 */
 			if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
 			{
-				char		querystr[MAX_QUOTED_REL_NAME_LEN + 100 +
-							(MAX_QUOTED_NAME_LEN + 32) * RI_MAX_NUMKEYS * 2];
-				char		qualstr[(MAX_QUOTED_NAME_LEN + 32) * RI_MAX_NUMKEYS];
+				StringInfoData querybuf;
+				StringInfoData qualbuf;
 				char		fkrelname[MAX_QUOTED_REL_NAME_LEN];
 				char		attname[MAX_QUOTED_NAME_LEN];
+				char		paramname[16];
 				const char *querysep;
 				const char *qualsep;
 				Oid			queryoids[RI_MAX_NUMKEYS * 2];
@@ -1236,36 +1300,43 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS)
 				/* ----------
 				 * The query string built is
 				 *	UPDATE ONLY <fktable> SET fkatt1 = $1 [, ...]
-				 *			WHERE fkatt1 = $n [AND ...]
+				 *			WHERE $n = fkatt1 [AND ...]
 				 * The type id's for the $ parameters are those of the
-				 * corresponding PK attributes. Thus, ri_PlanCheck could
-				 * eventually fail if the parser cannot identify some way
-				 * how to compare these two types by '='.
+				 * corresponding PK attributes.  Note that we are assuming
+				 * there is an assignment cast from the PK to the FK type;
+				 * else the parser will fail.
 				 * ----------
 				 */
+				initStringInfo(&querybuf);
+				initStringInfo(&qualbuf);
 				quoteRelationName(fkrelname, fk_rel);
-				snprintf(querystr, sizeof(querystr), "UPDATE ONLY %s SET", fkrelname);
-				qualstr[0] = '\0';
+				appendStringInfo(&querybuf, "UPDATE ONLY %s SET", fkrelname);
 				querysep = "";
 				qualsep = "WHERE";
-				for (i = 0, j = qkey.nkeypairs; i < qkey.nkeypairs; i++, j++)
+				for (i = 0, j = riinfo.nkeys; i < riinfo.nkeys; i++, j++)
 				{
+					Oid		pk_type = RIAttType(pk_rel, riinfo.pk_attnums[i]);
+					Oid		fk_type = RIAttType(fk_rel, riinfo.fk_attnums[i]);
+
 					quoteOneName(attname,
-								 tgargs[RI_FIRST_ATTNAME_ARGNO + i * 2 + RI_KEYPAIR_FK_IDX]);
-					snprintf(querystr + strlen(querystr), sizeof(querystr) - strlen(querystr), "%s %s = $%d",
-							 querysep, attname, i + 1);
-					snprintf(qualstr + strlen(qualstr), sizeof(qualstr) - strlen(qualstr), " %s %s = $%d",
-							 qualsep, attname, j + 1);
+								 RIAttName(fk_rel, riinfo.fk_attnums[i]));
+					appendStringInfo(&querybuf,
+									 "%s %s = $%d",
+									 querysep, attname, i + 1);
+					sprintf(paramname, "$%d", j + 1);
+					ri_GenerateQual(&qualbuf, qualsep,
+									paramname, pk_type,
+									riinfo.pf_eq_oprs[i],
+									attname, fk_type);
 					querysep = ",";
 					qualsep = "AND";
-					queryoids[i] = SPI_gettypeid(pk_rel->rd_att,
-										 qkey.keypair[i][RI_KEYPAIR_PK_IDX]);
-					queryoids[j] = queryoids[i];
+					queryoids[i] = pk_type;
+					queryoids[j] = pk_type;
 				}
-				strcat(querystr, qualstr);
+				appendStringInfoString(&querybuf, qualbuf.data);
 
 				/* Prepare and save the plan */
-				qplan = ri_PlanCheck(querystr, qkey.nkeypairs * 2, queryoids,
+				qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys * 2, queryoids,
 									 &qkey, fk_rel, pk_rel, true);
 			}
 
@@ -1277,7 +1348,7 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS)
 							old_row, new_row,
 							true,		/* must detect new rows */
 							SPI_OK_UPDATE,
-							tgargs[RI_CONSTRAINT_NAME_ARGNO]);
+							NameStr(riinfo.conname));
 
 			if (SPI_finish() != SPI_OK_FINISH)
 				elog(ERROR, "SPI_finish failed");
@@ -1289,7 +1360,7 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS)
 			/*
 			 * Handle MATCH PARTIAL cascade update.
 			 */
-		case RI_MATCH_TYPE_PARTIAL:
+		case FKCONSTR_MATCH_PARTIAL:
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("MATCH PARTIAL not yet implemented")));
@@ -1299,7 +1370,7 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS)
 	/*
 	 * Never reached
 	 */
-	elog(ERROR, "invalid match_type");
+	elog(ERROR, "invalid confmatchtype");
 	return PointerGetDatum(NULL);
 }
 
@@ -1321,8 +1392,7 @@ Datum
 RI_FKey_restrict_del(PG_FUNCTION_ARGS)
 {
 	TriggerData *trigdata = (TriggerData *) fcinfo->context;
-	int			tgnargs;
-	char	  **tgargs;
+	RI_ConstraintInfo riinfo;
 	Relation	fk_rel;
 	Relation	pk_rel;
 	HeapTuple	old_row;
@@ -1335,13 +1405,16 @@ RI_FKey_restrict_del(PG_FUNCTION_ARGS)
 	 */
 	ri_CheckTrigger(fcinfo, "RI_FKey_restrict_del", RI_TRIGTYPE_DELETE);
 
-	tgnargs = trigdata->tg_trigger->tgnargs;
-	tgargs = trigdata->tg_trigger->tgargs;
+	/*
+	 * Get arguments.
+	 */
+	ri_FetchConstraintInfo(&riinfo,
+						   trigdata->tg_trigger, trigdata->tg_relation, true);
 
 	/*
 	 * Nothing to do if no column names to compare given
 	 */
-	if (tgnargs == 4)
+	if (riinfo.nkeys == 0)
 		return PointerGetDatum(NULL);
 
 	/*
@@ -1350,11 +1423,11 @@ RI_FKey_restrict_del(PG_FUNCTION_ARGS)
 	 * fk_rel is opened in RowShareLock mode since that's what our eventual
 	 * SELECT FOR SHARE will get on it.
 	 */
-	fk_rel = heap_open(trigdata->tg_trigger->tgconstrrelid, RowShareLock);
+	fk_rel = heap_open(riinfo.fk_relid, RowShareLock);
 	pk_rel = trigdata->tg_relation;
 	old_row = trigdata->tg_trigtuple;
 
-	switch (ri_DetermineMatchType(tgargs[RI_MATCH_TYPE_ARGNO]))
+	switch (riinfo.confmatchtype)
 	{
 			/* ----------
 			 * SQL3 11.9 <referential constraint definition>
@@ -1363,12 +1436,10 @@ RI_FKey_restrict_del(PG_FUNCTION_ARGS)
 			 *			... ON DELETE CASCADE
 			 * ----------
 			 */
-		case RI_MATCH_TYPE_UNSPECIFIED:
-		case RI_MATCH_TYPE_FULL:
-			ri_BuildQueryKeyFull(&qkey, trigdata->tg_trigger->tgoid,
-								 RI_PLAN_RESTRICT_DEL_CHECKREF,
-								 fk_rel, pk_rel,
-								 tgnargs, tgargs);
+		case FKCONSTR_MATCH_UNSPECIFIED:
+		case FKCONSTR_MATCH_FULL:
+			ri_BuildQueryKeyFull(&qkey, &riinfo,
+								 RI_PLAN_RESTRICT_DEL_CHECKREF);
 
 			switch (ri_NullCheck(pk_rel, old_row, &qkey, RI_KEYPAIR_PK_IDX))
 			{
@@ -1399,39 +1470,44 @@ RI_FKey_restrict_del(PG_FUNCTION_ARGS)
 			 */
 			if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
 			{
-				char		querystr[MAX_QUOTED_REL_NAME_LEN + 100 +
-								(MAX_QUOTED_NAME_LEN + 32) * RI_MAX_NUMKEYS];
+				StringInfoData querybuf;
 				char		fkrelname[MAX_QUOTED_REL_NAME_LEN];
 				char		attname[MAX_QUOTED_NAME_LEN];
+				char		paramname[16];
 				const char *querysep;
 				Oid			queryoids[RI_MAX_NUMKEYS];
 
 				/* ----------
 				 * The query string built is
-				 *	SELECT 1 FROM ONLY <fktable> WHERE fkatt1 = $1 [AND ...]
+				 *	SELECT 1 FROM ONLY <fktable> WHERE $1 = fkatt1 [AND ...]
 				 * The type id's for the $ parameters are those of the
-				 * corresponding PK attributes. Thus, ri_PlanCheck could
-				 * eventually fail if the parser cannot identify some way
-				 * how to compare these two types by '='.
+				 * corresponding PK attributes.
 				 * ----------
 				 */
+				initStringInfo(&querybuf);
 				quoteRelationName(fkrelname, fk_rel);
-				snprintf(querystr, sizeof(querystr), "SELECT 1 FROM ONLY %s x", fkrelname);
+				appendStringInfo(&querybuf, "SELECT 1 FROM ONLY %s x",
+								 fkrelname);
 				querysep = "WHERE";
-				for (i = 0; i < qkey.nkeypairs; i++)
+				for (i = 0; i < riinfo.nkeys; i++)
 				{
+					Oid		pk_type = RIAttType(pk_rel, riinfo.pk_attnums[i]);
+					Oid		fk_type = RIAttType(fk_rel, riinfo.fk_attnums[i]);
+
 					quoteOneName(attname,
-								 tgargs[RI_FIRST_ATTNAME_ARGNO + i * 2 + RI_KEYPAIR_FK_IDX]);
-					snprintf(querystr + strlen(querystr), sizeof(querystr) - strlen(querystr), " %s %s = $%d",
-							 querysep, attname, i + 1);
+								 RIAttName(fk_rel, riinfo.fk_attnums[i]));
+					sprintf(paramname, "$%d", i + 1);
+					ri_GenerateQual(&querybuf, querysep,
+									paramname, pk_type,
+									riinfo.pf_eq_oprs[i],
+									attname, fk_type);
 					querysep = "AND";
-					queryoids[i] = SPI_gettypeid(pk_rel->rd_att,
-										 qkey.keypair[i][RI_KEYPAIR_PK_IDX]);
+					queryoids[i] = pk_type;
 				}
-				strcat(querystr, " FOR SHARE OF x");
+				appendStringInfo(&querybuf, " FOR SHARE OF x");
 
 				/* Prepare and save the plan */
-				qplan = ri_PlanCheck(querystr, qkey.nkeypairs, queryoids,
+				qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys, queryoids,
 									 &qkey, fk_rel, pk_rel, true);
 			}
 
@@ -1443,7 +1519,7 @@ RI_FKey_restrict_del(PG_FUNCTION_ARGS)
 							old_row, NULL,
 							true,		/* must detect new rows */
 							SPI_OK_SELECT,
-							tgargs[RI_CONSTRAINT_NAME_ARGNO]);
+							NameStr(riinfo.conname));
 
 			if (SPI_finish() != SPI_OK_FINISH)
 				elog(ERROR, "SPI_finish failed");
@@ -1455,7 +1531,7 @@ RI_FKey_restrict_del(PG_FUNCTION_ARGS)
 			/*
 			 * Handle MATCH PARTIAL restrict delete.
 			 */
-		case RI_MATCH_TYPE_PARTIAL:
+		case FKCONSTR_MATCH_PARTIAL:
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("MATCH PARTIAL not yet implemented")));
@@ -1465,7 +1541,7 @@ RI_FKey_restrict_del(PG_FUNCTION_ARGS)
 	/*
 	 * Never reached
 	 */
-	elog(ERROR, "invalid match_type");
+	elog(ERROR, "invalid confmatchtype");
 	return PointerGetDatum(NULL);
 }
 
@@ -1487,8 +1563,7 @@ Datum
 RI_FKey_restrict_upd(PG_FUNCTION_ARGS)
 {
 	TriggerData *trigdata = (TriggerData *) fcinfo->context;
-	int			tgnargs;
-	char	  **tgargs;
+	RI_ConstraintInfo riinfo;
 	Relation	fk_rel;
 	Relation	pk_rel;
 	HeapTuple	new_row;
@@ -1502,13 +1577,16 @@ RI_FKey_restrict_upd(PG_FUNCTION_ARGS)
 	 */
 	ri_CheckTrigger(fcinfo, "RI_FKey_restrict_upd", RI_TRIGTYPE_UPDATE);
 
-	tgnargs = trigdata->tg_trigger->tgnargs;
-	tgargs = trigdata->tg_trigger->tgargs;
+	/*
+	 * Get arguments.
+	 */
+	ri_FetchConstraintInfo(&riinfo,
+						   trigdata->tg_trigger, trigdata->tg_relation, true);
 
 	/*
 	 * Nothing to do if no column names to compare given
 	 */
-	if (tgnargs == 4)
+	if (riinfo.nkeys == 0)
 		return PointerGetDatum(NULL);
 
 	/*
@@ -1518,12 +1596,12 @@ RI_FKey_restrict_upd(PG_FUNCTION_ARGS)
 	 * fk_rel is opened in RowShareLock mode since that's what our eventual
 	 * SELECT FOR SHARE will get on it.
 	 */
-	fk_rel = heap_open(trigdata->tg_trigger->tgconstrrelid, RowShareLock);
+	fk_rel = heap_open(riinfo.fk_relid, RowShareLock);
 	pk_rel = trigdata->tg_relation;
 	new_row = trigdata->tg_newtuple;
 	old_row = trigdata->tg_trigtuple;
 
-	switch (ri_DetermineMatchType(tgargs[RI_MATCH_TYPE_ARGNO]))
+	switch (riinfo.confmatchtype)
 	{
 			/* ----------
 			 * SQL3 11.9 <referential constraint definition>
@@ -1532,12 +1610,10 @@ RI_FKey_restrict_upd(PG_FUNCTION_ARGS)
 			 *			... ON DELETE CASCADE
 			 * ----------
 			 */
-		case RI_MATCH_TYPE_UNSPECIFIED:
-		case RI_MATCH_TYPE_FULL:
-			ri_BuildQueryKeyFull(&qkey, trigdata->tg_trigger->tgoid,
-								 RI_PLAN_RESTRICT_UPD_CHECKREF,
-								 fk_rel, pk_rel,
-								 tgnargs, tgargs);
+		case FKCONSTR_MATCH_UNSPECIFIED:
+		case FKCONSTR_MATCH_FULL:
+			ri_BuildQueryKeyFull(&qkey, &riinfo,
+								 RI_PLAN_RESTRICT_UPD_CHECKREF);
 
 			switch (ri_NullCheck(pk_rel, old_row, &qkey, RI_KEYPAIR_PK_IDX))
 			{
@@ -1562,8 +1638,7 @@ RI_FKey_restrict_upd(PG_FUNCTION_ARGS)
 			/*
 			 * No need to check anything if old and new keys are equal
 			 */
-			if (ri_KeysEqual(pk_rel, old_row, new_row, &qkey,
-							 RI_KEYPAIR_PK_IDX))
+			if (ri_KeysEqual(pk_rel, old_row, new_row, &riinfo, true))
 			{
 				heap_close(fk_rel, RowShareLock);
 				return PointerGetDatum(NULL);
@@ -1578,39 +1653,44 @@ RI_FKey_restrict_upd(PG_FUNCTION_ARGS)
 			 */
 			if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
 			{
-				char		querystr[MAX_QUOTED_REL_NAME_LEN + 100 +
-								(MAX_QUOTED_NAME_LEN + 32) * RI_MAX_NUMKEYS];
+				StringInfoData querybuf;
 				char		fkrelname[MAX_QUOTED_REL_NAME_LEN];
 				char		attname[MAX_QUOTED_NAME_LEN];
+				char		paramname[16];
 				const char *querysep;
 				Oid			queryoids[RI_MAX_NUMKEYS];
 
 				/* ----------
 				 * The query string built is
-				 *	SELECT 1 FROM ONLY <fktable> WHERE fkatt1 = $1 [AND ...]
+				 *	SELECT 1 FROM ONLY <fktable> WHERE $1 = fkatt1 [AND ...]
 				 * The type id's for the $ parameters are those of the
-				 * corresponding PK attributes. Thus, ri_PlanCheck could
-				 * eventually fail if the parser cannot identify some way
-				 * how to compare these two types by '='.
+				 * corresponding PK attributes.
 				 * ----------
 				 */
+				initStringInfo(&querybuf);
 				quoteRelationName(fkrelname, fk_rel);
-				snprintf(querystr, sizeof(querystr), "SELECT 1 FROM ONLY %s x", fkrelname);
+				appendStringInfo(&querybuf, "SELECT 1 FROM ONLY %s x",
+								 fkrelname);
 				querysep = "WHERE";
-				for (i = 0; i < qkey.nkeypairs; i++)
+				for (i = 0; i < riinfo.nkeys; i++)
 				{
+					Oid		pk_type = RIAttType(pk_rel, riinfo.pk_attnums[i]);
+					Oid		fk_type = RIAttType(fk_rel, riinfo.fk_attnums[i]);
+
 					quoteOneName(attname,
-								 tgargs[RI_FIRST_ATTNAME_ARGNO + i * 2 + RI_KEYPAIR_FK_IDX]);
-					snprintf(querystr + strlen(querystr), sizeof(querystr) - strlen(querystr), " %s %s = $%d",
-							 querysep, attname, i + 1);
+								 RIAttName(fk_rel, riinfo.fk_attnums[i]));
+					sprintf(paramname, "$%d", i + 1);
+					ri_GenerateQual(&querybuf, querysep,
+									paramname, pk_type,
+									riinfo.pf_eq_oprs[i],
+									attname, fk_type);
 					querysep = "AND";
-					queryoids[i] = SPI_gettypeid(pk_rel->rd_att,
-										 qkey.keypair[i][RI_KEYPAIR_PK_IDX]);
+					queryoids[i] = pk_type;
 				}
-				strcat(querystr, " FOR SHARE OF x");
+				appendStringInfo(&querybuf, " FOR SHARE OF x");
 
 				/* Prepare and save the plan */
-				qplan = ri_PlanCheck(querystr, qkey.nkeypairs, queryoids,
+				qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys, queryoids,
 									 &qkey, fk_rel, pk_rel, true);
 			}
 
@@ -1622,7 +1702,7 @@ RI_FKey_restrict_upd(PG_FUNCTION_ARGS)
 							old_row, NULL,
 							true,		/* must detect new rows */
 							SPI_OK_SELECT,
-							tgargs[RI_CONSTRAINT_NAME_ARGNO]);
+							NameStr(riinfo.conname));
 
 			if (SPI_finish() != SPI_OK_FINISH)
 				elog(ERROR, "SPI_finish failed");
@@ -1634,7 +1714,7 @@ RI_FKey_restrict_upd(PG_FUNCTION_ARGS)
 			/*
 			 * Handle MATCH PARTIAL restrict update.
 			 */
-		case RI_MATCH_TYPE_PARTIAL:
+		case FKCONSTR_MATCH_PARTIAL:
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("MATCH PARTIAL not yet implemented")));
@@ -1644,7 +1724,7 @@ RI_FKey_restrict_upd(PG_FUNCTION_ARGS)
 	/*
 	 * Never reached
 	 */
-	elog(ERROR, "invalid match_type");
+	elog(ERROR, "invalid confmatchtype");
 	return PointerGetDatum(NULL);
 }
 
@@ -1659,8 +1739,7 @@ Datum
 RI_FKey_setnull_del(PG_FUNCTION_ARGS)
 {
 	TriggerData *trigdata = (TriggerData *) fcinfo->context;
-	int			tgnargs;
-	char	  **tgargs;
+	RI_ConstraintInfo riinfo;
 	Relation	fk_rel;
 	Relation	pk_rel;
 	HeapTuple	old_row;
@@ -1673,13 +1752,16 @@ RI_FKey_setnull_del(PG_FUNCTION_ARGS)
 	 */
 	ri_CheckTrigger(fcinfo, "RI_FKey_setnull_del", RI_TRIGTYPE_DELETE);
 
-	tgnargs = trigdata->tg_trigger->tgnargs;
-	tgargs = trigdata->tg_trigger->tgargs;
+	/*
+	 * Get arguments.
+	 */
+	ri_FetchConstraintInfo(&riinfo,
+						   trigdata->tg_trigger, trigdata->tg_relation, true);
 
 	/*
 	 * Nothing to do if no column names to compare given
 	 */
-	if (tgnargs == 4)
+	if (riinfo.nkeys == 0)
 		return PointerGetDatum(NULL);
 
 	/*
@@ -1688,11 +1770,11 @@ RI_FKey_setnull_del(PG_FUNCTION_ARGS)
 	 * fk_rel is opened in RowExclusiveLock mode since that's what our
 	 * eventual UPDATE will get on it.
 	 */
-	fk_rel = heap_open(trigdata->tg_trigger->tgconstrrelid, RowExclusiveLock);
+	fk_rel = heap_open(riinfo.fk_relid, RowExclusiveLock);
 	pk_rel = trigdata->tg_relation;
 	old_row = trigdata->tg_trigtuple;
 
-	switch (ri_DetermineMatchType(tgargs[RI_MATCH_TYPE_ARGNO]))
+	switch (riinfo.confmatchtype)
 	{
 			/* ----------
 			 * SQL3 11.9 <referential constraint definition>
@@ -1701,12 +1783,10 @@ RI_FKey_setnull_del(PG_FUNCTION_ARGS)
 			 *			... ON DELETE SET NULL
 			 * ----------
 			 */
-		case RI_MATCH_TYPE_UNSPECIFIED:
-		case RI_MATCH_TYPE_FULL:
-			ri_BuildQueryKeyFull(&qkey, trigdata->tg_trigger->tgoid,
-								 RI_PLAN_SETNULL_DEL_DOUPDATE,
-								 fk_rel, pk_rel,
-								 tgnargs, tgargs);
+		case FKCONSTR_MATCH_UNSPECIFIED:
+		case FKCONSTR_MATCH_FULL:
+			ri_BuildQueryKeyFull(&qkey, &riinfo,
+								 RI_PLAN_SETNULL_DEL_DOUPDATE);
 
 			switch (ri_NullCheck(pk_rel, old_row, &qkey, RI_KEYPAIR_PK_IDX))
 			{
@@ -1736,11 +1816,11 @@ RI_FKey_setnull_del(PG_FUNCTION_ARGS)
 			 */
 			if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
 			{
-				char		querystr[MAX_QUOTED_REL_NAME_LEN + 100 +
-							(MAX_QUOTED_NAME_LEN + 32) * RI_MAX_NUMKEYS * 2];
-				char		qualstr[(MAX_QUOTED_NAME_LEN + 32) * RI_MAX_NUMKEYS];
+				StringInfoData querybuf;
+				StringInfoData qualbuf;
 				char		fkrelname[MAX_QUOTED_REL_NAME_LEN];
 				char		attname[MAX_QUOTED_NAME_LEN];
+				char		paramname[16];
 				const char *querysep;
 				const char *qualsep;
 				Oid			queryoids[RI_MAX_NUMKEYS];
@@ -1748,35 +1828,40 @@ RI_FKey_setnull_del(PG_FUNCTION_ARGS)
 				/* ----------
 				 * The query string built is
 				 *	UPDATE ONLY <fktable> SET fkatt1 = NULL [, ...]
-				 *			WHERE fkatt1 = $1 [AND ...]
+				 *			WHERE $1 = fkatt1 [AND ...]
 				 * The type id's for the $ parameters are those of the
-				 * corresponding PK attributes. Thus, ri_PlanCheck could
-				 * eventually fail if the parser cannot identify some way
-				 * how to compare these two types by '='.
+				 * corresponding PK attributes.
 				 * ----------
 				 */
+				initStringInfo(&querybuf);
+				initStringInfo(&qualbuf);
 				quoteRelationName(fkrelname, fk_rel);
-				snprintf(querystr, sizeof(querystr), "UPDATE ONLY %s SET", fkrelname);
-				qualstr[0] = '\0';
+				appendStringInfo(&querybuf, "UPDATE ONLY %s SET", fkrelname);
 				querysep = "";
 				qualsep = "WHERE";
-				for (i = 0; i < qkey.nkeypairs; i++)
+				for (i = 0; i < riinfo.nkeys; i++)
 				{
+					Oid		pk_type = RIAttType(pk_rel, riinfo.pk_attnums[i]);
+					Oid		fk_type = RIAttType(fk_rel, riinfo.fk_attnums[i]);
+
 					quoteOneName(attname,
-								 tgargs[RI_FIRST_ATTNAME_ARGNO + i * 2 + RI_KEYPAIR_FK_IDX]);
-					snprintf(querystr + strlen(querystr), sizeof(querystr) - strlen(querystr), "%s %s = NULL",
-							 querysep, attname);
-					snprintf(qualstr + strlen(qualstr), sizeof(qualstr) - strlen(qualstr), " %s %s = $%d",
-							 qualsep, attname, i + 1);
+								 RIAttName(fk_rel, riinfo.fk_attnums[i]));
+					appendStringInfo(&querybuf,
+									 "%s %s = NULL",
+									 querysep, attname);
+					sprintf(paramname, "$%d", i + 1);
+					ri_GenerateQual(&qualbuf, qualsep,
+									paramname, pk_type,
+									riinfo.pf_eq_oprs[i],
+									attname, fk_type);
 					querysep = ",";
 					qualsep = "AND";
-					queryoids[i] = SPI_gettypeid(pk_rel->rd_att,
-										 qkey.keypair[i][RI_KEYPAIR_PK_IDX]);
+					queryoids[i] = pk_type;
 				}
-				strcat(querystr, qualstr);
+				appendStringInfoString(&querybuf, qualbuf.data);
 
 				/* Prepare and save the plan */
-				qplan = ri_PlanCheck(querystr, qkey.nkeypairs, queryoids,
+				qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys, queryoids,
 									 &qkey, fk_rel, pk_rel, true);
 			}
 
@@ -1788,7 +1873,7 @@ RI_FKey_setnull_del(PG_FUNCTION_ARGS)
 							old_row, NULL,
 							true,		/* must detect new rows */
 							SPI_OK_UPDATE,
-							tgargs[RI_CONSTRAINT_NAME_ARGNO]);
+							NameStr(riinfo.conname));
 
 			if (SPI_finish() != SPI_OK_FINISH)
 				elog(ERROR, "SPI_finish failed");
@@ -1800,7 +1885,7 @@ RI_FKey_setnull_del(PG_FUNCTION_ARGS)
 			/*
 			 * Handle MATCH PARTIAL set null delete.
 			 */
-		case RI_MATCH_TYPE_PARTIAL:
+		case FKCONSTR_MATCH_PARTIAL:
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("MATCH PARTIAL not yet implemented")));
@@ -1810,7 +1895,7 @@ RI_FKey_setnull_del(PG_FUNCTION_ARGS)
 	/*
 	 * Never reached
 	 */
-	elog(ERROR, "invalid match_type");
+	elog(ERROR, "invalid confmatchtype");
 	return PointerGetDatum(NULL);
 }
 
@@ -1825,8 +1910,7 @@ Datum
 RI_FKey_setnull_upd(PG_FUNCTION_ARGS)
 {
 	TriggerData *trigdata = (TriggerData *) fcinfo->context;
-	int			tgnargs;
-	char	  **tgargs;
+	RI_ConstraintInfo riinfo;
 	Relation	fk_rel;
 	Relation	pk_rel;
 	HeapTuple	new_row;
@@ -1834,7 +1918,6 @@ RI_FKey_setnull_upd(PG_FUNCTION_ARGS)
 	RI_QueryKey qkey;
 	void	   *qplan;
 	int			i;
-	int			match_type;
 	bool		use_cached_query;
 
 	/*
@@ -1842,13 +1925,16 @@ RI_FKey_setnull_upd(PG_FUNCTION_ARGS)
 	 */
 	ri_CheckTrigger(fcinfo, "RI_FKey_setnull_upd", RI_TRIGTYPE_UPDATE);
 
-	tgnargs = trigdata->tg_trigger->tgnargs;
-	tgargs = trigdata->tg_trigger->tgargs;
+	/*
+	 * Get arguments.
+	 */
+	ri_FetchConstraintInfo(&riinfo,
+						   trigdata->tg_trigger, trigdata->tg_relation, true);
 
 	/*
 	 * Nothing to do if no column names to compare given
 	 */
-	if (tgnargs == 4)
+	if (riinfo.nkeys == 0)
 		return PointerGetDatum(NULL);
 
 	/*
@@ -1857,13 +1943,12 @@ RI_FKey_setnull_upd(PG_FUNCTION_ARGS)
 	 * fk_rel is opened in RowExclusiveLock mode since that's what our
 	 * eventual UPDATE will get on it.
 	 */
-	fk_rel = heap_open(trigdata->tg_trigger->tgconstrrelid, RowExclusiveLock);
+	fk_rel = heap_open(riinfo.fk_relid, RowExclusiveLock);
 	pk_rel = trigdata->tg_relation;
 	new_row = trigdata->tg_newtuple;
 	old_row = trigdata->tg_trigtuple;
-	match_type = ri_DetermineMatchType(tgargs[RI_MATCH_TYPE_ARGNO]);
 
-	switch (match_type)
+	switch (riinfo.confmatchtype)
 	{
 			/* ----------
 			 * SQL3 11.9 <referential constraint definition>
@@ -1872,12 +1957,10 @@ RI_FKey_setnull_upd(PG_FUNCTION_ARGS)
 			 *			... ON UPDATE SET NULL
 			 * ----------
 			 */
-		case RI_MATCH_TYPE_UNSPECIFIED:
-		case RI_MATCH_TYPE_FULL:
-			ri_BuildQueryKeyFull(&qkey, trigdata->tg_trigger->tgoid,
-								 RI_PLAN_SETNULL_UPD_DOUPDATE,
-								 fk_rel, pk_rel,
-								 tgnargs, tgargs);
+		case FKCONSTR_MATCH_UNSPECIFIED:
+		case FKCONSTR_MATCH_FULL:
+			ri_BuildQueryKeyFull(&qkey, &riinfo,
+								 RI_PLAN_SETNULL_UPD_DOUPDATE);
 
 			switch (ri_NullCheck(pk_rel, old_row, &qkey, RI_KEYPAIR_PK_IDX))
 			{
@@ -1902,8 +1985,7 @@ RI_FKey_setnull_upd(PG_FUNCTION_ARGS)
 			/*
 			 * No need to do anything if old and new keys are equal
 			 */
-			if (ri_KeysEqual(pk_rel, old_row, new_row, &qkey,
-							 RI_KEYPAIR_PK_IDX))
+			if (ri_KeysEqual(pk_rel, old_row, new_row, &riinfo, true))
 			{
 				heap_close(fk_rel, RowExclusiveLock);
 				return PointerGetDatum(NULL);
@@ -1925,9 +2007,9 @@ RI_FKey_setnull_upd(PG_FUNCTION_ARGS)
 			 * know that the old key value has no NULLs (see above).
 			 */
 
-			use_cached_query = match_type == RI_MATCH_TYPE_FULL ||
+			use_cached_query = (riinfo.confmatchtype == FKCONSTR_MATCH_FULL) ||
 				ri_AllKeysUnequal(pk_rel, old_row, new_row,
-								  &qkey, RI_KEYPAIR_PK_IDX);
+								  &riinfo, true);
 
 			/*
 			 * Fetch or prepare a saved plan for the set null update operation
@@ -1936,11 +2018,11 @@ RI_FKey_setnull_upd(PG_FUNCTION_ARGS)
 			if (!use_cached_query ||
 				(qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
 			{
-				char		querystr[MAX_QUOTED_REL_NAME_LEN + 100 +
-							(MAX_QUOTED_NAME_LEN + 32) * RI_MAX_NUMKEYS * 2];
-				char		qualstr[(MAX_QUOTED_NAME_LEN + 32) * RI_MAX_NUMKEYS];
+				StringInfoData querybuf;
+				StringInfoData qualbuf;
 				char		fkrelname[MAX_QUOTED_REL_NAME_LEN];
 				char		attname[MAX_QUOTED_NAME_LEN];
+				char		paramname[16];
 				const char *querysep;
 				const char *qualsep;
 				Oid			queryoids[RI_MAX_NUMKEYS];
@@ -1948,48 +2030,52 @@ RI_FKey_setnull_upd(PG_FUNCTION_ARGS)
 				/* ----------
 				 * The query string built is
 				 *	UPDATE ONLY <fktable> SET fkatt1 = NULL [, ...]
-				 *			WHERE fkatt1 = $1 [AND ...]
+				 *			WHERE $1 = fkatt1 [AND ...]
 				 * The type id's for the $ parameters are those of the
-				 * corresponding PK attributes. Thus, ri_PlanCheck could
-				 * eventually fail if the parser cannot identify some way
-				 * how to compare these two types by '='.
+				 * corresponding PK attributes.
 				 * ----------
 				 */
+				initStringInfo(&querybuf);
+				initStringInfo(&qualbuf);
 				quoteRelationName(fkrelname, fk_rel);
-				snprintf(querystr, sizeof(querystr), "UPDATE ONLY %s SET", fkrelname);
-				qualstr[0] = '\0';
+				appendStringInfo(&querybuf, "UPDATE ONLY %s SET", fkrelname);
 				querysep = "";
 				qualsep = "WHERE";
-				for (i = 0; i < qkey.nkeypairs; i++)
+				for (i = 0; i < riinfo.nkeys; i++)
 				{
-					quoteOneName(attname,
-								 tgargs[RI_FIRST_ATTNAME_ARGNO + i * 2 + RI_KEYPAIR_FK_IDX]);
+					Oid		pk_type = RIAttType(pk_rel, riinfo.pk_attnums[i]);
+					Oid		fk_type = RIAttType(fk_rel, riinfo.fk_attnums[i]);
 
+					quoteOneName(attname,
+								 RIAttName(fk_rel, riinfo.fk_attnums[i]));
 					/*
 					 * MATCH <unspecified> - only change columns corresponding
 					 * to changed columns in pk_rel's key
 					 */
-					if (match_type == RI_MATCH_TYPE_FULL ||
-						!ri_OneKeyEqual(pk_rel, i, old_row, new_row, &qkey,
-										RI_KEYPAIR_PK_IDX))
+					if (riinfo.confmatchtype == FKCONSTR_MATCH_FULL ||
+						!ri_OneKeyEqual(pk_rel, i, old_row, new_row,
+										&riinfo, true))
 					{
-						snprintf(querystr + strlen(querystr), sizeof(querystr) - strlen(querystr), "%s %s = NULL",
-								 querysep, attname);
+						appendStringInfo(&querybuf,
+										 "%s %s = NULL",
+										 querysep, attname);
 						querysep = ",";
 					}
-					snprintf(qualstr + strlen(qualstr), sizeof(qualstr) - strlen(qualstr), " %s %s = $%d",
-							 qualsep, attname, i + 1);
+					sprintf(paramname, "$%d", i + 1);
+					ri_GenerateQual(&qualbuf, qualsep,
+									paramname, pk_type,
+									riinfo.pf_eq_oprs[i],
+									attname, fk_type);
 					qualsep = "AND";
-					queryoids[i] = SPI_gettypeid(pk_rel->rd_att,
-										 qkey.keypair[i][RI_KEYPAIR_PK_IDX]);
+					queryoids[i] = pk_type;
 				}
-				strcat(querystr, qualstr);
+				appendStringInfoString(&querybuf, qualbuf.data);
 
 				/*
 				 * Prepare the plan.  Save it only if we're building the
 				 * "standard" plan.
 				 */
-				qplan = ri_PlanCheck(querystr, qkey.nkeypairs, queryoids,
+				qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys, queryoids,
 									 &qkey, fk_rel, pk_rel,
 									 use_cached_query);
 			}
@@ -2002,7 +2088,7 @@ RI_FKey_setnull_upd(PG_FUNCTION_ARGS)
 							old_row, NULL,
 							true,		/* must detect new rows */
 							SPI_OK_UPDATE,
-							tgargs[RI_CONSTRAINT_NAME_ARGNO]);
+							NameStr(riinfo.conname));
 
 			if (SPI_finish() != SPI_OK_FINISH)
 				elog(ERROR, "SPI_finish failed");
@@ -2014,7 +2100,7 @@ RI_FKey_setnull_upd(PG_FUNCTION_ARGS)
 			/*
 			 * Handle MATCH PARTIAL set null update.
 			 */
-		case RI_MATCH_TYPE_PARTIAL:
+		case FKCONSTR_MATCH_PARTIAL:
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("MATCH PARTIAL not yet implemented")));
@@ -2024,7 +2110,7 @@ RI_FKey_setnull_upd(PG_FUNCTION_ARGS)
 	/*
 	 * Never reached
 	 */
-	elog(ERROR, "invalid match_type");
+	elog(ERROR, "invalid confmatchtype");
 	return PointerGetDatum(NULL);
 }
 
@@ -2039,8 +2125,7 @@ Datum
 RI_FKey_setdefault_del(PG_FUNCTION_ARGS)
 {
 	TriggerData *trigdata = (TriggerData *) fcinfo->context;
-	int			tgnargs;
-	char	  **tgargs;
+	RI_ConstraintInfo riinfo;
 	Relation	fk_rel;
 	Relation	pk_rel;
 	HeapTuple	old_row;
@@ -2052,13 +2137,16 @@ RI_FKey_setdefault_del(PG_FUNCTION_ARGS)
 	 */
 	ri_CheckTrigger(fcinfo, "RI_FKey_setdefault_del", RI_TRIGTYPE_DELETE);
 
-	tgnargs = trigdata->tg_trigger->tgnargs;
-	tgargs = trigdata->tg_trigger->tgargs;
+	/*
+	 * Get arguments.
+	 */
+	ri_FetchConstraintInfo(&riinfo,
+						   trigdata->tg_trigger, trigdata->tg_relation, true);
 
 	/*
 	 * Nothing to do if no column names to compare given
 	 */
-	if (tgnargs == 4)
+	if (riinfo.nkeys == 0)
 		return PointerGetDatum(NULL);
 
 	/*
@@ -2067,11 +2155,11 @@ RI_FKey_setdefault_del(PG_FUNCTION_ARGS)
 	 * fk_rel is opened in RowExclusiveLock mode since that's what our
 	 * eventual UPDATE will get on it.
 	 */
-	fk_rel = heap_open(trigdata->tg_trigger->tgconstrrelid, RowExclusiveLock);
+	fk_rel = heap_open(riinfo.fk_relid, RowExclusiveLock);
 	pk_rel = trigdata->tg_relation;
 	old_row = trigdata->tg_trigtuple;
 
-	switch (ri_DetermineMatchType(tgargs[RI_MATCH_TYPE_ARGNO]))
+	switch (riinfo.confmatchtype)
 	{
 			/* ----------
 			 * SQL3 11.9 <referential constraint definition>
@@ -2080,12 +2168,10 @@ RI_FKey_setdefault_del(PG_FUNCTION_ARGS)
 			 *			... ON DELETE SET DEFAULT
 			 * ----------
 			 */
-		case RI_MATCH_TYPE_UNSPECIFIED:
-		case RI_MATCH_TYPE_FULL:
-			ri_BuildQueryKeyFull(&qkey, trigdata->tg_trigger->tgoid,
-								 RI_PLAN_SETNULL_DEL_DOUPDATE,
-								 fk_rel, pk_rel,
-								 tgnargs, tgargs);
+		case FKCONSTR_MATCH_UNSPECIFIED:
+		case FKCONSTR_MATCH_FULL:
+			ri_BuildQueryKeyFull(&qkey, &riinfo,
+								 RI_PLAN_SETNULL_DEL_DOUPDATE);
 
 			switch (ri_NullCheck(pk_rel, old_row, &qkey, RI_KEYPAIR_PK_IDX))
 			{
@@ -2116,11 +2202,11 @@ RI_FKey_setdefault_del(PG_FUNCTION_ARGS)
 			 * default value could potentially change between calls.
 			 */
 			{
-				char		querystr[MAX_QUOTED_REL_NAME_LEN + 100 +
-							(MAX_QUOTED_NAME_LEN + 32) * RI_MAX_NUMKEYS * 2];
-				char		qualstr[(MAX_QUOTED_NAME_LEN + 32) * RI_MAX_NUMKEYS];
+				StringInfoData querybuf;
+				StringInfoData qualbuf;
 				char		fkrelname[MAX_QUOTED_REL_NAME_LEN];
 				char		attname[MAX_QUOTED_NAME_LEN];
+				char		paramname[16];
 				const char *querysep;
 				const char *qualsep;
 				Oid			queryoids[RI_MAX_NUMKEYS];
@@ -2129,35 +2215,40 @@ RI_FKey_setdefault_del(PG_FUNCTION_ARGS)
 				/* ----------
 				 * The query string built is
 				 *	UPDATE ONLY <fktable> SET fkatt1 = DEFAULT [, ...]
-				 *			WHERE fkatt1 = $1 [AND ...]
+				 *			WHERE $1 = fkatt1 [AND ...]
 				 * The type id's for the $ parameters are those of the
-				 * corresponding PK attributes. Thus, ri_PlanCheck could
-				 * eventually fail if the parser cannot identify some way
-				 * how to compare these two types by '='.
+				 * corresponding PK attributes.
 				 * ----------
 				 */
+				initStringInfo(&querybuf);
+				initStringInfo(&qualbuf);
 				quoteRelationName(fkrelname, fk_rel);
-				snprintf(querystr, sizeof(querystr), "UPDATE ONLY %s SET", fkrelname);
-				qualstr[0] = '\0';
+				appendStringInfo(&querybuf, "UPDATE ONLY %s SET", fkrelname);
 				querysep = "";
 				qualsep = "WHERE";
-				for (i = 0; i < qkey.nkeypairs; i++)
+				for (i = 0; i < riinfo.nkeys; i++)
 				{
+					Oid		pk_type = RIAttType(pk_rel, riinfo.pk_attnums[i]);
+					Oid		fk_type = RIAttType(fk_rel, riinfo.fk_attnums[i]);
+
 					quoteOneName(attname,
-								 tgargs[RI_FIRST_ATTNAME_ARGNO + i * 2 + RI_KEYPAIR_FK_IDX]);
-					snprintf(querystr + strlen(querystr), sizeof(querystr) - strlen(querystr), "%s %s = DEFAULT",
-							 querysep, attname);
-					snprintf(qualstr + strlen(qualstr), sizeof(qualstr) - strlen(qualstr), " %s %s = $%d",
-							 qualsep, attname, i + 1);
+								 RIAttName(fk_rel, riinfo.fk_attnums[i]));
+					appendStringInfo(&querybuf,
+									 "%s %s = DEFAULT",
+									 querysep, attname);
+					sprintf(paramname, "$%d", i + 1);
+					ri_GenerateQual(&qualbuf, qualsep,
+									paramname, pk_type,
+									riinfo.pf_eq_oprs[i],
+									attname, fk_type);
 					querysep = ",";
 					qualsep = "AND";
-					queryoids[i] = SPI_gettypeid(pk_rel->rd_att,
-										 qkey.keypair[i][RI_KEYPAIR_PK_IDX]);
+					queryoids[i] = pk_type;
 				}
-				strcat(querystr, qualstr);
+				appendStringInfoString(&querybuf, qualbuf.data);
 
 				/* Prepare the plan, don't save it */
-				qplan = ri_PlanCheck(querystr, qkey.nkeypairs, queryoids,
+				qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys, queryoids,
 									 &qkey, fk_rel, pk_rel, false);
 			}
 
@@ -2169,7 +2260,7 @@ RI_FKey_setdefault_del(PG_FUNCTION_ARGS)
 							old_row, NULL,
 							true,		/* must detect new rows */
 							SPI_OK_UPDATE,
-							tgargs[RI_CONSTRAINT_NAME_ARGNO]);
+							NameStr(riinfo.conname));
 
 			if (SPI_finish() != SPI_OK_FINISH)
 				elog(ERROR, "SPI_finish failed");
@@ -2191,7 +2282,7 @@ RI_FKey_setdefault_del(PG_FUNCTION_ARGS)
 			/*
 			 * Handle MATCH PARTIAL set null delete.
 			 */
-		case RI_MATCH_TYPE_PARTIAL:
+		case FKCONSTR_MATCH_PARTIAL:
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("MATCH PARTIAL not yet implemented")));
@@ -2201,7 +2292,7 @@ RI_FKey_setdefault_del(PG_FUNCTION_ARGS)
 	/*
 	 * Never reached
 	 */
-	elog(ERROR, "invalid match_type");
+	elog(ERROR, "invalid confmatchtype");
 	return PointerGetDatum(NULL);
 }
 
@@ -2216,28 +2307,29 @@ Datum
 RI_FKey_setdefault_upd(PG_FUNCTION_ARGS)
 {
 	TriggerData *trigdata = (TriggerData *) fcinfo->context;
-	int			tgnargs;
-	char	  **tgargs;
+	RI_ConstraintInfo riinfo;
 	Relation	fk_rel;
 	Relation	pk_rel;
 	HeapTuple	new_row;
 	HeapTuple	old_row;
 	RI_QueryKey qkey;
 	void	   *qplan;
-	int			match_type;
 
 	/*
 	 * Check that this is a valid trigger call on the right time and event.
 	 */
 	ri_CheckTrigger(fcinfo, "RI_FKey_setdefault_upd", RI_TRIGTYPE_UPDATE);
 
-	tgnargs = trigdata->tg_trigger->tgnargs;
-	tgargs = trigdata->tg_trigger->tgargs;
+	/*
+	 * Get arguments.
+	 */
+	ri_FetchConstraintInfo(&riinfo,
+						   trigdata->tg_trigger, trigdata->tg_relation, true);
 
 	/*
 	 * Nothing to do if no column names to compare given
 	 */
-	if (tgnargs == 4)
+	if (riinfo.nkeys == 0)
 		return PointerGetDatum(NULL);
 
 	/*
@@ -2246,14 +2338,12 @@ RI_FKey_setdefault_upd(PG_FUNCTION_ARGS)
 	 * fk_rel is opened in RowExclusiveLock mode since that's what our
 	 * eventual UPDATE will get on it.
 	 */
-	fk_rel = heap_open(trigdata->tg_trigger->tgconstrrelid, RowExclusiveLock);
+	fk_rel = heap_open(riinfo.fk_relid, RowExclusiveLock);
 	pk_rel = trigdata->tg_relation;
 	new_row = trigdata->tg_newtuple;
 	old_row = trigdata->tg_trigtuple;
 
-	match_type = ri_DetermineMatchType(tgargs[RI_MATCH_TYPE_ARGNO]);
-
-	switch (match_type)
+	switch (riinfo.confmatchtype)
 	{
 			/* ----------
 			 * SQL3 11.9 <referential constraint definition>
@@ -2262,12 +2352,10 @@ RI_FKey_setdefault_upd(PG_FUNCTION_ARGS)
 			 *			... ON UPDATE SET DEFAULT
 			 * ----------
 			 */
-		case RI_MATCH_TYPE_UNSPECIFIED:
-		case RI_MATCH_TYPE_FULL:
-			ri_BuildQueryKeyFull(&qkey, trigdata->tg_trigger->tgoid,
-								 RI_PLAN_SETNULL_DEL_DOUPDATE,
-								 fk_rel, pk_rel,
-								 tgnargs, tgargs);
+		case FKCONSTR_MATCH_UNSPECIFIED:
+		case FKCONSTR_MATCH_FULL:
+			ri_BuildQueryKeyFull(&qkey, &riinfo,
+								 RI_PLAN_SETNULL_DEL_DOUPDATE);
 
 			switch (ri_NullCheck(pk_rel, old_row, &qkey, RI_KEYPAIR_PK_IDX))
 			{
@@ -2292,8 +2380,7 @@ RI_FKey_setdefault_upd(PG_FUNCTION_ARGS)
 			/*
 			 * No need to do anything if old and new keys are equal
 			 */
-			if (ri_KeysEqual(pk_rel, old_row, new_row, &qkey,
-							 RI_KEYPAIR_PK_IDX))
+			if (ri_KeysEqual(pk_rel, old_row, new_row, &riinfo, true))
 			{
 				heap_close(fk_rel, RowExclusiveLock);
 				return PointerGetDatum(NULL);
@@ -2308,11 +2395,11 @@ RI_FKey_setdefault_upd(PG_FUNCTION_ARGS)
 			 * default value could potentially change between calls.
 			 */
 			{
-				char		querystr[MAX_QUOTED_REL_NAME_LEN + 100 +
-							(MAX_QUOTED_NAME_LEN + 32) * RI_MAX_NUMKEYS * 2];
-				char		qualstr[(MAX_QUOTED_NAME_LEN + 32) * RI_MAX_NUMKEYS];
+				StringInfoData querybuf;
+				StringInfoData qualbuf;
 				char		fkrelname[MAX_QUOTED_REL_NAME_LEN];
 				char		attname[MAX_QUOTED_NAME_LEN];
+				char		paramname[16];
 				const char *querysep;
 				const char *qualsep;
 				Oid			queryoids[RI_MAX_NUMKEYS];
@@ -2321,45 +2408,50 @@ RI_FKey_setdefault_upd(PG_FUNCTION_ARGS)
 				/* ----------
 				 * The query string built is
 				 *	UPDATE ONLY <fktable> SET fkatt1 = DEFAULT [, ...]
-				 *			WHERE fkatt1 = $1 [AND ...]
+				 *			WHERE $1 = fkatt1 [AND ...]
 				 * The type id's for the $ parameters are those of the
-				 * corresponding PK attributes. Thus, ri_PlanCheck could
-				 * eventually fail if the parser cannot identify some way
-				 * how to compare these two types by '='.
+				 * corresponding PK attributes.
 				 * ----------
 				 */
+				initStringInfo(&querybuf);
+				initStringInfo(&qualbuf);
 				quoteRelationName(fkrelname, fk_rel);
-				snprintf(querystr, sizeof(querystr), "UPDATE ONLY %s SET", fkrelname);
-				qualstr[0] = '\0';
+				appendStringInfo(&querybuf, "UPDATE ONLY %s SET", fkrelname);
 				querysep = "";
 				qualsep = "WHERE";
-				for (i = 0; i < qkey.nkeypairs; i++)
+				for (i = 0; i < riinfo.nkeys; i++)
 				{
+					Oid		pk_type = RIAttType(pk_rel, riinfo.pk_attnums[i]);
+					Oid		fk_type = RIAttType(fk_rel, riinfo.fk_attnums[i]);
+
 					quoteOneName(attname,
-								 tgargs[RI_FIRST_ATTNAME_ARGNO + i * 2 + RI_KEYPAIR_FK_IDX]);
+								 RIAttName(fk_rel, riinfo.fk_attnums[i]));
 
 					/*
 					 * MATCH <unspecified> - only change columns corresponding
 					 * to changed columns in pk_rel's key
 					 */
-					if (match_type == RI_MATCH_TYPE_FULL ||
-						!ri_OneKeyEqual(pk_rel, i, old_row,
-										new_row, &qkey, RI_KEYPAIR_PK_IDX))
+					if (riinfo.confmatchtype == FKCONSTR_MATCH_FULL ||
+						!ri_OneKeyEqual(pk_rel, i, old_row, new_row,
+										&riinfo, true))
 					{
-						snprintf(querystr + strlen(querystr), sizeof(querystr) - strlen(querystr), "%s %s = DEFAULT",
-								 querysep, attname);
+						appendStringInfo(&querybuf,
+										 "%s %s = DEFAULT",
+										 querysep, attname);
 						querysep = ",";
 					}
-					snprintf(qualstr + strlen(qualstr), sizeof(qualstr) - strlen(qualstr), " %s %s = $%d",
-							 qualsep, attname, i + 1);
+					sprintf(paramname, "$%d", i + 1);
+					ri_GenerateQual(&qualbuf, qualsep,
+									paramname, pk_type,
+									riinfo.pf_eq_oprs[i],
+									attname, fk_type);
 					qualsep = "AND";
-					queryoids[i] = SPI_gettypeid(pk_rel->rd_att,
-										 qkey.keypair[i][RI_KEYPAIR_PK_IDX]);
+					queryoids[i] = pk_type;
 				}
-				strcat(querystr, qualstr);
+				appendStringInfoString(&querybuf, qualbuf.data);
 
 				/* Prepare the plan, don't save it */
-				qplan = ri_PlanCheck(querystr, qkey.nkeypairs, queryoids,
+				qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys, queryoids,
 									 &qkey, fk_rel, pk_rel, false);
 			}
 
@@ -2371,7 +2463,7 @@ RI_FKey_setdefault_upd(PG_FUNCTION_ARGS)
 							old_row, NULL,
 							true,		/* must detect new rows */
 							SPI_OK_UPDATE,
-							tgargs[RI_CONSTRAINT_NAME_ARGNO]);
+							NameStr(riinfo.conname));
 
 			if (SPI_finish() != SPI_OK_FINISH)
 				elog(ERROR, "SPI_finish failed");
@@ -2393,7 +2485,7 @@ RI_FKey_setdefault_upd(PG_FUNCTION_ARGS)
 			/*
 			 * Handle MATCH PARTIAL set null delete.
 			 */
-		case RI_MATCH_TYPE_PARTIAL:
+		case FKCONSTR_MATCH_PARTIAL:
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("MATCH PARTIAL not yet implemented")));
@@ -2403,7 +2495,7 @@ RI_FKey_setdefault_upd(PG_FUNCTION_ARGS)
 	/*
 	 * Never reached
 	 */
-	elog(ERROR, "invalid match_type");
+	elog(ERROR, "invalid confmatchtype");
 	return PointerGetDatum(NULL);
 }
 
@@ -2420,57 +2512,37 @@ bool
 RI_FKey_keyequal_upd_pk(Trigger *trigger, Relation pk_rel,
 						HeapTuple old_row, HeapTuple new_row)
 {
-	int			tgnargs;
-	char	  **tgargs;
+	RI_ConstraintInfo riinfo;
 	Relation	fk_rel;
 	RI_QueryKey qkey;
 
 	/*
-	 * Check for the correct # of call arguments
+	 * Get arguments.
 	 */
-	tgnargs = trigger->tgnargs;
-	tgargs = trigger->tgargs;
-	if (tgnargs < 4 ||
-		tgnargs > RI_MAX_ARGUMENTS ||
-		(tgnargs % 2) != 0)
-		ereport(ERROR,
-				(errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
-				 errmsg("function \"%s\" called with wrong number of trigger arguments",
-						"RI_FKey_keyequal_upd")));
+	ri_FetchConstraintInfo(&riinfo, trigger, pk_rel, true);
 
 	/*
 	 * Nothing to do if no column names to compare given
 	 */
-	if (tgnargs == 4)
+	if (riinfo.nkeys == 0)
 		return true;
 
-	if (!OidIsValid(trigger->tgconstrrelid))
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-		   errmsg("no target table given for trigger \"%s\" on table \"%s\"",
-				  trigger->tgname,
-				  RelationGetRelationName(pk_rel)),
-		  errhint("Remove this referential integrity trigger and its mates, "
-				  "then do ALTER TABLE ADD CONSTRAINT.")));
+	fk_rel = heap_open(riinfo.fk_relid, AccessShareLock);
 
-	fk_rel = heap_open(trigger->tgconstrrelid, 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(riinfo.pk_relid, AccessShareLock);
 
-	pk_rel = heap_open(trigger->tgconstrrelid, 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;
+	HeapTuple	opertup;
+	Form_pg_operator operform;
+	char	   *oprname;
+	char	   *nspname;
 
-	elog(ERROR, "unrecognized referential integrity match type \"%s\"", str);
-	return 0;
+	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;
 	}
+}
+
+
+/*
+ * 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 for the correct # of call arguments
+	 * 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".
 	 */
-	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)));
-
-	/*
-	 * Check that tgconstrrelid is known.  We need to check here because of
-	 * ancient pg_dump bug; see notes in CreateTrigger().
-	 */
-	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)
 	{
-		/*
-		 * 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);
-		if (isnull)
-			return false;
+		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.
 		 */
-		newvalue = SPI_getbinval(newtup, rel->rd_att,
-								 key->keypair[i][pairidx], &isnull);
+		oldvalue = SPI_getbinval(oldtup, tupdesc, attnums[i], &isnull);
 		if (isnull)
 			return false;
 
 		/*
-		 * Get the attribute's type OID and call the '=' operator to compare
-		 * the values.
+		 * Get one attribute's newvalue. If it is NULL - they're not equal.
 		 */
-		typeid = SPI_gettypeid(rel->rd_att, key->keypair[i][pairidx]);
-		if (!ri_AttributesEqual(typeid, oldvalue, newvalue))
+		newvalue = SPI_getbinval(newtup, tupdesc, attnums[i], &isnull);
+		if (isnull)
+			return false;
+
+		/*
+		 * Compare them with the appropriate equality operator.
+		 */
+		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)
 	{
-		/*
-		 * Get one attributes oldvalue. If it is NULL - they're not equal.
-		 */
-		oldvalue = SPI_getbinval(oldtup, rel->rd_att,
-								 key->keypair[i][pairidx], &isnull);
-		if (isnull)
-			continue;
-
-		/*
-		 * Get one attributes oldvalue. If it is NULL - they're not equal.
-		 */
-		newvalue = SPI_getbinval(newtup, rel->rd_att,
-								 key->keypair[i][pairidx], &isnull);
-		if (isnull)
-			continue;
-
-		/*
-		 * Get the attributes type OID and call the '=' operator to compare
-		 * the values.
-		 */
-		typeid = SPI_gettypeid(rel->rd_att, key->keypair[i][pairidx]);
-		if (!ri_AttributesEqual(typeid, oldvalue, newvalue))
-			continue;
-		keys_unequal = false;
+		attnums = riinfo->pk_attnums;
+		eq_oprs = riinfo->pp_eq_oprs;
+	}
+	else
+	{
+		attnums = riinfo->fk_attnums;
+		eq_oprs = riinfo->ff_eq_oprs;
 	}
 
-	return keys_unequal;
+	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, tupdesc, attnums[i], &isnull);
+		if (isnull)
+			continue;
+
+		/*
+		 * Get one attribute's newvalue. If it is NULL - they're not equal.
+		 */
+		newvalue = SPI_getbinval(newtup, tupdesc, attnums[i], &isnull);
+		if (isnull)
+			continue;
+
+		/*
+		 * Compare them with the appropriate equality operator.
+		 */
+		if (ri_AttributesEqual(eq_oprs[i], RIAttType(rel, attnums[i]),
+							   oldvalue, newvalue))
+			return false;		/* found two equal items */
+	}
+
+	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)
 {
-	TypeCacheEntry *typentry;
+	RI_CompareHashEntry *entry = ri_HashCompareOp(eq_opr, typeid);
 
-	/*
-	 * Find the data type in the typcache, and ask for eq_opr info.
-	 */
-	typentry = lookup_type_cache(typeid, TYPECACHE_EQ_OPR_FINFO);
+	/* 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 */
+	}
 
-	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))));
-
-	/*
-	 * Call the type specific '=' function
-	 */
-	return DatumGetBool(FunctionCall2(&(typentry->eq_opr_finfo),
+	/* 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)
+{
+	RI_CompareKey key;
+	RI_CompareHashEntry *entry;
+	bool		found;
+
+	/*
+	 * On the first call initialize the hashtable
+	 */
+	if (!ri_compare_cache)
+		ri_InitHashTables();
+
+	/*
+	 * 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;
+
+	/*
+	 * 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.
+	 */
+	if (!entry->valid)
+	{
+		Oid		lefttype,
+				righttype,
+				castfunc;
+
+		/* We always need to know how to call the equality operator */
+		fmgr_info_cxt(get_opcode(eq_opr), &entry->eq_opr_finfo,
+					  TopMemoryContext);
+
+		/*
+		 * If we chose to use a cast from FK to PK type, we may have to
+		 * apply the cast function to get to the operator's input type.
+		 */
+		op_input_types(eq_opr, &lefttype, &righttype);
+		Assert(lefttype == righttype);
+		if (typeid == lefttype)
+			castfunc = InvalidOid;				/* simplest case */
+		else if (!find_coercion_pathway(lefttype, typeid, COERCION_IMPLICIT,
+										&castfunc))
+		{
+			/* If target is ANYARRAY, assume it's OK, else punt. */
+			if (lefttype != ANYARRAYOID)
+				elog(ERROR, "no conversion function from %s to %s",
+					 format_type_be(typeid),
+					 format_type_be(lefttype));
+		}
+		if (OidIsValid(castfunc))
+			fmgr_info_cxt(castfunc, &entry->cast_func_finfo,
+						  TopMemoryContext);
+		else
+			entry->cast_func_finfo.fn_oid = InvalidOid;
+		entry->valid = true;
+	}
+
+	return entry;
+}
+
+
 /*
  * Given a trigger function OID, determine whether it is an RI trigger,
  * and if so whether it is attached to PK or FK relation.
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 3cd317361f4..064b8e07f76 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -9,7 +9,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.248 2007/02/03 14:06:54 petere Exp $
+ *	  $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.249 2007/02/14 01:58:57 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -875,30 +875,15 @@ static char *
 pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags)
 {
-	StringInfoData buf;
-	Relation	conDesc;
-	SysScanDesc conscan;
-	ScanKeyData skey[1];
 	HeapTuple	tup;
 	Form_pg_constraint conForm;
+	StringInfoData buf;
 
-	/*
-	 * Fetch the pg_constraint row.  There's no syscache for pg_constraint so
-	 * we must do it the hard way.
-	 */
-	conDesc = heap_open(ConstraintRelationId, AccessShareLock);
-
-	ScanKeyInit(&skey[0],
-				ObjectIdAttributeNumber,
-				BTEqualStrategyNumber, F_OIDEQ,
-				ObjectIdGetDatum(constraintId));
-
-	conscan = systable_beginscan(conDesc, ConstraintOidIndexId, true,
-								 SnapshotNow, 1, skey);
-
-	tup = systable_getnext(conscan);
-	if (!HeapTupleIsValid(tup))
-		elog(ERROR, "could not find tuple for constraint %u", constraintId);
+	tup = SearchSysCache(CONSTROID,
+						 ObjectIdGetDatum(constraintId),
+						 0, 0, 0);
+	if (!HeapTupleIsValid(tup)) /* should not happen */
+		elog(ERROR, "cache lookup failed for constraint %u", constraintId);
 	conForm = (Form_pg_constraint) GETSTRUCT(tup);
 
 	initStringInfo(&buf);
@@ -922,8 +907,8 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 				appendStringInfo(&buf, "FOREIGN KEY (");
 
 				/* Fetch and build referencing-column list */
-				val = heap_getattr(tup, Anum_pg_constraint_conkey,
-								   RelationGetDescr(conDesc), &isnull);
+				val = SysCacheGetAttr(CONSTROID, tup,
+									  Anum_pg_constraint_conkey, &isnull);
 				if (isnull)
 					elog(ERROR, "null conkey for constraint %u",
 						 constraintId);
@@ -935,8 +920,8 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 								 generate_relation_name(conForm->confrelid));
 
 				/* Fetch and build referenced-column list */
-				val = heap_getattr(tup, Anum_pg_constraint_confkey,
-								   RelationGetDescr(conDesc), &isnull);
+				val = SysCacheGetAttr(CONSTROID, tup,
+									  Anum_pg_constraint_confkey, &isnull);
 				if (isnull)
 					elog(ERROR, "null confkey for constraint %u",
 						 constraintId);
@@ -1038,8 +1023,8 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 					appendStringInfo(&buf, "UNIQUE (");
 
 				/* Fetch and build target column list */
-				val = heap_getattr(tup, Anum_pg_constraint_conkey,
-								   RelationGetDescr(conDesc), &isnull);
+				val = SysCacheGetAttr(CONSTROID, tup,
+									  Anum_pg_constraint_conkey, &isnull);
 				if (isnull)
 					elog(ERROR, "null conkey for constraint %u",
 						 constraintId);
@@ -1071,8 +1056,8 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 				List	   *context;
 
 				/* Fetch constraint expression in parsetree form */
-				val = heap_getattr(tup, Anum_pg_constraint_conbin,
-								   RelationGetDescr(conDesc), &isnull);
+				val = SysCacheGetAttr(CONSTROID, tup,
+									  Anum_pg_constraint_conbin, &isnull);
 				if (isnull)
 					elog(ERROR, "null conbin for constraint %u",
 						 constraintId);
@@ -1115,8 +1100,7 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 	}
 
 	/* Cleanup */
-	systable_endscan(conscan);
-	heap_close(conDesc, AccessShareLock);
+	ReleaseSysCache(tup);
 
 	return buf.data;
 }
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 8946cb73151..d6ff883c924 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/utils/cache/lsyscache.c,v 1.147 2007/01/30 01:33:36 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/utils/cache/lsyscache.c,v 1.148 2007/02/14 01:58:57 tgl Exp $
  *
  * NOTES
  *	  Eventually, the index information should go through here, too.
@@ -20,6 +20,7 @@
 #include "bootstrap/bootstrap.h"
 #include "catalog/pg_amop.h"
 #include "catalog/pg_amproc.h"
+#include "catalog/pg_constraint.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_operator.h"
@@ -897,10 +898,37 @@ get_atttypetypmod(Oid relid, AttrNumber attnum,
 	ReleaseSysCache(tp);
 }
 
-/*				---------- INDEX CACHE ----------						 */
+/*				---------- CONSTRAINT CACHE ----------					 */
 
-/*		watch this space...
+/*
+ * get_constraint_name
+ *		Returns the name of a given pg_constraint entry.
+ *
+ * Returns a palloc'd copy of the string, or NULL if no such constraint.
+ *
+ * NOTE: since constraint name is not unique, be wary of code that uses this
+ * for anything except preparing error messages.
  */
+char *
+get_constraint_name(Oid conoid)
+{
+	HeapTuple	tp;
+
+	tp = SearchSysCache(CONSTROID,
+						ObjectIdGetDatum(conoid),
+						0, 0, 0);
+	if (HeapTupleIsValid(tp))
+	{
+		Form_pg_constraint contup = (Form_pg_constraint) GETSTRUCT(tp);
+		char	   *result;
+
+		result = pstrdup(NameStr(contup->conname));
+		ReleaseSysCache(tp);
+		return result;
+	}
+	else
+		return NULL;
+}
 
 /*				---------- OPCLASS CACHE ----------						 */
 
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index 5b33ff423ea..75f290dda61 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/utils/cache/syscache.c,v 1.110 2007/01/05 22:19:43 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/utils/cache/syscache.c,v 1.111 2007/02/14 01:58:57 tgl Exp $
  *
  * NOTES
  *	  These routines allow the parser/planner/executor to perform
@@ -28,6 +28,7 @@
 #include "catalog/pg_auth_members.h"
 #include "catalog/pg_authid.h"
 #include "catalog/pg_cast.h"
+#include "catalog/pg_constraint.h"
 #include "catalog/pg_conversion.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_language.h"
@@ -298,7 +299,19 @@ static const struct cachedesc cacheinfo[] = {
 		},
 		128
 	},
-	{ConversionRelationId,		/* CONOID */
+	{ConstraintRelationId,		/* CONSTROID */
+		ConstraintOidIndexId,
+		0,
+		1,
+		{
+			ObjectIdAttributeNumber,
+			0,
+			0,
+			0
+		},
+		1024
+	},
+	{ConversionRelationId,		/* CONVOID */
 		ConversionOidIndexId,
 		0,
 		1,
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 2fec7326d50..eef9a4c875a 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -12,7 +12,7 @@
  *	by PostgreSQL
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/bin/pg_dump/pg_dump.c,v 1.459 2007/01/25 03:30:43 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/bin/pg_dump/pg_dump.c,v 1.460 2007/02/14 01:58:57 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -3841,11 +3841,30 @@ getTriggers(TableInfo tblinfo[], int numTables)
 		selectSourceSchema(tbinfo->dobj.namespace->dobj.name);
 
 		resetPQExpBuffer(query);
-		if (g_fout->remoteVersion >= 70300)
+		if (g_fout->remoteVersion >= 80300)
 		{
 			/*
 			 * We ignore triggers that are tied to a foreign-key constraint
 			 */
+			appendPQExpBuffer(query,
+							  "SELECT tgname, "
+							  "tgfoid::pg_catalog.regproc as tgfname, "
+							  "tgtype, tgnargs, tgargs, tgenabled, "
+							  "tgisconstraint, tgconstrname, tgdeferrable, "
+							  "tgconstrrelid, tginitdeferred, tableoid, oid, "
+					 "tgconstrrelid::pg_catalog.regclass as tgconstrrelname "
+							  "from pg_catalog.pg_trigger t "
+							  "where tgrelid = '%u'::pg_catalog.oid "
+							  "and tgconstraint = 0",
+							  tbinfo->dobj.catId.oid);
+		}
+		else if (g_fout->remoteVersion >= 70300)
+		{
+			/*
+			 * We ignore triggers that are tied to a foreign-key constraint,
+			 * but in these versions we have to grovel through pg_constraint
+			 * to find out
+			 */
 			appendPQExpBuffer(query,
 							  "SELECT tgname, "
 							  "tgfoid::pg_catalog.regproc as tgfname, "
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6469b6aee41..3e830d9eb9a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3,7 +3,7 @@
  *
  * Copyright (c) 2000-2007, PostgreSQL Global Development Group
  *
- * $PostgreSQL: pgsql/src/bin/psql/describe.c,v 1.150 2007/01/20 21:17:30 neilc Exp $
+ * $PostgreSQL: pgsql/src/bin/psql/describe.c,v 1.151 2007/02/14 01:58:58 tgl Exp $
  */
 #include "postgres_fe.h"
 #include "describe.h"
@@ -1128,12 +1128,8 @@ describeOneTableDetails(const char *schemaname,
 							  "FROM pg_catalog.pg_trigger t\n"
 							  "WHERE t.tgrelid = '%s' "
 							  "AND t.tgenabled "
-							  "AND (NOT t.tgisconstraint "
-							  " OR NOT EXISTS"
-							  "  (SELECT 1 FROM pg_catalog.pg_depend d "
-							  "   JOIN pg_catalog.pg_constraint c ON (d.refclassid = c.tableoid AND d.refobjid = c.oid) "
-							  "   WHERE d.classid = t.tableoid AND d.objid = t.oid AND d.deptype = 'i' AND c.contype = 'f'))"
-							  "   ORDER BY 1",
+							  "AND t.tgconstraint = 0\n"
+							  "ORDER BY 1",
 							  oid);
 			result4 = PSQLexec(buf.data, false);
 			if (!result4)
@@ -1152,12 +1148,8 @@ describeOneTableDetails(const char *schemaname,
 							  "FROM pg_catalog.pg_trigger t\n"
 							  "WHERE t.tgrelid = '%s' "
 							  "AND NOT t.tgenabled "
-							  "AND (NOT t.tgisconstraint "
-							  " OR NOT EXISTS"
-							  "  (SELECT 1 FROM pg_catalog.pg_depend d "
-							  "   JOIN pg_catalog.pg_constraint c ON (d.refclassid = c.tableoid AND d.refobjid = c.oid) "
-							  "   WHERE d.classid = t.tableoid AND d.objid = t.oid AND d.deptype = 'i' AND c.contype = 'f'))"
-							  "   ORDER BY 1",
+							  "AND t.tgconstraint = 0\n"
+							  "ORDER BY 1",
 							  oid);
 			result7 = PSQLexec(buf.data, false);
 			if (!result7)
diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h
index 5bdda05960e..aec8dee45cc 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -37,7 +37,7 @@
  * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.383 2007/02/09 03:35:34 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.384 2007/02/14 01:58:58 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -53,6 +53,6 @@
  */
 
 /*							yyyymmddN */
-#define CATALOG_VERSION_NO	200702081
+#define CATALOG_VERSION_NO	200702131
 
 #endif
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index 73e2cf0cc7d..baa650a8edb 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -8,7 +8,7 @@
  * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/catalog/indexing.h,v 1.97 2007/01/05 22:19:52 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/indexing.h,v 1.98 2007/02/14 01:58:58 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -214,9 +214,6 @@ DECLARE_UNIQUE_INDEX(pg_tablespace_spcname_index, 2698, on pg_tablespace using b
 /* This following index is not used for a cache and is not unique */
 DECLARE_INDEX(pg_trigger_tgconstrname_index, 2699, on pg_trigger using btree(tgconstrname name_ops));
 #define TriggerConstrNameIndexId  2699
-/* This following index is not used for a cache and is not unique */
-DECLARE_INDEX(pg_trigger_tgconstrrelid_index, 2700, on pg_trigger using btree(tgconstrrelid oid_ops));
-#define TriggerConstrRelidIndexId  2700
 DECLARE_UNIQUE_INDEX(pg_trigger_tgrelid_tgname_index, 2701, on pg_trigger using btree(tgrelid oid_ops, tgname name_ops));
 #define TriggerRelidNameIndexId  2701
 DECLARE_UNIQUE_INDEX(pg_trigger_oid_index, 2702, on pg_trigger using btree(oid oid_ops));
diff --git a/src/include/catalog/pg_constraint.h b/src/include/catalog/pg_constraint.h
index 6fbfb6a0cda..d01c22c703c 100644
--- a/src/include/catalog/pg_constraint.h
+++ b/src/include/catalog/pg_constraint.h
@@ -8,7 +8,7 @@
  * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/catalog/pg_constraint.h,v 1.24 2007/01/05 22:19:52 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/pg_constraint.h,v 1.25 2007/02/14 01:58:58 tgl Exp $
  *
  * NOTES
  *	  the genbki.sh script reads this file and generates .bki
@@ -91,6 +91,24 @@ CATALOG(pg_constraint,2606)
 	 */
 	int2		confkey[1];
 
+	/*
+	 * If a foreign key, the OIDs of the PK = FK equality operators for each
+	 * column of the constraint
+	 */
+	Oid			conpfeqop[1];
+
+	/*
+	 * If a foreign key, the OIDs of the PK = PK equality operators for each
+	 * column of the constraint (i.e., equality for the referenced columns)
+	 */
+	Oid			conppeqop[1];
+
+	/*
+	 * If a foreign key, the OIDs of the FK = FK equality operators for each
+	 * column of the constraint (i.e., equality for the referencing columns)
+	 */
+	Oid			conffeqop[1];
+
 	/*
 	 * If a check constraint, nodeToString representation of expression
 	 */
@@ -113,7 +131,7 @@ typedef FormData_pg_constraint *Form_pg_constraint;
  *		compiler constants for pg_constraint
  * ----------------
  */
-#define Natts_pg_constraint					15
+#define Natts_pg_constraint					18
 #define Anum_pg_constraint_conname			1
 #define Anum_pg_constraint_connamespace		2
 #define Anum_pg_constraint_contype			3
@@ -127,8 +145,11 @@ typedef FormData_pg_constraint *Form_pg_constraint;
 #define Anum_pg_constraint_confmatchtype	11
 #define Anum_pg_constraint_conkey			12
 #define Anum_pg_constraint_confkey			13
-#define Anum_pg_constraint_conbin			14
-#define Anum_pg_constraint_consrc			15
+#define Anum_pg_constraint_conpfeqop		14
+#define Anum_pg_constraint_conppeqop		15
+#define Anum_pg_constraint_conffeqop		16
+#define Anum_pg_constraint_conbin			17
+#define Anum_pg_constraint_consrc			18
 
 
 /* Valid values for contype */
@@ -167,6 +188,9 @@ extern Oid CreateConstraintEntry(const char *constraintName,
 					  Oid domainId,
 					  Oid foreignRelId,
 					  const int16 *foreignKey,
+					  const Oid *pfEqOp,
+					  const Oid *ppEqOp,
+					  const Oid *ffEqOp,
 					  int foreignNKeys,
 					  char foreignUpdateType,
 					  char foreignDeleteType,
@@ -184,8 +208,6 @@ extern char *ChooseConstraintName(const char *name1, const char *name2,
 					 const char *label, Oid namespace,
 					 List *others);
 
-extern char *GetConstraintNameForTrigger(Oid triggerId);
-
 extern void AlterConstraintNamespaces(Oid ownerId, Oid oldNspId,
 						  Oid newNspId, bool isType);
 
diff --git a/src/include/catalog/pg_trigger.h b/src/include/catalog/pg_trigger.h
index b5408253b34..df22089e310 100644
--- a/src/include/catalog/pg_trigger.h
+++ b/src/include/catalog/pg_trigger.h
@@ -1,11 +1,14 @@
 /*-------------------------------------------------------------------------
  *
  * pg_trigger.h
+ *	  definition of the system "trigger" relation (pg_trigger)
+ *	  along with the relation's initial contents.
  *
  *
  * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
- * $PostgreSQL: pgsql/src/include/catalog/pg_trigger.h,v 1.26 2007/01/05 22:19:53 momjian Exp $
+ *
+ * $PostgreSQL: pgsql/src/include/catalog/pg_trigger.h,v 1.27 2007/02/14 01:58:58 tgl Exp $
  *
  * NOTES
  *	  the genbki.sh script reads this file and generates .bki
@@ -26,23 +29,30 @@
 /* ----------------
  *		pg_trigger definition.	cpp turns this into
  *		typedef struct FormData_pg_trigger
+ *
+ * Note: when tgconstraint is nonzero, tgisconstraint must be true, and
+ * tgconstrname, tgconstrrelid, tgdeferrable, tginitdeferred are redundant
+ * with the referenced pg_constraint entry.  The reason we keep these fields
+ * is that we support "stand-alone" constraint triggers with no corresponding
+ * pg_constraint entry.
  * ----------------
  */
 #define TriggerRelationId  2620
 
 CATALOG(pg_trigger,2620)
 {
-	Oid			tgrelid;		/* triggered relation */
-	NameData	tgname;			/* trigger' name */
+	Oid			tgrelid;		/* relation trigger is attached to */
+	NameData	tgname;			/* trigger's name */
 	Oid			tgfoid;			/* OID of function to be called */
 	int2		tgtype;			/* BEFORE/AFTER UPDATE/DELETE/INSERT
-								 * ROW/STATEMENT */
+								 * ROW/STATEMENT; see below */
 	bool		tgenabled;		/* trigger is enabled/disabled */
-	bool		tgisconstraint; /* trigger is a RI constraint */
-	NameData	tgconstrname;	/* RI constraint name */
-	Oid			tgconstrrelid;	/* RI table of foreign key definition */
-	bool		tgdeferrable;	/* RI trigger is deferrable */
-	bool		tginitdeferred; /* RI trigger is deferred initially */
+	bool		tgisconstraint; /* trigger is a constraint trigger */
+	NameData	tgconstrname;	/* constraint name */
+	Oid			tgconstrrelid;	/* constraint's FROM table, if any */
+	Oid			tgconstraint;	/* owning pg_constraint entry, if any */
+	bool		tgdeferrable;	/* constraint trigger is deferrable */
+	bool		tginitdeferred; /* constraint trigger is deferred initially */
 	int2		tgnargs;		/* # of extra arguments in tgargs */
 
 	/* VARIABLE LENGTH FIELDS: */
@@ -61,7 +71,7 @@ typedef FormData_pg_trigger *Form_pg_trigger;
  *		compiler constants for pg_trigger
  * ----------------
  */
-#define Natts_pg_trigger				13
+#define Natts_pg_trigger				14
 #define Anum_pg_trigger_tgrelid			1
 #define Anum_pg_trigger_tgname			2
 #define Anum_pg_trigger_tgfoid			3
@@ -70,18 +80,21 @@ typedef FormData_pg_trigger *Form_pg_trigger;
 #define Anum_pg_trigger_tgisconstraint	6
 #define Anum_pg_trigger_tgconstrname	7
 #define Anum_pg_trigger_tgconstrrelid	8
-#define Anum_pg_trigger_tgdeferrable	9
-#define Anum_pg_trigger_tginitdeferred	10
-#define Anum_pg_trigger_tgnargs			11
-#define Anum_pg_trigger_tgattr			12
-#define Anum_pg_trigger_tgargs			13
+#define Anum_pg_trigger_tgconstraint	9
+#define Anum_pg_trigger_tgdeferrable	10
+#define Anum_pg_trigger_tginitdeferred	11
+#define Anum_pg_trigger_tgnargs			12
+#define Anum_pg_trigger_tgattr			13
+#define Anum_pg_trigger_tgargs			14
 
+/* Bits within tgtype */
 #define TRIGGER_TYPE_ROW				(1 << 0)
 #define TRIGGER_TYPE_BEFORE				(1 << 1)
 #define TRIGGER_TYPE_INSERT				(1 << 2)
 #define TRIGGER_TYPE_DELETE				(1 << 3)
 #define TRIGGER_TYPE_UPDATE				(1 << 4)
 
+/* Macros for manipulating tgtype */
 #define TRIGGER_CLEAR_TYPE(type)		((type) = 0)
 
 #define TRIGGER_SETT_ROW(type)			((type) |= TRIGGER_TYPE_ROW)
diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h
index 284815b47f7..ac9eb7296d1 100644
--- a/src/include/commands/trigger.h
+++ b/src/include/commands/trigger.h
@@ -6,7 +6,7 @@
  * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/commands/trigger.h,v 1.60 2007/01/05 22:19:54 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/commands/trigger.h,v 1.61 2007/02/14 01:58:58 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -78,34 +78,8 @@ typedef struct TriggerData
 #define TRIGGER_FIRED_AFTER(event)				\
 		(!TRIGGER_FIRED_BEFORE (event))
 
-/*
- * RI trigger function arguments are stored in pg_trigger.tgargs bytea
- *
- *	 constrname\0fkrel\0pkrel\0matchtype\0fkatt\0pkatt\0fkatt\0pkatt\0...
- *
- * There are one or more pairs of fkatt/pkatt names.
- *
- * The relation names are no longer of much use since they are not
- * guaranteed unique; they are present only for backwards compatibility.
- * Use the tgrelid and tgconstrrelid fields to identify the referenced
- * relations, instead.	(But note that which is which will depend on which
- * trigger you are looking at!)
- */
-#define RI_CONSTRAINT_NAME_ARGNO		0
-#define RI_FK_RELNAME_ARGNO				1
-#define RI_PK_RELNAME_ARGNO				2
-#define RI_MATCH_TYPE_ARGNO				3
-#define RI_FIRST_ATTNAME_ARGNO			4		/* first attname pair starts
-												 * here */
 
-#define RI_KEYPAIR_FK_IDX				0
-#define RI_KEYPAIR_PK_IDX				1
-
-#define RI_MAX_NUMKEYS					INDEX_MAX_KEYS
-#define RI_MAX_ARGUMENTS		(RI_FIRST_ATTNAME_ARGNO + (RI_MAX_NUMKEYS * 2))
-
-
-extern Oid	CreateTrigger(CreateTrigStmt *stmt, bool forConstraint);
+extern Oid	CreateTrigger(CreateTrigStmt *stmt, Oid constraintOid);
 
 extern void DropTrigger(Oid relid, const char *trigname,
 			DropBehavior behavior, bool missing_ok);
@@ -175,10 +149,10 @@ extern bool RI_FKey_keyequal_upd_pk(Trigger *trigger, Relation pk_rel,
 						HeapTuple old_row, HeapTuple new_row);
 extern bool RI_FKey_keyequal_upd_fk(Trigger *trigger, Relation fk_rel,
 						HeapTuple old_row, HeapTuple new_row);
-extern bool RI_Initial_Check(FkConstraint *fkconstraint,
-				 Relation rel,
-				 Relation pkrel);
+extern bool RI_Initial_Check(Trigger *trigger,
+							 Relation fk_rel, Relation pk_rel);
 
+/* result values for RI_FKey_trigger_type: */
 #define RI_TRIGGER_PK	1		/* is a trigger on the PK relation */
 #define RI_TRIGGER_FK	2		/* is a trigger on the FK relation */
 #define RI_TRIGGER_NONE 0		/* is not an RI trigger function */
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index 8e9bfaa1edc..78c45891fa4 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -6,7 +6,7 @@
  * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/utils/lsyscache.h,v 1.116 2007/01/30 01:33:36 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/utils/lsyscache.h,v 1.117 2007/02/14 01:58:58 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -58,6 +58,7 @@ extern Oid	get_atttype(Oid relid, AttrNumber attnum);
 extern int32 get_atttypmod(Oid relid, AttrNumber attnum);
 extern void get_atttypetypmod(Oid relid, AttrNumber attnum,
 				  Oid *typid, int32 *typmod);
+extern char *get_constraint_name(Oid conoid);
 extern Oid	get_opclass_family(Oid opclass);
 extern Oid	get_opclass_input_type(Oid opclass);
 extern RegProcedure get_opcode(Oid opno);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 8ce7e118a4f..3ac44b11cdc 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/utils/rel.h,v 1.96 2007/01/25 02:17:26 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/utils/rel.h,v 1.97 2007/02/14 01:58:58 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -56,6 +56,7 @@ typedef struct Trigger
 	bool		tgenabled;
 	bool		tgisconstraint;
 	Oid			tgconstrrelid;
+	Oid			tgconstraint;
 	bool		tgdeferrable;
 	bool		tginitdeferred;
 	int16		tgnargs;
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index b8e55e57efa..c6967251ce1 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -9,7 +9,7 @@
  * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/utils/syscache.h,v 1.67 2007/01/05 22:19:59 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/utils/syscache.h,v 1.68 2007/02/14 01:58:58 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -28,42 +28,43 @@
  *		Keep them in alphabetical order.
  */
 
-#define AGGFNOID		0
-#define AMNAME			1
-#define AMOID			2
-#define AMOPOPID		3
-#define AMOPSTRATEGY	4
-#define AMPROCNUM		5
-#define ATTNAME			6
-#define ATTNUM			7
-#define AUTHMEMMEMROLE	8
-#define AUTHMEMROLEMEM	9
-#define AUTHNAME		10
-#define AUTHOID			11
-#define CASTSOURCETARGET 12
-#define CLAAMNAMENSP	13
-#define CLAOID			14
-#define CONDEFAULT		15
-#define CONNAMENSP		16
-#define CONOID			17
-#define DATABASEOID		18
-#define INDEXRELID		19
-#define LANGNAME		20
-#define LANGOID			21
-#define NAMESPACENAME	22
-#define NAMESPACEOID	23
-#define OPERNAMENSP		24
-#define OPEROID			25
-#define OPFAMILYAMNAMENSP 26
-#define OPFAMILYOID		27
-#define PROCNAMEARGSNSP 28
-#define PROCOID			29
-#define RELNAMENSP		30
-#define RELOID			31
-#define RULERELNAME		32
-#define STATRELATT		33
-#define TYPENAMENSP		34
-#define TYPEOID			35
+#define AGGFNOID			0
+#define AMNAME				1
+#define AMOID				2
+#define AMOPOPID			3
+#define AMOPSTRATEGY		4
+#define AMPROCNUM			5
+#define ATTNAME				6
+#define ATTNUM				7
+#define AUTHMEMMEMROLE		8
+#define AUTHMEMROLEMEM		9
+#define AUTHNAME			10
+#define AUTHOID				11
+#define CASTSOURCETARGET	12
+#define CLAAMNAMENSP		13
+#define CLAOID				14
+#define CONDEFAULT			15
+#define CONNAMENSP			16
+#define CONSTROID			17
+#define CONVOID				18
+#define DATABASEOID			19
+#define INDEXRELID			20
+#define LANGNAME			21
+#define LANGOID				22
+#define NAMESPACENAME		23
+#define NAMESPACEOID		24
+#define OPERNAMENSP			25
+#define OPEROID				26
+#define OPFAMILYAMNAMENSP	27
+#define OPFAMILYOID			28
+#define PROCNAMEARGSNSP		29
+#define PROCOID				30
+#define RELNAMENSP			31
+#define RELOID				32
+#define RULERELNAME			33
+#define STATRELATT			34
+#define TYPENAMENSP			35
+#define TYPEOID				36
 
 extern void InitCatalogCache(void);
 extern void InitCatalogCachePhase2(void);
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index a1a61f710cf..11b8c24c3d6 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -195,8 +195,9 @@ DROP TABLE tmp2;
 -- is run in parallel with foreign_key.sql.
 CREATE TEMP TABLE PKTABLE (ptest1 int PRIMARY KEY);
 NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "pktable_pkey" for table "pktable"
+INSERT INTO PKTABLE VALUES(42);
 CREATE TEMP TABLE FKTABLE (ftest1 inet);
--- This next should fail, because inet=int does not exist
+-- This next should fail, because int=inet does not exist
 ALTER TABLE FKTABLE ADD FOREIGN KEY(ftest1) references pktable;
 ERROR:  foreign key constraint "fktable_ftest1_fkey" cannot be implemented
 DETAIL:  Key columns "ftest1" and "ptest1" are of incompatible types: inet and integer.
@@ -205,21 +206,40 @@ DETAIL:  Key columns "ftest1" and "ptest1" are of incompatible types: inet and i
 ALTER TABLE FKTABLE ADD FOREIGN KEY(ftest1) references pktable(ptest1);
 ERROR:  foreign key constraint "fktable_ftest1_fkey" cannot be implemented
 DETAIL:  Key columns "ftest1" and "ptest1" are of incompatible types: inet and integer.
--- This should succeed, even though they are different types
--- because varchar=int does exist
 DROP TABLE FKTABLE;
-CREATE TEMP TABLE FKTABLE (ftest1 varchar);
+-- This should succeed, even though they are different types,
+-- because int=int8 exists and is a member of the integer opfamily
+CREATE TEMP TABLE FKTABLE (ftest1 int8);
 ALTER TABLE FKTABLE ADD FOREIGN KEY(ftest1) references pktable;
-WARNING:  foreign key constraint "fktable_ftest1_fkey" will require costly sequential scans
-DETAIL:  Key columns "ftest1" and "ptest1" are of different types: character varying and integer.
--- As should this
-ALTER TABLE FKTABLE ADD FOREIGN KEY(ftest1) references pktable(ptest1);
-WARNING:  foreign key constraint "fktable_ftest1_fkey1" will require costly sequential scans
-DETAIL:  Key columns "ftest1" and "ptest1" are of different types: character varying and integer.
-DROP TABLE pktable cascade;
-NOTICE:  drop cascades to constraint fktable_ftest1_fkey1 on table fktable
-NOTICE:  drop cascades to constraint fktable_ftest1_fkey on table fktable
-DROP TABLE fktable;
+-- Check it actually works
+INSERT INTO FKTABLE VALUES(42);		-- should succeed
+INSERT INTO FKTABLE VALUES(43);		-- should fail
+ERROR:  insert or update on table "fktable" violates foreign key constraint "fktable_ftest1_fkey"
+DETAIL:  Key (ftest1)=(43) is not present in table "pktable".
+DROP TABLE FKTABLE;
+-- This should fail, because we'd have to cast numeric to int which is
+-- not an implicit coercion (or use numeric=numeric, but that's not part
+-- of the integer opfamily)
+CREATE TEMP TABLE FKTABLE (ftest1 numeric);
+ALTER TABLE FKTABLE ADD FOREIGN KEY(ftest1) references pktable;
+ERROR:  foreign key constraint "fktable_ftest1_fkey" cannot be implemented
+DETAIL:  Key columns "ftest1" and "ptest1" are of incompatible types: numeric and integer.
+DROP TABLE FKTABLE;
+DROP TABLE PKTABLE;
+-- On the other hand, this should work because int implicitly promotes to
+-- numeric, and we allow promotion on the FK side
+CREATE TEMP TABLE PKTABLE (ptest1 numeric PRIMARY KEY);
+NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "pktable_pkey" for table "pktable"
+INSERT INTO PKTABLE VALUES(42);
+CREATE TEMP TABLE FKTABLE (ftest1 int);
+ALTER TABLE FKTABLE ADD FOREIGN KEY(ftest1) references pktable;
+-- Check it actually works
+INSERT INTO FKTABLE VALUES(42);		-- should succeed
+INSERT INTO FKTABLE VALUES(43);		-- should fail
+ERROR:  insert or update on table "fktable" violates foreign key constraint "fktable_ftest1_fkey"
+DETAIL:  Key (ftest1)=(43) is not present in table "pktable".
+DROP TABLE FKTABLE;
+DROP TABLE PKTABLE;
 CREATE TEMP TABLE PKTABLE (ptest1 int, ptest2 inet,
                            PRIMARY KEY(ptest1, ptest2));
 NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "pktable_pkey" for table "pktable"
diff --git a/src/test/regress/expected/foreign_key.out b/src/test/regress/expected/foreign_key.out
index 5424731d4d0..41c2f397882 100644
--- a/src/test/regress/expected/foreign_key.out
+++ b/src/test/regress/expected/foreign_key.out
@@ -646,7 +646,7 @@ SELECT * from FKTABLE;
 UPDATE PKTABLE set ptest2=5 where ptest2=2;
 ERROR:  insert or update on table "fktable" violates foreign key constraint "constrname3"
 DETAIL:  Key (ftest1,ftest2,ftest3)=(1,-1,3) is not present in table "pktable".
-CONTEXT:  SQL statement "UPDATE ONLY "public"."fktable" SET "ftest2" = DEFAULT WHERE "ftest1" = $1 AND "ftest2" = $2 AND "ftest3" = $3"
+CONTEXT:  SQL statement "UPDATE ONLY "public"."fktable" SET "ftest2" = DEFAULT WHERE $1 OPERATOR(pg_catalog.=) "ftest1" AND $2 OPERATOR(pg_catalog.=) "ftest2" AND $3 OPERATOR(pg_catalog.=) "ftest3""
 -- Try to update something that will set default
 UPDATE PKTABLE set ptest1=0, ptest2=5, ptest3=10 where ptest2=2;
 UPDATE PKTABLE set ptest2=10 where ptest2=4;
@@ -749,7 +749,8 @@ DROP TABLE PKTABLE;
 -- Basic one column, two table setup 
 CREATE TABLE PKTABLE (ptest1 int PRIMARY KEY);
 NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "pktable_pkey" for table "pktable"
--- This next should fail, because inet=int does not exist
+INSERT INTO PKTABLE VALUES(42);
+-- This next should fail, because int=inet does not exist
 CREATE TABLE FKTABLE (ftest1 inet REFERENCES pktable);
 ERROR:  foreign key constraint "fktable_ftest1_fkey" cannot be implemented
 DETAIL:  Key columns "ftest1" and "ptest1" are of incompatible types: inet and integer.
@@ -758,16 +759,41 @@ DETAIL:  Key columns "ftest1" and "ptest1" are of incompatible types: inet and i
 CREATE TABLE FKTABLE (ftest1 inet REFERENCES pktable(ptest1));
 ERROR:  foreign key constraint "fktable_ftest1_fkey" cannot be implemented
 DETAIL:  Key columns "ftest1" and "ptest1" are of incompatible types: inet and integer.
--- This should succeed (with a warning), even though they are different types
--- because int=varchar does exist
-CREATE TABLE FKTABLE (ftest1 varchar REFERENCES pktable);
-WARNING:  foreign key constraint "fktable_ftest1_fkey" will require costly sequential scans
-DETAIL:  Key columns "ftest1" and "ptest1" are of different types: character varying and integer.
+-- This should succeed, even though they are different types,
+-- because int=int8 exists and is a member of the integer opfamily
+CREATE TABLE FKTABLE (ftest1 int8 REFERENCES pktable);
+-- Check it actually works
+INSERT INTO FKTABLE VALUES(42);		-- should succeed
+INSERT INTO FKTABLE VALUES(43);		-- should fail
+ERROR:  insert or update on table "fktable" violates foreign key constraint "fktable_ftest1_fkey"
+DETAIL:  Key (ftest1)=(43) is not present in table "pktable".
+UPDATE FKTABLE SET ftest1 = ftest1;	-- should succeed
+UPDATE FKTABLE SET ftest1 = ftest1 + 1;	-- should fail
+ERROR:  insert or update on table "fktable" violates foreign key constraint "fktable_ftest1_fkey"
+DETAIL:  Key (ftest1)=(43) is not present in table "pktable".
 DROP TABLE FKTABLE;
--- As should this
-CREATE TABLE FKTABLE (ftest1 varchar REFERENCES pktable(ptest1));
-WARNING:  foreign key constraint "fktable_ftest1_fkey" will require costly sequential scans
-DETAIL:  Key columns "ftest1" and "ptest1" are of different types: character varying and integer.
+-- This should fail, because we'd have to cast numeric to int which is
+-- not an implicit coercion (or use numeric=numeric, but that's not part
+-- of the integer opfamily)
+CREATE TABLE FKTABLE (ftest1 numeric REFERENCES pktable);
+ERROR:  foreign key constraint "fktable_ftest1_fkey" cannot be implemented
+DETAIL:  Key columns "ftest1" and "ptest1" are of incompatible types: numeric and integer.
+DROP TABLE PKTABLE;
+-- On the other hand, this should work because int implicitly promotes to
+-- numeric, and we allow promotion on the FK side
+CREATE TABLE PKTABLE (ptest1 numeric PRIMARY KEY);
+NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "pktable_pkey" for table "pktable"
+INSERT INTO PKTABLE VALUES(42);
+CREATE TABLE FKTABLE (ftest1 int REFERENCES pktable);
+-- Check it actually works
+INSERT INTO FKTABLE VALUES(42);		-- should succeed
+INSERT INTO FKTABLE VALUES(43);		-- should fail
+ERROR:  insert or update on table "fktable" violates foreign key constraint "fktable_ftest1_fkey"
+DETAIL:  Key (ftest1)=(43) is not present in table "pktable".
+UPDATE FKTABLE SET ftest1 = ftest1;	-- should succeed
+UPDATE FKTABLE SET ftest1 = ftest1 + 1;	-- should fail
+ERROR:  insert or update on table "fktable" violates foreign key constraint "fktable_ftest1_fkey"
+DETAIL:  Key (ftest1)=(43) is not present in table "pktable".
 DROP TABLE FKTABLE;
 DROP TABLE PKTABLE;
 -- Two columns, two tables
@@ -1083,21 +1109,24 @@ CREATE TEMP TABLE fktable (
         x5      INT2
 );
 -- check individual constraints with alter table.
--- should generate warnings
+-- should fail
+-- varchar does not promote to real
 ALTER TABLE fktable ADD CONSTRAINT fk_2_3
 FOREIGN KEY (x2) REFERENCES pktable(id3);
-WARNING:  foreign key constraint "fk_2_3" will require costly sequential scans
-DETAIL:  Key columns "x2" and "id3" are of different types: character varying and real.
+ERROR:  foreign key constraint "fk_2_3" cannot be implemented
+DETAIL:  Key columns "x2" and "id3" are of incompatible types: character varying and real.
+-- nor to int4
 ALTER TABLE fktable ADD CONSTRAINT fk_2_1
 FOREIGN KEY (x2) REFERENCES pktable(id1);
-WARNING:  foreign key constraint "fk_2_1" will require costly sequential scans
-DETAIL:  Key columns "x2" and "id1" are of different types: character varying and integer.
+ERROR:  foreign key constraint "fk_2_1" cannot be implemented
+DETAIL:  Key columns "x2" and "id1" are of incompatible types: character varying and integer.
+-- real does not promote to int4
 ALTER TABLE fktable ADD CONSTRAINT fk_3_1
 FOREIGN KEY (x3) REFERENCES pktable(id1);
-WARNING:  foreign key constraint "fk_3_1" will require costly sequential scans
-DETAIL:  Key columns "x3" and "id1" are of different types: real and integer.
--- should NOT generate warnings
--- int4 promotes to text, so this is ok
+ERROR:  foreign key constraint "fk_3_1" cannot be implemented
+DETAIL:  Key columns "x3" and "id1" are of incompatible types: real and integer.
+-- should succeed
+-- int4 promotes to text, so this is allowed (though pretty durn debatable)
 ALTER TABLE fktable ADD CONSTRAINT fk_1_2
 FOREIGN KEY (x1) REFERENCES pktable(id2);
 -- int4 promotes to real
@@ -1106,45 +1135,36 @@ FOREIGN KEY (x1) REFERENCES pktable(id3);
 -- text is compatible with varchar
 ALTER TABLE fktable ADD CONSTRAINT fk_4_2
 FOREIGN KEY (x4) REFERENCES pktable(id2);
--- int2 is part of int4 opclass as of 8.0
+-- int2 is part of integer opfamily as of 8.0
 ALTER TABLE fktable ADD CONSTRAINT fk_5_1
 FOREIGN KEY (x5) REFERENCES pktable(id1);
 -- check multikey cases, especially out-of-order column lists
--- no warnings here
+-- these should work
 ALTER TABLE fktable ADD CONSTRAINT fk_123_123
 FOREIGN KEY (x1,x2,x3) REFERENCES pktable(id1,id2,id3);
 ALTER TABLE fktable ADD CONSTRAINT fk_213_213
 FOREIGN KEY (x2,x1,x3) REFERENCES pktable(id2,id1,id3);
 ALTER TABLE fktable ADD CONSTRAINT fk_253_213
 FOREIGN KEY (x2,x5,x3) REFERENCES pktable(id2,id1,id3);
--- warnings here
+-- these should fail
 ALTER TABLE fktable ADD CONSTRAINT fk_123_231
 FOREIGN KEY (x1,x2,x3) REFERENCES pktable(id2,id3,id1);
-WARNING:  foreign key constraint "fk_123_231" will require costly sequential scans
-DETAIL:  Key columns "x2" and "id3" are of different types: character varying and real.
-WARNING:  foreign key constraint "fk_123_231" will require costly sequential scans
-DETAIL:  Key columns "x3" and "id1" are of different types: real and integer.
+ERROR:  foreign key constraint "fk_123_231" cannot be implemented
+DETAIL:  Key columns "x2" and "id3" are of incompatible types: character varying and real.
 ALTER TABLE fktable ADD CONSTRAINT fk_241_132
 FOREIGN KEY (x2,x4,x1) REFERENCES pktable(id1,id3,id2);
-WARNING:  foreign key constraint "fk_241_132" will require costly sequential scans
-DETAIL:  Key columns "x2" and "id1" are of different types: character varying and integer.
-WARNING:  foreign key constraint "fk_241_132" will require costly sequential scans
-DETAIL:  Key columns "x4" and "id3" are of different types: text and real.
+ERROR:  foreign key constraint "fk_241_132" cannot be implemented
+DETAIL:  Key columns "x2" and "id1" are of incompatible types: character varying and integer.
 DROP TABLE pktable, fktable CASCADE;
-NOTICE:  drop cascades to constraint fk_241_132 on table fktable
-NOTICE:  drop cascades to constraint fk_123_231 on table fktable
 NOTICE:  drop cascades to constraint fk_253_213 on table fktable
 NOTICE:  drop cascades to constraint fk_213_213 on table fktable
 NOTICE:  drop cascades to constraint fk_123_123 on table fktable
 NOTICE:  drop cascades to constraint fk_5_1 on table fktable
-NOTICE:  drop cascades to constraint fk_3_1 on table fktable
-NOTICE:  drop cascades to constraint fk_2_1 on table fktable
 NOTICE:  drop cascades to constraint fktable_x1_fkey on table fktable
 NOTICE:  drop cascades to constraint fk_4_2 on table fktable
 NOTICE:  drop cascades to constraint fk_1_2 on table fktable
 NOTICE:  drop cascades to constraint fktable_x2_fkey on table fktable
 NOTICE:  drop cascades to constraint fk_1_3 on table fktable
-NOTICE:  drop cascades to constraint fk_2_3 on table fktable
 NOTICE:  drop cascades to constraint fktable_x3_fkey on table fktable
 -- test a tricky case: we can elide firing the FK check trigger during
 -- an UPDATE if the UPDATE did not change the foreign key
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 2ed67b3a1c6..d52d6c822e2 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -241,21 +241,40 @@ DROP TABLE tmp2;
 -- is run in parallel with foreign_key.sql.
 
 CREATE TEMP TABLE PKTABLE (ptest1 int PRIMARY KEY);
+INSERT INTO PKTABLE VALUES(42);
 CREATE TEMP TABLE FKTABLE (ftest1 inet);
--- This next should fail, because inet=int does not exist
+-- This next should fail, because int=inet does not exist
 ALTER TABLE FKTABLE ADD FOREIGN KEY(ftest1) references pktable;
 -- This should also fail for the same reason, but here we
 -- give the column name
 ALTER TABLE FKTABLE ADD FOREIGN KEY(ftest1) references pktable(ptest1);
--- This should succeed, even though they are different types
--- because varchar=int does exist
 DROP TABLE FKTABLE;
-CREATE TEMP TABLE FKTABLE (ftest1 varchar);
+-- This should succeed, even though they are different types,
+-- because int=int8 exists and is a member of the integer opfamily
+CREATE TEMP TABLE FKTABLE (ftest1 int8);
 ALTER TABLE FKTABLE ADD FOREIGN KEY(ftest1) references pktable;
--- As should this
-ALTER TABLE FKTABLE ADD FOREIGN KEY(ftest1) references pktable(ptest1);
-DROP TABLE pktable cascade;
-DROP TABLE fktable;
+-- Check it actually works
+INSERT INTO FKTABLE VALUES(42);		-- should succeed
+INSERT INTO FKTABLE VALUES(43);		-- should fail
+DROP TABLE FKTABLE;
+-- This should fail, because we'd have to cast numeric to int which is
+-- not an implicit coercion (or use numeric=numeric, but that's not part
+-- of the integer opfamily)
+CREATE TEMP TABLE FKTABLE (ftest1 numeric);
+ALTER TABLE FKTABLE ADD FOREIGN KEY(ftest1) references pktable;
+DROP TABLE FKTABLE;
+DROP TABLE PKTABLE;
+-- On the other hand, this should work because int implicitly promotes to
+-- numeric, and we allow promotion on the FK side
+CREATE TEMP TABLE PKTABLE (ptest1 numeric PRIMARY KEY);
+INSERT INTO PKTABLE VALUES(42);
+CREATE TEMP TABLE FKTABLE (ftest1 int);
+ALTER TABLE FKTABLE ADD FOREIGN KEY(ftest1) references pktable;
+-- Check it actually works
+INSERT INTO FKTABLE VALUES(42);		-- should succeed
+INSERT INTO FKTABLE VALUES(43);		-- should fail
+DROP TABLE FKTABLE;
+DROP TABLE PKTABLE;
 
 CREATE TEMP TABLE PKTABLE (ptest1 int, ptest2 inet,
                            PRIMARY KEY(ptest1, ptest2));
diff --git a/src/test/regress/sql/foreign_key.sql b/src/test/regress/sql/foreign_key.sql
index 2b22d0cecd1..16eee1e7545 100644
--- a/src/test/regress/sql/foreign_key.sql
+++ b/src/test/regress/sql/foreign_key.sql
@@ -444,17 +444,36 @@ DROP TABLE PKTABLE;
 --
 -- Basic one column, two table setup 
 CREATE TABLE PKTABLE (ptest1 int PRIMARY KEY);
--- This next should fail, because inet=int does not exist
+INSERT INTO PKTABLE VALUES(42);
+-- This next should fail, because int=inet does not exist
 CREATE TABLE FKTABLE (ftest1 inet REFERENCES pktable);
 -- This should also fail for the same reason, but here we
 -- give the column name
 CREATE TABLE FKTABLE (ftest1 inet REFERENCES pktable(ptest1));
--- This should succeed (with a warning), even though they are different types
--- because int=varchar does exist
-CREATE TABLE FKTABLE (ftest1 varchar REFERENCES pktable);
+-- This should succeed, even though they are different types,
+-- because int=int8 exists and is a member of the integer opfamily
+CREATE TABLE FKTABLE (ftest1 int8 REFERENCES pktable);
+-- Check it actually works
+INSERT INTO FKTABLE VALUES(42);		-- should succeed
+INSERT INTO FKTABLE VALUES(43);		-- should fail
+UPDATE FKTABLE SET ftest1 = ftest1;	-- should succeed
+UPDATE FKTABLE SET ftest1 = ftest1 + 1;	-- should fail
 DROP TABLE FKTABLE;
--- As should this
-CREATE TABLE FKTABLE (ftest1 varchar REFERENCES pktable(ptest1));
+-- This should fail, because we'd have to cast numeric to int which is
+-- not an implicit coercion (or use numeric=numeric, but that's not part
+-- of the integer opfamily)
+CREATE TABLE FKTABLE (ftest1 numeric REFERENCES pktable);
+DROP TABLE PKTABLE;
+-- On the other hand, this should work because int implicitly promotes to
+-- numeric, and we allow promotion on the FK side
+CREATE TABLE PKTABLE (ptest1 numeric PRIMARY KEY);
+INSERT INTO PKTABLE VALUES(42);
+CREATE TABLE FKTABLE (ftest1 int REFERENCES pktable);
+-- Check it actually works
+INSERT INTO FKTABLE VALUES(42);		-- should succeed
+INSERT INTO FKTABLE VALUES(43);		-- should fail
+UPDATE FKTABLE SET ftest1 = ftest1;	-- should succeed
+UPDATE FKTABLE SET ftest1 = ftest1 + 1;	-- should fail
 DROP TABLE FKTABLE;
 DROP TABLE PKTABLE;
 
@@ -727,20 +746,23 @@ CREATE TEMP TABLE fktable (
 
 -- check individual constraints with alter table.
 
--- should generate warnings
+-- should fail
 
+-- varchar does not promote to real
 ALTER TABLE fktable ADD CONSTRAINT fk_2_3
 FOREIGN KEY (x2) REFERENCES pktable(id3);
 
+-- nor to int4
 ALTER TABLE fktable ADD CONSTRAINT fk_2_1
 FOREIGN KEY (x2) REFERENCES pktable(id1);
 
+-- real does not promote to int4
 ALTER TABLE fktable ADD CONSTRAINT fk_3_1
 FOREIGN KEY (x3) REFERENCES pktable(id1);
 
--- should NOT generate warnings
+-- should succeed
 
--- int4 promotes to text, so this is ok
+-- int4 promotes to text, so this is allowed (though pretty durn debatable)
 ALTER TABLE fktable ADD CONSTRAINT fk_1_2
 FOREIGN KEY (x1) REFERENCES pktable(id2);
 
@@ -752,13 +774,13 @@ FOREIGN KEY (x1) REFERENCES pktable(id3);
 ALTER TABLE fktable ADD CONSTRAINT fk_4_2
 FOREIGN KEY (x4) REFERENCES pktable(id2);
 
--- int2 is part of int4 opclass as of 8.0
+-- int2 is part of integer opfamily as of 8.0
 ALTER TABLE fktable ADD CONSTRAINT fk_5_1
 FOREIGN KEY (x5) REFERENCES pktable(id1);
 
 -- check multikey cases, especially out-of-order column lists
 
--- no warnings here
+-- these should work
 
 ALTER TABLE fktable ADD CONSTRAINT fk_123_123
 FOREIGN KEY (x1,x2,x3) REFERENCES pktable(id1,id2,id3);
@@ -769,7 +791,7 @@ FOREIGN KEY (x2,x1,x3) REFERENCES pktable(id2,id1,id3);
 ALTER TABLE fktable ADD CONSTRAINT fk_253_213
 FOREIGN KEY (x2,x5,x3) REFERENCES pktable(id2,id1,id3);
 
--- warnings here
+-- these should fail
 
 ALTER TABLE fktable ADD CONSTRAINT fk_123_231
 FOREIGN KEY (x1,x2,x3) REFERENCES pktable(id2,id3,id1);