Allow specifying column list for foreign key ON DELETE SET actions
authorPeter Eisentraut <peter@eisentraut.org>
Wed, 8 Dec 2021 10:09:44 +0000 (11:09 +0100)
committerPeter Eisentraut <peter@eisentraut.org>
Wed, 8 Dec 2021 10:13:57 +0000 (11:13 +0100)
Extend the foreign key ON DELETE actions SET NULL and SET DEFAULT by
allowing the specification of a column list, like

    CREATE TABLE posts (
        ...
        FOREIGN KEY (tenant_id, author_id) REFERENCES users ON DELETE SET NULL (author_id)
    );

If a column list is specified, only those columns are set to
null/default, instead of all the columns in the foreign-key
constraint.

This is useful for multitenant or sharded schemas, where the tenant or
shard ID is included in the primary key of all tables but shouldn't be
set to null.

Author: Paul Martinez <paulmtz@google.com>
Discussion: https://www.postgresql.org/message-id/flat/CACqFVBZQyMYJV=njbSMxf+rbDHpx=W=B7AEaMKn8dWn9OZJY7w@mail.gmail.com

22 files changed:
doc/src/sgml/catalogs.sgml
doc/src/sgml/ddl.sgml
doc/src/sgml/ref/alter_table.sgml
doc/src/sgml/ref/create_table.sgml
src/backend/catalog/heap.c
src/backend/catalog/index.c
src/backend/catalog/pg_constraint.c
src/backend/commands/tablecmds.c
src/backend/commands/trigger.c
src/backend/commands/typecmds.c
src/backend/nodes/copyfuncs.c
src/backend/nodes/equalfuncs.c
src/backend/nodes/outfuncs.c
src/backend/parser/gram.y
src/backend/utils/adt/ri_triggers.c
src/backend/utils/adt/ruleutils.c
src/backend/utils/cache/relcache.c
src/include/catalog/catversion.h
src/include/catalog/pg_constraint.h
src/include/nodes/parsenodes.h
src/test/regress/expected/foreign_key.out
src/test/regress/sql/foreign_key.sql

index c1d11be73f73abff697b891dafc83e93691c1a75..025db98763351aa96a76eee2a714ac50bb6290a2 100644 (file)
@@ -2708,6 +2708,18 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
       </para></entry>
      </row>
 
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>confdelsetcols</structfield> <type>int2[]</type>
+       (references <link linkend="catalog-pg-attribute"><structname>pg_attribute</structname></link>.<structfield>attnum</structfield>)
+      </para>
+      <para>
+       If a foreign key with a <literal>SET NULL</literal> or <literal>SET
+       DEFAULT</literal> delete action, the columns that will be updated.
+       If null, all of the referencing columns will be updated.
+      </para></entry>
+     </row>
+
      <row>
       <entry role="catalog_table_entry"><para role="column_definition">
        <structfield>conexclop</structfield> <type>oid[]</type>
