Arrange to print the relevant key values when reporting a foreign-key
authorTom Lane
Sat, 15 Mar 2003 21:19:40 +0000 (21:19 +0000)
committerTom Lane
Sat, 15 Mar 2003 21:19:40 +0000 (21:19 +0000)
violation.  Also, factor out some duplicate code in the RI triggers.
Patch by Dmitry Tkach, reviewed by Stephan Szabo and Tom Lane.

src/backend/utils/adt/ri_triggers.c
src/test/regress/expected/alter_table.out
src/test/regress/expected/cluster.out
src/test/regress/expected/foreign_key.out

index 104f93a355c216e4daea2f6bf629fb5880c41027..0ef06cf7ecb5315a956069afb505730ed736f1dc 100644 (file)
@@ -17,7 +17,7 @@
  *
  * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
  *
- * $Header: /cvsroot/pgsql/src/backend/utils/adt/ri_triggers.c,v 1.46 2002/12/12 15:49:40 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/utils/adt/ri_triggers.c,v 1.47 2003/03/15 21:19:40 tgl Exp $
  *
  * ----------
  */
 #define RI_KEYS_SOME_NULL              1
 #define RI_KEYS_NONE_NULL              2
 
-
+/* queryno values must be distinct for the convenience of ri_PerformCheck */
 #define RI_PLAN_CHECK_LOOKUPPK_NOCOLS  1
 #define RI_PLAN_CHECK_LOOKUPPK         2
-#define RI_PLAN_CASCADE_DEL_DODELETE   1
-#define RI_PLAN_CASCADE_UPD_DOUPDATE   1
-#define RI_PLAN_NOACTION_DEL_CHECKREF  1
-#define RI_PLAN_NOACTION_UPD_CHECKREF  1
-#define RI_PLAN_RESTRICT_DEL_CHECKREF  1
-#define RI_PLAN_RESTRICT_UPD_CHECKREF  1
-#define RI_PLAN_SETNULL_DEL_DOUPDATE   1
-#define RI_PLAN_SETNULL_UPD_DOUPDATE   1
+#define RI_PLAN_CASCADE_DEL_DODELETE   3
+#define RI_PLAN_CASCADE_UPD_DOUPDATE   4
+#define RI_PLAN_NOACTION_DEL_CHECKREF  5
+#define RI_PLAN_NOACTION_UPD_CHECKREF  6
+#define RI_PLAN_RESTRICT_DEL_CHECKREF  7
+#define RI_PLAN_RESTRICT_UPD_CHECKREF  8
+#define RI_PLAN_SETNULL_DEL_DOUPDATE   9
+#define RI_PLAN_SETNULL_UPD_DOUPDATE   10
+#define RI_PLAN_KEYEQUAL_UPD           11
 
 #define MAX_QUOTED_NAME_LEN  (NAMEDATALEN*2+3)
 #define MAX_QUOTED_REL_NAME_LEN  (MAX_QUOTED_NAME_LEN*2)
 
+#define RI_TRIGTYPE_INSERT 1
+#define RI_TRIGTYPE_UPDATE 2
+#define RI_TRIGTYPE_INUP   3
+#define RI_TRIGTYPE_DELETE 4
+
 
 /* ----------
  * RI_QueryKey
@@ -142,13 +148,29 @@ static bool 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);
 static bool ri_AttributesEqual(Oid typeid, Datum oldvalue, Datum newvalue);
-static bool ri_Check_Pk_Match(Relation pk_rel, HeapTuple old_row,
-                 Oid tgoid, int match_type, int tgnargs, char **tgargs);
+static bool ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel,
+                             HeapTuple old_row,
+                             Oid tgoid, int match_type,
+                             int tgnargs, char **tgargs);
 
 static void ri_InitHashTables(void);
 static void *ri_FetchPreparedPlan(RI_QueryKey *key);
 static void ri_HashPreparedPlan(RI_QueryKey *key, void *plan);
 
+static void ri_CheckTrigger(FunctionCallInfo fcinfo, const char *funcname,
+                           int tgkind);
+static bool ri_PerformCheck(RI_QueryKey *qkey, void *qplan,
+                           Relation fk_rel, Relation pk_rel,
+                           HeapTuple old_tuple, HeapTuple new_tuple,
+                           int expect_OK, const char *constrname);
+static void ri_ExtractValues(RI_QueryKey *qkey, int key_idx,
+                            Relation rel, HeapTuple tuple,
+                            Datum *vals, char *nulls);
+static void ri_ReportViolation(RI_QueryKey *qkey, const char *constrname,
+                              Relation pk_rel, Relation fk_rel,
+                              HeapTuple violator, bool spi_err);
+
+
 /* ----------
  * RI_FKey_check -
  *
@@ -167,14 +189,8 @@ RI_FKey_check(PG_FUNCTION_ARGS)
    HeapTuple   old_row;
    RI_QueryKey qkey;
    void       *qplan;
-   Datum       check_values[RI_MAX_NUMKEYS];
-   char        check_nulls[RI_MAX_NUMKEYS + 1];
-   bool        isnull;
    int         i;
    int         match_type;
-   AclId       save_uid;
-
-   save_uid = GetUserId();
 
    ReferentialIntegritySnapshotOverride = true;
 
@@ -182,14 +198,7 @@ RI_FKey_check(PG_FUNCTION_ARGS)
     * Check that this is a valid trigger call on the right time and
     * event.
     */
-   if (!CALLED_AS_TRIGGER(fcinfo))
-       elog(ERROR, "RI_FKey_check() not fired by trigger manager");
-   if (!TRIGGER_FIRED_AFTER(trigdata->tg_event) ||
-       !TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
-       elog(ERROR, "RI_FKey_check() must be fired AFTER ROW");
-   if (!TRIGGER_FIRED_BY_INSERT(trigdata->tg_event) &&
-       !TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
-       elog(ERROR, "RI_FKey_check() must be fired for INSERT or UPDATE");
+   ri_CheckTrigger(fcinfo, "RI_FKey_check", RI_TRIGTYPE_INUP);
 
    /*
     * Check for the correct # of call arguments
@@ -289,23 +298,16 @@ RI_FKey_check(PG_FUNCTION_ARGS)
         * Execute the plan
         */
        if (SPI_connect() != SPI_OK_CONNECT)
-           elog(WARNING, "SPI_connect() failed in RI_FKey_check()");
-
-       SetUserId(RelationGetForm(pk_rel)->relowner);
-
-       if (SPI_execp(qplan, check_values, check_nulls, 1) != SPI_OK_SELECT)
-           elog(ERROR, "SPI_execp() failed in RI_FKey_check()");
+           elog(ERROR, "SPI_connect() failed in RI_FKey_check()");
 
-       SetUserId(save_uid);
-
-       if (SPI_processed == 0)
-           elog(ERROR, "%s referential integrity violation - "
-                "no rows found in %s",
-                tgargs[RI_CONSTRAINT_NAME_ARGNO],
-                RelationGetRelationName(pk_rel));
+       ri_PerformCheck(&qkey, qplan,
+                       fk_rel, pk_rel,
+                       NULL, NULL,
+                       SPI_OK_SELECT,
+                       tgargs[RI_CONSTRAINT_NAME_ARGNO]);
 
        if (SPI_finish() != SPI_OK_FINISH)
-           elog(WARNING, "SPI_finish() failed in RI_FKey_check()");
+           elog(ERROR, "SPI_finish() failed in RI_FKey_check()");
 
        heap_close(pk_rel, RowShareLock);
 
@@ -396,12 +398,11 @@ RI_FKey_check(PG_FUNCTION_ARGS)
     * are the same. Otherwise, someone could DELETE the PK that consists
     * of the DEFAULT values, and if there are any references, a ON DELETE
     * SET DEFAULT action would update the references to exactly these
-    * values but we wouldn't see that weired case (this is the only place
+    * values but we wouldn't see that weird case (this is the only place
     * to see it).
     */
    if (SPI_connect() != SPI_OK_CONNECT)
-       elog(WARNING, "SPI_connect() failed in RI_FKey_check()");
-
+       elog(ERROR, "SPI_connect() failed in RI_FKey_check()");
 
    /*
     * Fetch or prepare a saved plan for the real check
@@ -447,60 +448,21 @@ RI_FKey_check(PG_FUNCTION_ARGS)
        ri_HashPreparedPlan(&qkey, qplan);
    }
 
-   /*
-    * We have a plan now. Build up the arguments for SPI_execp() from the
-    * key values in the new FK tuple.
-    */
-   for (i = 0; i < qkey.nkeypairs; i++)
-   {
-       /*
-        * We can implement MATCH PARTIAL by excluding this column from
-        * the query if it is null.  Simple!  Unfortunately, the
-        * referential actions aren't so I've not bothered to do so for
-        * the moment.
-        */
-
-       check_values[i] = SPI_getbinval(new_row,
-                                       fk_rel->rd_att,
-                                     qkey.keypair[i][RI_KEYPAIR_FK_IDX],
-                                       &isnull);
-       if (isnull)
-           check_nulls[i] = 'n';
-       else
-           check_nulls[i] = ' ';
-   }
-   check_nulls[i] = '\0';
-
    /*
     * Now check that foreign key exists in PK table
     */
-
-   SetUserId(RelationGetForm(pk_rel)->relowner);
-
-   if (SPI_execp(qplan, check_values, check_nulls, 1) != SPI_OK_SELECT)
-       elog(ERROR, "SPI_execp() failed in RI_FKey_check()");
-
-   SetUserId(save_uid);
-
-   if (SPI_processed == 0)
-       elog(ERROR, "%s referential integrity violation - "
-            "key referenced from %s not found in %s",
-            tgargs[RI_CONSTRAINT_NAME_ARGNO],
-            RelationGetRelationName(fk_rel),
-            RelationGetRelationName(pk_rel));
+   ri_PerformCheck(&qkey, qplan,
+                   fk_rel, pk_rel,
+                   NULL, new_row,
+                   SPI_OK_SELECT,
+                   tgargs[RI_CONSTRAINT_NAME_ARGNO]);
 
    if (SPI_finish() != SPI_OK_FINISH)
-       elog(WARNING, "SPI_finish() failed in RI_FKey_check()");
+       elog(ERROR, "SPI_finish() failed in RI_FKey_check()");
 
    heap_close(pk_rel, RowShareLock);
 
    return PointerGetDatum(NULL);
-
-   /*
-    * Never reached
-    */
-   elog(ERROR, "internal error #1 in ri_triggers.c");
-   return PointerGetDatum(NULL);
 }
 
 
