Share RI trigger code between NO ACTION and RESTRICT cases.
authorTom Lane <tgl@sss.pgh.pa.us>
Tue, 19 Jun 2012 18:31:54 +0000 (14:31 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Tue, 19 Jun 2012 18:31:54 +0000 (14:31 -0400)
These triggers are identical except for whether ri_Check_Pk_Match is to be
called, so factor out the common code to save a couple hundred lines.

Also, eliminate null-column checks in ri_Check_Pk_Match, since they're
duplicate with the calling functions and require unnecessary complication
in its API statement.

Simplify the way code is shared between RI_FKey_check_ins and
RI_FKey_check_upd, too.

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

index 2fb060b4779ea76d2f217224395a9981c7ae1bb7..f83f20be7abd56d89df41409ca4ffc425f0dd922 100644 (file)
 /* these queries are executed against the FK (referencing) table: */
 #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_SETDEFAULT_DEL_DOUPDATE        11
-#define RI_PLAN_SETDEFAULT_UPD_DOUPDATE        12
+#define RI_PLAN_RESTRICT_DEL_CHECKREF  5
+#define RI_PLAN_RESTRICT_UPD_CHECKREF  6
+#define RI_PLAN_SETNULL_DEL_DOUPDATE   7
+#define RI_PLAN_SETNULL_UPD_DOUPDATE   8
+#define RI_PLAN_SETDEFAULT_DEL_DOUPDATE        9
+#define RI_PLAN_SETDEFAULT_UPD_DOUPDATE        10
 
 #define MAX_QUOTED_NAME_LEN  (NAMEDATALEN*2+3)
 #define MAX_QUOTED_REL_NAME_LEN  (MAX_QUOTED_NAME_LEN*2)
@@ -92,8 +90,7 @@
 
 #define RI_TRIGTYPE_INSERT 1
 #define RI_TRIGTYPE_UPDATE 2
-#define RI_TRIGTYPE_INUP   3
-#define RI_TRIGTYPE_DELETE 4
+#define RI_TRIGTYPE_DELETE 3
 
 
 /* ----------
@@ -185,6 +182,11 @@ static HTAB *ri_compare_cache = NULL;
  * Local function prototypes
  * ----------
  */
+static bool ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel,
+                                 HeapTuple old_row,
+                                 const RI_ConstraintInfo *riinfo);
+static Datum ri_restrict_del(TriggerData *trigdata, bool is_no_action);
+static Datum ri_restrict_upd(TriggerData *trigdata, bool is_no_action);
 static void quoteOneName(char *buffer, const char *name);
 static void quoteRelationName(char *buffer, Relation rel);
 static void ri_GenerateQual(StringInfo buf,
@@ -203,9 +205,6 @@ static bool ri_KeysEqual(Relation rel, HeapTuple oldtup, HeapTuple newtup,
                         const RI_ConstraintInfo *riinfo, bool rel_is_pk);
 static bool ri_AttributesEqual(Oid eq_opr, Oid typeid,
                                   Datum oldvalue, Datum newvalue);
-static bool ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel,
-                                 HeapTuple old_row,
-                                 const RI_ConstraintInfo *riinfo);
 
 static void ri_InitHashTables(void);
 static SPIPlanPtr ri_FetchPreparedPlan(RI_QueryKey *key);