index 642ea2a70d04ccf73d4e3cbfb028e76409500177..64d9030652288e0409d92957ac9d38045935e7b4 100644 (file)
@@ -1083,10 +1083,41 @@ CREATE TABLE order_items (
     manager to null or a default might be useful.
    </para>
 
+   <para>
+    The actions <literal>SET NULL</literal> and <literal>SET DEFAULT</literal>
+    can take a column list to specify which columns to set.  Normally, all
+    columns of the foreign-key constraint are set; setting only a subset is
+    useful in some special cases.  Consider the following example:
+<programlisting>
+CREATE TABLE tenants (
+    tenant_id integer PRIMARY KEY
+);
+
+CREATE TABLE users (
+    tenant_id integer REFERENCES tenants ON DELETE CASCADE,
+    user_id integer NOT NULL,
+    PRIMARY KEY (tenant_id, user_id)
+);
+
+CREATE TABLE posts (
+    tenant_id integer REFERENCES tenants ON DELETE CASCADE,
+    post_id integer NOT NULL,
+    author_id integer,
+    PRIMARY KEY (tenant_id, post_id),
+    FOREIGN KEY (tenant_id, author_id) REFERENCES users ON DELETE SET NULL <emphasis>(author_id)</emphasis>
+);
+</programlisting>
+    Without the specification of the column, the foreign key would also set
+    the column <literal>tenant_id</literal> to null, but that column is still
+    required as part of the primary key.
+   </para>
+
    <para>
     Analogous to <literal>ON DELETE</literal> there is also
     <literal>ON UPDATE</literal> which is invoked when a referenced
-    column is changed (updated).  The possible actions are the same.
+    column is changed (updated).  The possible actions are the same,
+    except that column lists cannot be specified for <literal>SET
+    NULL</literal> and <literal>SET DEFAULT</literal>.
     In this case, <literal>CASCADE</literal> means that the updated values of the
     referenced column(s) should be copied into the referencing row(s).
    </para>
index bc5dcba59ced6e402e2c1c5ab6b77cbb6c8458c6..8f14e4a5c4a1a2191a45ab3c4bc62d005bd6811c 100644 (file)
@@ -138,7 +138,7 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
 
 <phrase><replaceable class="parameter">referential_action</replaceable> in a <literal>FOREIGN KEY</literal>/<literal>REFERENCES</literal> constraint is:</phrase>
 
-{ NO ACTION | RESTRICT | CASCADE | SET NULL | SET DEFAULT }
+{ NO ACTION | RESTRICT | CASCADE | SET NULL [ ( <replaceable class="parameter">column_name</replaceable> [, ... ] ) ] | SET DEFAULT [ ( <replaceable class="parameter">column_name</replaceable> [, ... ] ) ] }
 </synopsis>
  </refsynopsisdiv>
 
index 57d51a676a739914514e93af494560685257fdc9..b97bb9ded159a31e2c4f403931ae49f48587d28f 100644 (file)
@@ -108,7 +108,7 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
 
 <phrase><replaceable class="parameter">referential_action</replaceable> in a <literal>FOREIGN KEY</literal>/<literal>REFERENCES</literal> constraint is:</phrase>
 
-{ NO ACTION | RESTRICT | CASCADE | SET NULL | SET DEFAULT }
+{ NO ACTION | RESTRICT | CASCADE | SET NULL [ ( <replaceable class="parameter">column_name</replaceable> [, ... ] ) ] | SET DEFAULT [ ( <replaceable class="parameter">column_name</replaceable> [, ... ] ) ] }
 </synopsis>
 
  </refsynopsisdiv>
@@ -1169,19 +1169,23 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
        </varlistentry>
 
        <varlistentry>
-        <term><literal>SET NULL</literal></term>
+        <term><literal>SET NULL [ ( <replaceable>column_name</replaceable> [, ... ] ) ]</literal></term>
         <listitem>
          <para>
-          Set the referencing column(s) to null.
+          Set all of the referencing columns, or a specified subset of the
+          referencing columns, to null. A subset of columns can only be
+          specified for <literal>ON DELETE</literal> actions.
          </para>
         </listitem>
        </varlistentry>
 
        <varlistentry>
-        <term><literal>SET DEFAULT</literal></term>
+        <term><literal>SET DEFAULT [ ( <replaceable>column_name</replaceable> [, ... ] ) ]</literal></term>
         <listitem>
          <para>
-          Set the referencing column(s) to their default values.
+          Set all of the referencing columns, or a specified subset of the
+          referencing columns, to their default values. A subset of columns
+          can only be specified for <literal>ON DELETE</literal> actions.
           (There must be a row in the referenced table matching the default
           values, if they are not null, or the operation will fail.)
          </para>
@@ -2223,6 +2227,16 @@ CREATE TABLE cities_partdef
    </para>
   </refsect2>
 
+  <refsect2>
+   <title>Foreign-Key Constraint Actions</title>
+
+   <para>
+    The ability to specify column lists in the foreign-key actions
+    <literal>SET DEFAULT</literal> and <literal>SET NULL</literal> is a
+    <productname>PostgreSQL</productname> extension.
+   </para>
+  </refsect2>
+
   <refsect2>
    <title><literal>NULL</literal> <quote>Constraint</quote></title>
 
index eadc3ff76bab60198f08caeeb4ec189a40829ead..6780ec53b7c8e662634745c7ef2d563e863cf40b 100644 (file)
@@ -2441,6 +2441,8 @@ StoreRelCheck(Relation rel, const char *ccname, Node *expr,
                                                          0,
                                                          ' ',
                                                          ' ',
+                                                         NULL,
+                                                         0,
                                                          ' ',
                                                          NULL, /* not an exclusion constraint */
                                                          expr, /* Tree form of check constraint */
index 27f0385ae0c91bba3bf663da35949a8cec6a5c33..1757cd3446faf9920f90765dbea4bfdb060dff99 100644 (file)
@@ -1969,6 +1969,8 @@ index_constraint_create(Relation heapRelation,
                                                                   0,
                                                                   ' ',
                                                                   ' ',
+                                                                  NULL,
+                                                                  0,
                                                                   ' ',
                                                                   indexInfo->ii_ExclusionOps,
                                                                   NULL,        /* no check constraint */
index a4e890020ff4bbeee083e73638292c5ab54750a3..2ced770c39f3952f2d8875babefb558f2421d969 100644 (file)
@@ -68,6 +68,8 @@ CreateConstraintEntry(const char *constraintName,
                                          int foreignNKeys,
                                          char foreignUpdateType,
                                          char foreignDeleteType,
+                                         const int16 *fkDeleteSetCols,
+                                         int numFkDeleteSetCols,
                                          char foreignMatchType,
                                          const Oid *exclOp,
                                          Node *conExpr,
@@ -88,6 +90,7 @@ CreateConstraintEntry(const char *constraintName,
        ArrayType  *conppeqopArray;
        ArrayType  *conffeqopArray;
        ArrayType  *conexclopArray;
+       ArrayType  *confdelsetcolsArray;
        NameData        cname;
        int                     i;
        ObjectAddress conobject;
@@ -136,6 +139,16 @@ CreateConstraintEntry(const char *constraintName,
                        fkdatums[i] = ObjectIdGetDatum(ffEqOp[i]);
                conffeqopArray = construct_array(fkdatums, foreignNKeys,
                                                                                 OIDOID, sizeof(Oid), true, TYPALIGN_INT);
+
+               if (numFkDeleteSetCols > 0)
+               {
+                       for (i = 0; i < numFkDeleteSetCols; i++)
+                               fkdatums[i] = Int16GetDatum(fkDeleteSetCols[i]);
+                       confdelsetcolsArray = construct_array(fkdatums, numFkDeleteSetCols,
+                                                                                  INT2OID, 2, true, TYPALIGN_SHORT);
+               }
+               else
+                       confdelsetcolsArray = NULL;
        }
        else
        {
@@ -143,6 +156,7 @@ CreateConstraintEntry(const char *constraintName,
                conpfeqopArray = NULL;
                conppeqopArray = NULL;
                conffeqopArray = NULL;
+               confdelsetcolsArray = NULL;
        }
 
        if (exclOp != NULL)
@@ -211,6 +225,11 @@ CreateConstraintEntry(const char *constraintName,
        else
                nulls[Anum_pg_constraint_conffeqop - 1] = true;
 
+       if (confdelsetcolsArray)
+               values[Anum_pg_constraint_confdelsetcols - 1] = PointerGetDatum(confdelsetcolsArray);
+       else
+               nulls[Anum_pg_constraint_confdelsetcols - 1] = true;
+
        if (conexclopArray)
                values[Anum_pg_constraint_conexclop - 1] = PointerGetDatum(conexclopArray);
        else
@@ -1157,13 +1176,15 @@ get_primary_key_attnos(Oid relid, bool deferrableOk, Oid *constraintOid)
 /*
  * Extract data from the pg_constraint tuple of a foreign-key constraint.
  *
- * All arguments save the first are output arguments; the last three of them
- * can be passed as NULL if caller doesn't need them.
+ * All arguments save the first are output arguments.  All output arguments
+ * other than numfks, conkey and confkey can be passed as NULL if caller
+ * doesn't need them.
  */
 void
 DeconstructFkConstraintRow(HeapTuple tuple, int *numfks,
                                                   AttrNumber *conkey, AttrNumber *confkey,
-                                                  Oid *pf_eq_oprs, Oid *pp_eq_oprs, Oid *ff_eq_oprs)
+                                                  Oid *pf_eq_oprs, Oid *pp_eq_oprs, Oid *ff_eq_oprs,
+                                                  int *num_fk_del_set_cols, AttrNumber *fk_del_set_cols)
 {
        Oid                     constrId;
        Datum           adatum;
@@ -1260,6 +1281,32 @@ DeconstructFkConstraintRow(HeapTuple tuple, int *numfks,
                        pfree(arr);                     /* free de-toasted copy, if any */
        }
 
+       if (fk_del_set_cols)
+       {
+               adatum = SysCacheGetAttr(CONSTROID, tuple,
+                                                                Anum_pg_constraint_confdelsetcols, &isNull);
+               if (isNull)
+               {
+                       *num_fk_del_set_cols = 0;
+               }
+               else
+               {
+                       int num_delete_cols;
+
+                       arr = DatumGetArrayTypeP(adatum);       /* ensure not toasted */
+                       if (ARR_NDIM(arr) != 1 ||
+                               ARR_HASNULL(arr) ||
+                               ARR_ELEMTYPE(arr) != INT2OID)
+                               elog(ERROR, "confdelsetcols is not a 1-D smallint array");
+                       num_delete_cols = ARR_DIMS(arr)[0];
+                       memcpy(fk_del_set_cols, ARR_DATA_PTR(arr), num_delete_cols * sizeof(int16));
+                       if ((Pointer) arr != DatumGetPointer(adatum))
+                               pfree(arr);                             /* free de-toasted copy, if any */
+
+                       *num_fk_del_set_cols = num_delete_cols;
+               }
+       }
+
        *numfks = numkeys;
 }
 
index c82127130696b8583f68ed5e461af12cca0cf63c..47b29001d5c0819b32ce1dd6b4b4953288a38a77 100644 (file)
@@ -482,11 +482,16 @@ static ObjectAddress addFkRecurseReferenced(List **wqueue, Constraint *fkconstra
                                                                                        Relation rel, Relation pkrel, Oid indexOid, Oid parentConstr,
                                                                                        int numfks, int16 *pkattnum, int16 *fkattnum,
                                                                                        Oid *pfeqoperators, Oid *ppeqoperators, Oid *ffeqoperators,
+                                                                                       int numfkdelsetcols, int16 *fkdelsetcols,
                                                                                        bool old_check_ok);
+static void validateFkOnDeleteSetColumns(int numfks, const int16 *fkattnums,
+                                                                          int numfksetcols, const int16 *fksetcolsattnums,
+                                                                          List *fksetcols);
 static void addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint,
                                                                        Relation rel, Relation pkrel, Oid indexOid, Oid parentConstr,
                                                                        int numfks, int16 *pkattnum, int16 *fkattnum,
                                                                        Oid *pfeqoperators, Oid *ppeqoperators, Oid *ffeqoperators,
+                                                                       int numfkdelsetcols, int16 *fkdelsetcols,
                                                                        bool old_check_ok, LOCKMODE lockmode);
 static void CloneForeignKeyConstraints(List **wqueue, Relation parentRel,
                                                                           Relation partitionRel);
@@ -8973,9 +8978,11 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
        Oid                     pfeqoperators[INDEX_MAX_KEYS];
        Oid                     ppeqoperators[INDEX_MAX_KEYS];
        Oid                     ffeqoperators[INDEX_MAX_KEYS];
+       int16           fkdelsetcols[INDEX_MAX_KEYS];
        int                     i;
        int                     numfks,
-                               numpks;
+                               numpks,
+                               numfkdelsetcols;
        Oid                     indexOid;
        bool            old_check_ok;
        ObjectAddress address;
@@ -9071,11 +9078,19 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
        MemSet(pfeqoperators, 0, sizeof(pfeqoperators));
        MemSet(ppeqoperators, 0, sizeof(ppeqoperators));
        MemSet(ffeqoperators, 0, sizeof(ffeqoperators));
+       MemSet(fkdelsetcols, 0, sizeof(fkdelsetcols));
 
        numfks = transformColumnNameList(RelationGetRelid(rel),
                                                                         fkconstraint->fk_attrs,
                                                                         fkattnum, fktypoid);
 
+       numfkdelsetcols = transformColumnNameList(RelationGetRelid(rel),
+                                                                                         fkconstraint->fk_del_set_cols,
+                                                                                         fkdelsetcols, NULL);
+       validateFkOnDeleteSetColumns(numfks, fkattnum,
+                                                                numfkdelsetcols, fkdelsetcols,
+                                                                fkconstraint->fk_del_set_cols);
+
        /*
         * If the attribute list for the referenced table was omitted, lookup the
         * definition of the primary key and use it.  Otherwise, validate the
@@ -9350,6 +9365,8 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
                                                                         pfeqoperators,
                                                                         ppeqoperators,
                                                                         ffeqoperators,
+                                                                        numfkdelsetcols,
+                                                                        fkdelsetcols,
                                                                         old_check_ok);
 
        /* Now handle the referencing side. */
@@ -9362,6 +9379,8 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
                                                        pfeqoperators,
                                                        ppeqoperators,
                                                        ffeqoperators,
+                                                       numfkdelsetcols,
+                                                       fkdelsetcols,
                                                        old_check_ok,
                                                        lockmode);
 
@@ -9373,6 +9392,40 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
        return address;
 }
 
+/*
+ * validateFkActionSetColumns
+ *             Verifies that columns used in ON DELETE SET NULL/DEFAULT (...)
+ *             column lists are valid.
+ */
+void
+validateFkOnDeleteSetColumns(int numfks, const int16 *fkattnums,
+                                                        int numfksetcols, const int16 *fksetcolsattnums,
+                                                        List *fksetcols)
+{
+       for (int i = 0; i < numfksetcols; i++)
+       {
+               int16 setcol_attnum = fksetcolsattnums[i];
+               bool seen = false;
+
+               for (int j = 0; j < numfks; j++)
+               {
+                       if (fkattnums[j] == setcol_attnum)
+                       {
+                               seen = true;
+                               break;
+                       }
+               }
+
+               if (!seen)
+               {
+                       char *col = strVal(list_nth(fksetcols, i));
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
+                                        errmsg("column \"%s\" referenced in ON DELETE SET action must be part of foreign key", col)));
+               }
+       }
+}
+
 /*
  * addFkRecurseReferenced
  *             subroutine for ATAddForeignKeyConstraint; recurses on the referenced
@@ -9394,6 +9447,10 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
  * numfks is the number of columns in the foreign key
  * pkattnum is the attnum array of referenced attributes.
  * fkattnum is the attnum array of referencing attributes.
+ * numfkdelsetcols is the number of columns in the ON DELETE SET NULL/DELETE
+ *      (...) clause
+ * fkdelsetcols is the attnum array of the columns in the ON DELETE SET
+ *      NULL/DELETE clause
  * pf/pp/ffeqoperators are OID array of operators between columns.
  * old_check_ok signals that this constraint replaces an existing one that
  * was already validated (thus this one doesn't need validation).
@@ -9403,7 +9460,9 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
                                           Relation pkrel, Oid indexOid, Oid parentConstr,
                                           int numfks,
                                           int16 *pkattnum, int16 *fkattnum, Oid *pfeqoperators,
-                                          Oid *ppeqoperators, Oid *ffeqoperators, bool old_check_ok)
+                                          Oid *ppeqoperators, Oid *ffeqoperators,
+                                          int numfkdelsetcols, int16 *fkdelsetcols,
+                                          bool old_check_ok)
 {
        ObjectAddress address;
        Oid                     constrOid;
@@ -9478,6 +9537,8 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
                                                                          numfks,
                                                                          fkconstraint->fk_upd_action,
                                                                          fkconstraint->fk_del_action,
+                                                                         fkdelsetcols,
+                                                                         numfkdelsetcols,
                                                                          fkconstraint->fk_matchtype,
                                                                          NULL, /* no exclusion constraint */
                                                                          NULL, /* no check constraint */
@@ -9559,6 +9620,7 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
                                                                   partIndexId, constrOid, numfks,
                                                                   mapped_pkattnum, fkattnum,
                                                                   pfeqoperators, ppeqoperators, ffeqoperators,
+                                                                  numfkdelsetcols, fkdelsetcols,
                                                                   old_check_ok);
 
                        /* Done -- clean up (but keep the lock) */