@@ -539,19 +501,16 @@ RI_FKey_check_upd(PG_FUNCTION_ARGS)
  * ----------
  */
 static bool
-ri_Check_Pk_Match(Relation pk_rel, HeapTuple old_row, Oid tgoid, int match_type, int tgnargs, char **tgargs)
+ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel,
+                 HeapTuple old_row,
+                 Oid tgoid, int match_type,
+                 int tgnargs, char **tgargs)
 {
    void       *qplan;
    RI_QueryKey qkey;
-   bool        isnull;
-   Datum       check_values[RI_MAX_NUMKEYS];
-   char        check_nulls[RI_MAX_NUMKEYS + 1];
    int         i;
-   AclId       save_uid;
    bool        result;
 
-   save_uid = GetUserId();
-
    ri_BuildQueryKeyPkCheck(&qkey, tgoid,
                            RI_PLAN_CHECK_LOOKUPPK, pk_rel,
                            tgnargs, tgargs);
@@ -605,8 +564,7 @@ ri_Check_Pk_Match(Relation pk_rel, HeapTuple old_row, Oid tgoid, int match_type,
    }
 
    if (SPI_connect() != SPI_OK_CONNECT)
-       elog(WARNING, "SPI_connect() failed in RI_FKey_check()");
-
+       elog(ERROR, "SPI_connect() failed in RI_FKey_check()");
 
    /*
     * Fetch or prepare a saved plan for the real check
@@ -653,37 +611,15 @@ ri_Check_Pk_Match(Relation pk_rel, HeapTuple old_row, Oid tgoid, int match_type,
    }
 
    /*
-    * We have a plan now. Build up the arguments for SPI_execp() from the
-    * key values in the new FK tuple.
-    */
-   for (i = 0; i < qkey.nkeypairs; i++)
-   {
-       check_values[i] = SPI_getbinval(old_row,
-                                       pk_rel->rd_att,
-                                     qkey.keypair[i][RI_KEYPAIR_PK_IDX],
-                                       &isnull);
-       if (isnull)
-           check_nulls[i] = 'n';
-       else
-           check_nulls[i] = ' ';
-   }
-   check_nulls[i] = '\0';
-
-   /*
-    * Now check that foreign key exists in PK table
+    * We have a plan now. Run it.
     */
-
-   SetUserId(RelationGetForm(pk_rel)->relowner);
-
-   if (SPI_execp(qplan, check_values, check_nulls, 1) != SPI_OK_SELECT)
-       elog(ERROR, "SPI_execp() failed in ri_Check_Pk_Match()");
-
-   SetUserId(save_uid);
-
-   result = (SPI_processed != 0);
+   result = ri_PerformCheck(&qkey, qplan,
+                            fk_rel, pk_rel,
+                            old_row, NULL,
+                            SPI_OK_SELECT, NULL);
 
    if (SPI_finish() != SPI_OK_FINISH)
-       elog(WARNING, "SPI_finish() failed in ri_Check_Pk_Match()");
+       elog(ERROR, "SPI_finish() failed in ri_Check_Pk_Match()");
 
    return result;
 }
@@ -708,14 +644,8 @@ RI_FKey_noaction_del(PG_FUNCTION_ARGS)
    HeapTuple   old_row;
    RI_QueryKey qkey;
    void       *qplan;
-   Datum       del_values[RI_MAX_NUMKEYS];
-   char        del_nulls[RI_MAX_NUMKEYS + 1];
-   bool        isnull;
    int         i;
    int         match_type;
-   AclId       save_uid;
-
-   save_uid = GetUserId();
 
    ReferentialIntegritySnapshotOverride = true;
 
@@ -723,13 +653,7 @@ RI_FKey_noaction_del(PG_FUNCTION_ARGS)
     * Check that this is a valid trigger call on the right time and
     * event.
     */
