v19-0004-Add-CASCADE-SET-NULL-SET-DEFAULT-for-temporal-fo.patch

text/x-patch

Filename: v19-0004-Add-CASCADE-SET-NULL-SET-DEFAULT-for-temporal-fo.patch
Type: text/x-patch
Part: 3
Message: Re: SQL:2011 application time

Patch

Same data as JSON: GET /api/v1/attachments/:id/patch the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes. API reference →
Format: format-patch
Series: patch v19-0004
Subject: Add CASCADE/SET NULL/SET DEFAULT for temporal foreign keys
File+
src/backend/commands/tablecmds.c 34 8
src/backend/utils/adt/ri_triggers.c 547 2
src/include/catalog/pg_proc.dat 20 0
src/test/regress/expected/without_overlaps.out 807 20
src/test/regress/sql/without_overlaps.sql 428 10
From fb43eee9ff404e9247c000c0d85de71179e608ab Mon Sep 17 00:00:00 2001
From: "Paul A. Jungwirth" <pj@illuminatedcomputing.com>
Date: Sat, 3 Jun 2023 21:41:11 -0400
Subject: [PATCH v19 4/5] Add CASCADE/SET NULL/SET DEFAULT for temporal foreign
 keys

---
 src/backend/commands/tablecmds.c              |  42 +-
 src/backend/utils/adt/ri_triggers.c           | 549 +++++++++++-
 src/include/catalog/pg_proc.dat               |  20 +
 .../regress/expected/without_overlaps.out     | 827 +++++++++++++++++-
 src/test/regress/sql/without_overlaps.sql     | 438 +++++++++-
 5 files changed, 1836 insertions(+), 40 deletions(-)

diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 4b6ecb51cb..46c148ce41 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -505,7 +505,7 @@ static ObjectAddress addFkRecurseReferenced(List **wqueue, Constraint *fkconstra
 											bool old_check_ok,
 											Oid parentDelTrigger, Oid parentUpdTrigger,
 											bool is_temporal);