@@ -9599,6 +9661,10 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
  * pkattnum is the attnum array of referenced attributes.
  * fkattnum is the attnum array of referencing attributes.
  * pf/pp/ffeqoperators are OID array of operators between columns.
+ * numfkdelsetcols is the number of columns in the ON DELETE SET NULL/DELETE
+ *      (...) clause
+ * fkdelsetcols is the attnum array of the columns in the ON DELETE SET
+ *      NULL/DELETE clause
  * old_check_ok signals that this constraint replaces an existing one that
  *             was already validated (thus this one doesn't need validation).
  * lockmode is the lockmode to acquire on partitions when recursing.
@@ -9608,6 +9674,7 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
                                                Relation pkrel, Oid indexOid, Oid parentConstr,
                                                int numfks, int16 *pkattnum, int16 *fkattnum,
                                                Oid *pfeqoperators, Oid *ppeqoperators, Oid *ffeqoperators,
+                                               int numfkdelsetcols, int16 *fkdelsetcols,
                                                bool old_check_ok, LOCKMODE lockmode)
 {
        AssertArg(OidIsValid(parentConstr));
@@ -9746,6 +9813,8 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
                                                                          numfks,
                                                                          fkconstraint->fk_upd_action,
                                                                          fkconstraint->fk_del_action,
+                                                                         fkdelsetcols,
+                                                                         numfkdelsetcols,
                                                                          fkconstraint->fk_matchtype,
                                                                          NULL,
                                                                          NULL,
@@ -9778,6 +9847,8 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
                                                                        pfeqoperators,
                                                                        ppeqoperators,
                                                                        ffeqoperators,
+                                                                       numfkdelsetcols,
+                                                                       fkdelsetcols,
                                                                        old_check_ok,
                                                                        lockmode);
 
@@ -9883,6 +9954,8 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel)
                Oid                     conpfeqop[INDEX_MAX_KEYS];
                Oid                     conppeqop[INDEX_MAX_KEYS];
                Oid                     conffeqop[INDEX_MAX_KEYS];
+               int                     numfkdelsetcols;
+               AttrNumber      confdelsetcols[INDEX_MAX_KEYS];
                Constraint *fkconstraint;
 
                tuple = SearchSysCache1(CONSTROID, constrOid);
@@ -9915,7 +9988,9 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel)
                                                                   confkey,
                                                                   conpfeqop,
                                                                   conppeqop,
-                                                                  conffeqop);
+                                                                  conffeqop,
+                                                                  &numfkdelsetcols,
+                                                                  confdelsetcols);
 
                for (int i = 0; i < numfks; i++)
                        mapped_confkey[i] = attmap->attnums[confkey[i] - 1];
@@ -9962,6 +10037,8 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel)
                                                           conpfeqop,
                                                           conppeqop,
                                                           conffeqop,
+                                                          numfkdelsetcols,
+                                                          confdelsetcols,
                                                           true);
 
                table_close(fkRel, NoLock);
@@ -10032,6 +10109,8 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
                Oid                     conpfeqop[INDEX_MAX_KEYS];
                Oid                     conppeqop[INDEX_MAX_KEYS];
                Oid                     conffeqop[INDEX_MAX_KEYS];