@@ -240,9 +239,8 @@ static void ri_ReportViolation(const RI_ConstraintInfo *riinfo,
  * ----------
  */
 static Datum
-RI_FKey_check(PG_FUNCTION_ARGS)
+RI_FKey_check(TriggerData *trigdata)
 {
-       TriggerData *trigdata = (TriggerData *) fcinfo->context;
        RI_ConstraintInfo riinfo;
        Relation        fk_rel;
        Relation        pk_rel;
@@ -252,11 +250,6 @@ RI_FKey_check(PG_FUNCTION_ARGS)
        SPIPlanPtr      qplan;
        int                     i;
 
-       /*
-        * Check that this is a valid trigger call on the right time and event.
-        */
-       ri_CheckTrigger(fcinfo, "RI_FKey_check", RI_TRIGTYPE_INUP);
-
        /*
         * Get arguments.
         */
@@ -510,7 +503,15 @@ RI_FKey_check(PG_FUNCTION_ARGS)
 Datum
 RI_FKey_check_ins(PG_FUNCTION_ARGS)
 {
-       return RI_FKey_check(fcinfo);
+       /*
+        * Check that this is a valid trigger call on the right time and event.
+        */
+       ri_CheckTrigger(fcinfo, "RI_FKey_check_ins", RI_TRIGTYPE_INSERT);
+
+       /*
+        * Share code with UPDATE case.
+        */
+       return RI_FKey_check((TriggerData *) fcinfo->context);
 }
 
 
@@ -523,16 +524,27 @@ RI_FKey_check_ins(PG_FUNCTION_ARGS)
 Datum
 RI_FKey_check_upd(PG_FUNCTION_ARGS)
 {
-       return RI_FKey_check(fcinfo);
+       /*
+        * Check that this is a valid trigger call on the right time and event.
+        */
+       ri_CheckTrigger(fcinfo, "RI_FKey_check_upd", RI_TRIGTYPE_UPDATE);
+
+       /*
+        * Share code with INSERT case.
+        */
+       return RI_FKey_check((TriggerData *) fcinfo->context);
 }
 
 
 /* ----------
  * ri_Check_Pk_Match
  *
- * Check for matching value of old pk row in current state for
- * noaction triggers. Returns false if no row was found and a fk row
- * could potentially be referencing this row, true otherwise.
+ * Check to see if another PK row has been created that provides the same
+ * key values as the "old_row" that's been modified or deleted in our trigger
+ * event.  Returns true if a match is found in the PK table.
+ *
+ * We assume the caller checked that the old_row contains no NULL key values,
+ * since otherwise a match is impossible.
  * ----------
  */
 static bool
@@ -545,65 +557,15 @@ ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel,
        int                     i;
        bool            result;
 
-       switch (ri_NullCheck(old_row, riinfo, true))
-       {
-               case RI_KEYS_ALL_NULL:
-
-                       /*
-                        * No check - nothing could have been referencing this row anyway.
-                        */
-                       return true;
-
-               case RI_KEYS_SOME_NULL:
-
-                       /*
-                        * This is the only case that differs between the three kinds of
-                        * MATCH.
-                        */
-                       switch (riinfo->confmatchtype)
-                       {
-                               case FKCONSTR_MATCH_FULL:
-                               case FKCONSTR_MATCH_SIMPLE:
-
-                                       /*
-                                        * MATCH SIMPLE/FULL - if ANY column is null, nothing
-                                        * could have been referencing this row.
-                                        */
-                                       return true;
-
-                               case FKCONSTR_MATCH_PARTIAL:
-
-                                       /*
-                                        * MATCH PARTIAL - all non-null columns must match. (not
-                                        * implemented, can be done by modifying the query below
-                                        * to only include non-null columns, or by writing a
-                                        * special version here)
-                                        */
-                                       ereport(ERROR,
-                                                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                                                        errmsg("MATCH PARTIAL not yet implemented")));
-                                       break;
-
-                               default:
-                                       elog(ERROR, "unrecognized confmatchtype: %d",
-                                                riinfo->confmatchtype);
-                                       break;
-                       }
-
-               case RI_KEYS_NONE_NULL:
-
-                       /*
-                        * Have a full qualified key - continue below for all three kinds
-                        * of MATCH.
-                        */
-                       break;
-       }
+       /* Only called for non-null rows */
+       Assert(ri_NullCheck(old_row, riinfo, true) == RI_KEYS_NONE_NULL);
 
        if (SPI_connect() != SPI_OK_CONNECT)
                elog(ERROR, "SPI_connect failed");
 
        /*
-        * Fetch or prepare a saved plan for the real check
+        * Fetch or prepare a saved plan for checking PK table with values coming
+        * from a PK row
         */
        ri_BuildQueryKey(&qkey, riinfo, RI_PLAN_CHECK_LOOKUPPK_FROM_PK);
 
@@ -675,196 +637,59 @@ ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel,
 Datum
 RI_FKey_noaction_del(PG_FUNCTION_ARGS)
 {
-       TriggerData *trigdata = (TriggerData *) fcinfo->context;
-       RI_ConstraintInfo riinfo;
-       Relation        fk_rel;
-       Relation        pk_rel;
-       HeapTuple       old_row;
-       RI_QueryKey qkey;
-       SPIPlanPtr      qplan;
-       int                     i;
-
        /*
         * Check that this is a valid trigger call on the right time and event.
         */
        ri_CheckTrigger(fcinfo, "RI_FKey_noaction_del", RI_TRIGTYPE_DELETE);
 
        /*
-        * Get arguments.
+        * Share code with RESTRICT case.
         */
-       ri_FetchConstraintInfo(&riinfo,
-                                                  trigdata->tg_trigger, trigdata->tg_relation, true);
+       return ri_restrict_del((TriggerData *) fcinfo->context, true);
+}
 
+/* ----------
+ * RI_FKey_restrict_del -
+ *
+ *     Restrict delete from PK table to rows unreferenced by foreign key.
+ *
+ *     The SQL standard intends that this referential action occur exactly when
+ *     the delete is performed, rather than after.  This appears to be
+ *     the only difference between "NO ACTION" and "RESTRICT".  In Postgres
+ *     we still implement this as an AFTER trigger, but it's non-deferrable.
+ * ----------
+ */
+Datum
+RI_FKey_restrict_del(PG_FUNCTION_ARGS)
+{
        /*
-        * Nothing to do if no column names to compare given
+        * Check that this is a valid trigger call on the right time and event.
         */
-       if (riinfo.nkeys == 0)
-               return PointerGetDatum(NULL);
+       ri_CheckTrigger(fcinfo, "RI_FKey_restrict_del", RI_TRIGTYPE_DELETE);
 
        /*
-        * Get the relation descriptors of the FK and PK tables and the old tuple.
-        *
-        * fk_rel is opened in RowShareLock mode since that's what our eventual
-        * SELECT FOR SHARE will get on it.
+        * Share code with NO ACTION case.
         */
-       fk_rel = heap_open(riinfo.fk_relid, RowShareLock);
-       pk_rel = trigdata->tg_relation;
-       old_row = trigdata->tg_trigtuple;
-
-       if (ri_Check_Pk_Match(pk_rel, fk_rel, old_row, &riinfo))
-       {
-               /*
-                * There's either another row, or no row could match this one.  In
-                * either case, we don't need to do the check.
-                */
-               heap_close(fk_rel, RowShareLock);
-               return PointerGetDatum(NULL);
-       }
-
-       switch (riinfo.confmatchtype)
-       {
-                       /* ----------
-                        * SQL:2008 15.17 <Execution of referential actions>
-                        *      General rules 9) a) iv):
-                        *              MATCH SIMPLE/FULL
-                        *                      ... ON DELETE NO ACTION
-                        * ----------
-                        */
-               case FKCONSTR_MATCH_SIMPLE:
-               case FKCONSTR_MATCH_FULL:
-                       switch (ri_NullCheck(old_row, &riinfo, true))
-                       {
-                               case RI_KEYS_ALL_NULL:
-                               case RI_KEYS_SOME_NULL:
-
-                                       /*
-                                        * No check needed - there cannot be any reference to old
-                                        * key if it contains a NULL
-                                        */
-                                       heap_close(fk_rel, RowShareLock);
-                                       return PointerGetDatum(NULL);
-
-                               case RI_KEYS_NONE_NULL:
-
-                                       /*
-                                        * Have a full qualified key - continue below
-                                        */
-                                       break;
-                       }
-
-                       if (SPI_connect() != SPI_OK_CONNECT)
-                               elog(ERROR, "SPI_connect failed");
-
-                       /*
-                        * Fetch or prepare a saved plan for the restrict delete lookup
-                        */
-                       ri_BuildQueryKey(&qkey, &riinfo, RI_PLAN_NOACTION_DEL_CHECKREF);
-
-                       if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
-                       {
-                               StringInfoData querybuf;
-                               char            fkrelname[MAX_QUOTED_REL_NAME_LEN];
-                               char            attname[MAX_QUOTED_NAME_LEN];
-                               char            paramname[16];
-                               const char *querysep;
-                               Oid                     queryoids[RI_MAX_NUMKEYS];
-
-                               /* ----------
-                                * The query string built is
-                                *      SELECT 1 FROM ONLY <fktable> WHERE $1 = fkatt1 [AND ...]
-                                * The type id's for the $ parameters are those of the
-                                * corresponding PK attributes.
-                                * ----------
-                                */
-                               initStringInfo(&querybuf);
-                               quoteRelationName(fkrelname, fk_rel);
-                               appendStringInfo(&querybuf, "SELECT 1 FROM ONLY %s x",
-                                                                fkrelname);
-                               querysep = "WHERE";
-                               for (i = 0; i < riinfo.nkeys; i++)
-                               {
-                                       Oid                     pk_type = RIAttType(pk_rel, riinfo.pk_attnums[i]);
-                                       Oid                     fk_type = RIAttType(fk_rel, riinfo.fk_attnums[i]);
-
-                                       quoteOneName(attname,
-                                                                RIAttName(fk_rel, riinfo.fk_attnums[i]));
-                                       sprintf(paramname, "$%d", i + 1);
-                                       ri_GenerateQual(&querybuf, querysep,
-                                                                       paramname, pk_type,
-                                                                       riinfo.pf_eq_oprs[i],
-                                                                       attname, fk_type);
-                                       querysep = "AND";
-                                       queryoids[i] = pk_type;
-                               }
-                               appendStringInfo(&querybuf, " FOR SHARE OF x");
-
-                               /* Prepare and save the plan */
-                               qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys, queryoids,
-                                                                        &qkey, fk_rel, pk_rel, true);
-                       }
-
-                       /*
-                        * We have a plan now. Run it to check for existing references.
-                        */
-                       ri_PerformCheck(&riinfo, &qkey, qplan,
-                                                       fk_rel, pk_rel,
-                                                       old_row, NULL,
-                                                       true,           /* must detect new rows */
-                                                       SPI_OK_SELECT);
-
-                       if (SPI_finish() != SPI_OK_FINISH)
-                               elog(ERROR, "SPI_finish failed");
-
-                       heap_close(fk_rel, RowShareLock);
-
-                       return PointerGetDatum(NULL);
-
-                       /*
-                        * Handle MATCH PARTIAL restrict delete.
-                        */
-               case FKCONSTR_MATCH_PARTIAL:
-                       ereport(ERROR,
-                                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                                        errmsg("MATCH PARTIAL not yet implemented")));
-                       return PointerGetDatum(NULL);
-
-               default:
-                       elog(ERROR, "unrecognized confmatchtype: %d",
-                                riinfo.confmatchtype);
-                       break;
-       }
-
-       /* Never reached */
-       return PointerGetDatum(NULL);
+       return ri_restrict_del((TriggerData *) fcinfo->context, false);
 }
 
