From d8c9a5a72fc691f92d8afddb9cece66c380a17c9 Mon Sep 17 00:00:00 2001
From: "Paul A. Jungwirth" <pj@illuminatedcomputing.com>
Date: Sun, 27 Jun 2021 12:58:51 -0700
Subject: [PATCH v9 4/4] Add temporal FOREIGN KEYs

- Added bison support for temporal FOREIGN KEYs. Like primary keys, they
  can have either a PERIOD or a range column for the temporal component.
- Added temporal trigger functions akin to existing RI trigger
  functions.
- Added support for ON {UPDATE,DELETE} {CASCADE,SET NULL,SET DEFAULT}.
- Added pg_constraint.confperiod to record the oid of a PERIOD used in
  the other table. If an FK uses a range column instead, we just record
  its attnum with the other attnums used by the constraint.
- We support foreign keys where one table uses a PERIOD and the other a
  range: you don't have to choose one or the other for your whole
  database.
- Added pg_dump support.
- Show the correct syntax in psql \d output for foreign keys.
- Added tests and documentation.
---
 doc/src/sgml/ddl.sgml                         |    4 +
 doc/src/sgml/ref/create_table.sgml            |   40 +-
 src/backend/catalog/heap.c                    |    1 +
 src/backend/catalog/index.c                   |    1 +
 src/backend/catalog/pg_constraint.c           |   12 +-
 src/backend/commands/tablecmds.c              |  891 +++++---
 src/backend/commands/trigger.c                |    2 +
 src/backend/commands/typecmds.c               |    1 +
 src/backend/nodes/copyfuncs.c                 |    3 +
 src/backend/parser/gram.y                     |   31 +-
 src/backend/utils/adt/rangetypes.c            |   29 +
 src/backend/utils/adt/ri_triggers.c           | 1543 ++++++++++++--
 src/backend/utils/adt/ruleutils.c             |   17 +-
 src/include/catalog/pg_constraint.h           |   10 +-
 src/include/catalog/pg_proc.dat               |   44 +
 src/include/nodes/parsenodes.h                |    2 +
 src/include/utils/rangetypes.h                |    1 +
 src/test/regress/expected/for_portion_of.out  |    9 +
 src/test/regress/expected/sanity_check.out    |    1 +
 .../regress/expected/without_overlaps.out     | 1846 +++++++++++++++++
 src/test/regress/sql/for_portion_of.sql       |    6 +
 src/test/regress/sql/without_overlaps.sql     | 1402 +++++++++++++
 22 files changed, 5399 insertions(+), 497 deletions(-)

diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index 57e0dc1363..baa2c101b7 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -1192,6 +1192,10 @@ CREATE TABLE billing_addresses (
    <para>
     Any table with a temporal primary key supports temporal <literal>UPDATE</literal> and <literal>DELETE</literal> using <literal>FOR PORTION OF</literal> syntax. See <xref linkend="sql-update"/> and <xref linkend="sql-delete"/> for details.
    </para>
+
+   <para>
+    Temporal foreign keys have the same structure as temporal primary keys and must reference temporal primary keys. The referenced entity specified by the ordinary key parts must exist for at least as long as the value in the referencing row. Note that several rows in the referenced table may be required to completely satisfy a referencing value.
+   </para>
   </sect2>
 
   <sect2 id="ddl-periods-system-periods">
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index fcf15013a7..7d64fc8348 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -88,7 +88,7 @@ PERIOD FOR { <replaceable class="parameter">period_name</replaceable> | SYSTEM_T
   UNIQUE ( <replaceable class="parameter">column_name</replaceable> [, ... ] [, <replaceable class="parameter">temporal_interval</replaceable> WITHOUT OVERLAPS ] ) <replaceable class="parameter">index_parameters</replaceable> |
   PRIMARY KEY ( <replaceable class="parameter">column_name</replaceable> [, ... ] [, <replaceable class="parameter">temporal_interval</replaceable> WITHOUT OVERLAPS ] ) <replaceable class="parameter">index_parameters</replaceable> |
   EXCLUDE [ USING <replaceable class="parameter">index_method</replaceable> ] ( <replaceable class="parameter">exclude_element</replaceable> WITH <replaceable class="parameter">operator</replaceable> [, ... ] ) <replaceable class="parameter">index_parameters</replaceable> [ WHERE ( <replaceable class="parameter">predicate</replaceable> ) ] |
-  FOREIGN KEY ( <replaceable class="parameter">column_name</replaceable> [, ... ] ) REFERENCES <replaceable class="parameter">reftable</replaceable> [ ( <replaceable class="parameter">refcolumn</replaceable> [, ... ] ) ]
+  FOREIGN KEY ( <replaceable class="parameter">column_name</replaceable> [, ... ] [, PERIOD <replaceable class="parameter">temporal_interval</replaceable> ] ) REFERENCES <replaceable class="parameter">reftable</replaceable> [ ( <replaceable class="parameter">refcolumn</replaceable> [, ... ] [, PERIOD <replaceable class="parameter">temporal_interval</replaceable> ] ) ]
     [ MATCH FULL | MATCH PARTIAL | MATCH SIMPLE ] [ ON DELETE <replaceable
 class="parameter">referential_action</replaceable> ] [ ON UPDATE <replaceable class="parameter">referential_action</replaceable> ] }
 [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
@@ -1141,8 +1141,8 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
    <varlistentry>
     <term><literal>REFERENCES <replaceable class="parameter">reftable</replaceable> [ ( <replaceable class="parameter">refcolumn</replaceable> ) ] [ MATCH <replaceable class="parameter">matchtype</replaceable> ] [ ON DELETE <replaceable class="parameter">referential_action</replaceable> ] [ ON UPDATE <replaceable class="parameter">referential_action</replaceable> ]</literal> (column constraint)</term>
 
-   <term><literal>FOREIGN KEY ( <replaceable class="parameter">column_name</replaceable> [, ... ] )
-    REFERENCES <replaceable class="parameter">reftable</replaceable> [ ( <replaceable class="parameter">refcolumn</replaceable> [, ... ] ) ]
+   <term><literal>FOREIGN KEY ( <replaceable class="parameter">column_name</replaceable> [, ... ] [, PERIOD <replaceable class="parameter">temporal_interval</replaceable> ] )
+    REFERENCES <replaceable class="parameter">reftable</replaceable> [ ( <replaceable class="parameter">refcolumn</replaceable> [, ... ] [, PERIOD <replaceable class="parameter">temporal_interval</replaceable> ] ) ]
     [ MATCH <replaceable class="parameter">matchtype</replaceable> ]
     [ ON DELETE <replaceable class="parameter">referential_action</replaceable> ]
     [ ON UPDATE <replaceable class="parameter">referential_action</replaceable> ]</literal>
@@ -1153,11 +1153,29 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
       These clauses specify a foreign key constraint, which requires
       that a group of one or more columns of the new table must only
       contain values that match values in the referenced
-      column(s) of some row of the referenced table.  If the <replaceable
+      column(s) of some row of the referenced table.
+      If the <replaceable
       class="parameter">refcolumn</replaceable> list is omitted, the
       primary key of the <replaceable class="parameter">reftable</replaceable>
       is used.  The referenced columns must be the columns of a non-deferrable
-      unique or primary key constraint in the referenced table.  The user
+      unique or primary key constraint in the referenced table.
+     </para>
+
+     <para>
+      If the last column is marked with <literal>PERIOD</literal>,
+      it must be a period or range column, and the referenced table
+      must have a temporal primary key.
+      The non-<literal>PERIOD</literal> columns are treated normally
+      (and there must be at least one of them),
+      but the <literal>PERIOD</literal> column is not compared for equality.
+      Instead the constraint is considered satisfied
+      if the referenced table has matching records whose combined ranges completely cover
+      the referencing record.
+      In other words, the reference must have a referent for its entire duration.
+     </para>
+
+     <para>
+      The user
       must have <literal>REFERENCES</literal> permission on the referenced table
       (either the whole table, or the specific referenced columns).  The
       addition of a foreign key constraint requires a
@@ -1229,7 +1247,10 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
          <para>
           Delete any rows referencing the deleted row, or update the
           values of the referencing column(s) to the new values of the
-          referenced columns, respectively.
+          referenced columns, respectively. In a temporal foreign key,
+          the delete will use <literal>FOR PORTION OF</literal> semantics
+          to constrain the effect to the bounds of the referenced row
+          being deleted.
          </para>
         </listitem>
        </varlistentry>
@@ -1238,7 +1259,9 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
         <term><literal>SET NULL</literal></term>
         <listitem>
          <para>
-          Set the referencing column(s) to null.
+          Set the referencing column(s) to null. In a temporal foreign key,
+          the change will use <literal>FOR PORTION OF</literal> semantics
+          to constrain the effect to the bounds of the referenced row.
          </para>
         </listitem>
        </varlistentry>
@@ -1250,6 +1273,9 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
           Set the referencing column(s) to their default values.
           (There must be a row in the referenced table matching the default
           values, if they are not null, or the operation will fail.)
+          In a temporal foreign key, the change will use <literal>FOR PORTION
+          OF</literal> semantics to constrain the effect to the bounds of the
+          referenced row.
          </para>
         </listitem>
        </varlistentry>
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index d8b99ade5c..e3b0089df9 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -2560,6 +2560,7 @@ StoreRelCheck(Relation rel, const char *ccname, Node *expr,
 							  is_no_inherit,	/* connoinherit */
 							  false,	/* contemporal */
 							  InvalidOid,	/* conperiod */
+							  InvalidOid,	/* confperiod */
 							  is_internal); /* internally constructed? */
 
 	pfree(ccbin);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 68c5700f6c..abffe387b7 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -2010,6 +2010,7 @@ index_constraint_create(Relation heapRelation,
 								   noinherit,
 								   is_temporal,	/* contemporal */
 								   periodid, /* conperiod */
+								   InvalidOid, /* confperiod */
 								   is_internal);
 
 	/*
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index 7927fbde88..43dcdb3acc 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -78,6 +78,7 @@ CreateConstraintEntry(const char *constraintName,
 					  bool conNoInherit,
 					  bool conTemporal,
 					  Oid period,
+					  Oid fperiod,
 					  bool is_internal)
 {
 	Relation	conDesc;
@@ -190,6 +191,7 @@ CreateConstraintEntry(const char *constraintName,
 	values[Anum_pg_constraint_connoinherit - 1] = BoolGetDatum(conNoInherit);
 	values[Anum_pg_constraint_contemporal - 1] = BoolGetDatum(conTemporal);
 	values[Anum_pg_constraint_conperiod - 1] = ObjectIdGetDatum(period);
+	values[Anum_pg_constraint_confperiod - 1] = ObjectIdGetDatum(fperiod);
 
 	if (conkeyArray)
 		values[Anum_pg_constraint_conkey - 1] = PointerGetDatum(conkeyArray);
@@ -291,7 +293,7 @@ CreateConstraintEntry(const char *constraintName,
 	{
 		/*
 		 * Register normal dependency from constraint to foreign relation, or
-		 * to specific column(s) if any are mentioned.
+		 * to specific column(s) and period if any are mentioned.
 		 */
 		ObjectAddress relobject;
 
@@ -309,6 +311,14 @@ CreateConstraintEntry(const char *constraintName,
 			ObjectAddressSet(relobject, RelationRelationId, foreignRelId);
 			add_exact_object_address(&relobject, addrs_normal);
 		}
+
+		if (OidIsValid(fperiod))
+		{
+			ObjectAddress periodobject;
+
+			ObjectAddressSet(periodobject, PeriodRelationId, fperiod);
+			add_exact_object_address(&periodobject, addrs_normal);
+		}
 	}
 
 	if (OidIsValid(indexRelId) && constraintType == CONSTRAINT_FOREIGN)
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 88b692bd8d..4907cbf1ba 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -367,19 +367,24 @@ static ObjectAddress ATExecValidateConstraint(List **wqueue,
 											  bool recurse, bool recursing, LOCKMODE lockmode);
 static int	transformColumnNameList(Oid relId, List *colList,
 									int16 *attnums, Oid *atttypids);
+static void	transformPeriodName(Oid relId, Node *periodName,
+								int16 *attnums, Oid *atttypids, Oid *periodid);
 static int	transformFkeyGetPrimaryKey(Relation pkrel, Oid *indexOid,
 									   List **attnamelist,
 									   int16 *attnums, Oid *atttypids,
+										 Node **periodattname, Oid *periodid,
+										 int16 *periodattnums, Oid *periodatttypids,
 									   Oid *opclasses);
 static Oid	transformFkeyCheckAttrs(Relation pkrel,
 									int numattrs, int16 *attnums,
-									Oid *opclasses);
+									bool is_temporal, int16 *periodattnums,
+									Oid periodid, Oid *opclasses);
 static void checkFkeyPermissions(Relation rel, int16 *attnums, int natts);
 static CoercionPathType findFkeyCast(Oid targetTypeId, Oid sourceTypeId,
 									 Oid *funcid);
 static void validateForeignKeyConstraint(char *conname,
 										 Relation rel, Relation pkrel,
-										 Oid pkindOid, Oid constraintOid);
+										 Oid pkindOid, Oid constraintOid, bool temporal);
 static void ATController(AlterTableStmt *parsetree,
 						 Relation rel, List *cmds, bool recurse, LOCKMODE lockmode,
 						 AlterTableUtilityContext *context);
@@ -493,12 +498,13 @@ static ObjectAddress addFkRecurseReferenced(List **wqueue, Constraint *fkconstra
 											Relation rel, Relation pkrel, Oid indexOid, Oid parentConstr,
 											int numfks, int16 *pkattnum, int16 *fkattnum,
 											Oid *pfeqoperators, Oid *ppeqoperators, Oid *ffeqoperators,
-											bool old_check_ok);
+											bool old_check_ok, bool is_temporal, Oid pkperiod, Oid fkperiod);
 static void addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint,
 									Relation rel, Relation pkrel, Oid indexOid, Oid parentConstr,
 									int numfks, int16 *pkattnum, int16 *fkattnum,
 									Oid *pfeqoperators, Oid *ppeqoperators, Oid *ffeqoperators,
-									bool old_check_ok, LOCKMODE lockmode);
+									bool old_check_ok, bool is_temporal, Oid pkperiod, Oid fkperiod,
+									LOCKMODE lockmode);
 static void CloneForeignKeyConstraints(List **wqueue, Relation parentRel,
 									   Relation partitionRel);
 static void CloneFkReferenced(Relation parentRel, Relation partitionRel);
@@ -515,6 +521,12 @@ static bool tryAttachPartitionForeignKey(ForeignKeyCacheInfo *fk,
 										 Oid parentConstrOid, int numfks,
 										 AttrNumber *mapped_conkey, AttrNumber *confkey,
 										 Oid *conpfeqop);
+static void FindFKComparisonOperators(Constraint *fkconstraint,
+					AlteredTableInfo *tab, int i, int16 *fkattnum,
+					bool *old_check_ok, ListCell **old_pfeqop_item,
+					Oid pktype, Oid fktype, Oid opclass,
+					bool is_temporal, bool for_overlaps,
+					Oid *pfeqopOut, Oid *ppeqopOut, Oid *ffeqopOut);
 static void ATExecDropConstraint(Relation rel, const char *constrName,
 								 DropBehavior behavior,
 								 bool recurse, bool recursing,
@@ -5831,7 +5843,8 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode,
 
 				validateForeignKeyConstraint(fkconstraint->conname, rel, refrel,
 											 con->refindid,
-											 con->conid);
+											 con->conid,
+											 con->contemporal);
 
 				/*
 				 * No need to mark the constraint row as validated, we did
@@ -9503,11 +9516,18 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			pfeqoperators[INDEX_MAX_KEYS];
 	Oid			ppeqoperators[INDEX_MAX_KEYS];
 	Oid			ffeqoperators[INDEX_MAX_KEYS];
+	bool		is_temporal = (fkconstraint->fk_period != NULL);
+	int16		pkperiodattnums[2];
+	int16		fkperiodattnums[2];
+	Oid			pkperiodtypoids[2];
+	Oid			fkperiodtypoids[2];
 	int			i;
 	int			numfks,
 				numpks;
 	Oid			indexOid;
 	bool		old_check_ok;
+	Oid			fkperiod = InvalidOid;
+	Oid			pkperiod = InvalidOid;
 	ObjectAddress address;
 	ListCell   *old_pfeqop_item = list_head(fkconstraint->old_conpfeqop);
 
@@ -9605,6 +9625,21 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	numfks = transformColumnNameList(RelationGetRelid(rel),
 									 fkconstraint->fk_attrs,
 									 fkattnum, fktypoid);
+	if (is_temporal)
+	{
+		/*
+		 * First see if we have a period name, otherwise look for a range column.
+		 * If it's a period, get the attnums of both start & end columns,
+		 * but instead of their types get period's rangetype.
+		 */
+		transformPeriodName(RelationGetRelid(rel),
+				fkconstraint->fk_period, fkperiodattnums, fkperiodtypoids, &fkperiod);
+
+		if (fkperiod == InvalidOid)
+			transformColumnNameList(RelationGetRelid(rel),
+								  list_make1(fkconstraint->fk_period),
+								  fkperiodattnums, fkperiodtypoids);
+	}
 
 	/*
 	 * If the attribute list for the referenced table was omitted, lookup the
@@ -9617,6 +9652,9 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 		numpks = transformFkeyGetPrimaryKey(pkrel, &indexOid,
 											&fkconstraint->pk_attrs,
 											pkattnum, pktypoid,
+											&fkconstraint->pk_period,
+											&pkperiod,
+											pkperiodattnums, pkperiodtypoids,
 											opclasses);
 	}
 	else
@@ -9624,8 +9662,19 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 		numpks = transformColumnNameList(RelationGetRelid(pkrel),
 										 fkconstraint->pk_attrs,
 										 pkattnum, pktypoid);
+		if (is_temporal) {
+			transformPeriodName(RelationGetRelid(pkrel),
+					fkconstraint->pk_period, pkperiodattnums, pkperiodtypoids, &pkperiod);
+
+			if (pkperiod == InvalidOid)
+				transformColumnNameList(RelationGetRelid(pkrel),
+										list_make1(fkconstraint->pk_period),
+										pkperiodattnums, pkperiodtypoids);
+		}
 		/* Look for an index matching the column list */
 		indexOid = transformFkeyCheckAttrs(pkrel, numpks, pkattnum,
+										   is_temporal, pkperiodattnums,
+										   pkperiod,
 										   opclasses);
 	}
 
@@ -9684,187 +9733,43 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 
 	for (i = 0; i < numpks; i++)
 	{
-		Oid			pktype = pktypoid[i];
-		Oid			fktype = fktypoid[i];
-		Oid			fktyped;
-		HeapTuple	cla_ht;
-		Form_pg_opclass cla_tup;
-		Oid			amid;
-		Oid			opfamily;
-		Oid			opcintype;
-		Oid			pfeqop;
-		Oid			ppeqop;
-		Oid			ffeqop;
-		int16		eqstrategy;
-		Oid			pfeqop_right;
-
-		/* We need several fields out of the pg_opclass entry */
-		cla_ht = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclasses[i]));
-		if (!HeapTupleIsValid(cla_ht))
-			elog(ERROR, "cache lookup failed for opclass %u", opclasses[i]);
-		cla_tup = (Form_pg_opclass) GETSTRUCT(cla_ht);
-		amid = cla_tup->opcmethod;
-		opfamily = cla_tup->opcfamily;
-		opcintype = cla_tup->opcintype;
-		ReleaseSysCache(cla_ht);
-
-		/*
-		 * Check it's a btree; currently this can never fail since no other
-		 * index AMs support unique indexes.  If we ever did have other types
-		 * of unique indexes, we'd need a way to determine which operator
-		 * strategy number is equality.  (Is it reasonable to insist that
-		 * every such index AM use btree's number for equality?)
-		 */
-		if (amid != BTREE_AM_OID)
-			elog(ERROR, "only b-tree indexes are supported for foreign keys");
-		eqstrategy = BTEqualStrategyNumber;
-
-		/*
-		 * There had better be a primary equality operator for the index.
-		 * We'll use it for PK = PK comparisons.
-		 */
-		ppeqop = get_opfamily_member(opfamily, opcintype, opcintype,
-									 eqstrategy);
-
-		if (!OidIsValid(ppeqop))
-			elog(ERROR, "missing operator %d(%u,%u) in opfamily %u",
-				 eqstrategy, opcintype, opcintype, opfamily);
-
-		/*
-		 * Are there equality operators that take exactly the FK type? Assume
-		 * we should look through any domain here.
-		 */
-		fktyped = getBaseType(fktype);
-
-		pfeqop = get_opfamily_member(opfamily, opcintype, fktyped,
-									 eqstrategy);
-		if (OidIsValid(pfeqop))
+		FindFKComparisonOperators(
+				fkconstraint, tab, i, fkattnum,
+				&old_check_ok, &old_pfeqop_item,
+				pktypoid[i], fktypoid[i], opclasses[i],
+				is_temporal, false,
+				&pfeqoperators[i], &ppeqoperators[i], &ffeqoperators[i]);
+	}
+	if (is_temporal) {
+		if (pkperiod == InvalidOid)
 		{
-			pfeqop_right = fktyped;
-			ffeqop = get_opfamily_member(opfamily, fktyped, fktyped,
-										 eqstrategy);
+			pkattnum[numpks] = pkperiodattnums[0];
+			pktypoid[numpks] = pkperiodtypoids[0];
 		}
 		else
 		{
-			/* keep compiler quiet */
-			pfeqop_right = InvalidOid;
-			ffeqop = InvalidOid;
+			pkattnum[numpks] = InvalidOid;
+			pktypoid[numpks] = InvalidOid;
 		}
-
-		if (!(OidIsValid(pfeqop) && OidIsValid(ffeqop)))
-		{
-			/*
-			 * Otherwise, look for an implicit cast from the FK type to the
-			 * opcintype, and if found, use the primary equality operator.
-			 * This is a bit tricky because opcintype might be a polymorphic
-			 * type such as ANYARRAY or ANYENUM; so what we have to test is
-			 * whether the two actual column types can be concurrently cast to
-			 * that type.  (Otherwise, we'd fail to reject combinations such
-			 * as int[] and point[].)
-			 */
-			Oid			input_typeids[2];
-			Oid			target_typeids[2];
-
-			input_typeids[0] = pktype;
-			input_typeids[1] = fktype;
-			target_typeids[0] = opcintype;
-			target_typeids[1] = opcintype;
-			if (can_coerce_type(2, input_typeids, target_typeids,
-								COERCION_IMPLICIT))
-			{
-				pfeqop = ffeqop = ppeqop;
-				pfeqop_right = opcintype;
-			}
-		}
-
-		if (!(OidIsValid(pfeqop) && OidIsValid(ffeqop)))
-			ereport(ERROR,
-					(errcode(ERRCODE_DATATYPE_MISMATCH),
-					 errmsg("foreign key constraint \"%s\" cannot be implemented",
-							fkconstraint->conname),
-					 errdetail("Key columns \"%s\" and \"%s\" "
-							   "are of incompatible types: %s and %s.",
-							   strVal(list_nth(fkconstraint->fk_attrs, i)),
-							   strVal(list_nth(fkconstraint->pk_attrs, i)),
-							   format_type_be(fktype),
-							   format_type_be(pktype))));
-
-		if (old_check_ok)
+		if (fkperiod == InvalidOid)
 		{
-			/*
-			 * When a pfeqop changes, revalidate the constraint.  We could
-			 * permit intra-opfamily changes, but that adds subtle complexity
-			 * without any concrete benefit for core types.  We need not
-			 * assess ppeqop or ffeqop, which RI_Initial_Check() does not use.
-			 */
-			old_check_ok = (pfeqop == lfirst_oid(old_pfeqop_item));
-			old_pfeqop_item = lnext(fkconstraint->old_conpfeqop,
-									old_pfeqop_item);
+			fkattnum[numpks] = fkperiodattnums[0];
+			fktypoid[numpks] = fkperiodtypoids[0];
 		}
-		if (old_check_ok)
+		else
 		{
-			Oid			old_fktype;
-			Oid			new_fktype;
-			CoercionPathType old_pathtype;
-			CoercionPathType new_pathtype;
-			Oid			old_castfunc;
-			Oid			new_castfunc;
-			Form_pg_attribute attr = TupleDescAttr(tab->oldDesc,
-												   fkattnum[i] - 1);
-
-			/*
-			 * Identify coercion pathways from each of the old and new FK-side
-			 * column types to the right (foreign) operand type of the pfeqop.
-			 * We may assume that pg_constraint.conkey is not changing.
-			 */
-			old_fktype = attr->atttypid;
-			new_fktype = fktype;
-			old_pathtype = findFkeyCast(pfeqop_right, old_fktype,
-										&old_castfunc);
-			new_pathtype = findFkeyCast(pfeqop_right, new_fktype,
-										&new_castfunc);
-
-			/*
-			 * Upon a change to the cast from the FK column to its pfeqop
-			 * operand, revalidate the constraint.  For this evaluation, a
-			 * binary coercion cast is equivalent to no cast at all.  While
-			 * type implementors should design implicit casts with an eye
-			 * toward consistency of operations like equality, we cannot
-			 * assume here that they have done so.
-			 *
-			 * A function with a polymorphic argument could change behavior
-			 * arbitrarily in response to get_fn_expr_argtype().  Therefore,
-			 * when the cast destination is polymorphic, we only avoid
-			 * revalidation if the input type has not changed at all.  Given
-			 * just the core data types and operator classes, this requirement
-			 * prevents no would-be optimizations.
-			 *
-			 * If the cast converts from a base type to a domain thereon, then
-			 * that domain type must be the opcintype of the unique index.
-			 * Necessarily, the primary key column must then be of the domain
-			 * type.  Since the constraint was previously valid, all values on
-			 * the foreign side necessarily exist on the primary side and in
-			 * turn conform to the domain.  Consequently, we need not treat
-			 * domains specially here.
-			 *
-			 * Since we require that all collations share the same notion of
-			 * equality (which they do, because texteq reduces to bitwise
-			 * equality), we don't compare collation here.
-			 *
-			 * We need not directly consider the PK type.  It's necessarily
-			 * binary coercible to the opcintype of the unique index column,
-			 * and ri_triggers.c will only deal with PK datums in terms of
-			 * that opcintype.  Changing the opcintype also changes pfeqop.
-			 */
-			old_check_ok = (new_pathtype == old_pathtype &&
-							new_castfunc == old_castfunc &&
-							(!IsPolymorphicType(pfeqop_right) ||
-							 new_fktype == old_fktype));
+			fkattnum[numpks] = InvalidOid;
+			fktypoid[numpks] = InvalidOid;
 		}
 
-		pfeqoperators[i] = pfeqop;
-		ppeqoperators[i] = ppeqop;
-		ffeqoperators[i] = ffeqop;
+		FindFKComparisonOperators(
+				fkconstraint, tab, numpks, fkattnum,
+				&old_check_ok, &old_pfeqop_item,
+				pkperiodtypoids[0], fkperiodtypoids[0], opclasses[numpks],
+				is_temporal, true,
+				&pfeqoperators[numpks], &ppeqoperators[numpks], &ffeqoperators[numpks]);
+		numfks += 1;
+		numpks += 1;
 	}
 
 	/*
@@ -9880,7 +9785,10 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 									 pfeqoperators,
 									 ppeqoperators,
 									 ffeqoperators,
-									 old_check_ok);
+									 old_check_ok,
+									 is_temporal,
+									 pkperiod,
+									 fkperiod);
 
 	/* Now handle the referencing side. */
 	addFkRecurseReferencing(wqueue, fkconstraint, rel, pkrel,
@@ -9893,6 +9801,9 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 							ppeqoperators,
 							ffeqoperators,
 							old_check_ok,
+							is_temporal,
+							pkperiod,
+							fkperiod,
 							lockmode);
 
 	/*
@@ -9933,7 +9844,8 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
 					   Relation pkrel, Oid indexOid, Oid parentConstr,
 					   int numfks,
 					   int16 *pkattnum, int16 *fkattnum, Oid *pfeqoperators,
-					   Oid *ppeqoperators, Oid *ffeqoperators, bool old_check_ok)
+					   Oid *ppeqoperators, Oid *ffeqoperators, bool old_check_ok,
+					   bool is_temporal, Oid pkperiod, Oid fkperiod)
 {
 	ObjectAddress address;
 	Oid			constrOid;
@@ -10015,8 +9927,9 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
 									  conislocal,	/* islocal */
 									  coninhcount,	/* inhcount */
 									  connoinherit, /* conNoInherit */
-									  false,	/* conTemporal */
-									  InvalidOid,
+									  is_temporal,
+									  fkperiod,
+									  pkperiod,
 									  false);	/* is_internal */
 
 	ObjectAddressSet(address, ConstraintRelationId, constrOid);
@@ -10091,7 +10004,7 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
 								   partIndexId, constrOid, numfks,
 								   mapped_pkattnum, fkattnum,
 								   pfeqoperators, ppeqoperators, ffeqoperators,
-								   old_check_ok);
+								   old_check_ok, is_temporal, pkperiod, fkperiod);
 
 			/* Done -- clean up (but keep the lock) */
 			table_close(partRel, NoLock);
@@ -10140,7 +10053,8 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
 						Relation pkrel, Oid indexOid, Oid parentConstr,
 						int numfks, int16 *pkattnum, int16 *fkattnum,
 						Oid *pfeqoperators, Oid *ppeqoperators, Oid *ffeqoperators,
-						bool old_check_ok, LOCKMODE lockmode)
+						bool old_check_ok, bool is_temporal, Oid pkperiod,
+						Oid fkperiod, LOCKMODE lockmode)
 {
 	AssertArg(OidIsValid(parentConstr));
 
@@ -10183,6 +10097,7 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
 			newcon->refrelid = RelationGetRelid(pkrel);
 			newcon->refindid = indexOid;
 			newcon->conid = parentConstr;
+			newcon->contemporal = fkconstraint->fk_period != NULL;
 			newcon->qual = (Node *) fkconstraint;
 
 			tab->constraints = lappend(tab->constraints, newcon);
@@ -10285,8 +10200,9 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
 									  false,
 									  1,
 									  false,
-									  false,	/* conTemporal */
-									  InvalidOid,
+									  is_temporal,
+									  fkperiod,
+									  pkperiod,
 									  false);
 
 			/*
@@ -10313,6 +10229,9 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
 									ppeqoperators,
 									ffeqoperators,
 									old_check_ok,
+									is_temporal,
+									pkperiod,
+									fkperiod,
 									lockmode);
 
 			table_close(partition, NoLock);
@@ -10450,6 +10369,7 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel)
 								   conpfeqop,
 								   conppeqop,
 								   conffeqop);
+		Assert(numfks == attmap->maplen);
 
 		for (int i = 0; i < numfks; i++)
 			mapped_confkey[i] = attmap->attnums[confkey[i] - 1];
@@ -10496,7 +10416,10 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel)
 							   conpfeqop,
 							   conppeqop,
 							   conffeqop,
-							   true);
+							   true,
+							   constrForm->contemporal,
+							   constrForm->confperiod,
+							   constrForm->conperiod);
 
 		table_close(fkRel, NoLock);
 		ReleaseSysCache(tuple);
@@ -10689,8 +10612,9 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
 								  false,	/* islocal */
 								  1,	/* inhcount */
 								  false,	/* conNoInherit */
-								  false,	/* conTemporal */
-								  InvalidOid,
+								  constrForm->contemporal,
+								  constrForm->conperiod,
+								  constrForm->confperiod,
 								  true);
 
 		/* Set up partition dependencies for the new constraint */
@@ -10720,11 +10644,232 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
 								conppeqop,
 								conffeqop,
 								false,	/* no old check exists */
+								constrForm->contemporal,
+								constrForm->conperiod,
+								constrForm->confperiod,
 								AccessExclusiveLock);
 		table_close(pkrel, NoLock);
 	}
 }
 
+static void
+FindFKComparisonOperators(Constraint *fkconstraint,
+		AlteredTableInfo *tab,
+		int i,
+		int16 *fkattnum,
+		bool *old_check_ok,
+		ListCell **old_pfeqop_item,
+		Oid pktype, Oid fktype, Oid opclass,
+		bool is_temporal, bool for_overlaps,
+		Oid *pfeqopOut, Oid *ppeqopOut, Oid *ffeqopOut)
+{
+	Oid			fktyped;
+	HeapTuple	cla_ht;
+	Form_pg_opclass cla_tup;
+	Oid			amid;
+	Oid			opfamily;
+	Oid			opcintype;
+	Oid			pfeqop;
+	Oid			ppeqop;
+	Oid			ffeqop;
+	int16		eqstrategy;
+	Oid			pfeqop_right;
+
+	/* We need several fields out of the pg_opclass entry */
+	cla_ht = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclass));
+	if (!HeapTupleIsValid(cla_ht))
+		elog(ERROR, "cache lookup failed for opclass %u", opclass);
+	cla_tup = (Form_pg_opclass) GETSTRUCT(cla_ht);
+	amid = cla_tup->opcmethod;
+	opfamily = cla_tup->opcfamily;
+	opcintype = cla_tup->opcintype;
+	ReleaseSysCache(cla_ht);
+
+	if (is_temporal)
+	{
+		if (amid != GIST_AM_OID)
+			elog(ERROR, "only GiST indexes are supported for temporal foreign keys");
+		eqstrategy = for_overlaps ? RTOverlapStrategyNumber : RTEqualStrategyNumber;
+	}
+	else
+	{
+		/*
+		 * Check it's a btree; currently this can never fail since no other
+		 * index AMs support unique indexes.  If we ever did have other types
+		 * of unique indexes, we'd need a way to determine which operator
+		 * strategy number is equality.  (Is it reasonable to insist that
+		 * every such index AM use btree's number for equality?)
+		 */
+		if (amid != BTREE_AM_OID)
+			elog(ERROR, "only b-tree indexes are supported for foreign keys");
+		eqstrategy = BTEqualStrategyNumber;
+	}
+
+	/*
+	 * There had better be a primary equality operator for the index.
+	 * We'll use it for PK = PK comparisons.
+	 */
+	ppeqop = get_opfamily_member(opfamily, opcintype, opcintype,
+								 eqstrategy);
+
+	if (!OidIsValid(ppeqop))
+		elog(ERROR, "missing operator %d(%u,%u) in opfamily %u",
+			 eqstrategy, opcintype, opcintype, opfamily);
+
+	/*
+	 * Are there equality operators that take exactly the FK type? Assume
+	 * we should look through any domain here.
+	 */
+	fktyped = getBaseType(fktype);
+
+	pfeqop = get_opfamily_member(opfamily, opcintype, fktyped,
+								 eqstrategy);
+	if (OidIsValid(pfeqop))
+	{
+		pfeqop_right = fktyped;
+		ffeqop = get_opfamily_member(opfamily, fktyped, fktyped,
+									 eqstrategy);
+	}
+	else
+	{
+		/* keep compiler quiet */
+		pfeqop_right = InvalidOid;
+		ffeqop = InvalidOid;
+	}
+
+	if (!(OidIsValid(pfeqop) && OidIsValid(ffeqop)))
+	{
+		/*
+		 * Otherwise, look for an implicit cast from the FK type to the
+		 * opcintype, and if found, use the primary equality operator.
+		 * This is a bit tricky because opcintype might be a polymorphic
+		 * type such as ANYARRAY or ANYENUM; so what we have to test is
+		 * whether the two actual column types can be concurrently cast to
+		 * that type.  (Otherwise, we'd fail to reject combinations such
+		 * as int[] and point[].)
+		 */
+		Oid			input_typeids[2];
+		Oid			target_typeids[2];
+
+		input_typeids[0] = pktype;
+		input_typeids[1] = fktype;
+		target_typeids[0] = opcintype;
+		target_typeids[1] = opcintype;
+		if (can_coerce_type(2, input_typeids, target_typeids,
+							COERCION_IMPLICIT))
+		{
+			pfeqop = ffeqop = ppeqop;
+			pfeqop_right = opcintype;
+		}
+	}
+
+	if (!(OidIsValid(pfeqop) && OidIsValid(ffeqop)))
+	{
+		char *fkattr_name;
+		char *pkattr_name;
+
+		if (for_overlaps)
+		{
+			fkattr_name = strVal(fkconstraint->fk_period);
+			pkattr_name = strVal(fkconstraint->pk_period);
+		}
+		else
+		{
+			fkattr_name = strVal(list_nth(fkconstraint->fk_attrs, i));
+			pkattr_name = strVal(list_nth(fkconstraint->pk_attrs, i));
+		}
+
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("foreign key constraint \"%s\" cannot be implemented",
+						fkconstraint->conname),
+				 errdetail("Key columns \"%s\" and \"%s\" "
+						   "are of incompatible types: %s and %s.",
+						   fkattr_name,
+						   pkattr_name,
+						   format_type_be(fktype),
+						   format_type_be(pktype))));
+	}
+
+	if (*old_check_ok)
+	{
+		/*
+		 * When a pfeqop changes, revalidate the constraint.  We could
+		 * permit intra-opfamily changes, but that adds subtle complexity
+		 * without any concrete benefit for core types.  We need not
+		 * assess ppeqop or ffeqop, which RI_Initial_Check() does not use.
+		 */
+		*old_check_ok = (pfeqop == lfirst_oid(*old_pfeqop_item));
+		*old_pfeqop_item = lnext(fkconstraint->old_conpfeqop,
+								*old_pfeqop_item);
+	}
+	if (*old_check_ok)
+	{
+		Oid			old_fktype;
+		Oid			new_fktype;
+		CoercionPathType old_pathtype;
+		CoercionPathType new_pathtype;
+		Oid			old_castfunc;
+		Oid			new_castfunc;
+		Form_pg_attribute attr = TupleDescAttr(tab->oldDesc,
+											   fkattnum[i] - 1);
+
+		/*
+		 * Identify coercion pathways from each of the old and new FK-side
+		 * column types to the right (foreign) operand type of the pfeqop.
+		 * We may assume that pg_constraint.conkey is not changing.
+		 */
+		old_fktype = attr->atttypid;
+		new_fktype = fktype;
+		old_pathtype = findFkeyCast(pfeqop_right, old_fktype,
+									&old_castfunc);
+		new_pathtype = findFkeyCast(pfeqop_right, new_fktype,
+									&new_castfunc);
+
+		/*
+		 * Upon a change to the cast from the FK column to its pfeqop
+		 * operand, revalidate the constraint.  For this evaluation, a
+		 * binary coercion cast is equivalent to no cast at all.  While
+		 * type implementors should design implicit casts with an eye
+		 * toward consistency of operations like equality, we cannot
+		 * assume here that they have done so.
+		 *
+		 * A function with a polymorphic argument could change behavior
+		 * arbitrarily in response to get_fn_expr_argtype().  Therefore,
+		 * when the cast destination is polymorphic, we only avoid
+		 * revalidation if the input type has not changed at all.  Given
+		 * just the core data types and operator classes, this requirement
+		 * prevents no would-be optimizations.
+		 *
+		 * If the cast converts from a base type to a domain thereon, then
+		 * that domain type must be the opcintype of the unique index.
+		 * Necessarily, the primary key column must then be of the domain
+		 * type.  Since the constraint was previously valid, all values on
+		 * the foreign side necessarily exist on the primary side and in
+		 * turn conform to the domain.  Consequently, we need not treat
+		 * domains specially here.
+		 *
+		 * Since we require that all collations share the same notion of
+		 * equality (which they do, because texteq reduces to bitwise
+		 * equality), we don't compare collation here.
+		 *
+		 * We need not directly consider the PK type.  It's necessarily
+		 * binary coercible to the opcintype of the unique index column,
+		 * and ri_triggers.c will only deal with PK datums in terms of
+		 * that opcintype.  Changing the opcintype also changes pfeqop.
+		 */
+		*old_check_ok = (new_pathtype == old_pathtype &&
+						new_castfunc == old_castfunc &&
+						(!IsPolymorphicType(pfeqop_right) ||
+						 new_fktype == old_fktype));
+
+	}
+
+	*pfeqopOut = pfeqop;
+	*ppeqopOut = ppeqop;
+	*ffeqopOut = ffeqop;
+}
+
 /*
  * When the parent of a partition receives [the referencing side of] a foreign
  * key, we must propagate that foreign key to the partition.  However, the
@@ -11337,6 +11482,38 @@ ATExecValidateConstraint(List **wqueue, Relation rel, char *constrName,
 	return address;
 }
 
+/*
+ * transformPeriodName - transform period name to its base columns
+ *
+ * Lookup the name and return zero as the column attnum. Set
+ * the period's rangetype (not the columns' type OIDs).
+ */
+static void
+transformPeriodName(Oid relId, Node *periodName,
+					int16 *attnums, Oid *atttypids, Oid *periodid)
+{
+	const char *periodStr = strVal(periodName);
+	HeapTuple pertuple = SearchSysCache2(PERIODNAME,
+					   ObjectIdGetDatum(relId),
+					   PointerGetDatum(periodStr));
+	Form_pg_period period;
+
+	if (!HeapTupleIsValid(pertuple))
+	{
+		attnums[0] = InvalidOid;
+		atttypids[0] = InvalidOid;
+		*periodid = InvalidOid;
+		return;
+	}
+
+	period = (Form_pg_period) GETSTRUCT(pertuple);
+
+	attnums[0] = InvalidOid;
+	atttypids[0] = period->perrngtype;
+	*periodid = period->oid;
+
+	ReleaseSysCache(pertuple);
+}
 
 /*
  * transformColumnNameList - transform list of column names
@@ -11362,6 +11539,7 @@ transformColumnNameList(Oid relId, List *colList,
 					(errcode(ERRCODE_UNDEFINED_COLUMN),
 					 errmsg("column \"%s\" referenced in foreign key constraint does not exist",
 							attname)));
+
 		if (attnum >= INDEX_MAX_KEYS)
 			ereport(ERROR,
 					(errcode(ERRCODE_TOO_MANY_COLUMNS),
@@ -11383,6 +11561,9 @@ transformColumnNameList(Oid relId, List *colList,
  *	for the pkrel.  Also return the index OID and index opclasses of the
  *	index supporting the primary key.  If this is a temporal primary key,
  *	also set the WITHOUT OVERLAPS attribute name, attnum, and atttypid.
+ *	For ranges we only set one thing for each of those. For periods we set
+ *	the start/end column names and attnums, but the typids we set to the
+ *	period's range type.
  *
  *	All parameters except pkrel are output parameters.  Also, the function
  *	return value is the number of attributes in the primary key,
@@ -11394,6 +11575,8 @@ static int
 transformFkeyGetPrimaryKey(Relation pkrel, Oid *indexOid,
 						   List **attnamelist,
 						   int16 *attnums, Oid *atttypids,
+						   Node **periodattname, Oid *periodid,
+						   int16 *periodattnums, Oid *periodatttypids,
 						   Oid *opclasses)
 {
 	List	   *indexoidlist;
@@ -11460,36 +11643,84 @@ transformFkeyGetPrimaryKey(Relation pkrel, Oid *indexOid,
 
 	/*
 	 * Now build the list of PK attributes from the indkey definition (we
-	 * assume a primary key cannot have expressional elements)
+	 * assume a primary key cannot have expressional elements, unless it
+	 * has a PERIOD)
 	 */
 	*attnamelist = NIL;
 	for (i = 0; i < indexStruct->indnkeyatts; i++)
 	{
 		int			pkattno = indexStruct->indkey.values[i];
 
-		attnums[i] = pkattno;
-		atttypids[i] = attnumTypeId(pkrel, pkattno);
-		opclasses[i] = indclass->values[i];
-		*attnamelist = lappend(*attnamelist,
-							   makeString(pstrdup(NameStr(*attnumAttName(pkrel, pkattno)))));
+		if (i == indexStruct->indnkeyatts - 1 && indexStruct->indisexclusion)
+		{
+			if (pkattno == InvalidOid)
+			{
+				/* we have a period */
+				HeapTuple periodTuple = SearchSysCache1(PERIODOID, ObjectIdGetDatum(indexStruct->indperiod));
+				Form_pg_period period;
+
+				if (!HeapTupleIsValid(periodTuple))
+					elog(ERROR, "cache lookup failed for period %u", indexStruct->indperiod);
+
+				period = (Form_pg_period) GETSTRUCT(periodTuple);
+				attnums[i] = InvalidOid;
+				periodattnums[0] = period->perstart;
+				periodattnums[1] = period->perend;
+				periodatttypids[0] = period->perrngtype;
+				periodatttypids[1] = period->perrngtype;
+				opclasses[i] = indclass->values[i];
+				*periodid = period->oid;
+				*periodattname = (Node *)makeString(pstrdup(NameStr(period->pername)));
+
+				ReleaseSysCache(periodTuple);
+			}
+			else {
+				/* we have a range */
+				/* The caller will set attnums[i] */
+				periodattnums[0] = pkattno;
+				periodatttypids[0] = attnumTypeId(pkrel, pkattno);
+				opclasses[i] = indclass->values[i];
+				*periodid = InvalidOid;
+				*periodattname = (Node *)makeString(pstrdup(NameStr(*attnumAttName(pkrel, pkattno))));
+			}
+		}
+		else
+		{
+			attnums[i] = pkattno;
+			atttypids[i] = attnumTypeId(pkrel, pkattno);
+			opclasses[i] = indclass->values[i];
+			*attnamelist = lappend(*attnamelist,
+								  makeString(pstrdup(NameStr(*attnumAttName(pkrel, pkattno)))));
+			*periodid = InvalidOid;
+		}
 	}
 
 	ReleaseSysCache(indexTuple);
 
-	return i;
+	if (indexStruct->indisexclusion) return i - 1;
+	else return i;
 }
 
 /*
  * transformFkeyCheckAttrs -
  *
  *	Make sure that the attributes of a referenced table belong to a unique
- *	(or primary key) constraint.  Return the OID of the index supporting
- *	the constraint, as well as the opclasses associated with the index
+ *	(or primary key) constraint.  Or if this is a temporal foreign key
+ *	the primary key should be an exclusion constraint instead.
+ *	It's okay to have a PERIOD referencing a range or a range referencing
+ *	a PERIOD, as long as the types match up. If the PK uses a PERIOD,
+ *	then periodid should be set and periodattnums should contain the
+ *	start and end attnums. Otherwise periodattnums should contain the
+ *	range attnum.
+ *	Return the OID of the index supporting the constraint,
+ *	as well as the opclasses associated with the index
  *	columns.
  */
 static Oid
 transformFkeyCheckAttrs(Relation pkrel,
 						int numattrs, int16 *attnums,
+						bool is_temporal, int16 *periodattnums,
+						Oid periodid,
 						Oid *opclasses) /* output parameter */
 {
 	Oid			indexoid = InvalidOid;
@@ -11499,6 +11730,7 @@ transformFkeyCheckAttrs(Relation pkrel,
 	ListCell   *indexoidscan;
 	int			i,
 				j;
+	bool		has_period = periodid != InvalidOid;
 
 	/*
 	 * Reject duplicate appearances of columns in the referenced-columns list.
@@ -11516,6 +11748,10 @@ transformFkeyCheckAttrs(Relation pkrel,
 						(errcode(ERRCODE_INVALID_FOREIGN_KEY),
 						 errmsg("foreign key referenced-columns list must not contain duplicates")));
 		}
+		if (is_temporal && attnums[i] == periodattnums[0])
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_FOREIGN_KEY),
+					 errmsg("foreign key referenced-columns list must not contain duplicates")));
 	}
 
 	/*
@@ -11537,15 +11773,19 @@ transformFkeyCheckAttrs(Relation pkrel,
 		indexStruct = (Form_pg_index) GETSTRUCT(indexTuple);
 
 		/*
-		 * Must have the right number of columns; must be unique and not a
-		 * partial index; forget it if there are any expressions, too. Invalid
-		 * indexes are out as well.
+		 * Must have the right number of columns; must be unique
+		 * (or if temporal then exclusion instead) and not a
+		 * partial index; forget it if there are any expressions, too (unless
+		 * it has a PERIOD). Invalid indexes are out as well.
 		 */
-		if (indexStruct->indnkeyatts == numattrs &&
-			indexStruct->indisunique &&
+		if ((is_temporal
+			  ? (indexStruct->indnkeyatts == numattrs + 1 &&
+				 indexStruct->indisexclusion)
+			  : (indexStruct->indnkeyatts == numattrs &&
+				 indexStruct->indisunique)) &&
 			indexStruct->indisvalid &&
 			heap_attisnull(indexTuple, Anum_pg_index_indpred, NULL) &&
-			heap_attisnull(indexTuple, Anum_pg_index_indexprs, NULL))
+			(indexStruct->indperiod != InvalidOid || heap_attisnull(indexTuple, Anum_pg_index_indexprs, NULL)))
 		{
 			Datum		indclassDatum;
 			bool		isnull;
@@ -11582,6 +11822,32 @@ transformFkeyCheckAttrs(Relation pkrel,
 				if (!found)
 					break;
 			}
+			if (found && is_temporal)
+			{
+				if (has_period)
+				{
+					if (periodid == indexStruct->indperiod)
+					{
+						found = true;
+						opclasses[numattrs] = indclass->values[numattrs - 1];
+					}
+					else
+						found = false;
+				}
+				else
+				{
+					found = false;
+					for (j = 0; j < numattrs + 1; j++)
+					{
+						if (periodattnums[0] == indexStruct->indkey.values[j])
+						{
+							opclasses[numattrs] = indclass->values[j];
+							found = true;
+							break;
+						}
+					}
+				}
+			}
 
 			/*
 			 * Refuse to use a deferrable unique/primary key.  This is per SQL
@@ -11691,7 +11957,8 @@ validateForeignKeyConstraint(char *conname,
 							 Relation rel,
 							 Relation pkrel,
 							 Oid pkindOid,
-							 Oid constraintOid)
+							 Oid constraintOid,
+							 bool temporal)
 {
 	TupleTableSlot *slot;
 	TableScanDesc scan;
@@ -11721,8 +11988,10 @@ validateForeignKeyConstraint(char *conname,
 	/*
 	 * See if we can do it with a single LEFT JOIN query.  A false result
 	 * indicates we must proceed with the fire-the-trigger method.
+	 * We can't do a LEFT JOIN for temporal FKs yet,
+	 * but we can once we support temporal left joins.
 	 */
-	if (RI_Initial_Check(&trig, rel, pkrel))
+	if (!temporal && RI_Initial_Check(&trig, rel, pkrel))
 		return;
 
 	/*
@@ -11783,6 +12052,7 @@ CreateFKCheckTrigger(Oid myRelOid, Oid refRelOid, Constraint *fkconstraint,
 					 Oid constraintOid, Oid indexOid, bool on_insert)
 {
 	CreateTrigStmt *fk_trigger;
+	bool is_temporal = fkconstraint->fk_period;
 
 	/*
 	 * Note: for a self-referential FK (referencing and referenced tables are
@@ -11796,18 +12066,27 @@ CreateFKCheckTrigger(Oid myRelOid, Oid refRelOid, Constraint *fkconstraint,
 	fk_trigger = makeNode(CreateTrigStmt);
 	fk_trigger->replace = false;
 	fk_trigger->isconstraint = true;
-	fk_trigger->trigname = "RI_ConstraintTrigger_c";
+	if (is_temporal)
+		fk_trigger->trigname = "TRI_ConstraintTrigger_c";
+	else
+		fk_trigger->trigname = "RI_ConstraintTrigger_c";
 	fk_trigger->relation = NULL;
 
 	/* Either ON INSERT or ON UPDATE */
 	if (on_insert)
 	{
-		fk_trigger->funcname = SystemFuncName("RI_FKey_check_ins");
+		if (is_temporal)
+			fk_trigger->funcname = SystemFuncName("TRI_FKey_check_ins");
+		else
+			fk_trigger->funcname = SystemFuncName("RI_FKey_check_ins");
 		fk_trigger->events = TRIGGER_TYPE_INSERT;
 	}
 	else
 	{
-		fk_trigger->funcname = SystemFuncName("RI_FKey_check_upd");
+		if (is_temporal)
+			fk_trigger->funcname = SystemFuncName("TRI_FKey_check_upd");
+		else
+			fk_trigger->funcname = SystemFuncName("RI_FKey_check_upd");
 		fk_trigger->events = TRIGGER_TYPE_UPDATE;
 	}
 
@@ -11856,37 +12135,76 @@ createForeignKeyActionTriggers(Relation rel, Oid refRelOid, Constraint *fkconstr
 	fk_trigger->whenClause = NULL;
 	fk_trigger->transitionRels = NIL;
 	fk_trigger->constrrel = NULL;
-	switch (fkconstraint->fk_del_action)
+	if (fkconstraint->fk_period != NULL)
 	{
-		case FKCONSTR_ACTION_NOACTION:
-			fk_trigger->deferrable = fkconstraint->deferrable;
-			fk_trigger->initdeferred = fkconstraint->initdeferred;
-			fk_trigger->funcname = SystemFuncName("RI_FKey_noaction_del");
-			break;
-		case FKCONSTR_ACTION_RESTRICT:
-			fk_trigger->deferrable = false;
-			fk_trigger->initdeferred = false;
-			fk_trigger->funcname = SystemFuncName("RI_FKey_restrict_del");
-			break;
-		case FKCONSTR_ACTION_CASCADE:
-			fk_trigger->deferrable = false;
-			fk_trigger->initdeferred = false;
-			fk_trigger->funcname = SystemFuncName("RI_FKey_cascade_del");
-			break;
-		case FKCONSTR_ACTION_SETNULL:
-			fk_trigger->deferrable = false;
-			fk_trigger->initdeferred = false;
-			fk_trigger->funcname = SystemFuncName("RI_FKey_setnull_del");
-			break;
-		case FKCONSTR_ACTION_SETDEFAULT:
-			fk_trigger->deferrable = false;
-			fk_trigger->initdeferred = false;
-			fk_trigger->funcname = SystemFuncName("RI_FKey_setdefault_del");
-			break;
-		default:
-			elog(ERROR, "unrecognized FK action type: %d",
-				 (int) fkconstraint->fk_del_action);
-			break;
+		/* Temporal foreign keys */
+		switch (fkconstraint->fk_del_action)
+		{
+			case FKCONSTR_ACTION_NOACTION:
+				fk_trigger->deferrable = fkconstraint->deferrable;
+				fk_trigger->initdeferred = fkconstraint->initdeferred;
+				fk_trigger->funcname = SystemFuncName("TRI_FKey_noaction_del");
+				break;
+			case FKCONSTR_ACTION_RESTRICT:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				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:
+				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",
+					 (int) fkconstraint->fk_del_action);
+				break;
+		}
+	}
+	else
+	{
+		switch (fkconstraint->fk_del_action)
+		{
+			case FKCONSTR_ACTION_NOACTION:
+				fk_trigger->deferrable = fkconstraint->deferrable;
+				fk_trigger->initdeferred = fkconstraint->initdeferred;
+				fk_trigger->funcname = SystemFuncName("RI_FKey_noaction_del");
+				break;
+			case FKCONSTR_ACTION_RESTRICT:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("RI_FKey_restrict_del");
+				break;
+			case FKCONSTR_ACTION_CASCADE:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("RI_FKey_cascade_del");
+				break;
+			case FKCONSTR_ACTION_SETNULL:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("RI_FKey_setnull_del");
+				break;
+			case FKCONSTR_ACTION_SETDEFAULT:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("RI_FKey_setdefault_del");
+				break;
+			default:
+				elog(ERROR, "unrecognized FK action type: %d",
+					 (int) fkconstraint->fk_del_action);
+				break;
+		}
 	}
 
 	(void) CreateTrigger(fk_trigger, NULL, refRelOid, RelationGetRelid(rel),
@@ -11913,37 +12231,76 @@ createForeignKeyActionTriggers(Relation rel, Oid refRelOid, Constraint *fkconstr
 	fk_trigger->whenClause = NULL;
 	fk_trigger->transitionRels = NIL;
 	fk_trigger->constrrel = NULL;
-	switch (fkconstraint->fk_upd_action)
+	if (fkconstraint->fk_period != NULL)
 	{
-		case FKCONSTR_ACTION_NOACTION:
-			fk_trigger->deferrable = fkconstraint->deferrable;
-			fk_trigger->initdeferred = fkconstraint->initdeferred;
-			fk_trigger->funcname = SystemFuncName("RI_FKey_noaction_upd");
-			break;
-		case FKCONSTR_ACTION_RESTRICT:
-			fk_trigger->deferrable = false;
-			fk_trigger->initdeferred = false;
-			fk_trigger->funcname = SystemFuncName("RI_FKey_restrict_upd");
-			break;
-		case FKCONSTR_ACTION_CASCADE:
-			fk_trigger->deferrable = false;
-			fk_trigger->initdeferred = false;
-			fk_trigger->funcname = SystemFuncName("RI_FKey_cascade_upd");
-			break;
-		case FKCONSTR_ACTION_SETNULL:
-			fk_trigger->deferrable = false;
-			fk_trigger->initdeferred = false;
-			fk_trigger->funcname = SystemFuncName("RI_FKey_setnull_upd");
-			break;
-		case FKCONSTR_ACTION_SETDEFAULT:
-			fk_trigger->deferrable = false;
-			fk_trigger->initdeferred = false;
-			fk_trigger->funcname = SystemFuncName("RI_FKey_setdefault_upd");
-			break;
-		default:
-			elog(ERROR, "unrecognized FK action type: %d",
-				 (int) fkconstraint->fk_upd_action);
-			break;
+		/* Temporal foreign keys */
+		switch (fkconstraint->fk_upd_action)
+		{
+			case FKCONSTR_ACTION_NOACTION:
+				fk_trigger->deferrable = fkconstraint->deferrable;
+				fk_trigger->initdeferred = fkconstraint->initdeferred;
+				fk_trigger->funcname = SystemFuncName("TRI_FKey_noaction_upd");
+				break;
+			case FKCONSTR_ACTION_RESTRICT:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				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:
+				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",
+					 (int) fkconstraint->fk_upd_action);
+				break;
+		}
+	}
+	else
+	{
+		switch (fkconstraint->fk_upd_action)
+		{
+			case FKCONSTR_ACTION_NOACTION:
+				fk_trigger->deferrable = fkconstraint->deferrable;
+				fk_trigger->initdeferred = fkconstraint->initdeferred;
+				fk_trigger->funcname = SystemFuncName("RI_FKey_noaction_upd");
+				break;
+			case FKCONSTR_ACTION_RESTRICT:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("RI_FKey_restrict_upd");
+				break;
+			case FKCONSTR_ACTION_CASCADE:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("RI_FKey_cascade_upd");
+				break;
+			case FKCONSTR_ACTION_SETNULL:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("RI_FKey_setnull_upd");
+				break;
+			case FKCONSTR_ACTION_SETDEFAULT:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("RI_FKey_setdefault_upd");
+				break;
+			default:
+				elog(ERROR, "unrecognized FK action type: %d",
+					 (int) fkconstraint->fk_upd_action);
+				break;
+		}
 	}
 
 	(void) CreateTrigger(fk_trigger, NULL, refRelOid, RelationGetRelid(rel),
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index a503d638be..cd5653e317 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -839,6 +839,7 @@ CreateTriggerFiringOn(CreateTrigStmt *stmt, const char *queryString,
 											  true, /* noinherit */
 											  false, /* contemporal */
 											  InvalidOid, /* conperiod */
+											  InvalidOid, /* confperiod */
 											  isInternal);	/* is_internal */
 	}
 
@@ -3885,6 +3886,7 @@ afterTriggerAddEvent(AfterTriggerEventList *events,
 			newshared->ats_relid == evtshared->ats_relid &&
 			newshared->ats_event == evtshared->ats_event &&
 			newshared->ats_table == evtshared->ats_table &&
+			newshared->for_portion_of == evtshared->for_portion_of &&
 			newshared->ats_firing_id == 0)
 			break;
 	}
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index ec48ac32c7..1bb400a482 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -3554,6 +3554,7 @@ domainAddConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid,
 							  false,	/* connoinherit */
 							  false,	/* contemporal */
 							  InvalidOid, /* conperiod */
+							  InvalidOid, /* confperiod */
 							  false);	/* is_internal */
 	if (constrAddr)
 		ObjectAddressSet(*constrAddr, ConstraintRelationId, ccoid);
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 833212fb7f..d34e590f84 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3118,7 +3118,9 @@ _copyConstraint(const Constraint *from)
 	COPY_NODE_FIELD(where_clause);
 	COPY_NODE_FIELD(pktable);
 	COPY_NODE_FIELD(fk_attrs);
+	COPY_NODE_FIELD(fk_period);
 	COPY_NODE_FIELD(pk_attrs);
+	COPY_NODE_FIELD(pk_period);
 	COPY_SCALAR_FIELD(fk_matchtype);
 	COPY_SCALAR_FIELD(fk_upd_action);
 	COPY_SCALAR_FIELD(fk_del_action);
@@ -3220,6 +3222,7 @@ _copyQuery(const Query *from)
 	COPY_SCALAR_FIELD(canSetTag);
 	COPY_NODE_FIELD(utilityStmt);
 	COPY_SCALAR_FIELD(resultRelation);
+	COPY_NODE_FIELD(forPortionOf);
 	COPY_SCALAR_FIELD(hasAggs);
 	COPY_SCALAR_FIELD(hasWindowFuncs);
 	COPY_SCALAR_FIELD(hasTargetSRFs);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index d7ffb1d7bb..81a3cec904 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -499,11 +499,12 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <node>	TableElement TypedTableElement ConstraintElem TableFuncElement
 %type <node>	columnDef columnOptions
 %type <defelt>	def_elem reloption_elem old_aggr_elem operator_def_elem
-%type <node>	def_arg columnElem withoutOverlapsClause
+%type <node>	def_arg columnElem withoutOverlapsClause optionalPeriodName
 				where_clause where_or_current_clause
 				a_expr b_expr c_expr AexprConst indirection_el opt_slice_bound
 				columnref in_expr having_clause func_table xmltable array_expr
 				OptWhereClause operator_def_arg
+%type <list>	opt_column_and_period_list
 %type <list>	rowsfrom_item rowsfrom_list opt_col_def_list
 %type <boolean> opt_ordinality
 %type <list>	ExclusionConstraintList ExclusionConstraintElem
@@ -3941,19 +3942,21 @@ ConstraintElem:
 								   NULL, yyscanner);
 					$$ = (Node *)n;
 				}
-			| FOREIGN KEY '(' columnList ')' REFERENCES qualified_name
-				opt_column_list key_match key_actions ConstraintAttributeSpec
+			| FOREIGN KEY '(' columnList optionalPeriodName ')' REFERENCES qualified_name
+				opt_column_and_period_list key_match key_actions ConstraintAttributeSpec
 				{
 					Constraint *n = makeNode(Constraint);
 					n->contype = CONSTR_FOREIGN;
 					n->location = @1;
-					n->pktable = $7;
+					n->pktable = $8;
 					n->fk_attrs = $4;
-					n->pk_attrs = $8;
-					n->fk_matchtype = $9;
-					n->fk_upd_action = (char) ($10 >> 8);
-					n->fk_del_action = (char) ($10 & 0xFF);
-					processCASbits($11, @11, "FOREIGN KEY",
+					n->fk_period = $5;
+					n->pk_attrs = linitial($9);
+					n->pk_period = lsecond($9);
+					n->fk_matchtype = $10;
+					n->fk_upd_action = (char) ($11 >> 8);
+					n->fk_del_action = (char) ($11 & 0xFF);
+					processCASbits($12, @12, "FOREIGN KEY",
 								   &n->deferrable, &n->initdeferred,
 								   &n->skip_validation, NULL,
 								   yyscanner);
@@ -3981,6 +3984,16 @@ withoutOverlapsClause:
 			| /*EMPTY*/               { $$ = NULL; }
 	;
 
+optionalPeriodName:
+			',' PERIOD columnElem { $$ = $3; }
+			| /*EMPTY*/               { $$ = NULL; }
+	;
+
+opt_column_and_period_list:
+			'(' columnList optionalPeriodName ')'			{ $$ = list_make2($2, $3); }
+			| /*EMPTY*/								{ $$ = list_make2(NIL, NULL); }
+		;
+
 columnElem: ColId
 				{
 					$$ = (Node *) makeString($1);
diff --git a/src/backend/utils/adt/rangetypes.c b/src/backend/utils/adt/rangetypes.c
index 5a38178485..1594fe0e29 100644
--- a/src/backend/utils/adt/rangetypes.c
+++ b/src/backend/utils/adt/rangetypes.c
@@ -2130,6 +2130,35 @@ make_empty_range(TypeCacheEntry *typcache)
 	return make_range(typcache, &lower, &upper, true);
 }
 
+/*
+ * Convert a range to a string. This isn't used anywhere but it handy for 
+ * debugging. Perhaps we should remove it?
+ */
+char *
+range_as_string(RangeType *r)
+{
+  int16       typlen;
+  bool        typbyval;
+  char        typalign;
+  char        typdelim;
+  char		 *rangeStr;
+  Oid typioparam;
+  Oid range_out_oid;
+
+  get_type_io_data(
+		  RangeTypeGetOid(r),
+		  IOFunc_output,
+		  &typlen,
+		  &typbyval,
+		  &typalign,
+		  &typdelim,
+		  &typioparam,
+		  &range_out_oid);
+  rangeStr = OidOutputFunctionCall(range_out_oid, RangeTypePGetDatum(r));
+
+  return rangeStr;
+}
+
 
 /*
  *----------------------------------------------------------
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index 96269fc2ad..4606ee81d0 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -31,6 +31,8 @@
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
 #include "catalog/pg_operator.h"
+#include "catalog/pg_period.h"
+#include "catalog/pg_range.h"
 #include "catalog/pg_type.h"
 #include "commands/trigger.h"
 #include "executor/executor.h"
@@ -78,6 +80,10 @@
 #define RI_PLAN_RESTRICT_CHECKREF		5
 #define RI_PLAN_SETNULL_DOUPDATE		6
 #define RI_PLAN_SETDEFAULT_DOUPDATE		7
+#define TRI_PLAN_CASCADE_DEL_DODELETE	8
+#define TRI_PLAN_CASCADE_UPD_DOUPDATE	9
+#define TRI_PLAN_SETNULL_DOUPDATE		10
+#define TRI_PLAN_SETDEFAULT_DOUPDATE	11
 
 #define MAX_QUOTED_NAME_LEN  (NAMEDATALEN*2+3)
 #define MAX_QUOTED_REL_NAME_LEN  (MAX_QUOTED_NAME_LEN*2)
@@ -111,7 +117,18 @@ typedef struct RI_ConstraintInfo
 	char		confupdtype;	/* foreign key's ON UPDATE action */
 	char		confdeltype;	/* foreign key's ON DELETE action */
 	char		confmatchtype;	/* foreign key's match type */
+	bool		temporal;		/* if the foreign key is temporal */
 	int			nkeys;			/* number of key columns */
+	Oid			pk_period;		/* set if the temporal primary key has a period */
+	Oid			fk_period;		/* set if the temporal foreign key has a period */
+	Oid			pk_period_rangetype;		/* set if the temporal primary key has a period */
+	Oid			fk_period_rangetype;		/* set if the temporal foreign key has a period */
+	char		pk_period_rangetype_name[NAMEDATALEN];	/* pk period's rangetype's name */
+	char		fk_period_rangetype_name[NAMEDATALEN];	/* fk period's rangetype's name */
+	Oid			pk_period_collation;		/* pk period's rangetype's collation */
+	Oid			fk_period_collation;		/* fk period's rangetype's collation */
+	int16		pk_period_attnums[2];		/* attnums of the referenced period cols */
+	int16		fk_period_attnums[2];		/* attnums of the referencing period cols */
 	int16		pk_attnums[RI_MAX_NUMKEYS]; /* attnums of referenced cols */
 	int16		fk_attnums[RI_MAX_NUMKEYS]; /* attnums of referencing cols */
 	Oid			pf_eq_oprs[RI_MAX_NUMKEYS]; /* equality operators (PK = FK) */
@@ -181,6 +198,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);
+static Datum tri_set(TriggerData *trigdata, bool is_set_null);
 static void quoteOneName(char *buffer, const char *name);
 static void quoteRelationName(char *buffer, Relation rel);
 static void ri_GenerateQual(StringInfo buf,
@@ -194,7 +212,7 @@ static int	ri_NullCheck(TupleDesc tupdesc, TupleTableSlot *slot,
 static void ri_BuildQueryKey(RI_QueryKey *key,
 							 const RI_ConstraintInfo *riinfo,
 							 int32 constr_queryno);
-static bool ri_KeysEqual(Relation rel, TupleTableSlot *oldslot, TupleTableSlot *newslot,
+static bool ri_KeysStable(Relation rel, TupleTableSlot *oldslot, TupleTableSlot *newslot,
 						 const RI_ConstraintInfo *riinfo, bool rel_is_pk);
 static bool ri_AttributesEqual(Oid eq_opr, Oid typeid,
 							   Datum oldvalue, Datum newvalue);
@@ -209,6 +227,7 @@ static void ri_CheckTrigger(FunctionCallInfo fcinfo, const char *funcname,
 							int tgkind);
 static const RI_ConstraintInfo *ri_FetchConstraintInfo(Trigger *trigger,
 													   Relation trig_rel, bool rel_is_pk);
+static void DeconstructFkConstraintPeriod(Oid periodid, Oid *rangetypeid, char *rangetypename, Oid *rngcollation, int16 *attnums);
 static const RI_ConstraintInfo *ri_LoadConstraintInfo(Oid constraintOid);
 static Oid	get_ri_constraint_root(Oid constrOid);
 static SPIPlanPtr ri_PlanCheck(const char *querystr, int nargs, Oid *argtypes,
@@ -217,16 +236,99 @@ static bool ri_PerformCheck(const RI_ConstraintInfo *riinfo,
 							RI_QueryKey *qkey, SPIPlanPtr qplan,
 							Relation fk_rel, Relation pk_rel,
 							TupleTableSlot *oldslot, TupleTableSlot *newslot,
+							bool hasForPortionOf, Datum forPortionOf,
 							bool detectNewRows, int expect_OK);
 static void ri_ExtractValues(Relation rel, TupleTableSlot *slot,
 							 const RI_ConstraintInfo *riinfo, bool rel_is_pk,
+							 bool hasForPortionOf, Datum forPortionOf,
 							 Datum *vals, char *nulls);
 static void ri_ReportViolation(const RI_ConstraintInfo *riinfo,
 							   Relation pk_rel, Relation fk_rel,
 							   TupleTableSlot *violatorslot, TupleDesc tupdesc,
 							   int queryno, bool partgone) pg_attribute_noreturn();
+static Datum tupleRange(TupleTableSlot *slot, const RI_ConstraintInfo *riinfo);
 
 
+/*
+ * query_period_range
+ *
+ * Builds a SQL string to construct a range from a period's start and end columns.
+ */
+static char *
+query_period_range(const RI_ConstraintInfo *riinfo, Relation rel, const char *table_name, bool rel_is_pk)
+{
+	StringInfo str = makeStringInfo();
+	char attname[MAX_QUOTED_NAME_LEN + 3];
+	int table_name_len = strlen(table_name);
+
+	Assert(table_name_len <= 3);
+
+	strcpy(attname, table_name);
+	if (rel_is_pk)
+	{
+		appendStringInfo(str, "%s(", riinfo->pk_period_rangetype_name);
+
+		quoteOneName(attname + table_name_len,
+					 RIAttName(rel, riinfo->pk_period_attnums[0]));
+		appendStringInfo(str, "%s, ", attname);
+
+		quoteOneName(attname + table_name_len,
+					 RIAttName(rel, riinfo->pk_period_attnums[1]));
+		appendStringInfo(str, "%s)", attname);
+	}
+	else
+	{
+		appendStringInfo(str, "%s(", riinfo->fk_period_rangetype_name);
+
+		quoteOneName(attname + table_name_len,
+					 RIAttName(rel, riinfo->fk_period_attnums[0]));
+		appendStringInfo(str, "%s, ", attname);
+
+		quoteOneName(attname + table_name_len,
+					 RIAttName(rel, riinfo->fk_period_attnums[1]));
+		appendStringInfo(str, "%s)", attname);
+	}
+
+	return pstrdup(str->data);
+}
+
+
+static Datum
+build_period_range(const RI_ConstraintInfo *riinfo, TupleTableSlot *slot, bool rel_is_pk)
+{
+	const int16 *period_attnums = rel_is_pk ? riinfo->pk_period_attnums : riinfo->fk_period_attnums;
+	Oid	rangetype = rel_is_pk ? riinfo->pk_period_rangetype : riinfo->fk_period_rangetype;
+	Datum startvalue;
+	Datum endvalue;
+	Datum result;
+	bool	startisnull;
+	bool	endisnull;
+	LOCAL_FCINFO(fcinfo, 2);
+	FmgrInfo	flinfo;
+	FuncExpr   *f;
+
+	InitFunctionCallInfoData(*fcinfo, &flinfo, 2, InvalidOid, NULL, NULL);
+	f = makeNode(FuncExpr);
+	f->funcresulttype = rangetype;
+	flinfo.fn_expr = (Node *) f;
+	flinfo.fn_extra = NULL;
+
+	/* compute oldvalue */
+	startvalue = slot_getattr(slot, period_attnums[0], &startisnull);
+	endvalue = slot_getattr(slot, period_attnums[1], &endisnull);
+
+	fcinfo->args[0].value = startvalue;
+	fcinfo->args[0].isnull = startisnull;
+	fcinfo->args[1].value = endvalue;
+	fcinfo->args[1].isnull = endisnull;
+
+	result = range_constructor2(fcinfo);
+	if (fcinfo->isnull)
+		elog(ERROR, "function %u returned NULL", flinfo.fn_oid);
+
+	return result;
+}
+
 /*
  * RI_FKey_check -
  *
@@ -348,6 +450,7 @@ RI_FKey_check(TriggerData *trigdata)
 		StringInfoData querybuf;
 		char		pkrelname[MAX_QUOTED_REL_NAME_LEN];
 		char		attname[MAX_QUOTED_NAME_LEN];
+		char	   *attstr;
 		char		paramname[16];
 		const char *querysep;
 		Oid			queryoids[RI_MAX_NUMKEYS];
@@ -355,35 +458,86 @@ RI_FKey_check(TriggerData *trigdata)
 
 		/* ----------
 		 * The query string built is
-		 *	SELECT 1 FROM [ONLY] <pktable> x WHERE pkatt1 = $1 [AND ...]
-		 *		   FOR KEY SHARE OF x
+		 *	SELECT 1
+		 *	FROM [ONLY] <pktable> x WHERE pkatt1 = $1 [AND ...]
+		 *	FOR KEY SHARE OF x
 		 * The type id's for the $ parameters are those of the
 		 * corresponding FK attributes.
+		 *
+		 * But for temporal FKs we need to make sure
+		 * the FK's range is completely covered.
+		 * So we use this query instead:
+		 *  SELECT 1
+		 *	FROM	(
+		 *		SELECT pkperiodatt AS r
+		 *		FROM   [ONLY] pktable x
+		 *		WHERE  pkatt1 = $1 [AND ...]
+		 *		AND    pkperiodatt && $n
+		 *		FOR KEY SHARE OF x
+		 *	) x1
+		 *  HAVING $n <@ range_agg(x1.r)
+		 * Note if FOR KEY SHARE ever allows GROUP BY and HAVING
+		 * we can make this a bit simpler.
 		 * ----------
 		 */
 		initStringInfo(&querybuf);
 		pk_only = pk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ?
 			"" : "ONLY ";
 		quoteRelationName(pkrelname, pk_rel);
-		appendStringInfo(&querybuf, "SELECT 1 FROM %s%s x",
-						 pk_only, pkrelname);
+		if (riinfo->temporal)
+		{
+			if (riinfo->pk_period == InvalidOid)
+			{
+				quoteOneName(attname,
+						RIAttName(pk_rel, riinfo->pk_attnums[riinfo->nkeys - 1]));
+				attstr = attname;
+			}
+			else
+				attstr = query_period_range(riinfo, pk_rel, "x.", true);
+
+			appendStringInfo(&querybuf,
+					"SELECT 1 FROM (SELECT %s AS r FROM %s%s x",
+					attstr, pk_only, pkrelname);
+		}
+		else {
+			appendStringInfo(&querybuf, "SELECT 1 FROM %s%s x",
+							 pk_only, pkrelname);
+		}
 		querysep = "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_type;
+			Oid	fk_type;
+
+			if (riinfo->pk_attnums[i] == InvalidOid)
+			{
+				pk_type = riinfo->pk_period_rangetype;
+				attstr = query_period_range(riinfo, pk_rel, "x.", true);
+			}
+			else
+			{
+				pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]);
+				quoteOneName(attname,
+							 RIAttName(pk_rel, riinfo->pk_attnums[i]));
+				attstr = attname;
+			}
+
+			if (riinfo->fk_attnums[i] == InvalidOid)
+				fk_type = riinfo->fk_period_rangetype;
+			else
+				fk_type = RIAttType(fk_rel, riinfo->fk_attnums[i]);
 
-			quoteOneName(attname,
-						 RIAttName(pk_rel, riinfo->pk_attnums[i]));
 			sprintf(paramname, "$%d", i + 1);
 			ri_GenerateQual(&querybuf, querysep,
-							attname, pk_type,
+							attstr, pk_type,
 							riinfo->pf_eq_oprs[i],
 							paramname, fk_type);
 			querysep = "AND";
 			queryoids[i] = fk_type;
 		}
 		appendStringInfoString(&querybuf, " FOR KEY SHARE OF x");
+		if (riinfo->temporal)
+			appendStringInfo(&querybuf, ") x1 HAVING $%d <@ pg_catalog.range_agg(x1.r)", riinfo->nkeys);
 
 		/* Prepare and save the plan */
 		qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids,
@@ -400,6 +554,7 @@ RI_FKey_check(TriggerData *trigdata)
 	ri_PerformCheck(riinfo, &qkey, qplan,
 					fk_rel, pk_rel,
 					NULL, newslot,
+					false, 0,
 					pk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE,
 					SPI_OK_SELECT);
 
@@ -502,13 +657,24 @@ ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel,
 		querysep = "WHERE";
 		for (int i = 0; i < riinfo->nkeys; i++)
 		{
-			Oid			pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]);
+			Oid		pk_type;
+			char   *attstr;
+			if (riinfo->pk_attnums[i] == InvalidOid)
+			{
+				pk_type = riinfo->pk_period_rangetype;
+				attstr = query_period_range(riinfo, pk_rel, "x.", true);
+			}
+			else
+			{
+				pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]);
+				quoteOneName(attname,
+							 RIAttName(pk_rel, riinfo->pk_attnums[i]));
+				attstr = attname;
+			}
 
-			quoteOneName(attname,
-						 RIAttName(pk_rel, riinfo->pk_attnums[i]));
 			sprintf(paramname, "$%d", i + 1);
 			ri_GenerateQual(&querybuf, querysep,
-							attname, pk_type,
+							attstr, pk_type,
 							riinfo->pp_eq_oprs[i],
 							paramname, pk_type);
 			querysep = "AND";
@@ -527,6 +693,7 @@ ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel,
 	result = ri_PerformCheck(riinfo, &qkey, qplan,
 							 fk_rel, pk_rel,
 							 oldslot, NULL,
+							 false, 0,
 							 true,	/* treat like update */
 							 SPI_OK_SELECT);
 
@@ -667,6 +834,7 @@ ri_restrict(TriggerData *trigdata, bool is_no_action)
 		StringInfoData querybuf;
 		char		fkrelname[MAX_QUOTED_REL_NAME_LEN];
 		char		attname[MAX_QUOTED_NAME_LEN];
+		char	   *attstr;
 		char		paramname[16];
 		const char *querysep;
 		Oid			queryoids[RI_MAX_NUMKEYS];
@@ -689,18 +857,46 @@ ri_restrict(TriggerData *trigdata, bool is_no_action)
 		querysep = "WHERE";
 		for (int i = 0; i < riinfo->nkeys; i++)
 		{
-			Oid			pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]);
-			Oid			fk_type = RIAttType(fk_rel, riinfo->fk_attnums[i]);
-			Oid			pk_coll = RIAttCollation(pk_rel, riinfo->pk_attnums[i]);
-			Oid			fk_coll = RIAttCollation(fk_rel, riinfo->fk_attnums[i]);
+			Oid		pk_type;
+			Oid		fk_type;
+			Oid		pk_coll;
+			Oid		fk_coll;
 
-			quoteOneName(attname,
-						 RIAttName(fk_rel, riinfo->fk_attnums[i]));
+			if (riinfo->pk_attnums[i] != InvalidOid)
+			{
+				pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]);
+				pk_coll = RIAttCollation(pk_rel, riinfo->pk_attnums[i]);
+			}
+			else
+			{
+				pk_type = riinfo->pk_period_rangetype;
+				pk_coll = riinfo->pk_period_collation;
+			}
+
+			if (riinfo->fk_attnums[i] != InvalidOid)
+			{
+				fk_type = RIAttType(fk_rel, riinfo->fk_attnums[i]);
+				fk_coll = RIAttCollation(fk_rel, riinfo->fk_attnums[i]);
+			}
+			else
+			{
+				fk_type = riinfo->fk_period_rangetype;
+				fk_coll = riinfo->fk_period_collation;
+			}
+
+			if (riinfo->fk_attnums[i] == InvalidOid)
+				attstr = query_period_range(riinfo, fk_rel, "x.", false);
+			else
+			{
+				quoteOneName(attname,
+							 RIAttName(fk_rel, riinfo->fk_attnums[i]));
+				attstr = attname;
+			}
 			sprintf(paramname, "$%d", i + 1);
 			ri_GenerateQual(&querybuf, querysep,
 							paramname, pk_type,
 							riinfo->pf_eq_oprs[i],
-							attname, fk_type);
+							attstr, fk_type);
 			if (pk_coll != fk_coll && !get_collation_isdeterministic(pk_coll))
 				ri_GenerateQualCollation(&querybuf, pk_coll);
 			querysep = "AND";
@@ -719,6 +915,7 @@ ri_restrict(TriggerData *trigdata, bool is_no_action)
 	ri_PerformCheck(riinfo, &qkey, qplan,
 					fk_rel, pk_rel,
 					oldslot, NULL,
+					false, 0,
 					true,		/* must detect new rows */
 					SPI_OK_SELECT);
 
@@ -825,6 +1022,7 @@ RI_FKey_cascade_del(PG_FUNCTION_ARGS)
 	ri_PerformCheck(riinfo, &qkey, qplan,
 					fk_rel, pk_rel,
 					oldslot, NULL,
+					false, 0,
 					true,		/* must detect new rows */
 					SPI_OK_DELETE);
 
@@ -946,6 +1144,7 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS)
 	ri_PerformCheck(riinfo, &qkey, qplan,
 					fk_rel, pk_rel,
 					oldslot, newslot,
+					false, 0,
 					true,		/* must detect new rows */
 					SPI_OK_UPDATE);
 
@@ -1105,7 +1304,856 @@ ri_set(TriggerData *trigdata, bool is_set_null)
 			ri_GenerateQual(&qualbuf, qualsep,
 							paramname, pk_type,
 							riinfo->pf_eq_oprs[i],
-							attname, fk_type);
+							attname, fk_type);
+			if (pk_coll != fk_coll && !get_collation_isdeterministic(pk_coll))
+				ri_GenerateQualCollation(&querybuf, pk_coll);
+			querysep = ",";
+			qualsep = "AND";
+			queryoids[i] = pk_type;
+		}
+		appendBinaryStringInfo(&querybuf, qualbuf.data, qualbuf.len);
+
+		/* Prepare and save the plan */
+		qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids,
+							 &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,
+					false, 0,
+					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 -
+ *
+ * Check if we really need to fire the RI trigger for an update or delete to a PK
+ * relation.  This is called by the AFTER trigger queue manager to see if
+ * it can skip queuing an instance of an RI trigger.  Returns true if the
+ * trigger must be fired, false if we can prove the constraint will still
+ * be satisfied.
+ *
+ * newslot will be NULL if this is called for a delete.
+ */
+bool
+RI_FKey_pk_upd_check_required(Trigger *trigger, Relation pk_rel,
+							  TupleTableSlot *oldslot, TupleTableSlot *newslot)
+{
+	const RI_ConstraintInfo *riinfo;
+
+	riinfo = ri_FetchConstraintInfo(trigger, pk_rel, true);
+
+	/*
+	 * If any old key value is NULL, the row could not have been referenced by
+	 * an FK row, so no check is needed.
+	 */
+	if (ri_NullCheck(RelationGetDescr(pk_rel), oldslot, riinfo, true) != RI_KEYS_NONE_NULL)
+		return false;
+
+	/* If all old and new key values are equal, no check is needed */
+	if (newslot && ri_KeysStable(pk_rel, oldslot, newslot, riinfo, true))
+		return false;
+
+	/* Else we need to fire the trigger. */
+	return true;
+}
+
+/*
+ * RI_FKey_fk_upd_check_required -
+ *
+ * Check if we really need to fire the RI trigger for an update to an FK
+ * relation.  This is called by the AFTER trigger queue manager to see if
+ * it can skip queuing an instance of an RI trigger.  Returns true if the
+ * trigger must be fired, false if we can prove the constraint will still
+ * be satisfied.
+ */
+bool
+RI_FKey_fk_upd_check_required(Trigger *trigger, Relation fk_rel,
+							  TupleTableSlot *oldslot, TupleTableSlot *newslot)
+{
+	const RI_ConstraintInfo *riinfo;
+	int			ri_nullcheck;
+	Datum		xminDatum;
+	TransactionId xmin;
+	bool		isnull;
+
+	riinfo = ri_FetchConstraintInfo(trigger, fk_rel, false);
+
+	ri_nullcheck = ri_NullCheck(RelationGetDescr(fk_rel), newslot, riinfo, false);
+
+	/*
+	 * If all new key values are NULL, the row satisfies the constraint, so no
+	 * check is needed.
+	 */
+	if (ri_nullcheck == RI_KEYS_ALL_NULL)
+		return false;
+
+	/*
+	 * If some new key values are NULL, the behavior depends on the match
+	 * type.
+	 */
+	else if (ri_nullcheck == RI_KEYS_SOME_NULL)
+	{
+		switch (riinfo->confmatchtype)
+		{
+			case FKCONSTR_MATCH_SIMPLE:
+
+				/*
+				 * If any new key value is NULL, the row must satisfy the
+				 * constraint, so no check is needed.
+				 */
+				return false;
+
+			case FKCONSTR_MATCH_PARTIAL:
+
+				/*
+				 * Don't know, must run full check.
+				 */
+				break;
+
+			case FKCONSTR_MATCH_FULL:
+
+				/*
+				 * If some new key values are NULL, the row fails the
+				 * constraint.  We must not throw error here, because the row
+				 * might get invalidated before the constraint is to be
+				 * checked, but we should queue the event to apply the check
+				 * later.
+				 */
+				return true;
+		}
+	}
+
+	/*
+	 * Continues here for no new key values are NULL, or we couldn't decide
+	 * yet.
+	 */
+
+	/*
+	 * If the original row was inserted by our own transaction, we must fire
+	 * the trigger whether or not the keys are equal.  This is because our
+	 * UPDATE will invalidate the INSERT so that the INSERT RI trigger will
+	 * not do anything; so we had better do the UPDATE check.  (We could skip
+	 * this if we knew the INSERT trigger already fired, but there is no easy
+	 * way to know that.)
+	 */
+	xminDatum = slot_getsysattr(oldslot, MinTransactionIdAttributeNumber, &isnull);
+	Assert(!isnull);
+	xmin = DatumGetTransactionId(xminDatum);
+	if (TransactionIdIsCurrentTransactionId(xmin))
+		return true;
+
+	/* If all old and new key values are equal, no check is needed */
+	if (ri_KeysStable(fk_rel, oldslot, newslot, riinfo, false))
+		return false;
+
+	/* Else we need to fire the trigger. */
+	return true;
+}
+
+/* ----------
+ * TRI_FKey_check_ins -
+ *
+ *	Check temporal foreign key existence at insert event on FK table.
+ * ----------
+ */
+Datum
+TRI_FKey_check_ins(PG_FUNCTION_ARGS)
+{
+	/*
+	 * Check that this is a valid trigger call on the right time and event.
+	 */
+	ri_CheckTrigger(fcinfo, "RI_FKey_check_ins", RI_TRIGTYPE_INSERT);
+
+	/*
+	 * Share code with UPDATE case.
+	 */
+	return RI_FKey_check((TriggerData *) fcinfo->context);
+}
+
+
+/* ----------
+ * TRI_FKey_check_upd -
+ *
+ *	Check temporal foreign key existence at update event on FK table.
+ * ----------
+ */
+Datum
+TRI_FKey_check_upd(PG_FUNCTION_ARGS)
+{
+	/*
+	 * Check that this is a valid trigger call on the right time and event.
+	 */
+	ri_CheckTrigger(fcinfo, "RI_FKey_check_upd", RI_TRIGTYPE_UPDATE);
+
+	/*
+	 * Share code with INSERT case.
+	 */
+	return RI_FKey_check((TriggerData *) fcinfo->context);
+}
+
+
+/* ----------
+ * TRI_FKey_noaction_del -
+ *
+ *	Give an error and roll back the current transaction if the
+ *	delete has resulted in a violation of the given temporal
+ *	referential integrity constraint.
+ * ----------
+ */
+Datum
+TRI_FKey_noaction_del(PG_FUNCTION_ARGS)
+{
+	/*
+	 * Check that this is a valid trigger call on the right time and event.
+	 */
+	ri_CheckTrigger(fcinfo, "TRI_FKey_noaction_del", RI_TRIGTYPE_DELETE);
+
+	/*
+	 * Share code with RESTRICT/UPDATE cases.
+	 */
+	return ri_restrict((TriggerData *) fcinfo->context, true);
+}
+
+/*
+ * TRI_FKey_restrict_del -
+ *
+ * Restrict delete from PK table to rows unreferenced by foreign key.
+ *
+ * The SQL standard intends that this referential action occur exactly when
+ * the delete is performed, rather than after.  This appears to be
+ * the only difference between "NO ACTION" and "RESTRICT".  In Postgres
+ * we still implement this as an AFTER trigger, but it's non-deferrable.
+ */
+Datum
+TRI_FKey_restrict_del(PG_FUNCTION_ARGS)
+{
+	/* Check that this is a valid trigger call on the right time and event. */
+	ri_CheckTrigger(fcinfo, "TRI_FKey_restrict_del", RI_TRIGTYPE_DELETE);
+
+	/* Share code with NO ACTION/UPDATE cases. */
+	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;
+
+	if (trigdata->tg_temporal)
+		targetRange = trigdata->tg_temporal->fp_targetRange;
+	else
+		targetRange = tupleRange(oldslot, riinfo);
+
+	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_DEL_DODELETE);
+
+	if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
+	{
+		bool		pkHasRange;
+		bool		fkHasRange;
+		StringInfoData querybuf;
+		char		fkrelname[MAX_QUOTED_REL_NAME_LEN];
+		char		attname[MAX_QUOTED_NAME_LEN];
+		char	   *attstr;
+		char		paramname[16];
+		const char *querysep;
+		Oid			queryoids[RI_MAX_NUMKEYS];
+		const char *fk_only;
+
+		pkHasRange = riinfo->pk_period == InvalidOid;
+		fkHasRange = riinfo->fk_period == InvalidOid;
+
+		/* ----------
+		 * 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);
+		if (fkHasRange)
+			quoteOneName(attname, RIAttName(fk_rel, riinfo->fk_attnums[riinfo->nkeys - 1]));
+		else
+			quoteOneName(attname, get_periodname(riinfo->fk_period, false));
+
+		// TODO: Better to find the greater of lower($n+1) and lower($1)
+		// and the lesser for upper($n+1) and upper($1),
+		// so we only delete what is being deleted from the pk table.
+		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;
+			Oid		fk_type;
+			Oid		pk_coll;
+			Oid		fk_coll;
+
+			if (riinfo->pk_attnums[i] != InvalidOid)
+			{
+				pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]);
+				pk_coll = RIAttCollation(pk_rel, riinfo->pk_attnums[i]);
+			}
+			else
+			{
+				pk_type = riinfo->pk_period_rangetype;
+				pk_coll = riinfo->pk_period_collation;
+			}
+
+			if (riinfo->fk_attnums[i] != InvalidOid)
+			{
+				fk_type = RIAttType(fk_rel, riinfo->fk_attnums[i]);
+				fk_coll = RIAttCollation(fk_rel, riinfo->fk_attnums[i]);
+			}
+			else
+			{
+				fk_type = riinfo->fk_period_rangetype;
+				fk_coll = riinfo->fk_period_collation;
+			}
+
+			if (riinfo->fk_attnums[i] == InvalidOid)
+				attstr = query_period_range(riinfo, fk_rel, "", false);
+			else
+			{
+				quoteOneName(attname,
+							 RIAttName(fk_rel, riinfo->fk_attnums[i]));
+				attstr = attname;
+			}
+			sprintf(paramname, "$%d", i + 1);
+			ri_GenerateQual(&querybuf, querysep,
+							paramname, pk_type,
+							riinfo->pf_eq_oprs[i],
+							attstr, fk_type);
+			if (pk_coll != fk_coll && !get_collation_isdeterministic(pk_coll))
+				ri_GenerateQualCollation(&querybuf, pk_coll);
+			querysep = "AND";
+			queryoids[i] = pk_type;
+		}
+
+		if (pkHasRange)
+			queryoids[riinfo->nkeys] = RIAttType(pk_rel, riinfo->pk_attnums[riinfo->nkeys - 1]);
+		else
+			queryoids[riinfo->nkeys] = riinfo->pk_period_rangetype;
+
+		/* 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,
+					true, 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 -
+ *
+ * Give an error and roll back the current transaction if the
+ * update has resulted in a violation of the given referential
+ * integrity constraint.
+ */
+Datum
+TRI_FKey_noaction_upd(PG_FUNCTION_ARGS)
+{
+	/* Check that this is a valid trigger call on the right time and event. */
+	ri_CheckTrigger(fcinfo, "TRI_FKey_noaction_upd", RI_TRIGTYPE_UPDATE);
+
+	/* Share code with RESTRICT/DELETE cases. */
+	return ri_restrict((TriggerData *) fcinfo->context, true);
+}
+
+/*
+ * TRI_FKey_restrict_upd -
+ *
+ * Restrict update of PK to rows unreferenced by foreign key.
+ *
+ * The SQL standard intends that this referential action occur exactly when
+ * the update is performed, rather than after.  This appears to be
+ * the only difference between "NO ACTION" and "RESTRICT".  In Postgres
+ * we still implement this as an AFTER trigger, but it's non-deferrable.
+ */
+Datum
+TRI_FKey_restrict_upd(PG_FUNCTION_ARGS)
+{
+	/* Check that this is a valid trigger call on the right time and event. */
+	ri_CheckTrigger(fcinfo, "TRI_FKey_restrict_upd", RI_TRIGTYPE_UPDATE);
+
+	/* Share code with NO ACTION/DELETE cases. */
+	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;
+
+	if (trigdata->tg_temporal)
+		targetRange = trigdata->tg_temporal->fp_targetRange;
+	else
+		targetRange = tupleRange(oldslot, riinfo);
+
+	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_UPD_DOUPDATE);
+
+	if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
+	{
+		bool		pkHasRange;
+		bool		fkHasRange;
+		StringInfoData querybuf;
+		StringInfoData qualbuf;
+		char		fkrelname[MAX_QUOTED_REL_NAME_LEN];
+		char		attname[MAX_QUOTED_NAME_LEN];
+		char	   *attstr;
+		char		paramname[16];
+		const char *querysep;
+		const char *qualsep;
+		Oid			queryoids[RI_MAX_NUMKEYS];
+		const char *fk_only;
+
+		pkHasRange = riinfo->pk_period == InvalidOid;
+		fkHasRange = riinfo->fk_period == InvalidOid;
+
+		/* ----------
+		 * The query string built is
+		 *	UPDATE [ONLY] <fktable>
+		 *	        FOR PORTION OF $fkatt FROM lower($n+1) TO upper($n+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);
+		if (fkHasRange)
+			quoteOneName(attname, RIAttName(fk_rel, riinfo->fk_attnums[riinfo->nkeys - 1]));
+		else
+			quoteOneName(attname, get_periodname(riinfo->fk_period, false));
+
+		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;
+			Oid		fk_type;
+			Oid		pk_coll;
+			Oid		fk_coll;
+
+			if (riinfo->pk_attnums[i] != InvalidOid)
+			{
+				pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]);
+				pk_coll = RIAttCollation(pk_rel, riinfo->pk_attnums[i]);
+			}
+			else
+			{
+				pk_type = riinfo->pk_period_rangetype;
+				pk_coll = riinfo->pk_period_collation;
+			}
+
+			if (riinfo->fk_attnums[i] != InvalidOid)
+			{
+				fk_type = RIAttType(fk_rel, riinfo->fk_attnums[i]);
+				fk_coll = RIAttCollation(fk_rel, riinfo->fk_attnums[i]);
+			}
+			else
+			{
+				fk_type = riinfo->fk_period_rangetype;
+				fk_coll = riinfo->fk_period_collation;
+			}
+
+			if (riinfo->fk_attnums[i] == InvalidOid)
+				attstr = query_period_range(riinfo, fk_rel, "", false);
+			else
+			{
+				quoteOneName(attname,
+							 RIAttName(fk_rel, riinfo->fk_attnums[i]));
+				attstr = attname;
+			}
+			/*
+			 * Don't set the temporal column(s).
+			 * FOR PORTION OF will take care of that.
+			 * TODO: I guess I could omit it from the qual too, right? And likewise with cascade deletes?
+			 */
+			if (i < riinfo->nkeys - 1)
+				appendStringInfo(&querybuf,
+								 "%s %s = $%d",
+								 querysep, attstr, i + 1);
+			sprintf(paramname, "$%d", j + 1);
+			ri_GenerateQual(&qualbuf, qualsep,
+							paramname, pk_type,
+							riinfo->pf_eq_oprs[i],
+							attstr, 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);
+
+		if (pkHasRange)
+			queryoids[2 * riinfo->nkeys] = RIAttType(pk_rel, riinfo->pk_attnums[riinfo->nkeys - 1]);
+		else
+			queryoids[2 * riinfo->nkeys] = riinfo->pk_period_rangetype;
+
+		/* 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. Build up the arguments from the key values in the
+	 * updated PK tuple and update the referencing rows
+	 */
+	ri_PerformCheck(riinfo, &qkey, qplan,
+					fk_rel, pk_rel,
+					oldslot, newslot,
+					true, 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);
+}
+
+/*
+ * 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);
+}
+
+/*
+ * 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);
+}
+
+/*
+ * 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);
+}
+
+
+/*
+ * 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)
+{
+	const RI_ConstraintInfo *riinfo;
+	Relation	fk_rel;
+	Relation	pk_rel;
+	TupleTableSlot *oldslot;
+	RI_QueryKey qkey;
+	SPIPlanPtr	qplan;
+	Datum targetRange;
+
+	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;
+
+	// TODO: It would be more correct to take the range from oldslot,
+	// and then take its intersection with the tg_temporal range (if any):
+	// Likewise for cascade_upd and cascade_del.
+	// But I don't think this does any harm:
+	if (trigdata->tg_temporal)
+		targetRange = trigdata->tg_temporal->fp_targetRange;
+	else
+		targetRange = tupleRange(oldslot, riinfo);
+
+	if (SPI_connect() != SPI_OK_CONNECT)
+		elog(ERROR, "SPI_connect failed");
+
+	/*
+	 * Fetch or prepare a saved plan for the set null/default operation (it's
+	 * the same query for delete and update cases)
+	 */
+	ri_BuildQueryKey(&qkey, riinfo,
+					  is_set_null
+						? TRI_PLAN_SETNULL_DOUPDATE
+						: TRI_PLAN_SETDEFAULT_DOUPDATE);
+
+	if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
+	{
+		bool		pkHasRange;
+		bool		fkHasRange;
+		StringInfoData querybuf;
+		StringInfoData qualbuf;
+		char		fkrelname[MAX_QUOTED_REL_NAME_LEN];
+		char		attname[MAX_QUOTED_NAME_LEN];
+		char	   *attstr;
+		char		paramname[16];
+		const char *querysep;
+		const char *qualsep;
+		Oid			queryoids[RI_MAX_NUMKEYS];
+		const char *fk_only;
+
+		pkHasRange = riinfo->pk_period == InvalidOid;
+		fkHasRange = riinfo->fk_period == InvalidOid;
+
+		/* ----------
+		 * 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);
+		if (fkHasRange)
+			quoteOneName(attname, RIAttName(fk_rel, riinfo->fk_attnums[riinfo->nkeys - 1]));
+		else
+			quoteOneName(attname, get_periodname(riinfo->fk_period, false));
+
+		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);
+
+		querysep = "";
+		qualsep = "WHERE";
+		for (int i = 0; i < riinfo->nkeys; i++)
+		{
+			Oid		pk_type;
+			Oid		fk_type;
+			Oid		pk_coll;
+			Oid		fk_coll;
+
+			if (riinfo->pk_attnums[i] != InvalidOid)
+			{
+				pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]);
+				pk_coll = RIAttCollation(pk_rel, riinfo->pk_attnums[i]);
+			}
+			else
+			{
+				pk_type = riinfo->pk_period_rangetype;
+				pk_coll = riinfo->pk_period_collation;
+			}
+
+			if (riinfo->fk_attnums[i] != InvalidOid)
+			{
+				fk_type = RIAttType(fk_rel, riinfo->fk_attnums[i]);
+				fk_coll = RIAttCollation(fk_rel, riinfo->fk_attnums[i]);
+			}
+			else
+			{
+				fk_type = riinfo->fk_period_rangetype;
+				fk_coll = riinfo->fk_period_collation;
+			}
+
+			if (riinfo->fk_attnums[i] == InvalidOid)
+				attstr = query_period_range(riinfo, fk_rel, "", false);
+			else
+			{
+				quoteOneName(attname,
+							 RIAttName(fk_rel, riinfo->fk_attnums[i]));
+				attstr = attname;
+			}
+
+			/*
+			 * Don't set the temporal column(s).
+			 * FOR PORTION OF will take care of that.
+			 * TODO: I guess I could omit it from the qual too, right? And likewise with cascade deletes?
+			 */
+			if (i < riinfo->nkeys - 1)
+				appendStringInfo(&querybuf,
+								 "%s %s = %s",
+								 querysep, attstr,
+								 is_set_null ? "NULL" : "DEFAULT");
+			sprintf(paramname, "$%d", i + 1);
+			ri_GenerateQual(&qualbuf, qualsep,
+							paramname, pk_type,
+							riinfo->pf_eq_oprs[i],
+							attstr, fk_type);
 			if (pk_coll != fk_coll && !get_collation_isdeterministic(pk_coll))
 				ri_GenerateQualCollation(&querybuf, pk_coll);
 			querysep = ",";
@@ -1114,8 +2162,13 @@ ri_set(TriggerData *trigdata, bool is_set_null)
 		}
 		appendBinaryStringInfo(&querybuf, qualbuf.data, qualbuf.len);
 
+		if (pkHasRange)
+			queryoids[riinfo->nkeys] = RIAttType(pk_rel, riinfo->pk_attnums[riinfo->nkeys - 1]);
+		else
+			queryoids[riinfo->nkeys] = riinfo->pk_period_rangetype;
+
 		/* Prepare and save the plan */
-		qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids,
+		qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys + 1, queryoids,
 							 &qkey, fk_rel, pk_rel);
 	}
 
@@ -1125,6 +2178,7 @@ ri_set(TriggerData *trigdata, bool is_set_null)
 	ri_PerformCheck(riinfo, &qkey, qplan,
 					fk_rel, pk_rel,
 					oldslot, NULL,
+					true, targetRange,
 					true,		/* must detect new rows */
 					SPI_OK_UPDATE);
 
@@ -1155,132 +2209,6 @@ ri_set(TriggerData *trigdata, bool is_set_null)
 }
 
 
-/*
- * RI_FKey_pk_upd_check_required -
- *
- * Check if we really need to fire the RI trigger for an update or delete to a PK
- * relation.  This is called by the AFTER trigger queue manager to see if
- * it can skip queuing an instance of an RI trigger.  Returns true if the
- * trigger must be fired, false if we can prove the constraint will still
- * be satisfied.
- *
- * newslot will be NULL if this is called for a delete.
- */
-bool
-RI_FKey_pk_upd_check_required(Trigger *trigger, Relation pk_rel,
-							  TupleTableSlot *oldslot, TupleTableSlot *newslot)
-{
-	const RI_ConstraintInfo *riinfo;
-
-	riinfo = ri_FetchConstraintInfo(trigger, pk_rel, true);
-
-	/*
-	 * If any old key value is NULL, the row could not have been referenced by
-	 * an FK row, so no check is needed.
-	 */
-	if (ri_NullCheck(RelationGetDescr(pk_rel), oldslot, riinfo, true) != RI_KEYS_NONE_NULL)
-		return false;
-
-	/* If all old and new key values are equal, no check is needed */
-	if (newslot && ri_KeysEqual(pk_rel, oldslot, newslot, riinfo, true))
-		return false;
-
-	/* Else we need to fire the trigger. */
-	return true;
-}
-
-/*
- * RI_FKey_fk_upd_check_required -
- *
- * Check if we really need to fire the RI trigger for an update to an FK
- * relation.  This is called by the AFTER trigger queue manager to see if
- * it can skip queuing an instance of an RI trigger.  Returns true if the
- * trigger must be fired, false if we can prove the constraint will still
- * be satisfied.
- */
-bool
-RI_FKey_fk_upd_check_required(Trigger *trigger, Relation fk_rel,
-							  TupleTableSlot *oldslot, TupleTableSlot *newslot)
-{
-	const RI_ConstraintInfo *riinfo;
-	int			ri_nullcheck;
-	Datum		xminDatum;
-	TransactionId xmin;
-	bool		isnull;
-
-	riinfo = ri_FetchConstraintInfo(trigger, fk_rel, false);
-
-	ri_nullcheck = ri_NullCheck(RelationGetDescr(fk_rel), newslot, riinfo, false);
-
-	/*
-	 * If all new key values are NULL, the row satisfies the constraint, so no
-	 * check is needed.
-	 */
-	if (ri_nullcheck == RI_KEYS_ALL_NULL)
-		return false;
-
-	/*
-	 * If some new key values are NULL, the behavior depends on the match
-	 * type.
-	 */
-	else if (ri_nullcheck == RI_KEYS_SOME_NULL)
-	{
-		switch (riinfo->confmatchtype)
-		{
-			case FKCONSTR_MATCH_SIMPLE:
-
-				/*
-				 * If any new key value is NULL, the row must satisfy the
-				 * constraint, so no check is needed.
-				 */
-				return false;
-
-			case FKCONSTR_MATCH_PARTIAL:
-
-				/*
-				 * Don't know, must run full check.
-				 */
-				break;
-
-			case FKCONSTR_MATCH_FULL:
-
-				/*
-				 * If some new key values are NULL, the row fails the
-				 * constraint.  We must not throw error here, because the row
-				 * might get invalidated before the constraint is to be
-				 * checked, but we should queue the event to apply the check
-				 * later.
-				 */
-				return true;
-		}
-	}
-
-	/*
-	 * Continues here for no new key values are NULL, or we couldn't decide
-	 * yet.
-	 */
-
-	/*
-	 * If the original row was inserted by our own transaction, we must fire
-	 * the trigger whether or not the keys are equal.  This is because our
-	 * UPDATE will invalidate the INSERT so that the INSERT RI trigger will
-	 * not do anything; so we had better do the UPDATE check.  (We could skip
-	 * this if we knew the INSERT trigger already fired, but there is no easy
-	 * way to know that.)
-	 */
-	xminDatum = slot_getsysattr(oldslot, MinTransactionIdAttributeNumber, &isnull);
-	Assert(!isnull);
-	xmin = DatumGetTransactionId(xminDatum);
-	if (TransactionIdIsCurrentTransactionId(xmin))
-		return true;
-
-	/* If all old and new key values are equal, no check is needed */
-	if (ri_KeysEqual(fk_rel, oldslot, newslot, riinfo, false))
-		return false;
-
-	/* Else we need to fire the trigger. */
-	return true;
-}
 
 /*
  * RI_Initial_Check -
@@ -1306,6 +2234,8 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	StringInfoData querybuf;
 	char		pkrelname[MAX_QUOTED_REL_NAME_LEN];
 	char		fkrelname[MAX_QUOTED_REL_NAME_LEN];
+	char	   *pkattpart;
+	char	   *fkattpart;
 	char		pkattname[MAX_QUOTED_NAME_LEN + 3];
 	char		fkattname[MAX_QUOTED_NAME_LEN + 3];
 	RangeTblEntry *pkrte;
@@ -1387,10 +2317,24 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	sep = "";
 	for (int i = 0; i < riinfo->nkeys; i++)
 	{
-		quoteOneName(fkattname,
-					 RIAttName(fk_rel, riinfo->fk_attnums[i]));
-		appendStringInfo(&querybuf, "%sfk.%s", sep, fkattname);
-		sep = ", ";
+		if (riinfo->fk_attnums[i] != InvalidOid)
+		{
+			quoteOneName(fkattname,
+						 RIAttName(fk_rel, riinfo->fk_attnums[i]));
+			appendStringInfo(&querybuf, "%sfk.%s", sep, fkattname);
+			sep = ", ";
+		}
+		else
+		{
+			quoteOneName(fkattname,
+						 RIAttName(fk_rel, riinfo->fk_period_attnums[0]));
+			appendStringInfo(&querybuf, "%sfk.%s", sep, fkattname);
+			sep = ", ";
+
+			quoteOneName(fkattname,
+						 RIAttName(fk_rel, riinfo->fk_period_attnums[1]));
+			appendStringInfo(&querybuf, "%sfk.%s", sep, fkattname);
+		}
 	}
 
 	quoteRelationName(pkrelname, pk_rel);
@@ -1408,19 +2352,47 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	sep = "(";
 	for (int i = 0; i < riinfo->nkeys; i++)
 	{
-		Oid			pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]);
-		Oid			fk_type = RIAttType(fk_rel, riinfo->fk_attnums[i]);
-		Oid			pk_coll = RIAttCollation(pk_rel, riinfo->pk_attnums[i]);
-		Oid			fk_coll = RIAttCollation(fk_rel, riinfo->fk_attnums[i]);
+		Oid			pk_type;
+		Oid			fk_type;
+		Oid			pk_coll;
+		Oid			fk_coll;
+
+		if (riinfo->pk_attnums[i] != InvalidOid)
+		{
+			pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]);
+			pk_coll = RIAttCollation(pk_rel, riinfo->pk_attnums[i]);
+
+			quoteOneName(pkattname + 3,
+						 RIAttName(pk_rel, riinfo->pk_attnums[i]));
+			pkattpart = pkattname;
+		}
+		else
+		{
+			pk_type = riinfo->pk_period_rangetype;
+			pk_coll = riinfo->pk_period_collation;
+			pkattpart = query_period_range(riinfo, pk_rel, "pk.", true);
+		}
+
+		if (riinfo->fk_attnums[i] != InvalidOid)
+		{
+			fk_type = RIAttType(fk_rel, riinfo->fk_attnums[i]);
+			fk_coll = RIAttCollation(fk_rel, riinfo->fk_attnums[i]);
+
+			quoteOneName(fkattname + 3,
+						 RIAttName(fk_rel, riinfo->fk_attnums[i]));
+			fkattpart = fkattname;
+		}
+		else
+		{
+			fk_type = riinfo->fk_period_rangetype;
+			fk_coll = riinfo->fk_period_collation;
+			fkattpart = query_period_range(riinfo, pk_rel, "fk.", false);
+		}
 
-		quoteOneName(pkattname + 3,
-					 RIAttName(pk_rel, riinfo->pk_attnums[i]));
-		quoteOneName(fkattname + 3,
-					 RIAttName(fk_rel, riinfo->fk_attnums[i]));
 		ri_GenerateQual(&querybuf, sep,
-						pkattname, pk_type,
+						pkattpart, pk_type,
 						riinfo->pf_eq_oprs[i],
-						fkattname, fk_type);
+						fkattpart, fk_type);
 		if (pk_coll != fk_coll)
 			ri_GenerateQualCollation(&querybuf, pk_coll);
 		sep = "AND";
@@ -2034,6 +3006,43 @@ ri_FetchConstraintInfo(Trigger *trigger, Relation trig_rel, bool rel_is_pk)
 	return riinfo;
 }
 
+static void DeconstructFkConstraintPeriod(Oid periodid, Oid *rangetypeid, char *rangetypename, Oid *rngcollation, int16 *attnums)
+{
+	HeapTuple pertuple = SearchSysCache1(PERIODOID, periodid);
+	Form_pg_period period;
+	Form_pg_range rangetype;
+	Form_pg_type type;
+	HeapTuple rngtuple;
+	HeapTuple typtuple;
+
+	if (!HeapTupleIsValid(pertuple))
+		elog(ERROR, "cache lookup failed for period %d", periodid);
+
+	period = (Form_pg_period) GETSTRUCT(pertuple);
+
+	*rangetypeid = period->perrngtype;
+	attnums[0] = period->perstart;
+	attnums[1] = period->perend;
+	ReleaseSysCache(pertuple);
+
+	rngtuple = SearchSysCache1(RANGETYPE, *rangetypeid);
+	if (!HeapTupleIsValid(rngtuple))
+		elog(ERROR, "cache lookup failed for range %d", *rangetypeid);
+
+	rangetype = (Form_pg_range) GETSTRUCT(rngtuple);
+
+	*rngcollation = rangetype->rngcollation;
+	ReleaseSysCache(rngtuple);
+
+	typtuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(*rangetypeid));
+	if (!HeapTupleIsValid(typtuple))
+		elog(ERROR, "cache lookup failed for type %u", *rangetypeid);
+
+	type = (Form_pg_type) GETSTRUCT(typtuple);
+	strcpy(rangetypename, NameStr(type->typname));
+	ReleaseSysCache(typtuple);
+}
+
 /*
  * Fetch or create the RI_ConstraintInfo struct for an FK constraint.
  */
@@ -2057,6 +3066,7 @@ ri_LoadConstraintInfo(Oid constraintOid)
 	riinfo = (RI_ConstraintInfo *) hash_search(ri_constraint_cache,
 											   (void *) &constraintOid,
 											   HASH_ENTER, &found);
+
 	if (!found)
 		riinfo->valid = false;
 	else if (riinfo->valid)
@@ -2091,6 +3101,24 @@ ri_LoadConstraintInfo(Oid constraintOid)
 	riinfo->confupdtype = conForm->confupdtype;
 	riinfo->confdeltype = conForm->confdeltype;
 	riinfo->confmatchtype = conForm->confmatchtype;
+	riinfo->temporal = conForm->contemporal;
+	riinfo->pk_period = conForm->confperiod;
+	riinfo->pk_period_rangetype = InvalidOid;
+	riinfo->pk_period_collation = InvalidOid;
+	strcpy(riinfo->pk_period_rangetype_name, "");
+	memset(riinfo->pk_period_attnums, 0, sizeof(riinfo->pk_period_attnums));
+	riinfo->fk_period = conForm->conperiod;
+	riinfo->fk_period_rangetype = InvalidOid;
+	riinfo->fk_period_collation = InvalidOid;
+	strcpy(riinfo->fk_period_rangetype_name, "");
+	memset(riinfo->fk_period_attnums, 0, sizeof(riinfo->fk_period_attnums));
+
+	if (conForm->confperiod != InvalidOid)
+	{
+		DeconstructFkConstraintPeriod(conForm->confperiod, &riinfo->pk_period_rangetype, riinfo->pk_period_rangetype_name, &riinfo->pk_period_collation, riinfo->pk_period_attnums);
+	}
+	if (conForm->conperiod != InvalidOid)
+		DeconstructFkConstraintPeriod(conForm->conperiod, &riinfo->fk_period_rangetype, riinfo->fk_period_rangetype_name, &riinfo->fk_period_collation, riinfo->fk_period_attnums);
 
 	DeconstructFkConstraintRow(tup,
 							   &riinfo->nkeys,
@@ -2242,6 +3270,7 @@ ri_PerformCheck(const RI_ConstraintInfo *riinfo,
 				RI_QueryKey *qkey, SPIPlanPtr qplan,
 				Relation fk_rel, Relation pk_rel,
 				TupleTableSlot *oldslot, TupleTableSlot *newslot,
+				bool hasForPortionOf, Datum forPortionOf,
 				bool detectNewRows, int expect_OK)
 {
 	Relation	query_rel,
@@ -2287,15 +3316,16 @@ ri_PerformCheck(const RI_ConstraintInfo *riinfo,
 	if (newslot)
 	{
 		ri_ExtractValues(source_rel, newslot, riinfo, source_is_pk,
-						 vals, nulls);
+						 hasForPortionOf, forPortionOf, vals, nulls);
 		if (oldslot)
 			ri_ExtractValues(source_rel, oldslot, riinfo, source_is_pk,
+							 hasForPortionOf, forPortionOf,
 							 vals + riinfo->nkeys, nulls + riinfo->nkeys);
 	}
 	else
 	{
 		ri_ExtractValues(source_rel, oldslot, riinfo, source_is_pk,
-						 vals, nulls);
+						 hasForPortionOf, forPortionOf, vals, nulls);
 	}
 
 	/*
@@ -2377,6 +3407,7 @@ ri_PerformCheck(const RI_ConstraintInfo *riinfo,
 static void
 ri_ExtractValues(Relation rel, TupleTableSlot *slot,
 				 const RI_ConstraintInfo *riinfo, bool rel_is_pk,
+				 bool hasForPortionOf, Datum forPortionOf,
 				 Datum *vals, char *nulls)
 {
 	const int16 *attnums;
@@ -2389,8 +3420,22 @@ ri_ExtractValues(Relation rel, TupleTableSlot *slot,
 
 	for (int i = 0; i < riinfo->nkeys; i++)
 	{
-		vals[i] = slot_getattr(slot, attnums[i], &isnull);
-		nulls[i] = isnull ? 'n' : ' ';
+		if (attnums[i] != InvalidOid)
+		{
+			vals[i] = slot_getattr(slot, attnums[i], &isnull);
+			nulls[i] = isnull ? 'n' : ' ';
+		}
+		else
+		{
+			vals[i] = build_period_range(riinfo, slot, rel_is_pk);
+			nulls[i] = ' ';
+		}
+	}
+
+	if (hasForPortionOf)
+	{
+		vals[riinfo->nkeys] = forPortionOf;
+		nulls[riinfo->nkeys] = ' ';
 	}
 }
 
@@ -2413,6 +3458,7 @@ ri_ReportViolation(const RI_ConstraintInfo *riinfo,
 	StringInfoData key_values;
 	bool		onfk;
 	const int16 *attnums;
+	const int16 *period_attnums;
 	Oid			rel_oid;
 	AclResult	aclresult;
 	bool		has_perm = true;
@@ -2425,6 +3471,7 @@ ri_ReportViolation(const RI_ConstraintInfo *riinfo,
 	if (onfk)
 	{
 		attnums = riinfo->fk_attnums;
+		period_attnums = riinfo->fk_period_attnums;
 		rel_oid = fk_rel->rd_id;
 		if (tupdesc == NULL)
 			tupdesc = fk_rel->rd_att;
@@ -2432,6 +3479,7 @@ ri_ReportViolation(const RI_ConstraintInfo *riinfo,
 	else
 	{
 		attnums = riinfo->pk_attnums;
+		period_attnums = riinfo->pk_period_attnums;
 		rel_oid = pk_rel->rd_id;
 		if (tupdesc == NULL)
 			tupdesc = pk_rel->rd_att;
@@ -2484,12 +3532,18 @@ ri_ReportViolation(const RI_ConstraintInfo *riinfo,
 		for (int idx = 0; idx < riinfo->nkeys; idx++)
 		{
 			int			fnum = attnums[idx];
-			Form_pg_attribute att = TupleDescAttr(tupdesc, fnum - 1);
+			bool		has_period = fnum == InvalidOid;
 			char	   *name,
 					   *val;
+			Form_pg_attribute att;
 			Datum		datum;
 			bool		isnull;
 
+			if (has_period)
+				fnum = period_attnums[0];
+
+			att = TupleDescAttr(tupdesc, fnum - 1);
+
 			name = NameStr(att->attname);
 
 			datum = slot_getattr(violatorslot, fnum, &isnull);
@@ -2511,6 +3565,32 @@ ri_ReportViolation(const RI_ConstraintInfo *riinfo,
 			}
 			appendStringInfoString(&key_names, name);
 			appendStringInfoString(&key_values, val);
+
+			if (has_period)
+			{
+				fnum = period_attnums[1];
+
+				att = TupleDescAttr(tupdesc, fnum - 1);
+
+				name = NameStr(att->attname);
+
+				datum = slot_getattr(violatorslot, fnum, &isnull);
+				if (!isnull)
+				{
+					Oid			foutoid;
+					bool		typisvarlena;
+
+					getTypeOutputInfo(att->atttypid, &foutoid, &typisvarlena);
+					val = OidOutputFunctionCall(foutoid, datum);
+				}
+				else
+					val = "null";
+
+				appendStringInfoString(&key_names, ", ");
+				appendStringInfoString(&key_values, ", ");
+				appendStringInfoString(&key_names, name);
+				appendStringInfoString(&key_values, val);
+			}
 		}
 	}
 
@@ -2577,10 +3657,21 @@ ri_NullCheck(TupleDesc tupDesc,
 
 	for (int i = 0; i < riinfo->nkeys; i++)
 	{
-		if (slot_attisnull(slot, attnums[i]))
-			nonenull = false;
-		else
+		if (attnums[i] == InvalidOid)
+		{
+			/*
+			 * Never treat a period as null, because even if start and end
+			 * are both null, that just signifies an unbounded range.
+			 */
 			allnull = false;
+		}
+		else
+		{
+			if (slot_attisnull(slot, attnums[i]))
+				nonenull = false;
+			else
+				allnull = false;
+		}
 	}
 
 	if (allnull)
@@ -2711,9 +3802,12 @@ ri_HashPreparedPlan(RI_QueryKey *key, SPIPlanPtr plan)
 
 
 /*
- * ri_KeysEqual -
+ * ri_KeysStable -
  *
- * Check if all key values in OLD and NEW are equal.
+ * Check if all key values in OLD and NEW are "equivalent":
+ * For normal FKs we check for equality.
+ * For temporal FKs we check that the PK side is a superset of its old value,
+ * or the FK side is a subset.
  *
  * Note: at some point we might wish to redefine this as checking for
  * "IS NOT DISTINCT" rather than "=", that is, allow two nulls to be
@@ -2721,7 +3815,7 @@ ri_HashPreparedPlan(RI_QueryKey *key, SPIPlanPtr plan)
  * previously found at least one of the rows to contain no nulls.
  */
 static bool
-ri_KeysEqual(Relation rel, TupleTableSlot *oldslot, TupleTableSlot *newslot,
+ri_KeysStable(Relation rel, TupleTableSlot *oldslot, TupleTableSlot *newslot,
 			 const RI_ConstraintInfo *riinfo, bool rel_is_pk)
 {
 	const int16 *attnums;
@@ -2738,45 +3832,72 @@ ri_KeysEqual(Relation rel, TupleTableSlot *oldslot, TupleTableSlot *newslot,
 		Datum		newvalue;
 		bool		isnull;
 
-		/*
-		 * Get one attribute's oldvalue. If it is NULL - they're not equal.
-		 */
-		oldvalue = slot_getattr(oldslot, attnums[i], &isnull);
-		if (isnull)
-			return false;
-
-		/*
-		 * Get one attribute's newvalue. If it is NULL - they're not equal.
-		 */
-		newvalue = slot_getattr(newslot, attnums[i], &isnull);
-		if (isnull)
-			return false;
-
-		if (rel_is_pk)
+		if (riinfo->temporal && attnums[i] == InvalidOid)
 		{
 			/*
-			 * If we are looking at the PK table, then do a bytewise
-			 * comparison.  We must propagate PK changes if the value is
-			 * changed to one that "looks" different but would compare as
-			 * equal using the equality operator.  This only makes a
-			 * difference for ON UPDATE CASCADE, but for consistency we treat
-			 * all changes to the PK the same.
+			 * We have a period, so we have to get the start/end columns
+			 * and build a range.
 			 */
-			Form_pg_attribute att = TupleDescAttr(oldslot->tts_tupleDescriptor, attnums[i] - 1);
-
-			if (!datum_image_eq(oldvalue, newvalue, att->attbyval, att->attlen))
-				return false;
+			oldvalue = build_period_range(riinfo, oldslot, rel_is_pk);
+			newvalue = build_period_range(riinfo, newslot, rel_is_pk);
 		}
 		else
 		{
 			/*
-			 * For the FK table, compare with the appropriate equality
-			 * operator.  Changes that compare equal will still satisfy the
-			 * constraint after the update.
+			 * Get one attribute's oldvalue. If it is NULL - they're not equal.
+			 */
+			oldvalue = slot_getattr(oldslot, attnums[i], &isnull);
+			if (isnull)
+				return false;
+
+			/*
+			 * Get one attribute's newvalue. If it is NULL - they're not equal.
 			 */
-			if (!ri_AttributesEqual(riinfo->ff_eq_oprs[i], RIAttType(rel, attnums[i]),
-									oldvalue, newvalue))
+			newvalue = slot_getattr(newslot, attnums[i], &isnull);
+			if (isnull)
 				return false;
+
+		}
+
+		if (rel_is_pk)
+		{
+			if (riinfo->temporal && i == riinfo->nkeys - 1)
+			{
+				return DatumGetBool(DirectFunctionCall2(range_contains, newvalue, oldvalue));
+			}
+			else
+			{
+				/*
+				 * If we are looking at the PK table, then do a bytewise
+				 * comparison.  We must propagate PK changes if the value is
+				 * changed to one that "looks" different but would compare as
+				 * equal using the equality operator.  This only makes a
+				 * difference for ON UPDATE CASCADE, but for consistency we treat
+				 * all changes to the PK the same.
+				 */
+				Form_pg_attribute att = TupleDescAttr(oldslot->tts_tupleDescriptor, attnums[i] - 1);
+
+				if (!datum_image_eq(oldvalue, newvalue, att->attbyval, att->attlen))
+					return false;
+			}
+		}
+		else
+		{
+			if (riinfo->temporal && i == riinfo->nkeys - 1)
+			{
+				return DatumGetBool(DirectFunctionCall2(range_contains, oldvalue, newvalue));
+			}
+			else
+			{
+				/*
+				 * For the FK table, compare with the appropriate equality
+				 * operator.  Changes that compare equal will still satisfy the
+				 * constraint after the update.
+				 */
+				if (!ri_AttributesEqual(riinfo->ff_eq_oprs[i], RIAttType(rel, attnums[i]),
+										oldvalue, newvalue))
+					return false;
+			}
 		}
 	}
 
@@ -2950,3 +4071,17 @@ RI_FKey_trigger_type(Oid tgfoid)
 
 	return RI_TRIGGER_NONE;
 }
+
+static Datum
+tupleRange(TupleTableSlot *slot, const RI_ConstraintInfo *riinfo)
+{
+	bool	isnull;
+	int16	attnum = riinfo->pk_attnums[riinfo->nkeys - 1];
+
+	if (attnum == InvalidOid)
+		/* Build a range from the PERIOD start and end columns */
+		return build_period_range(riinfo, slot, true);
+	else
+		/* Get the range from the range column */
+		return slot_getattr(slot, attnum, &isnull);
+}
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index a768e4f55f..59ed0aaf09 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -333,7 +333,7 @@ static char *pg_get_viewdef_worker(Oid viewoid,
 								   int prettyFlags, int wrapColumn);
 static char *pg_get_triggerdef_worker(Oid trigid, bool pretty);
 static int	decompile_column_index_array(Datum column_index_array, Oid relId, Oid indexId,
-										 bool withoutOverlaps, Oid periodid, StringInfo buf);
+										 bool withoutOverlaps, bool withPeriod, Oid periodid, StringInfo buf);
 static char *pg_get_ruledef_worker(Oid ruleoid, int prettyFlags);
 static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 									const Oid *excludeOps,
@@ -2196,7 +2196,12 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 					elog(ERROR, "null conkey for constraint %u",
 						 constraintId);
 
-				decompile_column_index_array(val, conForm->conrelid, conForm->conindid, false, InvalidOid, &buf);
+				/*
+				 * If it is a temporal foreign key
+				 * then it uses PERIOD (which may be a real range column
+				 * or may be a period).
+				 */
+				decompile_column_index_array(val, conForm->conrelid, conForm->conindid, false, conForm->contemporal, conForm->conperiod, &buf);
 
 				/* add foreign relation name */
 				appendStringInfo(&buf, ") REFERENCES %s(",
@@ -2210,7 +2215,7 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 					elog(ERROR, "null confkey for constraint %u",
 						 constraintId);
 
-				decompile_column_index_array(val, conForm->confrelid, conForm->conindid, false, InvalidOid, &buf);
+				decompile_column_index_array(val, conForm->confrelid, conForm->conindid, false, conForm->contemporal, conForm->confperiod, &buf);
 
 				appendStringInfoChar(&buf, ')');
 
@@ -2318,7 +2323,7 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 				indexId = conForm->conindid;
 				SysCacheGetAttr(CONSTROID, tup,
 						  Anum_pg_constraint_conexclop, &isnull);
-				keyatts = decompile_column_index_array(val, conForm->conrelid, indexId, !isnull, InvalidOid, &buf);
+				keyatts = decompile_column_index_array(val, conForm->conrelid, indexId, !isnull, false, InvalidOid, &buf);
 
 				appendStringInfoChar(&buf, ')');
 
@@ -2518,7 +2523,7 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
  */
 static int
 decompile_column_index_array(Datum column_index_array, Oid relId, Oid indexId,
-							 bool withoutOverlaps, Oid periodid, StringInfo buf)
+							 bool withoutOverlaps, bool withPeriod, Oid periodid, StringInfo buf)
 {
 	Datum	   *keys;
 	int			nKeys;
@@ -2567,6 +2572,8 @@ decompile_column_index_array(Datum column_index_array, Oid relId, Oid indexId,
 			appendStringInfoString(buf, quote_identifier(colName));
 		else if (withoutOverlaps && j == nKeys - 1)
 			appendStringInfo(buf, ", %s WITHOUT OVERLAPS", quote_identifier(colName));
+		else if (withPeriod && j == nKeys - 1)
+			appendStringInfo(buf, ", PERIOD %s", quote_identifier(colName));
 		else
 			appendStringInfo(buf, ", %s", quote_identifier(colName));
 	}
diff --git a/src/include/catalog/pg_constraint.h b/src/include/catalog/pg_constraint.h
index 6c6675b0f7..2babe42713 100644
--- a/src/include/catalog/pg_constraint.h
+++ b/src/include/catalog/pg_constraint.h
@@ -114,6 +114,7 @@ CATALOG(pg_constraint,2606,ConstraintRelationId)
 	bool		contemporal;
 
 	Oid			conperiod;		/* local PERIOD used in PK/FK constraint */
+	Oid			confperiod;		/* referenced foreign PERIOD */
 
 #ifdef CATALOG_VARLEN			/* variable-length fields start here */
 
@@ -129,19 +130,19 @@ CATALOG(pg_constraint,2606,ConstraintRelationId)
 	int16		confkey[1];
 
 	/*
-	 * If a foreign key, the OIDs of the PK = FK equality operators for each
+	 * If a foreign key, the OIDs of the PK = FK comparison operators for each
 	 * column of the constraint
 	 */
 	Oid			conpfeqop[1] BKI_LOOKUP(pg_operator);
 
 	/*
-	 * If a foreign key, the OIDs of the PK = PK equality operators for each
+	 * If a foreign key, the OIDs of the PK = PK comparison operators for each
 	 * column of the constraint (i.e., equality for the referenced columns)
 	 */
 	Oid			conppeqop[1] BKI_LOOKUP(pg_operator);
 
 	/*
-	 * If a foreign key, the OIDs of the FK = FK equality operators for each
+	 * If a foreign key, the OIDs of the FK = FK comparison operators for each
 	 * column of the constraint (i.e., equality for the referencing columns)
 	 */
 	Oid			conffeqop[1] BKI_LOOKUP(pg_operator);
@@ -176,7 +177,7 @@ DECLARE_INDEX(pg_constraint_conparentid_index, 2579, ConstraintParentIndexId, on
 
 /* conkey can contain zero (InvalidAttrNumber) if a whole-row Var is used */
 DECLARE_ARRAY_FOREIGN_KEY_OPT((conrelid, conkey), pg_attribute, (attrelid, attnum));
-DECLARE_ARRAY_FOREIGN_KEY((confrelid, confkey), pg_attribute, (attrelid, attnum));
+DECLARE_ARRAY_FOREIGN_KEY_OPT((confrelid, confkey), pg_attribute, (attrelid, attnum));
 
 #ifdef EXPOSE_TO_CLIENT_CODE
 
@@ -237,6 +238,7 @@ extern Oid	CreateConstraintEntry(const char *constraintName,
 								  bool conNoInherit,
 								  bool conTemporal,
 								  Oid period,
+								  Oid fperiod,
 								  bool is_internal);
 
 extern void RemoveConstraintById(Oid conId);
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 5537740256..3e503140eb 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -3883,6 +3883,50 @@
   prorettype => 'trigger', proargtypes => '',
   prosrc => 'RI_FKey_noaction_upd' },
 
+# Temporal referential integrity constraint triggers
+{ oid => '6122', descr => 'temporal referential integrity FOREIGN KEY ... REFERENCES',
+  proname => 'TRI_FKey_check_ins', provolatile => 'v', prorettype => 'trigger',
+  proargtypes => '', prosrc => 'TRI_FKey_check_ins' },
+{ 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 => '',
+  prosrc => 'TRI_FKey_restrict_del' },
+{ oid => '6127', descr => 'temporal referential integrity ON UPDATE RESTRICT',
+  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 => '',
+  prosrc => 'TRI_FKey_noaction_del' },
+{ oid => '6133', descr => 'temporal referential integrity ON UPDATE NO ACTION',
+  proname => 'TRI_FKey_noaction_upd', provolatile => 'v',
+  prorettype => 'trigger', proargtypes => '',
+  prosrc => 'TRI_FKey_noaction_upd' },
+
 # Temporal leftovers triggers
 { oid => '8157', descr => 'temporal leftovers trigger function',
   proname => 'FP_insert_leftovers', provolatile => 'v',
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index d2a59ca755..588f6407ae 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2338,7 +2338,9 @@ typedef struct Constraint
 	/* Fields used for FOREIGN KEY constraints: */
 	RangeVar   *pktable;		/* Primary key table */
 	List	   *fk_attrs;		/* Attributes of foreign key */
+	Node	   *fk_period;		/* String node naming Period or range column */
 	List	   *pk_attrs;		/* Corresponding attrs in PK table */
+	Node	   *pk_period;		/* String node naming Period or range column */
 	char		fk_matchtype;	/* FULL, PARTIAL, SIMPLE */
 	char		fk_upd_action;	/* ON UPDATE action */
 	char		fk_del_action;	/* ON DELETE action */
diff --git a/src/include/utils/rangetypes.h b/src/include/utils/rangetypes.h
index d8c1f1656a..fa18218bb2 100644
--- a/src/include/utils/rangetypes.h
+++ b/src/include/utils/rangetypes.h
@@ -150,5 +150,6 @@ extern bool range_split_internal(TypeCacheEntry *typcache, const RangeType *r1,
 extern void range_leftover_internal(TypeCacheEntry *typcache, const RangeType *r1,
 									const RangeType *r2, RangeType **output1,
 									RangeType **output2);
+extern char *range_as_string(RangeType *r);
 
 #endif							/* RANGETYPES_H */
diff --git a/src/test/regress/expected/for_portion_of.out b/src/test/regress/expected/for_portion_of.out
index c70baad685..590d3f0c83 100644
--- a/src/test/regress/expected/for_portion_of.out
+++ b/src/test/regress/expected/for_portion_of.out
@@ -50,6 +50,15 @@ FOR PORTION OF valid_at FROM '2018-06-01' TO MAXVALUE
 SET valid_at = '[1990-01-01,1999-01-01)'
 WHERE id = '[5,6)';
 ERROR:  multiple assignments to same column "valid_at"
+-- The wrong type fails
+UPDATE for_portion_of_test
+FOR PORTION OF valid_at FROM 1 TO 4
+SET name = 'nope'
+WHERE id = '[3,4)';
+ERROR:  function pg_catalog.tsrange(integer, integer) does not exist
+LINE 2: FOR PORTION OF valid_at FROM 1 TO 4
+                       ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
 -- Setting with timestamps reversed fails
 UPDATE for_portion_of_test
 FOR PORTION OF valid_at FROM '2018-06-01' TO '2018-01-01'
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 539e9688eb..069437b491 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -179,6 +179,7 @@ quad_poly_tbl|t
 radix_text_tbl|t
 ramp|f
 real_city|f
+referencing_period_test|t
 reservations|f
 road|t
 shighway|t
diff --git a/src/test/regress/expected/without_overlaps.out b/src/test/regress/expected/without_overlaps.out
index 708b5d3528..05fd62f4c3 100644
--- a/src/test/regress/expected/without_overlaps.out
+++ b/src/test/regress/expected/without_overlaps.out
@@ -284,3 +284,1849 @@ ALTER TABLE without_overlaps_test2 ALTER COLUMN valid_at TYPE tstzrange USING ts
 ALTER TABLE without_overlaps_test2 RENAME COLUMN valid_at TO valid_thru;
 ALTER TABLE without_overlaps_test2 DROP COLUMN valid_thru;
 DROP TABLE without_overlaps_test2;
+--
+-- test FK dependencies
+--
+-- can't drop a range referenced by an FK, unless with CASCADE
+CREATE TABLE without_overlaps_test2 (
+	id int4range,
+	valid_at tsrange,
+	CONSTRAINT without_overlaps2_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+CREATE TABLE referencing_period_test2 (
+	id int4range,
+	valid_at tsrange,
+	parent_id int4range,
+	CONSTRAINT referencing_period2_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS),
+	CONSTRAINT referencing_period2_fk FOREIGN KEY (parent_id, PERIOD valid_at)
+		REFERENCES without_overlaps_test2 (id, PERIOD valid_at)
+);
+ALTER TABLE without_overlaps_test2 DROP COLUMN valid_at;
+ERROR:  cannot drop column valid_at of table without_overlaps_test2 because other objects depend on it
+DETAIL:  constraint referencing_period2_fk on table referencing_period_test2 depends on column valid_at of table without_overlaps_test2
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+ALTER TABLE without_overlaps_test2 DROP COLUMN valid_at CASCADE;
+NOTICE:  drop cascades to constraint referencing_period2_fk on table referencing_period_test2
+DROP TABLE referencing_period_test2;
+DROP TABLE without_overlaps_test2;
+-- can't drop a PERIOD referenced by an FK, unless with CASCADE
+CREATE TABLE without_overlaps_test2 (
+	id int4range,
+	valid_from timestamp,
+	valid_til timestamp,
+	PERIOD FOR valid_at (valid_from, valid_til),
+	CONSTRAINT without_overlaps2_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+CREATE TABLE referencing_period_test2 (
+	id int4range,
+	valid_at tsrange,
+	parent_id int4range,
+	CONSTRAINT referencing_period2_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS),
+	CONSTRAINT referencing_period2_fk FOREIGN KEY (parent_id, PERIOD valid_at)
+		REFERENCES without_overlaps_test2 (id, PERIOD valid_at)
+);
+ALTER TABLE without_overlaps_test2 DROP PERIOD FOR valid_at;
+ERROR:  cannot drop period valid_at on table without_overlaps_test2 because other objects depend on it
+DETAIL:  constraint referencing_period2_fk on table referencing_period_test2 depends on period valid_at on table without_overlaps_test2
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+ALTER TABLE without_overlaps_test2 DROP PERIOD FOR valid_at CASCADE;
+NOTICE:  drop cascades to constraint referencing_period2_fk on table referencing_period_test2
+DROP TABLE referencing_period_test2;
+DROP TABLE without_overlaps_test2;
+--
+-- test FOREIGN KEY, range references range
+--
+-- Can't create a FK with a mismatched range type
+CREATE TABLE referencing_period_test2 (
+	id int4range,
+	valid_at int4range,
+	parent_id int4range,
+	CONSTRAINT referencing_period_pk2 PRIMARY KEY (id, valid_at WITHOUT OVERLAPS),
+	CONSTRAINT referencing_period_fk2 FOREIGN KEY (parent_id, PERIOD valid_at)
+		REFERENCES without_overlaps_test (id, PERIOD valid_at)
+);
+ERROR:  foreign key constraint "referencing_period_fk2" cannot be implemented
+DETAIL:  Key columns "valid_at" and "valid_at" are of incompatible types: int4range and tsrange.
+CREATE TABLE referencing_period_test (
+	id int4range,
+	valid_at tsrange,
+	parent_id int4range,
+	CONSTRAINT referencing_period_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS),
+	CONSTRAINT referencing_period_fk FOREIGN KEY (parent_id, PERIOD valid_at)
+		REFERENCES without_overlaps_test (id, PERIOD valid_at)
+);
+DROP TABLE referencing_period_test;
+-- with inferred PK on the referenced table:
+CREATE TABLE referencing_period_test (
+	id int4range,
+	valid_at tsrange,
+	parent_id int4range,
+	CONSTRAINT referencing_period_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS),
+	CONSTRAINT referencing_period_fk FOREIGN KEY (parent_id, PERIOD valid_at)
+		REFERENCES without_overlaps_test
+);
+DROP TABLE referencing_period_test;
+-- should fail because of duplicate referenced columns:
+CREATE TABLE referencing_period_test (
+	id int4range,
+	valid_at tsrange,
+	parent_id int4range,
+	CONSTRAINT referencing_period_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS),
+	CONSTRAINT referencing_period_fk FOREIGN KEY (parent_id, PERIOD parent_id)
+		REFERENCES without_overlaps_test (id, PERIOD id)
+);
+ERROR:  foreign key referenced-columns list must not contain duplicates
+--
+-- test ALTER TABLE ADD CONSTRAINT
+--
+CREATE TABLE referencing_period_test (
+	id int4range,
+	valid_at tsrange,
+	parent_id int4range,
+	CONSTRAINT referencing_period_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+ALTER TABLE referencing_period_test
+	ADD CONSTRAINT referencing_period_fk
+	FOREIGN KEY (parent_id, PERIOD valid_at)
+	REFERENCES without_overlaps_test (id, PERIOD valid_at);
+ALTER TABLE referencing_period_test
+	DROP CONSTRAINT referencing_period_fk;
+-- with inferred PK on the referenced table:
+ALTER TABLE referencing_period_test
+	ADD CONSTRAINT referencing_period_fk
+	FOREIGN KEY (parent_id, PERIOD valid_at)
+	REFERENCES without_overlaps_test;
+-- should fail because of duplicate referenced columns:
+ALTER TABLE referencing_period_test
+	ADD CONSTRAINT referencing_period_fk2
+	FOREIGN KEY (parent_id, PERIOD parent_id)
+	REFERENCES without_overlaps_test (id, PERIOD id);
+ERROR:  foreign key referenced-columns list must not contain duplicates
+--
+-- test with rows already
+--
+DELETE FROM referencing_period_test;
+ALTER TABLE referencing_period_test
+	DROP CONSTRAINT referencing_period_fk;
+INSERT INTO referencing_period_test VALUES ('[1,1]', tsrange('2018-01-02', '2018-02-01'), '[1,1]');
+ALTER TABLE referencing_period_test
+	ADD CONSTRAINT referencing_period_fk
+	FOREIGN KEY (parent_id, PERIOD valid_at)
+	REFERENCES without_overlaps_test;
+ALTER TABLE referencing_period_test
+	DROP CONSTRAINT referencing_period_fk;
+INSERT INTO referencing_period_test VALUES ('[2,2]', tsrange('2018-01-02', '2018-04-01'), '[1,1]');
+-- should fail:
+ALTER TABLE referencing_period_test
+	ADD CONSTRAINT referencing_period_fk
+	FOREIGN KEY (parent_id, PERIOD valid_at)
+	REFERENCES without_overlaps_test;
+ERROR:  insert or update on table "referencing_period_test" violates foreign key constraint "referencing_period_fk"
+DETAIL:  Key (parent_id, valid_at)=([1,2), ["Tue Jan 02 00:00:00 2018","Sun Apr 01 00:00:00 2018")) is not present in table "without_overlaps_test".
+-- okay again:
+DELETE FROM referencing_period_test;
+ALTER TABLE referencing_period_test
+	ADD CONSTRAINT referencing_period_fk
+	FOREIGN KEY (parent_id, PERIOD valid_at)
+	REFERENCES without_overlaps_test;
+--
+-- test pg_get_constraintdef
+--
+SELECT pg_get_constraintdef(oid) FROM pg_constraint WHERE conname = 'referencing_period_fk';
+                                      pg_get_constraintdef                                      
+------------------------------------------------------------------------------------------------
+ FOREIGN KEY (parent_id, PERIOD valid_at) REFERENCES without_overlaps_test(id, PERIOD valid_at)
+(1 row)
+
+--
+-- test FK child inserts
+--
+INSERT INTO referencing_period_test VALUES ('[1,1]', tsrange('2018-01-02', '2018-02-01'), '[1,1]');
+-- should fail:
+INSERT INTO referencing_period_test VALUES ('[2,2]', tsrange('2018-01-02', '2018-04-01'), '[1,1]');
+ERROR:  insert or update on table "referencing_period_test" violates foreign key constraint "referencing_period_fk"
+DETAIL:  Key (parent_id, valid_at)=([1,2), ["Tue Jan 02 00:00:00 2018","Sun Apr 01 00:00:00 2018")) is not present in table "without_overlaps_test".
+-- now it should work:
+INSERT INTO without_overlaps_test VALUES ('[1,1]', tsrange('2018-02-03', '2018-03-03'));
+INSERT INTO referencing_period_test VALUES ('[2,2]', tsrange('2018-01-02', '2018-04-01'), '[1,1]');
+--
+-- test FK child updates
+--
+UPDATE referencing_period_test SET valid_at = tsrange('2018-01-02', '2018-03-01') WHERE id = '[1,1]';
+-- should fail:
+UPDATE referencing_period_test SET valid_at = tsrange('2018-01-02', '2018-05-01') WHERE id = '[1,1]';
+ERROR:  insert or update on table "referencing_period_test" violates foreign key constraint "referencing_period_fk"
+DETAIL:  Key (parent_id, valid_at)=([1,2), ["Tue Jan 02 00:00:00 2018","Tue May 01 00:00:00 2018")) is not present in table "without_overlaps_test".
+UPDATE referencing_period_test SET parent_id = '[8,8]' WHERE id = '[1,1]';
+ERROR:  insert or update on table "referencing_period_test" violates foreign key constraint "referencing_period_fk"
+DETAIL:  Key (parent_id, valid_at)=([8,9), ["Tue Jan 02 00:00:00 2018","Thu Mar 01 00:00:00 2018")) is not present in table "without_overlaps_test".
+--
+-- test FK parent updates NO ACTION
+--
+-- a PK update that succeeds because the numeric id isn't referenced:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01') WHERE id = '[5,5]';
+-- a PK update that succeeds even though the numeric id is referenced because the range isn't:
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-02-01', '2018-03-01'));
+INSERT INTO referencing_period_test VALUES ('[3,3]', tsrange('2018-01-05', '2018-01-10'), '[5,5]');
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-02-01', '2016-03-01')
+WHERE id = '[5,5]' AND valid_at = tsrange('2018-02-01', '2018-03-01');
+-- a PK update that fails because both are referenced:
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01')
+WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+ERROR:  update or delete on table "without_overlaps_test" violates foreign key constraint "referencing_period_fk" on table "referencing_period_test"
+DETAIL:  Key (id, valid_at)=([5,6), ["Mon Jan 01 00:00:00 2018","Thu Feb 01 00:00:00 2018")) is still referenced from table "referencing_period_test".
+-- then delete the objecting FK record and the same PK update succeeds:
+DELETE FROM referencing_period_test WHERE id = '[3,3]';
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01')
+WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+-- clean up:
+DELETE FROM referencing_period_test WHERE parent_id = '[5,5]';
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+--
+-- test FK parent updates RESTRICT
+--
+ALTER TABLE referencing_period_test
+	DROP CONSTRAINT referencing_period_fk;
+ALTER TABLE referencing_period_test
+	ADD CONSTRAINT referencing_period_fk
+	FOREIGN KEY (parent_id, PERIOD valid_at)
+	REFERENCES without_overlaps_test
+	ON DELETE RESTRICT;
+-- a PK update that succeeds because the numeric id isn't referenced:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01') WHERE id = '[5,5]';
+-- a PK update that succeeds even though the numeric id is referenced because the range isn't:
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-02-01', '2018-03-01'));
+INSERT INTO referencing_period_test VALUES ('[3,3]', tsrange('2018-01-05', '2018-01-10'), '[5,5]');
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-02-01', '2016-03-01')
+WHERE id = '[5,5]' AND valid_at = tsrange('2018-02-01', '2018-03-01');
+-- a PK update that fails because both are referenced:
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01')
+WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+ERROR:  update or delete on table "without_overlaps_test" violates foreign key constraint "referencing_period_fk" on table "referencing_period_test"
+DETAIL:  Key (id, valid_at)=([5,6), ["Mon Jan 01 00:00:00 2018","Thu Feb 01 00:00:00 2018")) is still referenced from table "referencing_period_test".
+-- then delete the objecting FK record and the same PK update succeeds:
+DELETE FROM referencing_period_test WHERE id = '[3,3]';
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01')
+WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+-- clean up:
+DELETE FROM referencing_period_test WHERE parent_id = '[5,5]';
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+--
+-- test FK parent deletes NO ACTION
+--
+ALTER TABLE referencing_period_test
+	DROP CONSTRAINT referencing_period_fk;
+ALTER TABLE referencing_period_test
+	ADD CONSTRAINT referencing_period_fk
+	FOREIGN KEY (parent_id, PERIOD valid_at)
+	REFERENCES without_overlaps_test;
+-- a PK delete that succeeds because the numeric id isn't referenced:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+-- a PK delete that succeeds even though the numeric id is referenced because the range isn't:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-02-01', '2018-03-01'));
+INSERT INTO referencing_period_test VALUES ('[3,3]', tsrange('2018-01-05', '2018-01-10'), '[5,5]');
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-02-01', '2018-03-01');
+-- a PK delete that fails because both are referenced:
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+ERROR:  update or delete on table "without_overlaps_test" violates foreign key constraint "referencing_period_fk" on table "referencing_period_test"
+DETAIL:  Key (id, valid_at)=([5,6), ["Mon Jan 01 00:00:00 2018","Thu Feb 01 00:00:00 2018")) is still referenced from table "referencing_period_test".
+-- then delete the objecting FK record and the same PK delete succeeds:
+DELETE FROM referencing_period_test WHERE id = '[3,3]';
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+--
+-- test FK parent deletes RESTRICT
+--
+ALTER TABLE referencing_period_test
+	DROP CONSTRAINT referencing_period_fk;
+ALTER TABLE referencing_period_test
+	ADD CONSTRAINT referencing_period_fk
+	FOREIGN KEY (parent_id, PERIOD valid_at)
+	REFERENCES without_overlaps_test
+	ON DELETE RESTRICT;
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+-- a PK delete that succeeds even though the numeric id is referenced because the range isn't:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-02-01', '2018-03-01'));
+INSERT INTO referencing_period_test VALUES ('[3,3]', tsrange('2018-01-05', '2018-01-10'), '[5,5]');
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-02-01', '2018-03-01');
+-- a PK delete that fails because both are referenced:
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+ERROR:  update or delete on table "without_overlaps_test" violates foreign key constraint "referencing_period_fk" on table "referencing_period_test"
+DETAIL:  Key (id, valid_at)=([5,6), ["Mon Jan 01 00:00:00 2018","Thu Feb 01 00:00:00 2018")) is still referenced from table "referencing_period_test".
+-- then delete the objecting FK record and the same PK delete succeeds:
+DELETE FROM referencing_period_test WHERE id = '[3,3]';
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+--
+-- test ON UPDATE/DELETE options
+--
+-- test FK parent updates CASCADE
+INSERT INTO without_overlaps_test VALUES ('[6,6]', tsrange('2018-01-01', '2021-01-01'));
+INSERT INTO referencing_period_test VALUES ('[4,4]', tsrange('2018-01-01', '2021-01-01'), '[6,6]');
+ALTER TABLE referencing_period_test
+	DROP CONSTRAINT referencing_period_fk,
+	ADD CONSTRAINT referencing_period_fk
+		FOREIGN KEY (parent_id, PERIOD valid_at)
+		REFERENCES without_overlaps_test
+		ON DELETE CASCADE ON UPDATE CASCADE;
+UPDATE without_overlaps_test FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' SET id = '[7,7]' WHERE id = '[6,6]';
+SELECT * FROM referencing_period_test WHERE id = '[4,4]';
+  id   |                        valid_at                         | parent_id 
+-------+---------------------------------------------------------+-----------
+ [4,5) | ["Tue Jan 01 00:00:00 2019","Wed Jan 01 00:00:00 2020") | [7,8)
+ [4,5) | ["Mon Jan 01 00:00:00 2018","Tue Jan 01 00:00:00 2019") | [6,7)
+ [4,5) | ["Wed Jan 01 00:00:00 2020","Fri Jan 01 00:00:00 2021") | [6,7)
+(3 rows)
+
+UPDATE without_overlaps_test SET id = '[7,7]' WHERE id = '[6,6]';
+SELECT * FROM referencing_period_test WHERE id = '[4,4]';
+  id   |                        valid_at                         | parent_id 
+-------+---------------------------------------------------------+-----------
+ [4,5) | ["Tue Jan 01 00:00:00 2019","Wed Jan 01 00:00:00 2020") | [7,8)
+ [4,5) | ["Mon Jan 01 00:00:00 2018","Tue Jan 01 00:00:00 2019") | [7,8)
+ [4,5) | ["Wed Jan 01 00:00:00 2020","Fri Jan 01 00:00:00 2021") | [7,8)
+(3 rows)
+
+INSERT INTO without_overlaps_test VALUES ('[15,15]', tsrange('2018-01-01', '2020-01-01'));
+INSERT INTO without_overlaps_test VALUES ('[15,15]', tsrange('2020-01-01', '2021-01-01'));
+INSERT INTO referencing_period_test VALUES ('[10,10]', tsrange('2018-01-01', '2021-01-01'), '[15,15]');
+UPDATE without_overlaps_test SET id = '[16,16]' WHERE id = '[15,15]' AND valid_at @> '2019-01-01'::timestamp;
+SELECT * FROM referencing_period_test WHERE id = '[10,10]';
+   id    |                        valid_at                         | parent_id 
+---------+---------------------------------------------------------+-----------
+ [10,11) | ["Mon Jan 01 00:00:00 2018","Wed Jan 01 00:00:00 2020") | [16,17)
+ [10,11) | ["Wed Jan 01 00:00:00 2020","Fri Jan 01 00:00:00 2021") | [15,16)
+(2 rows)
+
+-- test FK parent deletes CASCADE
+INSERT INTO without_overlaps_test VALUES ('[8,8]', tsrange('2018-01-01', '2021-01-01'));
+INSERT INTO referencing_period_test VALUES ('[5,5]', tsrange('2018-01-01', '2021-01-01'), '[8,8]');
+DELETE FROM without_overlaps_test FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' WHERE id = '[8,8]';
+SELECT * FROM referencing_period_test WHERE id = '[5,5]';
+  id   |                        valid_at                         | parent_id 
+-------+---------------------------------------------------------+-----------
+ [5,6) | ["Mon Jan 01 00:00:00 2018","Tue Jan 01 00:00:00 2019") | [8,9)
+ [5,6) | ["Wed Jan 01 00:00:00 2020","Fri Jan 01 00:00:00 2021") | [8,9)
+(2 rows)
+
+DELETE FROM without_overlaps_test WHERE id = '[8,8]';
+SELECT * FROM referencing_period_test WHERE id = '[5,5]';
+ id | valid_at | parent_id 
+----+----------+-----------
+(0 rows)
+
+INSERT INTO without_overlaps_test VALUES ('[17,17]', tsrange('2018-01-01', '2020-01-01'));
+INSERT INTO without_overlaps_test VALUES ('[17,17]', tsrange('2020-01-01', '2021-01-01'));
+INSERT INTO referencing_period_test VALUES ('[11,11]', tsrange('2018-01-01', '2021-01-01'), '[17,17]');
+DELETE FROM without_overlaps_test WHERE id = '[17,17]' AND valid_at @> '2019-01-01'::timestamp;
+SELECT * FROM referencing_period_test WHERE id = '[11,11]';
+   id    |                        valid_at                         | parent_id 
+---------+---------------------------------------------------------+-----------
+ [11,12) | ["Wed Jan 01 00:00:00 2020","Fri Jan 01 00:00:00 2021") | [17,18)
+(1 row)
+
+-- test FK parent updates SET NULL
+INSERT INTO without_overlaps_test VALUES ('[9,9]', tsrange('2018-01-01', '2021-01-01'));
+INSERT INTO referencing_period_test VALUES ('[6,6]', tsrange('2018-01-01', '2021-01-01'), '[9,9]');
+ALTER TABLE referencing_period_test
+	DROP CONSTRAINT referencing_period_fk,
+	ADD CONSTRAINT referencing_period_fk
+		FOREIGN KEY (parent_id, PERIOD valid_at)
+		REFERENCES without_overlaps_test
+		ON DELETE SET NULL ON UPDATE SET NULL;
+UPDATE without_overlaps_test FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' SET id = '[10,10]' WHERE id = '[9,9]';
+SELECT * FROM referencing_period_test WHERE id = '[6,6]';
+  id   |                        valid_at                         | parent_id 
+-------+---------------------------------------------------------+-----------
+ [6,7) | ["Tue Jan 01 00:00:00 2019","Wed Jan 01 00:00:00 2020") | 
+ [6,7) | ["Mon Jan 01 00:00:00 2018","Tue Jan 01 00:00:00 2019") | [9,10)
+ [6,7) | ["Wed Jan 01 00:00:00 2020","Fri Jan 01 00:00:00 2021") | [9,10)
+(3 rows)
+
+UPDATE without_overlaps_test SET id = '[10,10]' WHERE id = '[9,9]';
+SELECT * FROM referencing_period_test WHERE id = '[6,6]';
+  id   |                        valid_at                         | parent_id 
+-------+---------------------------------------------------------+-----------
+ [6,7) | ["Tue Jan 01 00:00:00 2019","Wed Jan 01 00:00:00 2020") | 
+ [6,7) | ["Mon Jan 01 00:00:00 2018","Tue Jan 01 00:00:00 2019") | 
+ [6,7) | ["Wed Jan 01 00:00:00 2020","Fri Jan 01 00:00:00 2021") | 
+(3 rows)
+
+INSERT INTO without_overlaps_test VALUES ('[18,18]', tsrange('2018-01-01', '2020-01-01'));
+INSERT INTO without_overlaps_test VALUES ('[18,18]', tsrange('2020-01-01', '2021-01-01'));
+INSERT INTO referencing_period_test VALUES ('[12,12]', tsrange('2018-01-01', '2021-01-01'), '[18,18]');
+UPDATE without_overlaps_test SET id = '[19,19]' WHERE id = '[18,18]' AND valid_at @> '2019-01-01'::timestamp;
+SELECT * FROM referencing_period_test WHERE id = '[12,12]';
+   id    |                        valid_at                         | parent_id 
+---------+---------------------------------------------------------+-----------
+ [12,13) | ["Mon Jan 01 00:00:00 2018","Wed Jan 01 00:00:00 2020") | 
+ [12,13) | ["Wed Jan 01 00:00:00 2020","Fri Jan 01 00:00:00 2021") | [18,19)
+(2 rows)
+
+-- test FK parent deletes SET NULL
+INSERT INTO without_overlaps_test VALUES ('[11,11]', tsrange('2018-01-01', '2021-01-01'));
+INSERT INTO referencing_period_test VALUES ('[7,7]', tsrange('2018-01-01', '2021-01-01'), '[11,11]');
+DELETE FROM without_overlaps_test FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' WHERE id = '[11,11]';
+SELECT * FROM referencing_period_test WHERE id = '[7,7]';
+  id   |                        valid_at                         | parent_id 
+-------+---------------------------------------------------------+-----------
+ [7,8) | ["Tue Jan 01 00:00:00 2019","Wed Jan 01 00:00:00 2020") | 
+ [7,8) | ["Mon Jan 01 00:00:00 2018","Tue Jan 01 00:00:00 2019") | [11,12)
+ [7,8) | ["Wed Jan 01 00:00:00 2020","Fri Jan 01 00:00:00 2021") | [11,12)
+(3 rows)
+
+DELETE FROM without_overlaps_test WHERE id = '[11,11]';
+SELECT * FROM referencing_period_test WHERE id = '[7,7]';
+  id   |                        valid_at                         | parent_id 
+-------+---------------------------------------------------------+-----------
+ [7,8) | ["Tue Jan 01 00:00:00 2019","Wed Jan 01 00:00:00 2020") | 
+ [7,8) | ["Mon Jan 01 00:00:00 2018","Tue Jan 01 00:00:00 2019") | 
+ [7,8) | ["Wed Jan 01 00:00:00 2020","Fri Jan 01 00:00:00 2021") | 
+(3 rows)
+
+INSERT INTO without_overlaps_test VALUES ('[20,20]', tsrange('2018-01-01', '2020-01-01'));
+INSERT INTO without_overlaps_test VALUES ('[20,20]', tsrange('2020-01-01', '2021-01-01'));
+INSERT INTO referencing_period_test VALUES ('[13,13]', tsrange('2018-01-01', '2021-01-01'), '[20,20]');
+DELETE FROM without_overlaps_test WHERE id = '[20,20]' AND valid_at @> '2019-01-01'::timestamp;
+SELECT * FROM referencing_period_test WHERE id = '[13,13]';
+   id    |                        valid_at                         | parent_id 
+---------+---------------------------------------------------------+-----------
+ [13,14) | ["Mon Jan 01 00:00:00 2018","Wed Jan 01 00:00:00 2020") | 
+ [13,14) | ["Wed Jan 01 00:00:00 2020","Fri Jan 01 00:00:00 2021") | [20,21)
+(2 rows)
+
+-- test FK parent updates SET DEFAULT
+INSERT INTO without_overlaps_test VALUES ('[-1,-1]', tsrange(null, null));
+INSERT INTO without_overlaps_test VALUES ('[12,12]', tsrange('2018-01-01', '2021-01-01'));
+INSERT INTO referencing_period_test VALUES ('[8,8]', tsrange('2018-01-01', '2021-01-01'), '[12,12]');
+ALTER TABLE referencing_period_test
+  ALTER COLUMN parent_id SET DEFAULT '[-1,-1]',
+	DROP CONSTRAINT referencing_period_fk,
+	ADD CONSTRAINT referencing_period_fk
+		FOREIGN KEY (parent_id, PERIOD valid_at)
+		REFERENCES without_overlaps_test
+		ON DELETE SET DEFAULT ON UPDATE SET DEFAULT;
+UPDATE without_overlaps_test FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' SET id = '[13,13]' WHERE id = '[12,12]';
+SELECT * FROM referencing_period_test WHERE id = '[8,8]';
+  id   |                        valid_at                         | parent_id 
+-------+---------------------------------------------------------+-----------
+ [8,9) | ["Tue Jan 01 00:00:00 2019","Wed Jan 01 00:00:00 2020") | [-1,0)
+ [8,9) | ["Mon Jan 01 00:00:00 2018","Tue Jan 01 00:00:00 2019") | [12,13)
+ [8,9) | ["Wed Jan 01 00:00:00 2020","Fri Jan 01 00:00:00 2021") | [12,13)
+(3 rows)
+
+UPDATE without_overlaps_test SET id = '[13,13]' WHERE id = '[12,12]';
+SELECT * FROM referencing_period_test WHERE id = '[8,8]';
+  id   |                        valid_at                         | parent_id 
+-------+---------------------------------------------------------+-----------
+ [8,9) | ["Tue Jan 01 00:00:00 2019","Wed Jan 01 00:00:00 2020") | [-1,0)
+ [8,9) | ["Mon Jan 01 00:00:00 2018","Tue Jan 01 00:00:00 2019") | [-1,0)
+ [8,9) | ["Wed Jan 01 00:00:00 2020","Fri Jan 01 00:00:00 2021") | [-1,0)
+(3 rows)
+
+INSERT INTO without_overlaps_test VALUES ('[22,22]', tsrange('2018-01-01', '2020-01-01'));
+INSERT INTO without_overlaps_test VALUES ('[22,22]', tsrange('2020-01-01', '2021-01-01'));
+INSERT INTO referencing_period_test VALUES ('[14,14]', tsrange('2018-01-01', '2021-01-01'), '[22,22]');
+UPDATE without_overlaps_test SET id = '[23,23]' WHERE id = '[22,22]' AND valid_at @> '2019-01-01'::timestamp;
+SELECT * FROM referencing_period_test WHERE id = '[14,14]';
+   id    |                        valid_at                         | parent_id 
+---------+---------------------------------------------------------+-----------
+ [14,15) | ["Mon Jan 01 00:00:00 2018","Wed Jan 01 00:00:00 2020") | [-1,0)
+ [14,15) | ["Wed Jan 01 00:00:00 2020","Fri Jan 01 00:00:00 2021") | [22,23)
+(2 rows)
+
+-- test FK parent deletes SET DEFAULT
+INSERT INTO without_overlaps_test VALUES ('[14,14]', tsrange('2018-01-01', '2021-01-01'));
+INSERT INTO referencing_period_test VALUES ('[9,9]', tsrange('2018-01-01', '2021-01-01'), '[14,14]');
+DELETE FROM without_overlaps_test FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' WHERE id = '[14,14]';
+SELECT * FROM referencing_period_test WHERE id = '[9,9]';
+   id   |                        valid_at                         | parent_id 
+--------+---------------------------------------------------------+-----------
+ [9,10) | ["Tue Jan 01 00:00:00 2019","Wed Jan 01 00:00:00 2020") | [-1,0)
+ [9,10) | ["Mon Jan 01 00:00:00 2018","Tue Jan 01 00:00:00 2019") | [14,15)
+ [9,10) | ["Wed Jan 01 00:00:00 2020","Fri Jan 01 00:00:00 2021") | [14,15)
+(3 rows)
+
+DELETE FROM without_overlaps_test WHERE id = '[14,14]';
+SELECT * FROM referencing_period_test WHERE id = '[9,9]';
+   id   |                        valid_at                         | parent_id 
+--------+---------------------------------------------------------+-----------
+ [9,10) | ["Tue Jan 01 00:00:00 2019","Wed Jan 01 00:00:00 2020") | [-1,0)
+ [9,10) | ["Mon Jan 01 00:00:00 2018","Tue Jan 01 00:00:00 2019") | [-1,0)
+ [9,10) | ["Wed Jan 01 00:00:00 2020","Fri Jan 01 00:00:00 2021") | [-1,0)
+(3 rows)
+
+INSERT INTO without_overlaps_test VALUES ('[24,24]', tsrange('2018-01-01', '2020-01-01'));
+INSERT INTO without_overlaps_test VALUES ('[24,24]', tsrange('2020-01-01', '2021-01-01'));
+INSERT INTO referencing_period_test VALUES ('[15,15]', tsrange('2018-01-01', '2021-01-01'), '[24,24]');
+DELETE FROM without_overlaps_test WHERE id = '[24,24]' AND valid_at @> '2019-01-01'::timestamp;
+SELECT * FROM referencing_period_test WHERE id = '[15,15]';
+   id    |                        valid_at                         | parent_id 
+---------+---------------------------------------------------------+-----------
+ [15,16) | ["Mon Jan 01 00:00:00 2018","Wed Jan 01 00:00:00 2020") | [-1,0)
+ [15,16) | ["Wed Jan 01 00:00:00 2020","Fri Jan 01 00:00:00 2021") | [24,25)
+(2 rows)
+
+--
+-- test FOREIGN KEY, range references PERIOD
+--
+DROP TABLE without_overlaps_test CASCADE;
+NOTICE:  drop cascades to constraint referencing_period_fk on table referencing_period_test
+CREATE TABLE without_overlaps_test (
+	id int4range,
+	valid_from timestamp,
+	valid_til timestamp,
+	PERIOD FOR valid_at (valid_from, valid_til),
+	CONSTRAINT without_overlaps_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+INSERT INTO without_overlaps_test VALUES ('[1,1]', '2018-01-02', '2018-02-03');
+INSERT INTO without_overlaps_test VALUES ('[1,1]', '2018-03-03', '2018-04-04');
+INSERT INTO without_overlaps_test VALUES ('[2,2]', '2018-01-01', '2018-01-05');
+INSERT INTO without_overlaps_test VALUES ('[3,3]', '2018-01-01', NULL);
+-- Can't create a FK with a mismatched range type
+CREATE TABLE referencing_period_test2 (
+	id int4range,
+	valid_at int4range,
+	parent_id int4range,
+	CONSTRAINT referencing_period_pk2 PRIMARY KEY (id, valid_at WITHOUT OVERLAPS),
+	CONSTRAINT referencing_period_fk2 FOREIGN KEY (parent_id, PERIOD valid_at)
+		REFERENCES without_overlaps_test (id, PERIOD valid_at)
+);
+ERROR:  foreign key constraint "referencing_period_fk2" cannot be implemented
+DETAIL:  Key columns "valid_at" and "valid_at" are of incompatible types: int4range and tsrange.
+-- with inferred PK on the referenced table:
+DROP TABLE referencing_period_test;
+CREATE TABLE referencing_period_test (
+	id int4range,
+	valid_at tsrange,
+	parent_id int4range,
+	CONSTRAINT referencing_period_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS),
+	CONSTRAINT referencing_period_fk FOREIGN KEY (parent_id, PERIOD valid_at)
+		REFERENCES without_overlaps_test
+);
+DROP TABLE referencing_period_test;
+-- should fail because of duplicate referenced columns:
+CREATE TABLE referencing_period_test (
+	id int4range,
+	valid_at tsrange,
+	parent_id int4range,
+	CONSTRAINT referencing_period_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS),
+	CONSTRAINT referencing_period_fk FOREIGN KEY (parent_id, PERIOD parent_id)
+		REFERENCES without_overlaps_test (id, PERIOD id)
+);
+ERROR:  foreign key referenced-columns list must not contain duplicates
+--
+-- test ALTER TABLE ADD CONSTRAINT
+--
+CREATE TABLE referencing_period_test (
+	id int4range,
+	valid_at tsrange,
+	parent_id int4range,
+	CONSTRAINT referencing_period_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+ALTER TABLE referencing_period_test
+	ADD CONSTRAINT referencing_period_fk
+	FOREIGN KEY (parent_id, PERIOD valid_at)
+	REFERENCES without_overlaps_test (id, PERIOD valid_at);
+ALTER TABLE referencing_period_test
+	DROP CONSTRAINT referencing_period_fk;
+-- with inferred PK on the referenced table:
+ALTER TABLE referencing_period_test
+	ADD CONSTRAINT referencing_period_fk
+	FOREIGN KEY (parent_id, PERIOD valid_at)
+	REFERENCES without_overlaps_test;
+-- should fail because of duplicate referenced columns:
+ALTER TABLE referencing_period_test
+	ADD CONSTRAINT referencing_period_fk2
+	FOREIGN KEY (parent_id, PERIOD parent_id)
+	REFERENCES without_overlaps_test (id, PERIOD id);
+ERROR:  foreign key referenced-columns list must not contain duplicates
+--
+-- test with rows already
+--
+DELETE FROM referencing_period_test;
+ALTER TABLE referencing_period_test
+	DROP CONSTRAINT referencing_period_fk;
+INSERT INTO referencing_period_test VALUES ('[1,1]', tsrange('2018-01-02', '2018-02-01'), '[1,1]');
+ALTER TABLE referencing_period_test
+	ADD CONSTRAINT referencing_period_fk
+	FOREIGN KEY (parent_id, PERIOD valid_at)
+	REFERENCES without_overlaps_test;
+ALTER TABLE referencing_period_test
+	DROP CONSTRAINT referencing_period_fk;
+INSERT INTO referencing_period_test VALUES ('[2,2]', tsrange('2018-01-02', '2018-04-01'), '[1,1]');
+-- should fail:
+ALTER TABLE referencing_period_test
+	ADD CONSTRAINT referencing_period_fk
+	FOREIGN KEY (parent_id, PERIOD valid_at)
+	REFERENCES without_overlaps_test;
+ERROR:  insert or update on table "referencing_period_test" violates foreign key constraint "referencing_period_fk"
+DETAIL:  Key (parent_id, valid_at)=([1,2), ["Tue Jan 02 00:00:00 2018","Sun Apr 01 00:00:00 2018")) is not present in table "without_overlaps_test".
+-- okay again:
+DELETE FROM referencing_period_test;
+ALTER TABLE referencing_period_test
+	ADD CONSTRAINT referencing_period_fk
+	FOREIGN KEY (parent_id, PERIOD valid_at)
+	REFERENCES without_overlaps_test;
+--
+-- test pg_get_constraintdef
+--
+SELECT pg_get_constraintdef(oid) FROM pg_constraint WHERE conname = 'referencing_period_fk';
+                                      pg_get_constraintdef                                      
+------------------------------------------------------------------------------------------------
+ FOREIGN KEY (parent_id, PERIOD valid_at) REFERENCES without_overlaps_test(id, PERIOD valid_at)
+(1 row)
+
+--
+-- test FK child inserts
+--
+INSERT INTO referencing_period_test VALUES ('[1,1]', tsrange('2018-01-02', '2018-02-01'), '[1,1]');
+-- should fail:
+INSERT INTO referencing_period_test VALUES ('[2,2]', tsrange('2018-01-02', '2018-04-01'), '[1,1]');
+ERROR:  insert or update on table "referencing_period_test" violates foreign key constraint "referencing_period_fk"
+DETAIL:  Key (parent_id, valid_at)=([1,2), ["Tue Jan 02 00:00:00 2018","Sun Apr 01 00:00:00 2018")) is not present in table "without_overlaps_test".
+-- now it should work:
+INSERT INTO without_overlaps_test VALUES ('[1,1]', '2018-02-03', '2018-03-03');
+INSERT INTO referencing_period_test VALUES ('[2,2]', tsrange('2018-01-02', '2018-04-01'), '[1,1]');
+--
+-- test FK child updates
+--
+UPDATE referencing_period_test SET valid_at = tsrange('2018-01-02', '2018-03-01') WHERE id = '[1,1]';
+-- should fail:
+UPDATE referencing_period_test SET valid_at = tsrange('2018-01-02', '2018-05-01') WHERE id = '[1,1]';
+ERROR:  insert or update on table "referencing_period_test" violates foreign key constraint "referencing_period_fk"
+DETAIL:  Key (parent_id, valid_at)=([1,2), ["Tue Jan 02 00:00:00 2018","Tue May 01 00:00:00 2018")) is not present in table "without_overlaps_test".
+UPDATE referencing_period_test SET parent_id = '[8,8]' WHERE id = '[1,1]';
+ERROR:  insert or update on table "referencing_period_test" violates foreign key constraint "referencing_period_fk"
+DETAIL:  Key (parent_id, valid_at)=([8,9), ["Tue Jan 02 00:00:00 2018","Thu Mar 01 00:00:00 2018")) is not present in table "without_overlaps_test".
+--
+-- test FK parent updates NO ACTION
+--
+-- a PK update that succeeds because the numeric id isn't referenced:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', '2018-01-01', '2018-02-01');
+UPDATE without_overlaps_test SET valid_from = '2016-01-01', valid_til = '2016-02-01' WHERE id = '[5,5]';
+-- a PK update that succeeds even though the numeric id is referenced because the range isn't:
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+INSERT INTO without_overlaps_test VALUES ('[5,5]', '2018-01-01', '2018-02-01');
+INSERT INTO without_overlaps_test VALUES ('[5,5]', '2018-02-01', '2018-03-01');
+INSERT INTO referencing_period_test VALUES ('[3,3]', tsrange('2018-01-05', '2018-01-10'), '[5,5]');
+UPDATE without_overlaps_test SET valid_from = '2016-02-01', valid_til = '2016-03-01'
+WHERE id = '[5,5]' AND valid_from = '2018-02-01' AND valid_til = '2018-03-01';
+-- a PK update that fails because both are referenced:
+UPDATE without_overlaps_test SET valid_from = '2016-01-01', valid_til = '2016-02-01'
+WHERE id = '[5,5]' AND valid_from = '2018-01-01' AND valid_til = '2018-02-01';
+ERROR:  update or delete on table "without_overlaps_test" violates foreign key constraint "referencing_period_fk" on table "referencing_period_test"
+DETAIL:  Key (id, valid_from, valid_til)=([5,6), Mon Jan 01 00:00:00 2018, Thu Feb 01 00:00:00 2018) is still referenced from table "referencing_period_test".
+-- then delete the objecting FK record and the same PK update succeeds:
+DELETE FROM referencing_period_test WHERE id = '[3,3]';
+UPDATE without_overlaps_test SET valid_from = '2016-01-01', valid_til = '2016-02-01'
+WHERE id = '[5,5]' AND valid_from = '2018-01-01' AND valid_til = '2018-02-01';
+-- clean up:
+DELETE FROM referencing_period_test WHERE parent_id = '[5,5]';
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+--
+-- test FK parent updates RESTRICT
+--
+ALTER TABLE referencing_period_test
+	DROP CONSTRAINT referencing_period_fk;
+ALTER TABLE referencing_period_test
+	ADD CONSTRAINT referencing_period_fk
+	FOREIGN KEY (parent_id, PERIOD valid_at)
+	REFERENCES without_overlaps_test
+	ON DELETE RESTRICT;
+-- a PK update that succeeds because the numeric id isn't referenced:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', '2018-01-01', '2018-02-01');
+UPDATE without_overlaps_test SET valid_from = '2016-01-01', valid_til = '2016-02-01' WHERE id = '[5,5]';
+-- a PK update that succeeds even though the numeric id is referenced because the range isn't:
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+INSERT INTO without_overlaps_test VALUES ('[5,5]', '2018-01-01', '2018-02-01');
+INSERT INTO without_overlaps_test VALUES ('[5,5]', '2018-02-01', '2018-03-01');
+INSERT INTO referencing_period_test VALUES ('[3,3]', tsrange('2018-01-05', '2018-01-10'), '[5,5]');
+UPDATE without_overlaps_test SET valid_from = '2016-02-01', valid_til = '2016-03-01'
+WHERE id = '[5,5]' AND valid_from = '2018-02-01' AND valid_til = '2018-03-01';
+-- a PK update that fails because both are referenced:
+UPDATE without_overlaps_test SET valid_from = '2016-01-01', valid_til = '2016-02-01'
+WHERE id = '[5,5]' AND valid_from = '2018-01-01' AND valid_til = '2018-02-01';
+ERROR:  update or delete on table "without_overlaps_test" violates foreign key constraint "referencing_period_fk" on table "referencing_period_test"
+DETAIL:  Key (id, valid_from, valid_til)=([5,6), Mon Jan 01 00:00:00 2018, Thu Feb 01 00:00:00 2018) is still referenced from table "referencing_period_test".
+-- then delete the objecting FK record and the same PK update succeeds:
+DELETE FROM referencing_period_test WHERE id = '[3,3]';
+UPDATE without_overlaps_test SET valid_from = '2016-01-01', valid_til = '2016-02-01'
+WHERE id = '[5,5]' AND valid_from = '2018-01-01' AND valid_til = '2018-02-01';
+-- clean up:
+DELETE FROM referencing_period_test WHERE parent_id = '[5,5]';
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+--
+-- test FK parent deletes NO ACTION
+--
+ALTER TABLE referencing_period_test
+	DROP CONSTRAINT referencing_period_fk;
+ALTER TABLE referencing_period_test
+	ADD CONSTRAINT referencing_period_fk
+	FOREIGN KEY (parent_id, PERIOD valid_at)
+	REFERENCES without_overlaps_test;
+-- a PK delete that succeeds because the numeric id isn't referenced:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', '2018-01-01', '2018-02-01');
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+-- a PK delete that succeeds even though the numeric id is referenced because the range isn't:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', '2018-01-01', '2018-02-01');
+INSERT INTO without_overlaps_test VALUES ('[5,5]', '2018-02-01', '2018-03-01');
+INSERT INTO referencing_period_test VALUES ('[3,3]', tsrange('2018-01-05', '2018-01-10'), '[5,5]');
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_from = '2018-02-01' AND valid_til = '2018-03-01';
+-- a PK delete that fails because both are referenced:
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_from = '2018-01-01' AND valid_til = '2018-02-01';
+ERROR:  update or delete on table "without_overlaps_test" violates foreign key constraint "referencing_period_fk" on table "referencing_period_test"
+DETAIL:  Key (id, valid_from, valid_til)=([5,6), Mon Jan 01 00:00:00 2018, Thu Feb 01 00:00:00 2018) is still referenced from table "referencing_period_test".
+-- then delete the objecting FK record and the same PK delete succeeds:
+DELETE FROM referencing_period_test WHERE id = '[3,3]';
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_from = '2018-01-01' AND valid_til = '2018-02-01';
+--
+-- test FK parent deletes RESTRICT
+--
+ALTER TABLE referencing_period_test
+	DROP CONSTRAINT referencing_period_fk;
+ALTER TABLE referencing_period_test
+	ADD CONSTRAINT referencing_period_fk
+	FOREIGN KEY (parent_id, PERIOD valid_at)
+	REFERENCES without_overlaps_test
+	ON DELETE RESTRICT;
+INSERT INTO without_overlaps_test VALUES ('[5,5]', '2018-01-01', '2018-02-01');
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+-- a PK delete that succeeds even though the numeric id is referenced because the range isn't:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', '2018-01-01', '2018-02-01');
+INSERT INTO without_overlaps_test VALUES ('[5,5]', '2018-02-01', '2018-03-01');
+INSERT INTO referencing_period_test VALUES ('[3,3]', tsrange('2018-01-05', '2018-01-10'), '[5,5]');
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_from = '2018-02-01' AND valid_til = '2018-03-01';
+-- a PK delete that fails because both are referenced:
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_from = '2018-01-01' AND valid_til = '2018-02-01';
+ERROR:  update or delete on table "without_overlaps_test" violates foreign key constraint "referencing_period_fk" on table "referencing_period_test"
+DETAIL:  Key (id, valid_from, valid_til)=([5,6), Mon Jan 01 00:00:00 2018, Thu Feb 01 00:00:00 2018) is still referenced from table "referencing_period_test".
+-- then delete the objecting FK record and the same PK delete succeeds:
+DELETE FROM referencing_period_test WHERE id = '[3,3]';
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_from = '2018-01-01' AND valid_til = '2018-02-01';
+--
+-- test ON UPDATE/DELETE options
+--
+-- test FK parent updates CASCADE
+INSERT INTO without_overlaps_test VALUES ('[6,6]', '2018-01-01', '2021-01-01');
+INSERT INTO referencing_period_test VALUES ('[4,4]', tsrange('2018-01-01', '2021-01-01'), '[6,6]');
+ALTER TABLE referencing_period_test
+	DROP CONSTRAINT referencing_period_fk,
+	ADD CONSTRAINT referencing_period_fk
+		FOREIGN KEY (parent_id, PERIOD valid_at)
+		REFERENCES without_overlaps_test
+		ON DELETE CASCADE ON UPDATE CASCADE;
+UPDATE without_overlaps_test FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' SET id = '[7,7]' WHERE id = '[6,6]';
+SELECT * FROM referencing_period_test WHERE id = '[4,4]';
+  id   |                        valid_at                         | parent_id 
+-------+---------------------------------------------------------+-----------
+ [4,5) | ["Tue Jan 01 00:00:00 2019","Wed Jan 01 00:00:00 2020") | [7,8)
+ [4,5) | ["Mon Jan 01 00:00:00 2018","Tue Jan 01 00:00:00 2019") | [6,7)
+ [4,5) | ["Wed Jan 01 00:00:00 2020","Fri Jan 01 00:00:00 2021") | [6,7)
+(3 rows)
+
+UPDATE without_overlaps_test SET id = '[7,7]' WHERE id = '[6,6]';
+SELECT * FROM referencing_period_test WHERE id = '[4,4]';
+  id   |                        valid_at                         | parent_id 
+-------+---------------------------------------------------------+-----------
+ [4,5) | ["Tue Jan 01 00:00:00 2019","Wed Jan 01 00:00:00 2020") | [7,8)
+ [4,5) | ["Mon Jan 01 00:00:00 2018","Tue Jan 01 00:00:00 2019") | [7,8)
+ [4,5) | ["Wed Jan 01 00:00:00 2020","Fri Jan 01 00:00:00 2021") | [7,8)
+(3 rows)
+
+INSERT INTO without_overlaps_test VALUES ('[15,15]', '2018-01-01', '2020-01-01');
+INSERT INTO without_overlaps_test VALUES ('[15,15]', '2020-01-01', '2021-01-01');
+INSERT INTO referencing_period_test VALUES ('[10,10]', tsrange('2018-01-01', '2021-01-01'), '[15,15]');
+UPDATE without_overlaps_test SET id = '[16,16]' WHERE id = '[15,15]' AND tsrange(valid_from, valid_til) @> '2019-01-01'::timestamp;
+SELECT * FROM referencing_period_test WHERE id = '[10,10]';
+   id    |                        valid_at                         | parent_id 
+---------+---------------------------------------------------------+-----------
+ [10,11) | ["Mon Jan 01 00:00:00 2018","Wed Jan 01 00:00:00 2020") | [16,17)
+ [10,11) | ["Wed Jan 01 00:00:00 2020","Fri Jan 01 00:00:00 2021") | [15,16)
+(2 rows)
+
+-- test FK parent deletes CASCADE
+INSERT INTO without_overlaps_test VALUES ('[8,8]', '2018-01-01', '2021-01-01');
+INSERT INTO referencing_period_test VALUES ('[5,5]', tsrange('2018-01-01', '2021-01-01'), '[8,8]');
+DELETE FROM without_overlaps_test FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' WHERE id = '[8,8]';
+SELECT * FROM referencing_period_test WHERE id = '[5,5]';
+  id   |                        valid_at                         | parent_id 
+-------+---------------------------------------------------------+-----------
+ [5,6) | ["Mon Jan 01 00:00:00 2018","Tue Jan 01 00:00:00 2019") | [8,9)
+ [5,6) | ["Wed Jan 01 00:00:00 2020","Fri Jan 01 00:00:00 2021") | [8,9)
+(2 rows)
+
+DELETE FROM without_overlaps_test WHERE id = '[8,8]';
+SELECT * FROM referencing_period_test WHERE id = '[5,5]';
+ id | valid_at | parent_id 
+----+----------+-----------
+(0 rows)
+
+INSERT INTO without_overlaps_test VALUES ('[17,17]', '2018-01-01', '2020-01-01');
+INSERT INTO without_overlaps_test VALUES ('[17,17]', '2020-01-01', '2021-01-01');
+INSERT INTO referencing_period_test VALUES ('[11,11]', tsrange('2018-01-01', '2021-01-01'), '[17,17]');
+DELETE FROM without_overlaps_test WHERE id = '[17,17]' AND tsrange(valid_from, valid_til) @> '2019-01-01'::timestamp;
+SELECT * FROM referencing_period_test WHERE id = '[11,11]';
+   id    |                        valid_at                         | parent_id 
+---------+---------------------------------------------------------+-----------
+ [11,12) | ["Wed Jan 01 00:00:00 2020","Fri Jan 01 00:00:00 2021") | [17,18)
+(1 row)
+
+-- test FK parent updates SET NULL
+INSERT INTO without_overlaps_test VALUES ('[9,9]', '2018-01-01', '2021-01-01');
+INSERT INTO referencing_period_test VALUES ('[6,6]', tsrange('2018-01-01', '2021-01-01'), '[9,9]');
+ALTER TABLE referencing_period_test
+	DROP CONSTRAINT referencing_period_fk,
+	ADD CONSTRAINT referencing_period_fk
+		FOREIGN KEY (parent_id, PERIOD valid_at)
+		REFERENCES without_overlaps_test
+		ON DELETE SET NULL ON UPDATE SET NULL;
+UPDATE without_overlaps_test FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' SET id = '[10,10]' WHERE id = '[9,9]';
+SELECT * FROM referencing_period_test WHERE id = '[6,6]';
+  id   |                        valid_at                         | parent_id 
+-------+---------------------------------------------------------+-----------
+ [6,7) | ["Tue Jan 01 00:00:00 2019","Wed Jan 01 00:00:00 2020") | 
+ [6,7) | ["Mon Jan 01 00:00:00 2018","Tue Jan 01 00:00:00 2019") | [9,10)
+ [6,7) | ["Wed Jan 01 00:00:00 2020","Fri Jan 01 00:00:00 2021") | [9,10)
+(3 rows)
+
+UPDATE without_overlaps_test SET id = '[10,10]' WHERE id = '[9,9]';
+SELECT * FROM referencing_period_test WHERE id = '[6,6]';
+  id   |                        valid_at                         | parent_id 
+-------+---------------------------------------------------------+-----------
+ [6,7) | ["Tue Jan 01 00:00:00 2019","Wed Jan 01 00:00:00 2020") | 
+ [6,7) | ["Mon Jan 01 00:00:00 2018","Tue Jan 01 00:00:00 2019") | 
+ [6,7) | ["Wed Jan 01 00:00:00 2020","Fri Jan 01 00:00:00 2021") | 
+(3 rows)
+
+INSERT INTO without_overlaps_test VALUES ('[18,18]', '2018-01-01', '2020-01-01');
+INSERT INTO without_overlaps_test VALUES ('[18,18]', '2020-01-01', '2021-01-01');
+INSERT INTO referencing_period_test VALUES ('[12,12]', tsrange('2018-01-01', '2021-01-01'), '[18,18]');
+UPDATE without_overlaps_test SET id = '[19,19]' WHERE id = '[18,18]' AND tsrange(valid_from, valid_til) @> '2019-01-01'::timestamp;
+SELECT * FROM referencing_period_test WHERE id = '[12,12]';
+   id    |                        valid_at                         | parent_id 
+---------+---------------------------------------------------------+-----------
+ [12,13) | ["Mon Jan 01 00:00:00 2018","Wed Jan 01 00:00:00 2020") | 
+ [12,13) | ["Wed Jan 01 00:00:00 2020","Fri Jan 01 00:00:00 2021") | [18,19)
+(2 rows)
+
+-- test FK parent deletes SET NULL
+INSERT INTO without_overlaps_test VALUES ('[11,11]', '2018-01-01', '2021-01-01');
+INSERT INTO referencing_period_test VALUES ('[7,7]', tsrange('2018-01-01', '2021-01-01'), '[11,11]');
+DELETE FROM without_overlaps_test FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' WHERE id = '[11,11]';
+SELECT * FROM referencing_period_test WHERE id = '[7,7]';
+  id   |                        valid_at                         | parent_id 
+-------+---------------------------------------------------------+-----------
+ [7,8) | ["Tue Jan 01 00:00:00 2019","Wed Jan 01 00:00:00 2020") | 
+ [7,8) | ["Mon Jan 01 00:00:00 2018","Tue Jan 01 00:00:00 2019") | [11,12)
+ [7,8) | ["Wed Jan 01 00:00:00 2020","Fri Jan 01 00:00:00 2021") | [11,12)
+(3 rows)
+
+DELETE FROM without_overlaps_test WHERE id = '[11,11]';
+SELECT * FROM referencing_period_test WHERE id = '[7,7]';
+  id   |                        valid_at                         | parent_id 
+-------+---------------------------------------------------------+-----------
+ [7,8) | ["Tue Jan 01 00:00:00 2019","Wed Jan 01 00:00:00 2020") | 
+ [7,8) | ["Mon Jan 01 00:00:00 2018","Tue Jan 01 00:00:00 2019") | 
+ [7,8) | ["Wed Jan 01 00:00:00 2020","Fri Jan 01 00:00:00 2021") | 
+(3 rows)
+
+INSERT INTO without_overlaps_test VALUES ('[20,20]', '2018-01-01', '2020-01-01');
+INSERT INTO without_overlaps_test VALUES ('[20,20]', '2020-01-01', '2021-01-01');
+INSERT INTO referencing_period_test VALUES ('[13,13]', tsrange('2018-01-01', '2021-01-01'), '[20,20]');
+DELETE FROM without_overlaps_test WHERE id = '[20,20]' AND tsrange(valid_from, valid_til) @> '2019-01-01'::timestamp;
+SELECT * FROM referencing_period_test WHERE id = '[13,13]';
+   id    |                        valid_at                         | parent_id 
+---------+---------------------------------------------------------+-----------
+ [13,14) | ["Mon Jan 01 00:00:00 2018","Wed Jan 01 00:00:00 2020") | 
+ [13,14) | ["Wed Jan 01 00:00:00 2020","Fri Jan 01 00:00:00 2021") | [20,21)
+(2 rows)
+
+-- test FK parent updates SET DEFAULT
+INSERT INTO without_overlaps_test VALUES ('[-1,-1]', null, null);
+INSERT INTO without_overlaps_test VALUES ('[12,12]', '2018-01-01', '2021-01-01');
+INSERT INTO referencing_period_test VALUES ('[8,8]', tsrange('2018-01-01', '2021-01-01'), '[12,12]');
+ALTER TABLE referencing_period_test
+  ALTER COLUMN parent_id SET DEFAULT '[-1,-1]',
+	DROP CONSTRAINT referencing_period_fk,
+	ADD CONSTRAINT referencing_period_fk
+		FOREIGN KEY (parent_id, PERIOD valid_at)
+		REFERENCES without_overlaps_test
+		ON DELETE SET DEFAULT ON UPDATE SET DEFAULT;
+UPDATE without_overlaps_test FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' SET id = '[13,13]' WHERE id = '[12,12]';
+SELECT * FROM referencing_period_test WHERE id = '[8,8]';
+  id   |                        valid_at                         | parent_id 
+-------+---------------------------------------------------------+-----------
+ [8,9) | ["Tue Jan 01 00:00:00 2019","Wed Jan 01 00:00:00 2020") | [-1,0)
+ [8,9) | ["Mon Jan 01 00:00:00 2018","Tue Jan 01 00:00:00 2019") | [12,13)
+ [8,9) | ["Wed Jan 01 00:00:00 2020","Fri Jan 01 00:00:00 2021") | [12,13)
+(3 rows)
+
+UPDATE without_overlaps_test SET id = '[13,13]' WHERE id = '[12,12]';
+SELECT * FROM referencing_period_test WHERE id = '[8,8]';
+  id   |                        valid_at                         | parent_id 
+-------+---------------------------------------------------------+-----------
+ [8,9) | ["Tue Jan 01 00:00:00 2019","Wed Jan 01 00:00:00 2020") | [-1,0)
+ [8,9) | ["Mon Jan 01 00:00:00 2018","Tue Jan 01 00:00:00 2019") | [-1,0)
+ [8,9) | ["Wed Jan 01 00:00:00 2020","Fri Jan 01 00:00:00 2021") | [-1,0)
+(3 rows)
+
+INSERT INTO without_overlaps_test VALUES ('[22,22]', '2018-01-01', '2020-01-01');
+INSERT INTO without_overlaps_test VALUES ('[22,22]', '2020-01-01', '2021-01-01');
+INSERT INTO referencing_period_test VALUES ('[14,14]', tsrange('2018-01-01', '2021-01-01'), '[22,22]');
+UPDATE without_overlaps_test SET id = '[23,23]' WHERE id = '[22,22]' AND tsrange(valid_from, valid_til) @> '2019-01-01'::timestamp;
+SELECT * FROM referencing_period_test WHERE id = '[14,14]';
+   id    |                        valid_at                         | parent_id 
+---------+---------------------------------------------------------+-----------
+ [14,15) | ["Mon Jan 01 00:00:00 2018","Wed Jan 01 00:00:00 2020") | [-1,0)
+ [14,15) | ["Wed Jan 01 00:00:00 2020","Fri Jan 01 00:00:00 2021") | [22,23)
+(2 rows)
+
+-- test FK parent deletes SET DEFAULT
+INSERT INTO without_overlaps_test VALUES ('[14,14]', '2018-01-01', '2021-01-01');
+INSERT INTO referencing_period_test VALUES ('[9,9]', tsrange('2018-01-01', '2021-01-01'), '[14,14]');
+DELETE FROM without_overlaps_test FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' WHERE id = '[14,14]';
+SELECT * FROM referencing_period_test WHERE id = '[9,9]';
+   id   |                        valid_at                         | parent_id 
+--------+---------------------------------------------------------+-----------
+ [9,10) | ["Tue Jan 01 00:00:00 2019","Wed Jan 01 00:00:00 2020") | [-1,0)
+ [9,10) | ["Mon Jan 01 00:00:00 2018","Tue Jan 01 00:00:00 2019") | [14,15)
+ [9,10) | ["Wed Jan 01 00:00:00 2020","Fri Jan 01 00:00:00 2021") | [14,15)
+(3 rows)
+
+DELETE FROM without_overlaps_test WHERE id = '[14,14]';
+SELECT * FROM referencing_period_test WHERE id = '[9,9]';
+   id   |                        valid_at                         | parent_id 
+--------+---------------------------------------------------------+-----------
+ [9,10) | ["Tue Jan 01 00:00:00 2019","Wed Jan 01 00:00:00 2020") | [-1,0)
+ [9,10) | ["Mon Jan 01 00:00:00 2018","Tue Jan 01 00:00:00 2019") | [-1,0)
+ [9,10) | ["Wed Jan 01 00:00:00 2020","Fri Jan 01 00:00:00 2021") | [-1,0)
+(3 rows)
+
+INSERT INTO without_overlaps_test VALUES ('[24,24]', '2018-01-01', '2020-01-01');
+INSERT INTO without_overlaps_test VALUES ('[24,24]', '2020-01-01', '2021-01-01');
+INSERT INTO referencing_period_test VALUES ('[15,15]', tsrange('2018-01-01', '2021-01-01'), '[24,24]');
+DELETE FROM without_overlaps_test WHERE id = '[24,24]' AND tsrange(valid_from, valid_til) @> '2019-01-01'::timestamp;
+SELECT * FROM referencing_period_test WHERE id = '[15,15]';
+   id    |                        valid_at                         | parent_id 
+---------+---------------------------------------------------------+-----------
+ [15,16) | ["Mon Jan 01 00:00:00 2018","Wed Jan 01 00:00:00 2020") | [-1,0)
+ [15,16) | ["Wed Jan 01 00:00:00 2020","Fri Jan 01 00:00:00 2021") | [24,25)
+(2 rows)
+
+--
+-- test FOREIGN KEY, PERIOD references range
+--
+DROP TABLE without_overlaps_test CASCADE;
+NOTICE:  drop cascades to constraint referencing_period_fk on table referencing_period_test
+CREATE TABLE without_overlaps_test (
+	id int4range,
+	valid_at tsrange,
+	CONSTRAINT without_overlaps_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+INSERT INTO without_overlaps_test VALUES ('[1,1]', tsrange('2018-01-02', '2018-02-03'));
+INSERT INTO without_overlaps_test VALUES ('[1,1]', tsrange('2018-03-03', '2018-04-04'));
+INSERT INTO without_overlaps_test VALUES ('[2,2]', tsrange('2018-01-01', '2018-01-05'));
+INSERT INTO without_overlaps_test VALUES ('[3,3]', tsrange('2018-01-01', NULL));
+-- Can't create a FK with a mismatched range type
+CREATE TABLE referencing_period_test2 (
+	id int4range,
+	valid_from int4,
+  valid_til int4,
+	PERIOD FOR valid_at (valid_from, valid_til),
+	parent_id int4range,
+	CONSTRAINT referencing_period_pk2 PRIMARY KEY (id, valid_at WITHOUT OVERLAPS),
+	CONSTRAINT referencing_period_fk2 FOREIGN KEY (parent_id, PERIOD valid_at)
+		REFERENCES without_overlaps_test (id, PERIOD valid_at)
+);
+ERROR:  foreign key constraint "referencing_period_fk2" cannot be implemented
+DETAIL:  Key columns "valid_at" and "valid_at" are of incompatible types: int4range and tsrange.
+-- with inferred PK on the referenced table:
+DROP TABLE referencing_period_test;
+CREATE TABLE referencing_period_test (
+	id int4range,
+	valid_from timestamp,
+	valid_til timestamp,
+	PERIOD FOR valid_at (valid_from, valid_til),
+	parent_id int4range,
+	CONSTRAINT referencing_period_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS),
+	CONSTRAINT referencing_period_fk FOREIGN KEY (parent_id, PERIOD valid_at)
+		REFERENCES without_overlaps_test
+);
+DROP TABLE referencing_period_test;
+-- should fail because of duplicate referenced columns:
+CREATE TABLE referencing_period_test (
+	id int4range,
+	valid_from timestamp,
+	valid_til timestamp,
+	PERIOD FOR valid_at (valid_from, valid_til),
+	parent_id int4range,
+	CONSTRAINT referencing_period_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS),
+	CONSTRAINT referencing_period_fk FOREIGN KEY (parent_id, PERIOD parent_id)
+		REFERENCES without_overlaps_test (id, PERIOD id)
+);
+ERROR:  foreign key referenced-columns list must not contain duplicates
+--
+-- test ALTER TABLE ADD CONSTRAINT
+--
+CREATE TABLE referencing_period_test (
+	id int4range,
+	valid_from timestamp,
+	valid_til timestamp,
+	PERIOD FOR valid_at (valid_from, valid_til),
+	parent_id int4range,
+	CONSTRAINT referencing_period_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+ALTER TABLE referencing_period_test
+	ADD CONSTRAINT referencing_period_fk
+	FOREIGN KEY (parent_id, PERIOD valid_at)
+	REFERENCES without_overlaps_test (id, PERIOD valid_at);
+ALTER TABLE referencing_period_test
+	DROP CONSTRAINT referencing_period_fk;
+-- with inferred PK on the referenced table:
+ALTER TABLE referencing_period_test
+	ADD CONSTRAINT referencing_period_fk
+	FOREIGN KEY (parent_id, PERIOD valid_at)
+	REFERENCES without_overlaps_test;
+-- should fail because of duplicate referenced columns:
+ALTER TABLE referencing_period_test
+	ADD CONSTRAINT referencing_period_fk2
+	FOREIGN KEY (parent_id, PERIOD parent_id)
+	REFERENCES without_overlaps_test (id, PERIOD id);
+ERROR:  foreign key referenced-columns list must not contain duplicates
+--
+-- test with rows already
+--
+DELETE FROM referencing_period_test;
+ALTER TABLE referencing_period_test
+	DROP CONSTRAINT referencing_period_fk;
+INSERT INTO referencing_period_test VALUES ('[1,1]', '2018-01-02', '2018-02-01', '[1,1]');
+ALTER TABLE referencing_period_test
+	ADD CONSTRAINT referencing_period_fk
+	FOREIGN KEY (parent_id, PERIOD valid_at)
+	REFERENCES without_overlaps_test;
+ALTER TABLE referencing_period_test
+	DROP CONSTRAINT referencing_period_fk;
+INSERT INTO referencing_period_test VALUES ('[2,2]', '2018-01-02', '2018-04-01', '[1,1]');
+-- should fail:
+ALTER TABLE referencing_period_test
+	ADD CONSTRAINT referencing_period_fk
+	FOREIGN KEY (parent_id, PERIOD valid_at)
+	REFERENCES without_overlaps_test;
+ERROR:  insert or update on table "referencing_period_test" violates foreign key constraint "referencing_period_fk"
+DETAIL:  Key (parent_id, valid_from, valid_til)=([1,2), Tue Jan 02 00:00:00 2018, Sun Apr 01 00:00:00 2018) is not present in table "without_overlaps_test".
+-- okay again:
+DELETE FROM referencing_period_test;
+ALTER TABLE referencing_period_test
+	ADD CONSTRAINT referencing_period_fk
+	FOREIGN KEY (parent_id, PERIOD valid_at)
+	REFERENCES without_overlaps_test;
+--
+-- test pg_get_constraintdef
+--
+SELECT pg_get_constraintdef(oid) FROM pg_constraint WHERE conname = 'referencing_period_fk';
+                                      pg_get_constraintdef                                      
+------------------------------------------------------------------------------------------------
+ FOREIGN KEY (parent_id, PERIOD valid_at) REFERENCES without_overlaps_test(id, PERIOD valid_at)
+(1 row)
+
+--
+-- test FK child inserts
+--
+INSERT INTO referencing_period_test VALUES ('[1,1]', '2018-01-02', '2018-02-01', '[1,1]');
+-- should fail:
+INSERT INTO referencing_period_test VALUES ('[2,2]', '2018-01-02', '2018-04-01', '[1,1]');
+ERROR:  insert or update on table "referencing_period_test" violates foreign key constraint "referencing_period_fk"
+DETAIL:  Key (parent_id, valid_from, valid_til)=([1,2), Tue Jan 02 00:00:00 2018, Sun Apr 01 00:00:00 2018) is not present in table "without_overlaps_test".
+-- now it should work:
+INSERT INTO without_overlaps_test VALUES ('[1,1]', tsrange('2018-02-03', '2018-03-03'));
+INSERT INTO referencing_period_test VALUES ('[2,2]', '2018-01-02', '2018-04-01', '[1,1]');
+--
+-- test FK child updates
+--
+UPDATE referencing_period_test SET valid_from = '2018-01-02', valid_til = '2018-03-01' WHERE id = '[1,1]';
+-- should fail:
+UPDATE referencing_period_test SET valid_from = '2018-01-02', valid_til = '2018-05-01' WHERE id = '[1,1]';
+ERROR:  insert or update on table "referencing_period_test" violates foreign key constraint "referencing_period_fk"
+DETAIL:  Key (parent_id, valid_from, valid_til)=([1,2), Tue Jan 02 00:00:00 2018, Tue May 01 00:00:00 2018) is not present in table "without_overlaps_test".
+UPDATE referencing_period_test SET parent_id = '[8,8]' WHERE id = '[1,1]';
+ERROR:  insert or update on table "referencing_period_test" violates foreign key constraint "referencing_period_fk"
+DETAIL:  Key (parent_id, valid_from, valid_til)=([8,9), Tue Jan 02 00:00:00 2018, Thu Mar 01 00:00:00 2018) is not present in table "without_overlaps_test".
+--
+-- test FK parent updates NO ACTION
+--
+-- a PK update that succeeds because the numeric id isn't referenced:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01') WHERE id = '[5,5]';
+-- a PK update that succeeds even though the numeric id is referenced because the range isn't:
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-02-01', '2018-03-01'));
+INSERT INTO referencing_period_test VALUES ('[3,3]', '2018-01-05', '2018-01-10', '[5,5]');
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-02-01', '2016-03-01')
+WHERE id = '[5,5]' AND valid_at = tsrange('2018-02-01', '2018-03-01');
+-- a PK update that fails because both are referenced:
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01')
+WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01','2018-02-01');
+ERROR:  update or delete on table "without_overlaps_test" violates foreign key constraint "referencing_period_fk" on table "referencing_period_test"
+DETAIL:  Key (id, valid_at)=([5,6), ["Mon Jan 01 00:00:00 2018","Thu Feb 01 00:00:00 2018")) is still referenced from table "referencing_period_test".
+-- then delete the objecting FK record and the same PK update succeeds:
+DELETE FROM referencing_period_test WHERE id = '[3,3]';
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01')
+WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+-- clean up:
+DELETE FROM referencing_period_test WHERE parent_id = '[5,5]';
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+--
+-- test FK parent updates RESTRICT
+--
+ALTER TABLE referencing_period_test
+	DROP CONSTRAINT referencing_period_fk;
+ALTER TABLE referencing_period_test
+	ADD CONSTRAINT referencing_period_fk
+	FOREIGN KEY (parent_id, PERIOD valid_at)
+	REFERENCES without_overlaps_test
+	ON DELETE RESTRICT;
+-- a PK update that succeeds because the numeric id isn't referenced:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01') WHERE id = '[5,5]';
+-- a PK update that succeeds even though the numeric id is referenced because the range isn't:
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-02-01', '2018-03-01'));
+INSERT INTO referencing_period_test VALUES ('[3,3]', '2018-01-05', '2018-01-10', '[5,5]');
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-02-01', '2016-03-01')
+WHERE id = '[5,5]' AND valid_at = tsrange('2018-02-01', '2018-03-01');
+-- a PK update that fails because both are referenced:
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01')
+WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+ERROR:  update or delete on table "without_overlaps_test" violates foreign key constraint "referencing_period_fk" on table "referencing_period_test"
+DETAIL:  Key (id, valid_at)=([5,6), ["Mon Jan 01 00:00:00 2018","Thu Feb 01 00:00:00 2018")) is still referenced from table "referencing_period_test".
+-- then delete the objecting FK record and the same PK update succeeds:
+DELETE FROM referencing_period_test WHERE id = '[3,3]';
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01')
+WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+-- clean up:
+DELETE FROM referencing_period_test WHERE parent_id = '[5,5]';
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+--
+-- test FK parent deletes NO ACTION
+--
+ALTER TABLE referencing_period_test
+	DROP CONSTRAINT referencing_period_fk;
+ALTER TABLE referencing_period_test
+	ADD CONSTRAINT referencing_period_fk
+	FOREIGN KEY (parent_id, PERIOD valid_at)
+	REFERENCES without_overlaps_test;
+-- a PK delete that succeeds because the numeric id isn't referenced:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+-- a PK delete that succeeds even though the numeric id is referenced because the range isn't:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-02-01', '2018-03-01'));
+INSERT INTO referencing_period_test VALUES ('[3,3]', '2018-01-05', '2018-01-10', '[5,5]');
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-02-01', '2018-03-01');
+-- a PK delete that fails because both are referenced:
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+ERROR:  update or delete on table "without_overlaps_test" violates foreign key constraint "referencing_period_fk" on table "referencing_period_test"
+DETAIL:  Key (id, valid_at)=([5,6), ["Mon Jan 01 00:00:00 2018","Thu Feb 01 00:00:00 2018")) is still referenced from table "referencing_period_test".
+-- then delete the objecting FK record and the same PK delete succeeds:
+DELETE FROM referencing_period_test WHERE id = '[3,3]';
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+--
+-- test FK parent deletes RESTRICT
+--
+ALTER TABLE referencing_period_test
+	DROP CONSTRAINT referencing_period_fk;
+ALTER TABLE referencing_period_test
+	ADD CONSTRAINT referencing_period_fk
+	FOREIGN KEY (parent_id, PERIOD valid_at)
+	REFERENCES without_overlaps_test
+	ON DELETE RESTRICT;
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+-- a PK delete that succeeds even though the numeric id is referenced because the range isn't:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-02-01', '2018-03-01'));
+INSERT INTO referencing_period_test VALUES ('[3,3]', '2018-01-05', '2018-01-10', '[5,5]');
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-02-01', '2018-03-01');
+-- a PK delete that fails because both are referenced:
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+ERROR:  update or delete on table "without_overlaps_test" violates foreign key constraint "referencing_period_fk" on table "referencing_period_test"
+DETAIL:  Key (id, valid_at)=([5,6), ["Mon Jan 01 00:00:00 2018","Thu Feb 01 00:00:00 2018")) is still referenced from table "referencing_period_test".
+-- then delete the objecting FK record and the same PK delete succeeds:
+DELETE FROM referencing_period_test WHERE id = '[3,3]';
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+--
+-- test ON UPDATE/DELETE options
+--
+-- test FK parent updates CASCADE
+INSERT INTO without_overlaps_test VALUES ('[6,6]', tsrange('2018-01-01', '2021-01-01'));
+INSERT INTO referencing_period_test VALUES ('[4,4]', '2018-01-01', '2021-01-01', '[6,6]');
+ALTER TABLE referencing_period_test
+	DROP CONSTRAINT referencing_period_fk,
+	ADD CONSTRAINT referencing_period_fk
+		FOREIGN KEY (parent_id, PERIOD valid_at)
+		REFERENCES without_overlaps_test
+		ON DELETE CASCADE ON UPDATE CASCADE;
+UPDATE without_overlaps_test FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' SET id = '[7,7]' WHERE id = '[6,6]';
+SELECT * FROM referencing_period_test WHERE id = '[4,4]';
+  id   |        valid_from        |        valid_til         | parent_id 
+-------+--------------------------+--------------------------+-----------
+ [4,5) | Tue Jan 01 00:00:00 2019 | Wed Jan 01 00:00:00 2020 | [7,8)
+ [4,5) | Mon Jan 01 00:00:00 2018 | Tue Jan 01 00:00:00 2019 | [6,7)
+ [4,5) | Wed Jan 01 00:00:00 2020 | Fri Jan 01 00:00:00 2021 | [6,7)
+(3 rows)
+
+UPDATE without_overlaps_test SET id = '[7,7]' WHERE id = '[6,6]';
+SELECT * FROM referencing_period_test WHERE id = '[4,4]';
+  id   |        valid_from        |        valid_til         | parent_id 
+-------+--------------------------+--------------------------+-----------
+ [4,5) | Tue Jan 01 00:00:00 2019 | Wed Jan 01 00:00:00 2020 | [7,8)
+ [4,5) | Mon Jan 01 00:00:00 2018 | Tue Jan 01 00:00:00 2019 | [7,8)
+ [4,5) | Wed Jan 01 00:00:00 2020 | Fri Jan 01 00:00:00 2021 | [7,8)
+(3 rows)
+
+INSERT INTO without_overlaps_test VALUES ('[15,15]', tsrange('2018-01-01', '2020-01-01'));
+INSERT INTO without_overlaps_test VALUES ('[15,15]', tsrange('2020-01-01', '2021-01-01'));
+INSERT INTO referencing_period_test VALUES ('[10,10]', '2018-01-01', '2021-01-01', '[15,15]');
+UPDATE without_overlaps_test SET id = '[16,16]' WHERE id = '[15,15]' AND valid_at @> '2019-01-01'::timestamp;
+SELECT * FROM referencing_period_test WHERE id = '[10,10]';
+   id    |        valid_from        |        valid_til         | parent_id 
+---------+--------------------------+--------------------------+-----------
+ [10,11) | Mon Jan 01 00:00:00 2018 | Wed Jan 01 00:00:00 2020 | [16,17)
+ [10,11) | Wed Jan 01 00:00:00 2020 | Fri Jan 01 00:00:00 2021 | [15,16)
+(2 rows)
+
+-- test FK parent deletes CASCADE
+INSERT INTO without_overlaps_test VALUES ('[8,8]', tsrange('2018-01-01', '2021-01-01'));
+INSERT INTO referencing_period_test VALUES ('[5,5]', '2018-01-01', '2021-01-01', '[8,8]');
+DELETE FROM without_overlaps_test FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' WHERE id = '[8,8]';
+SELECT * FROM referencing_period_test WHERE id = '[5,5]';
+  id   |        valid_from        |        valid_til         | parent_id 
+-------+--------------------------+--------------------------+-----------
+ [5,6) | Mon Jan 01 00:00:00 2018 | Tue Jan 01 00:00:00 2019 | [8,9)
+ [5,6) | Wed Jan 01 00:00:00 2020 | Fri Jan 01 00:00:00 2021 | [8,9)
+(2 rows)
+
+DELETE FROM without_overlaps_test WHERE id = '[8,8]';
+SELECT * FROM referencing_period_test WHERE id = '[5,5]';
+ id | valid_from | valid_til | parent_id 
+----+------------+-----------+-----------
+(0 rows)
+
+INSERT INTO without_overlaps_test VALUES ('[17,17]', tsrange('2018-01-01', '2020-01-01'));
+INSERT INTO without_overlaps_test VALUES ('[17,17]', tsrange('2020-01-01', '2021-01-01'));
+INSERT INTO referencing_period_test VALUES ('[11,11]', '2018-01-01', '2021-01-01', '[17,17]');
+DELETE FROM without_overlaps_test WHERE id = '[17,17]' AND valid_at @> '2019-01-01'::timestamp;
+SELECT * FROM referencing_period_test WHERE id = '[11,11]';
+   id    |        valid_from        |        valid_til         | parent_id 
+---------+--------------------------+--------------------------+-----------
+ [11,12) | Wed Jan 01 00:00:00 2020 | Fri Jan 01 00:00:00 2021 | [17,18)
+(1 row)
+
+-- test FK parent updates SET NULL
+INSERT INTO without_overlaps_test VALUES ('[9,9]', tsrange('2018-01-01', '2021-01-01'));
+INSERT INTO referencing_period_test VALUES ('[6,6]', '2018-01-01', '2021-01-01', '[9,9]');
+ALTER TABLE referencing_period_test
+	DROP CONSTRAINT referencing_period_fk,
+	ADD CONSTRAINT referencing_period_fk
+		FOREIGN KEY (parent_id, PERIOD valid_at)
+		REFERENCES without_overlaps_test
+		ON DELETE SET NULL ON UPDATE SET NULL;
+UPDATE without_overlaps_test FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' SET id = '[10,10]' WHERE id = '[9,9]';
+SELECT * FROM referencing_period_test WHERE id = '[6,6]';
+  id   |        valid_from        |        valid_til         | parent_id 
+-------+--------------------------+--------------------------+-----------
+ [6,7) | Tue Jan 01 00:00:00 2019 | Wed Jan 01 00:00:00 2020 | 
+ [6,7) | Mon Jan 01 00:00:00 2018 | Tue Jan 01 00:00:00 2019 | [9,10)
+ [6,7) | Wed Jan 01 00:00:00 2020 | Fri Jan 01 00:00:00 2021 | [9,10)
+(3 rows)
+
+UPDATE without_overlaps_test SET id = '[10,10]' WHERE id = '[9,9]';
+SELECT * FROM referencing_period_test WHERE id = '[6,6]';
+  id   |        valid_from        |        valid_til         | parent_id 
+-------+--------------------------+--------------------------+-----------
+ [6,7) | Tue Jan 01 00:00:00 2019 | Wed Jan 01 00:00:00 2020 | 
+ [6,7) | Mon Jan 01 00:00:00 2018 | Tue Jan 01 00:00:00 2019 | 
+ [6,7) | Wed Jan 01 00:00:00 2020 | Fri Jan 01 00:00:00 2021 | 
+(3 rows)
+
+INSERT INTO without_overlaps_test VALUES ('[18,18]', tsrange('2018-01-01', '2020-01-01'));
+INSERT INTO without_overlaps_test VALUES ('[18,18]', tsrange('2020-01-01', '2021-01-01'));
+INSERT INTO referencing_period_test VALUES ('[12,12]', '2018-01-01', '2021-01-01', '[18,18]');
+UPDATE without_overlaps_test SET id = '[19,19]' WHERE id = '[18,18]' AND valid_at @> '2019-01-01'::timestamp;
+SELECT * FROM referencing_period_test WHERE id = '[12,12]';
+   id    |        valid_from        |        valid_til         | parent_id 
+---------+--------------------------+--------------------------+-----------
+ [12,13) | Mon Jan 01 00:00:00 2018 | Wed Jan 01 00:00:00 2020 | 
+ [12,13) | Wed Jan 01 00:00:00 2020 | Fri Jan 01 00:00:00 2021 | [18,19)
+(2 rows)
+
+-- test FK parent deletes SET NULL
+INSERT INTO without_overlaps_test VALUES ('[11,11]', tsrange('2018-01-01', '2021-01-01'));
+INSERT INTO referencing_period_test VALUES ('[7,7]', '2018-01-01', '2021-01-01', '[11,11]');
+DELETE FROM without_overlaps_test FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' WHERE id = '[11,11]';
+SELECT * FROM referencing_period_test WHERE id = '[7,7]';
+  id   |        valid_from        |        valid_til         | parent_id 
+-------+--------------------------+--------------------------+-----------
+ [7,8) | Tue Jan 01 00:00:00 2019 | Wed Jan 01 00:00:00 2020 | 
+ [7,8) | Mon Jan 01 00:00:00 2018 | Tue Jan 01 00:00:00 2019 | [11,12)
+ [7,8) | Wed Jan 01 00:00:00 2020 | Fri Jan 01 00:00:00 2021 | [11,12)
+(3 rows)
+
+DELETE FROM without_overlaps_test WHERE id = '[11,11]';
+SELECT * FROM referencing_period_test WHERE id = '[7,7]';
+  id   |        valid_from        |        valid_til         | parent_id 
+-------+--------------------------+--------------------------+-----------
+ [7,8) | Tue Jan 01 00:00:00 2019 | Wed Jan 01 00:00:00 2020 | 
+ [7,8) | Mon Jan 01 00:00:00 2018 | Tue Jan 01 00:00:00 2019 | 
+ [7,8) | Wed Jan 01 00:00:00 2020 | Fri Jan 01 00:00:00 2021 | 
+(3 rows)
+
+INSERT INTO without_overlaps_test VALUES ('[20,20]', tsrange('2018-01-01', '2020-01-01'));
+INSERT INTO without_overlaps_test VALUES ('[20,20]', tsrange('2020-01-01', '2021-01-01'));
+INSERT INTO referencing_period_test VALUES ('[13,13]', '2018-01-01', '2021-01-01', '[20,20]');
+DELETE FROM without_overlaps_test WHERE id = '[20,20]' AND valid_at @> '2019-01-01'::timestamp;
+SELECT * FROM referencing_period_test WHERE id = '[13,13]';
+   id    |        valid_from        |        valid_til         | parent_id 
+---------+--------------------------+--------------------------+-----------
+ [13,14) | Mon Jan 01 00:00:00 2018 | Wed Jan 01 00:00:00 2020 | 
+ [13,14) | Wed Jan 01 00:00:00 2020 | Fri Jan 01 00:00:00 2021 | [20,21)
+(2 rows)
+
+-- test FK parent updates SET DEFAULT
+INSERT INTO without_overlaps_test VALUES ('[-1,-1]', tsrange(null, null));
+INSERT INTO without_overlaps_test VALUES ('[12,12]', tsrange('2018-01-01', '2021-01-01'));
+INSERT INTO referencing_period_test VALUES ('[8,8]', '2018-01-01', '2021-01-01', '[12,12]');
+ALTER TABLE referencing_period_test
+  ALTER COLUMN parent_id SET DEFAULT '[-1,-1]',
+	DROP CONSTRAINT referencing_period_fk,
+	ADD CONSTRAINT referencing_period_fk
+		FOREIGN KEY (parent_id, PERIOD valid_at)
+		REFERENCES without_overlaps_test
+		ON DELETE SET DEFAULT ON UPDATE SET DEFAULT;
+UPDATE without_overlaps_test FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' SET id = '[13,13]' WHERE id = '[12,12]';
+SELECT * FROM referencing_period_test WHERE id = '[8,8]';
+  id   |        valid_from        |        valid_til         | parent_id 
+-------+--------------------------+--------------------------+-----------
+ [8,9) | Tue Jan 01 00:00:00 2019 | Wed Jan 01 00:00:00 2020 | [-1,0)
+ [8,9) | Mon Jan 01 00:00:00 2018 | Tue Jan 01 00:00:00 2019 | [12,13)
+ [8,9) | Wed Jan 01 00:00:00 2020 | Fri Jan 01 00:00:00 2021 | [12,13)
+(3 rows)
+
+UPDATE without_overlaps_test SET id = '[13,13]' WHERE id = '[12,12]';
+SELECT * FROM referencing_period_test WHERE id = '[8,8]';
+  id   |        valid_from        |        valid_til         | parent_id 
+-------+--------------------------+--------------------------+-----------
+ [8,9) | Tue Jan 01 00:00:00 2019 | Wed Jan 01 00:00:00 2020 | [-1,0)
+ [8,9) | Mon Jan 01 00:00:00 2018 | Tue Jan 01 00:00:00 2019 | [-1,0)
+ [8,9) | Wed Jan 01 00:00:00 2020 | Fri Jan 01 00:00:00 2021 | [-1,0)
+(3 rows)
+
+INSERT INTO without_overlaps_test VALUES ('[22,22]', tsrange('2018-01-01', '2020-01-01'));
+INSERT INTO without_overlaps_test VALUES ('[22,22]', tsrange('2020-01-01', '2021-01-01'));
+INSERT INTO referencing_period_test VALUES ('[14,14]', '2018-01-01', '2021-01-01', '[22,22]');
+UPDATE without_overlaps_test SET id = '[23,23]' WHERE id = '[22,22]' AND valid_at @> '2019-01-01'::timestamp;
+SELECT * FROM referencing_period_test WHERE id = '[14,14]';
+   id    |        valid_from        |        valid_til         | parent_id 
+---------+--------------------------+--------------------------+-----------
+ [14,15) | Mon Jan 01 00:00:00 2018 | Wed Jan 01 00:00:00 2020 | [-1,0)
+ [14,15) | Wed Jan 01 00:00:00 2020 | Fri Jan 01 00:00:00 2021 | [22,23)
+(2 rows)
+
+-- test FK parent deletes SET DEFAULT
+INSERT INTO without_overlaps_test VALUES ('[14,14]', tsrange('2018-01-01', '2021-01-01'));
+INSERT INTO referencing_period_test VALUES ('[9,9]', '2018-01-01', '2021-01-01', '[14,14]');
+DELETE FROM without_overlaps_test FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' WHERE id = '[14,14]';
+SELECT * FROM referencing_period_test WHERE id = '[9,9]';
+   id   |        valid_from        |        valid_til         | parent_id 
+--------+--------------------------+--------------------------+-----------
+ [9,10) | Tue Jan 01 00:00:00 2019 | Wed Jan 01 00:00:00 2020 | [-1,0)
+ [9,10) | Mon Jan 01 00:00:00 2018 | Tue Jan 01 00:00:00 2019 | [14,15)
+ [9,10) | Wed Jan 01 00:00:00 2020 | Fri Jan 01 00:00:00 2021 | [14,15)
+(3 rows)
+
+DELETE FROM without_overlaps_test WHERE id = '[14,14]';
+SELECT * FROM referencing_period_test WHERE id = '[9,9]';
+   id   |        valid_from        |        valid_til         | parent_id 
+--------+--------------------------+--------------------------+-----------
+ [9,10) | Tue Jan 01 00:00:00 2019 | Wed Jan 01 00:00:00 2020 | [-1,0)
+ [9,10) | Mon Jan 01 00:00:00 2018 | Tue Jan 01 00:00:00 2019 | [-1,0)
+ [9,10) | Wed Jan 01 00:00:00 2020 | Fri Jan 01 00:00:00 2021 | [-1,0)
+(3 rows)
+
+INSERT INTO without_overlaps_test VALUES ('[24,24]', tsrange('2018-01-01', '2020-01-01'));
+INSERT INTO without_overlaps_test VALUES ('[24,24]', tsrange('2020-01-01', '2021-01-01'));
+INSERT INTO referencing_period_test VALUES ('[15,15]', '2018-01-01', '2021-01-01', '[24,24]');
+DELETE FROM without_overlaps_test WHERE id = '[24,24]' AND valid_at @> '2019-01-01'::timestamp;
+SELECT * FROM referencing_period_test WHERE id = '[15,15]';
+   id    |        valid_from        |        valid_til         | parent_id 
+---------+--------------------------+--------------------------+-----------
+ [15,16) | Mon Jan 01 00:00:00 2018 | Wed Jan 01 00:00:00 2020 | [-1,0)
+ [15,16) | Wed Jan 01 00:00:00 2020 | Fri Jan 01 00:00:00 2021 | [24,25)
+(2 rows)
+
+--
+-- test FOREIGN KEY, PERIOD references PERIOD
+--
+DROP TABLE without_overlaps_test CASCADE;
+NOTICE:  drop cascades to constraint referencing_period_fk on table referencing_period_test
+CREATE TABLE without_overlaps_test (
+	id int4range,
+	valid_from timestamp,
+	valid_til timestamp,
+	PERIOD FOR valid_at (valid_from, valid_til),
+	CONSTRAINT without_overlaps_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+INSERT INTO without_overlaps_test VALUES ('[1,1]', '2018-01-02', '2018-02-03');
+INSERT INTO without_overlaps_test VALUES ('[1,1]', '2018-03-03', '2018-04-04');
+INSERT INTO without_overlaps_test VALUES ('[2,2]', '2018-01-01', '2018-01-05');
+INSERT INTO without_overlaps_test VALUES ('[3,3]', '2018-01-01', NULL);
+-- Can't create a FK with a mismatched range type
+CREATE TABLE referencing_period_test2 (
+	id int4range,
+	valid_from int4,
+  valid_til int4,
+	PERIOD FOR valid_at (valid_from, valid_til),
+	parent_id int4range,
+	CONSTRAINT referencing_period_pk2 PRIMARY KEY (id, valid_at WITHOUT OVERLAPS),
+	CONSTRAINT referencing_period_fk2 FOREIGN KEY (parent_id, PERIOD valid_at)
+		REFERENCES without_overlaps_test (id, PERIOD valid_at)
+);
+ERROR:  foreign key constraint "referencing_period_fk2" cannot be implemented
+DETAIL:  Key columns "valid_at" and "valid_at" are of incompatible types: int4range and tsrange.
+-- with inferred PK on the referenced table:
+DROP TABLE referencing_period_test;
+CREATE TABLE referencing_period_test (
+	id int4range,
+	valid_from timestamp,
+	valid_til timestamp,
+	PERIOD FOR valid_at (valid_from, valid_til),
+	parent_id int4range,
+	CONSTRAINT referencing_period_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS),
+	CONSTRAINT referencing_period_fk FOREIGN KEY (parent_id, PERIOD valid_at)
+		REFERENCES without_overlaps_test
+);
+DROP TABLE referencing_period_test;
+-- should fail because of duplicate referenced columns:
+CREATE TABLE referencing_period_test (
+	id int4range,
+	valid_from timestamp,
+	valid_til timestamp,
+	PERIOD FOR valid_at (valid_from, valid_til),
+	parent_id int4range,
+	CONSTRAINT referencing_period_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS),
+	CONSTRAINT referencing_period_fk FOREIGN KEY (parent_id, PERIOD parent_id)
+		REFERENCES without_overlaps_test (id, PERIOD id)
+);
+ERROR:  foreign key referenced-columns list must not contain duplicates
+--
+-- test ALTER TABLE ADD CONSTRAINT
+--
+CREATE TABLE referencing_period_test (
+	id int4range,
+	valid_from timestamp,
+	valid_til timestamp,
+	PERIOD FOR valid_at (valid_from, valid_til),
+	parent_id int4range,
+	CONSTRAINT referencing_period_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+ALTER TABLE referencing_period_test
+	ADD CONSTRAINT referencing_period_fk
+	FOREIGN KEY (parent_id, PERIOD valid_at)
+	REFERENCES without_overlaps_test (id, PERIOD valid_at);
+ALTER TABLE referencing_period_test
+	DROP CONSTRAINT referencing_period_fk;
+-- with inferred PK on the referenced table:
+ALTER TABLE referencing_period_test
+	ADD CONSTRAINT referencing_period_fk
+	FOREIGN KEY (parent_id, PERIOD valid_at)
+	REFERENCES without_overlaps_test;
+-- should fail because of duplicate referenced columns:
+ALTER TABLE referencing_period_test
+	ADD CONSTRAINT referencing_period_fk2
+	FOREIGN KEY (parent_id, PERIOD parent_id)
+	REFERENCES without_overlaps_test (id, PERIOD id);
+ERROR:  foreign key referenced-columns list must not contain duplicates
+--
+-- test with rows already
+--
+DELETE FROM referencing_period_test;
+ALTER TABLE referencing_period_test
+	DROP CONSTRAINT referencing_period_fk;
+INSERT INTO referencing_period_test VALUES ('[1,1]', '2018-01-02', '2018-02-01', '[1,1]');
+ALTER TABLE referencing_period_test
+	ADD CONSTRAINT referencing_period_fk
+	FOREIGN KEY (parent_id, PERIOD valid_at)
+	REFERENCES without_overlaps_test;
+ALTER TABLE referencing_period_test
+	DROP CONSTRAINT referencing_period_fk;
+INSERT INTO referencing_period_test VALUES ('[2,2]', '2018-01-02', '2018-04-01', '[1,1]');
+-- should fail:
+ALTER TABLE referencing_period_test
+	ADD CONSTRAINT referencing_period_fk
+	FOREIGN KEY (parent_id, PERIOD valid_at)
+	REFERENCES without_overlaps_test;
+ERROR:  insert or update on table "referencing_period_test" violates foreign key constraint "referencing_period_fk"
+DETAIL:  Key (parent_id, valid_from, valid_til)=([1,2), Tue Jan 02 00:00:00 2018, Sun Apr 01 00:00:00 2018) is not present in table "without_overlaps_test".
+-- okay again:
+DELETE FROM referencing_period_test;
+ALTER TABLE referencing_period_test
+	ADD CONSTRAINT referencing_period_fk
+	FOREIGN KEY (parent_id, PERIOD valid_at)
+	REFERENCES without_overlaps_test;
+--
+-- test pg_get_constraintdef
+--
+SELECT pg_get_constraintdef(oid) FROM pg_constraint WHERE conname = 'referencing_period_fk';
+                                      pg_get_constraintdef                                      
+------------------------------------------------------------------------------------------------
+ FOREIGN KEY (parent_id, PERIOD valid_at) REFERENCES without_overlaps_test(id, PERIOD valid_at)
+(1 row)
+
+--
+-- test FK child inserts
+--
+INSERT INTO referencing_period_test VALUES ('[1,1]', '2018-01-02', '2018-02-01', '[1,1]');
+-- should fail:
+INSERT INTO referencing_period_test VALUES ('[2,2]', '2018-01-02', '2018-04-01', '[1,1]');
+ERROR:  insert or update on table "referencing_period_test" violates foreign key constraint "referencing_period_fk"
+DETAIL:  Key (parent_id, valid_from, valid_til)=([1,2), Tue Jan 02 00:00:00 2018, Sun Apr 01 00:00:00 2018) is not present in table "without_overlaps_test".
+-- now it should work:
+INSERT INTO without_overlaps_test VALUES ('[1,1]', '2018-02-03', '2018-03-03');
+INSERT INTO referencing_period_test VALUES ('[2,2]', '2018-01-02', '2018-04-01', '[1,1]');
+--
+-- test FK child updates
+--
+UPDATE referencing_period_test SET valid_from = '2018-01-02', valid_til = '2018-03-01' WHERE id = '[1,1]';
+-- should fail:
+UPDATE referencing_period_test SET valid_from = '2018-01-02', valid_til = '2018-05-01' WHERE id = '[1,1]';
+ERROR:  insert or update on table "referencing_period_test" violates foreign key constraint "referencing_period_fk"
+DETAIL:  Key (parent_id, valid_from, valid_til)=([1,2), Tue Jan 02 00:00:00 2018, Tue May 01 00:00:00 2018) is not present in table "without_overlaps_test".
+UPDATE referencing_period_test SET parent_id = '[8,8]' WHERE id = '[1,1]';
+ERROR:  insert or update on table "referencing_period_test" violates foreign key constraint "referencing_period_fk"
+DETAIL:  Key (parent_id, valid_from, valid_til)=([8,9), Tue Jan 02 00:00:00 2018, Thu Mar 01 00:00:00 2018) is not present in table "without_overlaps_test".
+--
+-- test FK parent updates NO ACTION
+--
+-- a PK update that succeeds because the numeric id isn't referenced:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', '2018-01-01', '2018-02-01');
+UPDATE without_overlaps_test SET valid_from = '2016-01-01', valid_til = '2016-02-01' WHERE id = '[5,5]';
+-- a PK update that succeeds even though the numeric id is referenced because the range isn't:
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+INSERT INTO without_overlaps_test VALUES ('[5,5]', '2018-01-01', '2018-02-01');
+INSERT INTO without_overlaps_test VALUES ('[5,5]', '2018-02-01', '2018-03-01');
+INSERT INTO referencing_period_test VALUES ('[3,3]', '2018-01-05', '2018-01-10', '[5,5]');
+UPDATE without_overlaps_test SET valid_from = '2016-02-01', valid_til = '2016-03-01'
+WHERE id = '[5,5]' AND valid_from = '2018-02-01' AND valid_til = '2018-03-01';
+-- a PK update that fails because both are referenced:
+UPDATE without_overlaps_test SET valid_from = '2016-01-01', valid_til = '2016-02-01'
+WHERE id = '[5,5]' AND valid_from = '2018-01-01' AND valid_til = '2018-02-01';
+ERROR:  update or delete on table "without_overlaps_test" violates foreign key constraint "referencing_period_fk" on table "referencing_period_test"
+DETAIL:  Key (id, valid_from, valid_til)=([5,6), Mon Jan 01 00:00:00 2018, Thu Feb 01 00:00:00 2018) is still referenced from table "referencing_period_test".
+-- then delete the objecting FK record and the same PK update succeeds:
+DELETE FROM referencing_period_test WHERE id = '[3,3]';
+UPDATE without_overlaps_test SET valid_from = '2016-01-01', valid_til = '2016-02-01'
+WHERE id = '[5,5]' AND valid_from = '2018-01-01' AND valid_til = '2018-02-01';
+-- clean up:
+DELETE FROM referencing_period_test WHERE parent_id = '[5,5]';
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+--
+-- test FK parent updates RESTRICT
+--
+ALTER TABLE referencing_period_test
+	DROP CONSTRAINT referencing_period_fk;
+ALTER TABLE referencing_period_test
+	ADD CONSTRAINT referencing_period_fk
+	FOREIGN KEY (parent_id, PERIOD valid_at)
+	REFERENCES without_overlaps_test
+	ON DELETE RESTRICT;
+-- a PK update that succeeds because the numeric id isn't referenced:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', '2018-01-01', '2018-02-01');
+UPDATE without_overlaps_test SET valid_from = '2016-01-01', valid_til = '2016-02-01' WHERE id = '[5,5]';
+-- a PK update that succeeds even though the numeric id is referenced because the range isn't:
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+INSERT INTO without_overlaps_test VALUES ('[5,5]', '2018-01-01', '2018-02-01');
+INSERT INTO without_overlaps_test VALUES ('[5,5]', '2018-02-01', '2018-03-01');
+INSERT INTO referencing_period_test VALUES ('[3,3]', '2018-01-05', '2018-01-10', '[5,5]');
+UPDATE without_overlaps_test SET valid_from = '2016-02-01', valid_til = '2016-03-01'
+WHERE id = '[5,5]' AND valid_from = '2018-02-01' AND valid_til = '2018-03-01';
+-- a PK update that fails because both are referenced:
+UPDATE without_overlaps_test SET valid_from = '2016-01-01', valid_til = '2016-02-01'
+WHERE id = '[5,5]' AND valid_from = '2018-01-01' AND valid_til = '2018-02-01';
+ERROR:  update or delete on table "without_overlaps_test" violates foreign key constraint "referencing_period_fk" on table "referencing_period_test"
+DETAIL:  Key (id, valid_from, valid_til)=([5,6), Mon Jan 01 00:00:00 2018, Thu Feb 01 00:00:00 2018) is still referenced from table "referencing_period_test".
+-- then delete the objecting FK record and the same PK update succeeds:
+DELETE FROM referencing_period_test WHERE id = '[3,3]';
+UPDATE without_overlaps_test SET valid_from = '2016-01-01', valid_til = '2016-02-01'
+WHERE id = '[5,5]' AND valid_from = '2018-01-01' AND valid_til = '2018-02-01';
+-- clean up:
+DELETE FROM referencing_period_test WHERE parent_id = '[5,5]';
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+--
+-- test FK parent deletes NO ACTION
+--
+ALTER TABLE referencing_period_test
+	DROP CONSTRAINT referencing_period_fk;
+ALTER TABLE referencing_period_test
+	ADD CONSTRAINT referencing_period_fk
+	FOREIGN KEY (parent_id, PERIOD valid_at)
+	REFERENCES without_overlaps_test;
+-- a PK delete that succeeds because the numeric id isn't referenced:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', '2018-01-01', '2018-02-01');
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+-- a PK delete that succeeds even though the numeric id is referenced because the range isn't:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', '2018-01-01', '2018-02-01');
+INSERT INTO without_overlaps_test VALUES ('[5,5]', '2018-02-01', '2018-03-01');
+INSERT INTO referencing_period_test VALUES ('[3,3]', '2018-01-05', '2018-01-10', '[5,5]');
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_from = '2018-02-01' AND valid_til = '2018-03-01';
+-- a PK delete that fails because both are referenced:
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_from = '2018-01-01' AND valid_til = '2018-02-01';
+ERROR:  update or delete on table "without_overlaps_test" violates foreign key constraint "referencing_period_fk" on table "referencing_period_test"
+DETAIL:  Key (id, valid_from, valid_til)=([5,6), Mon Jan 01 00:00:00 2018, Thu Feb 01 00:00:00 2018) is still referenced from table "referencing_period_test".
+-- then delete the objecting FK record and the same PK delete succeeds:
+DELETE FROM referencing_period_test WHERE id = '[3,3]';
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_from = '2018-01-01' AND valid_til = '2018-02-01';
+--
+-- test FK parent deletes RESTRICT
+--
+ALTER TABLE referencing_period_test
+	DROP CONSTRAINT referencing_period_fk;
+ALTER TABLE referencing_period_test
+	ADD CONSTRAINT referencing_period_fk
+	FOREIGN KEY (parent_id, PERIOD valid_at)
+	REFERENCES without_overlaps_test
+	ON DELETE RESTRICT;
+INSERT INTO without_overlaps_test VALUES ('[5,5]', '2018-01-01', '2018-02-01');
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+-- a PK delete that succeeds even though the numeric id is referenced because the range isn't:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', '2018-01-01', '2018-02-01');
+INSERT INTO without_overlaps_test VALUES ('[5,5]', '2018-02-01', '2018-03-01');
+INSERT INTO referencing_period_test VALUES ('[3,3]', '2018-01-05', '2018-01-10', '[5,5]');
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_from = '2018-02-01' AND valid_til = '2018-03-01';
+-- a PK delete that fails because both are referenced:
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_from = '2018-01-01' AND valid_til = '2018-02-01';
+ERROR:  update or delete on table "without_overlaps_test" violates foreign key constraint "referencing_period_fk" on table "referencing_period_test"
+DETAIL:  Key (id, valid_from, valid_til)=([5,6), Mon Jan 01 00:00:00 2018, Thu Feb 01 00:00:00 2018) is still referenced from table "referencing_period_test".
+-- then delete the objecting FK record and the same PK delete succeeds:
+DELETE FROM referencing_period_test WHERE id = '[3,3]';
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_from = '2018-01-01' AND valid_til = '2018-02-01';
+--
+-- test ON UPDATE/DELETE options
+--
+-- test FK parent updates CASCADE
+INSERT INTO without_overlaps_test VALUES ('[6,6]', '2018-01-01', '2021-01-01');
+INSERT INTO referencing_period_test VALUES ('[4,4]', '2018-01-01', '2021-01-01', '[6,6]');
+ALTER TABLE referencing_period_test
+	DROP CONSTRAINT referencing_period_fk,
+	ADD CONSTRAINT referencing_period_fk
+		FOREIGN KEY (parent_id, PERIOD valid_at)
+		REFERENCES without_overlaps_test
+		ON DELETE CASCADE ON UPDATE CASCADE;
+UPDATE without_overlaps_test FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' SET id = '[7,7]' WHERE id = '[6,6]';
+SELECT * FROM referencing_period_test WHERE id = '[4,4]';
+  id   |        valid_from        |        valid_til         | parent_id 
+-------+--------------------------+--------------------------+-----------
+ [4,5) | Tue Jan 01 00:00:00 2019 | Wed Jan 01 00:00:00 2020 | [7,8)
+ [4,5) | Mon Jan 01 00:00:00 2018 | Tue Jan 01 00:00:00 2019 | [6,7)
+ [4,5) | Wed Jan 01 00:00:00 2020 | Fri Jan 01 00:00:00 2021 | [6,7)
+(3 rows)
+
+UPDATE without_overlaps_test SET id = '[7,7]' WHERE id = '[6,6]';
+SELECT * FROM referencing_period_test WHERE id = '[4,4]';
+  id   |        valid_from        |        valid_til         | parent_id 
+-------+--------------------------+--------------------------+-----------
+ [4,5) | Tue Jan 01 00:00:00 2019 | Wed Jan 01 00:00:00 2020 | [7,8)
+ [4,5) | Mon Jan 01 00:00:00 2018 | Tue Jan 01 00:00:00 2019 | [7,8)
+ [4,5) | Wed Jan 01 00:00:00 2020 | Fri Jan 01 00:00:00 2021 | [7,8)
+(3 rows)
+
+INSERT INTO without_overlaps_test VALUES ('[15,15]', '2018-01-01', '2020-01-01');
+INSERT INTO without_overlaps_test VALUES ('[15,15]', '2020-01-01', '2021-01-01');
+INSERT INTO referencing_period_test VALUES ('[10,10]', '2018-01-01', '2021-01-01', '[15,15]');
+UPDATE without_overlaps_test SET id = '[16,16]' WHERE id = '[15,15]' AND tsrange(valid_from, valid_til) @> '2019-01-01'::timestamp;
+SELECT * FROM referencing_period_test WHERE id = '[10,10]';
+   id    |        valid_from        |        valid_til         | parent_id 
+---------+--------------------------+--------------------------+-----------
+ [10,11) | Mon Jan 01 00:00:00 2018 | Wed Jan 01 00:00:00 2020 | [16,17)
+ [10,11) | Wed Jan 01 00:00:00 2020 | Fri Jan 01 00:00:00 2021 | [15,16)
+(2 rows)
+
+-- test FK parent deletes CASCADE
+INSERT INTO without_overlaps_test VALUES ('[8,8]', '2018-01-01', '2021-01-01');
+INSERT INTO referencing_period_test VALUES ('[5,5]', '2018-01-01', '2021-01-01', '[8,8]');
+DELETE FROM without_overlaps_test FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' WHERE id = '[8,8]';
+SELECT * FROM referencing_period_test WHERE id = '[5,5]';
+  id   |        valid_from        |        valid_til         | parent_id 
+-------+--------------------------+--------------------------+-----------
+ [5,6) | Mon Jan 01 00:00:00 2018 | Tue Jan 01 00:00:00 2019 | [8,9)
+ [5,6) | Wed Jan 01 00:00:00 2020 | Fri Jan 01 00:00:00 2021 | [8,9)
+(2 rows)
+
+DELETE FROM without_overlaps_test WHERE id = '[8,8]';
+SELECT * FROM referencing_period_test WHERE id = '[5,5]';
+ id | valid_from | valid_til | parent_id 
+----+------------+-----------+-----------
+(0 rows)
+
+INSERT INTO without_overlaps_test VALUES ('[17,17]', '2018-01-01', '2020-01-01');
+INSERT INTO without_overlaps_test VALUES ('[17,17]', '2020-01-01', '2021-01-01');
+INSERT INTO referencing_period_test VALUES ('[11,11]', '2018-01-01', '2021-01-01', '[17,17]');
+DELETE FROM without_overlaps_test WHERE id = '[17,17]' AND tsrange(valid_from, valid_til) @> '2019-01-01'::timestamp;
+SELECT * FROM referencing_period_test WHERE id = '[11,11]';
+   id    |        valid_from        |        valid_til         | parent_id 
+---------+--------------------------+--------------------------+-----------
+ [11,12) | Wed Jan 01 00:00:00 2020 | Fri Jan 01 00:00:00 2021 | [17,18)
+(1 row)
+
+-- test FK parent updates SET NULL
+INSERT INTO without_overlaps_test VALUES ('[9,9]', '2018-01-01', '2021-01-01');
+INSERT INTO referencing_period_test VALUES ('[6,6]', '2018-01-01', '2021-01-01', '[9,9]');
+ALTER TABLE referencing_period_test
+	DROP CONSTRAINT referencing_period_fk,
+	ADD CONSTRAINT referencing_period_fk
+		FOREIGN KEY (parent_id, PERIOD valid_at)
+		REFERENCES without_overlaps_test
+		ON DELETE SET NULL ON UPDATE SET NULL;
+UPDATE without_overlaps_test FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' SET id = '[10,10]' WHERE id = '[9,9]';
+SELECT * FROM referencing_period_test WHERE id = '[6,6]';
+  id   |        valid_from        |        valid_til         | parent_id 
+-------+--------------------------+--------------------------+-----------
+ [6,7) | Tue Jan 01 00:00:00 2019 | Wed Jan 01 00:00:00 2020 | 
+ [6,7) | Mon Jan 01 00:00:00 2018 | Tue Jan 01 00:00:00 2019 | [9,10)
+ [6,7) | Wed Jan 01 00:00:00 2020 | Fri Jan 01 00:00:00 2021 | [9,10)
+(3 rows)
+
+UPDATE without_overlaps_test SET id = '[10,10]' WHERE id = '[9,9]';
+SELECT * FROM referencing_period_test WHERE id = '[6,6]';
+  id   |        valid_from        |        valid_til         | parent_id 
+-------+--------------------------+--------------------------+-----------
+ [6,7) | Tue Jan 01 00:00:00 2019 | Wed Jan 01 00:00:00 2020 | 
+ [6,7) | Mon Jan 01 00:00:00 2018 | Tue Jan 01 00:00:00 2019 | 
+ [6,7) | Wed Jan 01 00:00:00 2020 | Fri Jan 01 00:00:00 2021 | 
+(3 rows)
+
+INSERT INTO without_overlaps_test VALUES ('[18,18]', '2018-01-01', '2020-01-01');
+INSERT INTO without_overlaps_test VALUES ('[18,18]', '2020-01-01', '2021-01-01');
+INSERT INTO referencing_period_test VALUES ('[12,12]', '2018-01-01', '2021-01-01', '[18,18]');
+UPDATE without_overlaps_test SET id = '[19,19]' WHERE id = '[18,18]' AND tsrange(valid_from, valid_til) @> '2019-01-01'::timestamp;
+SELECT * FROM referencing_period_test WHERE id = '[12,12]';
+   id    |        valid_from        |        valid_til         | parent_id 
+---------+--------------------------+--------------------------+-----------
+ [12,13) | Mon Jan 01 00:00:00 2018 | Wed Jan 01 00:00:00 2020 | 
+ [12,13) | Wed Jan 01 00:00:00 2020 | Fri Jan 01 00:00:00 2021 | [18,19)
+(2 rows)
+
+-- test FK parent deletes SET NULL
+INSERT INTO without_overlaps_test VALUES ('[11,11]', '2018-01-01', '2021-01-01');
+INSERT INTO referencing_period_test VALUES ('[7,7]', '2018-01-01', '2021-01-01', '[11,11]');
+DELETE FROM without_overlaps_test FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' WHERE id = '[11,11]';
+SELECT * FROM referencing_period_test WHERE id = '[7,7]';
+  id   |        valid_from        |        valid_til         | parent_id 
+-------+--------------------------+--------------------------+-----------
+ [7,8) | Tue Jan 01 00:00:00 2019 | Wed Jan 01 00:00:00 2020 | 
+ [7,8) | Mon Jan 01 00:00:00 2018 | Tue Jan 01 00:00:00 2019 | [11,12)
+ [7,8) | Wed Jan 01 00:00:00 2020 | Fri Jan 01 00:00:00 2021 | [11,12)
+(3 rows)
+
+DELETE FROM without_overlaps_test WHERE id = '[11,11]';
+SELECT * FROM referencing_period_test WHERE id = '[7,7]';
+  id   |        valid_from        |        valid_til         | parent_id 
+-------+--------------------------+--------------------------+-----------
+ [7,8) | Tue Jan 01 00:00:00 2019 | Wed Jan 01 00:00:00 2020 | 
+ [7,8) | Mon Jan 01 00:00:00 2018 | Tue Jan 01 00:00:00 2019 | 
+ [7,8) | Wed Jan 01 00:00:00 2020 | Fri Jan 01 00:00:00 2021 | 
+(3 rows)
+
+INSERT INTO without_overlaps_test VALUES ('[20,20]', '2018-01-01', '2020-01-01');
+INSERT INTO without_overlaps_test VALUES ('[20,20]', '2020-01-01', '2021-01-01');
+INSERT INTO referencing_period_test VALUES ('[13,13]', '2018-01-01', '2021-01-01', '[20,20]');
+DELETE FROM without_overlaps_test WHERE id = '[20,20]' AND tsrange(valid_from, valid_til) @> '2019-01-01'::timestamp;
+SELECT * FROM referencing_period_test WHERE id = '[13,13]';
+   id    |        valid_from        |        valid_til         | parent_id 
+---------+--------------------------+--------------------------+-----------
+ [13,14) | Mon Jan 01 00:00:00 2018 | Wed Jan 01 00:00:00 2020 | 
+ [13,14) | Wed Jan 01 00:00:00 2020 | Fri Jan 01 00:00:00 2021 | [20,21)
+(2 rows)
+
+-- test FK parent updates SET DEFAULT
+INSERT INTO without_overlaps_test VALUES ('[-1,-1]', null, null);
+INSERT INTO without_overlaps_test VALUES ('[12,12]', '2018-01-01', '2021-01-01');
+INSERT INTO referencing_period_test VALUES ('[8,8]', '2018-01-01', '2021-01-01', '[12,12]');
+ALTER TABLE referencing_period_test
+  ALTER COLUMN parent_id SET DEFAULT '[-1,-1]',
+	DROP CONSTRAINT referencing_period_fk,
+	ADD CONSTRAINT referencing_period_fk
+		FOREIGN KEY (parent_id, PERIOD valid_at)
+		REFERENCES without_overlaps_test
+		ON DELETE SET DEFAULT ON UPDATE SET DEFAULT;
+UPDATE without_overlaps_test FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' SET id = '[13,13]' WHERE id = '[12,12]';
+SELECT * FROM referencing_period_test WHERE id = '[8,8]';
+  id   |        valid_from        |        valid_til         | parent_id 
+-------+--------------------------+--------------------------+-----------
+ [8,9) | Tue Jan 01 00:00:00 2019 | Wed Jan 01 00:00:00 2020 | [-1,0)
+ [8,9) | Mon Jan 01 00:00:00 2018 | Tue Jan 01 00:00:00 2019 | [12,13)
+ [8,9) | Wed Jan 01 00:00:00 2020 | Fri Jan 01 00:00:00 2021 | [12,13)
+(3 rows)
+
+UPDATE without_overlaps_test SET id = '[13,13]' WHERE id = '[12,12]';
+SELECT * FROM referencing_period_test WHERE id = '[8,8]';
+  id   |        valid_from        |        valid_til         | parent_id 
+-------+--------------------------+--------------------------+-----------
+ [8,9) | Tue Jan 01 00:00:00 2019 | Wed Jan 01 00:00:00 2020 | [-1,0)
+ [8,9) | Mon Jan 01 00:00:00 2018 | Tue Jan 01 00:00:00 2019 | [-1,0)
+ [8,9) | Wed Jan 01 00:00:00 2020 | Fri Jan 01 00:00:00 2021 | [-1,0)
+(3 rows)
+
+INSERT INTO without_overlaps_test VALUES ('[22,22]', '2018-01-01', '2020-01-01');
+INSERT INTO without_overlaps_test VALUES ('[22,22]', '2020-01-01', '2021-01-01');
+INSERT INTO referencing_period_test VALUES ('[14,14]', '2018-01-01', '2021-01-01', '[22,22]');
+UPDATE without_overlaps_test SET id = '[23,23]' WHERE id = '[22,22]' AND tsrange(valid_from, valid_til) @> '2019-01-01'::timestamp;
+SELECT * FROM referencing_period_test WHERE id = '[14,14]';
+   id    |        valid_from        |        valid_til         | parent_id 
+---------+--------------------------+--------------------------+-----------
+ [14,15) | Mon Jan 01 00:00:00 2018 | Wed Jan 01 00:00:00 2020 | [-1,0)
+ [14,15) | Wed Jan 01 00:00:00 2020 | Fri Jan 01 00:00:00 2021 | [22,23)
+(2 rows)
+
+-- test FK parent deletes SET DEFAULT
+INSERT INTO without_overlaps_test VALUES ('[14,14]', '2018-01-01', '2021-01-01');
+INSERT INTO referencing_period_test VALUES ('[9,9]', '2018-01-01', '2021-01-01', '[14,14]');
+DELETE FROM without_overlaps_test FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' WHERE id = '[14,14]';
+SELECT * FROM referencing_period_test WHERE id = '[9,9]';
+   id   |        valid_from        |        valid_til         | parent_id 
+--------+--------------------------+--------------------------+-----------
+ [9,10) | Tue Jan 01 00:00:00 2019 | Wed Jan 01 00:00:00 2020 | [-1,0)
+ [9,10) | Mon Jan 01 00:00:00 2018 | Tue Jan 01 00:00:00 2019 | [14,15)
+ [9,10) | Wed Jan 01 00:00:00 2020 | Fri Jan 01 00:00:00 2021 | [14,15)
+(3 rows)
+
+DELETE FROM without_overlaps_test WHERE id = '[14,14]';
+SELECT * FROM referencing_period_test WHERE id = '[9,9]';
+   id   |        valid_from        |        valid_til         | parent_id 
+--------+--------------------------+--------------------------+-----------
+ [9,10) | Tue Jan 01 00:00:00 2019 | Wed Jan 01 00:00:00 2020 | [-1,0)
+ [9,10) | Mon Jan 01 00:00:00 2018 | Tue Jan 01 00:00:00 2019 | [-1,0)
+ [9,10) | Wed Jan 01 00:00:00 2020 | Fri Jan 01 00:00:00 2021 | [-1,0)
+(3 rows)
+
+INSERT INTO without_overlaps_test VALUES ('[24,24]', '2018-01-01', '2020-01-01');
+INSERT INTO without_overlaps_test VALUES ('[24,24]', '2020-01-01', '2021-01-01');
+INSERT INTO referencing_period_test VALUES ('[15,15]', '2018-01-01', '2021-01-01', '[24,24]');
+DELETE FROM without_overlaps_test WHERE id = '[24,24]' AND tsrange(valid_from, valid_til) @> '2019-01-01'::timestamp;
+SELECT * FROM referencing_period_test WHERE id = '[15,15]';
+   id    |        valid_from        |        valid_til         | parent_id 
+---------+--------------------------+--------------------------+-----------
+ [15,16) | Mon Jan 01 00:00:00 2018 | Wed Jan 01 00:00:00 2020 | [-1,0)
+ [15,16) | Wed Jan 01 00:00:00 2020 | Fri Jan 01 00:00:00 2021 | [24,25)
+(2 rows)
+
diff --git a/src/test/regress/sql/for_portion_of.sql b/src/test/regress/sql/for_portion_of.sql
index 3b5755ba00..b773ab7a08 100644
--- a/src/test/regress/sql/for_portion_of.sql
+++ b/src/test/regress/sql/for_portion_of.sql
@@ -48,6 +48,12 @@ FOR PORTION OF valid_at FROM '2018-06-01' TO MAXVALUE
 SET valid_at = '[1990-01-01,1999-01-01)'
 WHERE id = '[5,6)';
 
+-- The wrong type fails
+UPDATE for_portion_of_test
+FOR PORTION OF valid_at FROM 1 TO 4
+SET name = 'nope'
+WHERE id = '[3,4)';
+
 -- Setting with timestamps reversed fails
 UPDATE for_portion_of_test
 FOR PORTION OF valid_at FROM '2018-06-01' TO '2018-01-01'
diff --git a/src/test/regress/sql/without_overlaps.sql b/src/test/regress/sql/without_overlaps.sql
index e64ec69f0b..5ade1b8079 100644
--- a/src/test/regress/sql/without_overlaps.sql
+++ b/src/test/regress/sql/without_overlaps.sql
@@ -293,3 +293,1405 @@ ALTER TABLE without_overlaps_test2 ALTER COLUMN valid_at TYPE tstzrange USING ts
 ALTER TABLE without_overlaps_test2 RENAME COLUMN valid_at TO valid_thru;
 ALTER TABLE without_overlaps_test2 DROP COLUMN valid_thru;
 DROP TABLE without_overlaps_test2;
+
+--
+-- test FK dependencies
+--
+
+-- can't drop a range referenced by an FK, unless with CASCADE
+CREATE TABLE without_overlaps_test2 (
+	id int4range,
+	valid_at tsrange,
+	CONSTRAINT without_overlaps2_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+CREATE TABLE referencing_period_test2 (
+	id int4range,
+	valid_at tsrange,
+	parent_id int4range,
+	CONSTRAINT referencing_period2_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS),
+	CONSTRAINT referencing_period2_fk FOREIGN KEY (parent_id, PERIOD valid_at)
+		REFERENCES without_overlaps_test2 (id, PERIOD valid_at)
+);
+ALTER TABLE without_overlaps_test2 DROP COLUMN valid_at;
+ALTER TABLE without_overlaps_test2 DROP COLUMN valid_at CASCADE;
+DROP TABLE referencing_period_test2;
+DROP TABLE without_overlaps_test2;
+
+-- can't drop a PERIOD referenced by an FK, unless with CASCADE
+CREATE TABLE without_overlaps_test2 (
+	id int4range,
+	valid_from timestamp,
+	valid_til timestamp,
+	PERIOD FOR valid_at (valid_from, valid_til),
+	CONSTRAINT without_overlaps2_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+CREATE TABLE referencing_period_test2 (
+	id int4range,
+	valid_at tsrange,
+	parent_id int4range,
+	CONSTRAINT referencing_period2_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS),
+	CONSTRAINT referencing_period2_fk FOREIGN KEY (parent_id, PERIOD valid_at)
+		REFERENCES without_overlaps_test2 (id, PERIOD valid_at)
+);
+ALTER TABLE without_overlaps_test2 DROP PERIOD FOR valid_at;
+ALTER TABLE without_overlaps_test2 DROP PERIOD FOR valid_at CASCADE;
+DROP TABLE referencing_period_test2;
+DROP TABLE without_overlaps_test2;
+
+--
+-- test FOREIGN KEY, range references range
+--
+
+-- Can't create a FK with a mismatched range type
+CREATE TABLE referencing_period_test2 (
+	id int4range,
+	valid_at int4range,
+	parent_id int4range,
+	CONSTRAINT referencing_period_pk2 PRIMARY KEY (id, valid_at WITHOUT OVERLAPS),
+	CONSTRAINT referencing_period_fk2 FOREIGN KEY (parent_id, PERIOD valid_at)
+		REFERENCES without_overlaps_test (id, PERIOD valid_at)
+);
+
+CREATE TABLE referencing_period_test (
+	id int4range,
+	valid_at tsrange,
+	parent_id int4range,
+	CONSTRAINT referencing_period_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS),
+	CONSTRAINT referencing_period_fk FOREIGN KEY (parent_id, PERIOD valid_at)
+		REFERENCES without_overlaps_test (id, PERIOD valid_at)
+);
+DROP TABLE referencing_period_test;
+
+-- with inferred PK on the referenced table:
+CREATE TABLE referencing_period_test (
+	id int4range,
+	valid_at tsrange,
+	parent_id int4range,
+	CONSTRAINT referencing_period_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS),
+	CONSTRAINT referencing_period_fk FOREIGN KEY (parent_id, PERIOD valid_at)
+		REFERENCES without_overlaps_test
+);
+DROP TABLE referencing_period_test;
+
+-- should fail because of duplicate referenced columns:
+CREATE TABLE referencing_period_test (
+	id int4range,
+	valid_at tsrange,
+	parent_id int4range,
+	CONSTRAINT referencing_period_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS),
+	CONSTRAINT referencing_period_fk FOREIGN KEY (parent_id, PERIOD parent_id)
+		REFERENCES without_overlaps_test (id, PERIOD id)
+);
+
+--
+-- test ALTER TABLE ADD CONSTRAINT
+--
+
+CREATE TABLE referencing_period_test (
+	id int4range,
+	valid_at tsrange,
+	parent_id int4range,
+	CONSTRAINT referencing_period_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+ALTER TABLE referencing_period_test
+	ADD CONSTRAINT referencing_period_fk
+	FOREIGN KEY (parent_id, PERIOD valid_at)
+	REFERENCES without_overlaps_test (id, PERIOD valid_at);
+ALTER TABLE referencing_period_test
+	DROP CONSTRAINT referencing_period_fk;
+-- with inferred PK on the referenced table:
+ALTER TABLE referencing_period_test
+	ADD CONSTRAINT referencing_period_fk
+	FOREIGN KEY (parent_id, PERIOD valid_at)
+	REFERENCES without_overlaps_test;
+
+-- should fail because of duplicate referenced columns:
+ALTER TABLE referencing_period_test
+	ADD CONSTRAINT referencing_period_fk2
+	FOREIGN KEY (parent_id, PERIOD parent_id)
+	REFERENCES without_overlaps_test (id, PERIOD id);
+
+--
+-- test with rows already
+--
+
+DELETE FROM referencing_period_test;
+ALTER TABLE referencing_period_test
+	DROP CONSTRAINT referencing_period_fk;
+INSERT INTO referencing_period_test VALUES ('[1,1]', tsrange('2018-01-02', '2018-02-01'), '[1,1]');
+ALTER TABLE referencing_period_test
+	ADD CONSTRAINT referencing_period_fk
+	FOREIGN KEY (parent_id, PERIOD valid_at)
+	REFERENCES without_overlaps_test;
+ALTER TABLE referencing_period_test
+	DROP CONSTRAINT referencing_period_fk;
+INSERT INTO referencing_period_test VALUES ('[2,2]', tsrange('2018-01-02', '2018-04-01'), '[1,1]');
+-- should fail:
+ALTER TABLE referencing_period_test
+	ADD CONSTRAINT referencing_period_fk
+	FOREIGN KEY (parent_id, PERIOD valid_at)
+	REFERENCES without_overlaps_test;
+-- okay again:
+DELETE FROM referencing_period_test;
+ALTER TABLE referencing_period_test
+	ADD CONSTRAINT referencing_period_fk
+	FOREIGN KEY (parent_id, PERIOD valid_at)
+	REFERENCES without_overlaps_test;
+
+--
+-- test pg_get_constraintdef
+--
+
+SELECT pg_get_constraintdef(oid) FROM pg_constraint WHERE conname = 'referencing_period_fk';
+
+--
+-- test FK child inserts
+--
+
+INSERT INTO referencing_period_test VALUES ('[1,1]', tsrange('2018-01-02', '2018-02-01'), '[1,1]');
+-- should fail:
+INSERT INTO referencing_period_test VALUES ('[2,2]', tsrange('2018-01-02', '2018-04-01'), '[1,1]');
+-- now it should work:
+INSERT INTO without_overlaps_test VALUES ('[1,1]', tsrange('2018-02-03', '2018-03-03'));
+INSERT INTO referencing_period_test VALUES ('[2,2]', tsrange('2018-01-02', '2018-04-01'), '[1,1]');
+
+--
+-- test FK child updates
+--
+
+UPDATE referencing_period_test SET valid_at = tsrange('2018-01-02', '2018-03-01') WHERE id = '[1,1]';
+-- should fail:
+UPDATE referencing_period_test SET valid_at = tsrange('2018-01-02', '2018-05-01') WHERE id = '[1,1]';
+UPDATE referencing_period_test SET parent_id = '[8,8]' WHERE id = '[1,1]';
+
+--
+-- test FK parent updates NO ACTION
+--
+
+-- a PK update that succeeds because the numeric id isn't referenced:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01') WHERE id = '[5,5]';
+-- a PK update that succeeds even though the numeric id is referenced because the range isn't:
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-02-01', '2018-03-01'));
+INSERT INTO referencing_period_test VALUES ('[3,3]', tsrange('2018-01-05', '2018-01-10'), '[5,5]');
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-02-01', '2016-03-01')
+WHERE id = '[5,5]' AND valid_at = tsrange('2018-02-01', '2018-03-01');
+-- a PK update that fails because both are referenced:
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01')
+WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+-- then delete the objecting FK record and the same PK update succeeds:
+DELETE FROM referencing_period_test WHERE id = '[3,3]';
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01')
+WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+-- clean up:
+DELETE FROM referencing_period_test WHERE parent_id = '[5,5]';
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+
+--
+-- test FK parent updates RESTRICT
+--
+
+ALTER TABLE referencing_period_test
+	DROP CONSTRAINT referencing_period_fk;
+ALTER TABLE referencing_period_test
+	ADD CONSTRAINT referencing_period_fk
+	FOREIGN KEY (parent_id, PERIOD valid_at)
+	REFERENCES without_overlaps_test
+	ON DELETE RESTRICT;
+-- a PK update that succeeds because the numeric id isn't referenced:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01') WHERE id = '[5,5]';
+-- a PK update that succeeds even though the numeric id is referenced because the range isn't:
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-02-01', '2018-03-01'));
+INSERT INTO referencing_period_test VALUES ('[3,3]', tsrange('2018-01-05', '2018-01-10'), '[5,5]');
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-02-01', '2016-03-01')
+WHERE id = '[5,5]' AND valid_at = tsrange('2018-02-01', '2018-03-01');
+-- a PK update that fails because both are referenced:
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01')
+WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+-- then delete the objecting FK record and the same PK update succeeds:
+DELETE FROM referencing_period_test WHERE id = '[3,3]';
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01')
+WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+-- clean up:
+DELETE FROM referencing_period_test WHERE parent_id = '[5,5]';
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+--
+-- test FK parent deletes NO ACTION
+--
+ALTER TABLE referencing_period_test
+	DROP CONSTRAINT referencing_period_fk;
+ALTER TABLE referencing_period_test
+	ADD CONSTRAINT referencing_period_fk
+	FOREIGN KEY (parent_id, PERIOD valid_at)
+	REFERENCES without_overlaps_test;
+-- a PK delete that succeeds because the numeric id isn't referenced:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+-- a PK delete that succeeds even though the numeric id is referenced because the range isn't:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-02-01', '2018-03-01'));
+INSERT INTO referencing_period_test VALUES ('[3,3]', tsrange('2018-01-05', '2018-01-10'), '[5,5]');
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-02-01', '2018-03-01');
+-- a PK delete that fails because both are referenced:
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+-- then delete the objecting FK record and the same PK delete succeeds:
+DELETE FROM referencing_period_test WHERE id = '[3,3]';
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+
+--
+-- test FK parent deletes RESTRICT
+--
+
+ALTER TABLE referencing_period_test
+	DROP CONSTRAINT referencing_period_fk;
+ALTER TABLE referencing_period_test
+	ADD CONSTRAINT referencing_period_fk
+	FOREIGN KEY (parent_id, PERIOD valid_at)
+	REFERENCES without_overlaps_test
+	ON DELETE RESTRICT;
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+-- a PK delete that succeeds even though the numeric id is referenced because the range isn't:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-02-01', '2018-03-01'));
+INSERT INTO referencing_period_test VALUES ('[3,3]', tsrange('2018-01-05', '2018-01-10'), '[5,5]');
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-02-01', '2018-03-01');
+-- a PK delete that fails because both are referenced:
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+-- then delete the objecting FK record and the same PK delete succeeds:
+DELETE FROM referencing_period_test WHERE id = '[3,3]';
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+
+--
+-- test ON UPDATE/DELETE options
+--
+
+-- test FK parent updates CASCADE
+INSERT INTO without_overlaps_test VALUES ('[6,6]', tsrange('2018-01-01', '2021-01-01'));
+INSERT INTO referencing_period_test VALUES ('[4,4]', tsrange('2018-01-01', '2021-01-01'), '[6,6]');
+ALTER TABLE referencing_period_test
+	DROP CONSTRAINT referencing_period_fk,
+	ADD CONSTRAINT referencing_period_fk
+		FOREIGN KEY (parent_id, PERIOD valid_at)
+		REFERENCES without_overlaps_test
+		ON DELETE CASCADE ON UPDATE CASCADE;
+UPDATE without_overlaps_test FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' SET id = '[7,7]' WHERE id = '[6,6]';
+SELECT * FROM referencing_period_test WHERE id = '[4,4]';
+UPDATE without_overlaps_test SET id = '[7,7]' WHERE id = '[6,6]';
+SELECT * FROM referencing_period_test WHERE id = '[4,4]';
+INSERT INTO without_overlaps_test VALUES ('[15,15]', tsrange('2018-01-01', '2020-01-01'));
+INSERT INTO without_overlaps_test VALUES ('[15,15]', tsrange('2020-01-01', '2021-01-01'));
+INSERT INTO referencing_period_test VALUES ('[10,10]', tsrange('2018-01-01', '2021-01-01'), '[15,15]');
+UPDATE without_overlaps_test SET id = '[16,16]' WHERE id = '[15,15]' AND valid_at @> '2019-01-01'::timestamp;
+SELECT * FROM referencing_period_test WHERE id = '[10,10]';
+
+-- test FK parent deletes CASCADE
+INSERT INTO without_overlaps_test VALUES ('[8,8]', tsrange('2018-01-01', '2021-01-01'));
+INSERT INTO referencing_period_test VALUES ('[5,5]', tsrange('2018-01-01', '2021-01-01'), '[8,8]');
+DELETE FROM without_overlaps_test FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' WHERE id = '[8,8]';
+SELECT * FROM referencing_period_test WHERE id = '[5,5]';
+DELETE FROM without_overlaps_test WHERE id = '[8,8]';
+SELECT * FROM referencing_period_test WHERE id = '[5,5]';
+INSERT INTO without_overlaps_test VALUES ('[17,17]', tsrange('2018-01-01', '2020-01-01'));
+INSERT INTO without_overlaps_test VALUES ('[17,17]', tsrange('2020-01-01', '2021-01-01'));
+INSERT INTO referencing_period_test VALUES ('[11,11]', tsrange('2018-01-01', '2021-01-01'), '[17,17]');
+DELETE FROM without_overlaps_test WHERE id = '[17,17]' AND valid_at @> '2019-01-01'::timestamp;
+SELECT * FROM referencing_period_test WHERE id = '[11,11]';
+
+
+-- test FK parent updates SET NULL
+INSERT INTO without_overlaps_test VALUES ('[9,9]', tsrange('2018-01-01', '2021-01-01'));
+INSERT INTO referencing_period_test VALUES ('[6,6]', tsrange('2018-01-01', '2021-01-01'), '[9,9]');
+ALTER TABLE referencing_period_test
+	DROP CONSTRAINT referencing_period_fk,
+	ADD CONSTRAINT referencing_period_fk
+		FOREIGN KEY (parent_id, PERIOD valid_at)
+		REFERENCES without_overlaps_test
+		ON DELETE SET NULL ON UPDATE SET NULL;
+UPDATE without_overlaps_test FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' SET id = '[10,10]' WHERE id = '[9,9]';
+SELECT * FROM referencing_period_test WHERE id = '[6,6]';
+UPDATE without_overlaps_test SET id = '[10,10]' WHERE id = '[9,9]';
+SELECT * FROM referencing_period_test WHERE id = '[6,6]';
+INSERT INTO without_overlaps_test VALUES ('[18,18]', tsrange('2018-01-01', '2020-01-01'));
+INSERT INTO without_overlaps_test VALUES ('[18,18]', tsrange('2020-01-01', '2021-01-01'));
+INSERT INTO referencing_period_test VALUES ('[12,12]', tsrange('2018-01-01', '2021-01-01'), '[18,18]');
+UPDATE without_overlaps_test SET id = '[19,19]' WHERE id = '[18,18]' AND valid_at @> '2019-01-01'::timestamp;
+SELECT * FROM referencing_period_test WHERE id = '[12,12]';
+
+-- test FK parent deletes SET NULL
+INSERT INTO without_overlaps_test VALUES ('[11,11]', tsrange('2018-01-01', '2021-01-01'));
+INSERT INTO referencing_period_test VALUES ('[7,7]', tsrange('2018-01-01', '2021-01-01'), '[11,11]');
+DELETE FROM without_overlaps_test FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' WHERE id = '[11,11]';
+SELECT * FROM referencing_period_test WHERE id = '[7,7]';
+DELETE FROM without_overlaps_test WHERE id = '[11,11]';
+SELECT * FROM referencing_period_test WHERE id = '[7,7]';
+INSERT INTO without_overlaps_test VALUES ('[20,20]', tsrange('2018-01-01', '2020-01-01'));
+INSERT INTO without_overlaps_test VALUES ('[20,20]', tsrange('2020-01-01', '2021-01-01'));
+INSERT INTO referencing_period_test VALUES ('[13,13]', tsrange('2018-01-01', '2021-01-01'), '[20,20]');
+DELETE FROM without_overlaps_test WHERE id = '[20,20]' AND valid_at @> '2019-01-01'::timestamp;
+SELECT * FROM referencing_period_test WHERE id = '[13,13]';
+
+-- test FK parent updates SET DEFAULT
+INSERT INTO without_overlaps_test VALUES ('[-1,-1]', tsrange(null, null));
+INSERT INTO without_overlaps_test VALUES ('[12,12]', tsrange('2018-01-01', '2021-01-01'));
+INSERT INTO referencing_period_test VALUES ('[8,8]', tsrange('2018-01-01', '2021-01-01'), '[12,12]');
+ALTER TABLE referencing_period_test
+  ALTER COLUMN parent_id SET DEFAULT '[-1,-1]',
+	DROP CONSTRAINT referencing_period_fk,
+	ADD CONSTRAINT referencing_period_fk
+		FOREIGN KEY (parent_id, PERIOD valid_at)
+		REFERENCES without_overlaps_test
+		ON DELETE SET DEFAULT ON UPDATE SET DEFAULT;
+UPDATE without_overlaps_test FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' SET id = '[13,13]' WHERE id = '[12,12]';
+SELECT * FROM referencing_period_test WHERE id = '[8,8]';
+UPDATE without_overlaps_test SET id = '[13,13]' WHERE id = '[12,12]';
+SELECT * FROM referencing_period_test WHERE id = '[8,8]';
+INSERT INTO without_overlaps_test VALUES ('[22,22]', tsrange('2018-01-01', '2020-01-01'));
+INSERT INTO without_overlaps_test VALUES ('[22,22]', tsrange('2020-01-01', '2021-01-01'));
+INSERT INTO referencing_period_test VALUES ('[14,14]', tsrange('2018-01-01', '2021-01-01'), '[22,22]');
+UPDATE without_overlaps_test SET id = '[23,23]' WHERE id = '[22,22]' AND valid_at @> '2019-01-01'::timestamp;
+SELECT * FROM referencing_period_test WHERE id = '[14,14]';
+
+-- test FK parent deletes SET DEFAULT
+INSERT INTO without_overlaps_test VALUES ('[14,14]', tsrange('2018-01-01', '2021-01-01'));
+INSERT INTO referencing_period_test VALUES ('[9,9]', tsrange('2018-01-01', '2021-01-01'), '[14,14]');
+DELETE FROM without_overlaps_test FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' WHERE id = '[14,14]';
+SELECT * FROM referencing_period_test WHERE id = '[9,9]';
+DELETE FROM without_overlaps_test WHERE id = '[14,14]';
+SELECT * FROM referencing_period_test WHERE id = '[9,9]';
+INSERT INTO without_overlaps_test VALUES ('[24,24]', tsrange('2018-01-01', '2020-01-01'));
+INSERT INTO without_overlaps_test VALUES ('[24,24]', tsrange('2020-01-01', '2021-01-01'));
+INSERT INTO referencing_period_test VALUES ('[15,15]', tsrange('2018-01-01', '2021-01-01'), '[24,24]');
+DELETE FROM without_overlaps_test WHERE id = '[24,24]' AND valid_at @> '2019-01-01'::timestamp;
+SELECT * FROM referencing_period_test WHERE id = '[15,15]';
+
+
+
+--
+-- test FOREIGN KEY, range references PERIOD
+--
+
+DROP TABLE without_overlaps_test CASCADE;
+CREATE TABLE without_overlaps_test (
+	id int4range,
+	valid_from timestamp,
+	valid_til timestamp,
+	PERIOD FOR valid_at (valid_from, valid_til),
+	CONSTRAINT without_overlaps_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+INSERT INTO without_overlaps_test VALUES ('[1,1]', '2018-01-02', '2018-02-03');
+INSERT INTO without_overlaps_test VALUES ('[1,1]', '2018-03-03', '2018-04-04');
+INSERT INTO without_overlaps_test VALUES ('[2,2]', '2018-01-01', '2018-01-05');
+INSERT INTO without_overlaps_test VALUES ('[3,3]', '2018-01-01', NULL);
+
+-- Can't create a FK with a mismatched range type
+CREATE TABLE referencing_period_test2 (
+	id int4range,
+	valid_at int4range,
+	parent_id int4range,
+	CONSTRAINT referencing_period_pk2 PRIMARY KEY (id, valid_at WITHOUT OVERLAPS),
+	CONSTRAINT referencing_period_fk2 FOREIGN KEY (parent_id, PERIOD valid_at)
+		REFERENCES without_overlaps_test (id, PERIOD valid_at)
+);
+
+-- with inferred PK on the referenced table:
+DROP TABLE referencing_period_test;
+CREATE TABLE referencing_period_test (
+	id int4range,
+	valid_at tsrange,
+	parent_id int4range,
+	CONSTRAINT referencing_period_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS),
+	CONSTRAINT referencing_period_fk FOREIGN KEY (parent_id, PERIOD valid_at)
+		REFERENCES without_overlaps_test
+);
+DROP TABLE referencing_period_test;
+
+-- should fail because of duplicate referenced columns:
+CREATE TABLE referencing_period_test (
+	id int4range,
+	valid_at tsrange,
+	parent_id int4range,
+	CONSTRAINT referencing_period_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS),
+	CONSTRAINT referencing_period_fk FOREIGN KEY (parent_id, PERIOD parent_id)
+		REFERENCES without_overlaps_test (id, PERIOD id)
+);
+
+--
+-- test ALTER TABLE ADD CONSTRAINT
+--
+
+CREATE TABLE referencing_period_test (
+	id int4range,
+	valid_at tsrange,
+	parent_id int4range,
+	CONSTRAINT referencing_period_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+ALTER TABLE referencing_period_test
+	ADD CONSTRAINT referencing_period_fk
+	FOREIGN KEY (parent_id, PERIOD valid_at)
+	REFERENCES without_overlaps_test (id, PERIOD valid_at);
+ALTER TABLE referencing_period_test
+	DROP CONSTRAINT referencing_period_fk;
+-- with inferred PK on the referenced table:
+ALTER TABLE referencing_period_test
+	ADD CONSTRAINT referencing_period_fk
+	FOREIGN KEY (parent_id, PERIOD valid_at)
+	REFERENCES without_overlaps_test;
+
+-- should fail because of duplicate referenced columns:
+ALTER TABLE referencing_period_test
+	ADD CONSTRAINT referencing_period_fk2
+	FOREIGN KEY (parent_id, PERIOD parent_id)
+	REFERENCES without_overlaps_test (id, PERIOD id);
+
+--
+-- test with rows already
+--
+
+DELETE FROM referencing_period_test;
+ALTER TABLE referencing_period_test
+	DROP CONSTRAINT referencing_period_fk;
+INSERT INTO referencing_period_test VALUES ('[1,1]', tsrange('2018-01-02', '2018-02-01'), '[1,1]');
+ALTER TABLE referencing_period_test
+	ADD CONSTRAINT referencing_period_fk
+	FOREIGN KEY (parent_id, PERIOD valid_at)
+	REFERENCES without_overlaps_test;
+ALTER TABLE referencing_period_test
+	DROP CONSTRAINT referencing_period_fk;
+INSERT INTO referencing_period_test VALUES ('[2,2]', tsrange('2018-01-02', '2018-04-01'), '[1,1]');
+-- should fail:
+ALTER TABLE referencing_period_test
+	ADD CONSTRAINT referencing_period_fk
+	FOREIGN KEY (parent_id, PERIOD valid_at)
+	REFERENCES without_overlaps_test;
+-- okay again:
+DELETE FROM referencing_period_test;
+ALTER TABLE referencing_period_test
+	ADD CONSTRAINT referencing_period_fk
+	FOREIGN KEY (parent_id, PERIOD valid_at)
+	REFERENCES without_overlaps_test;
+
+--
+-- test pg_get_constraintdef
+--
+
+SELECT pg_get_constraintdef(oid) FROM pg_constraint WHERE conname = 'referencing_period_fk';
+
+--
+-- test FK child inserts
+--
+
+INSERT INTO referencing_period_test VALUES ('[1,1]', tsrange('2018-01-02', '2018-02-01'), '[1,1]');
+-- should fail:
+INSERT INTO referencing_period_test VALUES ('[2,2]', tsrange('2018-01-02', '2018-04-01'), '[1,1]');
+-- now it should work:
+INSERT INTO without_overlaps_test VALUES ('[1,1]', '2018-02-03', '2018-03-03');
+INSERT INTO referencing_period_test VALUES ('[2,2]', tsrange('2018-01-02', '2018-04-01'), '[1,1]');
+
+--
+-- test FK child updates
+--
+UPDATE referencing_period_test SET valid_at = tsrange('2018-01-02', '2018-03-01') WHERE id = '[1,1]';
+-- should fail:
+UPDATE referencing_period_test SET valid_at = tsrange('2018-01-02', '2018-05-01') WHERE id = '[1,1]';
+UPDATE referencing_period_test SET parent_id = '[8,8]' WHERE id = '[1,1]';
+
+--
+-- test FK parent updates NO ACTION
+--
+
+-- a PK update that succeeds because the numeric id isn't referenced:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', '2018-01-01', '2018-02-01');
+UPDATE without_overlaps_test SET valid_from = '2016-01-01', valid_til = '2016-02-01' WHERE id = '[5,5]';
+-- a PK update that succeeds even though the numeric id is referenced because the range isn't:
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+INSERT INTO without_overlaps_test VALUES ('[5,5]', '2018-01-01', '2018-02-01');
+INSERT INTO without_overlaps_test VALUES ('[5,5]', '2018-02-01', '2018-03-01');
+INSERT INTO referencing_period_test VALUES ('[3,3]', tsrange('2018-01-05', '2018-01-10'), '[5,5]');
+UPDATE without_overlaps_test SET valid_from = '2016-02-01', valid_til = '2016-03-01'
+WHERE id = '[5,5]' AND valid_from = '2018-02-01' AND valid_til = '2018-03-01';
+-- a PK update that fails because both are referenced:
+UPDATE without_overlaps_test SET valid_from = '2016-01-01', valid_til = '2016-02-01'
+WHERE id = '[5,5]' AND valid_from = '2018-01-01' AND valid_til = '2018-02-01';
+-- then delete the objecting FK record and the same PK update succeeds:
+DELETE FROM referencing_period_test WHERE id = '[3,3]';
+UPDATE without_overlaps_test SET valid_from = '2016-01-01', valid_til = '2016-02-01'
+WHERE id = '[5,5]' AND valid_from = '2018-01-01' AND valid_til = '2018-02-01';
+-- clean up:
+DELETE FROM referencing_period_test WHERE parent_id = '[5,5]';
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+
+--
+-- test FK parent updates RESTRICT
+--
+
+ALTER TABLE referencing_period_test
+	DROP CONSTRAINT referencing_period_fk;
+ALTER TABLE referencing_period_test
+	ADD CONSTRAINT referencing_period_fk
+	FOREIGN KEY (parent_id, PERIOD valid_at)
+	REFERENCES without_overlaps_test
+	ON DELETE RESTRICT;
+-- a PK update that succeeds because the numeric id isn't referenced:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', '2018-01-01', '2018-02-01');
+UPDATE without_overlaps_test SET valid_from = '2016-01-01', valid_til = '2016-02-01' WHERE id = '[5,5]';
+-- a PK update that succeeds even though the numeric id is referenced because the range isn't:
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+INSERT INTO without_overlaps_test VALUES ('[5,5]', '2018-01-01', '2018-02-01');
+INSERT INTO without_overlaps_test VALUES ('[5,5]', '2018-02-01', '2018-03-01');
+INSERT INTO referencing_period_test VALUES ('[3,3]', tsrange('2018-01-05', '2018-01-10'), '[5,5]');
+UPDATE without_overlaps_test SET valid_from = '2016-02-01', valid_til = '2016-03-01'
+WHERE id = '[5,5]' AND valid_from = '2018-02-01' AND valid_til = '2018-03-01';
+-- a PK update that fails because both are referenced:
+UPDATE without_overlaps_test SET valid_from = '2016-01-01', valid_til = '2016-02-01'
+WHERE id = '[5,5]' AND valid_from = '2018-01-01' AND valid_til = '2018-02-01';
+-- then delete the objecting FK record and the same PK update succeeds:
+DELETE FROM referencing_period_test WHERE id = '[3,3]';
+UPDATE without_overlaps_test SET valid_from = '2016-01-01', valid_til = '2016-02-01'
+WHERE id = '[5,5]' AND valid_from = '2018-01-01' AND valid_til = '2018-02-01';
+-- clean up:
+DELETE FROM referencing_period_test WHERE parent_id = '[5,5]';
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+
+--
+-- test FK parent deletes NO ACTION
+--
+
+ALTER TABLE referencing_period_test
+	DROP CONSTRAINT referencing_period_fk;
+ALTER TABLE referencing_period_test
+	ADD CONSTRAINT referencing_period_fk
+	FOREIGN KEY (parent_id, PERIOD valid_at)
+	REFERENCES without_overlaps_test;
+-- a PK delete that succeeds because the numeric id isn't referenced:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', '2018-01-01', '2018-02-01');
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+-- a PK delete that succeeds even though the numeric id is referenced because the range isn't:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', '2018-01-01', '2018-02-01');
+INSERT INTO without_overlaps_test VALUES ('[5,5]', '2018-02-01', '2018-03-01');
+INSERT INTO referencing_period_test VALUES ('[3,3]', tsrange('2018-01-05', '2018-01-10'), '[5,5]');
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_from = '2018-02-01' AND valid_til = '2018-03-01';
+-- a PK delete that fails because both are referenced:
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_from = '2018-01-01' AND valid_til = '2018-02-01';
+-- then delete the objecting FK record and the same PK delete succeeds:
+DELETE FROM referencing_period_test WHERE id = '[3,3]';
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_from = '2018-01-01' AND valid_til = '2018-02-01';
+
+--
+-- test FK parent deletes RESTRICT
+--
+
+ALTER TABLE referencing_period_test
+	DROP CONSTRAINT referencing_period_fk;
+ALTER TABLE referencing_period_test
+	ADD CONSTRAINT referencing_period_fk
+	FOREIGN KEY (parent_id, PERIOD valid_at)
+	REFERENCES without_overlaps_test
+	ON DELETE RESTRICT;
+INSERT INTO without_overlaps_test VALUES ('[5,5]', '2018-01-01', '2018-02-01');
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+-- a PK delete that succeeds even though the numeric id is referenced because the range isn't:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', '2018-01-01', '2018-02-01');
+INSERT INTO without_overlaps_test VALUES ('[5,5]', '2018-02-01', '2018-03-01');
+INSERT INTO referencing_period_test VALUES ('[3,3]', tsrange('2018-01-05', '2018-01-10'), '[5,5]');
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_from = '2018-02-01' AND valid_til = '2018-03-01';
+-- a PK delete that fails because both are referenced:
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_from = '2018-01-01' AND valid_til = '2018-02-01';
+-- then delete the objecting FK record and the same PK delete succeeds:
+DELETE FROM referencing_period_test WHERE id = '[3,3]';
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_from = '2018-01-01' AND valid_til = '2018-02-01';
+
+--
+-- test ON UPDATE/DELETE options
+--
+
+-- test FK parent updates CASCADE
+INSERT INTO without_overlaps_test VALUES ('[6,6]', '2018-01-01', '2021-01-01');
+INSERT INTO referencing_period_test VALUES ('[4,4]', tsrange('2018-01-01', '2021-01-01'), '[6,6]');
+ALTER TABLE referencing_period_test
+	DROP CONSTRAINT referencing_period_fk,
+	ADD CONSTRAINT referencing_period_fk
+		FOREIGN KEY (parent_id, PERIOD valid_at)
+		REFERENCES without_overlaps_test
+		ON DELETE CASCADE ON UPDATE CASCADE;
+UPDATE without_overlaps_test FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' SET id = '[7,7]' WHERE id = '[6,6]';
+SELECT * FROM referencing_period_test WHERE id = '[4,4]';
+UPDATE without_overlaps_test SET id = '[7,7]' WHERE id = '[6,6]';
+SELECT * FROM referencing_period_test WHERE id = '[4,4]';
+INSERT INTO without_overlaps_test VALUES ('[15,15]', '2018-01-01', '2020-01-01');
+INSERT INTO without_overlaps_test VALUES ('[15,15]', '2020-01-01', '2021-01-01');
+INSERT INTO referencing_period_test VALUES ('[10,10]', tsrange('2018-01-01', '2021-01-01'), '[15,15]');
+UPDATE without_overlaps_test SET id = '[16,16]' WHERE id = '[15,15]' AND tsrange(valid_from, valid_til) @> '2019-01-01'::timestamp;
+SELECT * FROM referencing_period_test WHERE id = '[10,10]';
+
+-- test FK parent deletes CASCADE
+INSERT INTO without_overlaps_test VALUES ('[8,8]', '2018-01-01', '2021-01-01');
+INSERT INTO referencing_period_test VALUES ('[5,5]', tsrange('2018-01-01', '2021-01-01'), '[8,8]');
+DELETE FROM without_overlaps_test FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' WHERE id = '[8,8]';
+SELECT * FROM referencing_period_test WHERE id = '[5,5]';
+DELETE FROM without_overlaps_test WHERE id = '[8,8]';
+SELECT * FROM referencing_period_test WHERE id = '[5,5]';
+INSERT INTO without_overlaps_test VALUES ('[17,17]', '2018-01-01', '2020-01-01');
+INSERT INTO without_overlaps_test VALUES ('[17,17]', '2020-01-01', '2021-01-01');
+INSERT INTO referencing_period_test VALUES ('[11,11]', tsrange('2018-01-01', '2021-01-01'), '[17,17]');
+DELETE FROM without_overlaps_test WHERE id = '[17,17]' AND tsrange(valid_from, valid_til) @> '2019-01-01'::timestamp;
+SELECT * FROM referencing_period_test WHERE id = '[11,11]';
+
+-- test FK parent updates SET NULL
+INSERT INTO without_overlaps_test VALUES ('[9,9]', '2018-01-01', '2021-01-01');
+INSERT INTO referencing_period_test VALUES ('[6,6]', tsrange('2018-01-01', '2021-01-01'), '[9,9]');
+ALTER TABLE referencing_period_test
+	DROP CONSTRAINT referencing_period_fk,
+	ADD CONSTRAINT referencing_period_fk
+		FOREIGN KEY (parent_id, PERIOD valid_at)
+		REFERENCES without_overlaps_test
+		ON DELETE SET NULL ON UPDATE SET NULL;
+UPDATE without_overlaps_test FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' SET id = '[10,10]' WHERE id = '[9,9]';
+SELECT * FROM referencing_period_test WHERE id = '[6,6]';
+UPDATE without_overlaps_test SET id = '[10,10]' WHERE id = '[9,9]';
+SELECT * FROM referencing_period_test WHERE id = '[6,6]';
+INSERT INTO without_overlaps_test VALUES ('[18,18]', '2018-01-01', '2020-01-01');
+INSERT INTO without_overlaps_test VALUES ('[18,18]', '2020-01-01', '2021-01-01');
+INSERT INTO referencing_period_test VALUES ('[12,12]', tsrange('2018-01-01', '2021-01-01'), '[18,18]');
+UPDATE without_overlaps_test SET id = '[19,19]' WHERE id = '[18,18]' AND tsrange(valid_from, valid_til) @> '2019-01-01'::timestamp;
+SELECT * FROM referencing_period_test WHERE id = '[12,12]';
+
+-- test FK parent deletes SET NULL
+INSERT INTO without_overlaps_test VALUES ('[11,11]', '2018-01-01', '2021-01-01');
+INSERT INTO referencing_period_test VALUES ('[7,7]', tsrange('2018-01-01', '2021-01-01'), '[11,11]');
+DELETE FROM without_overlaps_test FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' WHERE id = '[11,11]';
+SELECT * FROM referencing_period_test WHERE id = '[7,7]';
+DELETE FROM without_overlaps_test WHERE id = '[11,11]';
+SELECT * FROM referencing_period_test WHERE id = '[7,7]';
+INSERT INTO without_overlaps_test VALUES ('[20,20]', '2018-01-01', '2020-01-01');
+INSERT INTO without_overlaps_test VALUES ('[20,20]', '2020-01-01', '2021-01-01');
+INSERT INTO referencing_period_test VALUES ('[13,13]', tsrange('2018-01-01', '2021-01-01'), '[20,20]');
+DELETE FROM without_overlaps_test WHERE id = '[20,20]' AND tsrange(valid_from, valid_til) @> '2019-01-01'::timestamp;
+SELECT * FROM referencing_period_test WHERE id = '[13,13]';
+
+-- test FK parent updates SET DEFAULT
+INSERT INTO without_overlaps_test VALUES ('[-1,-1]', null, null);
+INSERT INTO without_overlaps_test VALUES ('[12,12]', '2018-01-01', '2021-01-01');
+INSERT INTO referencing_period_test VALUES ('[8,8]', tsrange('2018-01-01', '2021-01-01'), '[12,12]');
+ALTER TABLE referencing_period_test
+  ALTER COLUMN parent_id SET DEFAULT '[-1,-1]',
+	DROP CONSTRAINT referencing_period_fk,
+	ADD CONSTRAINT referencing_period_fk
+		FOREIGN KEY (parent_id, PERIOD valid_at)
+		REFERENCES without_overlaps_test
+		ON DELETE SET DEFAULT ON UPDATE SET DEFAULT;
+UPDATE without_overlaps_test FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' SET id = '[13,13]' WHERE id = '[12,12]';
+SELECT * FROM referencing_period_test WHERE id = '[8,8]';
+UPDATE without_overlaps_test SET id = '[13,13]' WHERE id = '[12,12]';
+SELECT * FROM referencing_period_test WHERE id = '[8,8]';
+INSERT INTO without_overlaps_test VALUES ('[22,22]', '2018-01-01', '2020-01-01');
+INSERT INTO without_overlaps_test VALUES ('[22,22]', '2020-01-01', '2021-01-01');
+INSERT INTO referencing_period_test VALUES ('[14,14]', tsrange('2018-01-01', '2021-01-01'), '[22,22]');
+UPDATE without_overlaps_test SET id = '[23,23]' WHERE id = '[22,22]' AND tsrange(valid_from, valid_til) @> '2019-01-01'::timestamp;
+SELECT * FROM referencing_period_test WHERE id = '[14,14]';
+
+-- test FK parent deletes SET DEFAULT
+INSERT INTO without_overlaps_test VALUES ('[14,14]', '2018-01-01', '2021-01-01');
+INSERT INTO referencing_period_test VALUES ('[9,9]', tsrange('2018-01-01', '2021-01-01'), '[14,14]');
+DELETE FROM without_overlaps_test FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' WHERE id = '[14,14]';
+SELECT * FROM referencing_period_test WHERE id = '[9,9]';
+DELETE FROM without_overlaps_test WHERE id = '[14,14]';
+SELECT * FROM referencing_period_test WHERE id = '[9,9]';
+INSERT INTO without_overlaps_test VALUES ('[24,24]', '2018-01-01', '2020-01-01');
+INSERT INTO without_overlaps_test VALUES ('[24,24]', '2020-01-01', '2021-01-01');
+INSERT INTO referencing_period_test VALUES ('[15,15]', tsrange('2018-01-01', '2021-01-01'), '[24,24]');
+DELETE FROM without_overlaps_test WHERE id = '[24,24]' AND tsrange(valid_from, valid_til) @> '2019-01-01'::timestamp;
+SELECT * FROM referencing_period_test WHERE id = '[15,15]';
+
+
+--
+-- test FOREIGN KEY, PERIOD references range
+--
+
+DROP TABLE without_overlaps_test CASCADE;
+CREATE TABLE without_overlaps_test (
+	id int4range,
+	valid_at tsrange,
+	CONSTRAINT without_overlaps_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+INSERT INTO without_overlaps_test VALUES ('[1,1]', tsrange('2018-01-02', '2018-02-03'));
+INSERT INTO without_overlaps_test VALUES ('[1,1]', tsrange('2018-03-03', '2018-04-04'));
+INSERT INTO without_overlaps_test VALUES ('[2,2]', tsrange('2018-01-01', '2018-01-05'));
+INSERT INTO without_overlaps_test VALUES ('[3,3]', tsrange('2018-01-01', NULL));
+
+-- Can't create a FK with a mismatched range type
+CREATE TABLE referencing_period_test2 (
+	id int4range,
+	valid_from int4,
+  valid_til int4,
+	PERIOD FOR valid_at (valid_from, valid_til),
+	parent_id int4range,
+	CONSTRAINT referencing_period_pk2 PRIMARY KEY (id, valid_at WITHOUT OVERLAPS),
+	CONSTRAINT referencing_period_fk2 FOREIGN KEY (parent_id, PERIOD valid_at)
+		REFERENCES without_overlaps_test (id, PERIOD valid_at)
+);
+
+-- with inferred PK on the referenced table:
+DROP TABLE referencing_period_test;
+CREATE TABLE referencing_period_test (
+	id int4range,
+	valid_from timestamp,
+	valid_til timestamp,
+	PERIOD FOR valid_at (valid_from, valid_til),
+	parent_id int4range,
+	CONSTRAINT referencing_period_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS),
+	CONSTRAINT referencing_period_fk FOREIGN KEY (parent_id, PERIOD valid_at)
+		REFERENCES without_overlaps_test
+);
+DROP TABLE referencing_period_test;
+
+-- should fail because of duplicate referenced columns:
+CREATE TABLE referencing_period_test (
+	id int4range,
+	valid_from timestamp,
+	valid_til timestamp,
+	PERIOD FOR valid_at (valid_from, valid_til),
+	parent_id int4range,
+	CONSTRAINT referencing_period_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS),
+	CONSTRAINT referencing_period_fk FOREIGN KEY (parent_id, PERIOD parent_id)
+		REFERENCES without_overlaps_test (id, PERIOD id)
+);
+
+--
+-- test ALTER TABLE ADD CONSTRAINT
+--
+
+CREATE TABLE referencing_period_test (
+	id int4range,
+	valid_from timestamp,
+	valid_til timestamp,
+	PERIOD FOR valid_at (valid_from, valid_til),
+	parent_id int4range,
+	CONSTRAINT referencing_period_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+ALTER TABLE referencing_period_test
+	ADD CONSTRAINT referencing_period_fk
+	FOREIGN KEY (parent_id, PERIOD valid_at)
+	REFERENCES without_overlaps_test (id, PERIOD valid_at);
+ALTER TABLE referencing_period_test
+	DROP CONSTRAINT referencing_period_fk;
+-- with inferred PK on the referenced table:
+ALTER TABLE referencing_period_test
+	ADD CONSTRAINT referencing_period_fk
+	FOREIGN KEY (parent_id, PERIOD valid_at)
+	REFERENCES without_overlaps_test;
+
+-- should fail because of duplicate referenced columns:
+ALTER TABLE referencing_period_test
+	ADD CONSTRAINT referencing_period_fk2
+	FOREIGN KEY (parent_id, PERIOD parent_id)
+	REFERENCES without_overlaps_test (id, PERIOD id);
+
+--
+-- test with rows already
+--
+
+DELETE FROM referencing_period_test;
+ALTER TABLE referencing_period_test
+	DROP CONSTRAINT referencing_period_fk;
+INSERT INTO referencing_period_test VALUES ('[1,1]', '2018-01-02', '2018-02-01', '[1,1]');
+ALTER TABLE referencing_period_test
+	ADD CONSTRAINT referencing_period_fk
+	FOREIGN KEY (parent_id, PERIOD valid_at)
+	REFERENCES without_overlaps_test;
+ALTER TABLE referencing_period_test
+	DROP CONSTRAINT referencing_period_fk;
+INSERT INTO referencing_period_test VALUES ('[2,2]', '2018-01-02', '2018-04-01', '[1,1]');
+-- should fail:
+ALTER TABLE referencing_period_test
+	ADD CONSTRAINT referencing_period_fk
+	FOREIGN KEY (parent_id, PERIOD valid_at)
+	REFERENCES without_overlaps_test;
+-- okay again:
+DELETE FROM referencing_period_test;
+ALTER TABLE referencing_period_test
+	ADD CONSTRAINT referencing_period_fk
+	FOREIGN KEY (parent_id, PERIOD valid_at)
+	REFERENCES without_overlaps_test;
+
+--
+-- test pg_get_constraintdef
+--
+
+SELECT pg_get_constraintdef(oid) FROM pg_constraint WHERE conname = 'referencing_period_fk';
+
+--
+-- test FK child inserts
+--
+
+INSERT INTO referencing_period_test VALUES ('[1,1]', '2018-01-02', '2018-02-01', '[1,1]');
+-- should fail:
+INSERT INTO referencing_period_test VALUES ('[2,2]', '2018-01-02', '2018-04-01', '[1,1]');
+-- now it should work:
+INSERT INTO without_overlaps_test VALUES ('[1,1]', tsrange('2018-02-03', '2018-03-03'));
+INSERT INTO referencing_period_test VALUES ('[2,2]', '2018-01-02', '2018-04-01', '[1,1]');
+
+--
+-- test FK child updates
+--
+UPDATE referencing_period_test SET valid_from = '2018-01-02', valid_til = '2018-03-01' WHERE id = '[1,1]';
+-- should fail:
+UPDATE referencing_period_test SET valid_from = '2018-01-02', valid_til = '2018-05-01' WHERE id = '[1,1]';
+UPDATE referencing_period_test SET parent_id = '[8,8]' WHERE id = '[1,1]';
+
+--
+-- test FK parent updates NO ACTION
+--
+
+-- a PK update that succeeds because the numeric id isn't referenced:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01') WHERE id = '[5,5]';
+-- a PK update that succeeds even though the numeric id is referenced because the range isn't:
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-02-01', '2018-03-01'));
+INSERT INTO referencing_period_test VALUES ('[3,3]', '2018-01-05', '2018-01-10', '[5,5]');
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-02-01', '2016-03-01')
+WHERE id = '[5,5]' AND valid_at = tsrange('2018-02-01', '2018-03-01');
+-- a PK update that fails because both are referenced:
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01')
+WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01','2018-02-01');
+-- then delete the objecting FK record and the same PK update succeeds:
+DELETE FROM referencing_period_test WHERE id = '[3,3]';
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01')
+WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+-- clean up:
+DELETE FROM referencing_period_test WHERE parent_id = '[5,5]';
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+
+--
+-- test FK parent updates RESTRICT
+--
+
+ALTER TABLE referencing_period_test
+	DROP CONSTRAINT referencing_period_fk;
+ALTER TABLE referencing_period_test
+	ADD CONSTRAINT referencing_period_fk
+	FOREIGN KEY (parent_id, PERIOD valid_at)
+	REFERENCES without_overlaps_test
+	ON DELETE RESTRICT;
+-- a PK update that succeeds because the numeric id isn't referenced:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01') WHERE id = '[5,5]';
+-- a PK update that succeeds even though the numeric id is referenced because the range isn't:
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-02-01', '2018-03-01'));
+INSERT INTO referencing_period_test VALUES ('[3,3]', '2018-01-05', '2018-01-10', '[5,5]');
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-02-01', '2016-03-01')
+WHERE id = '[5,5]' AND valid_at = tsrange('2018-02-01', '2018-03-01');
+-- a PK update that fails because both are referenced:
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01')
+WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+-- then delete the objecting FK record and the same PK update succeeds:
+DELETE FROM referencing_period_test WHERE id = '[3,3]';
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01')
+WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+-- clean up:
+DELETE FROM referencing_period_test WHERE parent_id = '[5,5]';
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+
+--
+-- test FK parent deletes NO ACTION
+--
+
+ALTER TABLE referencing_period_test
+	DROP CONSTRAINT referencing_period_fk;
+ALTER TABLE referencing_period_test
+	ADD CONSTRAINT referencing_period_fk
+	FOREIGN KEY (parent_id, PERIOD valid_at)
+	REFERENCES without_overlaps_test;
+-- a PK delete that succeeds because the numeric id isn't referenced:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+-- a PK delete that succeeds even though the numeric id is referenced because the range isn't:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-02-01', '2018-03-01'));
+INSERT INTO referencing_period_test VALUES ('[3,3]', '2018-01-05', '2018-01-10', '[5,5]');
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-02-01', '2018-03-01');
+-- a PK delete that fails because both are referenced:
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+-- then delete the objecting FK record and the same PK delete succeeds:
+DELETE FROM referencing_period_test WHERE id = '[3,3]';
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+
+--
+-- test FK parent deletes RESTRICT
+--
+
+ALTER TABLE referencing_period_test
+	DROP CONSTRAINT referencing_period_fk;
+ALTER TABLE referencing_period_test
+	ADD CONSTRAINT referencing_period_fk
+	FOREIGN KEY (parent_id, PERIOD valid_at)
+	REFERENCES without_overlaps_test
+	ON DELETE RESTRICT;
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+-- a PK delete that succeeds even though the numeric id is referenced because the range isn't:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-02-01', '2018-03-01'));
+INSERT INTO referencing_period_test VALUES ('[3,3]', '2018-01-05', '2018-01-10', '[5,5]');
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-02-01', '2018-03-01');
+-- a PK delete that fails because both are referenced:
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+-- then delete the objecting FK record and the same PK delete succeeds:
+DELETE FROM referencing_period_test WHERE id = '[3,3]';
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+
+--
+-- test ON UPDATE/DELETE options
+--
+
+-- test FK parent updates CASCADE
+INSERT INTO without_overlaps_test VALUES ('[6,6]', tsrange('2018-01-01', '2021-01-01'));
+INSERT INTO referencing_period_test VALUES ('[4,4]', '2018-01-01', '2021-01-01', '[6,6]');
+ALTER TABLE referencing_period_test
+	DROP CONSTRAINT referencing_period_fk,
+	ADD CONSTRAINT referencing_period_fk
+		FOREIGN KEY (parent_id, PERIOD valid_at)
+		REFERENCES without_overlaps_test
+		ON DELETE CASCADE ON UPDATE CASCADE;
+UPDATE without_overlaps_test FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' SET id = '[7,7]' WHERE id = '[6,6]';
+SELECT * FROM referencing_period_test WHERE id = '[4,4]';
+UPDATE without_overlaps_test SET id = '[7,7]' WHERE id = '[6,6]';
+SELECT * FROM referencing_period_test WHERE id = '[4,4]';
+INSERT INTO without_overlaps_test VALUES ('[15,15]', tsrange('2018-01-01', '2020-01-01'));
+INSERT INTO without_overlaps_test VALUES ('[15,15]', tsrange('2020-01-01', '2021-01-01'));
+INSERT INTO referencing_period_test VALUES ('[10,10]', '2018-01-01', '2021-01-01', '[15,15]');
+UPDATE without_overlaps_test SET id = '[16,16]' WHERE id = '[15,15]' AND valid_at @> '2019-01-01'::timestamp;
+SELECT * FROM referencing_period_test WHERE id = '[10,10]';
+
+-- test FK parent deletes CASCADE
+INSERT INTO without_overlaps_test VALUES ('[8,8]', tsrange('2018-01-01', '2021-01-01'));
+INSERT INTO referencing_period_test VALUES ('[5,5]', '2018-01-01', '2021-01-01', '[8,8]');
+DELETE FROM without_overlaps_test FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' WHERE id = '[8,8]';
+SELECT * FROM referencing_period_test WHERE id = '[5,5]';
+DELETE FROM without_overlaps_test WHERE id = '[8,8]';
+SELECT * FROM referencing_period_test WHERE id = '[5,5]';
+INSERT INTO without_overlaps_test VALUES ('[17,17]', tsrange('2018-01-01', '2020-01-01'));
+INSERT INTO without_overlaps_test VALUES ('[17,17]', tsrange('2020-01-01', '2021-01-01'));
+INSERT INTO referencing_period_test VALUES ('[11,11]', '2018-01-01', '2021-01-01', '[17,17]');
+DELETE FROM without_overlaps_test WHERE id = '[17,17]' AND valid_at @> '2019-01-01'::timestamp;
+SELECT * FROM referencing_period_test WHERE id = '[11,11]';
+
+-- test FK parent updates SET NULL
+INSERT INTO without_overlaps_test VALUES ('[9,9]', tsrange('2018-01-01', '2021-01-01'));
+INSERT INTO referencing_period_test VALUES ('[6,6]', '2018-01-01', '2021-01-01', '[9,9]');
+ALTER TABLE referencing_period_test
+	DROP CONSTRAINT referencing_period_fk,
+	ADD CONSTRAINT referencing_period_fk
+		FOREIGN KEY (parent_id, PERIOD valid_at)
+		REFERENCES without_overlaps_test
+		ON DELETE SET NULL ON UPDATE SET NULL;
+UPDATE without_overlaps_test FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' SET id = '[10,10]' WHERE id = '[9,9]';
+SELECT * FROM referencing_period_test WHERE id = '[6,6]';
+UPDATE without_overlaps_test SET id = '[10,10]' WHERE id = '[9,9]';
+SELECT * FROM referencing_period_test WHERE id = '[6,6]';
+INSERT INTO without_overlaps_test VALUES ('[18,18]', tsrange('2018-01-01', '2020-01-01'));
+INSERT INTO without_overlaps_test VALUES ('[18,18]', tsrange('2020-01-01', '2021-01-01'));
+INSERT INTO referencing_period_test VALUES ('[12,12]', '2018-01-01', '2021-01-01', '[18,18]');
+UPDATE without_overlaps_test SET id = '[19,19]' WHERE id = '[18,18]' AND valid_at @> '2019-01-01'::timestamp;
+SELECT * FROM referencing_period_test WHERE id = '[12,12]';
+
+-- test FK parent deletes SET NULL
+INSERT INTO without_overlaps_test VALUES ('[11,11]', tsrange('2018-01-01', '2021-01-01'));
+INSERT INTO referencing_period_test VALUES ('[7,7]', '2018-01-01', '2021-01-01', '[11,11]');
+DELETE FROM without_overlaps_test FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' WHERE id = '[11,11]';
+SELECT * FROM referencing_period_test WHERE id = '[7,7]';
+DELETE FROM without_overlaps_test WHERE id = '[11,11]';
+SELECT * FROM referencing_period_test WHERE id = '[7,7]';
+INSERT INTO without_overlaps_test VALUES ('[20,20]', tsrange('2018-01-01', '2020-01-01'));
+INSERT INTO without_overlaps_test VALUES ('[20,20]', tsrange('2020-01-01', '2021-01-01'));
+INSERT INTO referencing_period_test VALUES ('[13,13]', '2018-01-01', '2021-01-01', '[20,20]');
+DELETE FROM without_overlaps_test WHERE id = '[20,20]' AND valid_at @> '2019-01-01'::timestamp;
+SELECT * FROM referencing_period_test WHERE id = '[13,13]';
+
+-- test FK parent updates SET DEFAULT
+INSERT INTO without_overlaps_test VALUES ('[-1,-1]', tsrange(null, null));
+INSERT INTO without_overlaps_test VALUES ('[12,12]', tsrange('2018-01-01', '2021-01-01'));
+INSERT INTO referencing_period_test VALUES ('[8,8]', '2018-01-01', '2021-01-01', '[12,12]');
+ALTER TABLE referencing_period_test
+  ALTER COLUMN parent_id SET DEFAULT '[-1,-1]',
+	DROP CONSTRAINT referencing_period_fk,
+	ADD CONSTRAINT referencing_period_fk
+		FOREIGN KEY (parent_id, PERIOD valid_at)
+		REFERENCES without_overlaps_test
+		ON DELETE SET DEFAULT ON UPDATE SET DEFAULT;
+UPDATE without_overlaps_test FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' SET id = '[13,13]' WHERE id = '[12,12]';
+SELECT * FROM referencing_period_test WHERE id = '[8,8]';
+UPDATE without_overlaps_test SET id = '[13,13]' WHERE id = '[12,12]';
+SELECT * FROM referencing_period_test WHERE id = '[8,8]';
+INSERT INTO without_overlaps_test VALUES ('[22,22]', tsrange('2018-01-01', '2020-01-01'));
+INSERT INTO without_overlaps_test VALUES ('[22,22]', tsrange('2020-01-01', '2021-01-01'));
+INSERT INTO referencing_period_test VALUES ('[14,14]', '2018-01-01', '2021-01-01', '[22,22]');
+UPDATE without_overlaps_test SET id = '[23,23]' WHERE id = '[22,22]' AND valid_at @> '2019-01-01'::timestamp;
+SELECT * FROM referencing_period_test WHERE id = '[14,14]';
+
+-- test FK parent deletes SET DEFAULT
+INSERT INTO without_overlaps_test VALUES ('[14,14]', tsrange('2018-01-01', '2021-01-01'));
+INSERT INTO referencing_period_test VALUES ('[9,9]', '2018-01-01', '2021-01-01', '[14,14]');
+DELETE FROM without_overlaps_test FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' WHERE id = '[14,14]';
+SELECT * FROM referencing_period_test WHERE id = '[9,9]';
+DELETE FROM without_overlaps_test WHERE id = '[14,14]';
+SELECT * FROM referencing_period_test WHERE id = '[9,9]';
+INSERT INTO without_overlaps_test VALUES ('[24,24]', tsrange('2018-01-01', '2020-01-01'));
+INSERT INTO without_overlaps_test VALUES ('[24,24]', tsrange('2020-01-01', '2021-01-01'));
+INSERT INTO referencing_period_test VALUES ('[15,15]', '2018-01-01', '2021-01-01', '[24,24]');
+DELETE FROM without_overlaps_test WHERE id = '[24,24]' AND valid_at @> '2019-01-01'::timestamp;
+SELECT * FROM referencing_period_test WHERE id = '[15,15]';
+
+
+--
+-- test FOREIGN KEY, PERIOD references PERIOD
+--
+
+DROP TABLE without_overlaps_test CASCADE;
+CREATE TABLE without_overlaps_test (
+	id int4range,
+	valid_from timestamp,
+	valid_til timestamp,
+	PERIOD FOR valid_at (valid_from, valid_til),
+	CONSTRAINT without_overlaps_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+INSERT INTO without_overlaps_test VALUES ('[1,1]', '2018-01-02', '2018-02-03');
+INSERT INTO without_overlaps_test VALUES ('[1,1]', '2018-03-03', '2018-04-04');
+INSERT INTO without_overlaps_test VALUES ('[2,2]', '2018-01-01', '2018-01-05');
+INSERT INTO without_overlaps_test VALUES ('[3,3]', '2018-01-01', NULL);
+
+-- Can't create a FK with a mismatched range type
+CREATE TABLE referencing_period_test2 (
+	id int4range,
+	valid_from int4,
+  valid_til int4,
+	PERIOD FOR valid_at (valid_from, valid_til),
+	parent_id int4range,
+	CONSTRAINT referencing_period_pk2 PRIMARY KEY (id, valid_at WITHOUT OVERLAPS),
+	CONSTRAINT referencing_period_fk2 FOREIGN KEY (parent_id, PERIOD valid_at)
+		REFERENCES without_overlaps_test (id, PERIOD valid_at)
+);
+
+-- with inferred PK on the referenced table:
+DROP TABLE referencing_period_test;
+CREATE TABLE referencing_period_test (
+	id int4range,
+	valid_from timestamp,
+	valid_til timestamp,
+	PERIOD FOR valid_at (valid_from, valid_til),
+	parent_id int4range,
+	CONSTRAINT referencing_period_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS),
+	CONSTRAINT referencing_period_fk FOREIGN KEY (parent_id, PERIOD valid_at)
+		REFERENCES without_overlaps_test
+);
+DROP TABLE referencing_period_test;
+
+-- should fail because of duplicate referenced columns:
+CREATE TABLE referencing_period_test (
+	id int4range,
+	valid_from timestamp,
+	valid_til timestamp,
+	PERIOD FOR valid_at (valid_from, valid_til),
+	parent_id int4range,
+	CONSTRAINT referencing_period_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS),
+	CONSTRAINT referencing_period_fk FOREIGN KEY (parent_id, PERIOD parent_id)
+		REFERENCES without_overlaps_test (id, PERIOD id)
+);
+
+--
+-- test ALTER TABLE ADD CONSTRAINT
+--
+
+CREATE TABLE referencing_period_test (
+	id int4range,
+	valid_from timestamp,
+	valid_til timestamp,
+	PERIOD FOR valid_at (valid_from, valid_til),
+	parent_id int4range,
+	CONSTRAINT referencing_period_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+ALTER TABLE referencing_period_test
+	ADD CONSTRAINT referencing_period_fk
+	FOREIGN KEY (parent_id, PERIOD valid_at)
+	REFERENCES without_overlaps_test (id, PERIOD valid_at);
+ALTER TABLE referencing_period_test
+	DROP CONSTRAINT referencing_period_fk;
+-- with inferred PK on the referenced table:
+ALTER TABLE referencing_period_test
+	ADD CONSTRAINT referencing_period_fk
+	FOREIGN KEY (parent_id, PERIOD valid_at)
+	REFERENCES without_overlaps_test;
+
+-- should fail because of duplicate referenced columns:
+ALTER TABLE referencing_period_test
+	ADD CONSTRAINT referencing_period_fk2
+	FOREIGN KEY (parent_id, PERIOD parent_id)
+	REFERENCES without_overlaps_test (id, PERIOD id);
+
+--
+-- test with rows already
+--
+
+DELETE FROM referencing_period_test;
+ALTER TABLE referencing_period_test
+	DROP CONSTRAINT referencing_period_fk;
+INSERT INTO referencing_period_test VALUES ('[1,1]', '2018-01-02', '2018-02-01', '[1,1]');
+ALTER TABLE referencing_period_test
+	ADD CONSTRAINT referencing_period_fk
+	FOREIGN KEY (parent_id, PERIOD valid_at)
+	REFERENCES without_overlaps_test;
+ALTER TABLE referencing_period_test
+	DROP CONSTRAINT referencing_period_fk;
+INSERT INTO referencing_period_test VALUES ('[2,2]', '2018-01-02', '2018-04-01', '[1,1]');
+-- should fail:
+ALTER TABLE referencing_period_test
+	ADD CONSTRAINT referencing_period_fk
+	FOREIGN KEY (parent_id, PERIOD valid_at)
+	REFERENCES without_overlaps_test;
+-- okay again:
+DELETE FROM referencing_period_test;
+ALTER TABLE referencing_period_test
+	ADD CONSTRAINT referencing_period_fk
+	FOREIGN KEY (parent_id, PERIOD valid_at)
+	REFERENCES without_overlaps_test;
+
+--
+-- test pg_get_constraintdef
+--
+
+SELECT pg_get_constraintdef(oid) FROM pg_constraint WHERE conname = 'referencing_period_fk';
+
+--
+-- test FK child inserts
+--
+
+INSERT INTO referencing_period_test VALUES ('[1,1]', '2018-01-02', '2018-02-01', '[1,1]');
+-- should fail:
+INSERT INTO referencing_period_test VALUES ('[2,2]', '2018-01-02', '2018-04-01', '[1,1]');
+-- now it should work:
+INSERT INTO without_overlaps_test VALUES ('[1,1]', '2018-02-03', '2018-03-03');
+INSERT INTO referencing_period_test VALUES ('[2,2]', '2018-01-02', '2018-04-01', '[1,1]');
+
+--
+-- test FK child updates
+--
+UPDATE referencing_period_test SET valid_from = '2018-01-02', valid_til = '2018-03-01' WHERE id = '[1,1]';
+-- should fail:
+UPDATE referencing_period_test SET valid_from = '2018-01-02', valid_til = '2018-05-01' WHERE id = '[1,1]';
+UPDATE referencing_period_test SET parent_id = '[8,8]' WHERE id = '[1,1]';
+
+--
+-- test FK parent updates NO ACTION
+--
+
+-- a PK update that succeeds because the numeric id isn't referenced:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', '2018-01-01', '2018-02-01');
+UPDATE without_overlaps_test SET valid_from = '2016-01-01', valid_til = '2016-02-01' WHERE id = '[5,5]';
+-- a PK update that succeeds even though the numeric id is referenced because the range isn't:
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+INSERT INTO without_overlaps_test VALUES ('[5,5]', '2018-01-01', '2018-02-01');
+INSERT INTO without_overlaps_test VALUES ('[5,5]', '2018-02-01', '2018-03-01');
+INSERT INTO referencing_period_test VALUES ('[3,3]', '2018-01-05', '2018-01-10', '[5,5]');
+UPDATE without_overlaps_test SET valid_from = '2016-02-01', valid_til = '2016-03-01'
+WHERE id = '[5,5]' AND valid_from = '2018-02-01' AND valid_til = '2018-03-01';
+-- a PK update that fails because both are referenced:
+UPDATE without_overlaps_test SET valid_from = '2016-01-01', valid_til = '2016-02-01'
+WHERE id = '[5,5]' AND valid_from = '2018-01-01' AND valid_til = '2018-02-01';
+-- then delete the objecting FK record and the same PK update succeeds:
+DELETE FROM referencing_period_test WHERE id = '[3,3]';
+UPDATE without_overlaps_test SET valid_from = '2016-01-01', valid_til = '2016-02-01'
+WHERE id = '[5,5]' AND valid_from = '2018-01-01' AND valid_til = '2018-02-01';
+-- clean up:
+DELETE FROM referencing_period_test WHERE parent_id = '[5,5]';
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+
+--
+-- test FK parent updates RESTRICT
+--
+
+ALTER TABLE referencing_period_test
+	DROP CONSTRAINT referencing_period_fk;
+ALTER TABLE referencing_period_test
+	ADD CONSTRAINT referencing_period_fk
+	FOREIGN KEY (parent_id, PERIOD valid_at)
+	REFERENCES without_overlaps_test
+	ON DELETE RESTRICT;
+-- a PK update that succeeds because the numeric id isn't referenced:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', '2018-01-01', '2018-02-01');
+UPDATE without_overlaps_test SET valid_from = '2016-01-01', valid_til = '2016-02-01' WHERE id = '[5,5]';
+-- a PK update that succeeds even though the numeric id is referenced because the range isn't:
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+INSERT INTO without_overlaps_test VALUES ('[5,5]', '2018-01-01', '2018-02-01');
+INSERT INTO without_overlaps_test VALUES ('[5,5]', '2018-02-01', '2018-03-01');
+INSERT INTO referencing_period_test VALUES ('[3,3]', '2018-01-05', '2018-01-10', '[5,5]');
+UPDATE without_overlaps_test SET valid_from = '2016-02-01', valid_til = '2016-03-01'
+WHERE id = '[5,5]' AND valid_from = '2018-02-01' AND valid_til = '2018-03-01';
+-- a PK update that fails because both are referenced:
+UPDATE without_overlaps_test SET valid_from = '2016-01-01', valid_til = '2016-02-01'
+WHERE id = '[5,5]' AND valid_from = '2018-01-01' AND valid_til = '2018-02-01';
+-- then delete the objecting FK record and the same PK update succeeds:
+DELETE FROM referencing_period_test WHERE id = '[3,3]';
+UPDATE without_overlaps_test SET valid_from = '2016-01-01', valid_til = '2016-02-01'
+WHERE id = '[5,5]' AND valid_from = '2018-01-01' AND valid_til = '2018-02-01';
+-- clean up:
+DELETE FROM referencing_period_test WHERE parent_id = '[5,5]';
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+
+--
+-- test FK parent deletes NO ACTION
+--
+
+ALTER TABLE referencing_period_test
+	DROP CONSTRAINT referencing_period_fk;
+ALTER TABLE referencing_period_test
+	ADD CONSTRAINT referencing_period_fk
+	FOREIGN KEY (parent_id, PERIOD valid_at)
+	REFERENCES without_overlaps_test;
+-- a PK delete that succeeds because the numeric id isn't referenced:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', '2018-01-01', '2018-02-01');
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+-- a PK delete that succeeds even though the numeric id is referenced because the range isn't:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', '2018-01-01', '2018-02-01');
+INSERT INTO without_overlaps_test VALUES ('[5,5]', '2018-02-01', '2018-03-01');
+INSERT INTO referencing_period_test VALUES ('[3,3]', '2018-01-05', '2018-01-10', '[5,5]');
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_from = '2018-02-01' AND valid_til = '2018-03-01';
+-- a PK delete that fails because both are referenced:
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_from = '2018-01-01' AND valid_til = '2018-02-01';
+-- then delete the objecting FK record and the same PK delete succeeds:
+DELETE FROM referencing_period_test WHERE id = '[3,3]';
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_from = '2018-01-01' AND valid_til = '2018-02-01';
+
+--
+-- test FK parent deletes RESTRICT
+--
+
+ALTER TABLE referencing_period_test
+	DROP CONSTRAINT referencing_period_fk;
+ALTER TABLE referencing_period_test
+	ADD CONSTRAINT referencing_period_fk
+	FOREIGN KEY (parent_id, PERIOD valid_at)
+	REFERENCES without_overlaps_test
+	ON DELETE RESTRICT;
+INSERT INTO without_overlaps_test VALUES ('[5,5]', '2018-01-01', '2018-02-01');
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+-- a PK delete that succeeds even though the numeric id is referenced because the range isn't:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', '2018-01-01', '2018-02-01');
+INSERT INTO without_overlaps_test VALUES ('[5,5]', '2018-02-01', '2018-03-01');
+INSERT INTO referencing_period_test VALUES ('[3,3]', '2018-01-05', '2018-01-10', '[5,5]');
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_from = '2018-02-01' AND valid_til = '2018-03-01';
+-- a PK delete that fails because both are referenced:
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_from = '2018-01-01' AND valid_til = '2018-02-01';
+-- then delete the objecting FK record and the same PK delete succeeds:
+DELETE FROM referencing_period_test WHERE id = '[3,3]';
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_from = '2018-01-01' AND valid_til = '2018-02-01';
+
+--
+-- test ON UPDATE/DELETE options
+--
+
+-- test FK parent updates CASCADE
+INSERT INTO without_overlaps_test VALUES ('[6,6]', '2018-01-01', '2021-01-01');
+INSERT INTO referencing_period_test VALUES ('[4,4]', '2018-01-01', '2021-01-01', '[6,6]');
+ALTER TABLE referencing_period_test
+	DROP CONSTRAINT referencing_period_fk,
+	ADD CONSTRAINT referencing_period_fk
+		FOREIGN KEY (parent_id, PERIOD valid_at)
+		REFERENCES without_overlaps_test
+		ON DELETE CASCADE ON UPDATE CASCADE;
+UPDATE without_overlaps_test FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' SET id = '[7,7]' WHERE id = '[6,6]';
+SELECT * FROM referencing_period_test WHERE id = '[4,4]';
+UPDATE without_overlaps_test SET id = '[7,7]' WHERE id = '[6,6]';
+SELECT * FROM referencing_period_test WHERE id = '[4,4]';
+INSERT INTO without_overlaps_test VALUES ('[15,15]', '2018-01-01', '2020-01-01');
+INSERT INTO without_overlaps_test VALUES ('[15,15]', '2020-01-01', '2021-01-01');
+INSERT INTO referencing_period_test VALUES ('[10,10]', '2018-01-01', '2021-01-01', '[15,15]');
+UPDATE without_overlaps_test SET id = '[16,16]' WHERE id = '[15,15]' AND tsrange(valid_from, valid_til) @> '2019-01-01'::timestamp;
+SELECT * FROM referencing_period_test WHERE id = '[10,10]';
+
+-- test FK parent deletes CASCADE
+INSERT INTO without_overlaps_test VALUES ('[8,8]', '2018-01-01', '2021-01-01');
+INSERT INTO referencing_period_test VALUES ('[5,5]', '2018-01-01', '2021-01-01', '[8,8]');
+DELETE FROM without_overlaps_test FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' WHERE id = '[8,8]';
+SELECT * FROM referencing_period_test WHERE id = '[5,5]';
+DELETE FROM without_overlaps_test WHERE id = '[8,8]';
+SELECT * FROM referencing_period_test WHERE id = '[5,5]';
+INSERT INTO without_overlaps_test VALUES ('[17,17]', '2018-01-01', '2020-01-01');
+INSERT INTO without_overlaps_test VALUES ('[17,17]', '2020-01-01', '2021-01-01');
+INSERT INTO referencing_period_test VALUES ('[11,11]', '2018-01-01', '2021-01-01', '[17,17]');
+DELETE FROM without_overlaps_test WHERE id = '[17,17]' AND tsrange(valid_from, valid_til) @> '2019-01-01'::timestamp;
+SELECT * FROM referencing_period_test WHERE id = '[11,11]';
+
+-- test FK parent updates SET NULL
+INSERT INTO without_overlaps_test VALUES ('[9,9]', '2018-01-01', '2021-01-01');
+INSERT INTO referencing_period_test VALUES ('[6,6]', '2018-01-01', '2021-01-01', '[9,9]');
+ALTER TABLE referencing_period_test
+	DROP CONSTRAINT referencing_period_fk,
+	ADD CONSTRAINT referencing_period_fk
+		FOREIGN KEY (parent_id, PERIOD valid_at)
+		REFERENCES without_overlaps_test
+		ON DELETE SET NULL ON UPDATE SET NULL;
+UPDATE without_overlaps_test FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' SET id = '[10,10]' WHERE id = '[9,9]';
+SELECT * FROM referencing_period_test WHERE id = '[6,6]';
+UPDATE without_overlaps_test SET id = '[10,10]' WHERE id = '[9,9]';
+SELECT * FROM referencing_period_test WHERE id = '[6,6]';
+INSERT INTO without_overlaps_test VALUES ('[18,18]', '2018-01-01', '2020-01-01');
+INSERT INTO without_overlaps_test VALUES ('[18,18]', '2020-01-01', '2021-01-01');
+INSERT INTO referencing_period_test VALUES ('[12,12]', '2018-01-01', '2021-01-01', '[18,18]');
+UPDATE without_overlaps_test SET id = '[19,19]' WHERE id = '[18,18]' AND tsrange(valid_from, valid_til) @> '2019-01-01'::timestamp;
+SELECT * FROM referencing_period_test WHERE id = '[12,12]';
+
+-- test FK parent deletes SET NULL
+INSERT INTO without_overlaps_test VALUES ('[11,11]', '2018-01-01', '2021-01-01');
+INSERT INTO referencing_period_test VALUES ('[7,7]', '2018-01-01', '2021-01-01', '[11,11]');
+DELETE FROM without_overlaps_test FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' WHERE id = '[11,11]';
+SELECT * FROM referencing_period_test WHERE id = '[7,7]';
+DELETE FROM without_overlaps_test WHERE id = '[11,11]';
+SELECT * FROM referencing_period_test WHERE id = '[7,7]';
+INSERT INTO without_overlaps_test VALUES ('[20,20]', '2018-01-01', '2020-01-01');
+INSERT INTO without_overlaps_test VALUES ('[20,20]', '2020-01-01', '2021-01-01');
+INSERT INTO referencing_period_test VALUES ('[13,13]', '2018-01-01', '2021-01-01', '[20,20]');
+DELETE FROM without_overlaps_test WHERE id = '[20,20]' AND tsrange(valid_from, valid_til) @> '2019-01-01'::timestamp;
+SELECT * FROM referencing_period_test WHERE id = '[13,13]';
+
+-- test FK parent updates SET DEFAULT
+INSERT INTO without_overlaps_test VALUES ('[-1,-1]', null, null);
+INSERT INTO without_overlaps_test VALUES ('[12,12]', '2018-01-01', '2021-01-01');
+INSERT INTO referencing_period_test VALUES ('[8,8]', '2018-01-01', '2021-01-01', '[12,12]');
+ALTER TABLE referencing_period_test
+  ALTER COLUMN parent_id SET DEFAULT '[-1,-1]',
+	DROP CONSTRAINT referencing_period_fk,
+	ADD CONSTRAINT referencing_period_fk
+		FOREIGN KEY (parent_id, PERIOD valid_at)
+		REFERENCES without_overlaps_test
+		ON DELETE SET DEFAULT ON UPDATE SET DEFAULT;
+UPDATE without_overlaps_test FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' SET id = '[13,13]' WHERE id = '[12,12]';
+SELECT * FROM referencing_period_test WHERE id = '[8,8]';
+UPDATE without_overlaps_test SET id = '[13,13]' WHERE id = '[12,12]';
+SELECT * FROM referencing_period_test WHERE id = '[8,8]';
+INSERT INTO without_overlaps_test VALUES ('[22,22]', '2018-01-01', '2020-01-01');
+INSERT INTO without_overlaps_test VALUES ('[22,22]', '2020-01-01', '2021-01-01');
+INSERT INTO referencing_period_test VALUES ('[14,14]', '2018-01-01', '2021-01-01', '[22,22]');
+UPDATE without_overlaps_test SET id = '[23,23]' WHERE id = '[22,22]' AND tsrange(valid_from, valid_til) @> '2019-01-01'::timestamp;
+SELECT * FROM referencing_period_test WHERE id = '[14,14]';
+
+-- test FK parent deletes SET DEFAULT
+INSERT INTO without_overlaps_test VALUES ('[14,14]', '2018-01-01', '2021-01-01');
+INSERT INTO referencing_period_test VALUES ('[9,9]', '2018-01-01', '2021-01-01', '[14,14]');
+DELETE FROM without_overlaps_test FOR PORTION OF valid_at FROM '2019-01-01' TO '2020-01-01' WHERE id = '[14,14]';
+SELECT * FROM referencing_period_test WHERE id = '[9,9]';
+DELETE FROM without_overlaps_test WHERE id = '[14,14]';
+SELECT * FROM referencing_period_test WHERE id = '[9,9]';
+INSERT INTO without_overlaps_test VALUES ('[24,24]', '2018-01-01', '2020-01-01');
+INSERT INTO without_overlaps_test VALUES ('[24,24]', '2020-01-01', '2021-01-01');
+INSERT INTO referencing_period_test VALUES ('[15,15]', '2018-01-01', '2021-01-01', '[24,24]');
+DELETE FROM without_overlaps_test WHERE id = '[24,24]' AND tsrange(valid_from, valid_til) @> '2019-01-01'::timestamp;
+SELECT * FROM referencing_period_test WHERE id = '[15,15]';
-- 
2.25.1