+               int                     numfkdelsetcols;
+               AttrNumber      confdelsetcols[INDEX_MAX_KEYS];
                Constraint *fkconstraint;
                bool            attached;
                Oid                     indexOid;
@@ -10063,7 +10142,8 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
                                                                           ShareRowExclusiveLock, NULL);
 
                DeconstructFkConstraintRow(tuple, &numfks, conkey, confkey,
-                                                                  conpfeqop, conppeqop, conffeqop);
+                                                                  conpfeqop, conppeqop, conffeqop,
+                                                                  &numfkdelsetcols, confdelsetcols);
                for (int i = 0; i < numfks; i++)
                        mapped_conkey[i] = attmap->attnums[conkey[i] - 1];
 
@@ -10148,6 +10228,8 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
                                                                  numfks,
                                                                  fkconstraint->fk_upd_action,
                                                                  fkconstraint->fk_del_action,
+                                                                 confdelsetcols,
+                                                                 numfkdelsetcols,
                                                                  fkconstraint->fk_matchtype,
                                                                  NULL,
                                                                  NULL,
@@ -10183,6 +10265,8 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
                                                                conpfeqop,
                                                                conppeqop,
                                                                conffeqop,
+                                                               numfkdelsetcols,
+                                                               confdelsetcols,
                                                                false,  /* no old check exists */
                                                                AccessExclusiveLock);
                table_close(pkrel, NoLock);
@@ -10804,7 +10888,7 @@ ATExecValidateConstraint(List **wqueue, Relation rel, char *constrName,
 /*
  * transformColumnNameList - transform list of column names
  *
- * Lookup each name and return its attnum and type OID
+ * Lookup each name and return its attnum and, optionally, type OID
  */
 static int
 transformColumnNameList(Oid relId, List *colList,
@@ -10831,7 +10915,8 @@ transformColumnNameList(Oid relId, List *colList,
                                         errmsg("cannot have more than %d keys in a foreign key",
                                                        INDEX_MAX_KEYS)));
                attnums[attnum] = ((Form_pg_attribute) GETSTRUCT(atttuple))->attnum;
-               atttypids[attnum] = ((Form_pg_attribute) GETSTRUCT(atttuple))->atttypid;
+               if (atttypids != NULL)
+                       atttypids[attnum] = ((Form_pg_attribute) GETSTRUCT(atttuple))->atttypid;
                ReleaseSysCache(atttuple);
                attnum++;
        }
index d8890d2c748a7015ddb49879654e72d12a16eed3..7c8826089bb3e21d1b73a322920d374f4c346d12 100644 (file)
@@ -829,6 +829,8 @@ CreateTriggerFiringOn(CreateTrigStmt *stmt, const char *queryString,
                                                                                          0,
                                                                                          ' ',
                                                                                          ' ',
+                                                                                         NULL,
+                                                                                         0,
                                                                                          ' ',
                                                                                          NULL, /* no exclusion */
                                                                                          NULL, /* no check constraint */
index 9ab40341793113b5d22f0be5f68f8436e80951f6..9dc4fdad49e7d25402eaaa8a1434ec3d0c128d70 100644 (file)
@@ -3545,6 +3545,8 @@ domainAddConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid,
                                                          0,
                                                          ' ',
                                                          ' ',
+                                                         NULL,
+                                                         0,
                                                          ' ',
                                                          NULL, /* not an exclusion constraint */
                                                          expr, /* Tree form of check constraint */
index 297b6ee715f063e1a5167f7d8b16a2f256aea0aa..df0b74788380f9db93cf42498cbf478bdfce0afd 100644 (file)
@@ -3083,6 +3083,7 @@ _copyConstraint(const Constraint *from)
        COPY_SCALAR_FIELD(fk_matchtype);
        COPY_SCALAR_FIELD(fk_upd_action);
        COPY_SCALAR_FIELD(fk_del_action);
+       COPY_NODE_FIELD(fk_del_set_cols);
        COPY_NODE_FIELD(old_conpfeqop);
        COPY_SCALAR_FIELD(old_pktable_oid);
        COPY_SCALAR_FIELD(skip_validation);
index f537d3eb968cb673caa5f191f49e21f9d8238ad2..cb7ddd463cbae562a076c84e3ce8ddd7690069a6 100644 (file)
@@ -2725,6 +2725,7 @@ _equalConstraint(const Constraint *a, const Constraint *b)
        COMPARE_SCALAR_FIELD(fk_matchtype);
        COMPARE_SCALAR_FIELD(fk_upd_action);
        COMPARE_SCALAR_FIELD(fk_del_action);
+       COMPARE_NODE_FIELD(fk_del_set_cols);
        COMPARE_NODE_FIELD(old_conpfeqop);
        COMPARE_SCALAR_FIELD(old_pktable_oid);
        COMPARE_SCALAR_FIELD(skip_validation);
index 3600c9fa364899fef01eee10db4109da35fae08b..91a89b6d51fd50e9ddc6cabe24ac030e3b2c904f 100644 (file)
@@ -3735,6 +3735,7 @@ _outConstraint(StringInfo str, const Constraint *node)
                        WRITE_CHAR_FIELD(fk_matchtype);
                        WRITE_CHAR_FIELD(fk_upd_action);
                        WRITE_CHAR_FIELD(fk_del_action);
+                       WRITE_NODE_FIELD(fk_del_set_cols);
                        WRITE_NODE_FIELD(old_conpfeqop);
                        WRITE_OID_FIELD(old_pktable_oid);
                        WRITE_BOOL_FIELD(skip_validation);
index 86ce33bd97aa1634b14e81a85eaa41f3601df32e..3d4dd43e47bcdfea3de5869b5c429815a76c440f 100644 (file)
@@ -141,6 +141,19 @@ typedef struct GroupClause
        List   *list;
 } GroupClause;
 
+/* Private structs for the result of key_actions and key_action productions */
+typedef struct KeyAction
+{
+       char action;
+       List *cols;
+} KeyAction;
+
+typedef struct KeyActions
+{
+       KeyAction *updateAction;
+       KeyAction *deleteAction;
+} KeyActions;
+
 /* ConstraintAttributeSpec yields an integer bitmask of these flags: */
 #define CAS_NOT_DEFERRABLE                     0x01
 #define CAS_DEFERRABLE                         0x02
@@ -265,6 +278,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
        struct SelectLimit      *selectlimit;
        SetQuantifier    setquantifier;
        struct GroupClause  *groupclause;
+       struct KeyActions       *keyactions;
+       struct KeyAction        *keyaction;
 }
 
 %type <node>   stmt toplevel_stmt schema_stmt routine_body_stmt
@@ -570,7 +585,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <str>            column_compression opt_column_compression
 %type <list>   ColQualList
 %type <node>   ColConstraint ColConstraintElem ConstraintAttr
-%type <ival>   key_actions key_delete key_match key_update key_action
+%type <ival>   key_match
+%type <keyaction> key_delete key_update key_action
+%type <keyactions> key_actions
 %type <ival>   ConstraintAttributeSpec ConstraintAttributeElem
 %type <str>            ExistingIndex
 
@@ -3690,8 +3707,9 @@ ColConstraintElem:
                                        n->fk_attrs = NIL;
                                        n->pk_attrs = $3;
                                        n->fk_matchtype = $4;
-                                       n->fk_upd_action = (char) ($5 >> 8);
-                                       n->fk_del_action = (char) ($5 & 0xFF);
+                                       n->fk_upd_action = ($5)->updateAction->action;
+                                       n->fk_del_action = ($5)->deleteAction->action;
+                                       n->fk_del_set_cols = ($5)->deleteAction->cols;
                                        n->skip_validation = false;
                                        n->initially_valid = true;
                                        $$ = (Node *)n;
@@ -3901,8 +3919,9 @@ ConstraintElem:
                                        n->fk_attrs = $4;
                                        n->pk_attrs = $8;
                                        n->fk_matchtype = $9;