-
 /* ----------
- * RI_FKey_noaction_upd -
+ * ri_restrict_del -
  *
- *     Give an error and roll back the current transaction if the
- *     update has resulted in a violation of the given referential
- *     integrity constraint.
+ *     Common code for ON DELETE RESTRICT and ON DELETE NO ACTION.
  * ----------
  */
-Datum
-RI_FKey_noaction_upd(PG_FUNCTION_ARGS)
+static Datum
+ri_restrict_del(TriggerData *trigdata, bool is_no_action)
 {
-       TriggerData *trigdata = (TriggerData *) fcinfo->context;
        RI_ConstraintInfo riinfo;
        Relation        fk_rel;
        Relation        pk_rel;
-       HeapTuple       new_row;
        HeapTuple       old_row;
        RI_QueryKey qkey;
        SPIPlanPtr      qplan;
        int                     i;
 
-       /*
-        * Check that this is a valid trigger call on the right time and event.
-        */
-       ri_CheckTrigger(fcinfo, "RI_FKey_noaction_upd", RI_TRIGTYPE_UPDATE);
-
        /*
         * Get arguments.
         */
@@ -878,24 +703,22 @@ RI_FKey_noaction_upd(PG_FUNCTION_ARGS)
                return PointerGetDatum(NULL);
 
        /*
-        * Get the relation descriptors of the FK and PK tables and the new and
-        * old tuple.
+        * Get the relation descriptors of the FK and PK tables and the old tuple.
         *
         * fk_rel is opened in RowShareLock mode since that's what our eventual
         * SELECT FOR SHARE will get on it.
         */
        fk_rel = heap_open(riinfo.fk_relid, RowShareLock);
        pk_rel = trigdata->tg_relation;
-       new_row = trigdata->tg_newtuple;
        old_row = trigdata->tg_trigtuple;
 
        switch (riinfo.confmatchtype)
        {
                        /* ----------
                         * SQL:2008 15.17 <Execution of referential actions>
-                        *      General rules 10) a) iv):
+                        *      General rules 9) a) iv):
                         *              MATCH SIMPLE/FULL
-                        *                      ... ON UPDATE NO ACTION
+                        *                      ... ON DELETE RESTRICT
                         * ----------
                         */
                case FKCONSTR_MATCH_SIMPLE:
@@ -921,31 +744,25 @@ RI_FKey_noaction_upd(PG_FUNCTION_ARGS)
                        }
 
                        /*
-                        * No need to check anything if old and new keys are equal
+                        * If another PK row now exists providing the old key values,
+                        * we should not do anything.  However, this check should only be
+                        * made in the NO ACTION case; in RESTRICT cases we don't wish to
+                        * allow another row to be substituted.
                         */
-                       if (ri_KeysEqual(pk_rel, old_row, new_row, &riinfo, true))
+                       if (is_no_action &&
+                               ri_Check_Pk_Match(pk_rel, fk_rel, old_row, &riinfo))
                        {
                                heap_close(fk_rel, RowShareLock);
                                return PointerGetDatum(NULL);
                        }
 
-                       if (ri_Check_Pk_Match(pk_rel, fk_rel, old_row, &riinfo))
-                       {
-                               /*
-                                * There's either another row, or no row could match this one.
-                                * In either case, we don't need to do the check.
-                                */
-                               heap_close(fk_rel, RowShareLock);
-                               return PointerGetDatum(NULL);
-                       }
-
                        if (SPI_connect() != SPI_OK_CONNECT)
                                elog(ERROR, "SPI_connect failed");
 
                        /*
-                        * Fetch or prepare a saved plan for the noaction update lookup
+                        * Fetch or prepare a saved plan for the restrict delete lookup
                         */
-                       ri_BuildQueryKey(&qkey, &riinfo, RI_PLAN_NOACTION_UPD_CHECKREF);
+                       ri_BuildQueryKey(&qkey, &riinfo, RI_PLAN_RESTRICT_DEL_CHECKREF);
 
                        if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
                        {
@@ -990,186 +807,24 @@ RI_FKey_noaction_upd(PG_FUNCTION_ARGS)
                                                                         &qkey, fk_rel, pk_rel, true);
                        }
 
