diff --git a/.unreleased/pr_6989 b/.unreleased/pr_6989
new file mode 100644
index 00000000000..dc28514dde9
--- /dev/null
+++ b/.unreleased/pr_6989
@@ -0,0 +1 @@
+Implements: #6989 Add support for foreign keys from plain tables to hypertables
diff --git a/coccinelle/namedata.cocci b/coccinelle/namedata.cocci
index f3188ae525e..e1469be14c0 100644
--- a/coccinelle/namedata.cocci
+++ b/coccinelle/namedata.cocci
@@ -16,12 +16,13 @@ struct I1
 }
 
 @rule_namedata_strlcpy@
-expression E1, E2;
+identifier I1;
+expression E1;
 symbol NAMEDATALEN;
 @@
-- strlcpy(E1, E2, NAMEDATALEN);
+- strlcpy(I1, E1, NAMEDATALEN);
 + /* You are using strlcpy with NAMEDATALEN, please consider using NameData and namestrcpy instead. */
-+ namestrcpy(E1, E2);
++ namestrcpy(I1, E1);
 
 @rule_namedata_memcpy@
 expression E1, E2;
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 650780bab42..8c3d72e3e80 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -21,6 +21,7 @@ set(SOURCES
     extension.c
     extension_constants.c
     expression_utils.c
+    foreign_key.c
     gapfill.c
     guc.c
     histogram.c
diff --git a/src/chunk_constraint.c b/src/chunk_constraint.c
index d6d9219b086..87bbeb60058 100644
--- a/src/chunk_constraint.c
+++ b/src/chunk_constraint.c
@@ -33,6 +33,7 @@
 #include "dimension_vector.h"
 #include "errors.h"
 #include "export.h"
+#include "foreign_key.h"
 #include "hypercube.h"
 #include "hypertable.h"
 #include "partitioning.h"
@@ -512,6 +513,9 @@ ts_chunk_constraints_create(const Hypertable *ht, const Chunk *chunk)
 		Assert(list_length(cookedconstrs) == list_length(newconstrs));
 		CommandCounterIncrement();
 	}
+
+	/* Copy FK triggers to this chunk */
+	ts_chunk_copy_referencing_fk(ht, chunk);
 }
 
 ScanIterator
diff --git a/src/foreign_key.c b/src/foreign_key.c
new file mode 100644
index 00000000000..f7c4b6f862e
--- /dev/null
+++ b/src/foreign_key.c
@@ -0,0 +1,574 @@
+/*
+ * This file and its contents are licensed under the Apache License 2.0.
+ * Please see the included NOTICE for copyright information and
+ * LICENSE-APACHE for a copy of the license.
+ */
+
+/*
+ * The table referenced by a foreign constraint is supposed to have a
+ * constraint that prevents removing the referenced rows. The constraint
+ * is enforced by a pair of update and delete triggers. Normally this
+ * is done by the postgres addFkRecurseReferenced(), but it doesn't work
+ * for hypertables because they use inheritance, and that function only
+ * recurses into declarative partitioning hierarchy.
+ */
+
+#include <postgres.h>
+#include "access/attmap.h"
+#include "catalog/pg_trigger.h"
+#include "commands/trigger.h"
+#include "parser/parser.h"
+
+#include "compat/compat.h"
+#include "chunk.h"
+#include "foreign_key.h"
+#include "hypertable.h"
+
+static HeapTuple relation_get_fk_constraint(Oid conrelid, Oid confrelid);
+static List *relation_get_referencing_fk(Oid reloid);
+static Oid get_fk_index(Relation rel, int nkeys, AttrNumber *confkeys);
+static void constraint_get_trigger(Oid conoid, Oid *updtrigoid, Oid *deltrigoid);
+static char *ChooseForeignKeyConstraintNameAddition(int numkeys, AttrNumber *keys, Oid relid);
+static void createForeignKeyActionTriggers(Form_pg_constraint fk, Oid relid, Oid refRelOid,
+										   Oid constraintOid, Oid indexOid, Oid parentDelTrigger,
+										   Oid parentUpdTrigger);
+static void clone_constraint_on_chunk(const Chunk *chunk, Relation parentRel, Form_pg_constraint fk,
+									  int numfks, AttrNumber *conkey, AttrNumber *confkey,
+									  Oid *conpfeqop, Oid *conppeqop, Oid *conffeqop,
+#if PG15_GE
+									  int numfkdelsetcols, AttrNumber *confdelsetcols,
+#endif
+									  Oid parentDelTrigger, Oid parentUpdTrigger);
+
+/*
+ * Copy foreign key constraint fk_tuple to all chunks.
+ */
+static void
+propagate_fk(Relation ht_rel, HeapTuple fk_tuple, List *chunks)
+{
+	Form_pg_constraint fk = (Form_pg_constraint) GETSTRUCT(fk_tuple);
+
+	int numfks;
+	AttrNumber conkey[INDEX_MAX_KEYS];
+	AttrNumber confkey[INDEX_MAX_KEYS];
+	Oid conpfeqop[INDEX_MAX_KEYS];
+	Oid conppeqop[INDEX_MAX_KEYS];
+	Oid conffeqop[INDEX_MAX_KEYS];
+#if PG15_GE
+	int numfkdelsetcols;
+	AttrNumber confdelsetcols[INDEX_MAX_KEYS];
+#endif
+
+	DeconstructFkConstraintRow(fk_tuple,
+							   &numfks,
+							   conkey,
+							   confkey,
+							   conpfeqop,
+							   conppeqop,
+							   conffeqop
+#if PG15_GE
+							   ,
+							   &numfkdelsetcols,
+							   confdelsetcols
+#endif
+	);
+
+	Oid parentDelTrigger, parentUpdTrigger;
+	constraint_get_trigger(fk->oid, &parentUpdTrigger, &parentDelTrigger);
+
+	ListCell *lc;
+	foreach (lc, chunks)
+	{
+		Chunk *chunk = lfirst(lc);
+		clone_constraint_on_chunk(chunk,
+								  ht_rel,
+								  fk,
+								  numfks,
+								  conkey,
+								  confkey,
+								  conpfeqop,
+								  conppeqop,
+								  conffeqop,
+#if PG15_GE
+								  numfkdelsetcols,
+								  confdelsetcols,
+#endif
+								  parentDelTrigger,
+								  parentUpdTrigger);
+	}
+}
+
+/*
+ * Copy all foreign key constraints from the main table to a chunk.
+ */
+void
+ts_chunk_copy_referencing_fk(const Hypertable *ht, const Chunk *chunk)
+{
+	ListCell *lc;
+	List *chunks = list_make1((Chunk *) chunk);
+	List *fks = relation_get_referencing_fk(ht->main_table_relid);
+
+	Relation ht_rel = table_open(ht->main_table_relid, AccessShareLock);
+	foreach (lc, fks)
+	{
+		HeapTuple fk_tuple = lfirst(lc);
+		propagate_fk(ht_rel, fk_tuple, chunks);
+	}
+	table_close(ht_rel, NoLock);
+}
+
+/*
+ * Copy one foreign key constraint from the main table to all chunks.
+ */
+void
+ts_fk_propagate(Oid conrelid, Hypertable *ht)
+{
+	HeapTuple fk_tuple = relation_get_fk_constraint(conrelid, ht->main_table_relid);
+
+	if (!fk_tuple)
+		elog(ERROR, "foreign key constraint not found");
+
+	Relation ht_rel = table_open(ht->main_table_relid, AccessShareLock);
+	List *chunks = ts_chunk_get_by_hypertable_id(ht->fd.id);
+	propagate_fk(ht_rel, fk_tuple, chunks);
+	table_close(ht_rel, NoLock);
+}
+
+/*
+ * Clone a single constraint to a single chunk.
+ */
+static void
+clone_constraint_on_chunk(const Chunk *chunk, Relation parentRel, Form_pg_constraint fk, int numfks,
+						  AttrNumber *conkey, AttrNumber *confkey, Oid *conpfeqop, Oid *conppeqop,
+						  Oid *conffeqop,
+#if PG15_GE
+						  int numfkdelsetcols, AttrNumber *confdelsetcols,
+#endif
+						  Oid parentDelTrigger, Oid parentUpdTrigger)
+{
+	AttrNumber mapped_confkey[INDEX_MAX_KEYS];
+	Relation pkrel = table_open(chunk->table_id, AccessShareLock);
+
+	/* Map the foreign key columns on the hypertable side to the chunk columns */
+#if PG16_GE
+	AttrMap *attmap =
+		build_attrmap_by_name(RelationGetDescr(pkrel), RelationGetDescr(parentRel), false);
+#else
+	AttrMap *attmap = build_attrmap_by_name(RelationGetDescr(pkrel), RelationGetDescr(parentRel));
+#endif
+	for (int i = 0; i < numfks; i++)
+		mapped_confkey[i] = attmap->attnums[confkey[i] - 1];
+
+	Oid indexoid = get_fk_index(pkrel, numfks, mapped_confkey);
+	/* Since postgres accepted the constraint, there should be a supporting index. */
+	Ensure(OidIsValid(indexoid), "index for constraint not found on chunk");
+
+	table_close(pkrel, NoLock);
+
+	char *conname_addition =
+		ChooseForeignKeyConstraintNameAddition(numfks, confkey, parentRel->rd_id);
+	char *conname = ChooseConstraintName(get_rel_name(fk->conrelid),
+										 conname_addition,
+										 "fkey",
+										 fk->connamespace,
+										 NIL);
+	Oid conoid = CreateConstraintEntry(conname,
+									   fk->connamespace,
+									   CONSTRAINT_FOREIGN,
+									   fk->condeferrable,
+									   fk->condeferred,
+									   fk->convalidated,
+									   fk->oid,
+									   fk->conrelid,
+									   conkey,
+									   numfks,
+									   numfks,
+									   InvalidOid,
+									   indexoid,
+									   chunk->table_id,
+									   mapped_confkey,
+									   conpfeqop,
+									   conppeqop,
+									   conffeqop,
+									   numfks,
+									   fk->confupdtype,
+									   fk->confdeltype,
+#if PG15_GE
+									   confdelsetcols,
+									   numfkdelsetcols,
+#endif
+									   fk->confmatchtype,
+									   NULL,
+									   NULL,
+									   NULL,
+									   false,
+									   1,
+									   false,
+									   false);
+
+	ObjectAddress address, referenced;
+	ObjectAddressSet(address, ConstraintRelationId, conoid);
+	ObjectAddressSet(referenced, ConstraintRelationId, fk->oid);
+	recordDependencyOn(&address, &referenced, DEPENDENCY_INTERNAL);
+
+	CommandCounterIncrement();
+
+	createForeignKeyActionTriggers(fk,
+								   fk->conrelid,
+								   chunk->table_id,
+								   conoid,
+								   indexoid,
+								   parentDelTrigger,
+								   parentUpdTrigger);
+}
+
+/*
+ * Generate the column-name portion of the constraint name for a new foreign
+ * key given the list of column names that reference the referenced
+ * table.  This will be passed to ChooseConstraintName along with the parent
+ * table name and the "fkey" suffix.
+ *
+ * We know that less than NAMEDATALEN characters will actually be used, so we
+ * can truncate the result once we've generated that many.
+ *
+ * This function is based on a static function by the same name in tablecmds.c in PostgreSQL.
+ */
+static char *
+ChooseForeignKeyConstraintNameAddition(int numkeys, AttrNumber *keys, Oid relid)
+{
+	char buf[NAMEDATALEN * 2];
+	int buflen = 0;
+
+	buf[0] = '\0';
+	for (int i = 0; i < numkeys; i++)
+	{
+		char *name = get_attname(relid, keys[i], false);
+		if (buflen > 0)
+			buf[buflen++] = '_'; /* insert _ between names */
+
+		/*
+		 * At this point we have buflen <= NAMEDATALEN.  name should be less
+		 * than NAMEDATALEN already, but use strlcpy for paranoia.
+		 */
+		strlcpy(buf + buflen, name, NAMEDATALEN);
+		buflen += strlen(buf + buflen);
+		if (buflen >= NAMEDATALEN)
+			break;
+	}
+	return pstrdup(buf);
+}
+
+/*
+ * createForeignKeyActionTriggers
+ *		Create the referenced-side "action" triggers that implement a foreign
+ *		key.
+ * This function is based on a static function by the same name in tablecmds.c in PostgreSQL.
+ */
+static void
+createForeignKeyActionTriggers(Form_pg_constraint fk, Oid relid, Oid refRelOid, Oid constraintOid,
+							   Oid indexOid, Oid parentDelTrigger, Oid parentUpdTrigger)
+{
+	CreateTrigStmt *fk_trigger;
+
+	/*
+	 * Build and execute a CREATE CONSTRAINT TRIGGER statement for the ON
+	 * DELETE action on the referenced table.
+	 */
+	fk_trigger = makeNode(CreateTrigStmt);
+	fk_trigger->replace = false;
+	fk_trigger->isconstraint = true;
+	fk_trigger->trigname = "RI_ConstraintTrigger_a";
+	fk_trigger->relation = NULL;
+	fk_trigger->args = NIL;
+	fk_trigger->row = true;
+	fk_trigger->timing = TRIGGER_TYPE_AFTER;
+	fk_trigger->events = TRIGGER_TYPE_DELETE;
+	fk_trigger->columns = NIL;
+	fk_trigger->whenClause = NULL;
+	fk_trigger->transitionRels = NIL;
+	fk_trigger->constrrel = NULL;
+	switch (fk->confdeltype)
+	{
+		case FKCONSTR_ACTION_NOACTION:
+			fk_trigger->deferrable = fk->condeferrable;
+			fk_trigger->initdeferred = fk->condeferred;
+			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) fk->confdeltype);
+			break;
+	}
+
+	/*
+	 * clang will complain here about swapped arguments but this is intentional
+	 * as this is the reverse trigger from the referenced table back to the
+	 * referencing table. So we disable that specific warning for the next call.
+	 *
+	 * NOLINTBEGIN(readability-suspicious-call-argument)
+	 */
+	CreateTrigger(fk_trigger,
+				  NULL,
+				  refRelOid,
+				  relid,
+				  constraintOid,
+				  indexOid,
+				  InvalidOid,
+				  parentDelTrigger,
+				  NULL,
+				  true,
+				  false);
+	/* NOLINTEND(readability-suspicious-call-argument) */
+
+	/* Make changes-so-far visible */
+	CommandCounterIncrement();
+
+	/*
+	 * Build and execute a CREATE CONSTRAINT TRIGGER statement for the ON
+	 * UPDATE action on the referenced table.
+	 */
+	fk_trigger = makeNode(CreateTrigStmt);
+	fk_trigger->replace = false;
+	fk_trigger->isconstraint = true;
+	fk_trigger->trigname = "RI_ConstraintTrigger_a";
+	fk_trigger->relation = NULL;
+	fk_trigger->args = NIL;
+	fk_trigger->row = true;
+	fk_trigger->timing = TRIGGER_TYPE_AFTER;
+	fk_trigger->events = TRIGGER_TYPE_UPDATE;
+	fk_trigger->columns = NIL;
+	fk_trigger->whenClause = NULL;
+	fk_trigger->transitionRels = NIL;
+	fk_trigger->constrrel = NULL;
+	switch (fk->confupdtype)
+	{
+		case FKCONSTR_ACTION_NOACTION:
+			fk_trigger->deferrable = fk->condeferrable;
+			fk_trigger->initdeferred = fk->condeferred;
+			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) fk->confupdtype);
+			break;
+	}
+
+	/*
+	 * clang will complain here about swapped arguments but this is intentional
+	 * as this is the reverse trigger from the referenced table back to the
+	 * referencing table.
+	 *
+	 * NOLINTBEGIN(readability-suspicious-call-argument)
+	 */
+	CreateTrigger(fk_trigger,
+				  NULL,
+				  refRelOid,
+				  relid,
+				  constraintOid,
+				  indexOid,
+				  InvalidOid,
+				  parentUpdTrigger,
+				  NULL,
+				  true,
+				  false);
+	/* NOLINTEND(readability-suspicious-call-argument) */
+
+	/* Make changes-so-far visible */
+	CommandCounterIncrement();
+}
+
+/*
+ * Return a list of foreign key pg_constraint heap tuples referencing reloid.
+ */
+static List *
+relation_get_referencing_fk(Oid reloid)
+{
+	List *result = NIL;
+	Relation conrel;
+	SysScanDesc conscan;
+	ScanKeyData skey[2];
+	HeapTuple htup;
+
+	/* Prepare to scan pg_constraint for entries having confrelid = this rel. */
+	ScanKeyInit(&skey[0],
+				Anum_pg_constraint_confrelid,
+				BTEqualStrategyNumber,
+				F_OIDEQ,
+				ObjectIdGetDatum(reloid));
+
+	ScanKeyInit(&skey[1],
+				Anum_pg_constraint_contype,
+				BTEqualStrategyNumber,
+				F_CHAREQ,
+				CharGetDatum(CONSTRAINT_FOREIGN));
+
+	conrel = table_open(ConstraintRelationId, AccessShareLock);
+	conscan = systable_beginscan(conrel, InvalidOid, false, NULL, 2, skey);
+
+	while (HeapTupleIsValid(htup = systable_getnext(conscan)))
+	{
+		result = lappend(result, heap_copytuple(htup));
+	}
+
+	systable_endscan(conscan);
+	table_close(conrel, AccessShareLock);
+
+	return result;
+}
+
+/*
+ * Return a list of foreign key pg_constraint heap tuples referencing reloid.
+ */
+static HeapTuple
+relation_get_fk_constraint(Oid conrelid, Oid confrelid)
+{
+	Relation conrel;
+	SysScanDesc conscan;
+	ScanKeyData skey[3];
+	HeapTuple htup = NULL;
+
+	/* Prepare to scan pg_constraint for entries having confrelid = this rel. */
+	ScanKeyInit(&skey[0],
+				Anum_pg_constraint_conrelid,
+				BTEqualStrategyNumber,
+				F_OIDEQ,
+				ObjectIdGetDatum(conrelid));
+
+	ScanKeyInit(&skey[1],
+				Anum_pg_constraint_confrelid,
+				BTEqualStrategyNumber,
+				F_OIDEQ,
+				ObjectIdGetDatum(confrelid));
+
+	ScanKeyInit(&skey[2],
+				Anum_pg_constraint_contype,
+				BTEqualStrategyNumber,
+				F_CHAREQ,
+				CharGetDatum(CONSTRAINT_FOREIGN));
+
+	conrel = table_open(ConstraintRelationId, AccessShareLock);
+	conscan = systable_beginscan(conrel, InvalidOid, false, NULL, 3, skey);
+
+	if (HeapTupleIsValid(htup = systable_getnext(conscan)))
+	{
+		htup = heap_copytuple(htup);
+	}
+
+	systable_endscan(conscan);
+	table_close(conrel, AccessShareLock);
+
+	return htup;
+}
+
+/* Get the UPDATE and DELETE trigger OIDs for the given constraint OID */
+static void
+constraint_get_trigger(Oid conoid, Oid *updtrigoid, Oid *deltrigoid)
+{
+	Relation rel;
+	SysScanDesc scan;
+	ScanKeyData skey[1];
+	HeapTuple htup;
+
+	*updtrigoid = InvalidOid;
+	*deltrigoid = InvalidOid;
+
+	ScanKeyInit(&skey[0],
+				Anum_pg_trigger_tgconstraint,
+				BTEqualStrategyNumber,
+				F_OIDEQ,
+				ObjectIdGetDatum(conoid));
+
+	rel = table_open(TriggerRelationId, AccessShareLock);
+	scan = systable_beginscan(rel, TriggerConstraintIndexId, true, NULL, 1, skey);
+
+	while (HeapTupleIsValid(htup = systable_getnext(scan)))
+	{
+		Form_pg_trigger trigform = (Form_pg_trigger) GETSTRUCT(htup);
+
+		if ((trigform->tgtype & TRIGGER_TYPE_UPDATE) == TRIGGER_TYPE_UPDATE)
+			*updtrigoid = trigform->oid;
+		if ((trigform->tgtype & TRIGGER_TYPE_DELETE) == TRIGGER_TYPE_DELETE)
+			*deltrigoid = trigform->oid;
+	}
+
+	systable_endscan(scan);
+	table_close(rel, AccessShareLock);
+}
+
+/*
+ * Return the oid of the index supporting the foreign key constraint.
+ */
+static Oid
+get_fk_index(Relation rel, int nkeys, AttrNumber *confkeys)
+{
+	Oid indexoid = InvalidOid;
+	List *indexes = RelationGetIndexList(rel);
+	ListCell *lc;
+
+	foreach (lc, indexes)
+	{
+		Oid indexoid = lfirst_oid(lc);
+		Relation indexrel = index_open(indexoid, AccessShareLock);
+		bool match = true;
+
+		if (!indexrel->rd_index->indisunique || indexrel->rd_index->indnkeyatts != nkeys)
+		{
+			index_close(indexrel, AccessShareLock);
+			continue;
+		}
+
+		for (int i = 0; i < nkeys; i++)
+		{
+			if (indexrel->rd_index->indkey.values[i] != confkeys[i])
+			{
+				match = false;
+			}
+		}
+
+		index_close(indexrel, AccessShareLock);
+		if (match)
+		{
+			return indexoid;
+		}
+	}
+
+	return indexoid;
+}
diff --git a/src/foreign_key.h b/src/foreign_key.h
new file mode 100644
index 00000000000..e5d6cae1063
--- /dev/null
+++ b/src/foreign_key.h
@@ -0,0 +1,16 @@
+/*
+ * This file and its contents are licensed under the Apache License 2.0.
+ * Please see the included NOTICE for copyright information and
+ * LICENSE-APACHE for a copy of the license.
+ */
+
+#include <postgres.h>
+#include <catalog/pg_constraint.h>
+#include <nodes/parsenodes.h>
+
+#include "chunk.h"
+#include "export.h"
+#include "hypertable.h"
+
+extern TSDLLEXPORT void ts_fk_propagate(Oid conrelid, Hypertable *ht);
+extern TSDLLEXPORT void ts_chunk_copy_referencing_fk(const Hypertable *ht, const Chunk *chunk);
diff --git a/src/hypertable.c b/src/hypertable.c
index 7ba6667ef85..008092b76b6 100644
--- a/src/hypertable.c
+++ b/src/hypertable.c
@@ -1276,6 +1276,15 @@ hypertable_validate_constraints(Oid relid)
 	{
 		Form_pg_constraint form = (Form_pg_constraint) GETSTRUCT(tuple);
 
+		if (form->contype == CONSTRAINT_FOREIGN)
+		{
+			if (ts_hypertable_relid_to_id(form->confrelid) != -1)
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("hypertables cannot be used as foreign key references of "
+								"hypertables")));
+		}
+
 		if (form->contype == CONSTRAINT_CHECK && form->connoinherit)
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
diff --git a/src/planner/planner.c b/src/planner/planner.c
index 2ed58cfcaf6..f22537ebc8f 100644
--- a/src/planner/planner.c
+++ b/src/planner/planner.c
@@ -383,6 +383,71 @@ preprocess_query(Node *node, PreprocessQueryContext *context)
 		Index rti = 1;
 		bool ret;
 