-                                       n->fk_upd_action = (char) ($10 >> 8);
-                                       n->fk_del_action = (char) ($10 & 0xFF);
+                                       n->fk_upd_action = ($10)->updateAction->action;
+                                       n->fk_del_action = ($10)->deleteAction->action;
+                                       n->fk_del_set_cols = ($10)->deleteAction->cols;
                                        processCASbits($11, @11, "FOREIGN KEY",
                                                                   &n->deferrable, &n->initdeferred,
                                                                   &n->skip_validation, NULL,
@@ -3980,37 +3999,106 @@ OptWhereClause:
                        | /*EMPTY*/                                                             { $$ = NULL; }
                ;
 
-/*
- * We combine the update and delete actions into one value temporarily
- * for simplicity of parsing, and then break them down again in the
- * calling production.  update is in the left 8 bits, delete in the right.
- * Note that NOACTION is the default.
- */
 key_actions:
                        key_update
-                               { $$ = ($1 << 8) | (FKCONSTR_ACTION_NOACTION & 0xFF); }
+                               {
+                                       KeyActions *n = palloc(sizeof(KeyActions));
+                                       n->updateAction = $1;
+                                       n->deleteAction = palloc(sizeof(KeyAction));
+                                       n->deleteAction->action = FKCONSTR_ACTION_NOACTION;
+                                       n->deleteAction->cols = NIL;
+                                       $$ = n;
+                               }
                        | key_delete
-                               { $$ = (FKCONSTR_ACTION_NOACTION << 8) | ($1 & 0xFF); }
+                               {
+                                       KeyActions *n = palloc(sizeof(KeyActions));
+                                       n->updateAction = palloc(sizeof(KeyAction));
+                                       n->updateAction->action = FKCONSTR_ACTION_NOACTION;
+                                       n->updateAction->cols = NIL;
+                                       n->deleteAction = $1;
+                                       $$ = n;
+                               }
                        | key_update key_delete
-                               { $$ = ($1 << 8) | ($2 & 0xFF); }
+                               {
+                                       KeyActions *n = palloc(sizeof(KeyActions));
+                                       n->updateAction = $1;
+                                       n->deleteAction = $2;
+                                       $$ = n;
+                               }
                        | key_delete key_update
-                               { $$ = ($2 << 8) | ($1 & 0xFF); }
+                               {
+                                       KeyActions *n = palloc(sizeof(KeyActions));
+                                       n->updateAction = $2;
+                                       n->deleteAction = $1;
+                                       $$ = n;
+                               }
                        | /*EMPTY*/
-                               { $$ = (FKCONSTR_ACTION_NOACTION << 8) | (FKCONSTR_ACTION_NOACTION & 0xFF); }
+                               {
+                                       KeyActions *n = palloc(sizeof(KeyActions));
+                                       n->updateAction = palloc(sizeof(KeyAction));
+                                       n->updateAction->action = FKCONSTR_ACTION_NOACTION;
+                                       n->updateAction->cols = NIL;
+                                       n->deleteAction = palloc(sizeof(KeyAction));
+                                       n->deleteAction->action = FKCONSTR_ACTION_NOACTION;
+                                       n->deleteAction->cols = NIL;
+                                       $$ = n;
+                               }
                ;
 
-key_update: ON UPDATE key_action               { $$ = $3; }
+key_update: ON UPDATE key_action
+                               {
+                                       if (($3)->cols)
+                                               ereport(ERROR,
+                                                               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                                                errmsg("a column list with %s is only supported for ON DELETE actions",
+                                                                               ($3)->action == FKCONSTR_ACTION_SETNULL ? "SET NULL" : "SET DEFAULT"),
+                                                                parser_errposition(@1)));
+                                       $$ = $3;
+                               }
                ;
 
-key_delete: ON DELETE_P key_action             { $$ = $3; }
+key_delete: ON DELETE_P key_action
+                               {
+                                       $$ = $3;
+                               }
                ;
 
 key_action:
-                       NO ACTION                                       { $$ = FKCONSTR_ACTION_NOACTION; }
-                       | RESTRICT                                      { $$ = FKCONSTR_ACTION_RESTRICT; }
-                       | CASCADE                                       { $$ = FKCONSTR_ACTION_CASCADE; }
-                       | SET NULL_P                            { $$ = FKCONSTR_ACTION_SETNULL; }
-                       | SET DEFAULT                           { $$ = FKCONSTR_ACTION_SETDEFAULT; }
+                       NO ACTION
+                               {
+                                       KeyAction *n = palloc(sizeof(KeyAction));
+                                       n->action = FKCONSTR_ACTION_NOACTION;
+                                       n->cols = NIL;
+                                       $$ = n;
+                               }
+                       | RESTRICT
+                               {
+                                       KeyAction *n = palloc(sizeof(KeyAction));
+                                       n->action = FKCONSTR_ACTION_RESTRICT;
+                                       n->cols = NIL;
+                                       $$ = n;
+                               }
+                       | CASCADE
+                               {
+                                       KeyAction *n = palloc(sizeof(KeyAction));
+                                       n->action = FKCONSTR_ACTION_CASCADE;
+                                       n->cols = NIL;
+                                       $$ = n;
+                               }
+                       | SET NULL_P opt_column_list
+                               {
+                                       KeyAction *n = palloc(sizeof(KeyAction));
+                                       n->action = FKCONSTR_ACTION_SETNULL;
+                                       n->cols = $3;
+                                       $$ = n;
+                               }
+                       | SET DEFAULT opt_column_list
+                               {
+                                       KeyAction *n = palloc(sizeof(KeyAction));
+                                       n->action = FKCONSTR_ACTION_SETDEFAULT;
+                                       n->cols = $3;
+                                       $$ = n;
+                               }
                ;
 
 OptInherit: INHERITS '(' qualified_name_list ')'       { $$ = $3; }
index 96269fc2adb638a87df368b99b34a89f3682c304..8ebb2a50a11036a78be8a62bfc3ced9218459aa6 100644 (file)
 #define RI_PLAN_CHECK_LOOKUPPK_FROM_PK 2
 #define RI_PLAN_LAST_ON_PK                             RI_PLAN_CHECK_LOOKUPPK_FROM_PK
 /* 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_RESTRICT_CHECKREF              5
-#define RI_PLAN_SETNULL_DOUPDATE               6
-#define RI_PLAN_SETDEFAULT_DOUPDATE            7
+#define RI_PLAN_CASCADE_ONDELETE               3
+#define RI_PLAN_CASCADE_ONUPDATE               4
+/* For RESTRICT, the same plan can be used for both ON DELETE and ON UPDATE triggers. */
+#define RI_PLAN_RESTRICT                               5
+#define RI_PLAN_SETNULL_ONDELETE               6
+#define RI_PLAN_SETNULL_ONUPDATE               7
+#define RI_PLAN_SETDEFAULT_ONDELETE            8
+#define RI_PLAN_SETDEFAULT_ONUPDATE            9
 
 #define MAX_QUOTED_NAME_LEN  (NAMEDATALEN*2+3)
 #define MAX_QUOTED_REL_NAME_LEN  (MAX_QUOTED_NAME_LEN*2)
@@ -110,6 +113,8 @@ typedef struct RI_ConstraintInfo
        Oid                     fk_relid;               /* referencing relation */
        char            confupdtype;    /* foreign key's ON UPDATE action */
        char            confdeltype;    /* foreign key's ON DELETE action */
+       int                     ndelsetcols;    /* number of columns referenced in ON DELETE SET clause */
+       int16           confdelsetcols[RI_MAX_NUMKEYS]; /* attnums of cols to set on delete */
        char            confmatchtype;  /* foreign key's match type */
        int                     nkeys;                  /* number of key columns */
        int16           pk_attnums[RI_MAX_NUMKEYS]; /* attnums of referenced cols */
@@ -180,7 +185,7 @@ static bool ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel,
                                                          TupleTableSlot *oldslot,
                                                          const RI_ConstraintInfo *riinfo);
 static Datum ri_restrict(TriggerData *trigdata, bool is_no_action);