-                       /*
-                        * We have a plan now. Run it to check for existing references.
-                        */
-                       ri_PerformCheck(&riinfo, &qkey, qplan,
-                                                       fk_rel, pk_rel,
-                                                       old_row, NULL,
-                                                       true,           /* must detect new rows */
-                                                       SPI_OK_SELECT);
-
-                       if (SPI_finish() != SPI_OK_FINISH)
-                               elog(ERROR, "SPI_finish failed");
-
-                       heap_close(fk_rel, RowShareLock);
-
-                       return PointerGetDatum(NULL);
-
-                       /*
-                        * Handle MATCH PARTIAL noaction update.
-                        */
-               case FKCONSTR_MATCH_PARTIAL:
-                       ereport(ERROR,
-                                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                                        errmsg("MATCH PARTIAL not yet implemented")));
-                       return PointerGetDatum(NULL);
-
-               default:
-                       elog(ERROR, "unrecognized confmatchtype: %d",
-                                riinfo.confmatchtype);
-                       break;
-       }
-
-       /* Never reached */
-       return PointerGetDatum(NULL);
-}
-
-
-/* ----------
- * RI_FKey_cascade_del -
- *
- *     Cascaded delete foreign key references at delete event on PK table.
- * ----------
- */
-Datum
-RI_FKey_cascade_del(PG_FUNCTION_ARGS)
-{
-       TriggerData *trigdata = (TriggerData *) fcinfo->context;
-       RI_ConstraintInfo riinfo;
-       Relation        fk_rel;
-       Relation        pk_rel;
-       HeapTuple       old_row;
-       RI_QueryKey qkey;
-       SPIPlanPtr      qplan;
-       int                     i;
-
-       /*
-        * Check that this is a valid trigger call on the right time and event.
-        */
-       ri_CheckTrigger(fcinfo, "RI_FKey_cascade_del", RI_TRIGTYPE_DELETE);
-
-       /*
-        * Get arguments.
-        */
-       ri_FetchConstraintInfo(&riinfo,
-                                                  trigdata->tg_trigger, trigdata->tg_relation, true);
-
-       /*
-        * Nothing to do if no column names to compare given
-        */
-       if (riinfo.nkeys == 0)
-               return PointerGetDatum(NULL);
-
-       /*
-        * Get the relation descriptors of the FK and PK tables and the old tuple.
-        *
-        * fk_rel is opened in RowExclusiveLock mode since that's what our
-        * eventual DELETE will get on it.
-        */
-       fk_rel = heap_open(riinfo.fk_relid, RowExclusiveLock);
-       pk_rel = trigdata->tg_relation;
-       old_row = trigdata->tg_trigtuple;
-
-       switch (riinfo.confmatchtype)
-       {
-                       /* ----------
-                        * SQL:2008 15.17 <Execution of referential actions>
-                        *      General rules 9) a) i):
-                        *              MATCH SIMPLE/FULL
-                        *                      ... ON DELETE CASCADE
-                        * ----------
-                        */
-               case FKCONSTR_MATCH_SIMPLE:
-               case FKCONSTR_MATCH_FULL:
-                       switch (ri_NullCheck(old_row, &riinfo, true))
-                       {
-                               case RI_KEYS_ALL_NULL:
-                               case RI_KEYS_SOME_NULL:
-
-                                       /*
-                                        * No check needed - there cannot be any reference to old
-                                        * key if it contains a NULL
-                                        */
-                                       heap_close(fk_rel, RowExclusiveLock);
-                                       return PointerGetDatum(NULL);
-
-                               case RI_KEYS_NONE_NULL:
-
-                                       /*
-                                        * Have a full qualified key - continue below
-                                        */
-                                       break;
-                       }
-
-                       if (SPI_connect() != SPI_OK_CONNECT)
-                               elog(ERROR, "SPI_connect failed");
-
-                       /*
-                        * Fetch or prepare a saved plan for the cascaded delete
-                        */
-                       ri_BuildQueryKey(&qkey, &riinfo, RI_PLAN_CASCADE_DEL_DODELETE);
-
-                       if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
-                       {
-                               StringInfoData querybuf;
-                               char            fkrelname[MAX_QUOTED_REL_NAME_LEN];
-                               char            attname[MAX_QUOTED_NAME_LEN];
-                               char            paramname[16];
-                               const char *querysep;
-                               Oid                     queryoids[RI_MAX_NUMKEYS];
-
-                               /* ----------
-                                * The query string built is
-                                *      DELETE FROM ONLY <fktable> WHERE $1 = fkatt1 [AND ...]
-                                * The type id's for the $ parameters are those of the
-                                * corresponding PK attributes.
-                                * ----------
-                                */
-                               initStringInfo(&querybuf);
-                               quoteRelationName(fkrelname, fk_rel);
-                               appendStringInfo(&querybuf, "DELETE FROM ONLY %s", fkrelname);
-                               querysep = "WHERE";
-                               for (i = 0; i < riinfo.nkeys; i++)
-                               {
-                                       Oid                     pk_type = RIAttType(pk_rel, riinfo.pk_attnums[i]);
-                                       Oid                     fk_type = RIAttType(fk_rel, riinfo.fk_attnums[i]);
-
-                                       quoteOneName(attname,
-                                                                RIAttName(fk_rel, riinfo.fk_attnums[i]));
-                                       sprintf(paramname, "$%d", i + 1);
-                                       ri_GenerateQual(&querybuf, querysep,
-                                                                       paramname, pk_type,
-                                                                       riinfo.pf_eq_oprs[i],
-                                                                       attname, fk_type);
-                                       querysep = "AND";
-                                       queryoids[i] = pk_type;
-                               }
-
-                               /* Prepare and save the plan */
-                               qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys, queryoids,
-                                                                        &qkey, fk_rel, pk_rel, true);
-                       }
-
-                       /*
-                        * We have a plan now. Build up the arguments from the key values
-                        * in the deleted PK tuple and delete the referencing rows
+                       /*
+                        * We have a plan now. Run it to check for existing references.
                         */
                        ri_PerformCheck(&riinfo, &qkey, qplan,
                                                        fk_rel, pk_rel,
                                                        old_row, NULL,
                                                        true,           /* must detect new rows */
-                                                       SPI_OK_DELETE);
+                                                       SPI_OK_SELECT);
 
                        if (SPI_finish() != SPI_OK_FINISH)
                                elog(ERROR, "SPI_finish failed");
 
