diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 2b2c70a26e..f3e8645cf1 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -2703,6 +2703,30 @@ 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>confupdsetcols</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> update action, which columns should be updated.
+       If empty, all of the referencing columns should be updated.
+      </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, which columns should be updated.
+       If empty, all of the referencing columns should be updated.
+      </para></entry>
+     </row>
+
      <row>
       <entry role="catalog_table_entry"><para role="column_definition">
        <structfield>conexclop</structfield> <type>oid[]</type>
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 473a0a4aeb..4542197cd2 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -105,6 +105,11 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
 <phrase><replaceable class="parameter">exclude_element</replaceable> in an <literal>EXCLUDE</literal> constraint is:</phrase>
 
 { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [ ASC | DESC ] [ NULLS { FIRST | LAST } ]
+
+<phrase><replaceable class="parameter">referential_action</replaceable> in an <literal>EXCLUDE</literal> constraint is:</phrase>
+
+{ NO ACTION | RESTRICT | CASCADE | SET NULL [ ( <replaceable class="parameter">column_name</replaceable> [, ... ] ) ] | SET DEFAULT [ ( <replaceable class="parameter">column_name</replaceable> [, ... ] ) ] }
+
 </synopsis>
 
  </refsynopsisdiv>
@@ -1165,19 +1170,21 @@ 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.
          </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.
           (There must be a row in the referenced table matching the default
           values, if they are not null, or the operation will fail.)
          </para>
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 83746d3fd9..bb5413a62b 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -2480,7 +2480,11 @@ StoreRelCheck(Relation rel, const char *ccname, Node *expr,
 							  NULL,
 							  0,
 							  ' ',
+							  NULL,
+							  0,
 							  ' ',
+							  NULL,
+							  0,
 							  ' ',
 							  NULL, /* not an exclusion constraint */
 							  expr, /* Tree form of check constraint */
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 26bfa74ce7..7673636aaf 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1958,7 +1958,11 @@ index_constraint_create(Relation heapRelation,
 								   NULL,
 								   0,
 								   ' ',
+								   NULL,
+								   0,
 								   ' ',
+								   NULL,
+								   0,
 								   ' ',
 								   indexInfo->ii_ExclusionOps,
 								   NULL,	/* no check constraint */
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index a4e890020f..1a2f1ecee3 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -67,7 +67,11 @@ CreateConstraintEntry(const char *constraintName,
 					  const Oid *ffEqOp,
 					  int foreignNKeys,
 					  char foreignUpdateType,
+					  const int16 *fkUpdateSetCols,
+					  int numFkUpdateSetCols,
 					  char foreignDeleteType,
+					  const int16 *fkDeleteSetCols,
+					  int numFkDeleteSetCols,
 					  char foreignMatchType,
 					  const Oid *exclOp,
 					  Node *conExpr,
@@ -88,6 +92,8 @@ CreateConstraintEntry(const char *constraintName,
 	ArrayType  *conppeqopArray;
 	ArrayType  *conffeqopArray;
 	ArrayType  *conexclopArray;
+	ArrayType  *confupdsetcolsArray;
+	ArrayType  *confdelsetcolsArray;
 	NameData	cname;
 	int			i;
 	ObjectAddress conobject;
@@ -136,6 +142,14 @@ CreateConstraintEntry(const char *constraintName,
 			fkdatums[i] = ObjectIdGetDatum(ffEqOp[i]);
 		conffeqopArray = construct_array(fkdatums, foreignNKeys,
 										 OIDOID, sizeof(Oid), true, TYPALIGN_INT);
+		for (i = 0; i < numFkUpdateSetCols; i++)
+			fkdatums[i] = Int16GetDatum(fkUpdateSetCols[i]);
+		confupdsetcolsArray = construct_array(fkdatums, numFkUpdateSetCols,
+									   INT2OID, 2, true, TYPALIGN_SHORT);
+		for (i = 0; i < numFkDeleteSetCols; i++)
+			fkdatums[i] = Int16GetDatum(fkDeleteSetCols[i]);
+		confdelsetcolsArray = construct_array(fkdatums, numFkDeleteSetCols,
+									   INT2OID, 2, true, TYPALIGN_SHORT);
 	}
 	else
 	{
@@ -143,6 +157,8 @@ CreateConstraintEntry(const char *constraintName,
 		conpfeqopArray = NULL;
 		conppeqopArray = NULL;
 		conffeqopArray = NULL;
+		confupdsetcolsArray = NULL;
+		confdelsetcolsArray = NULL;
 	}
 
 	if (exclOp != NULL)
@@ -211,6 +227,16 @@ CreateConstraintEntry(const char *constraintName,
 	else
 		nulls[Anum_pg_constraint_conffeqop - 1] = true;
 
+	if (confupdsetcolsArray)
+		values[Anum_pg_constraint_confupdsetcols - 1] = PointerGetDatum(confupdsetcolsArray);
+	else
+		nulls[Anum_pg_constraint_confupdsetcols - 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 +1183,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; fields 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_upd_set_cols, AttrNumber *fk_upd_set_cols,
+						   int *num_fk_del_set_cols, AttrNumber *fk_del_set_cols)
 {
 	Oid			constrId;
 	Datum		adatum;
@@ -1260,6 +1288,52 @@ DeconstructFkConstraintRow(HeapTuple tuple, int *numfks,
 			pfree(arr);			/* free de-toasted copy, if any */
 	}
 
+	if (fk_upd_set_cols)
+	{
+		int num_update_cols = 0;
+		adatum = SysCacheGetAttr(CONSTROID, tuple,
+								 Anum_pg_constraint_confupdsetcols, &isNull);
+		if (isNull)
+			elog(ERROR, "null confupdsetcols for foreign-key constraint %u", constrId);
+		arr = DatumGetArrayTypeP(adatum);	/* ensure not toasted */
+		if (ARR_NDIM(arr) != 0)
+		{
+			if (ARR_NDIM(arr) != 1 ||
+				ARR_HASNULL(arr) ||
+				ARR_ELEMTYPE(arr) != INT2OID)
+				elog(ERROR, "confupdsetcols is not or an empty or 1-D smallint array");
+			num_update_cols = ARR_DIMS(arr)[0];
+			memcpy(fk_upd_set_cols, ARR_DATA_PTR(arr), num_update_cols * sizeof(int16));
+		}
+		if ((Pointer) arr != DatumGetPointer(adatum))
+			pfree(arr);				/* free de-toasted copy, if any */
+
+		*num_fk_upd_set_cols = num_update_cols;
+	}
+
+	if (fk_del_set_cols)
+	{
+		int num_delete_cols = 0;
+		adatum = SysCacheGetAttr(CONSTROID, tuple,
+								 Anum_pg_constraint_confdelsetcols, &isNull);
+		if (isNull)
+			elog(ERROR, "null confdelsetcols for foreign-key constraint %u", constrId);
+		arr = DatumGetArrayTypeP(adatum);	/* ensure not toasted */
+		if (ARR_NDIM(arr) != 0)
+		{
+			if (ARR_NDIM(arr) != 1 ||
+				ARR_HASNULL(arr) ||
+				ARR_ELEMTYPE(arr) != INT2OID)
+				elog(ERROR, "confdelsetcols is not an empty or 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;
 }
 
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index dbee6ae199..9adc3cac3f 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -482,11 +482,19 @@ 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 numfkupdsetcols, int16 *fkupdsetcols,
+											int numfkdelsetcols, int16 *fkdelsetcols,
 											bool old_check_ok);
+static void validateFkActionSetColumns(int numfks, int16 *fkattnums,
+									   int numfksetcols, int16 *fksetcolsattnums,
+									   List *fksetcols,
+									   char *trigger);
 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 numfkupdsetcols, int16 *fkupdsetcols,
+									int numfkdelsetcols, int16 *fkdelsetcols,
 									bool old_check_ok, LOCKMODE lockmode);
 static void CloneForeignKeyConstraints(List **wqueue, Relation parentRel,
 									   Relation partitionRel);
@@ -8927,9 +8935,13 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			pfeqoperators[INDEX_MAX_KEYS];
 	Oid			ppeqoperators[INDEX_MAX_KEYS];
 	Oid			ffeqoperators[INDEX_MAX_KEYS];
+	int16		fkupdsetcols[INDEX_MAX_KEYS];
+	int16		fkdelsetcols[INDEX_MAX_KEYS];
 	int			i;
 	int			numfks,
-				numpks;
+				numpks,
+				numfkupdsetcols,
+				numfkdelsetcols;
 	Oid			indexOid;
 	bool		old_check_ok;
 	ObjectAddress address;
@@ -9025,11 +9037,27 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	MemSet(pfeqoperators, 0, sizeof(pfeqoperators));
 	MemSet(ppeqoperators, 0, sizeof(ppeqoperators));
 	MemSet(ffeqoperators, 0, sizeof(ffeqoperators));
+	MemSet(fkupdsetcols, 0, sizeof(fkupdsetcols));
+	MemSet(fkdelsetcols, 0, sizeof(fkdelsetcols));
 
 	numfks = transformColumnNameList(RelationGetRelid(rel),
 									 fkconstraint->fk_attrs,
 									 fkattnum, fktypoid);
 
+	numfkupdsetcols = transformColumnNameList(RelationGetRelid(rel),
+											  fkconstraint->fk_upd_set_cols,
+											  fkupdsetcols, NULL);
+	validateFkActionSetColumns(numfks, fkattnum,
+							   numfkupdsetcols, fkupdsetcols,
+							   fkconstraint->fk_upd_set_cols, "UPDATE");
+
+	numfkdelsetcols = transformColumnNameList(RelationGetRelid(rel),
+											  fkconstraint->fk_del_set_cols,
+											  fkdelsetcols, NULL);
+	validateFkActionSetColumns(numfks, fkattnum,
+							   numfkdelsetcols, fkdelsetcols,
+							   fkconstraint->fk_del_set_cols, "DELETE");
+
 	/*
 	 * If the attribute list for the referenced table was omitted, lookup the
 	 * definition of the primary key and use it.  Otherwise, validate the
@@ -9304,6 +9332,10 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 									 pfeqoperators,
 									 ppeqoperators,
 									 ffeqoperators,
+									 numfkupdsetcols,
+									 fkupdsetcols,
+									 numfkdelsetcols,
+									 fkdelsetcols,
 									 old_check_ok);
 
 	/* Now handle the referencing side. */
@@ -9316,6 +9348,10 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 							pfeqoperators,
 							ppeqoperators,
 							ffeqoperators,
+							numfkupdsetcols,
+							fkupdsetcols,
+							numfkdelsetcols,
+							fkdelsetcols,
 							old_check_ok,
 							lockmode);
 
@@ -9327,6 +9363,36 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	return address;
 }
 
+/*
+ * validateFkActionSetColumns
+ *		Verifies that columns used in ON UPDATE/DELETE SET NULL/DEFAULT (...)
+ *		column lists are valid.
+ */
+void validateFkActionSetColumns(
+	int numfks, int16 *fkattnums,
+	int numfksetcols, int16 *fksetcolsattnums,
+	List *fksetcols,
+	char *trigger)
+{
+	for (int i = 0; i < numfksetcols; i++) {
+		int 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 %s SET action must be part of foreign key", col, trigger)));
+		}
+	}
+}
+
 /*
  * addFkRecurseReferenced
  *		subroutine for ATAddForeignKeyConstraint; recurses on the referenced
@@ -9348,6 +9414,14 @@ 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.
+ * numfkupdsetcols is the number of columns in the ON UPDATE SET NULL/DELETE
+ *      (...) clause
+ * fkupdsetcols is the attnum array of the columns in the ON UPDATE SET
+ *      NULL/DELETE clause
+ * 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).
@@ -9357,7 +9431,10 @@ 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 numfkupdsetcols, int16 *fkupdsetcols,
+					   int numfkdelsetcols, int16 *fkdelsetcols,
+					   bool old_check_ok)
 {
 	ObjectAddress address;
 	Oid			constrOid;
@@ -9431,7 +9508,11 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
 									  ffeqoperators,
 									  numfks,
 									  fkconstraint->fk_upd_action,
+									  fkupdsetcols,
+									  numfkupdsetcols,
 									  fkconstraint->fk_del_action,
+									  fkdelsetcols,
+									  numfkdelsetcols,
 									  fkconstraint->fk_matchtype,
 									  NULL, /* no exclusion constraint */
 									  NULL, /* no check constraint */
@@ -9513,6 +9594,8 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
 								   partIndexId, constrOid, numfks,
 								   mapped_pkattnum, fkattnum,
 								   pfeqoperators, ppeqoperators, ffeqoperators,
+								   numfkupdsetcols, fkupdsetcols,
+								   numfkdelsetcols, fkdelsetcols,
 								   old_check_ok);
 
 			/* Done -- clean up (but keep the lock) */
@@ -9553,6 +9636,14 @@ 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.
+ * numfkupdsetcols is the number of columns in the ON UPDATE SET NULL/DELETE
+ *      (...) clause
+ * fkupdsetcols is the attnum array of the columns in the ON UPDATE SET
+ *      NULL/DELETE clause
+ * 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.
@@ -9562,6 +9653,8 @@ 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 numfkupdsetcols, int16 *fkupdsetcols,
+						int numfkdelsetcols, int16 *fkdelsetcols,
 						bool old_check_ok, LOCKMODE lockmode)
 {
 	AssertArg(OidIsValid(parentConstr));
@@ -9699,7 +9792,11 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
 									  ffeqoperators,
 									  numfks,
 									  fkconstraint->fk_upd_action,
+									  fkupdsetcols,
+									  numfkupdsetcols,
 									  fkconstraint->fk_del_action,
+									  fkdelsetcols,
+									  numfkdelsetcols,
 									  fkconstraint->fk_matchtype,
 									  NULL,
 									  NULL,
@@ -9732,6 +9829,10 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
 									pfeqoperators,
 									ppeqoperators,
 									ffeqoperators,
+									numfkupdsetcols,
+									fkupdsetcols,
+									numfkdelsetcols,
+									fkdelsetcols,
 									old_check_ok,
 									lockmode);
 
@@ -9837,6 +9938,10 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel)
 		Oid			conpfeqop[INDEX_MAX_KEYS];
 		Oid			conppeqop[INDEX_MAX_KEYS];
 		Oid			conffeqop[INDEX_MAX_KEYS];
+		int			numfkupdsetcols;
+		AttrNumber	confupdsetcols[INDEX_MAX_KEYS];
+		int			numfkdelsetcols;
+		AttrNumber	confdelsetcols[INDEX_MAX_KEYS];
 		Constraint *fkconstraint;
 
 		tuple = SearchSysCache1(CONSTROID, constrOid);
@@ -9869,7 +9974,11 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel)
 								   confkey,
 								   conpfeqop,
 								   conppeqop,
-								   conffeqop);
+								   conffeqop,
+								   &numfkupdsetcols,
+								   confupdsetcols,
+								   &numfkdelsetcols,
+								   confdelsetcols);
 
 		for (int i = 0; i < numfks; i++)
 			mapped_confkey[i] = attmap->attnums[confkey[i] - 1];