-static void validateFkOnDeleteSetColumns(int numfks, const int16 *fkattnums,
+static void validateFkOnDeleteSetColumns(int numfks, const int16 *fkattnums, const int16 *fkperiodattnums,
 										 int numfksetcols, const int16 *fksetcolsattnums,
 										 List *fksetcols);
 static void addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint,
@@ -9650,7 +9650,7 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	numfkdelsetcols = transformColumnNameList(RelationGetRelid(rel),
 											  fkconstraint->fk_del_set_cols,
 											  fkdelsetcols, NULL);
-	validateFkOnDeleteSetColumns(numfks, fkattnum,
+	validateFkOnDeleteSetColumns(numfks, fkattnum, fkperiodattnums,
 								 numfkdelsetcols, fkdelsetcols,
 								 fkconstraint->fk_del_set_cols);
 
@@ -9822,12 +9822,15 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
  */
 void
 validateFkOnDeleteSetColumns(int numfks, const int16 *fkattnums,
+							 const int16 *fkperiodattnums,
 							 int numfksetcols, const int16 *fksetcolsattnums,
 							 List *fksetcols)
 {
 	for (int i = 0; i < numfksetcols; i++)
 	{
 		int16		setcol_attnum = fksetcolsattnums[i];
+		/* assume only one PERIOD key column in a foreign key */
+		int16		fkperiod_attnum = fkperiodattnums[0];
 		bool		seen = false;
 
 		for (int j = 0; j < numfks; j++)
@@ -9837,6 +9840,13 @@ validateFkOnDeleteSetColumns(int numfks, const int16 *fkattnums,
 				seen = true;
 				break;
 			}
+			if (fkperiod_attnum == setcol_attnum)
+			{
+				char	   *col = strVal(list_nth(fksetcols, i));
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
+						 errmsg("column \"%s\" referenced in ON DELETE SET action cannot be PERIOD", col)));
+			}
 		}
 
 		if (!seen)
@@ -12417,11 +12427,19 @@ createForeignKeyActionTriggers(Relation rel, Oid refRelOid, Constraint *fkconstr
 				fk_trigger->funcname = SystemFuncName("TRI_FKey_restrict_del");
 				break;
 			case FKCONSTR_ACTION_CASCADE:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("TRI_FKey_cascade_del");
+				break;
 			case FKCONSTR_ACTION_SETNULL:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("TRI_FKey_setnull_del");
+				break;
 			case FKCONSTR_ACTION_SETDEFAULT:
-				ereport(ERROR,
-						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-						 errmsg("action not supported for temporal foreign keys")));
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("TRI_FKey_setdefault_del");
 				break;
 			default:
 				elog(ERROR, "unrecognized FK action type: %d",
@@ -12508,11 +12526,19 @@ createForeignKeyActionTriggers(Relation rel, Oid refRelOid, Constraint *fkconstr
 				fk_trigger->funcname = SystemFuncName("TRI_FKey_restrict_upd");
 				break;
 			case FKCONSTR_ACTION_CASCADE:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("TRI_FKey_cascade_upd");
+				break;
 			case FKCONSTR_ACTION_SETNULL:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("TRI_FKey_setnull_upd");
+				break;
 			case FKCONSTR_ACTION_SETDEFAULT:
-				ereport(ERROR,
-						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-						 errmsg("action not supported for temporal foreign keys")));
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("TRI_FKey_setdefault_upd");
 				break;
 			default:
 				elog(ERROR, "unrecognized FK action type: %d",
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index 55b3248b78..70ace70e7a 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -83,6 +83,12 @@
 #define RI_PLAN_SETNULL_ONUPDATE		7
 #define RI_PLAN_SETDEFAULT_ONDELETE		8
 #define RI_PLAN_SETDEFAULT_ONUPDATE		9
+#define TRI_PLAN_CASCADE_ONDELETE		10
+#define TRI_PLAN_CASCADE_ONUPDATE		11
+#define TRI_PLAN_SETNULL_ONUPDATE		12
+#define TRI_PLAN_SETNULL_ONDELETE		13
+#define TRI_PLAN_SETDEFAULT_ONUPDATE	14
+#define TRI_PLAN_SETDEFAULT_ONDELETE	15
 
 #define MAX_QUOTED_NAME_LEN  (NAMEDATALEN*2+3)
 #define MAX_QUOTED_REL_NAME_LEN  (MAX_QUOTED_NAME_LEN*2)
@@ -190,6 +196,7 @@ static bool ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel,
 							  const RI_ConstraintInfo *riinfo);
 static Datum ri_restrict(TriggerData *trigdata, bool is_no_action);
 static Datum ri_set(TriggerData *trigdata, bool is_set_null, int tgkind);
+static Datum tri_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,
@@ -1391,6 +1398,127 @@ TRI_FKey_restrict_del(PG_FUNCTION_ARGS)
 	return ri_restrict((TriggerData *) fcinfo->context, false);
 }
 
+/*
+ * TRI_FKey_cascade_del -
+ *
+ * Cascaded delete foreign key references at delete event on temporal PK table.
+ */
+Datum
+TRI_FKey_cascade_del(PG_FUNCTION_ARGS)
+{
+	TriggerData *trigdata = (TriggerData *) fcinfo->context;
+	const RI_ConstraintInfo *riinfo;
+	Relation	fk_rel;
+	Relation	pk_rel;
+	TupleTableSlot *oldslot;
+	RI_QueryKey qkey;
+	SPIPlanPtr  qplan;
+	Datum targetRange;
+
+	/* Check that this is a valid trigger call on the right time and event. */
+	ri_CheckTrigger(fcinfo, "TRI_FKey_cascade_del", RI_TRIGTYPE_DELETE);
+
+	riinfo = ri_FetchConstraintInfo(trigdata->tg_trigger,
+									trigdata->tg_relation, true);
+
+	/*
+	 * Get the relation descriptors of the FK and PK tables and the old tuple.
+	 *
+	 * fk_rel is opened in RowExclusiveLock mode since that's what our
+	 * eventual DELETE will get on it.
+	 */
+	fk_rel = table_open(riinfo->fk_relid, RowExclusiveLock);
+	pk_rel = trigdata->tg_relation;
+	oldslot = trigdata->tg_trigslot;
+
+	/*
+	 * Don't delete than more than the PK's duration,
+	 * trimmed by an original FOR PORTION OF if necessary.
+	 */
+	targetRange = restrict_cascading_range(trigdata->tg_temporal, riinfo, oldslot);
+
+	if (SPI_connect() != SPI_OK_CONNECT)
+		elog(ERROR, "SPI_connect failed");
+
+	/* Fetch or prepare a saved plan for the cascaded delete */
+	ri_BuildQueryKey(&qkey, riinfo, TRI_PLAN_CASCADE_ONDELETE);
+
+	if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
+	{
+		StringInfoData querybuf;
+		char		fkrelname[MAX_QUOTED_REL_NAME_LEN];
+		char		attname[MAX_QUOTED_NAME_LEN];
+		char		paramname[16];
+		const char *querysep;
+		Oid			queryoids[RI_MAX_NUMKEYS + 1];
+		const char *fk_only;
+
+		/* ----------
+		 * The query string built is
+		 *  DELETE FROM [ONLY] <fktable>
+		 *  FOR PORTION OF $fkatt FROM lower(${n+1}) TO upper(${n+1})
+		 *  WHERE $1 = fkatt1 [AND ...]
+		 * The type id's for the $ parameters are those of the
+		 * corresponding PK attributes.
+		 * ----------
+		 */
+		initStringInfo(&querybuf);
+		fk_only = fk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ?
+			"" : "ONLY ";
+		quoteRelationName(fkrelname, fk_rel);
+		quoteOneName(attname, RIAttName(fk_rel, riinfo->fk_attnums[riinfo->nkeys - 1]));
+
+		appendStringInfo(&querybuf, "DELETE FROM %s%s FOR PORTION OF %s FROM lower($%d) TO upper($%d)",
+						 fk_only, fkrelname, attname, riinfo->nkeys + 1, riinfo->nkeys + 1);
+		querysep = "WHERE";
+		for (int i = 0; i < riinfo->nkeys; i++)
+		{
+			Oid pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]);
+			Oid pk_coll = RIAttCollation(pk_rel, riinfo->pk_attnums[i]);
+			Oid fk_type = RIAttType(fk_rel, riinfo->fk_attnums[i]);
+			Oid fk_coll = RIAttCollation(fk_rel, riinfo->fk_attnums[i]);
+
+			quoteOneName(attname,
+						 RIAttName(fk_rel, riinfo->fk_attnums[i]));
+			sprintf(paramname, "$%d", i + 1);
+			ri_GenerateQual(&querybuf, querysep,
+							paramname, pk_type,
+							riinfo->pf_eq_oprs[i],
+							attname, fk_type);
+			if (pk_coll != fk_coll && !get_collation_isdeterministic(pk_coll))
+				ri_GenerateQualCollation(&querybuf, pk_coll);
+			querysep = "AND";
+			queryoids[i] = pk_type;
+		}
+
+		/* Set a param for FOR PORTION OF TO/FROM */
+		queryoids[riinfo->nkeys] = RIAttType(pk_rel, riinfo->pk_attnums[riinfo->nkeys - 1]);
+
+		/* Prepare and save the plan */
+		qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys + 1, queryoids,
+							 &qkey, fk_rel, pk_rel);
+	}
+
+	/*
+	 * We have a plan now. Build up the arguments from the key values in the
+	 * deleted PK tuple and delete the referencing rows
+	 */
+	ri_PerformCheck(riinfo, &qkey, qplan,
+					fk_rel, pk_rel,
+					oldslot, NULL,
+					riinfo->nkeys, targetRange,
+					true,	   /* must detect new rows */
+					SPI_OK_DELETE);
+
+	if (SPI_finish() != SPI_OK_FINISH)
+		elog(ERROR, "SPI_finish failed");
+
+	table_close(fk_rel, RowExclusiveLock);
+
+	return PointerGetDatum(NULL);
+}
+
+
 /*
  * TRI_FKey_noaction_upd -
  *
@@ -1428,6 +1556,415 @@ TRI_FKey_restrict_upd(PG_FUNCTION_ARGS)
 	return ri_restrict((TriggerData *) fcinfo->context, false);
 }
 
+/*
+ * TRI_FKey_cascade_upd -
+ *
+ * Cascaded update foreign key references at update event on temporal PK table.
+ */
+Datum
+TRI_FKey_cascade_upd(PG_FUNCTION_ARGS)
+{
+	TriggerData *trigdata = (TriggerData *) fcinfo->context;
+	const RI_ConstraintInfo *riinfo;
+	Relation	fk_rel;
+	Relation	pk_rel;
+	TupleTableSlot *oldslot;
+	TupleTableSlot *newslot;
+	RI_QueryKey qkey;
+	SPIPlanPtr  qplan;
+	Datum targetRange;
+
+	/* Check that this is a valid trigger call on the right time and event. */
+	ri_CheckTrigger(fcinfo, "TRI_FKey_cascade_upd", RI_TRIGTYPE_UPDATE);
+
+	riinfo = ri_FetchConstraintInfo(trigdata->tg_trigger,
+									trigdata->tg_relation, true);
+
+	/*
+	 * Get the relation descriptors of the FK and PK tables and the new and
+	 * old tuple.
+	 *
+	 * fk_rel is opened in RowExclusiveLock mode since that's what our
+	 * eventual UPDATE will get on it.
+	 */
+	fk_rel = table_open(riinfo->fk_relid, RowExclusiveLock);
+	pk_rel = trigdata->tg_relation;
+	newslot = trigdata->tg_newslot;
+	oldslot = trigdata->tg_trigslot;
+
+	/*
+	 * Don't delete than more than the PK's duration,
+	 * trimmed by an original FOR PORTION OF if necessary.
+	 */
+	targetRange = restrict_cascading_range(trigdata->tg_temporal, riinfo, oldslot);
+
+	if (SPI_connect() != SPI_OK_CONNECT)
+		elog(ERROR, "SPI_connect failed");
+
+	/* Fetch or prepare a saved plan for the cascaded update */
+	ri_BuildQueryKey(&qkey, riinfo, TRI_PLAN_CASCADE_ONUPDATE);
+
+	if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
+	{
+		StringInfoData querybuf;
+		StringInfoData qualbuf;
+		char		fkrelname[MAX_QUOTED_REL_NAME_LEN];
+		char		attname[MAX_QUOTED_NAME_LEN];
+		char		paramname[16];
+		const char *querysep;
+		const char *qualsep;
+		Oid		 queryoids[2 * RI_MAX_NUMKEYS + 1];
+		const char *fk_only;
+
+		/* ----------
+		 * The query string built is
+		 *  UPDATE [ONLY] <fktable>
+		 *		  FOR PORTION OF $fkatt FROM lower(${2n+1}) TO upper(${2n+1})
+		 *		  SET fkatt1 = $1, [, ...]
+		 *		  WHERE $n = fkatt1 [AND ...]
+		 * The type id's for the $ parameters are those of the
+		 * corresponding PK attributes.  Note that we are assuming
+		 * there is an assignment cast from the PK to the FK type;
+		 * else the parser will fail.
+		 * ----------
+		 */
+		initStringInfo(&querybuf);
+		initStringInfo(&qualbuf);
+		fk_only = fk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ?
+			"" : "ONLY ";
+		quoteRelationName(fkrelname, fk_rel);
+		quoteOneName(attname, RIAttName(fk_rel, riinfo->fk_attnums[riinfo->nkeys - 1]));
+
+		appendStringInfo(&querybuf, "UPDATE %s%s FOR PORTION OF %s FROM lower($%d) TO upper($%d) SET",
+						 fk_only, fkrelname, attname, 2 * riinfo->nkeys + 1, 2 * riinfo->nkeys + 1);
+
+		querysep = "";
+		qualsep = "WHERE";
+		for (int i = 0, j = riinfo->nkeys; i < riinfo->nkeys; i++, j++)
+		{
+			Oid pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]);
+			Oid pk_coll = RIAttCollation(pk_rel, riinfo->pk_attnums[i]);
+			Oid fk_type = RIAttType(fk_rel, riinfo->fk_attnums[i]);
+			Oid fk_coll = RIAttCollation(fk_rel, riinfo->fk_attnums[i]);
+
+			quoteOneName(attname,
+						 RIAttName(fk_rel, riinfo->fk_attnums[i]));
+			/*
+			 * Don't set the temporal column(s).
+			 * FOR PORTION OF will take care of that.
+			 */
+			if (i < riinfo->nkeys - 1)
+				appendStringInfo(&querybuf,
+								 "%s %s = $%d",
+								 querysep, attname, i + 1);
+
+			sprintf(paramname, "$%d", j + 1);
+			ri_GenerateQual(&qualbuf, qualsep,
+							paramname, pk_type,
+							riinfo->pf_eq_oprs[i],
+							attname, fk_type);
+			if (pk_coll != fk_coll && !get_collation_isdeterministic(pk_coll))
+				ri_GenerateQualCollation(&querybuf, pk_coll);
+			querysep = ",";
+			qualsep = "AND";
+			queryoids[i] = pk_type;
+			queryoids[j] = pk_type;
+		}
+		appendBinaryStringInfo(&querybuf, qualbuf.data, qualbuf.len);
+
+		/* Set a param for FOR PORTION OF TO/FROM */
+		queryoids[2 * riinfo->nkeys] = RIAttType(pk_rel, riinfo->pk_attnums[riinfo->nkeys - 1]);
+
+		/* Prepare and save the plan */
+		qplan = ri_PlanCheck(querybuf.data, 2 * riinfo->nkeys + 1, queryoids,
+							 &qkey, fk_rel, pk_rel);
+	}
+
+	/*
+	 * We have a plan now. Run it to update the existing references.
+	 */
+	ri_PerformCheck(riinfo, &qkey, qplan,
+					fk_rel, pk_rel,
+					oldslot, newslot,
+					riinfo->nkeys * 2, targetRange,
+					true,		/* must detect new rows */
+					SPI_OK_UPDATE);
+
+	if (SPI_finish() != SPI_OK_FINISH)
+		elog(ERROR, "SPI_finish failed");
+
+	table_close(fk_rel, RowExclusiveLock);
+
+	return PointerGetDatum(NULL);
+}
+
+/*
+ * TRI_FKey_setnull_del -
+ *
+ * Set foreign key references to NULL values at delete event on PK table.
+ */
+Datum
+TRI_FKey_setnull_del(PG_FUNCTION_ARGS)
+{
+	/* Check that this is a valid trigger call on the right time and event. */
+	ri_CheckTrigger(fcinfo, "TRI_FKey_setnull_del", RI_TRIGTYPE_DELETE);
+
+	/* Share code with UPDATE case */
+	return tri_set((TriggerData *) fcinfo->context, true, RI_TRIGTYPE_DELETE);
+}
+
+/*
+ * TRI_FKey_setnull_upd -
+ *
+ * Set foreign key references to NULL at update event on PK table.
+ */
+Datum
+TRI_FKey_setnull_upd(PG_FUNCTION_ARGS)
+{
+	/* Check that this is a valid trigger call on the right time and event. */
+	ri_CheckTrigger(fcinfo, "TRI_FKey_setnull_upd", RI_TRIGTYPE_UPDATE);
+
+	/* Share code with DELETE case */
+	return tri_set((TriggerData *) fcinfo->context, true, RI_TRIGTYPE_UPDATE);
+}
+
+/*
+ * TRI_FKey_setdefault_del -
+ *
+ * Set foreign key references to defaults at delete event on PK table.
+ */
+Datum
+TRI_FKey_setdefault_del(PG_FUNCTION_ARGS)
+{
+	/* Check that this is a valid trigger call on the right time and event. */
+	ri_CheckTrigger(fcinfo, "TRI_FKey_setdefault_del", RI_TRIGTYPE_DELETE);
+
+	/* Share code with UPDATE case */
+	return tri_set((TriggerData *) fcinfo->context, false, RI_TRIGTYPE_DELETE);
+}
+
+/*
+ * TRI_FKey_setdefault_upd -
+ *
+ * Set foreign key references to defaults at update event on PK table.
+ */
+Datum
+TRI_FKey_setdefault_upd(PG_FUNCTION_ARGS)
+{
+	/* Check that this is a valid trigger call on the right time and event. */
+	ri_CheckTrigger(fcinfo, "TRI_FKey_setdefault_upd", RI_TRIGTYPE_UPDATE);
+
+	/* Share code with DELETE case */
+	return tri_set((TriggerData *) fcinfo->context, false, RI_TRIGTYPE_UPDATE);
+}
+
+/*
+ * tri_set -
+ *
+ * Common code for temporal ON DELETE SET NULL, ON DELETE SET DEFAULT, ON
+ * UPDATE SET NULL, and ON UPDATE SET DEFAULT.
+ */
+static Datum
+tri_set(TriggerData *trigdata, bool is_set_null, int tgkind)
+{
+	const RI_ConstraintInfo *riinfo;
+	Relation	fk_rel;
+	Relation	pk_rel;
+	TupleTableSlot *oldslot;
+	RI_QueryKey qkey;
+	SPIPlanPtr	qplan;
+	Datum		targetRange;
+	int32		queryno;
+
+	riinfo = ri_FetchConstraintInfo(trigdata->tg_trigger,
+									trigdata->tg_relation, true);
+
+	/*
+	 * Get the relation descriptors of the FK and PK tables and the old tuple.
+	 *
+	 * fk_rel is opened in RowExclusiveLock mode since that's what our
+	 * eventual UPDATE will get on it.
+	 */
+	fk_rel = table_open(riinfo->fk_relid, RowExclusiveLock);
+	pk_rel = trigdata->tg_relation;
+	oldslot = trigdata->tg_trigslot;
+
+	/*
+	 * Don't SET NULL/DEFAULT than more than the PK's duration,
+	 * trimmed by an original FOR PORTION OF if necessary.
+	 */
+	targetRange = restrict_cascading_range(trigdata->tg_temporal, riinfo, oldslot);
+
+	if (SPI_connect() != SPI_OK_CONNECT)
+		elog(ERROR, "SPI_connect failed");
+
+	/*
+	 * Fetch or prepare a saved plan for the trigger.
+	 */
+	switch (tgkind)
+	{
+		case RI_TRIGTYPE_UPDATE:
+			queryno = is_set_null
+				? TRI_PLAN_SETNULL_ONUPDATE
+				: TRI_PLAN_SETDEFAULT_ONUPDATE;
+			break;
+		case RI_TRIGTYPE_DELETE:
+			queryno = is_set_null
+				? TRI_PLAN_SETNULL_ONDELETE
+				: TRI_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];
+		const char *querysep;
+		const char *qualsep;
+		Oid			queryoids[RI_MAX_NUMKEYS + 1]; /* +1 for FOR PORTION OF */
+		const char *fk_only;
+		int			num_cols_to_set;
+		const int16 *set_cols;
+
+		switch (tgkind)
+		{
+			case RI_TRIGTYPE_UPDATE:
+				/* -1 so we let FOR PORTION OF set the range. */
+				num_cols_to_set = riinfo->nkeys - 1;
+				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
+				{
+					/* -1 so we let FOR PORTION OF set the range. */
+					num_cols_to_set = riinfo->nkeys - 1;
+					set_cols = riinfo->fk_attnums;
+				}
+				break;
+			default:
+				elog(ERROR, "invalid tgkind passed to ri_set");
+		}
+
+		/* ----------
+		 * The query string built is
+		 *	UPDATE [ONLY] <fktable>
+		 *			FOR PORTION OF $fkatt FROM lower(${n+1}) TO upper(${n+1})
+		 *			SET fkatt1 = {NULL|DEFAULT} [, ...]
+		 *			WHERE $1 = fkatt1 [AND ...]
+		 * The type id's for the $ parameters are those of the
+		 * corresponding PK attributes.
+		 * ----------
+		 */
+		initStringInfo(&querybuf);
+		initStringInfo(&qualbuf);
+		fk_only = fk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ?
+			"" : "ONLY ";
+		quoteRelationName(fkrelname, fk_rel);
+		quoteOneName(attname, RIAttName(fk_rel, riinfo->fk_attnums[riinfo->nkeys - 1]));
+
+		appendStringInfo(&querybuf, "UPDATE %s%s FOR PORTION OF %s FROM lower($%d) TO upper($%d) SET",
+						 fk_only, fkrelname, attname, riinfo->nkeys + 1, riinfo->nkeys + 1);
+
+		/*
+		 * 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]);
+
+			quoteOneName(attname,
+						 RIAttName(fk_rel, riinfo->fk_attnums[i]));
+
+			sprintf(paramname, "$%d", i + 1);
+			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);
+			qualsep = "AND";
+			queryoids[i] = pk_type;
+		}
+
+		/* Set a param for FOR PORTION OF TO/FROM */
+		queryoids[riinfo->nkeys] = RIAttType(pk_rel, riinfo->pk_attnums[riinfo->nkeys - 1]);
+
+		/* Prepare and save the plan */
+		qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys + 1, queryoids,
+							 &qkey, fk_rel, pk_rel);
+	}
+
+	/*
+	 * We have a plan now. Run it to update the existing references.
+	 */
+	ri_PerformCheck(riinfo, &qkey, qplan,
+					fk_rel, pk_rel,
+					oldslot, NULL,
+					riinfo->nkeys, targetRange,
+					true,		/* must detect new rows */
+					SPI_OK_UPDATE);
+
+	if (SPI_finish() != SPI_OK_FINISH)
+		elog(ERROR, "SPI_finish failed");
+
+	table_close(fk_rel, RowExclusiveLock);
+
+	if (is_set_null)
+		return PointerGetDatum(NULL);
+	else
+	{
+		/*
+		 * If we just deleted or updated the PK row whose key was equal to the
+		 * FK columns' default values, and a referencing row exists in the FK
+		 * table, we would have updated that row to the same values it already
+		 * had --- and RI_FKey_fk_upd_check_required would hence believe no
+		 * check is necessary.  So we need to do another lookup now and in
+		 * case a reference still exists, abort the operation.  That is
+		 * already implemented in the NO ACTION trigger, so just run it. (This
+		 * recheck is only needed in the SET DEFAULT case, since CASCADE would
+		 * remove such rows in case of a DELETE operation or would change the
+		 * FK key values in case of an UPDATE, while SET NULL is certain to
+		 * result in rows that satisfy the FK constraint.)
+		 */
+		return ri_restrict(trigdata, true);
+	}
+}
+
 /*
  * RI_FKey_pk_upd_check_required -
  *
@@ -2552,8 +3089,8 @@ ri_PerformCheck(const RI_ConstraintInfo *riinfo,
 	int			spi_result;
 	Oid			save_userid;
 	int			save_sec_context;
-	Datum		vals[RI_MAX_NUMKEYS * 2];
-	char		nulls[RI_MAX_NUMKEYS * 2];
+	Datum		vals[RI_MAX_NUMKEYS * 2 + 1];
+	char		nulls[RI_MAX_NUMKEYS * 2 + 1];
 
 	/*
 	 * Use the query type code to determine whether the query is run against
@@ -2588,8 +3125,10 @@ ri_PerformCheck(const RI_ConstraintInfo *riinfo,
 		ri_ExtractValues(source_rel, newslot, riinfo, source_is_pk,
 						 vals, nulls);
 		if (oldslot)
+		{
 			ri_ExtractValues(source_rel, oldslot, riinfo, source_is_pk,
 							 vals + riinfo->nkeys, nulls + riinfo->nkeys);
+		}
 	}
 	else
 	{
@@ -3300,8 +3839,14 @@ RI_FKey_trigger_type(Oid tgfoid)
 		case F_RI_FKEY_SETDEFAULT_UPD:
 		case F_RI_FKEY_NOACTION_DEL:
 		case F_RI_FKEY_NOACTION_UPD:
+		case F_TRI_FKEY_CASCADE_DEL:
+		case F_TRI_FKEY_CASCADE_UPD:
 		case F_TRI_FKEY_RESTRICT_DEL:
 		case F_TRI_FKEY_RESTRICT_UPD:
+		case F_TRI_FKEY_SETNULL_DEL:
+		case F_TRI_FKEY_SETNULL_UPD:
+		case F_TRI_FKEY_SETDEFAULT_DEL:
+		case F_TRI_FKEY_SETDEFAULT_UPD:
 		case F_TRI_FKEY_NOACTION_DEL:
 		case F_TRI_FKEY_NOACTION_UPD:
 			return RI_TRIGGER_PK;
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index e70b391e04..545a6b1ca0 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -3952,6 +3952,12 @@
 { oid => '6123', descr => 'temporal referential integrity FOREIGN KEY ... REFERENCES',
   proname => 'TRI_FKey_check_upd', provolatile => 'v', prorettype => 'trigger',
   proargtypes => '', prosrc => 'TRI_FKey_check_upd' },
+{ oid => '6124', descr => 'temporal referential integrity ON DELETE CASCADE',
+  proname => 'TRI_FKey_cascade_del', provolatile => 'v', prorettype => 'trigger',
+  proargtypes => '', prosrc => 'TRI_FKey_cascade_del' },
+{ oid => '6125', descr => 'temporal referential integrity ON UPDATE CASCADE',
+  proname => 'TRI_FKey_cascade_upd', provolatile => 'v', prorettype => 'trigger',
+  proargtypes => '', prosrc => 'TRI_FKey_cascade_upd' },
 { oid => '6126', descr => 'temporal referential integrity ON DELETE RESTRICT',
   proname => 'TRI_FKey_restrict_del', provolatile => 'v',
   prorettype => 'trigger', proargtypes => '',
@@ -3960,6 +3966,20 @@
   proname => 'TRI_FKey_restrict_upd', provolatile => 'v',
   prorettype => 'trigger', proargtypes => '',
   prosrc => 'TRI_FKey_restrict_upd' },
+{ oid => '6128', descr => 'temporal referential integrity ON DELETE SET NULL',
+  proname => 'TRI_FKey_setnull_del', provolatile => 'v', prorettype => 'trigger',
+  proargtypes => '', prosrc => 'TRI_FKey_setnull_del' },
+{ oid => '6129', descr => 'temporal referential integrity ON UPDATE SET NULL',
+  proname => 'TRI_FKey_setnull_upd', provolatile => 'v', prorettype => 'trigger',
+  proargtypes => '', prosrc => 'TRI_FKey_setnull_upd' },
+{ oid => '6130', descr => 'temporal referential integrity ON DELETE SET DEFAULT',
+  proname => 'TRI_FKey_setdefault_del', provolatile => 'v',
+  prorettype => 'trigger', proargtypes => '',
+  prosrc => 'TRI_FKey_setdefault_del' },
+{ oid => '6131', descr => 'temporal referential integrity ON UPDATE SET DEFAULT',
+  proname => 'TRI_FKey_setdefault_upd', provolatile => 'v',
+  prorettype => 'trigger', proargtypes => '',
+  prosrc => 'TRI_FKey_setdefault_upd' },
 { oid => '6132', descr => 'temporal referential integrity ON DELETE NO ACTION',
   proname => 'TRI_FKey_noaction_del', provolatile => 'v',
   prorettype => 'trigger', proargtypes => '',
diff --git a/src/test/regress/expected/without_overlaps.out b/src/test/regress/expected/without_overlaps.out
index 80a4e60695..a011b7dce5 100644
--- a/src/test/regress/expected/without_overlaps.out
+++ b/src/test/regress/expected/without_overlaps.out
@@ -904,30 +904,184 @@ DELETE FROM temporal_rng WHERE id IN ('[5,5]');
 --
 -- test ON UPDATE/DELETE options
 --
+-- TOC:
+-- parent updates CASCADE
+-- parent deletes CASCADE
+-- parent updates SET NULL
+-- parent deletes SET NULL
+-- parent updates SET DEFAULT
+-- parent deletes SET DEFAULT
+-- parent updates CASCADE (two scalar cols)
+-- parent deletes CASCADE (two scalar cols)
+-- parent updates SET NULL (two scalar cols)
+-- parent deletes SET NULL (two scalar cols)
+-- parent deletes SET NULL (two scalar cols, SET NULL subset)
+-- parent updates SET DEFAULT (two scalar cols)
+-- parent deletes SET DEFAULT (two scalar cols)
+-- parent deletes SET DEFAULT (two scalar cols, SET DEFAULT subset)
 -- test FK parent updates CASCADE
 INSERT INTO temporal_rng VALUES ('[6,6]', tsrange('2018-01-01', '2021-01-01'));
-INSERT INTO temporal_fk_rng2rng VALUES ('[4,4]', tsrange('2018-01-01', '2021-01-01'), '[6,6]');
+INSERT INTO temporal_fk_rng2rng VALUES ('[100,100]', tsrange('2018-01-01', '2021-01-01'), '[6,6]');
 ALTER TABLE temporal_fk_rng2rng
 	DROP CONSTRAINT temporal_fk_rng2rng_fk,
 	ADD CONSTRAINT temporal_fk_rng2rng_fk
 		FOREIGN KEY (parent_id, PERIOD valid_at)
 		REFERENCES temporal_rng
 		ON DELETE CASCADE ON UPDATE CASCADE;
-ERROR:  action not supported for temporal foreign keys
+-- leftovers on both sides:
+UPDATE temporal_rng FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' SET id = '[7,7]' WHERE id = '[6,6]';
+SELECT * FROM temporal_fk_rng2rng WHERE id = '[100,100]';
+    id     |                        valid_at                         | parent_id 
+-----------+---------------------------------------------------------+-----------
+ [100,101) | ["Tue Jan 01 00:00:00 2019","Wed Jan 01 00:00:00 2020") | [7,8)
+ [100,101) | ["Mon Jan 01 00:00:00 2018","Tue Jan 01 00:00:00 2019") | [6,7)
+ [100,101) | ["Wed Jan 01 00:00:00 2020","Fri Jan 01 00:00:00 2021") | [6,7)
+(3 rows)
+
+-- non-FPO update:
+UPDATE temporal_rng SET id = '[7,7]' WHERE id = '[6,6]';
+SELECT * FROM temporal_fk_rng2rng WHERE id = '[100,100]';
+    id     |                        valid_at                         | parent_id 
+-----------+---------------------------------------------------------+-----------
+ [100,101) | ["Tue Jan 01 00:00:00 2019","Wed Jan 01 00:00:00 2020") | [7,8)
+ [100,101) | ["Mon Jan 01 00:00:00 2018","Tue Jan 01 00:00:00 2019") | [7,8)
+ [100,101) | ["Wed Jan 01 00:00:00 2020","Fri Jan 01 00:00:00 2021") | [7,8)
+(3 rows)
+
+-- FK across two referenced rows:
+INSERT INTO temporal_rng VALUES ('[8,8]', tsrange('2018-01-01', '2020-01-01'));
+INSERT INTO temporal_rng VALUES ('[8,8]', tsrange('2020-01-01', '2021-01-01'));
+INSERT INTO temporal_fk_rng2rng VALUES ('[200,200]', tsrange('2018-01-01', '2021-01-01'), '[8,8]');
+UPDATE temporal_rng SET id = '[9,9]' WHERE id = '[8,8]' AND valid_at @> '2019-01-01'::timestamp;
+SELECT * FROM temporal_fk_rng2rng WHERE id = '[200,200]';
+    id     |                        valid_at                         | parent_id 
+-----------+---------------------------------------------------------+-----------
+ [200,201) | ["Mon Jan 01 00:00:00 2018","Wed Jan 01 00:00:00 2020") | [9,10)
+ [200,201) | ["Wed Jan 01 00:00:00 2020","Fri Jan 01 00:00:00 2021") | [8,9)
+(2 rows)
+
+-- clean up
+DELETE FROM temporal_fk_rng2rng WHERE id IN ('[100,100]', '[200,200]');
+DELETE FROM temporal_rng WHERE id IN ('[6,6]', '[7,7]', '[8,8]', '[9,9]');
+-- test FK parent deletes CASCADE
+INSERT INTO temporal_rng VALUES ('[6,6]', tsrange('2018-01-01', '2021-01-01'));
+INSERT INTO temporal_fk_rng2rng VALUES ('[100,100]', tsrange('2018-01-01', '2021-01-01'), '[6,6]');
+-- leftovers on both sides:
+DELETE FROM temporal_rng FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' WHERE id = '[6,6]';
+SELECT * FROM temporal_fk_rng2rng WHERE id = '[100,100]';
+    id     |                        valid_at                         | parent_id 
+-----------+---------------------------------------------------------+-----------
+ [100,101) | ["Mon Jan 01 00:00:00 2018","Tue Jan 01 00:00:00 2019") | [6,7)
+ [100,101) | ["Wed Jan 01 00:00:00 2020","Fri Jan 01 00:00:00 2021") | [6,7)
+(2 rows)
+
+-- non-FPO delete:
+DELETE FROM temporal_rng WHERE id = '[6,6]';
+SELECT * FROM temporal_fk_rng2rng WHERE id = '[100,100]';
+ id | valid_at | parent_id 
+----+----------+-----------
+(0 rows)
+
+-- FK across two referenced rows:
+INSERT INTO temporal_rng VALUES ('[8,8]', tsrange('2018-01-01', '2020-01-01'));
+INSERT INTO temporal_rng VALUES ('[8,8]', tsrange('2020-01-01', '2021-01-01'));
+INSERT INTO temporal_fk_rng2rng VALUES ('[200,200]', tsrange('2018-01-01', '2021-01-01'), '[8,8]');
+DELETE FROM temporal_rng WHERE id = '[8,8]' AND valid_at @> '2019-01-01'::timestamp;
+SELECT * FROM temporal_fk_rng2rng WHERE id = '[200,200]';
+    id     |                        valid_at                         | parent_id 
+-----------+---------------------------------------------------------+-----------
+ [200,201) | ["Wed Jan 01 00:00:00 2020","Fri Jan 01 00:00:00 2021") | [8,9)
+(1 row)
+
+-- clean up
+DELETE FROM temporal_fk_rng2rng WHERE id IN ('[100,100]', '[200,200]');
+DELETE FROM temporal_rng WHERE id IN ('[6,6]', '[7,7]', '[8,8]', '[9,9]');
 -- test FK parent updates SET NULL
-INSERT INTO temporal_rng VALUES ('[9,9]', tsrange('2018-01-01', '2021-01-01'));
-INSERT INTO temporal_fk_rng2rng VALUES ('[6,6]', tsrange('2018-01-01', '2021-01-01'), '[9,9]');
+INSERT INTO temporal_rng VALUES ('[6,6]', tsrange('2018-01-01', '2021-01-01'));
+INSERT INTO temporal_fk_rng2rng VALUES ('[100,100]', tsrange('2018-01-01', '2021-01-01'), '[6,6]');
 ALTER TABLE temporal_fk_rng2rng
 	DROP CONSTRAINT temporal_fk_rng2rng_fk,
 	ADD CONSTRAINT temporal_fk_rng2rng_fk
 		FOREIGN KEY (parent_id, PERIOD valid_at)
 		REFERENCES temporal_rng
 		ON DELETE SET NULL ON UPDATE SET NULL;
-ERROR:  action not supported for temporal foreign keys
+-- leftovers on both sides:
+UPDATE temporal_rng FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' SET id = '[7,7]' WHERE id = '[6,6]';
+SELECT * FROM temporal_fk_rng2rng WHERE id = '[100,100]';
+    id     |                        valid_at                         | parent_id 
+-----------+---------------------------------------------------------+-----------
+ [100,101) | ["Tue Jan 01 00:00:00 2019","Wed Jan 01 00:00:00 2020") | 
+ [100,101) | ["Mon Jan 01 00:00:00 2018","Tue Jan 01 00:00:00 2019") | [6,7)
+ [100,101) | ["Wed Jan 01 00:00:00 2020","Fri Jan 01 00:00:00 2021") | [6,7)
+(3 rows)
+
+-- non-FPO update:
+UPDATE temporal_rng SET id = '[7,7]' WHERE id = '[6,6]';
+SELECT * FROM temporal_fk_rng2rng WHERE id = '[100,100]';
+    id     |                        valid_at                         | parent_id 
+-----------+---------------------------------------------------------+-----------
+ [100,101) | ["Tue Jan 01 00:00:00 2019","Wed Jan 01 00:00:00 2020") | 
+ [100,101) | ["Mon Jan 01 00:00:00 2018","Tue Jan 01 00:00:00 2019") | 
+ [100,101) | ["Wed Jan 01 00:00:00 2020","Fri Jan 01 00:00:00 2021") | 
+(3 rows)
+
+-- FK across two referenced rows:
+INSERT INTO temporal_rng VALUES ('[8,8]', tsrange('2018-01-01', '2020-01-01'));
+INSERT INTO temporal_rng VALUES ('[8,8]', tsrange('2020-01-01', '2021-01-01'));
+INSERT INTO temporal_fk_rng2rng VALUES ('[200,200]', tsrange('2018-01-01', '2021-01-01'), '[8,8]');
+UPDATE temporal_rng SET id = '[9,9]' WHERE id = '[8,8]' AND valid_at @> '2019-01-01'::timestamp;
+SELECT * FROM temporal_fk_rng2rng WHERE id = '[200,200]';
+    id     |                        valid_at                         | parent_id 
+-----------+---------------------------------------------------------+-----------
+ [200,201) | ["Mon Jan 01 00:00:00 2018","Wed Jan 01 00:00:00 2020") | 
+ [200,201) | ["Wed Jan 01 00:00:00 2020","Fri Jan 01 00:00:00 2021") | [8,9)
+(2 rows)
+
+-- clean up
+DELETE FROM temporal_fk_rng2rng WHERE id IN ('[100,100]', '[200,200]');
+DELETE FROM temporal_rng WHERE id IN ('[6,6]', '[7,7]', '[8,8]', '[9,9]');
+-- test FK parent deletes SET NULL
+INSERT INTO temporal_rng VALUES ('[6,6]', tsrange('2018-01-01', '2021-01-01'));
+INSERT INTO temporal_fk_rng2rng VALUES ('[100,100]', tsrange('2018-01-01', '2021-01-01'), '[6,6]');
+-- leftovers on both sides:
+DELETE FROM temporal_rng FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' WHERE id = '[6,6]';
+SELECT * FROM temporal_fk_rng2rng WHERE id = '[100,100]';
+    id     |                        valid_at                         | parent_id 
+-----------+---------------------------------------------------------+-----------
+ [100,101) | ["Tue Jan 01 00:00:00 2019","Wed Jan 01 00:00:00 2020") | 
+ [100,101) | ["Mon Jan 01 00:00:00 2018","Tue Jan 01 00:00:00 2019") | [6,7)
+ [100,101) | ["Wed Jan 01 00:00:00 2020","Fri Jan 01 00:00:00 2021") | [6,7)
+(3 rows)
+
+-- non-FPO delete:
+DELETE FROM temporal_rng WHERE id = '[6,6]';
+SELECT * FROM temporal_fk_rng2rng WHERE id = '[100,100]';
+    id     |                        valid_at                         | parent_id 
+-----------+---------------------------------------------------------+-----------
+ [100,101) | ["Tue Jan 01 00:00:00 2019","Wed Jan 01 00:00:00 2020") | 
+ [100,101) | ["Mon Jan 01 00:00:00 2018","Tue Jan 01 00:00:00 2019") | 
+ [100,101) | ["Wed Jan 01 00:00:00 2020","Fri Jan 01 00:00:00 2021") | 
+(3 rows)
+
+-- FK across two referenced rows:
+INSERT INTO temporal_rng VALUES ('[8,8]', tsrange('2018-01-01', '2020-01-01'));
+INSERT INTO temporal_rng VALUES ('[8,8]', tsrange('2020-01-01', '2021-01-01'));
+INSERT INTO temporal_fk_rng2rng VALUES ('[200,200]', tsrange('2018-01-01', '2021-01-01'), '[8,8]');
+DELETE FROM temporal_rng WHERE id = '[8,8]' AND valid_at @> '2019-01-01'::timestamp;
+SELECT * FROM temporal_fk_rng2rng WHERE id = '[200,200]';
+    id     |                        valid_at                         | parent_id 
+-----------+---------------------------------------------------------+-----------
+ [200,201) | ["Mon Jan 01 00:00:00 2018","Wed Jan 01 00:00:00 2020") | 
+ [200,201) | ["Wed Jan 01 00:00:00 2020","Fri Jan 01 00:00:00 2021") | [8,9)
+(2 rows)
+
+-- clean up
+DELETE FROM temporal_fk_rng2rng WHERE id IN ('[100,100]', '[200,200]');
+DELETE FROM temporal_rng WHERE id IN ('[6,6]', '[7,7]', '[8,8]', '[9,9]');
 -- test FK parent updates SET DEFAULT
 INSERT INTO temporal_rng VALUES ('[-1,-1]', tsrange(null, null));
-INSERT INTO temporal_rng VALUES ('[12,12]', tsrange('2018-01-01', '2021-01-01'));
-INSERT INTO temporal_fk_rng2rng VALUES ('[8,8]', tsrange('2018-01-01', '2021-01-01'), '[12,12]');
+INSERT INTO temporal_rng VALUES ('[6,6]', tsrange('2018-01-01', '2021-01-01'));
+INSERT INTO temporal_fk_rng2rng VALUES ('[100,100]', tsrange('2018-01-01', '2021-01-01'), '[6,6]');
 ALTER TABLE temporal_fk_rng2rng
   ALTER COLUMN parent_id SET DEFAULT '[-1,-1]',
 	DROP CONSTRAINT temporal_fk_rng2rng_fk,
@@ -935,7 +1089,461 @@ ALTER TABLE temporal_fk_rng2rng
 		FOREIGN KEY (parent_id, PERIOD valid_at)
 		REFERENCES temporal_rng
 		ON DELETE SET DEFAULT ON UPDATE SET DEFAULT;
-ERROR:  action not supported for temporal foreign keys
+-- leftovers on both sides:
+UPDATE temporal_rng FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' SET id = '[7,7]' WHERE id = '[6,6]';
+SELECT * FROM temporal_fk_rng2rng WHERE id = '[100,100]';
+    id     |                        valid_at                         | parent_id 
+-----------+---------------------------------------------------------+-----------
+ [100,101) | ["Tue Jan 01 00:00:00 2019","Wed Jan 01 00:00:00 2020") | [-1,0)
+ [100,101) | ["Mon Jan 01 00:00:00 2018","Tue Jan 01 00:00:00 2019") | [6,7)
+ [100,101) | ["Wed Jan 01 00:00:00 2020","Fri Jan 01 00:00:00 2021") | [6,7)
+(3 rows)
+
+-- non-FPO update:
+UPDATE temporal_rng SET id = '[7,7]' WHERE id = '[6,6]';
+SELECT * FROM temporal_fk_rng2rng WHERE id = '[100,100]';
+    id     |                        valid_at                         | parent_id 
+-----------+---------------------------------------------------------+-----------
+ [100,101) | ["Tue Jan 01 00:00:00 2019","Wed Jan 01 00:00:00 2020") | [-1,0)
+ [100,101) | ["Mon Jan 01 00:00:00 2018","Tue Jan 01 00:00:00 2019") | [-1,0)
+ [100,101) | ["Wed Jan 01 00:00:00 2020","Fri Jan 01 00:00:00 2021") | [-1,0)
+(3 rows)
+
+-- FK across two referenced rows:
+INSERT INTO temporal_rng VALUES ('[8,8]', tsrange('2018-01-01', '2020-01-01'));
+INSERT INTO temporal_rng VALUES ('[8,8]', tsrange('2020-01-01', '2021-01-01'));
+INSERT INTO temporal_fk_rng2rng VALUES ('[200,200]', tsrange('2018-01-01', '2021-01-01'), '[8,8]');
+UPDATE temporal_rng SET id = '[9,9]' WHERE id = '[8,8]' AND valid_at @> '2019-01-01'::timestamp;
+SELECT * FROM temporal_fk_rng2rng WHERE id = '[200,200]';
+    id     |                        valid_at                         | parent_id 
+-----------+---------------------------------------------------------+-----------
+ [200,201) | ["Mon Jan 01 00:00:00 2018","Wed Jan 01 00:00:00 2020") | [-1,0)
+ [200,201) | ["Wed Jan 01 00:00:00 2020","Fri Jan 01 00:00:00 2021") | [8,9)
+(2 rows)
+
+-- clean up
+DELETE FROM temporal_fk_rng2rng WHERE id IN ('[100,100]', '[200,200]');
+DELETE FROM temporal_rng WHERE id IN ('[6,6]', '[7,7]', '[8,8]', '[9,9]');
+-- test FK parent deletes SET DEFAULT
+INSERT INTO temporal_rng VALUES ('[6,6]', tsrange('2018-01-01', '2021-01-01'));
+INSERT INTO temporal_fk_rng2rng VALUES ('[100,100]', tsrange('2018-01-01', '2021-01-01'), '[6,6]');
+-- leftovers on both sides:
+DELETE FROM temporal_rng FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' WHERE id = '[6,6]';
+SELECT * FROM temporal_fk_rng2rng WHERE id = '[100,100]';
+    id     |                        valid_at                         | parent_id 
+-----------+---------------------------------------------------------+-----------
+ [100,101) | ["Tue Jan 01 00:00:00 2019","Wed Jan 01 00:00:00 2020") | [-1,0)
+ [100,101) | ["Mon Jan 01 00:00:00 2018","Tue Jan 01 00:00:00 2019") | [6,7)
+ [100,101) | ["Wed Jan 01 00:00:00 2020","Fri Jan 01 00:00:00 2021") | [6,7)
+(3 rows)
+
+-- non-FPO update:
+DELETE FROM temporal_rng WHERE id = '[6,6]';
+SELECT * FROM temporal_fk_rng2rng WHERE id = '[100,100]';
+    id     |                        valid_at                         | parent_id 
+-----------+---------------------------------------------------------+-----------
+ [100,101) | ["Tue Jan 01 00:00:00 2019","Wed Jan 01 00:00:00 2020") | [-1,0)
+ [100,101) | ["Mon Jan 01 00:00:00 2018","Tue Jan 01 00:00:00 2019") | [-1,0)
+ [100,101) | ["Wed Jan 01 00:00:00 2020","Fri Jan 01 00:00:00 2021") | [-1,0)
+(3 rows)
+
+-- FK across two referenced rows:
+INSERT INTO temporal_rng VALUES ('[8,8]', tsrange('2018-01-01', '2020-01-01'));
+INSERT INTO temporal_rng VALUES ('[8,8]', tsrange('2020-01-01', '2021-01-01'));
+INSERT INTO temporal_fk_rng2rng VALUES ('[200,200]', tsrange('2018-01-01', '2021-01-01'), '[8,8]');
+DELETE FROM temporal_rng WHERE id = '[8,8]' AND valid_at @> '2019-01-01'::timestamp;
+SELECT * FROM temporal_fk_rng2rng WHERE id = '[200,200]';
+    id     |                        valid_at                         | parent_id 
+-----------+---------------------------------------------------------+-----------
+ [200,201) | ["Mon Jan 01 00:00:00 2018","Wed Jan 01 00:00:00 2020") | [-1,0)
+ [200,201) | ["Wed Jan 01 00:00:00 2020","Fri Jan 01 00:00:00 2021") | [8,9)
+(2 rows)
+
+-- clean up
+DELETE FROM temporal_fk_rng2rng WHERE id IN ('[100,100]', '[200,200]');
+DELETE FROM temporal_rng WHERE id IN ('[6,6]', '[7,7]', '[8,8]', '[9,9]');
+-- test FK parent updates CASCADE (two scalar cols)
+INSERT INTO temporal_rng2 VALUES ('[6,6]', '[6,6]', tsrange('2018-01-01', '2021-01-01'));
+INSERT INTO temporal_fk2_rng2rng VALUES ('[100,100]', tsrange('2018-01-01', '2021-01-01'), '[6,6]', '[6,6]');
+ALTER TABLE temporal_fk2_rng2rng
+	DROP CONSTRAINT temporal_fk2_rng2rng_fk,
+	ADD CONSTRAINT temporal_fk2_rng2rng_fk
+		FOREIGN KEY (parent_id1, parent_id2, PERIOD valid_at)
+		REFERENCES temporal_rng2
+		ON DELETE CASCADE ON UPDATE CASCADE;
+-- leftovers on both sides:
+UPDATE temporal_rng2 FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' SET id1 = '[7,7]' WHERE id1 = '[6,6]';
+SELECT * FROM temporal_fk2_rng2rng WHERE id = '[100,100]';
+    id     |                        valid_at                         | parent_id1 | parent_id2 
+-----------+---------------------------------------------------------+------------+------------
+ [100,101) | ["Tue Jan 01 00:00:00 2019","Wed Jan 01 00:00:00 2020") | [7,8)      | [6,7)
+ [100,101) | ["Mon Jan 01 00:00:00 2018","Tue Jan 01 00:00:00 2019") | [6,7)      | [6,7)
+ [100,101) | ["Wed Jan 01 00:00:00 2020","Fri Jan 01 00:00:00 2021") | [6,7)      | [6,7)
+(3 rows)
+
+-- non-FPO update:
+UPDATE temporal_rng2 SET id1 = '[7,7]' WHERE id1 = '[6,6]';
+SELECT * FROM temporal_fk2_rng2rng WHERE id = '[100,100]';
+    id     |                        valid_at                         | parent_id1 | parent_id2 
+-----------+---------------------------------------------------------+------------+------------
+ [100,101) | ["Tue Jan 01 00:00:00 2019","Wed Jan 01 00:00:00 2020") | [7,8)      | [6,7)
+ [100,101) | ["Mon Jan 01 00:00:00 2018","Tue Jan 01 00:00:00 2019") | [7,8)      | [6,7)
+ [100,101) | ["Wed Jan 01 00:00:00 2020","Fri Jan 01 00:00:00 2021") | [7,8)      | [6,7)
+(3 rows)
+
+-- FK across two referenced rows:
+INSERT INTO temporal_rng2 VALUES ('[8,8]', '[8,8]', tsrange('2018-01-01', '2020-01-01'));
+INSERT INTO temporal_rng2 VALUES ('[8,8]', '[8,8]', tsrange('2020-01-01', '2021-01-01'));
+INSERT INTO temporal_fk2_rng2rng VALUES ('[200,200]', tsrange('2018-01-01', '2021-01-01'), '[8,8]', '[8,8]');
+UPDATE temporal_rng2 SET id1 = '[9,9]' WHERE id1 = '[8,8]' AND valid_at @> '2019-01-01'::timestamp;
+SELECT * FROM temporal_fk2_rng2rng WHERE id = '[200,200]';
+    id     |                        valid_at                         | parent_id1 | parent_id2 
+-----------+---------------------------------------------------------+------------+------------
+ [200,201) | ["Mon Jan 01 00:00:00 2018","Wed Jan 01 00:00:00 2020") | [9,10)     | [8,9)
+ [200,201) | ["Wed Jan 01 00:00:00 2020","Fri Jan 01 00:00:00 2021") | [8,9)      | [8,9)
+(2 rows)
+
+-- clean up
+DELETE FROM temporal_fk2_rng2rng WHERE id IN ('[100,100]', '[200,200]');
+DELETE FROM temporal_rng2 WHERE id1 IN ('[6,6]', '[7,7]', '[8,8]', '[9,9]');
+-- test FK parent deletes CASCADE (two scalar cols)
+INSERT INTO temporal_rng2 VALUES ('[6,6]', '[6,6]', tsrange('2018-01-01', '2021-01-01'));
+INSERT INTO temporal_fk2_rng2rng VALUES ('[100,100]', tsrange('2018-01-01', '2021-01-01'), '[6,6]', '[6,6]');
+-- leftovers on both sides:
+DELETE FROM temporal_rng2 FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' WHERE id1 = '[6,6]';
+SELECT * FROM temporal_fk2_rng2rng WHERE id = '[100,100]';
+    id     |                        valid_at                         | parent_id1 | parent_id2 
+-----------+---------------------------------------------------------+------------+------------
+ [100,101) | ["Mon Jan 01 00:00:00 2018","Tue Jan 01 00:00:00 2019") | [6,7)      | [6,7)
+ [100,101) | ["Wed Jan 01 00:00:00 2020","Fri Jan 01 00:00:00 2021") | [6,7)      | [6,7)
+(2 rows)
+
+-- non-FPO delete:
+DELETE FROM temporal_rng2 WHERE id1 = '[6,6]';
+SELECT * FROM temporal_fk2_rng2rng WHERE id = '[100,100]';
+ id | valid_at | parent_id1 | parent_id2 
+----+----------+------------+------------
+(0 rows)
+
+-- FK across two referenced rows:
+INSERT INTO temporal_rng2 VALUES ('[8,8]', '[8,8]', tsrange('2018-01-01', '2020-01-01'));
+INSERT INTO temporal_rng2 VALUES ('[8,8]', '[8,8]', tsrange('2020-01-01', '2021-01-01'));
+INSERT INTO temporal_fk2_rng2rng VALUES ('[200,200]', tsrange('2018-01-01', '2021-01-01'), '[8,8]', '[8,8]');
+DELETE FROM temporal_rng2 WHERE id1 = '[8,8]' AND valid_at @> '2019-01-01'::timestamp;
+SELECT * FROM temporal_fk2_rng2rng WHERE id = '[200,200]';
+    id     |                        valid_at                         | parent_id1 | parent_id2 
+-----------+---------------------------------------------------------+------------+------------
+ [200,201) | ["Wed Jan 01 00:00:00 2020","Fri Jan 01 00:00:00 2021") | [8,9)      | [8,9)
+(1 row)
+
+-- clean up
+DELETE FROM temporal_fk2_rng2rng WHERE id IN ('[100,100]', '[200,200]');
+DELETE FROM temporal_rng2 WHERE id1 IN ('[6,6]', '[7,7]', '[8,8]', '[9,9]');
+-- test FK parent updates SET NULL (two scalar cols)
+INSERT INTO temporal_rng2 VALUES ('[6,6]', '[6,6]', tsrange('2018-01-01', '2021-01-01'));
+INSERT INTO temporal_fk2_rng2rng VALUES ('[100,100]', tsrange('2018-01-01', '2021-01-01'), '[6,6]', '[6,6]');
+ALTER TABLE temporal_fk2_rng2rng
+	DROP CONSTRAINT temporal_fk2_rng2rng_fk,
+	ADD CONSTRAINT temporal_fk2_rng2rng_fk
+		FOREIGN KEY (parent_id1, parent_id2, PERIOD valid_at)
+		REFERENCES temporal_rng2
+		ON DELETE SET NULL ON UPDATE SET NULL;
+-- leftovers on both sides:
+UPDATE temporal_rng2 FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' SET id1 = '[7,7]' WHERE id1 = '[6,6]';
+SELECT * FROM temporal_fk2_rng2rng WHERE id = '[100,100]';
+    id     |                        valid_at                         | parent_id1 | parent_id2 
+-----------+---------------------------------------------------------+------------+------------
+ [100,101) | ["Tue Jan 01 00:00:00 2019","Wed Jan 01 00:00:00 2020") |            | 
+ [100,101) | ["Mon Jan 01 00:00:00 2018","Tue Jan 01 00:00:00 2019") | [6,7)      | [6,7)
+ [100,101) | ["Wed Jan 01 00:00:00 2020","Fri Jan 01 00:00:00 2021") | [6,7)      | [6,7)
+(3 rows)
+
+-- non-FPO update:
+UPDATE temporal_rng2 SET id1 = '[7,7]' WHERE id1 = '[6,6]';
+SELECT * FROM temporal_fk2_rng2rng WHERE id = '[100,100]';
+    id     |                        valid_at                         | parent_id1 | parent_id2 
+-----------+---------------------------------------------------------+------------+------------
+ [100,101) | ["Tue Jan 01 00:00:00 2019","Wed Jan 01 00:00:00 2020") |            | 
+ [100,101) | ["Mon Jan 01 00:00:00 2018","Tue Jan 01 00:00:00 2019") |            | 
+ [100,101) | ["Wed Jan 01 00:00:00 2020","Fri Jan 01 00:00:00 2021") |            | 
+(3 rows)
+
+-- FK across two referenced rows:
+INSERT INTO temporal_rng2 VALUES ('[8,8]', '[8,8]', tsrange('2018-01-01', '2020-01-01'));
+INSERT INTO temporal_rng2 VALUES ('[8,8]', '[8,8]', tsrange('2020-01-01', '2021-01-01'));
+INSERT INTO temporal_fk2_rng2rng VALUES ('[200,200]', tsrange('2018-01-01', '2021-01-01'), '[8,8]', '[8,8]');
+UPDATE temporal_rng2 SET id1 = '[9,9]' WHERE id1 = '[8,8]' AND valid_at @> '2019-01-01'::timestamp;
+SELECT * FROM temporal_fk2_rng2rng WHERE id = '[200,200]';
+    id     |                        valid_at                         | parent_id1 | parent_id2 
+-----------+---------------------------------------------------------+------------+------------
+ [200,201) | ["Mon Jan 01 00:00:00 2018","Wed Jan 01 00:00:00 2020") |            | 
+ [200,201) | ["Wed Jan 01 00:00:00 2020","Fri Jan 01 00:00:00 2021") | [8,9)      | [8,9)
+(2 rows)
+
+-- clean up
+DELETE FROM temporal_fk2_rng2rng WHERE id IN ('[100,100]', '[200,200]');
+DELETE FROM temporal_rng2 WHERE id1 IN ('[6,6]', '[7,7]', '[8,8]', '[9,9]');
+-- test FK parent deletes SET NULL (two scalar cols)
+INSERT INTO temporal_rng2 VALUES ('[6,6]', '[6,6]', tsrange('2018-01-01', '2021-01-01'));
+INSERT INTO temporal_fk2_rng2rng VALUES ('[100,100]', tsrange('2018-01-01', '2021-01-01'), '[6,6]', '[6,6]');
+-- leftovers on both sides:
+DELETE FROM temporal_rng2 FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' WHERE id1 = '[6,6]';
+SELECT * FROM temporal_fk2_rng2rng WHERE id = '[100,100]';
+    id     |                        valid_at                         | parent_id1 | parent_id2 
+-----------+---------------------------------------------------------+------------+------------
+ [100,101) | ["Tue Jan 01 00:00:00 2019","Wed Jan 01 00:00:00 2020") |            | 
+ [100,101) | ["Mon Jan 01 00:00:00 2018","Tue Jan 01 00:00:00 2019") | [6,7)      | [6,7)
+ [100,101) | ["Wed Jan 01 00:00:00 2020","Fri Jan 01 00:00:00 2021") | [6,7)      | [6,7)
+(3 rows)
+
+-- non-FPO delete:
+DELETE FROM temporal_rng2 WHERE id1 = '[6,6]';
+SELECT * FROM temporal_fk2_rng2rng WHERE id = '[100,100]';
+    id     |                        valid_at                         | parent_id1 | parent_id2 
+-----------+---------------------------------------------------------+------------+------------
+ [100,101) | ["Tue Jan 01 00:00:00 2019","Wed Jan 01 00:00:00 2020") |            | 
+ [100,101) | ["Mon Jan 01 00:00:00 2018","Tue Jan 01 00:00:00 2019") |            | 
+ [100,101) | ["Wed Jan 01 00:00:00 2020","Fri Jan 01 00:00:00 2021") |            | 
+(3 rows)
+
+-- FK across two referenced rows:
+INSERT INTO temporal_rng2 VALUES ('[8,8]', '[8,8]', tsrange('2018-01-01', '2020-01-01'));
+INSERT INTO temporal_rng2 VALUES ('[8,8]', '[8,8]', tsrange('2020-01-01', '2021-01-01'));
+INSERT INTO temporal_fk2_rng2rng VALUES ('[200,200]', tsrange('2018-01-01', '2021-01-01'), '[8,8]', '[8,8]');
+DELETE FROM temporal_rng2 WHERE id1 = '[8,8]' AND valid_at @> '2019-01-01'::timestamp;
+SELECT * FROM temporal_fk2_rng2rng WHERE id = '[200,200]';
+    id     |                        valid_at                         | parent_id1 | parent_id2 
+-----------+---------------------------------------------------------+------------+------------
+ [200,201) | ["Mon Jan 01 00:00:00 2018","Wed Jan 01 00:00:00 2020") |            | 
+ [200,201) | ["Wed Jan 01 00:00:00 2020","Fri Jan 01 00:00:00 2021") | [8,9)      | [8,9)
+(2 rows)
+
+-- clean up
+DELETE FROM temporal_fk2_rng2rng WHERE id IN ('[100,100]', '[200,200]');
+DELETE FROM temporal_rng2 WHERE id1 IN ('[6,6]', '[7,7]', '[8,8]', '[9,9]');
+-- test FK parent deletes SET NULL (two scalar cols, SET NULL subset)
+INSERT INTO temporal_rng2 VALUES ('[6,6]', '[6,6]', tsrange('2018-01-01', '2021-01-01'));
+INSERT INTO temporal_fk2_rng2rng VALUES ('[100,100]', tsrange('2018-01-01', '2021-01-01'), '[6,6]', '[6,6]');
+-- fails because you can't set the PERIOD column:
+ALTER TABLE temporal_fk2_rng2rng
+	DROP CONSTRAINT temporal_fk2_rng2rng_fk,
+	ADD CONSTRAINT temporal_fk2_rng2rng_fk
+		FOREIGN KEY (parent_id1, parent_id2, PERIOD valid_at)
+		REFERENCES temporal_rng2
+		ON DELETE SET NULL (valid_at) ON UPDATE SET NULL;
+ERROR:  column "valid_at" referenced in ON DELETE SET action cannot be PERIOD
+-- ok:
+ALTER TABLE temporal_fk2_rng2rng
+	DROP CONSTRAINT temporal_fk2_rng2rng_fk,
+	ADD CONSTRAINT temporal_fk2_rng2rng_fk
+		FOREIGN KEY (parent_id1, parent_id2, PERIOD valid_at)
+		REFERENCES temporal_rng2
+		ON DELETE SET NULL (parent_id1) ON UPDATE SET NULL;
+-- leftovers on both sides:
+DELETE FROM temporal_rng2 FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' WHERE id1 = '[6,6]';
+SELECT * FROM temporal_fk2_rng2rng WHERE id = '[100,100]';
+    id     |                        valid_at                         | parent_id1 | parent_id2 
+-----------+---------------------------------------------------------+------------+------------
+ [100,101) | ["Tue Jan 01 00:00:00 2019","Wed Jan 01 00:00:00 2020") |            | [6,7)
+ [100,101) | ["Mon Jan 01 00:00:00 2018","Tue Jan 01 00:00:00 2019") | [6,7)      | [6,7)
+ [100,101) | ["Wed Jan 01 00:00:00 2020","Fri Jan 01 00:00:00 2021") | [6,7)      | [6,7)
+(3 rows)
+
+-- non-FPO delete:
+DELETE FROM temporal_rng2 WHERE id1 = '[6,6]';
+SELECT * FROM temporal_fk2_rng2rng WHERE id = '[100,100]';
+    id     |                        valid_at                         | parent_id1 | parent_id2 
+-----------+---------------------------------------------------------+------------+------------
+ [100,101) | ["Tue Jan 01 00:00:00 2019","Wed Jan 01 00:00:00 2020") |            | [6,7)
+ [100,101) | ["Mon Jan 01 00:00:00 2018","Tue Jan 01 00:00:00 2019") |            | [6,7)
+ [100,101) | ["Wed Jan 01 00:00:00 2020","Fri Jan 01 00:00:00 2021") |            | [6,7)
+(3 rows)
+
+-- FK across two referenced rows:
+INSERT INTO temporal_rng2 VALUES ('[8,8]', '[8,8]', tsrange('2018-01-01', '2020-01-01'));
+INSERT INTO temporal_rng2 VALUES ('[8,8]', '[8,8]', tsrange('2020-01-01', '2021-01-01'));
+INSERT INTO temporal_fk2_rng2rng VALUES ('[200,200]', tsrange('2018-01-01', '2021-01-01'), '[8,8]', '[8,8]');
+DELETE FROM temporal_rng2 WHERE id1 = '[8,8]' AND valid_at @> '2019-01-01'::timestamp;
+SELECT * FROM temporal_fk2_rng2rng WHERE id = '[200,200]';
+    id     |                        valid_at                         | parent_id1 | parent_id2 
+-----------+---------------------------------------------------------+------------+------------
+ [200,201) | ["Mon Jan 01 00:00:00 2018","Wed Jan 01 00:00:00 2020") |            | [8,9)
+ [200,201) | ["Wed Jan 01 00:00:00 2020","Fri Jan 01 00:00:00 2021") | [8,9)      | [8,9)
+(2 rows)
+
+-- clean up
+DELETE FROM temporal_fk2_rng2rng WHERE id IN ('[100,100]', '[200,200]');
+DELETE FROM temporal_rng2 WHERE id1 IN ('[6,6]', '[7,7]', '[8,8]', '[9,9]');
+-- test FK parent updates SET DEFAULT (two scalar cols)
+INSERT INTO temporal_rng2 VALUES ('[-1,-1]', '[-1,-1]', tsrange(null, null));
+INSERT INTO temporal_rng2 VALUES ('[6,6]', '[6,6]', tsrange('2018-01-01', '2021-01-01'));
+INSERT INTO temporal_fk2_rng2rng VALUES ('[100,100]', tsrange('2018-01-01', '2021-01-01'), '[6,6]', '[6,6]');
+ALTER TABLE temporal_fk2_rng2rng
+  ALTER COLUMN parent_id1 SET DEFAULT '[-1,-1]',
+  ALTER COLUMN parent_id2 SET DEFAULT '[-1,-1]',
+	DROP CONSTRAINT temporal_fk2_rng2rng_fk,
+	ADD CONSTRAINT temporal_fk2_rng2rng_fk
+		FOREIGN KEY (parent_id1, parent_id2, PERIOD valid_at)
+		REFERENCES temporal_rng2
+		ON DELETE SET DEFAULT ON UPDATE SET DEFAULT;
+-- leftovers on both sides:
+UPDATE temporal_rng2 FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' SET id1 = '[7,7]', id2 = '[7,7]' WHERE id1 = '[6,6]';
+SELECT * FROM temporal_fk2_rng2rng WHERE id = '[100,100]';
+    id     |                        valid_at                         | parent_id1 | parent_id2 
+-----------+---------------------------------------------------------+------------+------------
+ [100,101) | ["Tue Jan 01 00:00:00 2019","Wed Jan 01 00:00:00 2020") | [-1,0)     | [-1,0)
+ [100,101) | ["Mon Jan 01 00:00:00 2018","Tue Jan 01 00:00:00 2019") | [6,7)      | [6,7)
+ [100,101) | ["Wed Jan 01 00:00:00 2020","Fri Jan 01 00:00:00 2021") | [6,7)      | [6,7)
+(3 rows)
+
+-- non-FPO update:
+UPDATE temporal_rng2 SET id1 = '[7,7]', id2 = '[7,7]' WHERE id1 = '[6,6]';
+SELECT * FROM temporal_fk2_rng2rng WHERE id = '[100,100]';
+    id     |                        valid_at                         | parent_id1 | parent_id2 
+-----------+---------------------------------------------------------+------------+------------
+ [100,101) | ["Tue Jan 01 00:00:00 2019","Wed Jan 01 00:00:00 2020") | [-1,0)     | [-1,0)
+ [100,101) | ["Mon Jan 01 00:00:00 2018","Tue Jan 01 00:00:00 2019") | [-1,0)     | [-1,0)
+ [100,101) | ["Wed Jan 01 00:00:00 2020","Fri Jan 01 00:00:00 2021") | [-1,0)     | [-1,0)
+(3 rows)
+
+-- FK across two referenced rows:
+INSERT INTO temporal_rng2 VALUES ('[8,8]', '[8,8]', tsrange('2018-01-01', '2020-01-01'));
+INSERT INTO temporal_rng2 VALUES ('[8,8]', '[8,8]', tsrange('2020-01-01', '2021-01-01'));
+INSERT INTO temporal_fk2_rng2rng VALUES ('[200,200]', tsrange('2018-01-01', '2021-01-01'), '[8,8]', '[8,8]');
+UPDATE temporal_rng2 SET id1 = '[9,9]', id2 = '[9,9]' WHERE id1 = '[8,8]' AND valid_at @> '2019-01-01'::timestamp;
+SELECT * FROM temporal_fk2_rng2rng WHERE id = '[200,200]';
+    id     |                        valid_at                         | parent_id1 | parent_id2 
+-----------+---------------------------------------------------------+------------+------------
+ [200,201) | ["Mon Jan 01 00:00:00 2018","Wed Jan 01 00:00:00 2020") | [-1,0)     | [-1,0)
+ [200,201) | ["Wed Jan 01 00:00:00 2020","Fri Jan 01 00:00:00 2021") | [8,9)      | [8,9)
+(2 rows)
+
+-- clean up
+DELETE FROM temporal_fk2_rng2rng WHERE id IN ('[100,100]', '[200,200]');
+DELETE FROM temporal_rng2 WHERE id1 IN ('[6,6]', '[7,7]', '[8,8]', '[9,9]');
+-- test FK parent deletes SET DEFAULT (two scalar cols)
+INSERT INTO temporal_rng2 VALUES ('[6,6]', '[6,6]', tsrange('2018-01-01', '2021-01-01'));
+INSERT INTO temporal_fk2_rng2rng VALUES ('[100,100]', tsrange('2018-01-01', '2021-01-01'), '[6,6]', '[6,6]');
+-- leftovers on both sides:
+DELETE FROM temporal_rng2 FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' WHERE id1 = '[6,6]';
+SELECT * FROM temporal_fk2_rng2rng WHERE id = '[100,100]';
+    id     |                        valid_at                         | parent_id1 | parent_id2 
+-----------+---------------------------------------------------------+------------+------------
+ [100,101) | ["Tue Jan 01 00:00:00 2019","Wed Jan 01 00:00:00 2020") | [-1,0)     | [-1,0)
+ [100,101) | ["Mon Jan 01 00:00:00 2018","Tue Jan 01 00:00:00 2019") | [6,7)      | [6,7)
+ [100,101) | ["Wed Jan 01 00:00:00 2020","Fri Jan 01 00:00:00 2021") | [6,7)      | [6,7)
+(3 rows)
+
+-- non-FPO update:
+DELETE FROM temporal_rng2 WHERE id1 = '[6,6]';
+SELECT * FROM temporal_fk2_rng2rng WHERE id = '[100,100]';
+    id     |                        valid_at                         | parent_id1 | parent_id2 
+-----------+---------------------------------------------------------+------------+------------
+ [100,101) | ["Tue Jan 01 00:00:00 2019","Wed Jan 01 00:00:00 2020") | [-1,0)     | [-1,0)
+ [100,101) | ["Mon Jan 01 00:00:00 2018","Tue Jan 01 00:00:00 2019") | [-1,0)     | [-1,0)
+ [100,101) | ["Wed Jan 01 00:00:00 2020","Fri Jan 01 00:00:00 2021") | [-1,0)     | [-1,0)
+(3 rows)
+
+-- FK across two referenced rows:
+INSERT INTO temporal_rng2 VALUES ('[8,8]', '[8,8]', tsrange('2018-01-01', '2020-01-01'));
+INSERT INTO temporal_rng2 VALUES ('[8,8]', '[8,8]', tsrange('2020-01-01', '2021-01-01'));
+INSERT INTO temporal_fk2_rng2rng VALUES ('[200,200]', tsrange('2018-01-01', '2021-01-01'), '[8,8]', '[8,8]');
+DELETE FROM temporal_rng2 WHERE id1 = '[8,8]' AND valid_at @> '2019-01-01'::timestamp;
+SELECT * FROM temporal_fk2_rng2rng WHERE id = '[200,200]';
+    id     |                        valid_at                         | parent_id1 | parent_id2 
+-----------+---------------------------------------------------------+------------+------------
+ [200,201) | ["Mon Jan 01 00:00:00 2018","Wed Jan 01 00:00:00 2020") | [-1,0)     | [-1,0)
+ [200,201) | ["Wed Jan 01 00:00:00 2020","Fri Jan 01 00:00:00 2021") | [8,9)      | [8,9)
+(2 rows)
+
+-- clean up
+DELETE FROM temporal_fk2_rng2rng WHERE id IN ('[100,100]', '[200,200]');
+DELETE FROM temporal_rng2 WHERE id1 IN ('[6,6]', '[7,7]', '[8,8]', '[9,9]');
+-- test FK parent deletes SET DEFAULT (two scalar cols, SET DEFAULT subset)
+INSERT INTO temporal_rng2 VALUES ('[-1,-1]', '[6,6]', tsrange(null, null));
+INSERT INTO temporal_rng2 VALUES ('[6,6]', '[6,6]', tsrange('2018-01-01', '2021-01-01'));
+INSERT INTO temporal_fk2_rng2rng VALUES ('[100,100]', tsrange('2018-01-01', '2021-01-01'), '[6,6]', '[6,6]');
+-- fails because you can't set the PERIOD column:
+ALTER TABLE temporal_fk2_rng2rng
+  ALTER COLUMN parent_id1 SET DEFAULT '[-1,-1]',
+	DROP CONSTRAINT temporal_fk2_rng2rng_fk,
+	ADD CONSTRAINT temporal_fk2_rng2rng_fk
+		FOREIGN KEY (parent_id1, parent_id2, PERIOD valid_at)
+		REFERENCES temporal_rng2
+		ON DELETE SET DEFAULT (valid_at) ON UPDATE SET DEFAULT;
+ERROR:  column "valid_at" referenced in ON DELETE SET action cannot be PERIOD
+-- ok:
+ALTER TABLE temporal_fk2_rng2rng
+  ALTER COLUMN parent_id1 SET DEFAULT '[-1,-1]',
+	DROP CONSTRAINT temporal_fk2_rng2rng_fk,
+	ADD CONSTRAINT temporal_fk2_rng2rng_fk
+		FOREIGN KEY (parent_id1, parent_id2, PERIOD valid_at)
+		REFERENCES temporal_rng2
+		ON DELETE SET DEFAULT (parent_id1) ON UPDATE SET DEFAULT;
+-- leftovers on both sides:
+DELETE FROM temporal_rng2 FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' WHERE id1 = '[6,6]';
+SELECT * FROM temporal_fk2_rng2rng WHERE id = '[100,100]';
+    id     |                        valid_at                         | parent_id1 | parent_id2 
+-----------+---------------------------------------------------------+------------+------------
+ [100,101) | ["Tue Jan 01 00:00:00 2019","Wed Jan 01 00:00:00 2020") | [-1,0)     | [6,7)
+ [100,101) | ["Mon Jan 01 00:00:00 2018","Tue Jan 01 00:00:00 2019") | [6,7)      | [6,7)
+ [100,101) | ["Wed Jan 01 00:00:00 2020","Fri Jan 01 00:00:00 2021") | [6,7)      | [6,7)
+(3 rows)
+
+-- non-FPO update:
+DELETE FROM temporal_rng2 WHERE id1 = '[6,6]';
+SELECT * FROM temporal_fk2_rng2rng WHERE id = '[100,100]';
+    id     |                        valid_at                         | parent_id1 | parent_id2 
+-----------+---------------------------------------------------------+------------+------------
+ [100,101) | ["Tue Jan 01 00:00:00 2019","Wed Jan 01 00:00:00 2020") | [-1,0)     | [6,7)
+ [100,101) | ["Mon Jan 01 00:00:00 2018","Tue Jan 01 00:00:00 2019") | [-1,0)     | [6,7)
+ [100,101) | ["Wed Jan 01 00:00:00 2020","Fri Jan 01 00:00:00 2021") | [-1,0)     | [6,7)
+(3 rows)
+
+-- FK across two referenced rows:
+INSERT INTO temporal_rng2 VALUES ('[8,8]', '[8,8]', tsrange('2018-01-01', '2020-01-01'));
+INSERT INTO temporal_rng2 VALUES ('[8,8]', '[8,8]', tsrange('2020-01-01', '2021-01-01'));
+INSERT INTO temporal_rng2 VALUES ('[-1,-1]', '[8,8]', tsrange(null, null));
+INSERT INTO temporal_fk2_rng2rng VALUES ('[200,200]', tsrange('2018-01-01', '2021-01-01'), '[8,8]', '[8,8]');
+DELETE FROM temporal_rng2 WHERE id1 = '[8,8]' AND valid_at @> '2019-01-01'::timestamp;
+SELECT * FROM temporal_fk2_rng2rng WHERE id = '[200,200]';
+    id     |                        valid_at                         | parent_id1 | parent_id2 
+-----------+---------------------------------------------------------+------------+------------
+ [200,201) | ["Mon Jan 01 00:00:00 2018","Wed Jan 01 00:00:00 2020") | [-1,0)     | [8,9)
+ [200,201) | ["Wed Jan 01 00:00:00 2020","Fri Jan 01 00:00:00 2021") | [8,9)      | [8,9)
+(2 rows)
+
+-- clean up
+DELETE FROM temporal_fk2_rng2rng WHERE id IN ('[100,100]', '[200,200]');
+DELETE FROM temporal_rng2 WHERE id1 IN ('[6,6]', '[7,7]', '[8,8]', '[9,9]');
+-- FK with a custom range type
+CREATE TYPE mydaterange AS range(subtype=date);
+CREATE TABLE temporal_rng3 (
+	id int4range,
+	valid_at mydaterange,
+	CONSTRAINT temporal_rng3_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+CREATE TABLE temporal_fk3_rng2rng (
+	id int4range,
+	valid_at mydaterange,
+	parent_id int4range,
+	CONSTRAINT temporal_fk3_rng2rng_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS),
+	CONSTRAINT temporal_fk3_rng2rng_fk FOREIGN KEY (parent_id, PERIOD valid_at)
+		REFERENCES temporal_rng3 (id, PERIOD valid_at) ON DELETE CASCADE
+);
+INSERT INTO temporal_rng3 VALUES ('[8,8]', mydaterange('2018-01-01', '2021-01-01'));
+INSERT INTO temporal_fk3_rng2rng VALUES ('[5,5]', mydaterange('2018-01-01', '2021-01-01'), '[8,8]');
+DELETE FROM temporal_rng3 FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' WHERE id = '[8,8]';
+SELECT * FROM temporal_fk3_rng2rng WHERE id = '[5,5]';
+  id   |        valid_at         | parent_id 
+-------+-------------------------+-----------
+ [5,6) | [01-01-2018,01-01-2019) | [8,9)
+ [5,6) | [01-01-2020,01-01-2021) | [8,9)
+(2 rows)
+
+DROP TABLE temporal_fk3_rng2rng;
+DROP TABLE temporal_rng3;
+DROP TYPE mydaterange;
 -- FK between partitioned tables
 CREATE TABLE temporal_partitioned_rng (
 	id int4range,
@@ -943,8 +1551,8 @@ CREATE TABLE temporal_partitioned_rng (
   name text,
 	CONSTRAINT temporal_paritioned_rng_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
 ) PARTITION BY LIST (id);
-CREATE TABLE tp1 partition OF temporal_partitioned_rng FOR VALUES IN ('[1,1]', '[3,3]', '[5,5]', '[7,7]', '[9,9]', '[11,11]');
-CREATE TABLE tp2 partition OF temporal_partitioned_rng FOR VALUES IN ('[2,2]', '[4,4]', '[6,6]', '[8,8]', '[10,10]', '[12,12]');
+CREATE TABLE tp1 partition OF temporal_partitioned_rng FOR VALUES IN ('[1,1]', '[3,3]', '[5,5]', '[7,7]', '[9,9]', '[11,11]', '[13,13]', '[15,15]', '[17,17]', '[19,19]', '[21,21]', '[23,23]');
+CREATE TABLE tp2 partition OF temporal_partitioned_rng FOR VALUES IN ('[0,0]', '[2,2]', '[4,4]', '[6,6]', '[8,8]', '[10,10]', '[12,12]', '[14,14]', '[16,16]', '[18,18]', '[20,20]', '[22,22]', '[24,24]');
 INSERT INTO temporal_partitioned_rng VALUES
   ('[1,1]', daterange('2000-01-01', '2000-02-01'), 'one'),
   ('[1,1]', daterange('2000-02-01', '2000-03-01'), 'one'),
@@ -957,8 +1565,8 @@ CREATE TABLE temporal_partitioned_fk_rng2rng (
 	CONSTRAINT temporal_partitioned_fk_rng2rng_fk FOREIGN KEY (parent_id, PERIOD valid_at)
 		REFERENCES temporal_partitioned_rng (id, PERIOD valid_at)
 ) PARTITION BY LIST (id);
-CREATE TABLE tfkp1 partition OF temporal_partitioned_fk_rng2rng FOR VALUES IN ('[1,1]', '[3,3]', '[5,5]', '[7,7]', '[9,9]', '[11,11]');
-CREATE TABLE tfkp2 partition OF temporal_partitioned_fk_rng2rng FOR VALUES IN ('[2,2]', '[4,4]', '[6,6]', '[8,8]', '[10,10]', '[12,12]');
+CREATE TABLE tfkp1 partition OF temporal_partitioned_fk_rng2rng FOR VALUES IN ('[1,1]', '[3,3]', '[5,5]', '[7,7]', '[9,9]', '[11,11]', '[13,13]', '[15,15]', '[17,17]', '[19,19]', '[21,21]', '[23,23]');
+CREATE TABLE tfkp2 partition OF temporal_partitioned_fk_rng2rng FOR VALUES IN ('[0,0]', '[2,2]', '[4,4]', '[6,6]', '[8,8]', '[10,10]', '[12,12]', '[14,14]', '[16,16]', '[18,18]', '[20,20]', '[22,22]', '[24,24]');
 -- partitioned FK child inserts
 INSERT INTO temporal_partitioned_fk_rng2rng VALUES
   ('[1,1]', daterange('2000-01-01', '2000-02-15'), '[1,1]'),
@@ -993,7 +1601,7 @@ UPDATE temporal_partitioned_rng SET valid_at = daterange('2016-02-01', '2016-03-
 -- should fail:
 UPDATE temporal_partitioned_rng SET valid_at = daterange('2016-01-01', '2016-02-01')
   WHERE id = '[5,5]' AND valid_at = daterange('2018-01-01', '2018-02-01');
-ERROR:  update or delete on table "tp1" violates foreign key constraint "temporal_partitioned_fk_rng2rng_parent_id_fkey" on table "temporal_partitioned_fk_rng2rng"
+ERROR:  update or delete on table "tp1" violates foreign key constraint "temporal_partitioned_fk_rng2rng_parent_id_fkey1" on table "temporal_partitioned_fk_rng2rng"
 DETAIL:  Key (id, valid_at)=([5,6), [01-01-2018,02-01-2018)) is still referenced from table "temporal_partitioned_fk_rng2rng".
 -- clean up:
 DELETE FROM temporal_partitioned_fk_rng2rng WHERE parent_id = '[5,5]';
@@ -1005,7 +1613,7 @@ INSERT INTO temporal_partitioned_fk_rng2rng VALUES ('[3,3]', daterange('2018-01-
 DELETE FROM temporal_partitioned_rng WHERE id = '[5,5]' AND valid_at = daterange('2018-02-01', '2018-03-01');
 -- should fail:
 DELETE FROM temporal_partitioned_rng WHERE id = '[5,5]' AND valid_at = daterange('2018-01-01', '2018-02-01');
-ERROR:  update or delete on table "tp1" violates foreign key constraint "temporal_partitioned_fk_rng2rng_parent_id_fkey" on table "temporal_partitioned_fk_rng2rng"
+ERROR:  update or delete on table "tp1" violates foreign key constraint "temporal_partitioned_fk_rng2rng_parent_id_fkey1" on table "temporal_partitioned_fk_rng2rng"
 DETAIL:  Key (id, valid_at)=([5,6), [01-01-2018,02-01-2018)) is still referenced from table "temporal_partitioned_fk_rng2rng".
 -- clean up:
 DELETE FROM temporal_partitioned_fk_rng2rng WHERE parent_id = '[5,5]';
@@ -1027,7 +1635,7 @@ UPDATE temporal_partitioned_rng SET valid_at = daterange('2016-02-01', '2016-03-
 -- should fail:
 UPDATE temporal_partitioned_rng SET valid_at = daterange('2016-01-01', '2016-02-01')
   WHERE id = '[5,5]' AND valid_at = daterange('2018-01-01', '2018-02-01');
-ERROR:  update or delete on table "tp1" violates foreign key constraint "temporal_partitioned_fk_rng2rng_parent_id_fkey" on table "temporal_partitioned_fk_rng2rng"
+ERROR:  update or delete on table "tp1" violates foreign key constraint "temporal_partitioned_fk_rng2rng_parent_id_fkey1" on table "temporal_partitioned_fk_rng2rng"
 DETAIL:  Key (id, valid_at)=([5,6), [01-01-2018,02-01-2018)) is still referenced from table "temporal_partitioned_fk_rng2rng".
 -- clean up:
 DELETE FROM temporal_partitioned_fk_rng2rng WHERE parent_id = '[5,5]';
@@ -1039,35 +1647,214 @@ INSERT INTO temporal_partitioned_fk_rng2rng VALUES ('[3,3]', daterange('2018-01-
 DELETE FROM temporal_partitioned_rng WHERE id = '[5,5]' AND valid_at = daterange('2018-02-01', '2018-03-01');
 -- should fail:
 DELETE FROM temporal_partitioned_rng WHERE id = '[5,5]' AND valid_at = daterange('2018-01-01', '2018-02-01');
-ERROR:  update or delete on table "tp1" violates foreign key constraint "temporal_partitioned_fk_rng2rng_parent_id_fkey" on table "temporal_partitioned_fk_rng2rng"
+ERROR:  update or delete on table "tp1" violates foreign key constraint "temporal_partitioned_fk_rng2rng_parent_id_fkey1" on table "temporal_partitioned_fk_rng2rng"
 DETAIL:  Key (id, valid_at)=([5,6), [01-01-2018,02-01-2018)) is still referenced from table "temporal_partitioned_fk_rng2rng".
 -- partitioned FK parent updates CASCADE
+INSERT INTO temporal_partitioned_rng VALUES ('[6,6]', daterange('2018-01-01', '2021-01-01'));
+INSERT INTO temporal_partitioned_fk_rng2rng VALUES ('[4,4]', daterange('2018-01-01', '2021-01-01'), '[6,6]');
 ALTER TABLE temporal_partitioned_fk_rng2rng
 	DROP CONSTRAINT temporal_partitioned_fk_rng2rng_fk,
 	ADD CONSTRAINT temporal_partitioned_fk_rng2rng_fk
 		FOREIGN KEY (parent_id, PERIOD valid_at)
 		REFERENCES temporal_partitioned_rng
 		ON DELETE CASCADE ON UPDATE CASCADE;
-ERROR:  action not supported for temporal foreign keys
+UPDATE temporal_partitioned_rng FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' SET id = '[7,7]' WHERE id = '[6,6]';
+SELECT * FROM temporal_partitioned_fk_rng2rng WHERE id = '[4,4]';
+  id   |        valid_at         | parent_id 
+-------+-------------------------+-----------
+ [4,5) | [01-01-2019,01-01-2020) | [7,8)
+ [4,5) | [01-01-2018,01-01-2019) | [6,7)
+ [4,5) | [01-01-2020,01-01-2021) | [6,7)
+(3 rows)
+
+UPDATE temporal_partitioned_rng SET id = '[7,7]' WHERE id = '[6,6]';
+SELECT * FROM temporal_partitioned_fk_rng2rng WHERE id = '[4,4]';
+  id   |        valid_at         | parent_id 
+-------+-------------------------+-----------
+ [4,5) | [01-01-2019,01-01-2020) | [7,8)
+ [4,5) | [01-01-2018,01-01-2019) | [7,8)
+ [4,5) | [01-01-2020,01-01-2021) | [7,8)
+(3 rows)
+
+INSERT INTO temporal_partitioned_rng VALUES ('[15,15]', daterange('2018-01-01', '2020-01-01'));
+INSERT INTO temporal_partitioned_rng VALUES ('[15,15]', daterange('2020-01-01', '2021-01-01'));
+INSERT INTO temporal_partitioned_fk_rng2rng VALUES ('[10,10]', daterange('2018-01-01', '2021-01-01'), '[15,15]');
+UPDATE temporal_partitioned_rng SET id = '[16,16]' WHERE id = '[15,15]' AND valid_at @> '2019-01-01'::date;
+SELECT * FROM temporal_partitioned_fk_rng2rng WHERE id = '[10,10]';
+   id    |        valid_at         | parent_id 
+---------+-------------------------+-----------
+ [10,11) | [01-01-2018,01-01-2020) | [16,17)
+ [10,11) | [01-01-2020,01-01-2021) | [15,16)
+(2 rows)
+
 -- partitioned FK parent deletes CASCADE
+INSERT INTO temporal_partitioned_rng VALUES ('[8,8]', daterange('2018-01-01', '2021-01-01'));
+INSERT INTO temporal_partitioned_fk_rng2rng VALUES ('[5,5]', daterange('2018-01-01', '2021-01-01'), '[8,8]');
+DELETE FROM temporal_partitioned_rng FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' WHERE id = '[8,8]';
+SELECT * FROM temporal_partitioned_fk_rng2rng WHERE id = '[5,5]';
+  id   |        valid_at         | parent_id 
+-------+-------------------------+-----------
+ [5,6) | [01-01-2018,01-01-2019) | [8,9)
+ [5,6) | [01-01-2020,01-01-2021) | [8,9)
+(2 rows)
+
+DELETE FROM temporal_partitioned_rng WHERE id = '[8,8]';
+SELECT * FROM temporal_partitioned_fk_rng2rng WHERE id = '[5,5]';
+ id | valid_at | parent_id 
+----+----------+-----------
+(0 rows)
+
+INSERT INTO temporal_partitioned_rng VALUES ('[17,17]', daterange('2018-01-01', '2020-01-01'));
+INSERT INTO temporal_partitioned_rng VALUES ('[17,17]', daterange('2020-01-01', '2021-01-01'));
+INSERT INTO temporal_partitioned_fk_rng2rng VALUES ('[11,11]', daterange('2018-01-01', '2021-01-01'), '[17,17]');
+DELETE FROM temporal_partitioned_rng WHERE id = '[17,17]' AND valid_at @> '2019-01-01'::date;
+SELECT * FROM temporal_partitioned_fk_rng2rng WHERE id = '[11,11]';
+   id    |        valid_at         | parent_id 
+---------+-------------------------+-----------
+ [11,12) | [01-01-2020,01-01-2021) | [17,18)
+(1 row)
+
 -- partitioned FK parent updates SET NULL
+INSERT INTO temporal_partitioned_rng VALUES ('[9,9]', daterange('2018-01-01', '2021-01-01'));
+INSERT INTO temporal_partitioned_fk_rng2rng VALUES ('[6,6]', daterange('2018-01-01', '2021-01-01'), '[9,9]');
 ALTER TABLE temporal_partitioned_fk_rng2rng
 	DROP CONSTRAINT temporal_partitioned_fk_rng2rng_fk,
 	ADD CONSTRAINT temporal_partitioned_fk_rng2rng_fk
 		FOREIGN KEY (parent_id, PERIOD valid_at)
 		REFERENCES temporal_partitioned_rng
 		ON DELETE SET NULL ON UPDATE SET NULL;
-ERROR:  action not supported for temporal foreign keys
+UPDATE temporal_partitioned_rng FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' SET id = '[10,10]' WHERE id = '[9,9]';
+SELECT * FROM temporal_partitioned_fk_rng2rng WHERE id = '[6,6]';
+  id   |        valid_at         | parent_id 
+-------+-------------------------+-----------
+ [6,7) | [01-01-2019,01-01-2020) | 
+ [6,7) | [01-01-2018,01-01-2019) | [9,10)
+ [6,7) | [01-01-2020,01-01-2021) | [9,10)
+(3 rows)
+
+UPDATE temporal_partitioned_rng SET id = '[10,10]' WHERE id = '[9,9]';
+SELECT * FROM temporal_partitioned_fk_rng2rng WHERE id = '[6,6]';
+  id   |        valid_at         | parent_id 
+-------+-------------------------+-----------
+ [6,7) | [01-01-2019,01-01-2020) | 
+ [6,7) | [01-01-2018,01-01-2019) | 
+ [6,7) | [01-01-2020,01-01-2021) | 
+(3 rows)
+
+INSERT INTO temporal_partitioned_rng VALUES ('[18,18]', daterange('2018-01-01', '2020-01-01'));
+INSERT INTO temporal_partitioned_rng VALUES ('[18,18]', daterange('2020-01-01', '2021-01-01'));
+INSERT INTO temporal_partitioned_fk_rng2rng VALUES ('[12,12]', daterange('2018-01-01', '2021-01-01'), '[18,18]');
+UPDATE temporal_partitioned_rng SET id = '[19,19]' WHERE id = '[18,18]' AND valid_at @> '2019-01-01'::date;
+SELECT * FROM temporal_partitioned_fk_rng2rng WHERE id = '[12,12]';
+   id    |        valid_at         | parent_id 
+---------+-------------------------+-----------
+ [12,13) | [01-01-2018,01-01-2020) | 
+ [12,13) | [01-01-2020,01-01-2021) | [18,19)
+(2 rows)
+
 -- partitioned FK parent deletes SET NULL
+INSERT INTO temporal_partitioned_rng VALUES ('[11,11]', daterange('2018-01-01', '2021-01-01'));
+INSERT INTO temporal_partitioned_fk_rng2rng VALUES ('[7,7]', daterange('2018-01-01', '2021-01-01'), '[11,11]');
+DELETE FROM temporal_partitioned_rng FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' WHERE id = '[11,11]';
+SELECT * FROM temporal_partitioned_fk_rng2rng WHERE id = '[7,7]';
+  id   |        valid_at         | parent_id 
+-------+-------------------------+-----------
+ [7,8) | [01-01-2019,01-01-2020) | 
+ [7,8) | [01-01-2018,01-01-2019) | [11,12)
+ [7,8) | [01-01-2020,01-01-2021) | [11,12)
+(3 rows)
+
+DELETE FROM temporal_partitioned_rng WHERE id = '[11,11]';
+SELECT * FROM temporal_partitioned_fk_rng2rng WHERE id = '[7,7]';
+  id   |        valid_at         | parent_id 
+-------+-------------------------+-----------
+ [7,8) | [01-01-2019,01-01-2020) | 
+ [7,8) | [01-01-2018,01-01-2019) | 
+ [7,8) | [01-01-2020,01-01-2021) | 
+(3 rows)
+
+INSERT INTO temporal_partitioned_rng VALUES ('[20,20]', daterange('2018-01-01', '2020-01-01'));
+INSERT INTO temporal_partitioned_rng VALUES ('[20,20]', daterange('2020-01-01', '2021-01-01'));
+INSERT INTO temporal_partitioned_fk_rng2rng VALUES ('[13,13]', daterange('2018-01-01', '2021-01-01'), '[20,20]');
+DELETE FROM temporal_partitioned_rng WHERE id = '[20,20]' AND valid_at @> '2019-01-01'::date;
+SELECT * FROM temporal_partitioned_fk_rng2rng WHERE id = '[13,13]';
+   id    |        valid_at         | parent_id 
+---------+-------------------------+-----------
+ [13,14) | [01-01-2018,01-01-2020) | 
+ [13,14) | [01-01-2020,01-01-2021) | [20,21)
+(2 rows)
+
 -- partitioned FK parent updates SET DEFAULT
+INSERT INTO temporal_partitioned_rng VALUES ('[0,0]', daterange(null, null));
+INSERT INTO temporal_partitioned_rng VALUES ('[12,12]', daterange('2018-01-01', '2021-01-01'));
+INSERT INTO temporal_partitioned_fk_rng2rng VALUES ('[8,8]', daterange('2018-01-01', '2021-01-01'), '[12,12]');
 ALTER TABLE temporal_partitioned_fk_rng2rng
-  ALTER COLUMN parent_id SET DEFAULT '[-1,-1]',
+  ALTER COLUMN parent_id SET DEFAULT '[0,0]',
 	DROP CONSTRAINT temporal_partitioned_fk_rng2rng_fk,
 	ADD CONSTRAINT temporal_partitioned_fk_rng2rng_fk
 		FOREIGN KEY (parent_id, PERIOD valid_at)
 		REFERENCES temporal_partitioned_rng
 		ON DELETE SET DEFAULT ON UPDATE SET DEFAULT;
-ERROR:  action not supported for temporal foreign keys
+UPDATE temporal_partitioned_rng FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' SET id = '[13,13]' WHERE id = '[12,12]';
+SELECT * FROM temporal_partitioned_fk_rng2rng WHERE id = '[8,8]';
+  id   |        valid_at         | parent_id 
+-------+-------------------------+-----------
+ [8,9) | [01-01-2019,01-01-2020) | [0,1)
+ [8,9) | [01-01-2018,01-01-2019) | [12,13)
+ [8,9) | [01-01-2020,01-01-2021) | [12,13)
+(3 rows)
+
+UPDATE temporal_partitioned_rng SET id = '[13,13]' WHERE id = '[12,12]';
+SELECT * FROM temporal_partitioned_fk_rng2rng WHERE id = '[8,8]';
+  id   |        valid_at         | parent_id 
+-------+-------------------------+-----------
+ [8,9) | [01-01-2019,01-01-2020) | [0,1)
+ [8,9) | [01-01-2018,01-01-2019) | [0,1)
+ [8,9) | [01-01-2020,01-01-2021) | [0,1)
+(3 rows)
+
+INSERT INTO temporal_partitioned_rng VALUES ('[22,22]', daterange('2018-01-01', '2020-01-01'));
+INSERT INTO temporal_partitioned_rng VALUES ('[22,22]', daterange('2020-01-01', '2021-01-01'));
+INSERT INTO temporal_partitioned_fk_rng2rng VALUES ('[14,14]', daterange('2018-01-01', '2021-01-01'), '[22,22]');
+UPDATE temporal_partitioned_rng SET id = '[23,23]' WHERE id = '[22,22]' AND valid_at @> '2019-01-01'::date;
+SELECT * FROM temporal_partitioned_fk_rng2rng WHERE id = '[14,14]';
+   id    |        valid_at         | parent_id 
+---------+-------------------------+-----------
+ [14,15) | [01-01-2018,01-01-2020) | [0,1)
+ [14,15) | [01-01-2020,01-01-2021) | [22,23)
+(2 rows)
+
 -- partitioned FK parent deletes SET DEFAULT
+INSERT INTO temporal_partitioned_rng VALUES ('[14,14]', daterange('2018-01-01', '2021-01-01'));
+INSERT INTO temporal_partitioned_fk_rng2rng VALUES ('[9,9]', daterange('2018-01-01', '2021-01-01'), '[14,14]');
+DELETE FROM temporal_partitioned_rng FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' WHERE id = '[14,14]';
+SELECT * FROM temporal_partitioned_fk_rng2rng WHERE id = '[9,9]';
+   id   |        valid_at         | parent_id 
+--------+-------------------------+-----------
+ [9,10) | [01-01-2019,01-01-2020) | [0,1)
+ [9,10) | [01-01-2018,01-01-2019) | [14,15)
+ [9,10) | [01-01-2020,01-01-2021) | [14,15)
+(3 rows)
+
+DELETE FROM temporal_partitioned_rng WHERE id = '[14,14]';
+SELECT * FROM temporal_partitioned_fk_rng2rng WHERE id = '[9,9]';
+   id   |        valid_at         | parent_id 
+--------+-------------------------+-----------
+ [9,10) | [01-01-2019,01-01-2020) | [0,1)
+ [9,10) | [01-01-2018,01-01-2019) | [0,1)
+ [9,10) | [01-01-2020,01-01-2021) | [0,1)
+(3 rows)
+
+INSERT INTO temporal_partitioned_rng VALUES ('[24,24]', daterange('2018-01-01', '2020-01-01'));
+INSERT INTO temporal_partitioned_rng VALUES ('[24,24]', daterange('2020-01-01', '2021-01-01'));
+INSERT INTO temporal_partitioned_fk_rng2rng VALUES ('[15,15]', daterange('2018-01-01', '2021-01-01'), '[24,24]');
+DELETE FROM temporal_partitioned_rng WHERE id = '[24,24]' AND valid_at @> '2019-01-01'::date;
+SELECT * FROM temporal_partitioned_fk_rng2rng WHERE id = '[15,15]';
+   id    |        valid_at         | parent_id 
+---------+-------------------------+-----------
+ [15,16) | [01-01-2018,01-01-2020) | [0,1)
+ [15,16) | [01-01-2020,01-01-2021) | [24,25)
+(2 rows)
+
 DROP TABLE temporal_partitioned_fk_rng2rng;
 DROP TABLE temporal_partitioned_rng;
diff --git a/src/test/regress/sql/without_overlaps.sql b/src/test/regress/sql/without_overlaps.sql
index 250ee71b1c..bdb6a6177c 100644
--- a/src/test/regress/sql/without_overlaps.sql
+++ b/src/test/regress/sql/without_overlaps.sql
@@ -718,31 +718,114 @@ DELETE FROM temporal_rng WHERE id IN ('[5,5]');
 --
 -- test ON UPDATE/DELETE options
 --
+-- TOC:
+-- parent updates CASCADE
+-- parent deletes CASCADE
+-- parent updates SET NULL
+-- parent deletes SET NULL
+-- parent updates SET DEFAULT
+-- parent deletes SET DEFAULT
+-- parent updates CASCADE (two scalar cols)
+-- parent deletes CASCADE (two scalar cols)
+-- parent updates SET NULL (two scalar cols)
+-- parent deletes SET NULL (two scalar cols)
+-- parent deletes SET NULL (two scalar cols, SET NULL subset)
+-- parent updates SET DEFAULT (two scalar cols)
+-- parent deletes SET DEFAULT (two scalar cols)
+-- parent deletes SET DEFAULT (two scalar cols, SET DEFAULT subset)
 
 -- test FK parent updates CASCADE
 INSERT INTO temporal_rng VALUES ('[6,6]', tsrange('2018-01-01', '2021-01-01'));
-INSERT INTO temporal_fk_rng2rng VALUES ('[4,4]', tsrange('2018-01-01', '2021-01-01'), '[6,6]');
+INSERT INTO temporal_fk_rng2rng VALUES ('[100,100]', tsrange('2018-01-01', '2021-01-01'), '[6,6]');
 ALTER TABLE temporal_fk_rng2rng
 	DROP CONSTRAINT temporal_fk_rng2rng_fk,
 	ADD CONSTRAINT temporal_fk_rng2rng_fk
 		FOREIGN KEY (parent_id, PERIOD valid_at)
 		REFERENCES temporal_rng
 		ON DELETE CASCADE ON UPDATE CASCADE;
+-- leftovers on both sides:
+UPDATE temporal_rng FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' SET id = '[7,7]' WHERE id = '[6,6]';
+SELECT * FROM temporal_fk_rng2rng WHERE id = '[100,100]';
+-- non-FPO update:
+UPDATE temporal_rng SET id = '[7,7]' WHERE id = '[6,6]';
+SELECT * FROM temporal_fk_rng2rng WHERE id = '[100,100]';
+-- FK across two referenced rows:
+INSERT INTO temporal_rng VALUES ('[8,8]', tsrange('2018-01-01', '2020-01-01'));
+INSERT INTO temporal_rng VALUES ('[8,8]', tsrange('2020-01-01', '2021-01-01'));
+INSERT INTO temporal_fk_rng2rng VALUES ('[200,200]', tsrange('2018-01-01', '2021-01-01'), '[8,8]');
+UPDATE temporal_rng SET id = '[9,9]' WHERE id = '[8,8]' AND valid_at @> '2019-01-01'::timestamp;
+SELECT * FROM temporal_fk_rng2rng WHERE id = '[200,200]';
+-- clean up
+DELETE FROM temporal_fk_rng2rng WHERE id IN ('[100,100]', '[200,200]');
+DELETE FROM temporal_rng WHERE id IN ('[6,6]', '[7,7]', '[8,8]', '[9,9]');
+
+-- test FK parent deletes CASCADE
+INSERT INTO temporal_rng VALUES ('[6,6]', tsrange('2018-01-01', '2021-01-01'));
+INSERT INTO temporal_fk_rng2rng VALUES ('[100,100]', tsrange('2018-01-01', '2021-01-01'), '[6,6]');
+-- leftovers on both sides:
+DELETE FROM temporal_rng FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' WHERE id = '[6,6]';
+SELECT * FROM temporal_fk_rng2rng WHERE id = '[100,100]';
+-- non-FPO delete:
+DELETE FROM temporal_rng WHERE id = '[6,6]';
+SELECT * FROM temporal_fk_rng2rng WHERE id = '[100,100]';
+-- FK across two referenced rows:
+INSERT INTO temporal_rng VALUES ('[8,8]', tsrange('2018-01-01', '2020-01-01'));
+INSERT INTO temporal_rng VALUES ('[8,8]', tsrange('2020-01-01', '2021-01-01'));
+INSERT INTO temporal_fk_rng2rng VALUES ('[200,200]', tsrange('2018-01-01', '2021-01-01'), '[8,8]');
+DELETE FROM temporal_rng WHERE id = '[8,8]' AND valid_at @> '2019-01-01'::timestamp;
+SELECT * FROM temporal_fk_rng2rng WHERE id = '[200,200]';
+-- clean up
+DELETE FROM temporal_fk_rng2rng WHERE id IN ('[100,100]', '[200,200]');
+DELETE FROM temporal_rng WHERE id IN ('[6,6]', '[7,7]', '[8,8]', '[9,9]');
 
 -- test FK parent updates SET NULL
-INSERT INTO temporal_rng VALUES ('[9,9]', tsrange('2018-01-01', '2021-01-01'));
-INSERT INTO temporal_fk_rng2rng VALUES ('[6,6]', tsrange('2018-01-01', '2021-01-01'), '[9,9]');
+INSERT INTO temporal_rng VALUES ('[6,6]', tsrange('2018-01-01', '2021-01-01'));
+INSERT INTO temporal_fk_rng2rng VALUES ('[100,100]', tsrange('2018-01-01', '2021-01-01'), '[6,6]');
 ALTER TABLE temporal_fk_rng2rng
 	DROP CONSTRAINT temporal_fk_rng2rng_fk,
 	ADD CONSTRAINT temporal_fk_rng2rng_fk
 		FOREIGN KEY (parent_id, PERIOD valid_at)
 		REFERENCES temporal_rng
 		ON DELETE SET NULL ON UPDATE SET NULL;
+-- leftovers on both sides:
+UPDATE temporal_rng FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' SET id = '[7,7]' WHERE id = '[6,6]';
+SELECT * FROM temporal_fk_rng2rng WHERE id = '[100,100]';
+-- non-FPO update:
+UPDATE temporal_rng SET id = '[7,7]' WHERE id = '[6,6]';
+SELECT * FROM temporal_fk_rng2rng WHERE id = '[100,100]';
+-- FK across two referenced rows:
+INSERT INTO temporal_rng VALUES ('[8,8]', tsrange('2018-01-01', '2020-01-01'));
+INSERT INTO temporal_rng VALUES ('[8,8]', tsrange('2020-01-01', '2021-01-01'));
+INSERT INTO temporal_fk_rng2rng VALUES ('[200,200]', tsrange('2018-01-01', '2021-01-01'), '[8,8]');
+UPDATE temporal_rng SET id = '[9,9]' WHERE id = '[8,8]' AND valid_at @> '2019-01-01'::timestamp;
+SELECT * FROM temporal_fk_rng2rng WHERE id = '[200,200]';
+-- clean up
+DELETE FROM temporal_fk_rng2rng WHERE id IN ('[100,100]', '[200,200]');
+DELETE FROM temporal_rng WHERE id IN ('[6,6]', '[7,7]', '[8,8]', '[9,9]');
+
+-- test FK parent deletes SET NULL
+INSERT INTO temporal_rng VALUES ('[6,6]', tsrange('2018-01-01', '2021-01-01'));
+INSERT INTO temporal_fk_rng2rng VALUES ('[100,100]', tsrange('2018-01-01', '2021-01-01'), '[6,6]');
+-- leftovers on both sides:
+DELETE FROM temporal_rng FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' WHERE id = '[6,6]';
+SELECT * FROM temporal_fk_rng2rng WHERE id = '[100,100]';
+-- non-FPO delete:
+DELETE FROM temporal_rng WHERE id = '[6,6]';
+SELECT * FROM temporal_fk_rng2rng WHERE id = '[100,100]';
+-- FK across two referenced rows:
+INSERT INTO temporal_rng VALUES ('[8,8]', tsrange('2018-01-01', '2020-01-01'));
+INSERT INTO temporal_rng VALUES ('[8,8]', tsrange('2020-01-01', '2021-01-01'));
+INSERT INTO temporal_fk_rng2rng VALUES ('[200,200]', tsrange('2018-01-01', '2021-01-01'), '[8,8]');
+DELETE FROM temporal_rng WHERE id = '[8,8]' AND valid_at @> '2019-01-01'::timestamp;
+SELECT * FROM temporal_fk_rng2rng WHERE id = '[200,200]';
+-- clean up
+DELETE FROM temporal_fk_rng2rng WHERE id IN ('[100,100]', '[200,200]');
+DELETE FROM temporal_rng WHERE id IN ('[6,6]', '[7,7]', '[8,8]', '[9,9]');
 
 -- test FK parent updates SET DEFAULT
 INSERT INTO temporal_rng VALUES ('[-1,-1]', tsrange(null, null));
-INSERT INTO temporal_rng VALUES ('[12,12]', tsrange('2018-01-01', '2021-01-01'));
-INSERT INTO temporal_fk_rng2rng VALUES ('[8,8]', tsrange('2018-01-01', '2021-01-01'), '[12,12]');
+INSERT INTO temporal_rng VALUES ('[6,6]', tsrange('2018-01-01', '2021-01-01'));
+INSERT INTO temporal_fk_rng2rng VALUES ('[100,100]', tsrange('2018-01-01', '2021-01-01'), '[6,6]');
 ALTER TABLE temporal_fk_rng2rng
   ALTER COLUMN parent_id SET DEFAULT '[-1,-1]',
 	DROP CONSTRAINT temporal_fk_rng2rng_fk,
@@ -750,6 +833,271 @@ ALTER TABLE temporal_fk_rng2rng
 		FOREIGN KEY (parent_id, PERIOD valid_at)
 		REFERENCES temporal_rng
 		ON DELETE SET DEFAULT ON UPDATE SET DEFAULT;
+-- leftovers on both sides:
+UPDATE temporal_rng FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' SET id = '[7,7]' WHERE id = '[6,6]';
+SELECT * FROM temporal_fk_rng2rng WHERE id = '[100,100]';
+-- non-FPO update:
+UPDATE temporal_rng SET id = '[7,7]' WHERE id = '[6,6]';
+SELECT * FROM temporal_fk_rng2rng WHERE id = '[100,100]';
+-- FK across two referenced rows:
+INSERT INTO temporal_rng VALUES ('[8,8]', tsrange('2018-01-01', '2020-01-01'));
+INSERT INTO temporal_rng VALUES ('[8,8]', tsrange('2020-01-01', '2021-01-01'));
+INSERT INTO temporal_fk_rng2rng VALUES ('[200,200]', tsrange('2018-01-01', '2021-01-01'), '[8,8]');
+UPDATE temporal_rng SET id = '[9,9]' WHERE id = '[8,8]' AND valid_at @> '2019-01-01'::timestamp;
+SELECT * FROM temporal_fk_rng2rng WHERE id = '[200,200]';
+-- clean up
+DELETE FROM temporal_fk_rng2rng WHERE id IN ('[100,100]', '[200,200]');
+DELETE FROM temporal_rng WHERE id IN ('[6,6]', '[7,7]', '[8,8]', '[9,9]');
+
+-- test FK parent deletes SET DEFAULT
+INSERT INTO temporal_rng VALUES ('[6,6]', tsrange('2018-01-01', '2021-01-01'));
+INSERT INTO temporal_fk_rng2rng VALUES ('[100,100]', tsrange('2018-01-01', '2021-01-01'), '[6,6]');
+-- leftovers on both sides:
+DELETE FROM temporal_rng FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' WHERE id = '[6,6]';
+SELECT * FROM temporal_fk_rng2rng WHERE id = '[100,100]';
+-- non-FPO update:
+DELETE FROM temporal_rng WHERE id = '[6,6]';
+SELECT * FROM temporal_fk_rng2rng WHERE id = '[100,100]';
+-- FK across two referenced rows:
+INSERT INTO temporal_rng VALUES ('[8,8]', tsrange('2018-01-01', '2020-01-01'));
+INSERT INTO temporal_rng VALUES ('[8,8]', tsrange('2020-01-01', '2021-01-01'));
+INSERT INTO temporal_fk_rng2rng VALUES ('[200,200]', tsrange('2018-01-01', '2021-01-01'), '[8,8]');
+DELETE FROM temporal_rng WHERE id = '[8,8]' AND valid_at @> '2019-01-01'::timestamp;
+SELECT * FROM temporal_fk_rng2rng WHERE id = '[200,200]';
+-- clean up
+DELETE FROM temporal_fk_rng2rng WHERE id IN ('[100,100]', '[200,200]');
+DELETE FROM temporal_rng WHERE id IN ('[6,6]', '[7,7]', '[8,8]', '[9,9]');
+
+-- test FK parent updates CASCADE (two scalar cols)
+INSERT INTO temporal_rng2 VALUES ('[6,6]', '[6,6]', tsrange('2018-01-01', '2021-01-01'));
+INSERT INTO temporal_fk2_rng2rng VALUES ('[100,100]', tsrange('2018-01-01', '2021-01-01'), '[6,6]', '[6,6]');
+ALTER TABLE temporal_fk2_rng2rng
+	DROP CONSTRAINT temporal_fk2_rng2rng_fk,
+	ADD CONSTRAINT temporal_fk2_rng2rng_fk
+		FOREIGN KEY (parent_id1, parent_id2, PERIOD valid_at)
+		REFERENCES temporal_rng2
+		ON DELETE CASCADE ON UPDATE CASCADE;
+-- leftovers on both sides:
+UPDATE temporal_rng2 FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' SET id1 = '[7,7]' WHERE id1 = '[6,6]';
+SELECT * FROM temporal_fk2_rng2rng WHERE id = '[100,100]';
+-- non-FPO update:
+UPDATE temporal_rng2 SET id1 = '[7,7]' WHERE id1 = '[6,6]';
+SELECT * FROM temporal_fk2_rng2rng WHERE id = '[100,100]';
+-- FK across two referenced rows:
+INSERT INTO temporal_rng2 VALUES ('[8,8]', '[8,8]', tsrange('2018-01-01', '2020-01-01'));
+INSERT INTO temporal_rng2 VALUES ('[8,8]', '[8,8]', tsrange('2020-01-01', '2021-01-01'));
+INSERT INTO temporal_fk2_rng2rng VALUES ('[200,200]', tsrange('2018-01-01', '2021-01-01'), '[8,8]', '[8,8]');
+UPDATE temporal_rng2 SET id1 = '[9,9]' WHERE id1 = '[8,8]' AND valid_at @> '2019-01-01'::timestamp;
+SELECT * FROM temporal_fk2_rng2rng WHERE id = '[200,200]';
+-- clean up
+DELETE FROM temporal_fk2_rng2rng WHERE id IN ('[100,100]', '[200,200]');
+DELETE FROM temporal_rng2 WHERE id1 IN ('[6,6]', '[7,7]', '[8,8]', '[9,9]');
+
+-- test FK parent deletes CASCADE (two scalar cols)
+INSERT INTO temporal_rng2 VALUES ('[6,6]', '[6,6]', tsrange('2018-01-01', '2021-01-01'));
+INSERT INTO temporal_fk2_rng2rng VALUES ('[100,100]', tsrange('2018-01-01', '2021-01-01'), '[6,6]', '[6,6]');
+-- leftovers on both sides:
+DELETE FROM temporal_rng2 FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' WHERE id1 = '[6,6]';
+SELECT * FROM temporal_fk2_rng2rng WHERE id = '[100,100]';
+-- non-FPO delete:
+DELETE FROM temporal_rng2 WHERE id1 = '[6,6]';
+SELECT * FROM temporal_fk2_rng2rng WHERE id = '[100,100]';
+-- FK across two referenced rows:
+INSERT INTO temporal_rng2 VALUES ('[8,8]', '[8,8]', tsrange('2018-01-01', '2020-01-01'));
+INSERT INTO temporal_rng2 VALUES ('[8,8]', '[8,8]', tsrange('2020-01-01', '2021-01-01'));
+INSERT INTO temporal_fk2_rng2rng VALUES ('[200,200]', tsrange('2018-01-01', '2021-01-01'), '[8,8]', '[8,8]');
+DELETE FROM temporal_rng2 WHERE id1 = '[8,8]' AND valid_at @> '2019-01-01'::timestamp;
+SELECT * FROM temporal_fk2_rng2rng WHERE id = '[200,200]';
+-- clean up
+DELETE FROM temporal_fk2_rng2rng WHERE id IN ('[100,100]', '[200,200]');
+DELETE FROM temporal_rng2 WHERE id1 IN ('[6,6]', '[7,7]', '[8,8]', '[9,9]');
+
+-- test FK parent updates SET NULL (two scalar cols)
+INSERT INTO temporal_rng2 VALUES ('[6,6]', '[6,6]', tsrange('2018-01-01', '2021-01-01'));
+INSERT INTO temporal_fk2_rng2rng VALUES ('[100,100]', tsrange('2018-01-01', '2021-01-01'), '[6,6]', '[6,6]');
+ALTER TABLE temporal_fk2_rng2rng
+	DROP CONSTRAINT temporal_fk2_rng2rng_fk,
+	ADD CONSTRAINT temporal_fk2_rng2rng_fk
+		FOREIGN KEY (parent_id1, parent_id2, PERIOD valid_at)
+		REFERENCES temporal_rng2
+		ON DELETE SET NULL ON UPDATE SET NULL;
+-- leftovers on both sides:
+UPDATE temporal_rng2 FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' SET id1 = '[7,7]' WHERE id1 = '[6,6]';
+SELECT * FROM temporal_fk2_rng2rng WHERE id = '[100,100]';
+-- non-FPO update:
+UPDATE temporal_rng2 SET id1 = '[7,7]' WHERE id1 = '[6,6]';
+SELECT * FROM temporal_fk2_rng2rng WHERE id = '[100,100]';
+-- FK across two referenced rows:
+INSERT INTO temporal_rng2 VALUES ('[8,8]', '[8,8]', tsrange('2018-01-01', '2020-01-01'));
+INSERT INTO temporal_rng2 VALUES ('[8,8]', '[8,8]', tsrange('2020-01-01', '2021-01-01'));
+INSERT INTO temporal_fk2_rng2rng VALUES ('[200,200]', tsrange('2018-01-01', '2021-01-01'), '[8,8]', '[8,8]');
+UPDATE temporal_rng2 SET id1 = '[9,9]' WHERE id1 = '[8,8]' AND valid_at @> '2019-01-01'::timestamp;
+SELECT * FROM temporal_fk2_rng2rng WHERE id = '[200,200]';
+-- clean up
+DELETE FROM temporal_fk2_rng2rng WHERE id IN ('[100,100]', '[200,200]');
+DELETE FROM temporal_rng2 WHERE id1 IN ('[6,6]', '[7,7]', '[8,8]', '[9,9]');
+
+-- test FK parent deletes SET NULL (two scalar cols)
+INSERT INTO temporal_rng2 VALUES ('[6,6]', '[6,6]', tsrange('2018-01-01', '2021-01-01'));
+INSERT INTO temporal_fk2_rng2rng VALUES ('[100,100]', tsrange('2018-01-01', '2021-01-01'), '[6,6]', '[6,6]');
+-- leftovers on both sides:
+DELETE FROM temporal_rng2 FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' WHERE id1 = '[6,6]';
+SELECT * FROM temporal_fk2_rng2rng WHERE id = '[100,100]';
+-- non-FPO delete:
+DELETE FROM temporal_rng2 WHERE id1 = '[6,6]';
+SELECT * FROM temporal_fk2_rng2rng WHERE id = '[100,100]';
+-- FK across two referenced rows:
+INSERT INTO temporal_rng2 VALUES ('[8,8]', '[8,8]', tsrange('2018-01-01', '2020-01-01'));
+INSERT INTO temporal_rng2 VALUES ('[8,8]', '[8,8]', tsrange('2020-01-01', '2021-01-01'));
+INSERT INTO temporal_fk2_rng2rng VALUES ('[200,200]', tsrange('2018-01-01', '2021-01-01'), '[8,8]', '[8,8]');
+DELETE FROM temporal_rng2 WHERE id1 = '[8,8]' AND valid_at @> '2019-01-01'::timestamp;
+SELECT * FROM temporal_fk2_rng2rng WHERE id = '[200,200]';
+-- clean up
+DELETE FROM temporal_fk2_rng2rng WHERE id IN ('[100,100]', '[200,200]');
+DELETE FROM temporal_rng2 WHERE id1 IN ('[6,6]', '[7,7]', '[8,8]', '[9,9]');
+
+-- test FK parent deletes SET NULL (two scalar cols, SET NULL subset)
+INSERT INTO temporal_rng2 VALUES ('[6,6]', '[6,6]', tsrange('2018-01-01', '2021-01-01'));
+INSERT INTO temporal_fk2_rng2rng VALUES ('[100,100]', tsrange('2018-01-01', '2021-01-01'), '[6,6]', '[6,6]');
+-- fails because you can't set the PERIOD column:
+ALTER TABLE temporal_fk2_rng2rng
+	DROP CONSTRAINT temporal_fk2_rng2rng_fk,
+	ADD CONSTRAINT temporal_fk2_rng2rng_fk
+		FOREIGN KEY (parent_id1, parent_id2, PERIOD valid_at)
+		REFERENCES temporal_rng2
+		ON DELETE SET NULL (valid_at) ON UPDATE SET NULL;
+-- ok:
+ALTER TABLE temporal_fk2_rng2rng
+	DROP CONSTRAINT temporal_fk2_rng2rng_fk,
+	ADD CONSTRAINT temporal_fk2_rng2rng_fk
+		FOREIGN KEY (parent_id1, parent_id2, PERIOD valid_at)
+		REFERENCES temporal_rng2
+		ON DELETE SET NULL (parent_id1) ON UPDATE SET NULL;
+-- leftovers on both sides:
+DELETE FROM temporal_rng2 FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' WHERE id1 = '[6,6]';
+SELECT * FROM temporal_fk2_rng2rng WHERE id = '[100,100]';
+-- non-FPO delete:
+DELETE FROM temporal_rng2 WHERE id1 = '[6,6]';
+SELECT * FROM temporal_fk2_rng2rng WHERE id = '[100,100]';
+-- FK across two referenced rows:
+INSERT INTO temporal_rng2 VALUES ('[8,8]', '[8,8]', tsrange('2018-01-01', '2020-01-01'));
+INSERT INTO temporal_rng2 VALUES ('[8,8]', '[8,8]', tsrange('2020-01-01', '2021-01-01'));
+INSERT INTO temporal_fk2_rng2rng VALUES ('[200,200]', tsrange('2018-01-01', '2021-01-01'), '[8,8]', '[8,8]');
+DELETE FROM temporal_rng2 WHERE id1 = '[8,8]' AND valid_at @> '2019-01-01'::timestamp;
+SELECT * FROM temporal_fk2_rng2rng WHERE id = '[200,200]';
+-- clean up
+DELETE FROM temporal_fk2_rng2rng WHERE id IN ('[100,100]', '[200,200]');
+DELETE FROM temporal_rng2 WHERE id1 IN ('[6,6]', '[7,7]', '[8,8]', '[9,9]');
+
+-- test FK parent updates SET DEFAULT (two scalar cols)
+INSERT INTO temporal_rng2 VALUES ('[-1,-1]', '[-1,-1]', tsrange(null, null));
+INSERT INTO temporal_rng2 VALUES ('[6,6]', '[6,6]', tsrange('2018-01-01', '2021-01-01'));
+INSERT INTO temporal_fk2_rng2rng VALUES ('[100,100]', tsrange('2018-01-01', '2021-01-01'), '[6,6]', '[6,6]');
+ALTER TABLE temporal_fk2_rng2rng
+  ALTER COLUMN parent_id1 SET DEFAULT '[-1,-1]',
+  ALTER COLUMN parent_id2 SET DEFAULT '[-1,-1]',
+	DROP CONSTRAINT temporal_fk2_rng2rng_fk,
+	ADD CONSTRAINT temporal_fk2_rng2rng_fk
+		FOREIGN KEY (parent_id1, parent_id2, PERIOD valid_at)
+		REFERENCES temporal_rng2
+		ON DELETE SET DEFAULT ON UPDATE SET DEFAULT;
+-- leftovers on both sides:
+UPDATE temporal_rng2 FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' SET id1 = '[7,7]', id2 = '[7,7]' WHERE id1 = '[6,6]';
+SELECT * FROM temporal_fk2_rng2rng WHERE id = '[100,100]';
+-- non-FPO update:
+UPDATE temporal_rng2 SET id1 = '[7,7]', id2 = '[7,7]' WHERE id1 = '[6,6]';
+SELECT * FROM temporal_fk2_rng2rng WHERE id = '[100,100]';
+-- FK across two referenced rows:
+INSERT INTO temporal_rng2 VALUES ('[8,8]', '[8,8]', tsrange('2018-01-01', '2020-01-01'));
+INSERT INTO temporal_rng2 VALUES ('[8,8]', '[8,8]', tsrange('2020-01-01', '2021-01-01'));
+INSERT INTO temporal_fk2_rng2rng VALUES ('[200,200]', tsrange('2018-01-01', '2021-01-01'), '[8,8]', '[8,8]');
+UPDATE temporal_rng2 SET id1 = '[9,9]', id2 = '[9,9]' WHERE id1 = '[8,8]' AND valid_at @> '2019-01-01'::timestamp;
+SELECT * FROM temporal_fk2_rng2rng WHERE id = '[200,200]';
+-- clean up
+DELETE FROM temporal_fk2_rng2rng WHERE id IN ('[100,100]', '[200,200]');
+DELETE FROM temporal_rng2 WHERE id1 IN ('[6,6]', '[7,7]', '[8,8]', '[9,9]');
+
+-- test FK parent deletes SET DEFAULT (two scalar cols)
+INSERT INTO temporal_rng2 VALUES ('[6,6]', '[6,6]', tsrange('2018-01-01', '2021-01-01'));
+INSERT INTO temporal_fk2_rng2rng VALUES ('[100,100]', tsrange('2018-01-01', '2021-01-01'), '[6,6]', '[6,6]');
+-- leftovers on both sides:
+DELETE FROM temporal_rng2 FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' WHERE id1 = '[6,6]';
+SELECT * FROM temporal_fk2_rng2rng WHERE id = '[100,100]';
+-- non-FPO update:
+DELETE FROM temporal_rng2 WHERE id1 = '[6,6]';
+SELECT * FROM temporal_fk2_rng2rng WHERE id = '[100,100]';
+-- FK across two referenced rows:
+INSERT INTO temporal_rng2 VALUES ('[8,8]', '[8,8]', tsrange('2018-01-01', '2020-01-01'));
+INSERT INTO temporal_rng2 VALUES ('[8,8]', '[8,8]', tsrange('2020-01-01', '2021-01-01'));
+INSERT INTO temporal_fk2_rng2rng VALUES ('[200,200]', tsrange('2018-01-01', '2021-01-01'), '[8,8]', '[8,8]');
+DELETE FROM temporal_rng2 WHERE id1 = '[8,8]' AND valid_at @> '2019-01-01'::timestamp;
+SELECT * FROM temporal_fk2_rng2rng WHERE id = '[200,200]';
+-- clean up
+DELETE FROM temporal_fk2_rng2rng WHERE id IN ('[100,100]', '[200,200]');
+DELETE FROM temporal_rng2 WHERE id1 IN ('[6,6]', '[7,7]', '[8,8]', '[9,9]');
+
+-- test FK parent deletes SET DEFAULT (two scalar cols, SET DEFAULT subset)
+INSERT INTO temporal_rng2 VALUES ('[-1,-1]', '[6,6]', tsrange(null, null));
+INSERT INTO temporal_rng2 VALUES ('[6,6]', '[6,6]', tsrange('2018-01-01', '2021-01-01'));
+INSERT INTO temporal_fk2_rng2rng VALUES ('[100,100]', tsrange('2018-01-01', '2021-01-01'), '[6,6]', '[6,6]');
+-- fails because you can't set the PERIOD column:
+ALTER TABLE temporal_fk2_rng2rng
+  ALTER COLUMN parent_id1 SET DEFAULT '[-1,-1]',
+	DROP CONSTRAINT temporal_fk2_rng2rng_fk,
+	ADD CONSTRAINT temporal_fk2_rng2rng_fk
+		FOREIGN KEY (parent_id1, parent_id2, PERIOD valid_at)
+		REFERENCES temporal_rng2
+		ON DELETE SET DEFAULT (valid_at) ON UPDATE SET DEFAULT;
+-- ok:
+ALTER TABLE temporal_fk2_rng2rng
+  ALTER COLUMN parent_id1 SET DEFAULT '[-1,-1]',
+	DROP CONSTRAINT temporal_fk2_rng2rng_fk,
+	ADD CONSTRAINT temporal_fk2_rng2rng_fk
+		FOREIGN KEY (parent_id1, parent_id2, PERIOD valid_at)
+		REFERENCES temporal_rng2
+		ON DELETE SET DEFAULT (parent_id1) ON UPDATE SET DEFAULT;
+-- leftovers on both sides:
+DELETE FROM temporal_rng2 FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' WHERE id1 = '[6,6]';
+SELECT * FROM temporal_fk2_rng2rng WHERE id = '[100,100]';
+-- non-FPO update:
+DELETE FROM temporal_rng2 WHERE id1 = '[6,6]';
+SELECT * FROM temporal_fk2_rng2rng WHERE id = '[100,100]';
+-- FK across two referenced rows:
+INSERT INTO temporal_rng2 VALUES ('[8,8]', '[8,8]', tsrange('2018-01-01', '2020-01-01'));
+INSERT INTO temporal_rng2 VALUES ('[8,8]', '[8,8]', tsrange('2020-01-01', '2021-01-01'));
+INSERT INTO temporal_rng2 VALUES ('[-1,-1]', '[8,8]', tsrange(null, null));
+INSERT INTO temporal_fk2_rng2rng VALUES ('[200,200]', tsrange('2018-01-01', '2021-01-01'), '[8,8]', '[8,8]');
+DELETE FROM temporal_rng2 WHERE id1 = '[8,8]' AND valid_at @> '2019-01-01'::timestamp;
+SELECT * FROM temporal_fk2_rng2rng WHERE id = '[200,200]';
+-- clean up
+DELETE FROM temporal_fk2_rng2rng WHERE id IN ('[100,100]', '[200,200]');
+DELETE FROM temporal_rng2 WHERE id1 IN ('[6,6]', '[7,7]', '[8,8]', '[9,9]');
+
+-- FK with a custom range type
+
+CREATE TYPE mydaterange AS range(subtype=date);
+
+CREATE TABLE temporal_rng3 (
+	id int4range,
+	valid_at mydaterange,
+	CONSTRAINT temporal_rng3_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+CREATE TABLE temporal_fk3_rng2rng (
+	id int4range,
+	valid_at mydaterange,
+	parent_id int4range,
+	CONSTRAINT temporal_fk3_rng2rng_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS),
+	CONSTRAINT temporal_fk3_rng2rng_fk FOREIGN KEY (parent_id, PERIOD valid_at)
+		REFERENCES temporal_rng3 (id, PERIOD valid_at) ON DELETE CASCADE
+);
+INSERT INTO temporal_rng3 VALUES ('[8,8]', mydaterange('2018-01-01', '2021-01-01'));
+INSERT INTO temporal_fk3_rng2rng VALUES ('[5,5]', mydaterange('2018-01-01', '2021-01-01'), '[8,8]');
+DELETE FROM temporal_rng3 FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' WHERE id = '[8,8]';
+SELECT * FROM temporal_fk3_rng2rng WHERE id = '[5,5]';
+
+DROP TABLE temporal_fk3_rng2rng;
+DROP TABLE temporal_rng3;
+DROP TYPE mydaterange;
 
 -- FK between partitioned tables
 
@@ -759,8 +1107,8 @@ CREATE TABLE temporal_partitioned_rng (
   name text,
 	CONSTRAINT temporal_paritioned_rng_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
 ) PARTITION BY LIST (id);
-CREATE TABLE tp1 partition OF temporal_partitioned_rng FOR VALUES IN ('[1,1]', '[3,3]', '[5,5]', '[7,7]', '[9,9]', '[11,11]');
-CREATE TABLE tp2 partition OF temporal_partitioned_rng FOR VALUES IN ('[2,2]', '[4,4]', '[6,6]', '[8,8]', '[10,10]', '[12,12]');
+CREATE TABLE tp1 partition OF temporal_partitioned_rng FOR VALUES IN ('[1,1]', '[3,3]', '[5,5]', '[7,7]', '[9,9]', '[11,11]', '[13,13]', '[15,15]', '[17,17]', '[19,19]', '[21,21]', '[23,23]');
+CREATE TABLE tp2 partition OF temporal_partitioned_rng FOR VALUES IN ('[0,0]', '[2,2]', '[4,4]', '[6,6]', '[8,8]', '[10,10]', '[12,12]', '[14,14]', '[16,16]', '[18,18]', '[20,20]', '[22,22]', '[24,24]');
 INSERT INTO temporal_partitioned_rng VALUES
   ('[1,1]', daterange('2000-01-01', '2000-02-01'), 'one'),
   ('[1,1]', daterange('2000-02-01', '2000-03-01'), 'one'),
@@ -774,8 +1122,8 @@ CREATE TABLE temporal_partitioned_fk_rng2rng (
 	CONSTRAINT temporal_partitioned_fk_rng2rng_fk FOREIGN KEY (parent_id, PERIOD valid_at)
 		REFERENCES temporal_partitioned_rng (id, PERIOD valid_at)
 ) PARTITION BY LIST (id);
-CREATE TABLE tfkp1 partition OF temporal_partitioned_fk_rng2rng FOR VALUES IN ('[1,1]', '[3,3]', '[5,5]', '[7,7]', '[9,9]', '[11,11]');
-CREATE TABLE tfkp2 partition OF temporal_partitioned_fk_rng2rng FOR VALUES IN ('[2,2]', '[4,4]', '[6,6]', '[8,8]', '[10,10]', '[12,12]');
+CREATE TABLE tfkp1 partition OF temporal_partitioned_fk_rng2rng FOR VALUES IN ('[1,1]', '[3,3]', '[5,5]', '[7,7]', '[9,9]', '[11,11]', '[13,13]', '[15,15]', '[17,17]', '[19,19]', '[21,21]', '[23,23]');
+CREATE TABLE tfkp2 partition OF temporal_partitioned_fk_rng2rng FOR VALUES IN ('[0,0]', '[2,2]', '[4,4]', '[6,6]', '[8,8]', '[10,10]', '[12,12]', '[14,14]', '[16,16]', '[18,18]', '[20,20]', '[22,22]', '[24,24]');
 
 -- partitioned FK child inserts
 
@@ -859,37 +1207,107 @@ DELETE FROM temporal_partitioned_rng WHERE id = '[5,5]' AND valid_at = daterange
 
 -- partitioned FK parent updates CASCADE
 
+INSERT INTO temporal_partitioned_rng VALUES ('[6,6]', daterange('2018-01-01', '2021-01-01'));
+INSERT INTO temporal_partitioned_fk_rng2rng VALUES ('[4,4]', daterange('2018-01-01', '2021-01-01'), '[6,6]');
 ALTER TABLE temporal_partitioned_fk_rng2rng
 	DROP CONSTRAINT temporal_partitioned_fk_rng2rng_fk,
 	ADD CONSTRAINT temporal_partitioned_fk_rng2rng_fk
 		FOREIGN KEY (parent_id, PERIOD valid_at)
 		REFERENCES temporal_partitioned_rng
 		ON DELETE CASCADE ON UPDATE CASCADE;
+UPDATE temporal_partitioned_rng FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' SET id = '[7,7]' WHERE id = '[6,6]';
+SELECT * FROM temporal_partitioned_fk_rng2rng WHERE id = '[4,4]';
+UPDATE temporal_partitioned_rng SET id = '[7,7]' WHERE id = '[6,6]';
+SELECT * FROM temporal_partitioned_fk_rng2rng WHERE id = '[4,4]';
+INSERT INTO temporal_partitioned_rng VALUES ('[15,15]', daterange('2018-01-01', '2020-01-01'));
+INSERT INTO temporal_partitioned_rng VALUES ('[15,15]', daterange('2020-01-01', '2021-01-01'));
+INSERT INTO temporal_partitioned_fk_rng2rng VALUES ('[10,10]', daterange('2018-01-01', '2021-01-01'), '[15,15]');
+UPDATE temporal_partitioned_rng SET id = '[16,16]' WHERE id = '[15,15]' AND valid_at @> '2019-01-01'::date;
+SELECT * FROM temporal_partitioned_fk_rng2rng WHERE id = '[10,10]';
 
 -- partitioned FK parent deletes CASCADE
 
+INSERT INTO temporal_partitioned_rng VALUES ('[8,8]', daterange('2018-01-01', '2021-01-01'));
+INSERT INTO temporal_partitioned_fk_rng2rng VALUES ('[5,5]', daterange('2018-01-01', '2021-01-01'), '[8,8]');
+DELETE FROM temporal_partitioned_rng FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' WHERE id = '[8,8]';
+SELECT * FROM temporal_partitioned_fk_rng2rng WHERE id = '[5,5]';
+DELETE FROM temporal_partitioned_rng WHERE id = '[8,8]';
+SELECT * FROM temporal_partitioned_fk_rng2rng WHERE id = '[5,5]';
+INSERT INTO temporal_partitioned_rng VALUES ('[17,17]', daterange('2018-01-01', '2020-01-01'));
+INSERT INTO temporal_partitioned_rng VALUES ('[17,17]', daterange('2020-01-01', '2021-01-01'));
+INSERT INTO temporal_partitioned_fk_rng2rng VALUES ('[11,11]', daterange('2018-01-01', '2021-01-01'), '[17,17]');
+DELETE FROM temporal_partitioned_rng WHERE id = '[17,17]' AND valid_at @> '2019-01-01'::date;
+SELECT * FROM temporal_partitioned_fk_rng2rng WHERE id = '[11,11]';
+
 -- partitioned FK parent updates SET NULL
 
+INSERT INTO temporal_partitioned_rng VALUES ('[9,9]', daterange('2018-01-01', '2021-01-01'));
+INSERT INTO temporal_partitioned_fk_rng2rng VALUES ('[6,6]', daterange('2018-01-01', '2021-01-01'), '[9,9]');
 ALTER TABLE temporal_partitioned_fk_rng2rng
 	DROP CONSTRAINT temporal_partitioned_fk_rng2rng_fk,
 	ADD CONSTRAINT temporal_partitioned_fk_rng2rng_fk
 		FOREIGN KEY (parent_id, PERIOD valid_at)
 		REFERENCES temporal_partitioned_rng
 		ON DELETE SET NULL ON UPDATE SET NULL;
+UPDATE temporal_partitioned_rng FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' SET id = '[10,10]' WHERE id = '[9,9]';
+SELECT * FROM temporal_partitioned_fk_rng2rng WHERE id = '[6,6]';
+UPDATE temporal_partitioned_rng SET id = '[10,10]' WHERE id = '[9,9]';
+SELECT * FROM temporal_partitioned_fk_rng2rng WHERE id = '[6,6]';
+INSERT INTO temporal_partitioned_rng VALUES ('[18,18]', daterange('2018-01-01', '2020-01-01'));
+INSERT INTO temporal_partitioned_rng VALUES ('[18,18]', daterange('2020-01-01', '2021-01-01'));
+INSERT INTO temporal_partitioned_fk_rng2rng VALUES ('[12,12]', daterange('2018-01-01', '2021-01-01'), '[18,18]');
+UPDATE temporal_partitioned_rng SET id = '[19,19]' WHERE id = '[18,18]' AND valid_at @> '2019-01-01'::date;
+SELECT * FROM temporal_partitioned_fk_rng2rng WHERE id = '[12,12]';
 
 -- partitioned FK parent deletes SET NULL
 
+INSERT INTO temporal_partitioned_rng VALUES ('[11,11]', daterange('2018-01-01', '2021-01-01'));
+INSERT INTO temporal_partitioned_fk_rng2rng VALUES ('[7,7]', daterange('2018-01-01', '2021-01-01'), '[11,11]');
+DELETE FROM temporal_partitioned_rng FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' WHERE id = '[11,11]';
+SELECT * FROM temporal_partitioned_fk_rng2rng WHERE id = '[7,7]';
+DELETE FROM temporal_partitioned_rng WHERE id = '[11,11]';
+SELECT * FROM temporal_partitioned_fk_rng2rng WHERE id = '[7,7]';
+INSERT INTO temporal_partitioned_rng VALUES ('[20,20]', daterange('2018-01-01', '2020-01-01'));
+INSERT INTO temporal_partitioned_rng VALUES ('[20,20]', daterange('2020-01-01', '2021-01-01'));
+INSERT INTO temporal_partitioned_fk_rng2rng VALUES ('[13,13]', daterange('2018-01-01', '2021-01-01'), '[20,20]');
+DELETE FROM temporal_partitioned_rng WHERE id = '[20,20]' AND valid_at @> '2019-01-01'::date;
+SELECT * FROM temporal_partitioned_fk_rng2rng WHERE id = '[13,13]';
+
 -- partitioned FK parent updates SET DEFAULT
 
+INSERT INTO temporal_partitioned_rng VALUES ('[0,0]', daterange(null, null));
+INSERT INTO temporal_partitioned_rng VALUES ('[12,12]', daterange('2018-01-01', '2021-01-01'));
+INSERT INTO temporal_partitioned_fk_rng2rng VALUES ('[8,8]', daterange('2018-01-01', '2021-01-01'), '[12,12]');
 ALTER TABLE temporal_partitioned_fk_rng2rng
-  ALTER COLUMN parent_id SET DEFAULT '[-1,-1]',
+  ALTER COLUMN parent_id SET DEFAULT '[0,0]',
 	DROP CONSTRAINT temporal_partitioned_fk_rng2rng_fk,
 	ADD CONSTRAINT temporal_partitioned_fk_rng2rng_fk
 		FOREIGN KEY (parent_id, PERIOD valid_at)
 		REFERENCES temporal_partitioned_rng
 		ON DELETE SET DEFAULT ON UPDATE SET DEFAULT;
+UPDATE temporal_partitioned_rng FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' SET id = '[13,13]' WHERE id = '[12,12]';
+SELECT * FROM temporal_partitioned_fk_rng2rng WHERE id = '[8,8]';
+UPDATE temporal_partitioned_rng SET id = '[13,13]' WHERE id = '[12,12]';
+SELECT * FROM temporal_partitioned_fk_rng2rng WHERE id = '[8,8]';
+INSERT INTO temporal_partitioned_rng VALUES ('[22,22]', daterange('2018-01-01', '2020-01-01'));
+INSERT INTO temporal_partitioned_rng VALUES ('[22,22]', daterange('2020-01-01', '2021-01-01'));
+INSERT INTO temporal_partitioned_fk_rng2rng VALUES ('[14,14]', daterange('2018-01-01', '2021-01-01'), '[22,22]');
+UPDATE temporal_partitioned_rng SET id = '[23,23]' WHERE id = '[22,22]' AND valid_at @> '2019-01-01'::date;
+SELECT * FROM temporal_partitioned_fk_rng2rng WHERE id = '[14,14]';
 
 -- partitioned FK parent deletes SET DEFAULT
 
+INSERT INTO temporal_partitioned_rng VALUES ('[14,14]', daterange('2018-01-01', '2021-01-01'));
+INSERT INTO temporal_partitioned_fk_rng2rng VALUES ('[9,9]', daterange('2018-01-01', '2021-01-01'), '[14,14]');
+DELETE FROM temporal_partitioned_rng FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' WHERE id = '[14,14]';
+SELECT * FROM temporal_partitioned_fk_rng2rng WHERE id = '[9,9]';
+DELETE FROM temporal_partitioned_rng WHERE id = '[14,14]';
+SELECT * FROM temporal_partitioned_fk_rng2rng WHERE id = '[9,9]';
+INSERT INTO temporal_partitioned_rng VALUES ('[24,24]', daterange('2018-01-01', '2020-01-01'));
+INSERT INTO temporal_partitioned_rng VALUES ('[24,24]', daterange('2020-01-01', '2021-01-01'));
+INSERT INTO temporal_partitioned_fk_rng2rng VALUES ('[15,15]', daterange('2018-01-01', '2021-01-01'), '[24,24]');
+DELETE FROM temporal_partitioned_rng WHERE id = '[24,24]' AND valid_at @> '2019-01-01'::date;
+SELECT * FROM temporal_partitioned_fk_rng2rng WHERE id = '[15,15]';
+
 DROP TABLE temporal_partitioned_fk_rng2rng;
 DROP TABLE temporal_partitioned_rng;
-- 
2.42.0