-                       heap_close(fk_rel, RowExclusiveLock);
+                       heap_close(fk_rel, RowShareLock);
 
                        return PointerGetDatum(NULL);
 
                        /*
-                        * Handle MATCH PARTIAL cascaded delete.
+                        * Handle MATCH PARTIAL restrict delete.
                         */
                case FKCONSTR_MATCH_PARTIAL:
                        ereport(ERROR,
@@ -1189,15 +844,61 @@ RI_FKey_cascade_del(PG_FUNCTION_ARGS)
 
 
 /* ----------
- * RI_FKey_cascade_upd -
+ * RI_FKey_noaction_upd -
  *
- *     Cascaded update foreign key references at update event on PK table.
+ *     Give an error and roll back the current transaction if the
+ *     update has resulted in a violation of the given referential
+ *     integrity constraint.
  * ----------
  */
 Datum
-RI_FKey_cascade_upd(PG_FUNCTION_ARGS)
+RI_FKey_noaction_upd(PG_FUNCTION_ARGS)
+{
+       /*
+        * Check that this is a valid trigger call on the right time and event.
+        */
+       ri_CheckTrigger(fcinfo, "RI_FKey_noaction_upd", RI_TRIGTYPE_UPDATE);
+
+       /*
+        * Share code with RESTRICT case.
+        */
+       return ri_restrict_upd((TriggerData *) fcinfo->context, true);
+}
+
+/* ----------
+ * RI_FKey_restrict_upd -
+ *
+ *     Restrict update of PK to rows unreferenced by foreign key.
+ *
+ *     The SQL standard intends that this referential action occur exactly when
+ *     the update is performed, rather than after.  This appears to be
+ *     the only difference between "NO ACTION" and "RESTRICT".  In Postgres
+ *     we still implement this as an AFTER trigger, but it's non-deferrable.
+ * ----------
+ */
+Datum
+RI_FKey_restrict_upd(PG_FUNCTION_ARGS)
+{
+       /*
+        * Check that this is a valid trigger call on the right time and event.
+        */
+       ri_CheckTrigger(fcinfo, "RI_FKey_restrict_upd", RI_TRIGTYPE_UPDATE);
+
+       /*
+        * Share code with NO ACTION case.
+        */
+       return ri_restrict_upd((TriggerData *) fcinfo->context, false);
+}
+
+/* ----------
+ * ri_restrict_upd -
+ *
+ *     Common code for ON UPDATE RESTRICT and ON UPDATE NO ACTION.
+ * ----------
+ */
+static Datum
+ri_restrict_upd(TriggerData *trigdata, bool is_no_action)
 {
-       TriggerData *trigdata = (TriggerData *) fcinfo->context;
        RI_ConstraintInfo riinfo;
        Relation        fk_rel;
        Relation        pk_rel;
@@ -1206,12 +907,6 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS)
        RI_QueryKey qkey;
        SPIPlanPtr      qplan;
        int                     i;
-       int                     j;
-
-       /*
-        * Check that this is a valid trigger call on the right time and event.
-        */
-       ri_CheckTrigger(fcinfo, "RI_FKey_cascade_upd", RI_TRIGTYPE_UPDATE);
 
        /*
         * Get arguments.
@@ -1229,10 +924,10 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS)
         * Get the relation descriptors of the FK and PK tables and the new and
         * old tuple.
         *
-        * fk_rel is opened in RowExclusiveLock mode since that's what our
-        * eventual UPDATE will get on it.
+        * fk_rel is opened in RowShareLock mode since that's what our eventual
+        * SELECT FOR SHARE will get on it.
         */
-       fk_rel = heap_open(riinfo.fk_relid, RowExclusiveLock);
+       fk_rel = heap_open(riinfo.fk_relid, RowShareLock);
        pk_rel = trigdata->tg_relation;
        new_row = trigdata->tg_newtuple;
        old_row = trigdata->tg_trigtuple;
@@ -1241,9 +936,9 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS)
        {
                        /* ----------
                         * SQL:2008 15.17 <Execution of referential actions>
-                        *      General rules 10) a) i):
+                        *      General rules 10) a) iv):
                         *              MATCH SIMPLE/FULL
-                        *                      ... ON UPDATE CASCADE
+                        *                      ... ON UPDATE RESTRICT
                         * ----------
                         */
                case FKCONSTR_MATCH_SIMPLE:
@@ -1257,7 +952,7 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS)
                                         * No check needed - there cannot be any reference to old
                                         * key if it contains a NULL
                                         */
-                                       heap_close(fk_rel, RowExclusiveLock);
+                                       heap_close(fk_rel, RowShareLock);
                                        return PointerGetDatum(NULL);
 
                                case RI_KEYS_NONE_NULL:
@@ -1269,11 +964,24 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS)
                        }
 
                        /*
-                        * No need to do anything if old and new keys are equal
+                        * No need to check anything if old and new keys are equal
                         */
                        if (ri_KeysEqual(pk_rel, old_row, new_row, &riinfo, true))
                        {
-                               heap_close(fk_rel, RowExclusiveLock);
+                               heap_close(fk_rel, RowShareLock);
+                               return PointerGetDatum(NULL);
+                       }
+
+                       /*
+                        * If another PK row now exists providing the old key values,
+                        * we should not do anything.  However, this check should only be
+                        * made in the NO ACTION case; in RESTRICT cases we don't wish to
+                        * allow another row to be substituted.
+                        */
+                       if (is_no_action &&
+                               ri_Check_Pk_Match(pk_rel, fk_rel, old_row, &riinfo))
+                       {
+                               heap_close(fk_rel, RowShareLock);
                                return PointerGetDatum(NULL);
                        }
 
@@ -1281,82 +989,71 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS)
                                elog(ERROR, "SPI_connect failed");
 
                        /*
-                        * Fetch or prepare a saved plan for the cascaded update
+                        * Fetch or prepare a saved plan for the restrict update lookup
                         */