-   if (!CALLED_AS_TRIGGER(fcinfo))
-       elog(ERROR, "RI_FKey_noaction_del() not fired by trigger manager");
-   if (!TRIGGER_FIRED_AFTER(trigdata->tg_event) ||
-       !TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
-       elog(ERROR, "RI_FKey_noaction_del() must be fired AFTER ROW");
-   if (!TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
-       elog(ERROR, "RI_FKey_noaction_del() must be fired for DELETE");
+   ri_CheckTrigger(fcinfo, "RI_FKey_noaction_del", RI_TRIGTYPE_DELETE);
 
    /*
     * Check for the correct # of call arguments
@@ -766,7 +690,8 @@ RI_FKey_noaction_del(PG_FUNCTION_ARGS)
    old_row = trigdata->tg_trigtuple;
 
    match_type = ri_DetermineMatchType(tgargs[RI_MATCH_TYPE_ARGNO]);
-   if (ri_Check_Pk_Match(pk_rel, old_row, trigdata->tg_trigger->tgoid,
+   if (ri_Check_Pk_Match(pk_rel, fk_rel,
+                         old_row, trigdata->tg_trigger->tgoid,
                          match_type, tgnargs, tgargs))
    {
        /*
@@ -814,7 +739,7 @@ RI_FKey_noaction_del(PG_FUNCTION_ARGS)
            }
 
            if (SPI_connect() != SPI_OK_CONNECT)
-               elog(WARNING, "SPI_connect() failed in RI_FKey_noaction_del()");
+               elog(ERROR, "SPI_connect() failed in RI_FKey_noaction_del()");
 
            /*
             * Fetch or prepare a saved plan for the restrict delete
@@ -862,41 +787,16 @@ RI_FKey_noaction_del(PG_FUNCTION_ARGS)
            }
 
            /*
-            * We have a plan now. Build up the arguments for SPI_execp()
-            * from the key values in the deleted PK tuple.
-            */
-           for (i = 0; i < qkey.nkeypairs; i++)
-           {
-               del_values[i] = SPI_getbinval(old_row,
-                                             pk_rel->rd_att,
-                                     qkey.keypair[i][RI_KEYPAIR_PK_IDX],
-                                             &isnull);
-               if (isnull)
-                   del_nulls[i] = 'n';
-               else
-                   del_nulls[i] = ' ';
-           }
-           del_nulls[i] = '\0';
-
-           /*
-            * Now check for existing references
+            * We have a plan now. Run it to check for existing references.
             */
-           SetUserId(RelationGetForm(pk_rel)->relowner);
-
-           if (SPI_execp(qplan, del_values, del_nulls, 1) != SPI_OK_SELECT)
-               elog(ERROR, "SPI_execp() failed in RI_FKey_noaction_del()");
-
-           SetUserId(save_uid);
-
-           if (SPI_processed > 0)
-               elog(ERROR, "%s referential integrity violation - "
-                    "key in %s still referenced from %s",
-                    tgargs[RI_CONSTRAINT_NAME_ARGNO],
-                    RelationGetRelationName(pk_rel),
-                    RelationGetRelationName(fk_rel));
+           ri_PerformCheck(&qkey, qplan,
+                           fk_rel, pk_rel,
+                           old_row, NULL,
+                           SPI_OK_SELECT,
+                           tgargs[RI_CONSTRAINT_NAME_ARGNO]);
 
            if (SPI_finish() != SPI_OK_FINISH)
-               elog(WARNING, "SPI_finish() failed in RI_FKey_noaction_del()");
+               elog(ERROR, "SPI_finish() failed in RI_FKey_noaction_del()");
 
            heap_close(fk_rel, RowShareLock);
 
@@ -938,14 +838,8 @@ RI_FKey_noaction_upd(PG_FUNCTION_ARGS)
    HeapTuple   old_row;
    RI_QueryKey qkey;
    void       *qplan;
-   Datum       upd_values[RI_MAX_NUMKEYS];
-   char        upd_nulls[RI_MAX_NUMKEYS + 1];
-   bool        isnull;
    int         i;
    int         match_type;
-   AclId       save_uid;
-
-   save_uid = GetUserId();
 
    ReferentialIntegritySnapshotOverride = true;
 
@@ -953,13 +847,7 @@ RI_FKey_noaction_upd(PG_FUNCTION_ARGS)
     * Check that this is a valid trigger call on the right time and
     * event.
     */
-   if (!CALLED_AS_TRIGGER(fcinfo))
-       elog(ERROR, "RI_FKey_noaction_upd() not fired by trigger manager");
-   if (!TRIGGER_FIRED_AFTER(trigdata->tg_event) ||
-       !TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
-       elog(ERROR, "RI_FKey_noaction_upd() must be fired AFTER ROW");
-   if (!TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
-       elog(ERROR, "RI_FKey_noaction_upd() must be fired for UPDATE");
+   ri_CheckTrigger(fcinfo, "RI_FKey_noaction_upd", RI_TRIGTYPE_UPDATE);
 
    /*
     * Check for the correct # of call arguments
@@ -997,7 +885,8 @@ RI_FKey_noaction_upd(PG_FUNCTION_ARGS)
    old_row = trigdata->tg_trigtuple;
 
    match_type = ri_DetermineMatchType(tgargs[RI_MATCH_TYPE_ARGNO]);
-   if (ri_Check_Pk_Match(pk_rel, old_row, trigdata->tg_trigger->tgoid,
+   if (ri_Check_Pk_Match(pk_rel, fk_rel,
+                         old_row, trigdata->tg_trigger->tgoid,
                          match_type, tgnargs, tgargs))
    {
        /*
@@ -1055,7 +944,7 @@ RI_FKey_noaction_upd(PG_FUNCTION_ARGS)
            }
 
            if (SPI_connect() != SPI_OK_CONNECT)
-               elog(WARNING, "SPI_connect() failed in RI_FKey_noaction_upd()");
+               elog(ERROR, "SPI_connect() failed in RI_FKey_noaction_upd()");
 
            /*
             * Fetch or prepare a saved plan for the noaction update
@@ -1103,41 +992,16 @@ RI_FKey_noaction_upd(PG_FUNCTION_ARGS)
            }
 
            /*
-            * We have a plan now. Build up the arguments for SPI_execp()
-            * from the key values in the updated PK tuple.
+            * We have a plan now. Run it to check for existing references.
             */
-           for (i = 0; i < qkey.nkeypairs; i++)
-           {
-               upd_values[i] = SPI_getbinval(old_row,
-                                             pk_rel->rd_att,
-                                     qkey.keypair[i][RI_KEYPAIR_PK_IDX],
-                                             &isnull);
-               if (isnull)
-                   upd_nulls[i] = 'n';
-               else
-                   upd_nulls[i] = ' ';
-           }
-           upd_nulls[i] = '\0';
-
-           /*
-            * Now check for existing references
-            */
-           SetUserId(RelationGetForm(pk_rel)->relowner);
-
-           if (SPI_execp(qplan, upd_values, upd_nulls, 1) != SPI_OK_SELECT)
-               elog(ERROR, "SPI_execp() failed in RI_FKey_noaction_upd()");
-
-           SetUserId(save_uid);
-
-           if (SPI_processed > 0)
-               elog(ERROR, "%s referential integrity violation - "
-                    "key in %s still referenced from %s",
-                    tgargs[RI_CONSTRAINT_NAME_ARGNO],
-                    RelationGetRelationName(pk_rel),
-                    RelationGetRelationName(fk_rel));
+           ri_PerformCheck(&qkey, qplan,
+                           fk_rel, pk_rel,
+                           old_row, NULL,
+                           SPI_OK_SELECT,
+                           tgargs[RI_CONSTRAINT_NAME_ARGNO]);
 
            if (SPI_finish() != SPI_OK_FINISH)
-               elog(WARNING, "SPI_finish() failed in RI_FKey_noaction_upd()");
+               elog(ERROR, "SPI_finish() failed in RI_FKey_noaction_upd()");
 
            heap_close(fk_rel, RowShareLock);
 
@@ -1176,12 +1040,7 @@ RI_FKey_cascade_del(PG_FUNCTION_ARGS)
    HeapTuple   old_row;
    RI_QueryKey qkey;
    void       *qplan;
-   Datum       del_values[RI_MAX_NUMKEYS];
-   char        del_nulls[RI_MAX_NUMKEYS + 1];
-   bool        isnull;
    int         i;
-   AclId       save_uid;
-   AclId       fk_owner;
 
    ReferentialIntegritySnapshotOverride = true;
 
@@ -1189,13 +1048,7 @@ RI_FKey_cascade_del(PG_FUNCTION_ARGS)
     * Check that this is a valid trigger call on the right time and
     * event.
     */
-   if (!CALLED_AS_TRIGGER(fcinfo))
-       elog(ERROR, "RI_FKey_cascade_del() not fired by trigger manager");
-   if (!TRIGGER_FIRED_AFTER(trigdata->tg_event) ||
-       !TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
-       elog(ERROR, "RI_FKey_cascade_del() must be fired AFTER ROW");
-   if (!TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
-       elog(ERROR, "RI_FKey_cascade_del() must be fired for DELETE");
+   ri_CheckTrigger(fcinfo, "RI_FKey_cascade_del", RI_TRIGTYPE_DELETE);
 
    /*
     * Check for the correct # of call arguments
@@ -1230,7 +1083,6 @@ RI_FKey_cascade_del(PG_FUNCTION_ARGS)
    fk_rel = heap_open(trigdata->tg_trigger->tgconstrrelid, RowExclusiveLock);
    pk_rel = trigdata->tg_relation;
    old_row = trigdata->tg_trigtuple;
-   fk_owner = RelationGetForm(fk_rel)->relowner;
 
    switch (ri_DetermineMatchType(tgargs[RI_MATCH_TYPE_ARGNO]))
    {
@@ -1269,7 +1121,7 @@ RI_FKey_cascade_del(PG_FUNCTION_ARGS)
            }
 
            if (SPI_connect() != SPI_OK_CONNECT)
-               elog(WARNING, "SPI_connect() failed in RI_FKey_cascade_del()");
+               elog(ERROR, "SPI_connect() failed in RI_FKey_cascade_del()");
 
            /*
             * Fetch or prepare a saved plan for the cascaded delete
@@ -1315,35 +1167,18 @@ RI_FKey_cascade_del(PG_FUNCTION_ARGS)
            }
 
            /*
-            * We have a plan now. Build up the arguments for SPI_execp()
-            * from the key values in the deleted PK tuple.
+            * We have a plan now. Build up the arguments
+            * from the key values in the deleted PK tuple and delete the
+            * referencing rows
             */
-           for (i = 0; i < qkey.nkeypairs; i++)
-           {
-               del_values[i] = SPI_getbinval(old_row,
-                                             pk_rel->rd_att,
-                                     qkey.keypair[i][RI_KEYPAIR_PK_IDX],
-                                             &isnull);
-               if (isnull)
-                   del_nulls[i] = 'n';
-               else
-                   del_nulls[i] = ' ';
-           }
-           del_nulls[i] = '\0';
-
-           /*
-            * Now delete constraint
-            */
-           save_uid = GetUserId();
-           SetUserId(fk_owner);
-
-           if (SPI_execp(qplan, del_values, del_nulls, 0) != SPI_OK_DELETE)
-               elog(ERROR, "SPI_execp() failed in RI_FKey_cascade_del()");
-
-           SetUserId(save_uid);
+           ri_PerformCheck(&qkey, qplan,
+                           fk_rel, pk_rel,
+                           old_row, NULL,
+                           SPI_OK_DELETE,
+                           tgargs[RI_CONSTRAINT_NAME_ARGNO]);
 
            if (SPI_finish() != SPI_OK_FINISH)
-               elog(WARNING, "SPI_finish() failed in RI_FKey_cascade_del()");
+               elog(ERROR, "SPI_finish() failed in RI_FKey_cascade_del()");
 
            heap_close(fk_rel, RowExclusiveLock);
 
@@ -1383,13 +1218,8 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS)
    HeapTuple   old_row;
    RI_QueryKey qkey;
    void       *qplan;
-   Datum       upd_values[RI_MAX_NUMKEYS * 2];
-   char        upd_nulls[RI_MAX_NUMKEYS * 2 + 1];
-   bool        isnull;
    int         i;
    int         j;
-   AclId       save_uid;
-   AclId       fk_owner;
 
    ReferentialIntegritySnapshotOverride = true;
 
@@ -1397,13 +1227,7 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS)
     * Check that this is a valid trigger call on the right time and
     * event.
     */
-   if (!CALLED_AS_TRIGGER(fcinfo))
-       elog(ERROR, "RI_FKey_cascade_upd() not fired by trigger manager");
-   if (!TRIGGER_FIRED_AFTER(trigdata->tg_event) ||
-       !TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
-       elog(ERROR, "RI_FKey_cascade_upd() must be fired AFTER ROW");
-   if (!TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
-       elog(ERROR, "RI_FKey_cascade_upd() must be fired for UPDATE");
+   ri_CheckTrigger(fcinfo, "RI_FKey_cascade_upd", RI_TRIGTYPE_UPDATE);
 
    /*
     * Check for the correct # of call arguments
@@ -1439,7 +1263,6 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS)
    pk_rel = trigdata->tg_relation;
    new_row = trigdata->tg_newtuple;
    old_row = trigdata->tg_trigtuple;
-   fk_owner = RelationGetForm(fk_rel)->relowner;
 
    switch (ri_DetermineMatchType(tgargs[RI_MATCH_TYPE_ARGNO]))
    {
@@ -1488,7 +1311,7 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS)
            }
 
            if (SPI_connect() != SPI_OK_CONNECT)
-               elog(WARNING, "SPI_connect() failed in RI_FKey_cascade_upd()");
+               elog(ERROR, "SPI_connect() failed in RI_FKey_cascade_upd()");
 
            /*
             * Fetch or prepare a saved plan for the cascaded update of
@@ -1545,44 +1368,16 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS)
            }
 
            /*
-            * We have a plan now. Build up the arguments for SPI_execp()
-            * from the key values in the updated PK tuple.
+            * We have a plan now. Run it to update the existing references.
             */
-           for (i = 0, j = qkey.nkeypairs; i < qkey.nkeypairs; i++, j++)
-           {
-               upd_values[i] = SPI_getbinval(new_row,
-                                             pk_rel->rd_att,
-                                     qkey.keypair[i][RI_KEYPAIR_PK_IDX],
-                                             &isnull);
-               if (isnull)
-                   upd_nulls[i] = 'n';
-               else
-                   upd_nulls[i] = ' ';
-
-               upd_values[j] = SPI_getbinval(old_row,
-                                             pk_rel->rd_att,
-                                     qkey.keypair[i][RI_KEYPAIR_PK_IDX],
-                                             &isnull);
-               if (isnull)
-                   upd_nulls[j] = 'n';
-               else
-                   upd_nulls[j] = ' ';
-           }
-           upd_nulls[j] = '\0';
-
-           /*
-            * Now update the existing references
-            */
-           save_uid = GetUserId();
-           SetUserId(fk_owner);
-
-           if (SPI_execp(qplan, upd_values, upd_nulls, 0) != SPI_OK_UPDATE)
-               elog(ERROR, "SPI_execp() failed in RI_FKey_cascade_upd()");
-
-           SetUserId(save_uid);
+           ri_PerformCheck(&qkey, qplan,
+                           fk_rel, pk_rel,
+                           old_row, new_row,
+                           SPI_OK_UPDATE,
+                           tgargs[RI_CONSTRAINT_NAME_ARGNO]);
 
            if (SPI_finish() != SPI_OK_FINISH)
-               elog(WARNING, "SPI_finish() failed in RI_FKey_cascade_upd()");
+               elog(ERROR, "SPI_finish() failed in RI_FKey_cascade_upd()");
 
            heap_close(fk_rel, RowExclusiveLock);
 
@@ -1628,12 +1423,7 @@ RI_FKey_restrict_del(PG_FUNCTION_ARGS)
    HeapTuple   old_row;
    RI_QueryKey qkey;
    void       *qplan;
-   Datum       del_values[RI_MAX_NUMKEYS];
-   char        del_nulls[RI_MAX_NUMKEYS + 1];
-   bool        isnull;
    int         i;
-   AclId       save_uid;
-   AclId       fk_owner;
 
    ReferentialIntegritySnapshotOverride = true;
 
@@ -1641,13 +1431,7 @@ RI_FKey_restrict_del(PG_FUNCTION_ARGS)
     * Check that this is a valid trigger call on the right time and
     * event.
     */
-   if (!CALLED_AS_TRIGGER(fcinfo))
-       elog(ERROR, "RI_FKey_restrict_del() not fired by trigger manager");
-   if (!TRIGGER_FIRED_AFTER(trigdata->tg_event) ||
-       !TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
-       elog(ERROR, "RI_FKey_restrict_del() must be fired AFTER ROW");
-   if (!TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
-       elog(ERROR, "RI_FKey_restrict_del() must be fired for DELETE");
+   ri_CheckTrigger(fcinfo, "RI_FKey_restrict_del", RI_TRIGTYPE_DELETE);
 
    /*
     * Check for the correct # of call arguments
@@ -1682,7 +1466,6 @@ RI_FKey_restrict_del(PG_FUNCTION_ARGS)
    fk_rel = heap_open(trigdata->tg_trigger->tgconstrrelid, RowShareLock);
    pk_rel = trigdata->tg_relation;
    old_row = trigdata->tg_trigtuple;
-   fk_owner = RelationGetForm(fk_rel)->relowner;
 
    switch (ri_DetermineMatchType(tgargs[RI_MATCH_TYPE_ARGNO]))
    {
@@ -1721,7 +1504,7 @@ RI_FKey_restrict_del(PG_FUNCTION_ARGS)
            }
 
            if (SPI_connect() != SPI_OK_CONNECT)
-               elog(WARNING, "SPI_connect() failed in RI_FKey_restrict_del()");
+               elog(ERROR, "SPI_connect() failed in RI_FKey_restrict_del()");
 
            /*
             * Fetch or prepare a saved plan for the restrict delete
@@ -1769,42 +1552,16 @@ RI_FKey_restrict_del(PG_FUNCTION_ARGS)
            }
 
            /*
-            * We have a plan now. Build up the arguments for SPI_execp()
-            * from the key values in the deleted PK tuple.
+            * We have a plan now. Run it to check for existing references.
             */
-           for (i = 0; i < qkey.nkeypairs; i++)
-           {
-               del_values[i] = SPI_getbinval(old_row,
-                                             pk_rel->rd_att,
-                                     qkey.keypair[i][RI_KEYPAIR_PK_IDX],
-                                             &isnull);
-               if (isnull)
-                   del_nulls[i] = 'n';
-               else
-                   del_nulls[i] = ' ';
-           }
-           del_nulls[i] = '\0';
-
-           /*
-            * Now check for existing references
-            */
-           save_uid = GetUserId();
-           SetUserId(fk_owner);
-
-           if (SPI_execp(qplan, del_values, del_nulls, 1) != SPI_OK_SELECT)
-               elog(ERROR, "SPI_execp() failed in RI_FKey_restrict_del()");
-
-           SetUserId(save_uid);
-
-           if (SPI_processed > 0)
-               elog(ERROR, "%s referential integrity violation - "
-                    "key in %s still referenced from %s",
-                    tgargs[RI_CONSTRAINT_NAME_ARGNO],
-                    RelationGetRelationName(pk_rel),
-                    RelationGetRelationName(fk_rel));
+           ri_PerformCheck(&qkey, qplan,
+                           fk_rel, pk_rel,
+                           old_row, NULL,
+                           SPI_OK_SELECT,
+                           tgargs[RI_CONSTRAINT_NAME_ARGNO]);
 
            if (SPI_finish() != SPI_OK_FINISH)
-               elog(WARNING, "SPI_finish() failed in RI_FKey_restrict_del()");
+               elog(ERROR, "SPI_finish() failed in RI_FKey_restrict_del()");
 
            heap_close(fk_rel, RowShareLock);
 
@@ -1851,12 +1608,7 @@ RI_FKey_restrict_upd(PG_FUNCTION_ARGS)
    HeapTuple   old_row;
    RI_QueryKey qkey;
    void       *qplan;
-   Datum       upd_values[RI_MAX_NUMKEYS];
-   char        upd_nulls[RI_MAX_NUMKEYS + 1];
-   bool        isnull;
    int         i;
-   AclId       save_uid;
-   AclId       fk_owner;
 
    ReferentialIntegritySnapshotOverride = true;
 
@@ -1864,13 +1616,7 @@ RI_FKey_restrict_upd(PG_FUNCTION_ARGS)
     * Check that this is a valid trigger call on the right time and
     * event.
     */
-   if (!CALLED_AS_TRIGGER(fcinfo))
-       elog(ERROR, "RI_FKey_restrict_upd() not fired by trigger manager");
-   if (!TRIGGER_FIRED_AFTER(trigdata->tg_event) ||
-       !TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
-       elog(ERROR, "RI_FKey_restrict_upd() must be fired AFTER ROW");
-   if (!TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
-       elog(ERROR, "RI_FKey_restrict_upd() must be fired for UPDATE");
+   ri_CheckTrigger(fcinfo, "RI_FKey_restrict_upd", RI_TRIGTYPE_UPDATE);
 
    /*
     * Check for the correct # of call arguments
@@ -1906,7 +1652,6 @@ RI_FKey_restrict_upd(PG_FUNCTION_ARGS)
    pk_rel = trigdata->tg_relation;
    new_row = trigdata->tg_newtuple;
    old_row = trigdata->tg_trigtuple;
-   fk_owner = RelationGetForm(fk_rel)->relowner;
 
    switch (ri_DetermineMatchType(tgargs[RI_MATCH_TYPE_ARGNO]))
    {
@@ -1955,7 +1700,7 @@ RI_FKey_restrict_upd(PG_FUNCTION_ARGS)
            }
 
            if (SPI_connect() != SPI_OK_CONNECT)
-               elog(WARNING, "SPI_connect() failed in RI_FKey_restrict_upd()");
+               elog(ERROR, "SPI_connect() failed in RI_FKey_restrict_upd()");
 
            /*
             * Fetch or prepare a saved plan for the restrict update
@@ -2003,44 +1748,16 @@ RI_FKey_restrict_upd(PG_FUNCTION_ARGS)
            }
 
            /*
-            * We have a plan now. Build up the arguments for SPI_execp()
-            * from the key values in the updated PK tuple.
-            */
-           for (i = 0; i < qkey.nkeypairs; i++)
-           {
-               upd_values[i] = SPI_getbinval(old_row,
-                                             pk_rel->rd_att,
-                                     qkey.keypair[i][RI_KEYPAIR_PK_IDX],
-                                             &isnull);
-               if (isnull)
-                   upd_nulls[i] = 'n';
-               else
-                   upd_nulls[i] = ' ';
-           }
-           upd_nulls[i] = '\0';
-
-           /*
-            * Now check for existing references
+            * We have a plan now. Run it to check for existing references.
             */
-           save_uid = GetUserId();
-           SetUserId(fk_owner);
-
-           SetUserId(RelationGetForm(pk_rel)->relowner);
-
-           if (SPI_execp(qplan, upd_values, upd_nulls, 1) != SPI_OK_SELECT)
-               elog(ERROR, "SPI_execp() failed in RI_FKey_restrict_upd()");
-
-           SetUserId(save_uid);
-
-           if (SPI_processed > 0)
-               elog(ERROR, "%s referential integrity violation - "
-                    "key in %s still referenced from %s",
-                    tgargs[RI_CONSTRAINT_NAME_ARGNO],
-                    RelationGetRelationName(pk_rel),
-                    RelationGetRelationName(fk_rel));
+           ri_PerformCheck(&qkey, qplan,
+                           fk_rel, pk_rel,
+                           old_row, NULL,
+                           SPI_OK_SELECT,
+                           tgargs[RI_CONSTRAINT_NAME_ARGNO]);
 
            if (SPI_finish() != SPI_OK_FINISH)
-               elog(WARNING, "SPI_finish() failed in RI_FKey_restrict_upd()");
+               elog(ERROR, "SPI_finish() failed in RI_FKey_restrict_upd()");
 
            heap_close(fk_rel, RowShareLock);
 
@@ -2079,12 +1796,7 @@ RI_FKey_setnull_del(PG_FUNCTION_ARGS)
    HeapTuple   old_row;
    RI_QueryKey qkey;
    void       *qplan;
-   Datum       upd_values[RI_MAX_NUMKEYS];
-   char        upd_nulls[RI_MAX_NUMKEYS + 1];
-   bool        isnull;
    int         i;
-   AclId       save_uid;
-   AclId       fk_owner;
 
    ReferentialIntegritySnapshotOverride = true;
 
@@ -2092,13 +1804,7 @@ RI_FKey_setnull_del(PG_FUNCTION_ARGS)
     * Check that this is a valid trigger call on the right time and
     * event.
     */
-   if (!CALLED_AS_TRIGGER(fcinfo))
-       elog(ERROR, "RI_FKey_setnull_del() not fired by trigger manager");
-   if (!TRIGGER_FIRED_AFTER(trigdata->tg_event) ||
-       !TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
-       elog(ERROR, "RI_FKey_setnull_del() must be fired AFTER ROW");
-   if (!TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
-       elog(ERROR, "RI_FKey_setnull_del() must be fired for DELETE");
+   ri_CheckTrigger(fcinfo, "RI_FKey_setnull_del", RI_TRIGTYPE_DELETE);
 
    /*
     * Check for the correct # of call arguments
@@ -2133,7 +1839,6 @@ RI_FKey_setnull_del(PG_FUNCTION_ARGS)
    fk_rel = heap_open(trigdata->tg_trigger->tgconstrrelid, RowExclusiveLock);
    pk_rel = trigdata->tg_relation;
    old_row = trigdata->tg_trigtuple;
-   fk_owner = RelationGetForm(fk_rel)->relowner;
 
    switch (ri_DetermineMatchType(tgargs[RI_MATCH_TYPE_ARGNO]))
    {
@@ -2172,7 +1877,7 @@ RI_FKey_setnull_del(PG_FUNCTION_ARGS)
            }
 
            if (SPI_connect() != SPI_OK_CONNECT)
-               elog(WARNING, "SPI_connect() failed in RI_FKey_setnull_del()");
+               elog(ERROR, "SPI_connect() failed in RI_FKey_setnull_del()");
 
            /*
             * Fetch or prepare a saved plan for the set null delete
@@ -2228,35 +1933,16 @@ RI_FKey_setnull_del(PG_FUNCTION_ARGS)
            }
 
            /*
-            * We have a plan now. Build up the arguments for SPI_execp()
-            * from the key values in the updated PK tuple.
-            */
-           for (i = 0; i < qkey.nkeypairs; i++)
-           {
-               upd_values[i] = SPI_getbinval(old_row,
-                                             pk_rel->rd_att,
-                                     qkey.keypair[i][RI_KEYPAIR_PK_IDX],
-                                             &isnull);
-               if (isnull)
-                   upd_nulls[i] = 'n';
-               else
-                   upd_nulls[i] = ' ';
-           }
-           upd_nulls[i] = '\0';
-
-           /*
-            * Now update the existing references
+            * We have a plan now. Run it to check for existing references.
             */
-           save_uid = GetUserId();
-           SetUserId(fk_owner);
-
-           if (SPI_execp(qplan, upd_values, upd_nulls, 0) != SPI_OK_UPDATE)
-               elog(ERROR, "SPI_execp() failed in RI_FKey_setnull_del()");
-
-           SetUserId(save_uid);
+           ri_PerformCheck(&qkey, qplan,
+                           fk_rel, pk_rel,
+                           old_row, NULL,
+                           SPI_OK_UPDATE,
+                           tgargs[RI_CONSTRAINT_NAME_ARGNO]);
 
            if (SPI_finish() != SPI_OK_FINISH)
-               elog(WARNING, "SPI_finish() failed in RI_FKey_setnull_del()");
+               elog(ERROR, "SPI_finish() failed in RI_FKey_setnull_del()");
 
            heap_close(fk_rel, RowExclusiveLock);
 
@@ -2296,14 +1982,9 @@ RI_FKey_setnull_upd(PG_FUNCTION_ARGS)
    HeapTuple   old_row;
    RI_QueryKey qkey;
    void       *qplan;
-   Datum       upd_values[RI_MAX_NUMKEYS];
-   char        upd_nulls[RI_MAX_NUMKEYS + 1];
-   bool        isnull;
    int         i;
    int         match_type;
    bool        use_cached_query;
-   AclId       save_uid;
-   AclId       fk_owner;
 
    ReferentialIntegritySnapshotOverride = true;
 
@@ -2311,13 +1992,7 @@ RI_FKey_setnull_upd(PG_FUNCTION_ARGS)
     * Check that this is a valid trigger call on the right time and
     * event.
     */
-   if (!CALLED_AS_TRIGGER(fcinfo))
-       elog(ERROR, "RI_FKey_setnull_upd() not fired by trigger manager");
-   if (!TRIGGER_FIRED_AFTER(trigdata->tg_event) ||
-       !TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
-       elog(ERROR, "RI_FKey_setnull_upd() must be fired AFTER ROW");
-   if (!TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
-       elog(ERROR, "RI_FKey_setnull_upd() must be fired for UPDATE");
+   ri_CheckTrigger(fcinfo, "RI_FKey_setnull_upd", RI_TRIGTYPE_UPDATE);
 
    /*
     * Check for the correct # of call arguments
@@ -2354,7 +2029,6 @@ RI_FKey_setnull_upd(PG_FUNCTION_ARGS)
    new_row = trigdata->tg_newtuple;
    old_row = trigdata->tg_trigtuple;
    match_type = ri_DetermineMatchType(tgargs[RI_MATCH_TYPE_ARGNO]);
-   fk_owner = RelationGetForm(fk_rel)->relowner;
 
    switch (match_type)
    {
@@ -2403,7 +2077,7 @@ RI_FKey_setnull_upd(PG_FUNCTION_ARGS)
            }
 
            if (SPI_connect() != SPI_OK_CONNECT)
-               elog(WARNING, "SPI_connect() failed in RI_FKey_setnull_upd()");
+               elog(ERROR, "SPI_connect() failed in RI_FKey_setnull_upd()");
 
            /*
             * "MATCH " only changes columns corresponding to
@@ -2496,35 +2170,16 @@ RI_FKey_setnull_upd(PG_FUNCTION_ARGS)
            }
 
            /*
-            * We have a plan now. Build up the arguments for SPI_execp()
-            * from the key values in the updated PK tuple.
+            * We have a plan now. Run it to update the existing references.
             */
-           for (i = 0; i < qkey.nkeypairs; i++)
-           {
-               upd_values[i] = SPI_getbinval(old_row,
-                                             pk_rel->rd_att,
-                                     qkey.keypair[i][RI_KEYPAIR_PK_IDX],
-                                             &isnull);
-               if (isnull)
-                   upd_nulls[i] = 'n';
-               else
-                   upd_nulls[i] = ' ';
-           }
-           upd_nulls[i] = '\0';
-
-           /*
-            * Now update the existing references
-            */
-           save_uid = GetUserId();
-           SetUserId(fk_owner);
-
-           if (SPI_execp(qplan, upd_values, upd_nulls, 0) != SPI_OK_UPDATE)
-               elog(ERROR, "SPI_execp() failed in RI_FKey_setnull_upd()");
-
-           SetUserId(save_uid);
+           ri_PerformCheck(&qkey, qplan,
+                           fk_rel, pk_rel,
+                           old_row, NULL,
+                           SPI_OK_UPDATE,
+                           tgargs[RI_CONSTRAINT_NAME_ARGNO]);
 
            if (SPI_finish() != SPI_OK_FINISH)
-               elog(WARNING, "SPI_finish() failed in RI_FKey_setnull_upd()");
+               elog(ERROR, "SPI_finish() failed in RI_FKey_setnull_upd()");
 
            heap_close(fk_rel, RowExclusiveLock);
 
@@ -2563,12 +2218,6 @@ RI_FKey_setdefault_del(PG_FUNCTION_ARGS)
    HeapTuple   old_row;
    RI_QueryKey qkey;
    void       *qplan;
-   Datum       upd_values[RI_MAX_NUMKEYS];
-   char        upd_nulls[RI_MAX_NUMKEYS + 1];
-   bool        isnull;
-   int         i;
-   AclId       save_uid;
-   AclId           fk_owner;
 
    ReferentialIntegritySnapshotOverride = true;
 
@@ -2576,13 +2225,7 @@ RI_FKey_setdefault_del(PG_FUNCTION_ARGS)
     * Check that this is a valid trigger call on the right time and
     * event.
     */
-   if (!CALLED_AS_TRIGGER(fcinfo))
-       elog(ERROR, "RI_FKey_setdefault_del() not fired by trigger manager");
-   if (!TRIGGER_FIRED_AFTER(trigdata->tg_event) ||
-       !TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
-       elog(ERROR, "RI_FKey_setdefault_del() must be fired AFTER ROW");
-   if (!TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
-       elog(ERROR, "RI_FKey_setdefault_del() must be fired for DELETE");
+   ri_CheckTrigger(fcinfo, "RI_FKey_setdefault_del", RI_TRIGTYPE_DELETE);
 
    /*
     * Check for the correct # of call arguments
@@ -2617,7 +2260,6 @@ RI_FKey_setdefault_del(PG_FUNCTION_ARGS)
    fk_rel = heap_open(trigdata->tg_trigger->tgconstrrelid, RowExclusiveLock);
    pk_rel = trigdata->tg_relation;
    old_row = trigdata->tg_trigtuple;
-   fk_owner = RelationGetForm(fk_rel)->relowner;
 
    switch (ri_DetermineMatchType(tgargs[RI_MATCH_TYPE_ARGNO]))
    {
@@ -2656,7 +2298,7 @@ RI_FKey_setdefault_del(PG_FUNCTION_ARGS)
            }
 
            if (SPI_connect() != SPI_OK_CONNECT)
-               elog(WARNING, "SPI_connect() failed in RI_FKey_setdefault_del()");
+               elog(ERROR, "SPI_connect() failed in RI_FKey_setdefault_del()");
 
            /*
             * Prepare a plan for the set default delete operation.
@@ -2757,35 +2399,16 @@ RI_FKey_setdefault_del(PG_FUNCTION_ARGS)
            }
 
            /*
-            * We have a plan now. Build up the arguments for SPI_execp()
-            * from the key values in the deleted PK tuple.
+            * We have a plan now. Run it to update the existing references.
             */
-           for (i = 0; i < qkey.nkeypairs; i++)
-           {
-               upd_values[i] = SPI_getbinval(old_row,
-                                             pk_rel->rd_att,
-                                     qkey.keypair[i][RI_KEYPAIR_PK_IDX],
-                                             &isnull);
-               if (isnull)
-                   upd_nulls[i] = 'n';
-               else
-                   upd_nulls[i] = ' ';
-           }
-           upd_nulls[i] = '\0';
-
-           /*
-            * Now update the existing references
-            */
-           save_uid = GetUserId();
-           SetUserId(fk_owner);
-
-           if (SPI_execp(qplan, upd_values, upd_nulls, 0) != SPI_OK_UPDATE)
-               elog(ERROR, "SPI_execp() failed in RI_FKey_setdefault_del()");
-
-           SetUserId(save_uid);
+           ri_PerformCheck(&qkey, qplan,
+                           fk_rel, pk_rel,
+                           old_row, NULL,
+                           SPI_OK_UPDATE,
+                           tgargs[RI_CONSTRAINT_NAME_ARGNO]);
 
            if (SPI_finish() != SPI_OK_FINISH)
-               elog(WARNING, "SPI_finish() failed in RI_FKey_setdefault_del()");
+               elog(ERROR, "SPI_finish() failed in RI_FKey_setdefault_del()");
 
            heap_close(fk_rel, RowExclusiveLock);
 
@@ -2825,13 +2448,7 @@ RI_FKey_setdefault_upd(PG_FUNCTION_ARGS)
    HeapTuple   old_row;
    RI_QueryKey qkey;
    void       *qplan;
-   Datum       upd_values[RI_MAX_NUMKEYS];
-   char        upd_nulls[RI_MAX_NUMKEYS + 1];
-   bool        isnull;
-   int         i;
    int         match_type;
-   AclId       save_uid;
-   AclId       fk_owner;
 
    ReferentialIntegritySnapshotOverride = true;
 
@@ -2839,13 +2456,7 @@ RI_FKey_setdefault_upd(PG_FUNCTION_ARGS)
     * Check that this is a valid trigger call on the right time and
     * event.
     */
-   if (!CALLED_AS_TRIGGER(fcinfo))
-       elog(ERROR, "RI_FKey_setdefault_upd() not fired by trigger manager");
-   if (!TRIGGER_FIRED_AFTER(trigdata->tg_event) ||
-       !TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
-       elog(ERROR, "RI_FKey_setdefault_upd() must be fired AFTER ROW");
-   if (!TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
-       elog(ERROR, "RI_FKey_setdefault_upd() must be fired for UPDATE");
+   ri_CheckTrigger(fcinfo, "RI_FKey_setdefault_upd", RI_TRIGTYPE_UPDATE);
 
    /*
     * Check for the correct # of call arguments
@@ -2881,7 +2492,6 @@ RI_FKey_setdefault_upd(PG_FUNCTION_ARGS)
    pk_rel = trigdata->tg_relation;
    new_row = trigdata->tg_newtuple;
    old_row = trigdata->tg_trigtuple;
-   fk_owner = RelationGetForm(fk_rel)->relowner;
 
    match_type = ri_DetermineMatchType(tgargs[RI_MATCH_TYPE_ARGNO]);
 
@@ -2932,7 +2542,7 @@ RI_FKey_setdefault_upd(PG_FUNCTION_ARGS)
            }
 
            if (SPI_connect() != SPI_OK_CONNECT)
-               elog(WARNING, "SPI_connect() failed in RI_FKey_setdefault_upd()");
+               elog(ERROR, "SPI_connect() failed in RI_FKey_setdefault_upd()");
 
            /*
             * Prepare a plan for the set default delete operation.
@@ -3049,35 +2659,16 @@ RI_FKey_setdefault_upd(PG_FUNCTION_ARGS)
            }
 
            /*
-            * We have a plan now. Build up the arguments for SPI_execp()
-            * from the key values in the deleted PK tuple.
-            */
-           for (i = 0; i < qkey.nkeypairs; i++)
-           {
-               upd_values[i] = SPI_getbinval(old_row,
-                                             pk_rel->rd_att,
-                                     qkey.keypair[i][RI_KEYPAIR_PK_IDX],
-                                             &isnull);
-               if (isnull)
-                   upd_nulls[i] = 'n';
-               else
-                   upd_nulls[i] = ' ';
-           }
-           upd_nulls[i] = '\0';
-
-           /*
-            * Now update the existing references
+            * We have a plan now. Run it to update the existing references.
             */
-           save_uid = GetUserId();
-           SetUserId(fk_owner);
-
-           if (SPI_execp(qplan, upd_values, upd_nulls, 0) != SPI_OK_UPDATE)
-               elog(ERROR, "SPI_execp() failed in RI_FKey_setdefault_upd()");
-
-           SetUserId(save_uid);
+           ri_PerformCheck(&qkey, qplan,
+                           fk_rel, pk_rel,
+                           old_row, NULL,
+                           SPI_OK_UPDATE,
+                           tgargs[RI_CONSTRAINT_NAME_ARGNO]);
 
            if (SPI_finish() != SPI_OK_FINISH)
-               elog(WARNING, "SPI_finish() failed in RI_FKey_setdefault_upd()");
+               elog(ERROR, "SPI_finish() failed in RI_FKey_setdefault_upd()");
 
            heap_close(fk_rel, RowExclusiveLock);
 
@@ -3161,7 +2752,7 @@ RI_FKey_keyequal_upd(TriggerData *trigdata)
        case RI_MATCH_TYPE_UNSPECIFIED:
        case RI_MATCH_TYPE_FULL:
            ri_BuildQueryKeyFull(&qkey, trigdata->tg_trigger->tgoid,
-                                0,
+                                RI_PLAN_KEYEQUAL_UPD,
                                 fk_rel, pk_rel,
                                 tgnargs, tgargs);
 
@@ -3315,6 +2906,271 @@ ri_BuildQueryKeyFull(RI_QueryKey *key, Oid constr_id, int32 constr_queryno,
    }
 }
 
+/*
+ * Check that RI trigger function was called in expected context
+ */
+static void
+ri_CheckTrigger(FunctionCallInfo fcinfo, const char *funcname, int tgkind)
+{
+   TriggerData *trigdata = (TriggerData *) fcinfo->context;
+
+   if (!CALLED_AS_TRIGGER(fcinfo))
+       elog(ERROR, "%s() not fired by trigger manager", funcname);
+   if (!TRIGGER_FIRED_AFTER(trigdata->tg_event) ||
+       !TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
+       elog(ERROR, "%s() must be fired AFTER ROW", funcname);
+
+   switch (tgkind)
+   {
+       case RI_TRIGTYPE_INSERT:
+           if (!TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
+               elog(ERROR, "%s() must be fired for INSERT", funcname);
+           break;
+       case RI_TRIGTYPE_UPDATE:
+           if (!TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
+               elog(ERROR, "%s() must be fired for UPDATE", funcname);
+           break;
+       case RI_TRIGTYPE_INUP:
+           if (!TRIGGER_FIRED_BY_INSERT(trigdata->tg_event) &&
+               !TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
+               elog(ERROR, "%s() must be fired for INSERT or UPDATE",
+                    funcname);
+           break;
+       case RI_TRIGTYPE_DELETE:
+           if (!TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
+               elog(ERROR, "%s() must be fired for DELETE", funcname);
+           break;
+   }
+}
+
+
+/*
+ * Perform a query to enforce an RI restriction
+ */
+static bool
+ri_PerformCheck(RI_QueryKey *qkey, void *qplan,
+               Relation fk_rel, Relation pk_rel,
+               HeapTuple old_tuple, HeapTuple new_tuple,
+               int expect_OK, const char *constrname)
+{
+   Relation    query_rel,
+               source_rel;
+   int         key_idx;
+   int         limit;
+   int         spi_result;
+   AclId       save_uid;
+   Datum       vals[RI_MAX_NUMKEYS * 2];
+   char        nulls[RI_MAX_NUMKEYS * 2];
+
+   /*
+    * The query is always run against the FK table except
+    * when this is an update/insert trigger on the FK table itself -
+    * either RI_PLAN_CHECK_LOOKUPPK or RI_PLAN_CHECK_LOOKUPPK_NOCOLS
+    */
+   if (qkey->constr_queryno == RI_PLAN_CHECK_LOOKUPPK ||
+       qkey->constr_queryno == RI_PLAN_CHECK_LOOKUPPK_NOCOLS)
+       query_rel = pk_rel;
+   else
+       query_rel = fk_rel;
+
+   /*
+    * The values for the query are taken from the table on which the trigger
+    * is called - it is normally the other one with respect to query_rel.
+    * An exception is ri_Check_Pk_Match(), which uses the PK table for both
+    * (the case when constrname == NULL)
+    */
+   if (qkey->constr_queryno == RI_PLAN_CHECK_LOOKUPPK && constrname != NULL)
+   {
+       source_rel = fk_rel;
+       key_idx = RI_KEYPAIR_FK_IDX;
+   }
+   else
+   {
+       source_rel = pk_rel;
+       key_idx = RI_KEYPAIR_PK_IDX;
+   }
+
+   /* Extract the parameters to be passed into the query */
+   if (new_tuple)
+   {
+       ri_ExtractValues(qkey, key_idx, source_rel, new_tuple,
+                        vals, nulls);
+       if (old_tuple)
+           ri_ExtractValues(qkey, key_idx, source_rel, old_tuple,
+                            vals + qkey->nkeypairs, nulls + qkey->nkeypairs);
+   }
+   else
+   {
+       ri_ExtractValues(qkey, key_idx, source_rel, old_tuple,
+                        vals, nulls);
+   }
+
+   /* Switch to proper UID to perform check as */
+   save_uid = GetUserId();
+   SetUserId(RelationGetForm(query_rel)->relowner);
+
+   /*
+    * If this is a select query (e.g., for a 'no action' or 'restrict'
+    * trigger), we only need to see if there is a single row in the table,
+    * matching the key.  Otherwise, limit = 0 - because we want the query to
+    * affect ALL the matching rows.
+    */
+   limit = (expect_OK == SPI_OK_SELECT) ? 1 : 0;
+
+   /* Run the plan */
+   spi_result = SPI_execp(qplan, vals, nulls, limit);
+
+   /* Restore UID */
+   SetUserId(save_uid);
+
+   /* Check result */
+   if (spi_result < 0)
+       elog(ERROR, "SPI_execp() failed in ri_PerformCheck()");
+
+   if (expect_OK >= 0 && spi_result != expect_OK)
+       ri_ReportViolation(qkey, constrname ? constrname : "",
+                          pk_rel, fk_rel,
+                          new_tuple ? new_tuple : old_tuple,
+                          true);
+
+   /* XXX wouldn't it be clearer to do this part at the caller? */
+   if (constrname && expect_OK == SPI_OK_SELECT &&
+       (SPI_processed==0) == (qkey->constr_queryno==RI_PLAN_CHECK_LOOKUPPK))
+       ri_ReportViolation(qkey, constrname,
+                          pk_rel, fk_rel,
+                          new_tuple ? new_tuple : old_tuple,
+                          false);
+
+   return SPI_processed != 0;
+}
+
+/*
+ * Extract fields from a tuple into Datum/nulls arrays
+ */
+static void
+ri_ExtractValues(RI_QueryKey *qkey, int key_idx,
+                Relation rel, HeapTuple tuple,
+                Datum *vals, char *nulls)
+{
+   int         i;
+   bool        isnull;
+
+   for (i = 0; i < qkey->nkeypairs; i++)
+   {
+       vals[i] = SPI_getbinval(tuple, rel->rd_att,
+                               qkey->keypair[i][key_idx],
+                               &isnull);
+       nulls[i] = isnull ? 'n' : ' ';
+   }
+}
+
+/*
+ * Produce an error report
+ *
+ * If the failed constraint was on insert/update to the FK table,
+ * we want the key names and values extracted from there, and the error
+ * message to look like 'key blah referenced from FK not found in PK'.
+ * Otherwise, the attr names and values come from the PK table and the
+ * message looks like 'key blah in PK still referenced from FK'.
+ */
+static void
+ri_ReportViolation(RI_QueryKey *qkey, const char *constrname,
+                  Relation pk_rel, Relation fk_rel,
+                  HeapTuple violator, bool spi_err)
+{
+#define BUFLENGTH  512
+   char        key_names[BUFLENGTH];
+   char        key_values[BUFLENGTH];
+   char       *name_ptr = key_names;
+   char       *val_ptr = key_values;
+   bool        onfk;
+   Relation    rel,
+               other_rel;
+   int         idx,
+               key_idx;
+
+   /*
+    * rel is set to where the tuple description is coming from, and it also
+    * is the first relation mentioned in the message, other_rel is
+    * respectively the other relation.
+    */
+   onfk = (qkey->constr_queryno == RI_PLAN_CHECK_LOOKUPPK);
+   if (onfk)
+   {
+       rel = fk_rel;
+       other_rel = pk_rel;
+       key_idx = RI_KEYPAIR_FK_IDX;
+   }
+   else
+   {
+       rel = pk_rel;
+       other_rel = fk_rel;
+       key_idx = RI_KEYPAIR_PK_IDX;
+   }
+
+   /*
+    * Special case - if there are no keys at all, this is a 'no column'
+    * constraint - no need to try to extract the values, and the message
+    * in this case looks different.
+    */
+   if (qkey->nkeypairs == 0)
+   {
+       if (spi_err)
+           elog(ERROR, "%s referential action on %s from %s rewritten by rule",
+                constrname,
+                RelationGetRelationName(fk_rel),
+                RelationGetRelationName(pk_rel));
+       else
+           elog(ERROR, "%s referential integrity violation - no rows found in %s",
+                constrname, RelationGetRelationName(pk_rel));
+   }
+
+   /* Get printable versions of the keys involved */
+   for (idx = 0; idx < qkey->nkeypairs; idx++)
+   {
+       int     fnum = qkey->keypair[idx][key_idx];
+       char   *name,
+              *val;
+
+       name = SPI_fname(rel->rd_att, fnum);
+       val = SPI_getvalue(violator, rel->rd_att, fnum);
+       if (!val)
+           val = "null";
+
+       /*
+        * Go to "..." if name or value doesn't fit in buffer.  We reserve
+        * 5 bytes to ensure we can add comma, "...", null.
+        */
+       if (strlen(name) >= (key_names + BUFLENGTH - 5) - name_ptr ||
+           strlen(val) >= (key_values + BUFLENGTH - 5) - val_ptr)
+       {
+           sprintf(name_ptr, "...");
+           sprintf(val_ptr, "...");
+           break;
+       }
+
+       name_ptr += sprintf(name_ptr, "%s%s", idx > 0 ? "," : "", name);
+       val_ptr += sprintf(val_ptr, "%s%s", idx > 0 ? "," : "", val);
+  }
+
+  if (spi_err)
+     elog(ERROR, "%s referential action on %s from %s for (%s)=(%s) rewritten by rule",
+          constrname,
+          RelationGetRelationName(fk_rel),
+          RelationGetRelationName(pk_rel),
+          key_names, key_values);
+  else if (onfk)
+     elog(ERROR, "%s referential integrity violation - key (%s)=(%s) referenced from %s not found in %s",
+          constrname, key_names, key_values,
+          RelationGetRelationName(rel),
+          RelationGetRelationName(other_rel));
+  else
+     elog(ERROR, "%s referential integrity violation - key (%s)=(%s) in %s still referenced from %s",
+          constrname, key_names, key_values,
+          RelationGetRelationName(rel),
+          RelationGetRelationName(other_rel));
+}
+
 /* ----------
  * ri_BuildQueryKeyPkCheck -
  *
index 7015753d173fd457c28f3b31f0d75da5519caa3f..3cdb45edfb227bc29d25faffc5b4bfe9077c8d72 100644 (file)
@@ -326,7 +326,7 @@ ERROR:  ALTER TABLE: column "b" referenced in foreign key constraint does not ex
 -- Try (and fail) to add constraint due to invalid data
 ALTER TABLE tmp3 add constraint tmpconstr foreign key (a) references tmp2 match full;
 NOTICE:  ALTER TABLE will create implicit trigger(s) for FOREIGN KEY check(s)
-ERROR:  tmpconstr referential integrity violation - key referenced from tmp3 not found in tmp2
+ERROR:  tmpconstr referential integrity violation - key (a)=(5) referenced from tmp3 not found in tmp2
 -- Delete failing row
 DELETE FROM tmp3 where a=5;
 -- Try (and succeed)
index 48daf0fc25d3ae55d8585abba347c131d02bded7..d6cc4c969ca87f1b99ac81f36e9514c328ee3686 100644 (file)
@@ -248,7 +248,7 @@ SELECT a,b,c,substring(d for 30), length(d) from clstr_tst;
 
 -- Verify that foreign key link still works
 INSERT INTO clstr_tst (b, c) VALUES (1111, 'this should fail');
-ERROR:  clstr_tst_con referential integrity violation - key referenced from clstr_tst not found in clstr_tst_s
+ERROR:  clstr_tst_con referential integrity violation - key (b)=(1111) referenced from clstr_tst not found in clstr_tst_s
 SELECT conname FROM pg_constraint WHERE conrelid = 'clstr_tst'::regclass;
     conname     
 ----------------
index a68a406fdacd78678a22bb87cade5f4beca35c6c..7a0c47a96850a27a87104b8298b94543adaa1744 100644 (file)
@@ -22,7 +22,7 @@ INSERT INTO FKTABLE VALUES (3, 4);
 INSERT INTO FKTABLE VALUES (NULL, 1);
 -- Insert a failed row into FK TABLE
 INSERT INTO FKTABLE VALUES (100, 2);
-ERROR:  $1 referential integrity violation - key referenced from fktable not found in pktable
+ERROR:  $1 referential integrity violation - key (ftest1)=(100) referenced from fktable not found in pktable
 -- Check FKTABLE
 SELECT * FROM FKTABLE;
  ftest1 | ftest2 
@@ -80,9 +80,9 @@ INSERT INTO FKTABLE VALUES (3, 6, 12);
 INSERT INTO FKTABLE VALUES (NULL, NULL, 0);
 -- Insert failed rows into FK TABLE
 INSERT INTO FKTABLE VALUES (100, 2, 4);
-ERROR:  constrname referential integrity violation - key referenced from fktable not found in pktable
+ERROR:  constrname referential integrity violation - key (ftest1,ftest2)=(100,2) referenced from fktable not found in pktable
 INSERT INTO FKTABLE VALUES (2, 2, 4);
-ERROR:  constrname referential integrity violation - key referenced from fktable not found in pktable
+ERROR:  constrname referential integrity violation - key (ftest1,ftest2)=(2,2) referenced from fktable not found in pktable
 INSERT INTO FKTABLE VALUES (NULL, 2, 4);
 ERROR:  constrname referential integrity violation - MATCH FULL doesn't allow mixing of NULL and NON-NULL key values
 INSERT INTO FKTABLE VALUES (1, NULL, 4);
@@ -165,9 +165,9 @@ INSERT INTO FKTABLE VALUES (3, 6, 12);
 INSERT INTO FKTABLE VALUES (NULL, NULL, 0);
 -- Insert failed rows into FK TABLE
 INSERT INTO FKTABLE VALUES (100, 2, 4);
-ERROR:  constrname2 referential integrity violation - key referenced from fktable not found in pktable
+ERROR:  constrname2 referential integrity violation - key (ftest1,ftest2)=(100,2) referenced from fktable not found in pktable
 INSERT INTO FKTABLE VALUES (2, 2, 4);
-ERROR:  constrname2 referential integrity violation - key referenced from fktable not found in pktable
+ERROR:  constrname2 referential integrity violation - key (ftest1,ftest2)=(2,2) referenced from fktable not found in pktable
 INSERT INTO FKTABLE VALUES (NULL, 2, 4);
 ERROR:  constrname2 referential integrity violation - MATCH FULL doesn't allow mixing of NULL and NON-NULL key values
 INSERT INTO FKTABLE VALUES (1, NULL, 4);
@@ -250,7 +250,7 @@ INSERT INTO FKTABLE VALUES (3, 4);
 INSERT INTO FKTABLE VALUES (NULL, 1);
 -- Insert a failed row into FK TABLE
 INSERT INTO FKTABLE VALUES (100, 2);
-ERROR:  $1 referential integrity violation - key referenced from fktable not found in pktable
+ERROR:  $1 referential integrity violation - key (ftest1)=(100) referenced from fktable not found in pktable
 -- Check FKTABLE
 SELECT * FROM FKTABLE;
  ftest1 | ftest2 
@@ -274,7 +274,7 @@ SELECT * FROM PKTABLE;
 
 -- Delete a row from PK TABLE (should fail)
 DELETE FROM PKTABLE WHERE ptest1=1;
-ERROR:  $1 referential integrity violation - key in pktable still referenced from fktable
+ERROR:  $1 referential integrity violation - key (ptest1)=(1) in pktable still referenced from fktable
 -- Delete a row from PK TABLE (should succeed)
 DELETE FROM PKTABLE WHERE ptest1=5;
 -- Check PKTABLE for deletes
@@ -289,7 +289,7 @@ SELECT * FROM PKTABLE;
 
 -- Update a row from PK TABLE (should fail)
 UPDATE PKTABLE SET ptest1=0 WHERE ptest1=2;
-ERROR:  $1 referential integrity violation - key in pktable still referenced from fktable
+ERROR:  $1 referential integrity violation - key (ptest1)=(2) in pktable still referenced from fktable
 -- Update a row from PK TABLE (should succeed)
 UPDATE PKTABLE SET ptest1=0 WHERE ptest1=4;
 -- Check PKTABLE for updates
@@ -324,7 +324,7 @@ INSERT INTO FKTABLE VALUES (NULL, 2, 7, 4);
 INSERT INTO FKTABLE VALUES (NULL, 3, 4, 5);
 -- Insert a failed values
 INSERT INTO FKTABLE VALUES (1, 2, 7, 6);
-ERROR:  constrname3 referential integrity violation - key referenced from fktable not found in pktable
+ERROR:  constrname3 referential integrity violation - key (ftest1,ftest2,ftest3)=(1,2,7) referenced from fktable not found in pktable
 -- Show FKTABLE
 SELECT * from FKTABLE;
  ftest1 | ftest2 | ftest3 | ftest4 
@@ -338,12 +338,12 @@ SELECT * from FKTABLE;
 
 -- Try to update something that should fail
 UPDATE PKTABLE set ptest2=5 where ptest2=2;
-ERROR:  constrname3 referential integrity violation - key in pktable still referenced from fktable
+ERROR:  constrname3 referential integrity violation - key (ptest1,ptest2,ptest3)=(1,2,3) in pktable still referenced from fktable
 -- Try to update something that should succeed
 UPDATE PKTABLE set ptest1=1 WHERE ptest2=3;
 -- Try to delete something that should fail
 DELETE FROM PKTABLE where ptest1=1 and ptest2=2 and ptest3=3;
-ERROR:  constrname3 referential integrity violation - key in pktable still referenced from fktable
+ERROR:  constrname3 referential integrity violation - key (ptest1,ptest2,ptest3)=(1,2,3) in pktable still referenced from fktable
 -- Try to delete something that should work
 DELETE FROM PKTABLE where ptest1=2;
 -- Show PKTABLE and FKTABLE
@@ -387,7 +387,7 @@ INSERT INTO FKTABLE VALUES (NULL, 2, 7, 4);
 INSERT INTO FKTABLE VALUES (NULL, 3, 4, 5);
 -- Insert a failed values
 INSERT INTO FKTABLE VALUES (1, 2, 7, 6);
-ERROR:  constrname3 referential integrity violation - key referenced from fktable not found in pktable
+ERROR:  constrname3 referential integrity violation - key (ftest1,ftest2,ftest3)=(1,2,7) referenced from fktable not found in pktable
 -- Show FKTABLE
 SELECT * from FKTABLE;
  ftest1 | ftest2 | ftest3 | ftest4 
@@ -485,7 +485,7 @@ INSERT INTO FKTABLE VALUES (NULL, 2, 7, 4);
 INSERT INTO FKTABLE VALUES (NULL, 3, 4, 5);
 -- Insert a failed values
 INSERT INTO FKTABLE VALUES (1, 2, 7, 6);
-ERROR:  constrname3 referential integrity violation - key referenced from fktable not found in pktable
+ERROR:  constrname3 referential integrity violation - key (ftest1,ftest2,ftest3)=(1,2,7) referenced from fktable not found in pktable
 -- Show FKTABLE
 SELECT * from FKTABLE;
  ftest1 | ftest2 | ftest3 | ftest4 
@@ -591,7 +591,7 @@ INSERT INTO FKTABLE VALUES (NULL, 2, 7, 4);
 INSERT INTO FKTABLE VALUES (NULL, 3, 4, 5);
 -- Insert a failed values
 INSERT INTO FKTABLE VALUES (1, 2, 7, 6);
-ERROR:  constrname3 referential integrity violation - key referenced from fktable not found in pktable
+ERROR:  constrname3 referential integrity violation - key (ftest1,ftest2,ftest3)=(1,2,7) referenced from fktable not found in pktable
 -- Show FKTABLE
 SELECT * from FKTABLE;
  ftest1 | ftest2 | ftest3 | ftest4 
@@ -607,7 +607,7 @@ SELECT * from FKTABLE;
 
 -- Try to update something that will fail
 UPDATE PKTABLE set ptest2=5 where ptest2=2;
-ERROR:  constrname3 referential integrity violation - key referenced from fktable not found in pktable
+ERROR:  constrname3 referential integrity violation - key (ftest1,ftest2,ftest3)=(1,-1,3) referenced from fktable not found in pktable
 -- 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;
@@ -819,17 +819,17 @@ insert into pktable(base1) values (1);
 insert into pktable(base1) values (2);
 --  let's insert a non-existant fktable value
 insert into fktable(ftest1) values (3);
-ERROR:  $1 referential integrity violation - key referenced from fktable not found in pktable
+ERROR:  $1 referential integrity violation - key (ftest1)=(3) referenced from fktable not found in pktable
 --  let's make a valid row for that
 insert into pktable(base1) values (3);
 insert into fktable(ftest1) values (3);
 -- let's try removing a row that should fail from pktable
 delete from pktable where base1>2;
-ERROR:  $1 referential integrity violation - key in pktable still referenced from fktable
+ERROR:  $1 referential integrity violation - key (base1)=(3) in pktable still referenced from fktable
 -- okay, let's try updating all of the base1 values to *4
 -- which should fail.
 update pktable set base1=base1*4;
-ERROR:  $1 referential integrity violation - key in pktable still referenced from fktable
+ERROR:  $1 referential integrity violation - key (base1)=(3) in pktable still referenced from fktable
 -- okay, let's try an update that should work.
 update pktable set base1=base1*4 where base1<3;
 -- and a delete that should work
@@ -845,17 +845,17 @@ insert into pktable(base1, ptest1) values (1, 1);
 insert into pktable(base1, ptest1) values (2, 2);
 --  let's insert a non-existant fktable value
 insert into fktable(ftest1, ftest2) values (3, 1);
-ERROR:  $1 referential integrity violation - key referenced from fktable not found in pktable
+ERROR:  $1 referential integrity violation - key (ftest1,ftest2)=(3,1) referenced from fktable not found in pktable
 --  let's make a valid row for that
 insert into pktable(base1,ptest1) values (3, 1);
 insert into fktable(ftest1, ftest2) values (3, 1);
 -- let's try removing a row that should fail from pktable
 delete from pktable where base1>2;
-ERROR:  $1 referential integrity violation - key in pktable still referenced from fktable
+ERROR:  $1 referential integrity violation - key (base1,ptest1)=(3,1) in pktable still referenced from fktable
 -- okay, let's try updating all of the base1 values to *4
 -- which should fail.
 update pktable set base1=base1*4;
-ERROR:  $1 referential integrity violation - key in pktable still referenced from fktable
+ERROR:  $1 referential integrity violation - key (base1,ptest1)=(3,1) in pktable still referenced from fktable
 -- okay, let's try an update that should work.
 update pktable set base1=base1*4 where base1<3;
 -- and a delete that should work
@@ -876,13 +876,13 @@ insert into pktable (base1, ptest1, base2, ptest2) values (2, 2, 2, 1);
 insert into pktable (base1, ptest1, base2, ptest2) values (1, 3, 2, 2);
 -- fails (3,2) isn't in base1, ptest1
 insert into pktable (base1, ptest1, base2, ptest2) values (2, 3, 3, 2);
-ERROR:  $1 referential integrity violation - key referenced from pktable not found in pktable
+ERROR:  $1 referential integrity violation - key (base2,ptest2)=(3,2) referenced from pktable not found in pktable
 -- fails (2,2) is being referenced
 delete from pktable where base1=2;
-ERROR:  $1 referential integrity violation - key in pktable still referenced from pktable
+ERROR:  $1 referential integrity violation - key (base1,ptest1)=(2,2) in pktable still referenced from pktable
 -- fails (1,1) is being referenced (twice)
 update pktable set base1=3 where base1=1;
-ERROR:  $1 referential integrity violation - key referenced from pktable not found in pktable
+ERROR:  $1 referential integrity violation - key (base2,ptest2)=(1,1) referenced from pktable not found in pktable
 -- this sequence of two deletes will work, since after the first there will be no (2,*) references
 delete from pktable where base2=2;
 delete from pktable where base1=2;
@@ -963,7 +963,7 @@ NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index 'fktable_pkey' fo
 NOTICE:  CREATE TABLE will create implicit trigger(s) for FOREIGN KEY check(s)
 -- default to immediate: should fail
 INSERT INTO fktable VALUES (5, 10);
-ERROR:  $1 referential integrity violation - key referenced from fktable not found in pktable
+ERROR:  $1 referential integrity violation - key (fk)=(10) referenced from fktable not found in pktable
 -- explicitely defer the constraint
 BEGIN;
 SET CONSTRAINTS ALL DEFERRED;
@@ -993,7 +993,7 @@ BEGIN;
 SET CONSTRAINTS ALL IMMEDIATE;
 -- should fail
 INSERT INTO fktable VALUES (500, 1000);
-ERROR:  $1 referential integrity violation - key referenced from fktable not found in pktable
+ERROR:  $1 referential integrity violation - key (fk)=(1000) referenced from fktable not found in pktable
 COMMIT;
 DROP TABLE fktable, pktable;
 -- tricky behavior: according to SQL99, if a deferred constraint is set
@@ -1017,7 +1017,7 @@ SET CONSTRAINTS ALL DEFERRED;
 INSERT INTO fktable VALUES (1000, 2000);
 -- should cause transaction abort, due to preceding error
 SET CONSTRAINTS ALL IMMEDIATE;
-ERROR:  $1 referential integrity violation - key referenced from fktable not found in pktable
+ERROR:  $1 referential integrity violation - key (fk)=(2000) referenced from fktable not found in pktable
 INSERT INTO pktable VALUES (2000, 3); -- too late
 ERROR:  current transaction is aborted, queries ignored until end of transaction block
 COMMIT;