+		/*
+		 * Detect FOREIGN KEY lookup queries and mark the RTE for expansion.
+		 * Unfortunately postgres will create lookup queries for foreign keys
+		 * with `ONLY` preventing hypertable expansion. Only for declarative
+		 * partitioned tables the queries will be created without `ONLY`.
+		 * We try to detect these queries here and undo the `ONLY` flag for
+		 * these specific queries.
+		 *
+		 * The implementation of this on the postgres side can be found in
+		 * src/backend/utils/adt/ri_triggers.c
+		 */
+
+		/*
+		 * RI_FKey_check
+		 *
+		 * The RI_FKey_check query string built is
+		 *  SELECT 1 FROM [ONLY] <pktable> x WHERE pkatt1 = $1 [AND ...]
+		 *       FOR KEY SHARE OF x
+		 */
+		if (query->commandType == CMD_SELECT && query->hasForUpdate &&
+			list_length(query->rtable) == 1 && context->root->glob->boundParams)
+		{
+			RangeTblEntry *rte = linitial_node(RangeTblEntry, query->rtable);
+			if (!rte->inh && rte->rtekind == RTE_RELATION && rte->rellockmode == RowShareLock &&
+				list_length(query->jointree->fromlist) == 1 && query->jointree->quals &&
+				strcmp(rte->eref->aliasname, "x") == 0)
+			{
+				Hypertable *ht =
+					ts_hypertable_cache_get_entry(hcache, rte->relid, CACHE_FLAG_MISSING_OK);
+				if (ht)
+				{
+					rte->inh = true;
+					if (TS_HYPERTABLE_HAS_COMPRESSION_ENABLED(ht))
+						query->rowMarks = NIL;
+				}
+			}
+		}
+		/*
+		 * RI_Initial_Check query
+		 *
+		 * The RI_Initial_Check query string built is:
+		 *  SELECT fk.keycols FROM [ONLY] relname fk
+		 *   LEFT OUTER JOIN [ONLY] pkrelname pk
+		 *   ON (pk.pkkeycol1=fk.keycol1 [AND ...])
+		 *   WHERE pk.pkkeycol1 IS NULL AND
+		 * For MATCH SIMPLE:
+		 *   (fk.keycol1 IS NOT NULL [AND ...])
+		 * For MATCH FULL:
+		 *   (fk.keycol1 IS NOT NULL [OR ...])
+		 */
+		if (query->commandType == CMD_SELECT && list_length(query->rtable) == 3)
+		{
+			RangeTblEntry *rte1 = linitial_node(RangeTblEntry, query->rtable);
+			RangeTblEntry *rte2 = lsecond_node(RangeTblEntry, query->rtable);
+			if (!rte1->inh && !rte2->inh && rte1->rtekind == RTE_RELATION &&
+				rte2->rtekind == RTE_RELATION && strcmp(rte1->eref->aliasname, "fk") == 0 &&
+				strcmp(rte2->eref->aliasname, "pk") == 0)
+			{
+				if (ts_hypertable_cache_get_entry(hcache, rte1->relid, CACHE_FLAG_MISSING_OK))
+					rte1->inh = true;
+				if (ts_hypertable_cache_get_entry(hcache, rte2->relid, CACHE_FLAG_MISSING_OK))
+					rte2->inh = true;
+			}
+		}
+
 		foreach (lc, query->rtable)
 		{
 			RangeTblEntry *rte = lfirst_node(RangeTblEntry, lc);
diff --git a/src/process_utility.c b/src/process_utility.c
index 72cabb29925..575bd177bc9 100644
--- a/src/process_utility.c
+++ b/src/process_utility.c
@@ -3,8 +3,8 @@
  * Please see the included NOTICE for copyright information and
  * LICENSE-APACHE for a copy of the license.
  */
-#include <postgres.h>
 
+#include <postgres.h>
 #include <access/htup_details.h>
 #include <access/xact.h>
 #include <catalog/index.h>
@@ -56,6 +56,7 @@
 #include "export.h"
 #include "extension.h"
 #include "extension_constants.h"
+#include "foreign_key.h"
 #include "hypercube.h"
 #include "hypertable.h"
 #include "hypertable_cache.h"
@@ -2247,35 +2248,6 @@ process_altertable_drop_column(Hypertable *ht, AlterTableCmd *cmd)
 	}
 }
 