-                       ri_BuildQueryKey(&qkey, &riinfo, RI_PLAN_CASCADE_UPD_DOUPDATE);
+                       ri_BuildQueryKey(&qkey, &riinfo, RI_PLAN_RESTRICT_UPD_CHECKREF);
 
                        if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
                        {
                                StringInfoData querybuf;
-                               StringInfoData qualbuf;
                                char            fkrelname[MAX_QUOTED_REL_NAME_LEN];
                                char            attname[MAX_QUOTED_NAME_LEN];
                                char            paramname[16];
                                const char *querysep;
-                               const char *qualsep;
-                               Oid                     queryoids[RI_MAX_NUMKEYS * 2];
+                               Oid                     queryoids[RI_MAX_NUMKEYS];
 
                                /* ----------
                                 * The query string built is
-                                *      UPDATE ONLY <fktable> SET fkatt1 = $1 [, ...]
-                                *                      WHERE $n = fkatt1 [AND ...]
+                                *      SELECT 1 FROM ONLY <fktable> WHERE $1 = fkatt1 [AND ...]
                                 * The type id's for the $ parameters are those of the
-                                * corresponding PK attributes.  Note that we are assuming
-                                * there is an assignment cast from the PK to the FK type;
-                                * else the parser will fail.
+                                * corresponding PK attributes.
                                 * ----------
                                 */
                                initStringInfo(&querybuf);
-                               initStringInfo(&qualbuf);
                                quoteRelationName(fkrelname, fk_rel);
-                               appendStringInfo(&querybuf, "UPDATE ONLY %s SET", fkrelname);
-                               querysep = "";
-                               qualsep = "WHERE";
-                               for (i = 0, j = riinfo.nkeys; i < riinfo.nkeys; i++, j++)
+                               appendStringInfo(&querybuf, "SELECT 1 FROM ONLY %s x",
+                                                                fkrelname);
+                               querysep = "WHERE";
+                               for (i = 0; i < riinfo.nkeys; i++)
                                {
                                        Oid                     pk_type = RIAttType(pk_rel, riinfo.pk_attnums[i]);
                                        Oid                     fk_type = RIAttType(fk_rel, riinfo.fk_attnums[i]);
 
                                        quoteOneName(attname,
                                                                 RIAttName(fk_rel, riinfo.fk_attnums[i]));
-                                       appendStringInfo(&querybuf,
-                                                                        "%s %s = $%d",
-                                                                        querysep, attname, i + 1);
-                                       sprintf(paramname, "$%d", j + 1);
-                                       ri_GenerateQual(&qualbuf, qualsep,
+                                       sprintf(paramname, "$%d", i + 1);
+                                       ri_GenerateQual(&querybuf, querysep,
                                                                        paramname, pk_type,
                                                                        riinfo.pf_eq_oprs[i],
                                                                        attname, fk_type);
-                                       querysep = ",";
-                                       qualsep = "AND";
+                                       querysep = "AND";
                                        queryoids[i] = pk_type;
-                                       queryoids[j] = pk_type;
                                }
-                               appendStringInfoString(&querybuf, qualbuf.data);
+                               appendStringInfo(&querybuf, " FOR SHARE OF x");
 
                                /* Prepare and save the plan */
-                               qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys * 2, queryoids,
+                               qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys, queryoids,
                                                                         &qkey, fk_rel, pk_rel, true);
                        }
 
                        /*
-                        * We have a plan now. Run it to update the existing references.
+                        * We have a plan now. Run it to check for existing references.
                         */
                        ri_PerformCheck(&riinfo, &qkey, qplan,
                                                        fk_rel, pk_rel,
-                                                       old_row, new_row,
+                                                       old_row, NULL,
                                                        true,           /* must detect new rows */
-                                                       SPI_OK_UPDATE);
+                                                       SPI_OK_SELECT);
 
                        if (SPI_finish() != SPI_OK_FINISH)
                                elog(ERROR, "SPI_finish failed");
 
-                       heap_close(fk_rel, RowExclusiveLock);
+                       heap_close(fk_rel, RowShareLock);
 
                        return PointerGetDatum(NULL);
 
                        /*
-                        * Handle MATCH PARTIAL cascade update.
+                        * Handle MATCH PARTIAL restrict update.
                         */
                case FKCONSTR_MATCH_PARTIAL:
                        ereport(ERROR,
@@ -1376,18 +1073,13 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS)
 
 
 /* ----------
- * RI_FKey_restrict_del -
- *
- *     Restrict delete from PK table to rows unreferenced by foreign key.
+ * RI_FKey_cascade_del -
  *
- *     The SQL standard intends that this referential action occur BEFORE
- *     the delete is performed, rather than after.  This appears to be
- *     the only difference between "NO ACTION" and "RESTRICT".  In Postgres
- *     we still implement this as an AFTER trigger, but it's non-deferrable.
+ *     Cascaded delete foreign key references at delete event on PK table.
  * ----------
  */
 Datum
