From e45de721568879a9625fcd7b459bff94931b7586 Mon Sep 17 00:00:00 2001
From: "Paul A. Jungwirth" <pj@illuminatedcomputing.com>
Date: Fri, 13 Jun 2025 16:11:47 -0700
Subject: [PATCH v1 3/7] Look up additional temporal foreign key helper procs

To implement CASCADE/SET NULL/SET DEFAULT on temporal foreign keys, we
need an intersect and without_portion function. We can look them up
when we look up the operators already needed for NO ACTION temporal
foreign keys.

Author: Paul A. Jungwirth <pj@illuminatedcomputing.com>
---
 src/backend/catalog/pg_constraint.c | 37 +++++++++++++++++++++++++----
 src/backend/commands/tablecmds.c    |  7 ++++--
 src/backend/parser/analyze.c        |  2 +-
 src/backend/utils/adt/ri_triggers.c | 15 ++++++++----
 src/include/catalog/pg_constraint.h | 10 ++++----
 5 files changed, 55 insertions(+), 16 deletions(-)

diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index 48ede21007e..fd14df6cace 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -1655,7 +1655,7 @@ DeconstructFkConstraintRow(HeapTuple tuple, int *numfks,
 }
 
 /*
- * FindFKPeriodOpers -
+ * FindFKPeriodOpersAndProcs -
  *
  * Looks up the operator oids used for the PERIOD part of a temporal foreign key.
  * The opclass should be the opclass of that PERIOD element.
@@ -1666,12 +1666,18 @@ DeconstructFkConstraintRow(HeapTuple tuple, int *numfks,
  * That way foreign keys can compare fkattr <@ range_agg(pkattr).
  * intersectoperoid is used by NO ACTION constraints to trim the range being considered
  * to just what was updated/deleted.
+ * intersectprocoid is used to limit the effect of CASCADE/SET NULL/SET DEFAULT
+ * to the history the PK record kept.
+ * withoutportionprocoid is a set-returning function giving the history the PK
+ * record lost, so that CASCADE can delete it from the referencing rows.
  */
 void
