From 1923dbce2127f91f0ded31ed1f2465a2961cedca Mon Sep 17 00:00:00 2001
From: Junwang Zhao <zhjwpku@gmail.com>
Date: Tue, 10 Mar 2026 16:06:56 +0800
Subject: [PATCH v5 4/6] Refine fast-path FK validation path

The `use_cache` branch in `ri_FastPathCheck` is no longer needed.
Remove it and update the comments that are no longer applicable.
---
 src/backend/utils/adt/ri_triggers.c | 73 +++++++++--------------------
 1 file changed, 23 insertions(+), 50 deletions(-)

diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index 7929d6f8c85..200c4094861 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -200,7 +200,7 @@ typedef struct RI_CompareHashEntry
 
 /*
  * RI_FastPathEntry
- *		Per-constraint cache of resources needed by ri_FastPathCheck().
+ *		Per-constraint cache of resources needed by ri_FastPathBatchFlush().
  *
  * One entry per constraint, keyed by pg_constraint OID.  Created lazily
  * by ri_FastPathGetEntry() on first use within a trigger-firing batch
@@ -2722,10 +2722,14 @@ ri_PerformCheck(const RI_ConstraintInfo *riinfo,
 
 /*
  * ri_FastPathCheck
- *		Perform FK existence check via direct index probe, bypassing SPI.
+ *		Perform per row FK existence check via direct index probe,
+ *		bypassing SPI.
  *
  * If no matching PK row exists, report the violation via ri_ReportViolation(),
  * otherwise, the function returns normally.
+ *
+ * Note: This is only used by the ALTER TABLE validation path. Other paths use
+ * ri_FastPathBatchAdd().
  */
 static void
 ri_FastPathCheck(const RI_ConstraintInfo *riinfo,
@@ -2744,8 +2748,6 @@ ri_FastPathCheck(const RI_ConstraintInfo *riinfo,
 	int			saved_sec_context;
 	Snapshot	snapshot;
 	Snapshot	xact_snap = NULL;
-	bool		use_cache;
-	RI_FastPathEntry *fpentry = NULL;
 
 	/*
 	 * Use the per-batch cache only if we're inside the after-trigger
@@ -2753,10 +2755,7 @@ ri_FastPathCheck(const RI_ConstraintInfo *riinfo,
 	 * ... ADD FOREIGN KEY validation, triggers are called directly and the
 	 * callback would never run, leaking resources.
 	 */
-	use_cache = AfterTriggerBatchIsActive();
-
-	if (use_cache)
-		fpentry = ri_FastPathGetEntry(riinfo);
+	Assert(!AfterTriggerBatchIsActive());
 
 	/*
 	 * Advance the command counter so the snapshot sees the effects of prior
@@ -2764,36 +2763,14 @@ ri_FastPathCheck(const RI_ConstraintInfo *riinfo,
 	 * ri_PerformCheck().
 	 */
 	CommandCounterIncrement();
-	if (use_cache)
-	{
-		/*
-		 * The snapshot was registered once when the cache entry was created.
-		 * Patch curcid so it reflects the effects of prior triggers in this
-		 * statement.  We deliberately do not call GetLatestSnapshot() again:
-		 * the xmin/xmax/xip fields do not need refreshing because any PK row
-		 * we need to see was either already visible when the batch started or
-		 * will be found via the tuple-lock wait (LockTupleKeyShare).
-		 */
-		Assert(fpentry && fpentry->snapshot != NULL);
-		snapshot = fpentry->snapshot;
-		snapshot->curcid = GetCurrentCommandId(false);
-		xact_scan = fpentry->xact_scan;
-		xact_snap = fpentry->xact_snap;
-		pk_rel = fpentry->pk_rel;
-		idx_rel = fpentry->idx_rel;
-		scandesc = fpentry->scandesc;
-		slot = fpentry->slot;
-	}
-	else
-	{
-		snapshot = RegisterSnapshot(GetLatestSnapshot());
-		pk_rel = table_open(riinfo->pk_relid, RowShareLock);
-		idx_rel = index_open(riinfo->conindid, AccessShareLock);
-		scandesc = index_beginscan(pk_rel, idx_rel,
-								   snapshot, NULL,
-								   riinfo->nkeys, 0);
-		slot = table_slot_create(pk_rel, NULL);
-	}
+
+	snapshot = RegisterSnapshot(GetLatestSnapshot());
+	pk_rel = table_open(riinfo->pk_relid, RowShareLock);
+	idx_rel = index_open(riinfo->conindid, AccessShareLock);
+	scandesc = index_beginscan(pk_rel, idx_rel,
+							   snapshot, NULL,
+							   riinfo->nkeys, 0);
+	slot = table_slot_create(pk_rel, NULL);
 
 	if (riinfo->fpmeta == NULL)
 		ri_populate_fastpath_metadata((RI_ConstraintInfo *) riinfo,
@@ -2805,14 +2782,13 @@ ri_FastPathCheck(const RI_ConstraintInfo *riinfo,
 						   saved_sec_context |
 						   SECURITY_LOCAL_USERID_CHANGE |
 						   SECURITY_NOFORCE_RLS);
-	if (!use_cache)
-		ri_CheckPermissions(pk_rel);
+	ri_CheckPermissions(pk_rel);
 
 	ri_ExtractValues(fk_rel, newslot, riinfo, false, pk_vals, pk_nulls);
 	build_index_scankeys(riinfo, idx_rel, pk_vals, pk_nulls, skey);
 	found = ri_FastPathProbeOne(pk_rel, idx_rel, scandesc, xact_scan,
 								slot, snapshot, xact_snap, riinfo,
-								skey, riinfo->nkeys, use_cache);
+								skey, riinfo->nkeys, false);
 	if (!found)
 		ri_ReportViolation(riinfo, pk_rel, fk_rel,
 						   newslot, NULL,
@@ -2820,14 +2796,11 @@ ri_FastPathCheck(const RI_ConstraintInfo *riinfo,
 	SetUserIdAndSecContext(saved_userid, saved_sec_context);
 
 	/* Non-cached path: clean up per-invocation resources */
-	if (!use_cache)
-	{
-		index_endscan(scandesc);
-		index_close(idx_rel, NoLock);
-		table_close(pk_rel, NoLock);
-		ExecDropSingleTupleTableSlot(slot);
-		UnregisterSnapshot(snapshot);
-	}
+	index_endscan(scandesc);
+	index_close(idx_rel, NoLock);
+	table_close(pk_rel, NoLock);
+	ExecDropSingleTupleTableSlot(slot);
+	UnregisterSnapshot(snapshot);
 }
 
 /*
@@ -3987,7 +3960,7 @@ ri_FastPathGetEntry(const RI_ConstraintInfo *riinfo)
 
 		/*
 		 * Register an initial snapshot.  Its curcid will be patched in place
-		 * on each subsequent row (see ri_FastPathCheck()), avoiding per-row
+		 * on each subsequent row (see ri_FastPathBatchFlush()), avoiding per-row
 		 * GetSnapshotData() overhead.
 		 */
 		entry->snapshot = RegisterSnapshot(GetLatestSnapshot());
-- 
2.41.0