-RI_FKey_restrict_del(PG_FUNCTION_ARGS)
+RI_FKey_cascade_del(PG_FUNCTION_ARGS)
 {
        TriggerData *trigdata = (TriggerData *) fcinfo->context;
        RI_ConstraintInfo riinfo;
@@ -1401,7 +1093,7 @@ RI_FKey_restrict_del(PG_FUNCTION_ARGS)
        /*
         * Check that this is a valid trigger call on the right time and event.
         */
-       ri_CheckTrigger(fcinfo, "RI_FKey_restrict_del", RI_TRIGTYPE_DELETE);
+       ri_CheckTrigger(fcinfo, "RI_FKey_cascade_del", RI_TRIGTYPE_DELETE);
 
        /*
         * Get arguments.
@@ -1418,10 +1110,10 @@ RI_FKey_restrict_del(PG_FUNCTION_ARGS)
        /*
         * Get the relation descriptors of the FK and PK tables and the old tuple.
         *
-        * fk_rel is opened in RowShareLock mode since that's what our eventual
-        * SELECT FOR SHARE will get on it.
+        * fk_rel is opened in RowExclusiveLock mode since that's what our
+        * eventual DELETE will get on it.
         */
-       fk_rel = heap_open(riinfo.fk_relid, RowShareLock);
+       fk_rel = heap_open(riinfo.fk_relid, RowExclusiveLock);
        pk_rel = trigdata->tg_relation;
        old_row = trigdata->tg_trigtuple;
 
@@ -1429,9 +1121,9 @@ RI_FKey_restrict_del(PG_FUNCTION_ARGS)
        {
                        /* ----------
                         * SQL:2008 15.17 <Execution of referential actions>
-                        *      General rules 9) a) iv):
+                        *      General rules 9) a) i):
                         *              MATCH SIMPLE/FULL
-                        *                      ... ON DELETE RESTRICT
+                        *                      ... ON DELETE CASCADE
                         * ----------
                         */
                case FKCONSTR_MATCH_SIMPLE:
@@ -1445,7 +1137,7 @@ RI_FKey_restrict_del(PG_FUNCTION_ARGS)
                                         * No check needed - there cannot be any reference to old
                                         * key if it contains a NULL
                                         */
-                                       heap_close(fk_rel, RowShareLock);
+                                       heap_close(fk_rel, RowExclusiveLock);
                                        return PointerGetDatum(NULL);
 
                                case RI_KEYS_NONE_NULL:
@@ -1460,9 +1152,9 @@ RI_FKey_restrict_del(PG_FUNCTION_ARGS)
                                elog(ERROR, "SPI_connect failed");
 
                        /*
-                        * Fetch or prepare a saved plan for the restrict delete lookup
+                        * Fetch or prepare a saved plan for the cascaded delete
                         */
-                       ri_BuildQueryKey(&qkey, &riinfo, RI_PLAN_RESTRICT_DEL_CHECKREF);
+                       ri_BuildQueryKey(&qkey, &riinfo, RI_PLAN_CASCADE_DEL_DODELETE);
 
                        if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
                        {
@@ -1475,15 +1167,14 @@ RI_FKey_restrict_del(PG_FUNCTION_ARGS)
 
                                /* ----------
                                 * The query string built is
-                                *      SELECT 1 FROM ONLY <fktable> WHERE $1 = fkatt1 [AND ...]
+                                *      DELETE FROM ONLY <fktable> WHERE $1 = fkatt1 [AND ...]
                                 * The type id's for the $ parameters are those of the
                                 * corresponding PK attributes.
                                 * ----------
                                 */
                                initStringInfo(&querybuf);
                                quoteRelationName(fkrelname, fk_rel);
-                               appendStringInfo(&querybuf, "SELECT 1 FROM ONLY %s x",
-                                                                fkrelname);
+                               appendStringInfo(&querybuf, "DELETE FROM ONLY %s", fkrelname);
                                querysep = "WHERE";
                                for (i = 0; i < riinfo.nkeys; i++)
                                {
@@ -1500,7 +1191,6 @@ RI_FKey_restrict_del(PG_FUNCTION_ARGS)
                                        querysep = "AND";
                                        queryoids[i] = pk_type;
                                }
-                               appendStringInfo(&querybuf, " FOR SHARE OF x");
 
                                /* Prepare and save the plan */
                                qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys, queryoids,
@@ -1508,23 +1198,24 @@ RI_FKey_restrict_del(PG_FUNCTION_ARGS)
                        }
 
                        /*
-                        * We have a plan now. Run it to check for existing references.
+                        * We have a plan now. Build up the arguments from the key values
+                        * in the deleted PK tuple and delete the referencing rows
                         */
                        ri_PerformCheck(&riinfo, &qkey, qplan,
                                                        fk_rel, pk_rel,
                                                        old_row, NULL,
                                                        true,           /* must detect new rows */
-                                                       SPI_OK_SELECT);
+                                                       SPI_OK_DELETE);
 
                        if (SPI_finish() != SPI_OK_FINISH)
                                elog(ERROR, "SPI_finish failed");
 
-                       heap_close(fk_rel, RowShareLock);
+                       heap_close(fk_rel, RowExclusiveLock);
 
                        return PointerGetDatum(NULL);
 
                        /*
-                        * Handle MATCH PARTIAL restrict delete.
+                        * Handle MATCH PARTIAL cascaded delete.
                         */
                case FKCONSTR_MATCH_PARTIAL:
                        ereport(ERROR,
@@ -1544,18 +1235,13 @@ RI_FKey_restrict_del(PG_FUNCTION_ARGS)
 
 
 /* ----------
- * RI_FKey_restrict_upd -
- *
- *     Restrict update of PK to rows unreferenced by foreign key.
+ * RI_FKey_cascade_upd -
  *
- *     The SQL standard intends that this referential action occur BEFORE
- *     the update is performed, rather than after.  This appears to be
- *     the only difference between "NO ACTION" and "RESTRICT".  In Postgres
- *     we still implement this as an AFTER trigger, but it's non-deferrable.
+ *     Cascaded update foreign key references at update event on PK table.
  * ----------
  */
 Datum
-RI_FKey_restrict_upd(PG_FUNCTION_ARGS)
+RI_FKey_cascade_upd(PG_FUNCTION_ARGS)
 {
        TriggerData *trigdata = (TriggerData *) fcinfo->context;
        RI_ConstraintInfo riinfo;
@@ -1566,11 +1252,12 @@ RI_FKey_restrict_upd(PG_FUNCTION_ARGS)
        RI_QueryKey qkey;
        SPIPlanPtr      qplan;
        int                     i;
+       int                     j;
 
        /*
         * Check that this is a valid trigger call on the right time and event.
         */
-       ri_CheckTrigger(fcinfo, "RI_FKey_restrict_upd", RI_TRIGTYPE_UPDATE);
+       ri_CheckTrigger(fcinfo, "RI_FKey_cascade_upd", RI_TRIGTYPE_UPDATE);
 
        /*
         * Get arguments.
@@ -1588,10 +1275,10 @@ RI_FKey_restrict_upd(PG_FUNCTION_ARGS)
         * Get the relation descriptors of the FK and PK tables and the new and
         * old tuple.
         *
-        * fk_rel is opened in RowShareLock mode since that's what our eventual
-        * SELECT FOR SHARE will get on it.
+        * fk_rel is opened in RowExclusiveLock mode since that's what our
+        * eventual UPDATE will get on it.
         */
-       fk_rel = heap_open(riinfo.fk_relid, RowShareLock);
+       fk_rel = heap_open(riinfo.fk_relid, RowExclusiveLock);
        pk_rel = trigdata->tg_relation;
        new_row = trigdata->tg_newtuple;
        old_row = trigdata->tg_trigtuple;
@@ -1600,9 +1287,9 @@ RI_FKey_restrict_upd(PG_FUNCTION_ARGS)
        {
                        /* ----------
                         * SQL:2008 15.17 <Execution of referential actions>
-                        *      General rules 10) a) iv):
+                        *      General rules 10) a) i):
                         *              MATCH SIMPLE/FULL
-                        *                      ... ON UPDATE RESTRICT
+                        *                      ... ON UPDATE CASCADE
                         * ----------
                         */
                case FKCONSTR_MATCH_SIMPLE:
@@ -1616,7 +1303,7 @@ RI_FKey_restrict_upd(PG_FUNCTION_ARGS)
                                         * No check needed - there cannot be any reference to old
                                         * key if it contains a NULL
                                         */
-                                       heap_close(fk_rel, RowShareLock);
+                                       heap_close(fk_rel, RowExclusiveLock);
                                        return PointerGetDatum(NULL);
 
                                case RI_KEYS_NONE_NULL:
@@ -1628,11 +1315,11 @@ RI_FKey_restrict_upd(PG_FUNCTION_ARGS)
                        }
 
                        /*
-                        * No need to check anything if old and new keys are equal
+                        * No need to do anything if old and new keys are equal
                         */
                        if (ri_KeysEqual(pk_rel, old_row, new_row, &riinfo, true))
                        {
-                               heap_close(fk_rel, RowShareLock);
+                               heap_close(fk_rel, RowExclusiveLock);
                                return PointerGetDatum(NULL);
                        }
 
@@ -1640,71 +1327,82 @@ RI_FKey_restrict_upd(PG_FUNCTION_ARGS)
                                elog(ERROR, "SPI_connect failed");
 
                        /*
-                        * Fetch or prepare a saved plan for the restrict update lookup
+                        * Fetch or prepare a saved plan for the cascaded update
                         */
-                       ri_BuildQueryKey(&qkey, &riinfo, RI_PLAN_RESTRICT_UPD_CHECKREF);
+                       ri_BuildQueryKey(&qkey, &riinfo, RI_PLAN_CASCADE_UPD_DOUPDATE);
 
                        if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
                        {
                                StringInfoData querybuf;
+                               StringInfoData qualbuf;
                                char            fkrelname[MAX_QUOTED_REL_NAME_LEN];
                                char            attname[MAX_QUOTED_NAME_LEN];
                                char            paramname[16];
                                const char *querysep;
-                               Oid                     queryoids[RI_MAX_NUMKEYS];
+                               const char *qualsep;
+                               Oid                     queryoids[RI_MAX_NUMKEYS * 2];
 
                                /* ----------
                                 * The query string built is
-                                *      SELECT 1 FROM ONLY <fktable> WHERE $1 = fkatt1 [AND ...]
+                                *      UPDATE ONLY <fktable> SET fkatt1 = $1 [, ...]
+                                *                      WHERE $n = fkatt1 [AND ...]
                                 * The type id's for the $ parameters are those of the
-                                * corresponding PK attributes.
+                                * corresponding PK attributes.  Note that we are assuming
+                                * there is an assignment cast from the PK to the FK type;
+                                * else the parser will fail.
                                 * ----------
                                 */
                                initStringInfo(&querybuf);
+                               initStringInfo(&qualbuf);
                                quoteRelationName(fkrelname, fk_rel);
-                               appendStringInfo(&querybuf, "SELECT 1 FROM ONLY %s x",
-                                                                fkrelname);
-                               querysep = "WHERE";
-                               for (i = 0; i < riinfo.nkeys; i++)
+                               appendStringInfo(&querybuf, "UPDATE ONLY %s SET", fkrelname);
+                               querysep = "";
+                               qualsep = "WHERE";
+                               for (i = 0, j = riinfo.nkeys; i < riinfo.nkeys; i++, j++)
                                {
                                        Oid                     pk_type = RIAttType(pk_rel, riinfo.pk_attnums[i]);
                                        Oid                     fk_type = RIAttType(fk_rel, riinfo.fk_attnums[i]);
 
                                        quoteOneName(attname,
                                                                 RIAttName(fk_rel, riinfo.fk_attnums[i]));
-                                       sprintf(paramname, "$%d", i + 1);
-                                       ri_GenerateQual(&querybuf, querysep,
+                                       appendStringInfo(&querybuf,
+                                                                        "%s %s = $%d",
+                                                                        querysep, attname, i + 1);
+                                       sprintf(paramname, "$%d", j + 1);
+                                       ri_GenerateQual(&qualbuf, qualsep,
                                                                        paramname, pk_type,
                                                                        riinfo.pf_eq_oprs[i],
                                                                        attname, fk_type);
-                                       querysep = "AND";
+                                       querysep = ",";
+                                       qualsep = "AND";
                                        queryoids[i] = pk_type;
+                                       queryoids[j] = pk_type;
                                }
-                               appendStringInfo(&querybuf, " FOR SHARE OF x");
+                               appendStringInfoString(&querybuf, qualbuf.data);
 
                                /* Prepare and save the plan */
-                               qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys, queryoids,
+                               qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys * 2, queryoids,
                                                                         &qkey, fk_rel, pk_rel, true);
                        }
 
                        /*
-                        * We have a plan now. Run it to check for existing references.
+                        * We have a plan now. Run it to update the existing references.
                         */
                        ri_PerformCheck(&riinfo, &qkey, qplan,
                                                        fk_rel, pk_rel,
-                                                       old_row, NULL,
+                                                       old_row, new_row,
                                                        true,           /* must detect new rows */
-                                                       SPI_OK_SELECT);
+                                                       SPI_OK_UPDATE);
 
                        if (SPI_finish() != SPI_OK_FINISH)
                                elog(ERROR, "SPI_finish failed");
 
-                       heap_close(fk_rel, RowShareLock);
+                       heap_close(fk_rel, RowExclusiveLock);
 
                        return PointerGetDatum(NULL);
 
                        /*
-                        * Handle MATCH PARTIAL restrict update.
+                        * Handle MATCH PARTIAL cascade update.
                         */
                case FKCONSTR_MATCH_PARTIAL:
                        ereport(ERROR,
@@ -3043,14 +2741,6 @@ ri_CheckTrigger(FunctionCallInfo fcinfo, const char *funcname, int tgkind)
                                                (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
                                                 errmsg("function \"%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))
-                               ereport(ERROR,
-                                               (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
-                                errmsg("function \"%s\" must be fired for INSERT or UPDATE",
-                                               funcname)));
-                       break;
                case RI_TRIGTYPE_DELETE:
                        if (!TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
                                ereport(ERROR,
index a63a89f40bbea2d1109e5afd61d58f49dfeb1883..502307bac206d765baaafd4c284410fb4e4747ff 100644 (file)
@@ -1355,3 +1355,29 @@ select * from defc;
 delete from defp where f1 = 1; -- fail
 ERROR:  update or delete on table "defp" violates foreign key constraint "defc_f1_fkey" on table "defc"
 DETAIL:  Key (f1)=(1) is still referenced from table "defc".
+--
+-- Test the difference between NO ACTION and RESTRICT
+--
+create temp table pp (f1 int primary key);
+NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "pp_pkey" for table "pp"
+create temp table cc (f1 int references pp on update no action);
+insert into pp values(12);
+insert into pp values(11);
+update pp set f1=f1+1;
+insert into cc values(13);
+update pp set f1=f1+1;
+update pp set f1=f1+1; -- fail
+ERROR:  update or delete on table "pp" violates foreign key constraint "cc_f1_fkey" on table "cc"
+DETAIL:  Key (f1)=(13) is still referenced from table "cc".
+drop table pp, cc;
+create temp table pp (f1 int primary key);
+NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "pp_pkey" for table "pp"
+create temp table cc (f1 int references pp on update restrict);
+insert into pp values(12);
+insert into pp values(11);
+update pp set f1=f1+1;
+insert into cc values(13);
+update pp set f1=f1+1; -- fail
+ERROR:  update or delete on table "pp" violates foreign key constraint "cc_f1_fkey" on table "cc"
+DETAIL:  Key (f1)=(13) is still referenced from table "cc".
+drop table pp, cc;
index 43703d234e7fb990ad7bc12eafa943eedb800c07..377b36c226b5baa394ca69290f463696ab5e4207 100644 (file)
@@ -960,3 +960,25 @@ alter table defc alter column f1 set default 1;
 delete from defp where f1 = 0;
 select * from defc;
 delete from defp where f1 = 1; -- fail
+
+--
+-- Test the difference between NO ACTION and RESTRICT
+--
+create temp table pp (f1 int primary key);
+create temp table cc (f1 int references pp on update no action);
+insert into pp values(12);
+insert into pp values(11);
+update pp set f1=f1+1;
+insert into cc values(13);
+update pp set f1=f1+1;
+update pp set f1=f1+1; -- fail
+drop table pp, cc;
+
+create temp table pp (f1 int primary key);
+create temp table cc (f1 int references pp on update restrict);
+insert into pp values(12);
+insert into pp values(11);
+update pp set f1=f1+1;
+insert into cc values(13);
+update pp set f1=f1+1; -- fail
+drop table pp, cc;