From aa53366a3fe4257cf73b096b53875fb95addf813 Mon Sep 17 00:00:00 2001
From: David Rowley <dgrowley@gmail.com>
Date: Mon, 15 Sep 2025 21:59:36 +1200
Subject: [PATCH v3 2/2] Reduce rescan overheads in TID [Range] Scan

Until how the TIDs to scan have been recalculated after every rescan of
both TID Scan and TID Range Scan.  Here we adjust things so that the
calculated list of TIDs or calculated range of TIDs is only marked as
needing recalculated whenever a parameter has changed that might require
a recalculation.
---
 src/backend/executor/nodeTidrangescan.c | 59 ++++++++++++++++++-------
 src/backend/executor/nodeTidscan.c      | 17 +++++--
 src/backend/optimizer/plan/createplan.c | 25 ++++++++---
 src/include/nodes/execnodes.h           |  4 ++
 src/include/nodes/plannodes.h           |  4 ++
 5 files changed, 84 insertions(+), 25 deletions(-)

diff --git a/src/backend/executor/nodeTidrangescan.c b/src/backend/executor/nodeTidrangescan.c
index 1bce8d6cbfe..39239c6692b 100644
--- a/src/backend/executor/nodeTidrangescan.c
+++ b/src/backend/executor/nodeTidrangescan.c
@@ -128,14 +128,17 @@ TidExprListCreate(TidRangeScanState *tidrangestate)
  *		TidRangeEval
  *
  *		Compute and set node's block and offset range to scan by evaluating
- *		node->trss_tidexprs.  Returns false if we detect the range cannot
- *		contain any tuples.  Returns true if it's possible for the range to
- *		contain tuples.  We don't bother validating that trss_mintid is less
- *		than or equal to trss_maxtid, as the scan_set_tidrange() table AM
- *		function will handle that.
+ *		node->trss_tidexprs.  Sets node's trss_rangeIsEmpty to true if the
+ *		calculated range must be empty of any tuples, otherwise sets
+ *		trss_rangeIsEmpty to false and sets trss_mintid and trss_maxtid to the
+ *		calculated range.
+ *
+ *		We don't bother validating that trss_mintid is less than or equal to
+ *		trss_maxtid, as the scan_set_tidrange() table AM function will handle
+ *		that.
  * ----------------------------------------------------------------
  */