@@ -9916,6 +10025,10 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel)
 							   conpfeqop,
 							   conppeqop,
 							   conffeqop,
+							   numfkupdsetcols,
+							   confupdsetcols,
+							   numfkdelsetcols,
+							   confdelsetcols,
 							   true);
 
 		table_close(fkRel, NoLock);
@@ -9986,6 +10099,10 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
 		Oid			conpfeqop[INDEX_MAX_KEYS];
 		Oid			conppeqop[INDEX_MAX_KEYS];
 		Oid			conffeqop[INDEX_MAX_KEYS];
+		int			numfkupdsetcols;
+		AttrNumber	confupdsetcols[INDEX_MAX_KEYS];
+		int			numfkdelsetcols;
+		AttrNumber	confdelsetcols[INDEX_MAX_KEYS];
 		Constraint *fkconstraint;
 		bool		attached;
 		Oid			indexOid;
@@ -10017,7 +10134,9 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
 									   ShareRowExclusiveLock, NULL);
 
 		DeconstructFkConstraintRow(tuple, &numfks, conkey, confkey,
-								   conpfeqop, conppeqop, conffeqop);
+								   conpfeqop, conppeqop, conffeqop,
+								   &numfkupdsetcols, confupdsetcols,
+								   &numfkdelsetcols, confdelsetcols);
 		for (int i = 0; i < numfks; i++)
 			mapped_conkey[i] = attmap->attnums[conkey[i] - 1];
 