-/* process all regular-table alter commands to make sure they aren't adding
- * foreign-key constraints to hypertables */
-static void
-verify_constraint_plaintable(RangeVar *relation, Constraint *constr)
-{
-	Cache *hcache;
-	Hypertable *ht;
-
-	Assert(IsA(constr, Constraint));
-
-	hcache = ts_hypertable_cache_pin();
-
-	switch (constr->contype)
-	{
-		case CONSTR_FOREIGN:
-			ht = ts_hypertable_cache_get_entry_rv(hcache, constr->pktable);
-
-			if (NULL != ht)
-				ereport(ERROR,
-						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-						 errmsg("foreign keys to hypertables are not supported")));
-			break;
-		default:
-			break;
-	}
-
-	ts_cache_release(hcache);
-}
-
 /*
  * Verify that a constraint is supported on a hypertable.
  */
@@ -2294,6 +2266,16 @@ verify_constraint_hypertable(Hypertable *ht, Node *constr_node)
 		keys = (contype == CONSTR_EXCLUSION) ? constr->exclusions : constr->keys;
 		indexname = constr->indexname;
 
+		if (contype == CONSTR_FOREIGN)
+		{
+			Oid confrelid = ts_hypertable_relid(constr->pktable);
+			if (OidIsValid(confrelid))
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("hypertables cannot be used as foreign key references of "
+								"hypertables")));
+		}
+
 		/* NO INHERIT constraints do not really make sense on a hypertable */
 		if (constr->is_no_inherit)
 			ereport(ERROR,
@@ -2345,9 +2327,7 @@ verify_constraint(RangeVar *relation, Constraint *constr)
 	Cache *hcache = ts_hypertable_cache_pin();
 	Hypertable *ht = ts_hypertable_cache_get_entry_rv(hcache, relation);
 
-	if (NULL == ht)
-		verify_constraint_plaintable(relation, constr);
-	else
+	if (ht)
 		verify_constraint_hypertable(ht, (Node *) constr);
 
 	ts_cache_release(hcache);
@@ -3019,9 +2999,6 @@ process_cluster_start(ProcessUtilityArgs *args)
 /*
  * Process create table statements.
  *
- * For regular tables, we need to ensure that they don't have any foreign key
- * constraints that point to hypertables.
- *
  * NOTE that this function should be called after parse analysis (in an end DDL
  * trigger or by running parse analysis manually).
  */
@@ -3368,10 +3345,7 @@ process_altertable_start_table(ProcessUtilityArgs *args)
 				col = (ColumnDef *) cmd->def;
 				if (ht && TS_HYPERTABLE_HAS_COMPRESSION_ENABLED(ht))
 					check_altertable_add_column_for_compressed(ht, col);
-				if (NULL == ht)
-					foreach (constraint_lc, col->constraints)
-						verify_constraint_plaintable(stmt->relation, lfirst(constraint_lc));
-				else
+				if (ht)
 					foreach (constraint_lc, col->constraints)
 						verify_constraint_hypertable(ht, lfirst(constraint_lc));
 				break;
@@ -3380,7 +3354,7 @@ process_altertable_start_table(ProcessUtilityArgs *args)
 #if PG16_LT
 			case AT_DropColumnRecurse:
 #endif
-				if (NULL != ht)
+				if (ht)
 					process_altertable_drop_column(ht, cmd);
 				break;
 			case AT_AddConstraint:
@@ -3389,15 +3363,13 @@ process_altertable_start_table(ProcessUtilityArgs *args)
 #endif
 				Assert(IsA(cmd->def, Constraint));
 
-				if (NULL == ht)
-					verify_constraint_plaintable(stmt->relation, (Constraint *) cmd->def);
-				else
+				if (ht)
 					verify_constraint_hypertable(ht, cmd->def);
 				break;
 			case AT_AlterColumnType:
 				Assert(IsA(cmd->def, ColumnDef));
 
-				if (ht != NULL)
+				if (ht)
 					process_alter_column_type_start(ht, cmd);
 				break;
 			case AT_AttachPartition:
@@ -3407,7 +3379,7 @@ process_altertable_start_table(ProcessUtilityArgs *args)
 
 				partstmt = (PartitionCmd *) cmd->def;
 				relation = partstmt->name;
-				Assert(NULL != relation);
+				Assert(relation);
 
 				if (OidIsValid(ts_hypertable_relid(relation)))
 				{
@@ -3875,7 +3847,7 @@ process_altertable_end_table(Node *parsetree, CollectedCommand *cmd)
 
 	ht = ts_hypertable_cache_get_cache_and_entry(relid, CACHE_FLAG_MISSING_OK, &hcache);
 
-	if (NULL != ht)
+	if (ht)
 	{
 		switch (cmd->type)
 		{
@@ -3889,6 +3861,40 @@ process_altertable_end_table(Node *parsetree, CollectedCommand *cmd)
 				break;
 		}
 	}
+
+	/*
+	 * Check any ALTER TABLE command is adding a FOREIGN KEY constraint
+	 * referencing a hypertable.
+	 */
+	if (cmd->type == SCT_AlterTable)
+	{
+		AlterTableStmt *stmt = castNode(AlterTableStmt, parsetree);
+		ListCell *lc;
+
+		foreach (lc, stmt->cmds)
+		{
+			AlterTableCmd *subcmd = (AlterTableCmd *) lfirst(lc);
+
+			if (subcmd->subtype != AT_AddConstraint ||
+				castNode(Constraint, subcmd->def)->contype != CONSTR_FOREIGN)
+				continue;
+
+			Constraint *c = castNode(Constraint, subcmd->def);
+			Oid confrelid = RangeVarGetRelid(c->pktable, AccessShareLock, true);
+			Hypertable *pk =
+				ts_hypertable_cache_get_entry(hcache, confrelid, CACHE_FLAG_MISSING_OK);
+			if (pk)
+			{
+				if (ht)
+					ereport(ERROR,
+							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+							 errmsg("hypertables cannot be used as foreign key references of "
+									"hypertables")));
+				ts_fk_propagate(relid, pk);
+			}
+		}
+	}
+
 	ts_cache_release(hcache);
 }
 
diff --git a/test/expected/constraint.out b/test/expected/constraint.out
index b22baab9360..ec6ecce7204 100644
--- a/test/expected/constraint.out
+++ b/test/expected/constraint.out
@@ -413,7 +413,7 @@ INSERT INTO hyper_fk(time, device_id,sensor_1) VALUES
 --delete should fail
 \set ON_ERROR_STOP 0
 DELETE FROM devices;
-ERROR:  update or delete on table "devices" violates foreign key constraint "8_19_hyper_fk_device_id_fkey" on table "_hyper_4_8_chunk"
+ERROR:  update or delete on table "devices" violates foreign key constraint "hyper_fk_device_id_fkey" on table "hyper_fk"
 \set ON_ERROR_STOP 1
 ALTER TABLE hyper_fk DROP CONSTRAINT hyper_fk_device_id_fkey;
 --should now be able to add non-fk rows
@@ -423,7 +423,7 @@ INSERT INTO hyper_fk(time, device_id,sensor_1) VALUES
 \set ON_ERROR_STOP 0
 ALTER TABLE hyper_fk ADD CONSTRAINT hyper_fk_device_id_fkey
 FOREIGN KEY (device_id) REFERENCES devices(device_id);
-ERROR:  insert or update on table "_hyper_4_8_chunk" violates foreign key constraint "8_21_hyper_fk_device_id_fkey"
+ERROR:  insert or update on table "hyper_fk" violates foreign key constraint "hyper_fk_device_id_fkey"
 \set ON_ERROR_STOP 1
 --but can add a NOT VALID one
 ALTER TABLE hyper_fk ADD CONSTRAINT hyper_fk_device_id_fkey
@@ -431,7 +431,7 @@ FOREIGN KEY (device_id) REFERENCES devices(device_id) NOT VALID;
 --which will fail when validated
 \set ON_ERROR_STOP 0
 ALTER TABLE hyper_fk VALIDATE CONSTRAINT hyper_fk_device_id_fkey;
-ERROR:  insert or update on table "_hyper_4_8_chunk" violates foreign key constraint "8_22_hyper_fk_device_id_fkey"
+ERROR:  insert or update on table "hyper_fk" violates foreign key constraint "hyper_fk_device_id_fkey"
 \set ON_ERROR_STOP 1
 ALTER TABLE hyper_fk DROP CONSTRAINT hyper_fk_device_id_fkey;
 DELETE FROM hyper_fk WHERE device_id = 'dev3';
@@ -440,13 +440,13 @@ FOREIGN KEY (device_id) REFERENCES devices(device_id);
 \set ON_ERROR_STOP 0
 INSERT INTO hyper_fk(time, device_id,sensor_1) VALUES
 (1257987700000000002, 'dev3', 11);
-ERROR:  insert or update on table "_hyper_4_8_chunk" violates foreign key constraint "8_23_hyper_fk_device_id_fkey"
+ERROR:  insert or update on table "_hyper_4_8_chunk" violates foreign key constraint "8_22_hyper_fk_device_id_fkey"
 \set ON_ERROR_STOP 1
 SELECT * FROM test.show_constraints('_timescaledb_internal._hyper_4_8_chunk');
           Constraint          | Type |   Columns   |                   Index                    |                                           Expr                                           | Deferrable | Deferred | Validated 
 ------------------------------+------+-------------+--------------------------------------------+------------------------------------------------------------------------------------------+------------+----------+-----------
  8_20_hyper_fk_pkey           | p    | {time}      | _timescaledb_internal."8_20_hyper_fk_pkey" |                                                                                          | f          | f        | t
- 8_23_hyper_fk_device_id_fkey | f    | {device_id} | devices_pkey                               |                                                                                          | f          | f        | t
+ 8_22_hyper_fk_device_id_fkey | f    | {device_id} | devices_pkey                               |                                                                                          | f          | f        | t
  constraint_8                 | c    | {time}      | -                                          | (("time" >= '1257987700000000000'::bigint) AND ("time" < '1257987700000000010'::bigint)) | f          | f        | t
  hyper_fk_sensor_1_check      | c    | {sensor_1}  | -                                          | (sensor_1 > (10)::numeric)                                                               | f          | f        | t
 (4 rows)
@@ -463,7 +463,7 @@ SELECT * FROM _timescaledb_catalog.chunk_constraint;
         6 |                    | 6_16_hyper_pk_pkey           | hyper_pk_pkey
         8 |                  8 | constraint_8                 | 
         8 |                    | 8_20_hyper_fk_pkey           | hyper_fk_pkey
-        8 |                    | 8_23_hyper_fk_device_id_fkey | hyper_fk_device_id_fkey
+        8 |                    | 8_22_hyper_fk_device_id_fkey | hyper_fk_device_id_fkey
 (10 rows)
 
 --test CASCADE drop behavior
@@ -513,7 +513,7 @@ BEGIN;
 (1 row)
 
 COMMIT;
-ERROR:  insert or update on table "_hyper_4_8_chunk" violates foreign key constraint "8_24_hyper_fk_device_id_fkey"
+ERROR:  insert or update on table "_hyper_4_8_chunk" violates foreign key constraint "8_23_hyper_fk_device_id_fkey"
 \set ON_ERROR_STOP 1
 ALTER TABLE hyper_fk ALTER CONSTRAINT hyper_fk_device_id_fkey NOT DEFERRABLE;
 \set ON_ERROR_STOP 0
@@ -521,7 +521,7 @@ BEGIN;
   --error detected right away
   INSERT INTO hyper_fk(time, device_id,sensor_1) VALUES
   (1257987700000000003, 'dev4', 11);