-static Datum ri_set(TriggerData *trigdata, bool is_set_null);
+static Datum ri_set(TriggerData *trigdata, bool is_set_null, int tgkind);
 static void quoteOneName(char *buffer, const char *name);
 static void quoteRelationName(char *buffer, Relation rel);
 static void ri_GenerateQual(StringInfo buf,
@@ -660,7 +665,7 @@ ri_restrict(TriggerData *trigdata, bool is_no_action)
         * Fetch or prepare a saved plan for the restrict lookup (it's the same
         * query for delete and update cases)
         */
-       ri_BuildQueryKey(&qkey, riinfo, RI_PLAN_RESTRICT_CHECKREF);
+       ri_BuildQueryKey(&qkey, riinfo, RI_PLAN_RESTRICT);
 
        if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
        {
@@ -767,7 +772,7 @@ RI_FKey_cascade_del(PG_FUNCTION_ARGS)
                elog(ERROR, "SPI_connect failed");
 
        /* Fetch or prepare a saved plan for the cascaded delete */
-       ri_BuildQueryKey(&qkey, riinfo, RI_PLAN_CASCADE_DEL_DODELETE);
+       ri_BuildQueryKey(&qkey, riinfo, RI_PLAN_CASCADE_ONDELETE);
 
        if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
        {
@@ -876,7 +881,7 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS)
                elog(ERROR, "SPI_connect failed");
 
        /* Fetch or prepare a saved plan for the cascaded update */
-       ri_BuildQueryKey(&qkey, riinfo, RI_PLAN_CASCADE_UPD_DOUPDATE);
+       ri_BuildQueryKey(&qkey, riinfo, RI_PLAN_CASCADE_ONUPDATE);
 
        if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
        {
@@ -970,7 +975,7 @@ RI_FKey_setnull_del(PG_FUNCTION_ARGS)
        ri_CheckTrigger(fcinfo, "RI_FKey_setnull_del", RI_TRIGTYPE_DELETE);
 
        /* Share code with UPDATE case */
-       return ri_set((TriggerData *) fcinfo->context, true);
+       return ri_set((TriggerData *) fcinfo->context, true, RI_TRIGTYPE_DELETE);
 }
 
 /*
@@ -985,7 +990,7 @@ RI_FKey_setnull_upd(PG_FUNCTION_ARGS)
        ri_CheckTrigger(fcinfo, "RI_FKey_setnull_upd", RI_TRIGTYPE_UPDATE);
 
        /* Share code with DELETE case */
-       return ri_set((TriggerData *) fcinfo->context, true);
+       return ri_set((TriggerData *) fcinfo->context, true, RI_TRIGTYPE_UPDATE);
 }
 
 /*
@@ -1000,7 +1005,7 @@ RI_FKey_setdefault_del(PG_FUNCTION_ARGS)
        ri_CheckTrigger(fcinfo, "RI_FKey_setdefault_del", RI_TRIGTYPE_DELETE);
 
        /* Share code with UPDATE case */
-       return ri_set((TriggerData *) fcinfo->context, false);
+       return ri_set((TriggerData *) fcinfo->context, false, RI_TRIGTYPE_DELETE);
 }
 
 /*
@@ -1015,7 +1020,7 @@ RI_FKey_setdefault_upd(PG_FUNCTION_ARGS)
        ri_CheckTrigger(fcinfo, "RI_FKey_setdefault_upd", RI_TRIGTYPE_UPDATE);
 
        /* Share code with DELETE case */
-       return ri_set((TriggerData *) fcinfo->context, false);
+       return ri_set((TriggerData *) fcinfo->context, false, RI_TRIGTYPE_UPDATE);
 }
 
 /*
@@ -1025,7 +1030,7 @@ RI_FKey_setdefault_upd(PG_FUNCTION_ARGS)
  * NULL, and ON UPDATE SET DEFAULT.
  */
 static Datum
-ri_set(TriggerData *trigdata, bool is_set_null)
+ri_set(TriggerData *trigdata, bool is_set_null, int tgkind)
 {
        const RI_ConstraintInfo *riinfo;
        Relation        fk_rel;
@@ -1033,6 +1038,7 @@ ri_set(TriggerData *trigdata, bool is_set_null)
        TupleTableSlot *oldslot;
        RI_QueryKey qkey;
        SPIPlanPtr      qplan;
+       int32           queryno;
 
        riinfo = ri_FetchConstraintInfo(trigdata->tg_trigger,
                                                                        trigdata->tg_relation, true);
@@ -1051,18 +1057,28 @@ ri_set(TriggerData *trigdata, bool is_set_null)
                elog(ERROR, "SPI_connect failed");
 
        /*
-        * Fetch or prepare a saved plan for the set null/default operation (it's
-        * the same query for delete and update cases)
+        * Fetch or prepare a saved plan for the trigger.
         */
-       ri_BuildQueryKey(&qkey, riinfo,
-                                        (is_set_null
-                                         ? RI_PLAN_SETNULL_DOUPDATE
-                                         : RI_PLAN_SETDEFAULT_DOUPDATE));
+       switch (tgkind) {
+               case RI_TRIGTYPE_UPDATE:
+                       queryno = is_set_null
+                               ? RI_PLAN_SETNULL_ONUPDATE
+                               : RI_PLAN_SETDEFAULT_ONUPDATE;
+                       break;
+               case RI_TRIGTYPE_DELETE:
+                       queryno = is_set_null
+                               ? RI_PLAN_SETNULL_ONDELETE
+                               : RI_PLAN_SETDEFAULT_ONDELETE;
+                       break;
+               default:
+                       elog(ERROR, "invalid tgkind passed to ri_set");
+       }
+
+       ri_BuildQueryKey(&qkey, riinfo, queryno);
 
        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];
@@ -1070,6 +1086,32 @@ ri_set(TriggerData *trigdata, bool is_set_null)
                const char *qualsep;
                Oid                     queryoids[RI_MAX_NUMKEYS];
                const char *fk_only;
+               int num_cols_to_set;
+               const int16 *set_cols;
+
+               switch (tgkind) {
+                       case RI_TRIGTYPE_UPDATE:
+                               num_cols_to_set = riinfo->nkeys;
+                               set_cols = riinfo->fk_attnums;
+                               break;
+                       case RI_TRIGTYPE_DELETE:
+                               /*
+                                * If confdelsetcols are present, then we only update
+                                * the columns specified in that array, otherwise we
+                                * update all the referencing columns.
+                                */
+                               if (riinfo->ndelsetcols != 0) {
+                                       num_cols_to_set = riinfo->ndelsetcols;
+                                       set_cols = riinfo->confdelsetcols;
+                               }
+                               else {
+                                       num_cols_to_set = riinfo->nkeys;
+                                       set_cols = riinfo->fk_attnums;
+                               }
+                               break;
+                       default:
+                               elog(ERROR, "invalid tgkind passed to ri_set");
+               }
 
                /* ----------
                 * The query string built is
@@ -1080,13 +1122,29 @@ ri_set(TriggerData *trigdata, bool is_set_null)
                 * ----------
                 */
                initStringInfo(&querybuf);
-               initStringInfo(&qualbuf);
                fk_only = fk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ?
                        "" : "ONLY ";
                quoteRelationName(fkrelname, fk_rel);
                appendStringInfo(&querybuf, "UPDATE %s%s SET",
                                                 fk_only, fkrelname);
+
+               /*
+                * Add assignment clauses
+                */
                querysep = "";
