From b03264d55d0c23885fe9ce4f9d93a6ca65d45261 Mon Sep 17 00:00:00 2001
From: Tatsuo Ishii <ishii@postgresql.org>
Date: Wed, 10 Jul 2024 16:06:59 +0900
Subject: [PATCH v2 4/5] Add memory/disk usage for Recursive Union nodes in
 EXPLAIN

---
 src/backend/commands/explain.c            | 47 +++++++++++++++++------
 src/backend/executor/nodeRecursiveunion.c | 30 +++++++++++++++
 src/include/nodes/execnodes.h             |  2 +
 3 files changed, 68 insertions(+), 11 deletions(-)

diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 1a4e83ad38..054d909093 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -125,10 +125,12 @@ static void show_sort_info(SortState *sortstate, ExplainState *es);
 static void show_incremental_sort_info(IncrementalSortState *incrsortstate,
 									   ExplainState *es);
 static void show_hash_info(HashState *hashstate, ExplainState *es);
-static void show_storage_info(Tuplestorestate *tupstore, ExplainState *es);
+static void show_storage_info(Tuplestorestate *tupstore, int64 storage_used, char *storage_type,
+							  ExplainState *es);
 static void show_material_info(MaterialState *mstate, ExplainState *es);
 static void show_ctescan_info(CteScanState *ctescanstate, ExplainState *es);
 static void show_table_func_can_info(TableFuncScanState *tscanstate, ExplainState *es);
+static void show_recursive_union_info(RecursiveUnionState *rstate, ExplainState *es);
 static void show_memoize_info(MemoizeState *mstate, List *ancestors,
 							  ExplainState *es);
 static void show_hashagg_info(AggState *aggstate, ExplainState *es);
@@ -2264,6 +2266,9 @@ ExplainNode(PlanState *planstate, List *ancestors,
 			show_memoize_info(castNode(MemoizeState, planstate), ancestors,
 							  es);
 			break;
+		case T_RecursiveUnion:
+			show_recursive_union_info(castNode(RecursiveUnionState, planstate), es);
+			break;
 		default:
 			break;
 	}
@@ -3332,23 +3337,32 @@ show_hash_info(HashState *hashstate, ExplainState *es)
 }
 
 /*
- * Show information on storage method and maximum memory/disk space used.
+ * Show information on storage method and maximum memory/disk space used.  If
+ * tupstore is NULL, then storage_used and storage_type are used instead.
  */
 static void
-show_storage_info(Tuplestorestate *tupstore, ExplainState *es)
+show_storage_info(Tuplestorestate *tupstore, int64 storage_used, char *storage_type,
+				  ExplainState *es)
 {
 	const char *storageType;
 	int64		spaceUsedKB;
 
 	/*
-	 * Nothing to show if ANALYZE option wasn't used or if execution didn't
-	 * get as far as creating the tuplestore.
+	 * Nothing to show if ANALYZE option wasn't used.
 	 */
-	if (!es->analyze || tupstore == NULL)
+	if (!es->analyze)
 		return;
 
-	storageType = tuplestore_storage_type_name(tupstore);
-	spaceUsedKB = BYTES_TO_KILOBYTES(tuplestore_space_used(tupstore));
+	if (tupstore != NULL)
+	{
+		storageType = tuplestore_storage_type_name(tupstore);
+		spaceUsedKB = BYTES_TO_KILOBYTES(tuplestore_space_used(tupstore));
+	}
+	else
+	{
+		storageType = storage_type;
+		spaceUsedKB = BYTES_TO_KILOBYTES(storage_used);
+	}
 
 	if (es->format != EXPLAIN_FORMAT_TEXT)
 	{
@@ -3374,7 +3388,7 @@ show_material_info(MaterialState *mstate, ExplainState *es)
 {
 	Tuplestorestate *tupstore = mstate->tuplestorestate;
 
-	show_storage_info(tupstore, es);
+	show_storage_info(tupstore, 0, NULL, es);
 }
 
 /*
@@ -3386,7 +3400,7 @@ show_ctescan_info(CteScanState *ctescanstate, ExplainState *es)
 {
 	Tuplestorestate *tupstore = ctescanstate->leader->cte_table;
 
-	show_storage_info(tupstore, es);
+	show_storage_info(tupstore, 0, NULL, es);
 }
 
 /*
@@ -3398,7 +3412,18 @@ show_table_func_can_info(TableFuncScanState *tscanstate, ExplainState *es)
 {
 	Tuplestorestate *tupstore = tscanstate->tupstore;
 
-	show_storage_info(tupstore, es);
+	show_storage_info(tupstore, 0, NULL, es);
+}
+
+/*
+ * Show information on Recursive Union node, storage method and maximum
+ * memory/disk space used.
+ */
+
+static void
+show_recursive_union_info(RecursiveUnionState *rstate, ExplainState *es)
+{
+	show_storage_info(NULL, rstate->storageSize, rstate->storageType, es);
 }
 
 /*
diff --git a/src/backend/executor/nodeRecursiveunion.c b/src/backend/executor/nodeRecursiveunion.c
index c7f8a19fa4..8667b7ca93 100644
--- a/src/backend/executor/nodeRecursiveunion.c
+++ b/src/backend/executor/nodeRecursiveunion.c
@@ -52,6 +52,33 @@ build_hash_table(RecursiveUnionState *rustate)
 												false);
 }
 
+/*
+ * Track the maximum memory/disk usage in working_table and
+ * intermediate_table.  Supposed to be called just before tuplestore_end.
+ */
+static void
+track_storage_usage(RecursiveUnionState *state, Tuplestorestate *tup)
+{
+	int64	spaceUsed;
+
+	if (tup == NULL)
+		return;
+
+	spaceUsed = tuplestore_space_used(tup);
+
+	/*
+	 * We want to track the maximum mem/disk usage so that we can use the info
+	 * in EXPLAIN (ANALYZE).
+	 */
+	if (spaceUsed > state->storageSize)
+	{
+		if (state->storageType != NULL)
+			pfree(state->storageType);
+		state->storageType =
+			pstrdup(tuplestore_storage_type_name(tup));
+		state->storageSize = spaceUsed;
+	}
+}
 
 /* ----------------------------------------------------------------
  *		ExecRecursiveUnion(node)
@@ -120,6 +147,7 @@ ExecRecursiveUnion(PlanState *pstate)
 				break;
 
 			/* done with old working table ... */
+			track_storage_usage(node, node->working_table);
 			tuplestore_end(node->working_table);
 
 			/* intermediate table becomes working table */
@@ -191,6 +219,8 @@ ExecInitRecursiveUnion(RecursiveUnion *node, EState *estate, int eflags)
 	rustate->intermediate_empty = true;
 	rustate->working_table = tuplestore_begin_heap(false, false, work_mem);
 	rustate->intermediate_table = tuplestore_begin_heap(false, false, work_mem);
+	rustate->storageSize = 0;
+	rustate->storageType = NULL;
 
 	/*
 	 * If hashing, we need a per-tuple memory context for comparisons, and a
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index cac684d9b3..bc2c0baed6 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1509,6 +1509,8 @@ typedef struct RecursiveUnionState
 	bool		intermediate_empty;
 	Tuplestorestate *working_table;
 	Tuplestorestate *intermediate_table;
+	int64		storageSize;	/* max storage size Tuplestore */
+	char		*storageType;	/* the storage type above */
 	/* Remaining fields are unused in UNION ALL case */
 	Oid		   *eqfuncoids;		/* per-grouping-field equality fns */
 	FmgrInfo   *hashfunctions;	/* per-grouping-field hash fns */
-- 
2.25.1