@@ -10101,7 +10220,11 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
 								  conffeqop,
 								  numfks,
 								  fkconstraint->fk_upd_action,
+								  confupdsetcols,
+								  numfkupdsetcols,
 								  fkconstraint->fk_del_action,
+								  confdelsetcols,
+								  numfkdelsetcols,
 								  fkconstraint->fk_matchtype,
 								  NULL,
 								  NULL,
@@ -10137,6 +10260,10 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
 								conpfeqop,
 								conppeqop,
 								conffeqop,
+								numfkupdsetcols,
+								confupdsetcols,
+								numfkdelsetcols,
+								confdelsetcols,
 								false,	/* no old check exists */
 								AccessExclusiveLock);
 		table_close(pkrel, NoLock);
@@ -10758,7 +10885,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, if needed, type OID
  */
 static int
 transformColumnNameList(Oid relId, List *colList,
@@ -10785,7 +10912,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++;
 	}
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index d8890d2c74..6b17dc4ec8 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -828,7 +828,11 @@ CreateTriggerFiringOn(CreateTrigStmt *stmt, const char *queryString,
 											  NULL,
 											  0,
 											  ' ',
+											  NULL,
+											  0,
 											  ' ',
+											  NULL,
+											  0,
 											  ' ',
 											  NULL, /* no exclusion */
 											  NULL, /* no check constraint */
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 6bdb1a1660..a543adb441 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -3545,7 +3545,11 @@ domainAddConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid,
 							  NULL,
 							  0,
 							  ' ',
+							  NULL,
+							  0,
 							  ' ',
+							  NULL,
+							  0,
 							  ' ',
 							  NULL, /* not an exclusion constraint */
 							  expr, /* Tree form of check constraint */
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 38251c2b8e..74a3cfb352 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3070,7 +3070,9 @@ _copyConstraint(const Constraint *from)
 	COPY_NODE_FIELD(pk_attrs);
 	COPY_SCALAR_FIELD(fk_matchtype);
 	COPY_SCALAR_FIELD(fk_upd_action);
+	COPY_NODE_FIELD(fk_upd_set_cols);
 	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);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 8a1762000c..8ec602351a 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2691,7 +2691,9 @@ _equalConstraint(const Constraint *a, const Constraint *b)
 	COMPARE_NODE_FIELD(pk_attrs);
 	COMPARE_SCALAR_FIELD(fk_matchtype);
 	COMPARE_SCALAR_FIELD(fk_upd_action);