+               for (int i = 0; i < num_cols_to_set; i++)
+               {
+                       quoteOneName(attname, RIAttName(fk_rel, set_cols[i]));
+                       appendStringInfo(&querybuf,
+                                                        "%s %s = %s",
+                                                        querysep, attname,
+                                                        is_set_null ? "NULL" : "DEFAULT");
+                       querysep = ",";
+               }
+
+               /*
+                * Add WHERE clause
+                */
                qualsep = "WHERE";
                for (int i = 0; i < riinfo->nkeys; i++)
                {
@@ -1097,22 +1155,17 @@ ri_set(TriggerData *trigdata, bool is_set_null)
 
                        quoteOneName(attname,
                                                 RIAttName(fk_rel, riinfo->fk_attnums[i]));
-                       appendStringInfo(&querybuf,
-                                                        "%s %s = %s",
-                                                        querysep, attname,
-                                                        is_set_null ? "NULL" : "DEFAULT");
+
                        sprintf(paramname, "$%d", i + 1);
-                       ri_GenerateQual(&qualbuf, qualsep,
+                       ri_GenerateQual(&querybuf, qualsep,
                                                        paramname, pk_type,
                                                        riinfo->pf_eq_oprs[i],
                                                        attname, fk_type);
                        if (pk_coll != fk_coll && !get_collation_isdeterministic(pk_coll))
                                ri_GenerateQualCollation(&querybuf, pk_coll);
-                       querysep = ",";
                        qualsep = "AND";
                        queryoids[i] = pk_type;
                }
-               appendBinaryStringInfo(&querybuf, qualbuf.data, qualbuf.len);
 
                /* Prepare and save the plan */
                qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids,
@@ -2098,7 +2151,9 @@ ri_LoadConstraintInfo(Oid constraintOid)
                                                           riinfo->pk_attnums,
                                                           riinfo->pf_eq_oprs,
                                                           riinfo->pp_eq_oprs,
-                                                          riinfo->ff_eq_oprs);
+                                                          riinfo->ff_eq_oprs,
+                                                          &riinfo->ndelsetcols,
+                                                          riinfo->confdelsetcols);
 
        ReleaseSysCache(tup);
 
index 6b4022c3bcc7f7a74eb383b16c69d1314fc73051..8da525c715b2ab58c098a54da28ee74169316310 100644 (file)
@@ -2287,6 +2287,16 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
                                if (string)
                                        appendStringInfo(&buf, " ON DELETE %s", string);
 
+                               /* Add columns specified to SET NULL or SET DEFAULT if provided. */
+                               val = SysCacheGetAttr(CONSTROID, tup,
+                                                                         Anum_pg_constraint_confdelsetcols, &isnull);
+                               if (!isnull)
+                               {
+                                       appendStringInfo(&buf, " (");
+                                       decompile_column_index_array(val, conForm->conrelid, &buf);
+                                       appendStringInfo(&buf, ")");
+                               }
+
                                break;
                        }
                case CONSTRAINT_PRIMARY:
index 2b32edebe78a32f3e1190bb9adf71abe83a98b4c..105d8d4601c6f40fe9a4149a9cb374db690f98cb 100644 (file)
@@ -4592,7 +4592,7 @@ RelationGetFKeyList(Relation relation)
                                                                   info->conkey,
                                                                   info->confkey,
                                                                   info->conpfeqop,
-                                                                  NULL, NULL);
+                                                                  NULL, NULL, NULL, NULL);
 
                /* Add FK's node to the result list */
                result = lappend(result, info);
index d0fa1d1222b0855576ec8bd1755f77202a6fc2f3..c8259b833bcd5caf7fcad3e6d0303ead85b44b01 100644 (file)
@@ -53,6 +53,6 @@
  */
 
 /*                                                     yyyymmddN */
-#define CATALOG_VERSION_NO     202111301
+#define CATALOG_VERSION_NO     202112081
 
 #endif
index e75baa8e1ed1c39f9eb310a15ab64986d1749cd0..eab50053b01e418d6a66d582d60447df3faf4fbe 100644 (file)
@@ -138,6 +138,12 @@ CATALOG(pg_constraint,2606,ConstraintRelationId)
         */
        Oid                     conffeqop[1] BKI_LOOKUP(pg_operator);
 
+       /*
+        * If a foreign key with an ON DELETE SET NULL/DEFAULT action, the subset
+        * of conkey to updated.  If null, all columns are updated.
+        */
+       int16           confdelsetcols[1];
+
        /*
         * If an exclusion constraint, the OIDs of the exclusion operators for
         * each column of the constraint
@@ -220,6 +226,8 @@ extern Oid  CreateConstraintEntry(const char *constraintName,
                                                                  int foreignNKeys,
                                                                  char foreignUpdateType,
                                                                  char foreignDeleteType,
+                                                                 const int16 *fkDeleteSetCols,
+                                                                 int numFkDeleteSetCols,
                                                                  char foreignMatchType,
                                                                  const Oid *exclOp,
                                                                  Node *conExpr,
@@ -254,7 +262,8 @@ extern Bitmapset *get_primary_key_attnos(Oid relid, bool deferrableOk,
                                                                                 Oid *constraintOid);
 extern void DeconstructFkConstraintRow(HeapTuple tuple, int *numfks,
                                                                           AttrNumber *conkey, AttrNumber *confkey,
-                                                                          Oid *pf_eq_oprs, Oid *pp_eq_oprs, Oid *ff_eq_oprs);
+                                                                          Oid *pf_eq_oprs, Oid *pp_eq_oprs, Oid *ff_eq_oprs,
+                                                                          int *num_fk_del_set_cols, AttrNumber *fk_del_set_cols);
 
 extern bool check_functional_grouping(Oid relid,
                                                                          Index varno, Index varlevelsup,
index 067138e6b59409b6bf4856fc319aa934e3102195..4c5a8a39bf1ea1712dcba7d5a87640e9b0a542d9 100644 (file)
@@ -2301,6 +2301,7 @@ typedef struct Constraint
        char            fk_matchtype;   /* FULL, PARTIAL, SIMPLE */
        char            fk_upd_action;  /* ON UPDATE action */
        char            fk_del_action;  /* ON DELETE action */
+       List       *fk_del_set_cols;    /* ON DELETE SET NULL/DEFAULT (col1, col2) */
        List       *old_conpfeqop;      /* pg_constraint.conpfeqop of my former self */
        Oid                     old_pktable_oid;        /* pg_constraint.confrelid of my former
                                                                         * self */
index bf794dce9d8b68bef3d7d83bb522ebbf2001ba54..4c5274983d48b21ff7d4eaee192672d05f9b7c08 100644 (file)
@@ -755,6 +755,44 @@ SELECT * from FKTABLE;
         |        |        |      1
 (7 rows)
 
+DROP TABLE FKTABLE;
+DROP TABLE PKTABLE;
+-- Test for ON DELETE SET NULL/DEFAULT (column_list);
+CREATE TABLE PKTABLE (tid int, id int, PRIMARY KEY (tid, id));
+CREATE TABLE FKTABLE (tid int, id int, foo int, FOREIGN KEY (tid, id) REFERENCES PKTABLE ON DELETE SET NULL (bar));
+ERROR:  column "bar" referenced in foreign key constraint does not exist
+CREATE TABLE FKTABLE (tid int, id int, foo int, FOREIGN KEY (tid, id) REFERENCES PKTABLE ON DELETE SET NULL (foo));
+ERROR:  column "foo" referenced in ON DELETE SET action must be part of foreign key
+CREATE TABLE FKTABLE (tid int, id int, foo int, FOREIGN KEY (tid, foo) REFERENCES PKTABLE ON UPDATE SET NULL (foo));
+ERROR:  a column list with SET NULL is only supported for ON DELETE actions
+LINE 1: ...oo int, FOREIGN KEY (tid, foo) REFERENCES PKTABLE ON UPDATE ...
+                                                             ^
+CREATE TABLE FKTABLE (
+  tid int, id int,
+  fk_id_del_set_null int,
+  fk_id_del_set_default int DEFAULT 0,
+  FOREIGN KEY (tid, fk_id_del_set_null) REFERENCES PKTABLE ON DELETE SET NULL (fk_id_del_set_null),
+  FOREIGN KEY (tid, fk_id_del_set_default) REFERENCES PKTABLE ON DELETE SET DEFAULT (fk_id_del_set_default)
+);
+SELECT pg_get_constraintdef(oid) FROM pg_constraint WHERE conrelid = 'fktable'::regclass::oid ORDER BY oid;
+                                                pg_get_constraintdef                                                
+--------------------------------------------------------------------------------------------------------------------
+ FOREIGN KEY (tid, fk_id_del_set_null) REFERENCES pktable(tid, id) ON DELETE SET NULL (fk_id_del_set_null)
+ FOREIGN KEY (tid, fk_id_del_set_default) REFERENCES pktable(tid, id) ON DELETE SET DEFAULT (fk_id_del_set_default)
+(2 rows)
+
+INSERT INTO PKTABLE VALUES (1, 0), (1, 1), (1, 2);
+INSERT INTO FKTABLE VALUES
+  (1, 1, 1, NULL),
+  (1, 2, NULL, 2);
+DELETE FROM PKTABLE WHERE id = 1 OR id = 2;
+SELECT * FROM FKTABLE ORDER BY id;
+ tid | id | fk_id_del_set_null | fk_id_del_set_default 
+-----+----+--------------------+-----------------------
+   1 |  1 |                    |                      
+   1 |  2 |                    |                     0
+(2 rows)
+
 DROP TABLE FKTABLE;
 DROP TABLE PKTABLE;
 CREATE TABLE PKTABLE (ptest1 int PRIMARY KEY);