-FindFKPeriodOpers(Oid opclass,
-				  Oid *containedbyoperoid,
-				  Oid *aggedcontainedbyoperoid,
-				  Oid *intersectoperoid)
+FindFKPeriodOpersAndProcs(Oid opclass,
+						  Oid *containedbyoperoid,
+						  Oid *aggedcontainedbyoperoid,
+						  Oid *intersectoperoid,
+						  Oid *intersectprocoid,
+						  Oid *withoutportionprocoid)
 {
 	Oid			opfamily = InvalidOid;
 	Oid			opcintype = InvalidOid;
@@ -1713,17 +1719,38 @@ FindFKPeriodOpers(Oid opclass,
 							   aggedcontainedbyoperoid,
 							   &strat);
 
+	/*
+	 * Hardcode the intersect operator and the without_portion function for
+	 * ranges and multiranges, because we don't have a better way to look up
+	 * things that aren't used in indexes.
+	 *
+	 * If you change this code, you must change the code in
+	 * transformForPortionOfClause.
+	 *
+	 * XXX: Find a more extensible way to look these up, permitting
+	 * user-defined types.
+	 */
 	switch (opcintype)
 	{
 		case ANYRANGEOID:
 			*intersectoperoid = OID_RANGE_INTERSECT_RANGE_OP;
+			*withoutportionprocoid = F_RANGE_MINUS_MULTI;
 			break;
 		case ANYMULTIRANGEOID:
 			*intersectoperoid = OID_MULTIRANGE_INTERSECT_MULTIRANGE_OP;
+			*withoutportionprocoid = F_MULTIRANGE_MINUS_MULTI;
 			break;
 		default:
 			elog(ERROR, "unexpected opcintype: %u", opcintype);
 	}
+
+	/*
+	 * Look up the intersect proc. We use this in temporal foreign keys with
+	 * CASCADE/SET NULL/SET DEFAULT to build the FOR PORTION OF bounds. If
+	 * this is missing we don't need to complain here, because FOR PORTION OF
+	 * will not be allowed.
+	 */
+	*intersectprocoid = get_opcode(*intersectoperoid);
 }
 
 /*
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index fd144d783d9..1baa6c05718 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -10669,9 +10669,12 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 		Oid			periodoperoid;
 		Oid			aggedperiodoperoid;
 		Oid			intersectoperoid;
+		Oid			intersectprocoid;
+		Oid			withoutportionprocoid;
 
-		FindFKPeriodOpers(opclasses[numpks - 1], &periodoperoid, &aggedperiodoperoid,
-						  &intersectoperoid);
+		FindFKPeriodOpersAndProcs(opclasses[numpks - 1], &periodoperoid, &aggedperiodoperoid,
+								  &intersectoperoid, &intersectprocoid,
+								  &withoutportionprocoid);
 	}
 
 	/* First, create the constraint catalog entry itself. */
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 0be92fb7013..303a32d7141 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -1554,7 +1554,7 @@ transformForPortionOfClause(ParseState *pstate,
 		/*
 		 * Whatever operator is used for intersect by temporal foreign keys,
 		 * we can use its backing procedure for intersects in FOR PORTION OF.
-		 * XXX: Share code with FindFKPeriodOpers?
+		 * XXX: Share code with FindFKPeriodOpersAndProcs?
 		 */
 		switch (opcintype)
 		{
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index d4545618634..00ff0f949b9 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -140,6 +140,11 @@ typedef struct RI_ConstraintInfo
 	Oid			agged_period_contained_by_oper; /* fkattr <@ range_agg(pkattr) */
 	Oid			period_intersect_oper;	/* anyrange * anyrange (or
 										 * multiranges) */
+	Oid			period_intersect_proc;	/* anyrange * anyrange (or
+										 * multiranges) */
+	Oid			period_without_portion_proc;	/* anyrange minus anyrange,
+												 * returning SETOF anyrange
+												 * (or multiranges) */
 	dlist_node	valid_link;		/* Link in list of valid entries */
 
 	Oid			conindid;
@@ -2537,10 +2542,12 @@ ri_LoadConstraintInfo(Oid constraintOid)
 	{
 		Oid			opclass = get_index_column_opclass(conForm->conindid, riinfo->nkeys);
 
-		FindFKPeriodOpers(opclass,
-						  &riinfo->period_contained_by_oper,
-						  &riinfo->agged_period_contained_by_oper,
-						  &riinfo->period_intersect_oper);
+		FindFKPeriodOpersAndProcs(opclass,
+								  &riinfo->period_contained_by_oper,
+								  &riinfo->agged_period_contained_by_oper,
+								  &riinfo->period_intersect_oper,
+								  &riinfo->period_intersect_proc,
+								  &riinfo->period_without_portion_proc);
 	}
 
 	/* Metadata used by fast path. */
diff --git a/src/include/catalog/pg_constraint.h b/src/include/catalog/pg_constraint.h
index 1b7fedf1750..7d81b0257d6 100644
--- a/src/include/catalog/pg_constraint.h
+++ b/src/include/catalog/pg_constraint.h
@@ -292,10 +292,12 @@ extern void DeconstructFkConstraintRow(HeapTuple tuple, int *numfks,
 									   AttrNumber *conkey, AttrNumber *confkey,
 									   Oid *pf_eq_oprs, Oid *pp_eq_oprs, Oid *ff_eq_oprs,
 									   int *num_fk_del_set_cols, AttrNumber *fk_del_set_cols);
-extern void FindFKPeriodOpers(Oid opclass,
-							  Oid *containedbyoperoid,
-							  Oid *aggedcontainedbyoperoid,
-							  Oid *intersectoperoid);
+extern void FindFKPeriodOpersAndProcs(Oid opclass,
+									  Oid *containedbyoperoid,
+									  Oid *aggedcontainedbyoperoid,
+									  Oid *intersectoperoid,
+									  Oid *intersectprocoid,
+									  Oid *withoutportionprocoid);
 
 extern bool check_functional_grouping(Oid relid,
 									  Index varno, Index varlevelsup,
-- 
2.45.0