-static bool
+static void
 TidRangeEval(TidRangeScanState *node)
 {
 	ExprContext *econtext = node->ss.ps.ps_ExprContext;
@@ -165,7 +168,10 @@ TidRangeEval(TidRangeScanState *node)
 
 		/* If the bound is NULL, *nothing* matches the qual. */
 		if (isNull)
-			return false;
+		{
+			node->trss_rangeIsEmpty = true;
+			return;
+		}
 
 		if (tidopexpr->exprtype == TIDEXPR_LOWER_BOUND)
 		{
@@ -207,7 +213,7 @@ TidRangeEval(TidRangeScanState *node)
 	ItemPointerCopy(&lowerBound, &node->trss_mintid);
 	ItemPointerCopy(&upperBound, &node->trss_maxtid);
 
-	return true;
+	node->trss_rangeIsEmpty = false;
 }
 
 /* ----------------------------------------------------------------
@@ -234,12 +240,19 @@ TidRangeNext(TidRangeScanState *node)
 	slot = node->ss.ss_ScanTupleSlot;
 	direction = estate->es_direction;
 
-	if (!node->trss_inScan)
+	/* First time through, compute TID range to scan */
+	if (!node->trss_rangeCalcDone)
 	{
-		/* First time through, compute TID range to scan */
-		if (!TidRangeEval(node))
-			return NULL;
+		TidRangeEval(node);
+		node->trss_rangeCalcDone = true;
+	}
+
+	/* Check if the range was detected not to contain any tuples */
+	if (node->trss_rangeIsEmpty)
+		return NULL;
 
+	if (!node->trss_inScan)
+	{
 		if (scandesc == NULL)
 		{
 			scandesc = table_beginscan_tidrange(node->ss.ss_currentRelation,
@@ -274,13 +287,18 @@ TidRangeNext(TidRangeScanState *node)
 static bool
 TidRangeRecheck(TidRangeScanState *node, TupleTableSlot *slot)
 {
-	if (!TidRangeEval(node))
-		return false;
+	/* First call? Compute the TID Range */
+	if (!node->trss_rangeCalcDone)
+	{
+		TidRangeEval(node);
+		node->trss_rangeCalcDone = true;
+	}
 
 	Assert(ItemPointerIsValid(&slot->tts_tid));
 
 	/* Recheck the ctid is still within range */
-	if (ItemPointerCompare(&slot->tts_tid, &node->trss_mintid) < 0 ||
+	if (node->trss_rangeIsEmpty ||
+		ItemPointerCompare(&slot->tts_tid, &node->trss_mintid) < 0 ||
 		ItemPointerCompare(&slot->tts_tid, &node->trss_maxtid) > 0)
 		return false;
 
@@ -322,6 +340,13 @@ ExecReScanTidRangeScan(TidRangeScanState *node)
 	/* mark scan as not in progress, and tid range list as not computed yet */
 	node->trss_inScan = false;
 
+	/*
+	 * Set the Tid Range to be recalculated when any parameters that might
+	 * effect the calculated range have changed.
+	 */
+	if (bms_overlap(node->ss.ps.chgParam, ((TidRangeScan *) node->ss.ps.plan)->tidparamids))
+		node->trss_rangeCalcDone = false;
+
 	/*
 	 * We must wait until TidRangeNext before calling table_rescan_tidrange.
 	 */
@@ -380,6 +405,10 @@ ExecInitTidRangeScan(TidRangeScan *node, EState *estate, int eflags)
 	 * mark scan as not in progress, and TID range as not computed yet
 	 */
 	tidrangestate->trss_inScan = false;
+	tidrangestate->trss_rangeCalcDone = false;
+
+	/* This will be calculated correctly in TidRangeEval() */
+	tidrangestate->trss_rangeIsEmpty = true;
 
 	/*
 	 * open the scan relation
diff --git a/src/backend/executor/nodeTidscan.c b/src/backend/executor/nodeTidscan.c
index d50c6600358..b40a31e0d2f 100644
--- a/src/backend/executor/nodeTidscan.c
+++ b/src/backend/executor/nodeTidscan.c
@@ -457,10 +457,19 @@ ExecTidScan(PlanState *pstate)
 void
 ExecReScanTidScan(TidScanState *node)
 {
-	if (node->tss_TidList)
-		pfree(node->tss_TidList);
-	node->tss_TidList = NULL;
-	node->tss_NumTids = 0;
+	/*
+	 * Set the TidList to be recalculated when any parameters that might
+	 * effect the computed list has changed.
+	 */
+	if (bms_overlap(node->ss.ps.chgParam,
+					 ((TidScan *) node->ss.ps.plan)->tidparamids))
+	{
+		if (node->tss_TidList)
+			pfree(node->tss_TidList);
+		node->tss_TidList = NULL;
+		node->tss_NumTids = 0;
+	}
+
 	node->tss_TidPtr = -1;
 
 	/* not really necessary, but seems good form */
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 6791cbeb416..87e2597ee8c 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -201,9 +201,10 @@ static BitmapHeapScan *make_bitmap_heapscan(List *qptlist,
 											List *bitmapqualorig,
 											Index scanrelid);
 static TidScan *make_tidscan(List *qptlist, List *qpqual, Index scanrelid,
-							 List *tidquals);
+							 List *tidquals, Bitmapset *paramids);
 static TidRangeScan *make_tidrangescan(List *qptlist, List *qpqual,
-									   Index scanrelid, List *tidrangequals);
+									   Index scanrelid, List *tidrangequals,
+									   Bitmapset *paramids);
 static SubqueryScan *make_subqueryscan(List *qptlist,
 									   List *qpqual,
 									   Index scanrelid,
@@ -3384,6 +3385,7 @@ create_tidscan_plan(PlannerInfo *root, TidPath *best_path,
 	TidScan    *scan_plan;
 	Index		scan_relid = best_path->path.parent->relid;
 	List	   *tidquals = best_path->tidquals;
+	Bitmapset  *paramids;
 
 	/* it should be a base rel... */
 	Assert(scan_relid > 0);
@@ -3459,10 +3461,13 @@ create_tidscan_plan(PlannerInfo *root, TidPath *best_path,
 			replace_nestloop_params(root, (Node *) scan_clauses);
 	}
 
+	paramids = pull_paramids((Expr *) tidquals);
+
 	scan_plan = make_tidscan(tlist,
 							 scan_clauses,
 							 scan_relid,
-							 tidquals);
+							 tidquals,
+							 paramids);
 
 	copy_generic_path_info(&scan_plan->scan.plan, &best_path->path);
 
@@ -3481,6 +3486,7 @@ create_tidrangescan_plan(PlannerInfo *root, TidRangePath *best_path,
 	TidRangeScan *scan_plan;
 	Index		scan_relid = best_path->path.parent->relid;
 	List	   *tidrangequals = best_path->tidrangequals;
+	Bitmapset  *paramids;
 
 	/* it should be a base rel... */
 	Assert(scan_relid > 0);
@@ -3524,10 +3530,13 @@ create_tidrangescan_plan(PlannerInfo *root, TidRangePath *best_path,
 			replace_nestloop_params(root, (Node *) scan_clauses);
 	}
 
+	paramids = pull_paramids((Expr *) tidrangequals);
+
 	scan_plan = make_tidrangescan(tlist,
 								  scan_clauses,
 								  scan_relid,
-								  tidrangequals);
+								  tidrangequals,
+								  paramids);
 
 	copy_generic_path_info(&scan_plan->scan.plan, &best_path->path);
 
@@ -5622,7 +5631,8 @@ static TidScan *
 make_tidscan(List *qptlist,
 			 List *qpqual,
 			 Index scanrelid,
-			 List *tidquals)
+			 List *tidquals,
+			 Bitmapset *paramids)
 {
 	TidScan    *node = makeNode(TidScan);
 	Plan	   *plan = &node->scan.plan;
@@ -5633,6 +5643,7 @@ make_tidscan(List *qptlist,
 	plan->righttree = NULL;
 	node->scan.scanrelid = scanrelid;
 	node->tidquals = tidquals;
+	node->tidparamids = paramids;
 
 	return node;
 }
@@ -5641,7 +5652,8 @@ static TidRangeScan *
 make_tidrangescan(List *qptlist,
 				  List *qpqual,
 				  Index scanrelid,
-				  List *tidrangequals)
+				  List *tidrangequals,
+				  Bitmapset *paramids)
 {
 	TidRangeScan *node = makeNode(TidRangeScan);
 	Plan	   *plan = &node->scan.plan;
@@ -5652,6 +5664,7 @@ make_tidrangescan(List *qptlist,
 	plan->righttree = NULL;
 	node->scan.scanrelid = scanrelid;
 	node->tidrangequals = tidrangequals;
+	node->tidparamids = paramids;
 
 	return node;
 }
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 71857feae48..e625d04363e 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1926,6 +1926,8 @@ typedef struct TidScanState
  *		trss_mintid			the lowest TID in the scan range
  *		trss_maxtid			the highest TID in the scan range
  *		trss_inScan			is a scan currently in progress?
+ *		trss_rangeCalcDone	has the TID range been calculated yet?
+ *		trss_rangeIsEmpty	true if the TID range is certainly empty
  * ----------------
  */
 typedef struct TidRangeScanState
@@ -1935,6 +1937,8 @@ typedef struct TidRangeScanState
 	ItemPointerData trss_mintid;
 	ItemPointerData trss_maxtid;
 	bool		trss_inScan;
+	bool		trss_rangeCalcDone;
+	bool		trss_rangeIsEmpty;
 } TidRangeScanState;
 
 /* ----------------
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 29d7732d6a0..a3d220e9d5e 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -677,6 +677,8 @@ typedef struct TidScan
 	Scan		scan;
 	/* qual(s) involving CTID = something */
 	List	   *tidquals;
+	/* Set of ParamIds mentioned in tidquals */
+	Bitmapset  *tidparamids;
 } TidScan;
 
 /* ----------------
@@ -691,6 +693,8 @@ typedef struct TidRangeScan
 	Scan		scan;
 	/* qual(s) involving CTID op something */
 	List	   *tidrangequals;
+	/* Set of ParamIds mentioned in tidrangequals */
+	Bitmapset *tidparamids;
 } TidRangeScan;
 
 /* ----------------
-- 
2.43.0