@@ -1734,6 +1772,39 @@ SELECT * FROM fk_partitioned_fk WHERE b = 142857;
  2501 | 142857
 (1 row)
 
+-- ON DELETE SET NULL column_list
+ALTER TABLE fk_partitioned_fk DROP CONSTRAINT fk_partitioned_fk_a_b_fkey;
+ALTER TABLE fk_partitioned_fk ADD FOREIGN KEY (a, b)
+  REFERENCES fk_notpartitioned_pk
+  ON DELETE SET NULL (a);
+BEGIN;
+DELETE FROM fk_notpartitioned_pk WHERE b = 142857;
+SELECT * FROM fk_partitioned_fk WHERE a IS NOT NULL OR b IS NOT NULL ORDER BY a NULLS LAST;
+  a   |   b    
+------+--------
+ 2502 |       
+      | 142857
+(2 rows)
+
+ROLLBACK;
+-- ON DELETE SET DEFAULT column_list
+ALTER TABLE fk_partitioned_fk DROP CONSTRAINT fk_partitioned_fk_a_b_fkey;
+ALTER TABLE fk_partitioned_fk ADD FOREIGN KEY (a, b)
+  REFERENCES fk_notpartitioned_pk
+  ON DELETE SET DEFAULT (a);
+BEGIN;
+DELETE FROM fk_partitioned_fk;
+DELETE FROM fk_notpartitioned_pk;
+INSERT INTO fk_notpartitioned_pk VALUES (500, 100000), (2501, 100000);
+INSERT INTO fk_partitioned_fk VALUES (500, 100000);
+DELETE FROM fk_notpartitioned_pk WHERE a = 500;
+SELECT * FROM fk_partitioned_fk ORDER BY a;
+  a   |   b    
+------+--------
+ 2501 | 100000
+(1 row)
+
+ROLLBACK;
 -- ON UPDATE/DELETE CASCADE
 ALTER TABLE fk_partitioned_fk DROP CONSTRAINT fk_partitioned_fk_a_b_fkey;
 ALTER TABLE fk_partitioned_fk ADD FOREIGN KEY (a, b)
index de417b62b64dcf366e0cf16fc3affd62a16c5c40..fa781b6e32cc33fe6662e9bb63753fe6ebaa9857 100644 (file)
@@ -463,6 +463,33 @@ SELECT * from FKTABLE;
 DROP TABLE FKTABLE;
 DROP TABLE PKTABLE;
 
+-- Test for ON DELETE SET NULL/DEFAULT (column_list);
+CREATE TABLE PKTABLE (tid int, id int, PRIMARY KEY (tid, id));
+CREATE TABLE FKTABLE (tid int, id int, foo int, FOREIGN KEY (tid, id) REFERENCES PKTABLE ON DELETE SET NULL (bar));
+CREATE TABLE FKTABLE (tid int, id int, foo int, FOREIGN KEY (tid, id) REFERENCES PKTABLE ON DELETE SET NULL (foo));
+CREATE TABLE FKTABLE (tid int, id int, foo int, FOREIGN KEY (tid, foo) REFERENCES PKTABLE ON UPDATE SET NULL (foo));
+CREATE TABLE FKTABLE (
+  tid int, id int,
+  fk_id_del_set_null int,
+  fk_id_del_set_default int DEFAULT 0,
+  FOREIGN KEY (tid, fk_id_del_set_null) REFERENCES PKTABLE ON DELETE SET NULL (fk_id_del_set_null),
+  FOREIGN KEY (tid, fk_id_del_set_default) REFERENCES PKTABLE ON DELETE SET DEFAULT (fk_id_del_set_default)
+);
+
+SELECT pg_get_constraintdef(oid) FROM pg_constraint WHERE conrelid = 'fktable'::regclass::oid ORDER BY oid;
+
+INSERT INTO PKTABLE VALUES (1, 0), (1, 1), (1, 2);
+INSERT INTO FKTABLE VALUES
+  (1, 1, 1, NULL),
+  (1, 2, NULL, 2);
+
+DELETE FROM PKTABLE WHERE id = 1 OR id = 2;
+
+SELECT * FROM FKTABLE ORDER BY id;
+
+DROP TABLE FKTABLE;
+DROP TABLE PKTABLE;
+
 CREATE TABLE PKTABLE (ptest1 int PRIMARY KEY);
 CREATE TABLE FKTABLE_FAIL1 ( ftest1 int, CONSTRAINT fkfail1 FOREIGN KEY (ftest2) REFERENCES PKTABLE);
 CREATE TABLE FKTABLE_FAIL2 ( ftest1 int, CONSTRAINT fkfail1 FOREIGN KEY (ftest1) REFERENCES PKTABLE(ptest2));
@@ -1284,6 +1311,30 @@ INSERT INTO fk_notpartitioned_pk VALUES (2501, 142857);
 UPDATE fk_notpartitioned_pk SET a = 1500 WHERE a = 2502;
 SELECT * FROM fk_partitioned_fk WHERE b = 142857;
 
+-- ON DELETE SET NULL column_list
+ALTER TABLE fk_partitioned_fk DROP CONSTRAINT fk_partitioned_fk_a_b_fkey;
+ALTER TABLE fk_partitioned_fk ADD FOREIGN KEY (a, b)
+  REFERENCES fk_notpartitioned_pk
+  ON DELETE SET NULL (a);
+BEGIN;
+DELETE FROM fk_notpartitioned_pk WHERE b = 142857;
+SELECT * FROM fk_partitioned_fk WHERE a IS NOT NULL OR b IS NOT NULL ORDER BY a NULLS LAST;
+ROLLBACK;
+
+-- ON DELETE SET DEFAULT column_list
+ALTER TABLE fk_partitioned_fk DROP CONSTRAINT fk_partitioned_fk_a_b_fkey;
+ALTER TABLE fk_partitioned_fk ADD FOREIGN KEY (a, b)
+  REFERENCES fk_notpartitioned_pk
+  ON DELETE SET DEFAULT (a);
+BEGIN;
+DELETE FROM fk_partitioned_fk;
+DELETE FROM fk_notpartitioned_pk;
+INSERT INTO fk_notpartitioned_pk VALUES (500, 100000), (2501, 100000);
+INSERT INTO fk_partitioned_fk VALUES (500, 100000);
+DELETE FROM fk_notpartitioned_pk WHERE a = 500;
+SELECT * FROM fk_partitioned_fk ORDER BY a;
+ROLLBACK;
+
 -- ON UPDATE/DELETE CASCADE
 ALTER TABLE fk_partitioned_fk DROP CONSTRAINT fk_partitioned_fk_a_b_fkey;
 ALTER TABLE fk_partitioned_fk ADD FOREIGN KEY (a, b)