-ERROR:  insert or update on table "_hyper_4_8_chunk" violates foreign key constraint "8_24_hyper_fk_device_id_fkey"
+ERROR:  insert or update on table "_hyper_4_8_chunk" violates foreign key constraint "8_23_hyper_fk_device_id_fkey"
   SELECT 1;
 ERROR:  current transaction is aborted, commands ignored until end of transaction block
 COMMIT;
@@ -548,7 +548,6 @@ SELECT * FROM create_hypertable('hyper_for_ref', 'time', chunk_time_interval =>
 CREATE TABLE referrer (
     time BIGINT NOT NULL REFERENCES hyper_for_ref(time)
 );
-ERROR:  foreign keys to hypertables are not supported
 \set ON_ERROR_STOP 1
 CREATE TABLE referrer2 (
    time BIGINT NOT NULL
@@ -556,7 +555,6 @@ CREATE TABLE referrer2 (
 \set ON_ERROR_STOP 0
 ALTER TABLE referrer2 ADD CONSTRAINT hyper_fk_device_id_fkey
 FOREIGN KEY (time) REFERENCES  hyper_for_ref(time);
-ERROR:  foreign keys to hypertables are not supported
 \set ON_ERROR_STOP 1
 ----------------------- EXCLUSION CONSTRAINT  ------------------
 CREATE TABLE hyper_ex (
@@ -580,7 +578,7 @@ INSERT INTO hyper_ex(time, device_id,sensor_1) VALUES
 \set ON_ERROR_STOP 0
 INSERT INTO hyper_ex(time, device_id,sensor_1) VALUES
 (1257987700000000000, 'dev2', 12);
-ERROR:  conflicting key value violates exclusion constraint "9_26_hyper_ex_time_device_id_excl"
+ERROR:  conflicting key value violates exclusion constraint "9_25_hyper_ex_time_device_id_excl"
 \set ON_ERROR_STOP 1
 ALTER TABLE hyper_ex DROP CONSTRAINT hyper_ex_time_device_id_excl;
 --can now add
@@ -593,7 +591,7 @@ ALTER TABLE hyper_ex ADD CONSTRAINT hyper_ex_time_device_id_excl
         time WITH =, device_id WITH =
     ) WHERE (not canceled)
 ;
-ERROR:  could not create exclusion constraint "9_27_hyper_ex_time_device_id_excl"
+ERROR:  could not create exclusion constraint "9_26_hyper_ex_time_device_id_excl"
 \set ON_ERROR_STOP 1
 DELETE FROM hyper_ex WHERE sensor_1 = 12;
 ALTER TABLE hyper_ex ADD CONSTRAINT hyper_ex_time_device_id_excl
@@ -613,7 +611,7 @@ BEGIN;
 (1 row)
 
 COMMIT;
-ERROR:  conflicting key value violates exclusion constraint "9_28_hyper_ex_time_device_id_excl"
+ERROR:  conflicting key value violates exclusion constraint "9_27_hyper_ex_time_device_id_excl"
 \set ON_ERROR_STOP 1
 --cannot add exclusion constraint without partition key.
 CREATE TABLE hyper_ex_invalid (
@@ -684,7 +682,7 @@ BEGIN;
 (1 row)
 
 COMMIT;
-ERROR:  duplicate key value violates unique constraint "10_29_hyper_unique_deferred_time_key"
+ERROR:  duplicate key value violates unique constraint "10_28_hyper_unique_deferred_time_key"
 \set ON_ERROR_STOP 1
 --test deferred on create table
 CREATE TABLE hyper_pk_deferred (
@@ -710,7 +708,7 @@ BEGIN;
 (1 row)
 
 COMMIT;
-ERROR:  duplicate key value violates unique constraint "11_30_hyper_pk_deferred_pkey"
+ERROR:  duplicate key value violates unique constraint "11_29_hyper_pk_deferred_pkey"
 \set ON_ERROR_STOP 1
 --test that deferred works on create table too
 CREATE TABLE hyper_fk_deferred (
@@ -735,7 +733,7 @@ BEGIN;
 (1 row)
 
 COMMIT;
-ERROR:  insert or update on table "_hyper_11_12_chunk" violates foreign key constraint "12_31_hyper_fk_deferred_device_id_fkey"
+ERROR:  insert or update on table "_hyper_11_12_chunk" violates foreign key constraint "12_30_hyper_fk_deferred_device_id_fkey"
 \set ON_ERROR_STOP 1
 CREATE TABLE hyper_ex_deferred (
     time BIGINT,
@@ -765,7 +763,7 @@ BEGIN;
 (1 row)
 
 COMMIT;
-ERROR:  conflicting key value violates exclusion constraint "13_34_hyper_ex_deferred_time_device_id_excl"
+ERROR:  conflicting key value violates exclusion constraint "13_33_hyper_ex_deferred_time_device_id_excl"
 \set ON_ERROR_STOP 1
 -- Make sure renaming schemas won't break dropping constraints
 \c :TEST_DBNAME :ROLE_SUPERUSER
diff --git a/test/expected/create_table.out b/test/expected/create_table.out
index d828289caa2..8157265c4f3 100644
--- a/test/expected/create_table.out
+++ b/test/expected/create_table.out
@@ -14,7 +14,6 @@ SELECT create_hypertable('test_hyper_pk', 'time');
 \set ON_ERROR_STOP 0
 -- Foreign key constraints that reference hypertables are currently unsupported
 CREATE TABLE test_fk(time TIMESTAMPTZ REFERENCES test_hyper_pk(time));
-ERROR:  foreign keys to hypertables are not supported
 \set ON_ERROR_STOP 1
 CREATE TABLE test_delete(time timestamp with time zone PRIMARY KEY, temp float);
 SELECT create_hypertable('test_delete', 'time');
diff --git a/test/expected/rowsecurity-14.out b/test/expected/rowsecurity-14.out
index 3c95b64c662..9e4e4484fdf 100644
--- a/test/expected/rowsecurity-14.out
+++ b/test/expected/rowsecurity-14.out
@@ -542,8 +542,8 @@ SELECT * FROM document d FULL OUTER JOIN category c on d.cid = c.cid ORDER BY d.
 (6 rows)
 
 DELETE FROM category WHERE cid = 33;    -- fails with FK violation
-ERROR:  update or delete on table "category" violates foreign key constraint "4_7_document_cid_fkey" on table "_hyper_1_4_chunk"
-DETAIL:  Key is still referenced from table "_hyper_1_4_chunk".
+ERROR:  update or delete on table "category" violates foreign key constraint "document_cid_fkey" on table "document"
+DETAIL:  Key is still referenced from table "document".
 -- can insert FK referencing invisible PK
 SET SESSION AUTHORIZATION regress_rls_carol;
 SELECT * FROM document d FULL OUTER JOIN category c on d.cid = c.cid ORDER BY d.did, c.cid;
@@ -4715,8 +4715,8 @@ ALTER TABLE r2 ENABLE ROW LEVEL SECURITY;
 ALTER TABLE r2 FORCE ROW LEVEL SECURITY;
 -- Errors due to rows in r2
 DELETE FROM r1;
-ERROR:  update or delete on table "r1" violates foreign key constraint "113_23_r2_a_fkey" on table "_hyper_26_113_chunk"
-DETAIL:  Key (a)=(10) is still referenced from table "_hyper_26_113_chunk".
+ERROR:  update or delete on table "r1" violates foreign key constraint "r2_a_fkey" on table "r2"
+DETAIL:  Key (a)=(10) is still referenced from table "r2".
 -- Reset r2 to no-RLS
 DROP POLICY p1 ON r2;
 ALTER TABLE r2 NO FORCE ROW LEVEL SECURITY;
diff --git a/test/expected/rowsecurity-15.out b/test/expected/rowsecurity-15.out
index e2ce06ccb2a..ad13d7b7412 100644
--- a/test/expected/rowsecurity-15.out
+++ b/test/expected/rowsecurity-15.out
@@ -542,8 +542,8 @@ SELECT * FROM document d FULL OUTER JOIN category c on d.cid = c.cid ORDER BY d.
 (6 rows)
 
 DELETE FROM category WHERE cid = 33;    -- fails with FK violation
-ERROR:  update or delete on table "category" violates foreign key constraint "4_7_document_cid_fkey" on table "_hyper_1_4_chunk"
-DETAIL:  Key is still referenced from table "_hyper_1_4_chunk".
+ERROR:  update or delete on table "category" violates foreign key constraint "document_cid_fkey" on table "document"
+DETAIL:  Key is still referenced from table "document".
 -- can insert FK referencing invisible PK
 SET SESSION AUTHORIZATION regress_rls_carol;
 SELECT * FROM document d FULL OUTER JOIN category c on d.cid = c.cid ORDER BY d.did, c.cid;
@@ -4717,8 +4717,8 @@ ALTER TABLE r2 ENABLE ROW LEVEL SECURITY;
 ALTER TABLE r2 FORCE ROW LEVEL SECURITY;
 -- Errors due to rows in r2
 DELETE FROM r1;
-ERROR:  update or delete on table "r1" violates foreign key constraint "113_23_r2_a_fkey" on table "_hyper_26_113_chunk"
-DETAIL:  Key (a)=(10) is still referenced from table "_hyper_26_113_chunk".
+ERROR:  update or delete on table "r1" violates foreign key constraint "r2_a_fkey" on table "r2"
+DETAIL:  Key (a)=(10) is still referenced from table "r2".
 -- Reset r2 to no-RLS
 DROP POLICY p1 ON r2;
 ALTER TABLE r2 NO FORCE ROW LEVEL SECURITY;
diff --git a/test/expected/rowsecurity-16.out b/test/expected/rowsecurity-16.out
index e2ce06ccb2a..ad13d7b7412 100644
--- a/test/expected/rowsecurity-16.out
+++ b/test/expected/rowsecurity-16.out
@@ -542,8 +542,8 @@ SELECT * FROM document d FULL OUTER JOIN category c on d.cid = c.cid ORDER BY d.
 (6 rows)
 
 DELETE FROM category WHERE cid = 33;    -- fails with FK violation
-ERROR:  update or delete on table "category" violates foreign key constraint "4_7_document_cid_fkey" on table "_hyper_1_4_chunk"
-DETAIL:  Key is still referenced from table "_hyper_1_4_chunk".
+ERROR:  update or delete on table "category" violates foreign key constraint "document_cid_fkey" on table "document"
+DETAIL:  Key is still referenced from table "document".
 -- can insert FK referencing invisible PK
 SET SESSION AUTHORIZATION regress_rls_carol;
 SELECT * FROM document d FULL OUTER JOIN category c on d.cid = c.cid ORDER BY d.did, c.cid;
@@ -4717,8 +4717,8 @@ ALTER TABLE r2 ENABLE ROW LEVEL SECURITY;
 ALTER TABLE r2 FORCE ROW LEVEL SECURITY;
 -- Errors due to rows in r2
 DELETE FROM r1;
-ERROR:  update or delete on table "r1" violates foreign key constraint "113_23_r2_a_fkey" on table "_hyper_26_113_chunk"
-DETAIL:  Key (a)=(10) is still referenced from table "_hyper_26_113_chunk".
+ERROR:  update or delete on table "r1" violates foreign key constraint "r2_a_fkey" on table "r2"
+DETAIL:  Key (a)=(10) is still referenced from table "r2".
 -- Reset r2 to no-RLS
 DROP POLICY p1 ON r2;
 ALTER TABLE r2 NO FORCE ROW LEVEL SECURITY;
diff --git a/tsl/src/nodes/decompress_chunk/decompress_chunk.c b/tsl/src/nodes/decompress_chunk/decompress_chunk.c
index 94e5f79b92d..4edda1e0dfe 100644
--- a/tsl/src/nodes/decompress_chunk/decompress_chunk.c
+++ b/tsl/src/nodes/decompress_chunk/decompress_chunk.c
@@ -1682,7 +1682,7 @@ decompress_chunk_add_plannerinfo(PlannerInfo *root, CompressionInfo *info, Chunk
 
 	expand_planner_arrays(root, 1);
 	info->compressed_rte =
-		decompress_chunk_make_rte(compressed_reloid, AccessShareLock, root->parse);
+		decompress_chunk_make_rte(compressed_reloid, info->chunk_rte->rellockmode, root->parse);
 	root->simple_rte_array[compressed_index] = info->compressed_rte;
 
 	root->parse->rtable = lappend(root->parse->rtable, info->compressed_rte);
diff --git a/tsl/src/planner.c b/tsl/src/planner.c
index 91062604d6d..798529486b3 100644
--- a/tsl/src/planner.c
+++ b/tsl/src/planner.c
@@ -91,15 +91,7 @@ tsl_set_rel_pathlist_query(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeT
 			 * 2) if it is not a SELECT QUERY.
 			 * Caching is done by our hypertable expansion, which doesn't run in
 			 * these cases.
-			 *
-			 * Also on PG13 when a DELETE query runs through SPI, its command
-			 * type is CMD_SELECT. Apparently it goes into inheritance_planner,
-			 * which uses a hack to pretend it's actually a SELECT query, but
-			 * for some reason for non-SPI queries the query type is still
-			 * correct. You can observe it in the continuous_aggs-13 test.
-			 * Just ignore this assertion on 13 and look up the chunk.
 			 */
-			Assert(rel->reloptkind == RELOPT_BASEREL || root->parse->commandType != CMD_SELECT);
 			fdw_private->cached_chunk_struct =
 				ts_chunk_get_by_relid(rte->relid, /* fail_if_not_found = */ true);
 		}
diff --git a/tsl/test/expected/foreign_keys.out b/tsl/test/expected/foreign_keys.out
new file mode 100644
index 00000000000..416d287a94e
--- /dev/null
+++ b/tsl/test/expected/foreign_keys.out
@@ -0,0 +1,582 @@
+-- This file and its contents are licensed under the Timescale License.
+-- Please see the included NOTICE for copyright information and
+-- LICENSE-TIMESCALE for a copy of the license.
+-- test single column fk constraint from plain table to hypertable during hypertable creation
+CREATE TABLE metrics(time timestamptz primary key, device text, value float);
+SELECT table_name FROM create_hypertable('metrics', 'time');
+ table_name 
+------------
+ metrics
+(1 row)
+
+INSERT INTO metrics(time, device, value) VALUES ('2020-01-01 0:00:00', 'd1', 1.0);
+INSERT INTO metrics(time, device, value) VALUES ('2020-01-01 1:00:00', 'd1', 1.0);
+CREATE TABLE event(time timestamptz references metrics(time), info text);
+-- should fail
+\set ON_ERROR_STOP 0
+INSERT INTO event(time, info) VALUES ('2020-01-02', 'info1');
+ERROR:  insert or update on table "event" violates foreign key constraint "event_time_fkey"
+\set ON_ERROR_STOP 1
+-- should succeed
+INSERT INTO event(time, info) VALUES ('2020-01-01', 'info2');
+\set ON_ERROR_STOP 0
+-- should fail
+UPDATE event SET time = '2020-01-01 00:30:00' WHERE time = '2020-01-01';
+ERROR:  insert or update on table "event" violates foreign key constraint "event_time_fkey"
+\set ON_ERROR_STOP 1
+-- should succeed
+BEGIN;
+UPDATE event SET time = '2020-01-01 01:00:00' WHERE time = '2020-01-01';
+ROLLBACK;
+\set ON_ERROR_STOP 0
+-- should fail
+DELETE FROM metrics WHERE time = '2020-01-01';
+ERROR:  update or delete on table "_hyper_1_1_chunk" violates foreign key constraint "event_time_fkey1" on table "event"
+-- should fail
+UPDATE metrics SET time = '2020-01-01 00:30:00' WHERE time = '2020-01-01';
+ERROR:  update or delete on table "_hyper_1_1_chunk" violates foreign key constraint "event_time_fkey1" on table "event"
+\set ON_ERROR_STOP 1
+SELECT conname, conrelid::regclass, confrelid::regclass, conparentid <> 0 AS parent FROM pg_constraint WHERE conrelid='event'::regclass ORDER BY oid;
+     conname      | conrelid |               confrelid                | parent 
+------------------+----------+----------------------------------------+--------
+ event_time_fkey  | event    | metrics                                | f
+ event_time_fkey1 | event    | _timescaledb_internal._hyper_1_1_chunk | t
+(2 rows)
+
+SELECT tgfoid::regproc, tgparentid <> 0 AS parent, tgisinternal, tgconstrrelid::regclass FROM pg_trigger WHERE tgconstrrelid='event'::regclass ORDER BY oid;
+         tgfoid         | parent | tgisinternal | tgconstrrelid 
+------------------------+--------+--------------+---------------
+ "RI_FKey_noaction_del" | f      | t            | event
+ "RI_FKey_noaction_upd" | f      | t            | event
+ "RI_FKey_noaction_del" | t      | t            | event
+ "RI_FKey_noaction_upd" | t      | t            | event
+(4 rows)
+
+-- create new chunk and repeat the test
+INSERT INTO metrics(time, device, value) VALUES ('2021-01-01', 'd1', 1.0);
+-- should fail
+\set ON_ERROR_STOP 0
+INSERT INTO event(time, info) VALUES ('2021-01-02', 'info1');
+ERROR:  insert or update on table "event" violates foreign key constraint "event_time_fkey"
+\set ON_ERROR_STOP 1
+-- should succeed
+INSERT INTO event(time, info) VALUES ('2021-01-01', 'info2');
+SELECT conname, conrelid::regclass, confrelid::regclass, conparentid <> 0 AS parent FROM pg_constraint WHERE conrelid='event'::regclass ORDER BY oid;
+     conname      | conrelid |               confrelid                | parent 
+------------------+----------+----------------------------------------+--------
+ event_time_fkey  | event    | metrics                                | f
+ event_time_fkey1 | event    | _timescaledb_internal._hyper_1_1_chunk | t
+ event_time_fkey2 | event    | _timescaledb_internal._hyper_1_2_chunk | t
+(3 rows)
+
+SELECT tgfoid::regproc, tgparentid <> 0 AS parent, tgisinternal, tgconstrrelid::regclass FROM pg_trigger WHERE tgconstrrelid='event'::regclass ORDER BY oid;
+         tgfoid         | parent | tgisinternal | tgconstrrelid 
+------------------------+--------+--------------+---------------
+ "RI_FKey_noaction_del" | f      | t            | event
+ "RI_FKey_noaction_upd" | f      | t            | event
+ "RI_FKey_noaction_del" | t      | t            | event
+ "RI_FKey_noaction_upd" | t      | t            | event
+ "RI_FKey_noaction_del" | t      | t            | event
+ "RI_FKey_noaction_upd" | t      | t            | event
+(6 rows)
+
+-- chunks referenced in fk constraints must not be dropped or truncated
+\set ON_ERROR_STOP 0
+TRUNCATE metrics;
+ERROR:  cannot truncate a table referenced in a foreign key constraint
+TRUNCATE _timescaledb_internal._hyper_1_1_chunk;
+ERROR:  cannot truncate a table referenced in a foreign key constraint
+TRUNCATE _timescaledb_internal._hyper_1_2_chunk;
+ERROR:  cannot truncate a table referenced in a foreign key constraint
+DROP TABLE _timescaledb_internal._hyper_1_1_chunk;
+ERROR:  cannot drop table _timescaledb_internal._hyper_1_1_chunk because other objects depend on it
+DROP TABLE _timescaledb_internal._hyper_1_2_chunk;
+ERROR:  cannot drop table _timescaledb_internal._hyper_1_2_chunk because other objects depend on it
+SELECT drop_chunks('metrics', '1 month'::interval);
+ERROR:  cannot drop constraint 1_1_metrics_pkey on table _timescaledb_internal._hyper_1_1_chunk because other objects depend on it
+\set ON_ERROR_STOP 1
+-- after removing constraint dropping should succeed
+ALTER TABLE event DROP CONSTRAINT event_time_fkey;
+SELECT drop_chunks('metrics', '1 month'::interval);
+              drop_chunks               
+----------------------------------------
+ _timescaledb_internal._hyper_1_1_chunk
+ _timescaledb_internal._hyper_1_2_chunk
+(2 rows)
+
+DROP TABLE event;
+DROP TABLE metrics;
+-- test single column fk constraint from plain table to hypertable during hypertable creation with RESTRICT
+CREATE TABLE metrics(time timestamptz primary key, device text, value float);
+SELECT table_name FROM create_hypertable('metrics', 'time');
+ table_name 
+------------
+ metrics
+(1 row)
+
+INSERT INTO metrics(time, device, value) VALUES ('2020-01-01 0:00:00', 'd1', 1.0);
+INSERT INTO metrics(time, device, value) VALUES ('2020-01-01 1:00:00', 'd1', 1.0);
+CREATE TABLE event(time timestamptz references metrics(time) ON DELETE RESTRICT ON UPDATE RESTRICT, info text);
+-- should fail
+\set ON_ERROR_STOP 0
+INSERT INTO event(time, info) VALUES ('2020-01-02', 'info1');
+ERROR:  insert or update on table "event" violates foreign key constraint "event_time_fkey"
+\set ON_ERROR_STOP 1
+-- should succeed
+INSERT INTO event(time, info) VALUES ('2020-01-01', 'info2');
+\set ON_ERROR_STOP 0
+-- should fail
+UPDATE event SET time = '2020-01-01 00:30:00' WHERE time = '2020-01-01';
+ERROR:  insert or update on table "event" violates foreign key constraint "event_time_fkey"
+\set ON_ERROR_STOP 1
+-- should succeed
+BEGIN;
+UPDATE event SET time = '2020-01-01 01:00:00' WHERE time = '2020-01-01';
+ROLLBACK;
+\set ON_ERROR_STOP 0
+-- should fail
+DELETE FROM metrics WHERE time = '2020-01-01';
+ERROR:  update or delete on table "_hyper_2_3_chunk" violates foreign key constraint "event_time_fkey1" on table "event"
+-- should fail
+UPDATE metrics SET time = '2020-01-01 00:30:00' WHERE time = '2020-01-01';
+ERROR:  update or delete on table "_hyper_2_3_chunk" violates foreign key constraint "event_time_fkey1" on table "event"
+\set ON_ERROR_STOP 1
+SELECT conname, conrelid::regclass, confrelid::regclass, conparentid <> 0 AS parent FROM pg_constraint WHERE conrelid='event'::regclass ORDER BY oid;
+     conname      | conrelid |               confrelid                | parent 
+------------------+----------+----------------------------------------+--------
+ event_time_fkey  | event    | metrics                                | f
+ event_time_fkey1 | event    | _timescaledb_internal._hyper_2_3_chunk | t
+(2 rows)
+
+SELECT tgfoid::regproc, tgparentid <> 0 AS parent, tgisinternal, tgconstrrelid::regclass FROM pg_trigger WHERE tgconstrrelid='event'::regclass ORDER BY oid;
+         tgfoid         | parent | tgisinternal | tgconstrrelid 
+------------------------+--------+--------------+---------------
+ "RI_FKey_restrict_del" | f      | t            | event
+ "RI_FKey_restrict_upd" | f      | t            | event
+ "RI_FKey_restrict_del" | t      | t            | event
+ "RI_FKey_restrict_upd" | t      | t            | event
+(4 rows)
+
+-- create new chunk and repeat the test
+INSERT INTO metrics(time, device, value) VALUES ('2021-01-01', 'd1', 1.0);
+-- should fail
+\set ON_ERROR_STOP 0
+INSERT INTO event(time, info) VALUES ('2021-01-02', 'info1');
+ERROR:  insert or update on table "event" violates foreign key constraint "event_time_fkey"
+\set ON_ERROR_STOP 1
+-- should succeed
+INSERT INTO event(time, info) VALUES ('2021-01-01', 'info2');
+SELECT conname, conrelid::regclass, confrelid::regclass, conparentid <> 0 AS parent FROM pg_constraint WHERE conrelid='event'::regclass ORDER BY oid;
+     conname      | conrelid |               confrelid                | parent 
+------------------+----------+----------------------------------------+--------
+ event_time_fkey  | event    | metrics                                | f
+ event_time_fkey1 | event    | _timescaledb_internal._hyper_2_3_chunk | t
+ event_time_fkey2 | event    | _timescaledb_internal._hyper_2_4_chunk | t
+(3 rows)
+
+SELECT tgfoid::regproc, tgparentid <> 0 AS parent, tgisinternal, tgconstrrelid::regclass FROM pg_trigger WHERE tgconstrrelid='event'::regclass ORDER BY oid;
+         tgfoid         | parent | tgisinternal | tgconstrrelid 
+------------------------+--------+--------------+---------------
+ "RI_FKey_restrict_del" | f      | t            | event
+ "RI_FKey_restrict_upd" | f      | t            | event
+ "RI_FKey_restrict_del" | t      | t            | event
+ "RI_FKey_restrict_upd" | t      | t            | event
+ "RI_FKey_restrict_del" | t      | t            | event
+ "RI_FKey_restrict_upd" | t      | t            | event
+(6 rows)
+
+DROP TABLE event;
+DROP TABLE metrics;
+-- test single column fk constraint from plain table to hypertable during hypertable creation with CASCADE
+CREATE TABLE metrics(time timestamptz primary key, device text, value float);
+SELECT table_name FROM create_hypertable('metrics', 'time');
+ table_name 
+------------
+ metrics
+(1 row)
+
+INSERT INTO metrics(time, device, value) VALUES ('2020-01-01 0:00:00', 'd1', 1.0);
+INSERT INTO metrics(time, device, value) VALUES ('2020-01-01 1:00:00', 'd1', 1.0);
+CREATE TABLE event(time timestamptz references metrics(time) ON DELETE CASCADE ON UPDATE CASCADE, info text);
+-- should fail
+\set ON_ERROR_STOP 0
+INSERT INTO event(time, info) VALUES ('2020-01-02', 'info1');
+ERROR:  insert or update on table "event" violates foreign key constraint "event_time_fkey"
+\set ON_ERROR_STOP 1
+-- should succeed
+INSERT INTO event(time, info) VALUES ('2020-01-01', 'info2');
+\set ON_ERROR_STOP 0
+-- should fail
+UPDATE event SET time = '2020-01-01 00:30:00' WHERE time = '2020-01-01';
+ERROR:  insert or update on table "event" violates foreign key constraint "event_time_fkey"
+\set ON_ERROR_STOP 1
+-- should succeed
+BEGIN;
+UPDATE event SET time = '2020-01-01 01:00:00' WHERE time = '2020-01-01';
+ROLLBACK;
+-- should cascade
+BEGIN;
+DELETE FROM metrics WHERE time = '2020-01-01';
+SELECT * FROM event ORDER BY event;
+ time | info 
+------+------
+(0 rows)
+
+ROLLBACK;
+-- should cascade
+BEGIN;
+UPDATE metrics SET time = '2020-01-01 00:30:00' WHERE time = '2020-01-01';
+SELECT * FROM event ORDER BY event;
+             time             | info  
+------------------------------+-------
+ Wed Jan 01 00:30:00 2020 PST | info2
+(1 row)
+
+ROLLBACK;
+SELECT conname, conrelid::regclass, confrelid::regclass, conparentid <> 0 AS parent FROM pg_constraint WHERE conrelid='event'::regclass ORDER BY oid;
+     conname      | conrelid |               confrelid                | parent 
+------------------+----------+----------------------------------------+--------
+ event_time_fkey  | event    | metrics                                | f
+ event_time_fkey1 | event    | _timescaledb_internal._hyper_3_5_chunk | t
+(2 rows)
+
+SELECT tgfoid::regproc, tgparentid <> 0 AS parent, tgisinternal, tgconstrrelid::regclass FROM pg_trigger WHERE tgconstrrelid='event'::regclass ORDER BY oid;
+        tgfoid         | parent | tgisinternal | tgconstrrelid 
+-----------------------+--------+--------------+---------------
+ "RI_FKey_cascade_del" | f      | t            | event
+ "RI_FKey_cascade_upd" | f      | t            | event
+ "RI_FKey_cascade_del" | t      | t            | event
+ "RI_FKey_cascade_upd" | t      | t            | event
+(4 rows)
+
+DROP TABLE event;
+DROP TABLE metrics;
+-- test single column fk constraint from plain table to hypertable during hypertable creation with SET NULL
+CREATE TABLE metrics(time timestamptz primary key, device text, value float);
+SELECT table_name FROM create_hypertable('metrics', 'time');
+ table_name 
+------------
+ metrics
+(1 row)
+
+INSERT INTO metrics(time, device, value) VALUES ('2020-01-01 0:00:00', 'd1', 1.0);
+INSERT INTO metrics(time, device, value) VALUES ('2020-01-01 1:00:00', 'd1', 1.0);
+CREATE TABLE event(time timestamptz references metrics(time) ON DELETE SET NULL ON UPDATE SET NULL, info text);
+-- should fail
+\set ON_ERROR_STOP 0
+INSERT INTO event(time, info) VALUES ('2020-01-02', 'info1');
+ERROR:  insert or update on table "event" violates foreign key constraint "event_time_fkey"
+\set ON_ERROR_STOP 1
+-- should succeed
+INSERT INTO event(time, info) VALUES ('2020-01-01', 'info2');
+\set ON_ERROR_STOP 0
+-- should fail
+UPDATE event SET time = '2020-01-01 00:30:00' WHERE time = '2020-01-01';
+ERROR:  insert or update on table "event" violates foreign key constraint "event_time_fkey"
+\set ON_ERROR_STOP 1
+-- should succeed
+BEGIN;
+UPDATE event SET time = '2020-01-01 01:00:00' WHERE time = '2020-01-01';
+ROLLBACK;
+-- should cascade
+BEGIN;
+DELETE FROM metrics WHERE time = '2020-01-01';
+SELECT * FROM event ORDER BY event;
+ time | info  
+------+-------
+      | info2
+(1 row)
+
+ROLLBACK;
+-- should cascade
+BEGIN;
+UPDATE metrics SET time = '2020-01-01 00:30:00' WHERE time = '2020-01-01';
+SELECT * FROM event ORDER BY event;
+ time | info  
+------+-------
+      | info2
+(1 row)
+
+ROLLBACK;
+SELECT conname, conrelid::regclass, confrelid::regclass, conparentid <> 0 AS parent FROM pg_constraint WHERE conrelid='event'::regclass ORDER BY oid;
+     conname      | conrelid |               confrelid                | parent 
+------------------+----------+----------------------------------------+--------
+ event_time_fkey  | event    | metrics                                | f
+ event_time_fkey1 | event    | _timescaledb_internal._hyper_4_6_chunk | t
+(2 rows)
+
+SELECT tgfoid::regproc, tgparentid <> 0 AS parent, tgisinternal, tgconstrrelid::regclass FROM pg_trigger WHERE tgconstrrelid='event'::regclass ORDER BY oid;
+        tgfoid         | parent | tgisinternal | tgconstrrelid 
+-----------------------+--------+--------------+---------------
+ "RI_FKey_setnull_del" | f      | t            | event
+ "RI_FKey_setnull_upd" | f      | t            | event
+ "RI_FKey_setnull_del" | t      | t            | event
+ "RI_FKey_setnull_upd" | t      | t            | event
+(4 rows)
+
+DROP TABLE event;
+DROP TABLE metrics;
+-- test single column fk constraint from plain table to hypertable during hypertable creation with SET DEFAULT
+CREATE TABLE metrics(time timestamptz primary key, device text, value float);
+SELECT table_name FROM create_hypertable('metrics', 'time');
+ table_name 
+------------
+ metrics
+(1 row)
+
+INSERT INTO metrics(time, device, value) VALUES ('2020-01-01 0:00:00', 'd1', 1.0);
+INSERT INTO metrics(time, device, value) VALUES ('2020-01-01 1:00:00', 'd1', 1.0);
+CREATE TABLE event(time timestamptz default null references metrics(time) ON DELETE SET DEFAULT ON UPDATE SET DEFAULT, info text);
+-- should fail
+\set ON_ERROR_STOP 0
+INSERT INTO event(time, info) VALUES ('2020-01-02', 'info1');
+ERROR:  insert or update on table "event" violates foreign key constraint "event_time_fkey"
+\set ON_ERROR_STOP 1
+-- should succeed
+INSERT INTO event(time, info) VALUES ('2020-01-01', 'info2');
+\set ON_ERROR_STOP 0
+-- should fail
+UPDATE event SET time = '2020-01-01 00:30:00' WHERE time = '2020-01-01';
+ERROR:  insert or update on table "event" violates foreign key constraint "event_time_fkey"
+\set ON_ERROR_STOP 1
+-- should succeed
+BEGIN;
+UPDATE event SET time = '2020-01-01 01:00:00' WHERE time = '2020-01-01';
+ROLLBACK;
+-- should cascade
+BEGIN;
+DELETE FROM metrics WHERE time = '2020-01-01';
+SELECT * FROM event ORDER BY event;
+ time | info  
+------+-------
+      | info2
+(1 row)
+
+ROLLBACK;
+-- should cascade
+BEGIN;
+UPDATE metrics SET time = '2020-01-01 00:30:00' WHERE time = '2020-01-01';
+SELECT * FROM event ORDER BY event;
+ time | info  
+------+-------
+      | info2
+(1 row)
+
+ROLLBACK;
+SELECT conname, conrelid::regclass, confrelid::regclass, conparentid <> 0 AS parent FROM pg_constraint WHERE conrelid='event'::regclass ORDER BY oid;
+     conname      | conrelid |               confrelid                | parent 
+------------------+----------+----------------------------------------+--------
+ event_time_fkey  | event    | metrics                                | f
+ event_time_fkey1 | event    | _timescaledb_internal._hyper_5_7_chunk | t
+(2 rows)
+
+SELECT tgfoid::regproc, tgparentid <> 0 AS parent, tgisinternal, tgconstrrelid::regclass FROM pg_trigger WHERE tgconstrrelid='event'::regclass ORDER BY oid;
+          tgfoid          | parent | tgisinternal | tgconstrrelid 
+--------------------------+--------+--------------+---------------
+ "RI_FKey_setdefault_del" | f      | t            | event
+ "RI_FKey_setdefault_upd" | f      | t            | event
+ "RI_FKey_setdefault_del" | t      | t            | event
+ "RI_FKey_setdefault_upd" | t      | t            | event
+(4 rows)
+
+DROP TABLE event;
+DROP TABLE metrics;
+-- test single column fk constraint from plain table to hypertable with constraint being added separately
+CREATE TABLE metrics(time timestamptz primary key, device text, value float);
+SELECT table_name FROM create_hypertable('metrics', 'time');
+ table_name 
+------------
+ metrics
+(1 row)
+
+INSERT INTO metrics(time, device, value) VALUES ('2020-01-01', 'd1', 1.0);
+CREATE TABLE event(time timestamptz, info text);
+ALTER TABLE event ADD CONSTRAINT event_time_fkey FOREIGN KEY (time) REFERENCES metrics(time) ON DELETE RESTRICT;
+-- should fail
+\set ON_ERROR_STOP 0
+INSERT INTO event(time, info) VALUES ('2020-01-02', 'info1');
+ERROR:  insert or update on table "event" violates foreign key constraint "event_time_fkey"
+\set ON_ERROR_STOP 1
+-- should succeed
+INSERT INTO event(time, info) VALUES ('2020-01-01', 'info2');
+-- should fail
+\set ON_ERROR_STOP 0
+DELETE FROM metrics WHERE time = '2020-01-01';
+ERROR:  update or delete on table "_hyper_6_8_chunk" violates foreign key constraint "event_time_fkey1" on table "event"
+\set ON_ERROR_STOP 1
+SELECT conname, conrelid::regclass, confrelid::regclass, conparentid <> 0 AS parent FROM pg_constraint WHERE conrelid='event'::regclass ORDER BY oid;
+     conname      | conrelid |               confrelid                | parent 
+------------------+----------+----------------------------------------+--------
+ event_time_fkey  | event    | metrics                                | f
+ event_time_fkey1 | event    | _timescaledb_internal._hyper_6_8_chunk | t
+(2 rows)
+
+SELECT tgfoid::regproc, tgparentid <> 0 AS parent, tgisinternal, tgconstrrelid::regclass FROM pg_trigger WHERE tgconstrrelid='event'::regclass ORDER BY oid;
+         tgfoid         | parent | tgisinternal | tgconstrrelid 
+------------------------+--------+--------------+---------------
+ "RI_FKey_restrict_del" | f      | t            | event
+ "RI_FKey_noaction_upd" | f      | t            | event
+ "RI_FKey_restrict_del" | t      | t            | event
+ "RI_FKey_noaction_upd" | t      | t            | event
+(4 rows)
+
+DROP TABLE event;
+DROP TABLE metrics;
+-- test multi column fk constraint from plain table to hypertable
+CREATE TABLE metrics(time timestamptz , device text, value float, primary key (time, device));
+SELECT table_name FROM create_hypertable('metrics', 'time');
+ table_name 
+------------
+ metrics
+(1 row)
+
+INSERT INTO metrics(time, device, value) VALUES ('2020-01-01', 'd1', 1.0);
+CREATE TABLE event(time timestamptz, device text, info text);
+ALTER TABLE event ADD CONSTRAINT event_time_fkey FOREIGN KEY (time,device) REFERENCES metrics(time,device) ON DELETE RESTRICT;
+-- should fail
+\set ON_ERROR_STOP 0
+INSERT INTO event(time, device, info) VALUES ('2020-01-02', 'd1', 'info1');
+ERROR:  insert or update on table "event" violates foreign key constraint "event_time_fkey"
+INSERT INTO event(time, device, info) VALUES ('2020-01-01', 'd2', 'info2');
+ERROR:  insert or update on table "event" violates foreign key constraint "event_time_fkey"
+\set ON_ERROR_STOP 1
+-- should succeed
+INSERT INTO event(time, device, info) VALUES ('2020-01-01', 'd1', 'info2');
+-- should fail
+\set ON_ERROR_STOP 0
+DELETE FROM metrics WHERE time = '2020-01-01';
+ERROR:  update or delete on table "_hyper_7_9_chunk" violates foreign key constraint "event_time_device_fkey" on table "event"
+DELETE FROM metrics WHERE device = 'd1';
+ERROR:  update or delete on table "_hyper_7_9_chunk" violates foreign key constraint "event_time_device_fkey" on table "event"
+\set ON_ERROR_STOP 1
+DROP TABLE event;
+DROP TABLE metrics;
+-- test single column fk constraint from plain table to hypertable with constraint being added separately while data is present
+CREATE TABLE metrics(time timestamptz primary key, device text, value float);
+SELECT table_name FROM create_hypertable('metrics', 'time');
+ table_name 
+------------
+ metrics
+(1 row)
+
+INSERT INTO metrics(time, device, value) VALUES ('2020-01-01', 'd1', 1.0);
+CREATE TABLE event(time timestamptz, info text);
+INSERT INTO event(time, info) VALUES ('2020-01-01', 'info2');
+INSERT INTO event(time, info) VALUES ('2020-02-01', 'info1');
+-- should fail
+\set ON_ERROR_STOP 0
+ALTER TABLE event ADD CONSTRAINT event_time_fkey FOREIGN KEY (time) REFERENCES metrics(time) ON DELETE SET NULL;
+ERROR:  insert or update on table "event" violates foreign key constraint "event_time_fkey"
+\set ON_ERROR_STOP 1
+INSERT INTO metrics(time, device, value) VALUES ('2020-02-01', 'd1', 1.0);
+ALTER TABLE event ADD CONSTRAINT event_time_fkey FOREIGN KEY (time) REFERENCES metrics(time) ON DELETE CASCADE;
+-- delete should cascade
+DELETE FROM metrics WHERE time = '2020-01-01';
+SELECT * FROM event;
+             time             | info  
+------------------------------+-------
+ Sat Feb 01 00:00:00 2020 PST | info1
+(1 row)
+
+DROP TABLE event;
+DROP TABLE metrics;
+-- test single column fk constraint from plain table to compressed hypertable
+CREATE TABLE metrics(time timestamptz primary key, device text, value float);
+SELECT table_name FROM create_hypertable('metrics', 'time');
+ table_name 
+------------
+ metrics
+(1 row)
+
+INSERT INTO metrics(time, device, value) VALUES ('2020-01-01 0:00:00', 'd1', 1.0);
+INSERT INTO metrics(time, device, value) VALUES ('2020-01-01 1:00:00', 'd1', 1.0);
+ALTER TABLE metrics SET(timescaledb.compress, timescaledb.compress_segmentby='device');
+NOTICE:  default order by for hypertable "metrics" is set to ""time" DESC"
+SELECT count(compress_chunk(ch)) FROM show_chunks('metrics') ch;
+ count 
+-------
+     1
+(1 row)
+
+CREATE TABLE event(time timestamptz references metrics(time) ON DELETE CASCADE ON UPDATE CASCADE, info text);
+-- should fail
+\set ON_ERROR_STOP 0
+INSERT INTO event(time, info) VALUES ('2020-01-02', 'info1');
+ERROR:  insert or update on table "event" violates foreign key constraint "event_time_fkey"
+\set ON_ERROR_STOP 1
+-- should succeed
+INSERT INTO event(time, info) VALUES ('2020-01-01', 'info2');
+\set ON_ERROR_STOP 0
+-- should fail
+UPDATE event SET time = '2020-01-01 00:30:00' WHERE time = '2020-01-01';
+ERROR:  insert or update on table "event" violates foreign key constraint "event_time_fkey"
+\set ON_ERROR_STOP 1
+-- should succeed
+BEGIN;
+UPDATE event SET time = '2020-01-01 01:00:00' WHERE time = '2020-01-01';
+ROLLBACK;
+-- should cascade
+BEGIN;
+DELETE FROM metrics WHERE time = '2020-01-01';
+SELECT * FROM event ORDER BY event;
+ time | info 
+------+------
+(0 rows)
+
+ROLLBACK;
+-- should cascade
+BEGIN;
+UPDATE metrics SET time = '2020-01-01 00:30:00' WHERE time = '2020-01-01';
+SELECT * FROM event ORDER BY event;
+             time             | info  
+------------------------------+-------
+ Wed Jan 01 00:30:00 2020 PST | info2
+(1 row)
+
+ROLLBACK;
+SELECT conname, conrelid::regclass, confrelid::regclass, conparentid <> 0 AS parent FROM pg_constraint WHERE conrelid='event'::regclass ORDER BY oid;
+     conname      | conrelid |                confrelid                | parent 
+------------------+----------+-----------------------------------------+--------
+ event_time_fkey  | event    | metrics                                 | f
+ event_time_fkey1 | event    | _timescaledb_internal._hyper_9_12_chunk | t
+(2 rows)
+
+SELECT tgfoid::regproc, tgparentid <> 0 AS parent, tgisinternal, tgconstrrelid::regclass FROM pg_trigger WHERE tgconstrrelid='event'::regclass ORDER BY oid;
+        tgfoid         | parent | tgisinternal | tgconstrrelid 
+-----------------------+--------+--------------+---------------
+ "RI_FKey_cascade_del" | f      | t            | event
+ "RI_FKey_cascade_upd" | f      | t            | event
+ "RI_FKey_cascade_del" | t      | t            | event
+ "RI_FKey_cascade_upd" | t      | t            | event
+(4 rows)
+
+DROP TABLE event;
+DROP TABLE metrics;
+-- test single column fk constraint from hypertable to hypertable
+CREATE TABLE metrics(time timestamptz primary key, device text, value float);
+SELECT table_name FROM create_hypertable('metrics', 'time');
+ table_name 
+------------
+ metrics
+(1 row)
+
+CREATE TABLE event(time timestamptz, info text);
+SELECT table_name FROM create_hypertable('event', 'time');
+NOTICE:  adding not-null constraint to column "time"
+ table_name 
+------------
+ event
+(1 row)
+
+\set ON_ERROR_STOP 0
+ALTER TABLE event ADD CONSTRAINT event_time_fkey FOREIGN KEY (time) REFERENCES metrics(time);
+ERROR:  hypertables cannot be used as foreign key references of hypertables
+\set ON_ERROR_STOP 1
+CREATE TABLE event2(time timestamptz REFERENCES metrics(time), info text);
+\set ON_ERROR_STOP 0
+SELECT table_name FROM create_hypertable('event2', 'time');
+ERROR:  hypertables cannot be used as foreign key references of hypertables
+\set ON_ERROR_STOP 1
+DROP TABLE event;
+DROP TABLE event2;
+DROP TABLE metrics;
diff --git a/tsl/test/sql/CMakeLists.txt b/tsl/test/sql/CMakeLists.txt
index 3320af7fddd..2841c98ff10 100644
--- a/tsl/test/sql/CMakeLists.txt
+++ b/tsl/test/sql/CMakeLists.txt
@@ -31,6 +31,7 @@ set(TEST_FILES
     compression_sorted_merge_distinct.sql
     compression_sorted_merge_columns.sql
     decompress_index.sql
+    foreign_keys.sql
     move.sql
     partialize_finalize.sql
     policy_generalization.sql
diff --git a/tsl/test/sql/foreign_keys.sql b/tsl/test/sql/foreign_keys.sql
new file mode 100644
index 00000000000..ba760972e8d
--- /dev/null
+++ b/tsl/test/sql/foreign_keys.sql
@@ -0,0 +1,397 @@
+-- This file and its contents are licensed under the Timescale License.
+-- Please see the included NOTICE for copyright information and
+-- LICENSE-TIMESCALE for a copy of the license.
+
+-- test single column fk constraint from plain table to hypertable during hypertable creation
+CREATE TABLE metrics(time timestamptz primary key, device text, value float);
+SELECT table_name FROM create_hypertable('metrics', 'time');
+
+INSERT INTO metrics(time, device, value) VALUES ('2020-01-01 0:00:00', 'd1', 1.0);
+INSERT INTO metrics(time, device, value) VALUES ('2020-01-01 1:00:00', 'd1', 1.0);
+
+CREATE TABLE event(time timestamptz references metrics(time), info text);
+
+-- should fail
+\set ON_ERROR_STOP 0
+INSERT INTO event(time, info) VALUES ('2020-01-02', 'info1');
+\set ON_ERROR_STOP 1
+-- should succeed
+INSERT INTO event(time, info) VALUES ('2020-01-01', 'info2');
+
+\set ON_ERROR_STOP 0
+-- should fail
+UPDATE event SET time = '2020-01-01 00:30:00' WHERE time = '2020-01-01';
+\set ON_ERROR_STOP 1
+
+-- should succeed
+BEGIN;
+UPDATE event SET time = '2020-01-01 01:00:00' WHERE time = '2020-01-01';
+ROLLBACK;
+
+\set ON_ERROR_STOP 0
+-- should fail
+DELETE FROM metrics WHERE time = '2020-01-01';
+-- should fail
+UPDATE metrics SET time = '2020-01-01 00:30:00' WHERE time = '2020-01-01';
+\set ON_ERROR_STOP 1
+
+SELECT conname, conrelid::regclass, confrelid::regclass, conparentid <> 0 AS parent FROM pg_constraint WHERE conrelid='event'::regclass ORDER BY oid;
+SELECT tgfoid::regproc, tgparentid <> 0 AS parent, tgisinternal, tgconstrrelid::regclass FROM pg_trigger WHERE tgconstrrelid='event'::regclass ORDER BY oid;
+
+-- create new chunk and repeat the test
+INSERT INTO metrics(time, device, value) VALUES ('2021-01-01', 'd1', 1.0);
+
+-- should fail
+\set ON_ERROR_STOP 0
+INSERT INTO event(time, info) VALUES ('2021-01-02', 'info1');
+\set ON_ERROR_STOP 1
+-- should succeed
+INSERT INTO event(time, info) VALUES ('2021-01-01', 'info2');
+
+SELECT conname, conrelid::regclass, confrelid::regclass, conparentid <> 0 AS parent FROM pg_constraint WHERE conrelid='event'::regclass ORDER BY oid;
+SELECT tgfoid::regproc, tgparentid <> 0 AS parent, tgisinternal, tgconstrrelid::regclass FROM pg_trigger WHERE tgconstrrelid='event'::regclass ORDER BY oid;
+
+-- chunks referenced in fk constraints must not be dropped or truncated
+\set ON_ERROR_STOP 0
+TRUNCATE metrics;
+TRUNCATE _timescaledb_internal._hyper_1_1_chunk;
+TRUNCATE _timescaledb_internal._hyper_1_2_chunk;
+DROP TABLE _timescaledb_internal._hyper_1_1_chunk;
+DROP TABLE _timescaledb_internal._hyper_1_2_chunk;
+SELECT drop_chunks('metrics', '1 month'::interval);
+\set ON_ERROR_STOP 1
+
+-- after removing constraint dropping should succeed
+ALTER TABLE event DROP CONSTRAINT event_time_fkey;
+SELECT drop_chunks('metrics', '1 month'::interval);
+
+DROP TABLE event;
+DROP TABLE metrics;
+
+-- test single column fk constraint from plain table to hypertable during hypertable creation with RESTRICT
+CREATE TABLE metrics(time timestamptz primary key, device text, value float);
+SELECT table_name FROM create_hypertable('metrics', 'time');
+
+INSERT INTO metrics(time, device, value) VALUES ('2020-01-01 0:00:00', 'd1', 1.0);
+INSERT INTO metrics(time, device, value) VALUES ('2020-01-01 1:00:00', 'd1', 1.0);
+
+CREATE TABLE event(time timestamptz references metrics(time) ON DELETE RESTRICT ON UPDATE RESTRICT, info text);
+
+-- should fail
+\set ON_ERROR_STOP 0
+INSERT INTO event(time, info) VALUES ('2020-01-02', 'info1');
+\set ON_ERROR_STOP 1
+-- should succeed
+INSERT INTO event(time, info) VALUES ('2020-01-01', 'info2');
+
+\set ON_ERROR_STOP 0
+-- should fail
+UPDATE event SET time = '2020-01-01 00:30:00' WHERE time = '2020-01-01';
+\set ON_ERROR_STOP 1
+
+-- should succeed
+BEGIN;
+UPDATE event SET time = '2020-01-01 01:00:00' WHERE time = '2020-01-01';
+ROLLBACK;
+
+\set ON_ERROR_STOP 0
+-- should fail
+DELETE FROM metrics WHERE time = '2020-01-01';
+-- should fail
+UPDATE metrics SET time = '2020-01-01 00:30:00' WHERE time = '2020-01-01';
+\set ON_ERROR_STOP 1
+
+SELECT conname, conrelid::regclass, confrelid::regclass, conparentid <> 0 AS parent FROM pg_constraint WHERE conrelid='event'::regclass ORDER BY oid;
+SELECT tgfoid::regproc, tgparentid <> 0 AS parent, tgisinternal, tgconstrrelid::regclass FROM pg_trigger WHERE tgconstrrelid='event'::regclass ORDER BY oid;
+
+-- create new chunk and repeat the test
+INSERT INTO metrics(time, device, value) VALUES ('2021-01-01', 'd1', 1.0);
+
+-- should fail
+\set ON_ERROR_STOP 0
+INSERT INTO event(time, info) VALUES ('2021-01-02', 'info1');
+\set ON_ERROR_STOP 1
+-- should succeed
+INSERT INTO event(time, info) VALUES ('2021-01-01', 'info2');
+
+SELECT conname, conrelid::regclass, confrelid::regclass, conparentid <> 0 AS parent FROM pg_constraint WHERE conrelid='event'::regclass ORDER BY oid;
+SELECT tgfoid::regproc, tgparentid <> 0 AS parent, tgisinternal, tgconstrrelid::regclass FROM pg_trigger WHERE tgconstrrelid='event'::regclass ORDER BY oid;
+
+DROP TABLE event;
+DROP TABLE metrics;
+
+-- test single column fk constraint from plain table to hypertable during hypertable creation with CASCADE
+CREATE TABLE metrics(time timestamptz primary key, device text, value float);
+SELECT table_name FROM create_hypertable('metrics', 'time');
+
+INSERT INTO metrics(time, device, value) VALUES ('2020-01-01 0:00:00', 'd1', 1.0);
+INSERT INTO metrics(time, device, value) VALUES ('2020-01-01 1:00:00', 'd1', 1.0);
+
+CREATE TABLE event(time timestamptz references metrics(time) ON DELETE CASCADE ON UPDATE CASCADE, info text);
+
+-- should fail
+\set ON_ERROR_STOP 0
+INSERT INTO event(time, info) VALUES ('2020-01-02', 'info1');
+\set ON_ERROR_STOP 1
+-- should succeed
+INSERT INTO event(time, info) VALUES ('2020-01-01', 'info2');
+
+\set ON_ERROR_STOP 0
+-- should fail
+UPDATE event SET time = '2020-01-01 00:30:00' WHERE time = '2020-01-01';
+\set ON_ERROR_STOP 1
+
+-- should succeed
+BEGIN;
+UPDATE event SET time = '2020-01-01 01:00:00' WHERE time = '2020-01-01';
+ROLLBACK;
+
+-- should cascade
+BEGIN;
+DELETE FROM metrics WHERE time = '2020-01-01';
+SELECT * FROM event ORDER BY event;
+ROLLBACK;
+-- should cascade
+BEGIN;
+UPDATE metrics SET time = '2020-01-01 00:30:00' WHERE time = '2020-01-01';
+SELECT * FROM event ORDER BY event;
+ROLLBACK;
+
+SELECT conname, conrelid::regclass, confrelid::regclass, conparentid <> 0 AS parent FROM pg_constraint WHERE conrelid='event'::regclass ORDER BY oid;
+SELECT tgfoid::regproc, tgparentid <> 0 AS parent, tgisinternal, tgconstrrelid::regclass FROM pg_trigger WHERE tgconstrrelid='event'::regclass ORDER BY oid;
+
+DROP TABLE event;
+DROP TABLE metrics;
+
+-- test single column fk constraint from plain table to hypertable during hypertable creation with SET NULL
+CREATE TABLE metrics(time timestamptz primary key, device text, value float);
+SELECT table_name FROM create_hypertable('metrics', 'time');
+
+INSERT INTO metrics(time, device, value) VALUES ('2020-01-01 0:00:00', 'd1', 1.0);
+INSERT INTO metrics(time, device, value) VALUES ('2020-01-01 1:00:00', 'd1', 1.0);
+
+CREATE TABLE event(time timestamptz references metrics(time) ON DELETE SET NULL ON UPDATE SET NULL, info text);
+
+-- should fail
+\set ON_ERROR_STOP 0
+INSERT INTO event(time, info) VALUES ('2020-01-02', 'info1');
+\set ON_ERROR_STOP 1
+-- should succeed
+INSERT INTO event(time, info) VALUES ('2020-01-01', 'info2');
+
+\set ON_ERROR_STOP 0
+-- should fail
+UPDATE event SET time = '2020-01-01 00:30:00' WHERE time = '2020-01-01';
+\set ON_ERROR_STOP 1
+
+-- should succeed
+BEGIN;
+UPDATE event SET time = '2020-01-01 01:00:00' WHERE time = '2020-01-01';
+ROLLBACK;
+
+-- should cascade
+BEGIN;
+DELETE FROM metrics WHERE time = '2020-01-01';
+SELECT * FROM event ORDER BY event;
+ROLLBACK;
+-- should cascade
+BEGIN;
+UPDATE metrics SET time = '2020-01-01 00:30:00' WHERE time = '2020-01-01';
+SELECT * FROM event ORDER BY event;
+ROLLBACK;
+
+SELECT conname, conrelid::regclass, confrelid::regclass, conparentid <> 0 AS parent FROM pg_constraint WHERE conrelid='event'::regclass ORDER BY oid;
+SELECT tgfoid::regproc, tgparentid <> 0 AS parent, tgisinternal, tgconstrrelid::regclass FROM pg_trigger WHERE tgconstrrelid='event'::regclass ORDER BY oid;
+
+DROP TABLE event;
+DROP TABLE metrics;
+
+-- test single column fk constraint from plain table to hypertable during hypertable creation with SET DEFAULT
+CREATE TABLE metrics(time timestamptz primary key, device text, value float);
+SELECT table_name FROM create_hypertable('metrics', 'time');
+
+INSERT INTO metrics(time, device, value) VALUES ('2020-01-01 0:00:00', 'd1', 1.0);
+INSERT INTO metrics(time, device, value) VALUES ('2020-01-01 1:00:00', 'd1', 1.0);
+
+CREATE TABLE event(time timestamptz default null references metrics(time) ON DELETE SET DEFAULT ON UPDATE SET DEFAULT, info text);
+
+-- should fail
+\set ON_ERROR_STOP 0
+INSERT INTO event(time, info) VALUES ('2020-01-02', 'info1');
+\set ON_ERROR_STOP 1
+-- should succeed
+INSERT INTO event(time, info) VALUES ('2020-01-01', 'info2');
+
+\set ON_ERROR_STOP 0
+-- should fail
+UPDATE event SET time = '2020-01-01 00:30:00' WHERE time = '2020-01-01';
+\set ON_ERROR_STOP 1
+
+-- should succeed
+BEGIN;
+UPDATE event SET time = '2020-01-01 01:00:00' WHERE time = '2020-01-01';
+ROLLBACK;
+
+-- should cascade
+BEGIN;
+DELETE FROM metrics WHERE time = '2020-01-01';
+SELECT * FROM event ORDER BY event;
+ROLLBACK;
+-- should cascade
+BEGIN;
+UPDATE metrics SET time = '2020-01-01 00:30:00' WHERE time = '2020-01-01';
+SELECT * FROM event ORDER BY event;
+ROLLBACK;
+
+SELECT conname, conrelid::regclass, confrelid::regclass, conparentid <> 0 AS parent FROM pg_constraint WHERE conrelid='event'::regclass ORDER BY oid;
+SELECT tgfoid::regproc, tgparentid <> 0 AS parent, tgisinternal, tgconstrrelid::regclass FROM pg_trigger WHERE tgconstrrelid='event'::regclass ORDER BY oid;
+
+DROP TABLE event;
+DROP TABLE metrics;
+
+-- test single column fk constraint from plain table to hypertable with constraint being added separately
+CREATE TABLE metrics(time timestamptz primary key, device text, value float);
+SELECT table_name FROM create_hypertable('metrics', 'time');
+
+INSERT INTO metrics(time, device, value) VALUES ('2020-01-01', 'd1', 1.0);
+
+CREATE TABLE event(time timestamptz, info text);
+
+ALTER TABLE event ADD CONSTRAINT event_time_fkey FOREIGN KEY (time) REFERENCES metrics(time) ON DELETE RESTRICT;
+
+-- should fail
+\set ON_ERROR_STOP 0
+INSERT INTO event(time, info) VALUES ('2020-01-02', 'info1');
+\set ON_ERROR_STOP 1
+-- should succeed
+INSERT INTO event(time, info) VALUES ('2020-01-01', 'info2');
+
+-- should fail
+\set ON_ERROR_STOP 0
+DELETE FROM metrics WHERE time = '2020-01-01';
+\set ON_ERROR_STOP 1
+
+SELECT conname, conrelid::regclass, confrelid::regclass, conparentid <> 0 AS parent FROM pg_constraint WHERE conrelid='event'::regclass ORDER BY oid;
+SELECT tgfoid::regproc, tgparentid <> 0 AS parent, tgisinternal, tgconstrrelid::regclass FROM pg_trigger WHERE tgconstrrelid='event'::regclass ORDER BY oid;
+
+DROP TABLE event;
+DROP TABLE metrics;
+
+-- test multi column fk constraint from plain table to hypertable
+CREATE TABLE metrics(time timestamptz , device text, value float, primary key (time, device));
+SELECT table_name FROM create_hypertable('metrics', 'time');
+
+INSERT INTO metrics(time, device, value) VALUES ('2020-01-01', 'd1', 1.0);
+
+CREATE TABLE event(time timestamptz, device text, info text);
+
+ALTER TABLE event ADD CONSTRAINT event_time_fkey FOREIGN KEY (time,device) REFERENCES metrics(time,device) ON DELETE RESTRICT;
+
+-- should fail
+\set ON_ERROR_STOP 0
+INSERT INTO event(time, device, info) VALUES ('2020-01-02', 'd1', 'info1');
+INSERT INTO event(time, device, info) VALUES ('2020-01-01', 'd2', 'info2');
+\set ON_ERROR_STOP 1
+-- should succeed
+INSERT INTO event(time, device, info) VALUES ('2020-01-01', 'd1', 'info2');
+
+-- should fail
+\set ON_ERROR_STOP 0
+DELETE FROM metrics WHERE time = '2020-01-01';
+DELETE FROM metrics WHERE device = 'd1';
+\set ON_ERROR_STOP 1
+
+DROP TABLE event;
+DROP TABLE metrics;
+
+-- test single column fk constraint from plain table to hypertable with constraint being added separately while data is present
+CREATE TABLE metrics(time timestamptz primary key, device text, value float);
+SELECT table_name FROM create_hypertable('metrics', 'time');
+
+INSERT INTO metrics(time, device, value) VALUES ('2020-01-01', 'd1', 1.0);
+
+CREATE TABLE event(time timestamptz, info text);
+INSERT INTO event(time, info) VALUES ('2020-01-01', 'info2');
+INSERT INTO event(time, info) VALUES ('2020-02-01', 'info1');
+
+-- should fail
+\set ON_ERROR_STOP 0
+ALTER TABLE event ADD CONSTRAINT event_time_fkey FOREIGN KEY (time) REFERENCES metrics(time) ON DELETE SET NULL;
+\set ON_ERROR_STOP 1
+
+INSERT INTO metrics(time, device, value) VALUES ('2020-02-01', 'd1', 1.0);
+ALTER TABLE event ADD CONSTRAINT event_time_fkey FOREIGN KEY (time) REFERENCES metrics(time) ON DELETE CASCADE;
+
+-- delete should cascade
+DELETE FROM metrics WHERE time = '2020-01-01';
+SELECT * FROM event;
+
+DROP TABLE event;
+DROP TABLE metrics;
+
+-- test single column fk constraint from plain table to compressed hypertable
+CREATE TABLE metrics(time timestamptz primary key, device text, value float);
+SELECT table_name FROM create_hypertable('metrics', 'time');
+
+INSERT INTO metrics(time, device, value) VALUES ('2020-01-01 0:00:00', 'd1', 1.0);
+INSERT INTO metrics(time, device, value) VALUES ('2020-01-01 1:00:00', 'd1', 1.0);
+
+ALTER TABLE metrics SET(timescaledb.compress, timescaledb.compress_segmentby='device');
+
+SELECT count(compress_chunk(ch)) FROM show_chunks('metrics') ch;
+
+CREATE TABLE event(time timestamptz references metrics(time) ON DELETE CASCADE ON UPDATE CASCADE, info text);
+
+-- should fail
+\set ON_ERROR_STOP 0
+INSERT INTO event(time, info) VALUES ('2020-01-02', 'info1');
+\set ON_ERROR_STOP 1
+-- should succeed
+INSERT INTO event(time, info) VALUES ('2020-01-01', 'info2');
+
+\set ON_ERROR_STOP 0
+-- should fail
+UPDATE event SET time = '2020-01-01 00:30:00' WHERE time = '2020-01-01';
+\set ON_ERROR_STOP 1
+
+-- should succeed
+BEGIN;
+UPDATE event SET time = '2020-01-01 01:00:00' WHERE time = '2020-01-01';
+ROLLBACK;
+
+-- should cascade
+BEGIN;
+DELETE FROM metrics WHERE time = '2020-01-01';
+SELECT * FROM event ORDER BY event;
+ROLLBACK;
+-- should cascade
+BEGIN;
+UPDATE metrics SET time = '2020-01-01 00:30:00' WHERE time = '2020-01-01';
+SELECT * FROM event ORDER BY event;
+ROLLBACK;
+
+SELECT conname, conrelid::regclass, confrelid::regclass, conparentid <> 0 AS parent FROM pg_constraint WHERE conrelid='event'::regclass ORDER BY oid;
+SELECT tgfoid::regproc, tgparentid <> 0 AS parent, tgisinternal, tgconstrrelid::regclass FROM pg_trigger WHERE tgconstrrelid='event'::regclass ORDER BY oid;
+
+DROP TABLE event;
+DROP TABLE metrics;
+
+-- test single column fk constraint from hypertable to hypertable
+CREATE TABLE metrics(time timestamptz primary key, device text, value float);
+SELECT table_name FROM create_hypertable('metrics', 'time');
+
+CREATE TABLE event(time timestamptz, info text);
+SELECT table_name FROM create_hypertable('event', 'time');
+
+\set ON_ERROR_STOP 0
+ALTER TABLE event ADD CONSTRAINT event_time_fkey FOREIGN KEY (time) REFERENCES metrics(time);
+\set ON_ERROR_STOP 1
+
+CREATE TABLE event2(time timestamptz REFERENCES metrics(time), info text);
+\set ON_ERROR_STOP 0
+SELECT table_name FROM create_hypertable('event2', 'time');
+\set ON_ERROR_STOP 1
+
+DROP TABLE event;
+DROP TABLE event2;
+DROP TABLE metrics;