From e8dfb066e7d49cc1550969871b43fac9ed01c04c Mon Sep 17 00:00:00 2001
From: luquijeffrey <lucas.jeffrey@anachronics.com>
Date: Fri, 29 May 2026 12:23:54 -0300
Subject: [PATCH 2/2] =?UTF-8?q?Fix=20crash=20of=20self=E2=80=91referencing?=
 =?UTF-8?q?=20tables=20with=20delete=20triggers?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/backend/utils/adt/ri_triggers.c | 130 +++++++++++++++++++++++++++-
 1 file changed, 129 insertions(+), 1 deletion(-)

diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index dc89c686394..425e7f9e1eb 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -251,12 +251,25 @@ typedef struct RI_FastPathEntry
 	int			batch_count;
 } RI_FastPathEntry;
 
+/*
+ * RI_QueryPlanCacheExecutingRefCountEntry
+ *
+ * Entry to track the number of times a prepared plan is being executed.
+ */
+typedef struct RI_QueryPlanCacheExecutingRefCountEntry
+{
+	SPIPlanPtr  plan;
+	bool        markedForDeletion; /* If true, it will be freed when refcount reaches 0 */
+	uint32      refcount; /* number of times this plan is being executed (can be more than 1 if reentrant) */
+} RI_QueryPlanCacheExecutingRefCountEntry;
+
 /*
  * Local data
  */
 static HTAB *ri_constraint_cache = NULL;
 static HTAB *ri_query_cache = NULL;
 static HTAB *ri_compare_cache = NULL;
+static HTAB *ri_query_plan_cache_executing_refcount = NULL;
 static dclist_head ri_constraint_cache_valid_list;
 
 static HTAB *ri_fastpath_cache = NULL;
@@ -295,6 +308,11 @@ static SPIPlanPtr ri_FetchPreparedPlan(RI_QueryKey *key);
 static void ri_HashPreparedPlan(RI_QueryKey *key, SPIPlanPtr plan);
 static RI_CompareHashEntry *ri_HashCompareOp(Oid eq_opr, Oid typeid);
 
+/* Reentrancy protection: prevent segfault on deleting a plan in execution if invalidated during reentrant RI check. */
+static void ri_PreparedPlanExecutionStarted(SPIPlanPtr plan);
+static void ri_PreparedPlanExecutionFinished(SPIPlanPtr plan);
+static void ri_PreparedPlanReleaseASAP(SPIPlanPtr plan);
+
 static void ri_CheckTrigger(FunctionCallInfo fcinfo, const char *funcname,
 							int tgkind);
 static RI_ConstraintInfo *ri_FetchConstraintInfo(Trigger *trigger,
@@ -2724,6 +2742,9 @@ ri_PerformCheck(const RI_ConstraintInfo *riinfo,
 						   save_sec_context | SECURITY_LOCAL_USERID_CHANGE |
 						   SECURITY_NOFORCE_RLS);
 
+	/* Increase plan use count for reentrancy protection. */
+	ri_PreparedPlanExecutionStarted(qplan);
+
 	/*
 	 * Finally we can run the query.
 	 *
@@ -2735,6 +2756,9 @@ ri_PerformCheck(const RI_ConstraintInfo *riinfo,
 									  vals, nulls,
 									  test_snapshot, crosscheck_snapshot,
 									  false, false, limit);
+	
+	/* Decrease plan use count. this call can free the plan if it was invalidated and no longer in use. */
+	ri_PreparedPlanExecutionFinished(qplan);
 
 	/* Restore UID and security context */
 	SetUserIdAndSecContext(save_userid, save_sec_context);
@@ -3762,6 +3786,12 @@ ri_InitHashTables(void)
 	ri_compare_cache = hash_create("RI compare cache",
 								   RI_INIT_QUERYHASHSIZE,
 								   &ctl, HASH_ELEM | HASH_BLOBS);
+
+	ctl.keysize = sizeof(SPIPlanPtr);
+	ctl.entrysize = sizeof(RI_QueryPlanCacheExecutingRefCountEntry);
+	ri_query_plan_cache_executing_refcount = hash_create("RI plan cache execution refcount",
+								   RI_INIT_QUERYHASHSIZE,
+								   &ctl, HASH_ELEM | HASH_BLOBS);
 }
 
 