+	COMPARE_NODE_FIELD(fk_upd_set_cols);
 	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);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 87561cbb6f..f694b586b5 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2870,6 +2870,41 @@ _outPLAssignStmt(StringInfo str, const PLAssignStmt *node)
 	WRITE_LOCATION_FIELD(location);
 }
 
+static void
+_outAlterTableStmt(StringInfo str, const AlterTableStmt *node)
+{
+  WRITE_NODE_TYPE("ALTERTABLE");
+
+  WRITE_NODE_FIELD(relation);
+  WRITE_NODE_FIELD(cmds);
+  WRITE_ENUM_FIELD(objtype, ObjectType);
+  WRITE_BOOL_FIELD(missing_ok);
+}
+
+static void
+_outAlterTableCmd(StringInfo str, const AlterTableCmd *node)
+{
+  WRITE_NODE_TYPE("ALTERTABLE_CMD");
+
+  WRITE_ENUM_FIELD(subtype, AlterTableType);
+  WRITE_STRING_FIELD(name);
+  WRITE_INT_FIELD(num);
+  WRITE_NODE_FIELD(newowner);
+  WRITE_NODE_FIELD(def);
+  WRITE_ENUM_FIELD(behavior, DropBehavior);
+  WRITE_BOOL_FIELD(missing_ok);
+}
+
+static void
+_outRoleSpec(StringInfo str, const RoleSpec *node)
+{
+  WRITE_NODE_TYPE("ROLE_SPEC");
+
+  WRITE_ENUM_FIELD(roletype, RoleSpecType);
+  WRITE_STRING_FIELD(rolename);
+  WRITE_INT_FIELD(location);
+}
+
 static void
 _outFuncCall(StringInfo str, const FuncCall *node)
 {
@@ -3730,7 +3765,9 @@ _outConstraint(StringInfo str, const Constraint *node)
 			WRITE_NODE_FIELD(pk_attrs);
 			WRITE_CHAR_FIELD(fk_matchtype);
 			WRITE_CHAR_FIELD(fk_upd_action);
+			WRITE_NODE_FIELD(fk_upd_set_cols);
 			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);
