]> granicus.if.org Git - postgresql/commitdiff
Arrange to print the relevant key values when reporting a foreign-key
authorTom Lane <tgl@sss.pgh.pa.us>
Sat, 15 Mar 2003 21:19:40 +0000 (21:19 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
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 <unspecified>" 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;