@@ -3812,7 +3842,7 @@ ri_FetchPreparedPlan(RI_QueryKey *key)
 	 */
 	entry->plan = NULL;
 	if (plan)
-		SPI_freeplan(plan);
+		ri_PreparedPlanReleaseASAP(plan);
 
 	return NULL;
 }
@@ -3847,6 +3877,104 @@ ri_HashPreparedPlan(RI_QueryKey *key, SPIPlanPtr plan)
 }
 
 
+static void
+ri_PreparedPlanExecutionStarted(SPIPlanPtr plan)
+{	
+	RI_QueryPlanCacheExecutingRefCountEntry* entry;
+	bool found;
+
+	if (!ri_query_plan_cache_executing_refcount)
+		ri_InitHashTables();
+	
+	entry = (RI_QueryPlanCacheExecutingRefCountEntry*) hash_search(ri_query_plan_cache_executing_refcount, &plan, HASH_ENTER, &found);
+	if (found)
+		entry->refcount++;
+	else
+	{
+		entry->refcount = 1;
+		entry->markedForDeletion = false;
+	}
+}
+
+static void
+ri_PreparedPlanExecutionFinished(SPIPlanPtr plan)
+{	
+	RI_QueryPlanCacheExecutingRefCountEntry* entry;
+	bool found;
+
+	if (!ri_query_plan_cache_executing_refcount)
+		return;
+	
+	entry = (RI_QueryPlanCacheExecutingRefCountEntry*) hash_search(ri_query_plan_cache_executing_refcount, &plan, HASH_FIND, &found);
+	if (!entry)
+		return;
+	
+	entry->refcount--;
+	if (entry->refcount == 0 && entry->markedForDeletion)
+	{
+		// Remove the entry
+		hash_search(ri_query_plan_cache_executing_refcount, &plan, HASH_REMOVE, NULL);
+		SPI_freeplan(plan);
+	}
+}
+
+/*
+ * ri_PreparedPlanReleaseASAP
+ *
+ * Release a cached SPI plan, or mark it for deferred deletion if it
+ * is currently in use.
+ *
+ * If the plan has an active executing-refcount entry with refcount > 0,
+ * we cannot free it immediately.  Instead we mark it for deletion so
+ * that the last executor to finish will free it.
+ */
+static void
+ri_PreparedPlanReleaseASAP(SPIPlanPtr plan)
+{
+	RI_QueryPlanCacheExecutingRefCountEntry *entry;
+	bool		found;
+
+	/*
+	 * If there is no executing-refcount hash table, it's not in use,
+	 * so we can free immediately.
+	 */
+	if (!ri_query_plan_cache_executing_refcount)
+	{
+		SPI_freeplan(plan);
+		return;
+	}
+
+	entry = (RI_QueryPlanCacheExecutingRefCountEntry *)
+		hash_search(ri_query_plan_cache_executing_refcount,
+					&plan, HASH_FIND, &found);
+
+	/*
+	 * No refcount entry means the plan is not being executed; free it now.
+	 */
+	if (!found)
+	{
+		SPI_freeplan(plan);
+		return;
+	}
+
+	/*
+	 * If the refcount has dropped to zero, remove the entry and free the
+	 * plan.  Otherwise mark it for deletion once the last executor finishes.
+	 */
+	if (entry->refcount == 0)
+	{
+		hash_search(ri_query_plan_cache_executing_refcount,
+					&plan, HASH_REMOVE, NULL);
+		SPI_freeplan(plan);
+		return;
+	}
+
+	/*
+	 * Mark for deletion once the last executor finishes.
+	 */
+	entry->markedForDeletion = true;
+}
+
 /*
  * ri_KeysEqual -
  *
-- 
2.34.1