@@ -4370,6 +4407,15 @@ outNode(StringInfo str, const void *obj)
 			case T_PLAssignStmt:
 				_outPLAssignStmt(str, obj);
 				break;
+			case T_AlterTableStmt:
+				_outAlterTableStmt(str, obj);
+				break;
+			case T_AlterTableCmd:
+				_outAlterTableCmd(str, obj);
+				break;
+			case T_RoleSpec:
+				_outRoleSpec(str, obj);
+				break;
 			case T_ColumnDef:
 				_outColumnDef(str, obj);
 				break;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 39a2849eba..06f432040b 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -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
@@ -260,6 +273,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
@@ -564,7 +579,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
 
@@ -3684,8 +3701,10 @@ 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_upd_set_cols = ($5)->updateAction->cols;
+					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;
@@ -3895,8 +3914,10 @@ 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_upd_set_cols = ($10)->updateAction->cols;
+					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,
@@ -3974,23 +3995,50 @@ 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 = (KeyActions *) palloc(sizeof(KeyActions));
+                    n->updateAction = $1;
+                    n->deleteAction = (KeyAction *) palloc(sizeof(KeyAction));
+                    n->deleteAction->action = FKCONSTR_ACTION_NOACTION;
+                    n->deleteAction->cols = NIL;
+					$$ = n;
+                }
 			| key_delete
-				{ $$ = (FKCONSTR_ACTION_NOACTION << 8) | ($1 & 0xFF); }
+                {
+					KeyActions *n = (KeyActions *) palloc(sizeof(KeyActions));
+                    n->updateAction = (KeyAction *) 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 = (KeyActions *) palloc(sizeof(KeyActions));
+                    n->updateAction = $1;
+                    n->deleteAction = $2;
+					$$ = n;
+                }
 			| key_delete key_update
-				{ $$ = ($2 << 8) | ($1 & 0xFF); }
-			| /*EMPTY*/
-				{ $$ = (FKCONSTR_ACTION_NOACTION << 8) | (FKCONSTR_ACTION_NOACTION & 0xFF); }
+                {
+					KeyActions *n = (KeyActions *) palloc(sizeof(KeyActions));
+                    n->updateAction = $2;
+                    n->deleteAction = $1;
+					$$ = n;
+                }
+            | /*EMPTY*/
+                {
+					KeyActions *n = (KeyActions *) palloc(sizeof(KeyActions));
+					n->updateAction = (KeyAction *) palloc(sizeof(KeyAction));
+					n->updateAction->action = FKCONSTR_ACTION_NOACTION;
+					n->updateAction->cols = NIL;
+					n->deleteAction = (KeyAction *) palloc(sizeof(KeyAction));
+					n->deleteAction->action = FKCONSTR_ACTION_NOACTION;
+					n->deleteAction->cols = NIL;
+					$$ = n;
+                }
 		;
 
 key_update: ON UPDATE key_action		{ $$ = $3; }
@@ -4000,11 +4048,41 @@ 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 = (KeyAction *) palloc(sizeof(KeyAction));
+                    n->action = FKCONSTR_ACTION_NOACTION;
+                    n->cols = NIL;
+                    $$ = n;
+                }
+			| RESTRICT
+				{
+					KeyAction *n = (KeyAction *) palloc(sizeof(KeyAction));
+                    n->action = FKCONSTR_ACTION_RESTRICT;
+                    n->cols = NIL;
+                    $$ = n;
+                }
+			| CASCADE
+				{
+					KeyAction *n = (KeyAction *) palloc(sizeof(KeyAction));
+                    n->action = FKCONSTR_ACTION_CASCADE;
+                    n->cols = NIL;
+                    $$ = n;
+                }
+			| SET NULL_P opt_column_list
+				{
+					KeyAction *n = (KeyAction *) palloc(sizeof(KeyAction));
+                    n->action = FKCONSTR_ACTION_SETNULL;
+                    n->cols = $3;
+                    $$ = n;
+                }
+			| SET DEFAULT opt_column_list
+				{
+					KeyAction *n = (KeyAction *) palloc(sizeof(KeyAction));
+                    n->action = FKCONSTR_ACTION_SETDEFAULT;
+                    n->cols = $3;
+                    $$ = n;
+                }
 		;
 
 OptInherit: INHERITS '(' qualified_name_list ')'	{ $$ = $3; }
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index 96269fc2ad..d57a575565 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -76,8 +76,11 @@
 #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_ONUPDATE_SETNULL_DOUPDATE		6
+#define RI_PLAN_ONDELETE_SETNULL_DOUPDATE		7
+#define RI_PLAN_ONUPDATE_SETDEFAULT_DOUPDATE	8
+#define RI_PLAN_ONDELETE_SETDEFAULT_DOUPDATE	9
 
 #define MAX_QUOTED_NAME_LEN  (NAMEDATALEN*2+3)
 #define MAX_QUOTED_REL_NAME_LEN  (MAX_QUOTED_NAME_LEN*2)
@@ -109,7 +112,11 @@ typedef struct RI_ConstraintInfo
 	Oid			pk_relid;		/* referenced relation */
 	Oid			fk_relid;		/* referencing relation */
 	char		confupdtype;	/* foreign key's ON UPDATE action */
+	int			nupdsetcols;	/* number of columns referenced in ON UPDATE SET clause */
+	int16		confupdsetcols[RI_MAX_NUMKEYS]; /* attnums of cols to set on update */
 	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 +187,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,
@@ -970,7 +977,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 +992,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 +1007,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 +1022,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 +1032,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 +1040,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 +1059,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_ONUPDATE_SETNULL_DOUPDATE
+				: RI_PLAN_ONUPDATE_SETDEFAULT_DOUPDATE;
+			break;
+		case RI_TRIGTYPE_DELETE:
+			queryno = is_set_null
+				? RI_PLAN_ONDELETE_SETNULL_DOUPDATE
+				: RI_PLAN_ONDELETE_SETDEFAULT_DOUPDATE;
+			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 +1088,30 @@ 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->nupdsetcols;
+				set_cols = riinfo->confupdsetcols;
+				break;
+			case RI_TRIGTYPE_DELETE:
+				num_cols_to_set = riinfo->ndelsetcols;
+				set_cols = riinfo->confdelsetcols;
+				break;
+			default:
+				elog(ERROR, "invalid tgkind passed to ri_set");
+		}
+
+		/*
+		 * If confupdsetcols or confdelsetcols is non-empty, then we only
+		 * update the columns specified in that array.
+		 */
+		if (num_cols_to_set == 0) {
+			num_cols_to_set = riinfo->nkeys;
+			set_cols = riinfo->fk_attnums;
+		}
 
 		/* ----------
 		 * The query string built is
@@ -1080,39 +1122,46 @@ 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++)
 		{
-			Oid			pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]);
-			Oid			fk_type = RIAttType(fk_rel, riinfo->fk_attnums[i]);
-			Oid			pk_coll = RIAttCollation(pk_rel, riinfo->pk_attnums[i]);
-			Oid			fk_coll = RIAttCollation(fk_rel, riinfo->fk_attnums[i]);
+			Oid		pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]);
+			Oid		fk_type = RIAttType(fk_rel, riinfo->fk_attnums[i]);
+			Oid		pk_coll = RIAttCollation(pk_rel, riinfo->pk_attnums[i]);
+			Oid		fk_coll = RIAttCollation(fk_rel, riinfo->fk_attnums[i]);
 
 			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 +2147,11 @@ ri_LoadConstraintInfo(Oid constraintOid)
 							   riinfo->pk_attnums,
 							   riinfo->pf_eq_oprs,
 							   riinfo->pp_eq_oprs,
-							   riinfo->ff_eq_oprs);
+							   riinfo->ff_eq_oprs,
+							   &riinfo->nupdsetcols,
+							   riinfo->confupdsetcols,
+							   &riinfo->ndelsetcols,
+							   riinfo->confdelsetcols);
 
 	ReleaseSysCache(tup);
 
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8ff4e5dc07..eff27a20f4 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -2233,6 +2233,13 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 				appendStringInfoString(&buf, string);
 
 				/* Add ON UPDATE and ON DELETE clauses, if needed */
+
+				val = SysCacheGetAttr(CONSTROID, tup,
+									  Anum_pg_constraint_confupdsetcols, &isnull);
+				if (isnull)
+					elog(ERROR, "null confupdsetcols for foreign key constraint %u",
+						 constraintId);
+
 				switch (conForm->confupdtype)
 				{
 					case FKCONSTR_ACTION_NOACTION:
@@ -2259,6 +2266,20 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 				if (string)
 					appendStringInfo(&buf, " ON UPDATE %s", string);
 
+				/* Add columns specified to SET NULL or SET DEFAULT if provided. */
+				if (ARR_NDIM(DatumGetArrayTypeP(val)) > 0)
+				{
+					appendStringInfo(&buf, " (");
+					decompile_column_index_array(val, conForm->conrelid, &buf);
+					appendStringInfo(&buf, ")");
+				}
+
+				val = SysCacheGetAttr(CONSTROID, tup,
+									  Anum_pg_constraint_confdelsetcols, &isnull);
+				if (isnull)
+					elog(ERROR, "null confdelsetcols for foreign key constraint %u",
+						 constraintId);
+
 				switch (conForm->confdeltype)
 				{
 					case FKCONSTR_ACTION_NOACTION:
@@ -2285,6 +2306,14 @@ 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. */
+				if (ARR_NDIM(DatumGetArrayTypeP(val)) > 0)
+				{
+					appendStringInfo(&buf, " (");
+					decompile_column_index_array(val, conForm->conrelid, &buf);
+					appendStringInfo(&buf, ")");
+				}
+
 				break;
 			}
 		case CONSTRAINT_PRIMARY:
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 13d9994af3..d9e76a7c0e 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -4516,7 +4516,7 @@ RelationGetFKeyList(Relation relation)
 								   info->conkey,
 								   info->confkey,
 								   info->conpfeqop,
-								   NULL, NULL);
+								   NULL, NULL, NULL, NULL, NULL, NULL);
 
 		/* Add FK's node to the result list */
 		result = lappend(result, info);
diff --git a/src/include/catalog/pg_constraint.h b/src/include/catalog/pg_constraint.h
index e75baa8e1e..e2d782c653 100644
--- a/src/include/catalog/pg_constraint.h
+++ b/src/include/catalog/pg_constraint.h
@@ -138,6 +138,18 @@ CATALOG(pg_constraint,2606,ConstraintRelationId)
 	 */
 	Oid			conffeqop[1] BKI_LOOKUP(pg_operator);
 
+	/*
+	 * If a foreign key with a ON UPDATE SET NULL/DEFAULT action, the subset
+	 * of conkey to updated. If empty, all columns should be updated.
+	 */
+	Oid			confupdsetcols[1];
+
+	/*
+	 * If a foreign key with a ON DELETE SET NULL/DEFAULT action, the subset
+	 * of conkey to updated. If empty, all columns should be updated.
+	 */
+	Oid			confdelsetcols[1];
+
 	/*
 	 * If an exclusion constraint, the OIDs of the exclusion operators for
 	 * each column of the constraint
@@ -219,7 +231,11 @@ extern Oid	CreateConstraintEntry(const char *constraintName,
 								  const Oid *ffEqOp,
 								  int foreignNKeys,
 								  char foreignUpdateType,
+								  const int16 *fkUpdateSetCols,
+								  int numFkUpdateSetCols,
 								  char foreignDeleteType,
+								  const int16 *fkDeleteSetCols,
+								  int numFkDeleteSetCols,
 								  char foreignMatchType,
 								  const Oid *exclOp,
 								  Node *conExpr,
@@ -254,7 +270,9 @@ 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_upd_set_cols, AttrNumber *fk_upd_set_cols,
+									   int *num_fk_del_set_cols, AttrNumber *fk_del_set_cols);
 
 extern bool check_functional_grouping(Oid relid,
 									  Index varno, Index varlevelsup,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 7af13dee43..6ce5ba4ec9 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2287,7 +2287,10 @@ typedef struct Constraint
 	List	   *pk_attrs;		/* Corresponding attrs in PK table */
 	char		fk_matchtype;	/* FULL, PARTIAL, SIMPLE */
 	char		fk_upd_action;	/* ON UPDATE action */
+	List	   *fk_upd_set_cols;	/* ON UPDATE SET NULL/DEFAULT (col1, col2) */
 	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 */
diff --git a/src/test/regress/expected/foreign_key.out b/src/test/regress/expected/foreign_key.out
index bf794dce9d..a311388d0a 100644
--- a/src/test/regress/expected/foreign_key.out
+++ b/src/test/regress/expected/foreign_key.out
@@ -755,6 +755,56 @@ SELECT * from FKTABLE;
         |        |        |      1
 (7 rows)
 
+DROP TABLE FKTABLE;
+DROP TABLE PKTABLE;
+-- Test for ON UPDATE/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 UPDATE 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 (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 UPDATE SET NULL (foo));
+ERROR:  column "foo" referenced in ON UPDATE SET action must be part of foreign key
+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 DEFAULT 0, id int,
+  fk_id_upd_set_null int,
+  fk_id_upd_set_default int DEFAULT 0,
+  fk_id_del_set_null int,
+  fk_id_del_set_default int,
+  FOREIGN KEY (tid, fk_id_upd_set_null) REFERENCES PKTABLE ON UPDATE SET NULL (fk_id_upd_set_null),
+  FOREIGN KEY (tid, fk_id_upd_set_default) REFERENCES PKTABLE ON UPDATE SET DEFAULT (fk_id_upd_set_default),
+  FOREIGN KEY (tid, fk_id_del_set_null) REFERENCES PKTABLE ON DELETE SET NULL (tid),
+  FOREIGN KEY (tid, fk_id_del_set_default) REFERENCES PKTABLE ON DELETE SET DEFAULT (tid)
+);
+SELECT pg_get_constraintdef(oid) FROM pg_constraint WHERE conrelid = 'fktable'::regclass::oid ORDER BY oid;
+                                                pg_get_constraintdef                                                
+--------------------------------------------------------------------------------------------------------------------
+ FOREIGN KEY (tid, fk_id_upd_set_null) REFERENCES pktable(tid, id) ON UPDATE SET NULL (fk_id_upd_set_null)
+ FOREIGN KEY (tid, fk_id_upd_set_default) REFERENCES pktable(tid, id) ON UPDATE SET DEFAULT (fk_id_upd_set_default)
+ FOREIGN KEY (tid, fk_id_del_set_null) REFERENCES pktable(tid, id) ON DELETE SET NULL (tid)
+ FOREIGN KEY (tid, fk_id_del_set_default) REFERENCES pktable(tid, id) ON DELETE SET DEFAULT (tid)
+(4 rows)
+
+INSERT INTO PKTABLE VALUES (1, 0), (1, 1), (1, 2), (1, 3), (1, 4), (0, 4);
+INSERT INTO FKTABLE VALUES
+  (1, 1, 1, NULL, NULL, NULL),
+  (1, 2, NULL, 2, NULL, NULL),
+  (1, 3, NULL, NULL, 3, NULL),
+  (1, 4, NULL, NULL, NULL, 4);
+UPDATE PKTABLE SET id = 5 WHERE id = 1;
+UPDATE PKTABLE SET id = 6 WHERE id = 2;
+DELETE FROM PKTABLE WHERE tid = 1 AND (id = 3 OR id = 4);
+SELECT * FROM FKTABLE ORDER BY id;
+ tid | id | fk_id_upd_set_null | fk_id_upd_set_default | fk_id_del_set_null | fk_id_del_set_default 
+-----+----+--------------------+-----------------------+--------------------+-----------------------
+   1 |  1 |                    |                       |                    |                      
+   1 |  2 |                    |                     0 |                    |                      
+     |  3 |                    |                       |                  3 |                      
+   0 |  4 |                    |                       |                    |                     4
+(4 rows)
+
 DROP TABLE FKTABLE;
 DROP TABLE PKTABLE;
 CREATE TABLE PKTABLE (ptest1 int PRIMARY KEY);
@@ -1734,6 +1784,44 @@ SELECT * FROM fk_partitioned_fk WHERE b = 142857;
  2501 | 142857
 (1 row)
 
+-- ON UPDATE/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) ON UPDATE SET NULL (b);
+BEGIN;
+INSERT INTO fk_partitioned_fk VALUES (1500, 2503);
+UPDATE fk_notpartitioned_pk SET b = 2504 WHERE a = 1500;
+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    
+------+--------
+ 1500 |       
+ 2502 |       
+      | 142857
+(3 rows)
+
+ROLLBACK;
+-- ON UPDATE/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) ON UPDATE SET DEFAULT (b);
+BEGIN;
+DELETE FROM fk_partitioned_fk;
+DELETE FROM fk_notpartitioned_pk;
+INSERT INTO fk_notpartitioned_pk VALUES (500, 100000), (2501, 100000), (1500, 2503), (1500, 142857);
+INSERT INTO fk_partitioned_fk VALUES (500, 100000), (1500, 2503);
+UPDATE fk_notpartitioned_pk SET b = 2504 WHERE b = 2503;
+DELETE FROM fk_notpartitioned_pk WHERE a = 500;
+SELECT * FROM fk_partitioned_fk ORDER BY a;
+  a   |   b    
+------+--------
+ 1500 | 142857
+ 2501 | 100000
+(2 rows)
+
+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)
diff --git a/src/test/regress/sql/foreign_key.sql b/src/test/regress/sql/foreign_key.sql
index de417b62b6..5b3fed72e3 100644
--- a/src/test/regress/sql/foreign_key.sql
+++ b/src/test/regress/sql/foreign_key.sql
@@ -463,6 +463,42 @@ SELECT * from FKTABLE;
 DROP TABLE FKTABLE;
 DROP TABLE PKTABLE;
 
+-- Test for ON UPDATE/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 UPDATE SET NULL (bar));
+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 UPDATE SET NULL (foo));
+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 DEFAULT 0, id int,
+  fk_id_upd_set_null int,
+  fk_id_upd_set_default int DEFAULT 0,
+  fk_id_del_set_null int,
+  fk_id_del_set_default int,
+  FOREIGN KEY (tid, fk_id_upd_set_null) REFERENCES PKTABLE ON UPDATE SET NULL (fk_id_upd_set_null),
+  FOREIGN KEY (tid, fk_id_upd_set_default) REFERENCES PKTABLE ON UPDATE SET DEFAULT (fk_id_upd_set_default),
+  FOREIGN KEY (tid, fk_id_del_set_null) REFERENCES PKTABLE ON DELETE SET NULL (tid),
+  FOREIGN KEY (tid, fk_id_del_set_default) REFERENCES PKTABLE ON DELETE SET DEFAULT (tid)
+);
+
+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), (1, 3), (1, 4), (0, 4);
+INSERT INTO FKTABLE VALUES
+  (1, 1, 1, NULL, NULL, NULL),
+  (1, 2, NULL, 2, NULL, NULL),
+  (1, 3, NULL, NULL, 3, NULL),
+  (1, 4, NULL, NULL, NULL, 4);
+
+UPDATE PKTABLE SET id = 5 WHERE id = 1;
+UPDATE PKTABLE SET id = 6 WHERE id = 2;
+DELETE FROM PKTABLE WHERE tid = 1 AND (id = 3 OR id = 4);
+
+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 +1320,33 @@ 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 UPDATE/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) ON UPDATE SET NULL (b);
+BEGIN;
+INSERT INTO fk_partitioned_fk VALUES (1500, 2503);
+UPDATE fk_notpartitioned_pk SET b = 2504 WHERE a = 1500;
+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 UPDATE/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) ON UPDATE SET DEFAULT (b);
+BEGIN;
+DELETE FROM fk_partitioned_fk;
+DELETE FROM fk_notpartitioned_pk;
+INSERT INTO fk_notpartitioned_pk VALUES (500, 100000), (2501, 100000), (1500, 2503), (1500, 142857);
+INSERT INTO fk_partitioned_fk VALUES (500, 100000), (1500, 2503);
+UPDATE fk_notpartitioned_pk SET b = 2504 WHERE b = 2503;
+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)
