From 30b1543a8505c8270ac406a4db090d778d3f6658 Mon Sep 17 00:00:00 2001
From: Lyu Pan <lyu.steve.pan@gmail.com>
Date: Wed, 12 Oct 2022 18:49:44 +0000
Subject: [PATCH 4/5] Integrate EXPLAIN command with semijoin filter.

Semijoin filter info will be displayed in the MergeJoin node and the SeqScan node. For example:
EXPLAIN (VERBOSE, COSTS OFF) SELECT COUNT(*) FROM t1 JOIN t2 ON t1.i = t2.i;
                           QUERY PLAN
-----------------------------------------------------------------
 Aggregate
   Output: count(*)
   ->  Merge Join
         Merge Cond: (t1.i = t2.i)
         SemiJoin Filter Created Based on: (t1.i = t2.i)
         SemiJoin Estimated Filtering Rate: XXX
         ->  Sort
               Output: t1.i
               Sort Key: t1.i
               ->  Seq Scan on public.t1
                     Semijoin Filter building based on: { t1.i }
                     Output: t1.i
         ->  Sort
               Output: t2.i
               Sort Key: t2.i
               ->  Seq Scan on public.t2
                     Semijoin Filter checking based on: { t2.i }
                     Output: t2.i
---
 src/backend/commands/explain.c | 135 +++++++++++++++++++++++++++++++++
 src/backend/lib/bloomfilter.c  |  10 ---
 src/include/lib/bloomfilter.h  |  10 ++-
 3 files changed, 144 insertions(+), 11 deletions(-)

diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index f86983c660..bdcb2f3c14 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -21,6 +21,7 @@
 #include "executor/nodeHash.h"
 #include "foreign/fdwapi.h"
 #include "jit/jit.h"
+#include "lib/bloomfilter.h"
 #include "nodes/extensible.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
@@ -108,6 +109,10 @@ static void show_tablesample(TableSampleClause *tsc, PlanState *planstate,
 static void show_sort_info(SortState *sortstate, ExplainState *es);
 static void show_incremental_sort_info(IncrementalSortState *incrsortstate,
 									   ExplainState *es);
+static void show_semijoin_metadata(List *equijoins, PlanState *planstate,
+								   List *ancestors, ExplainState *es);
+static void show_semijoin_filters_info(Plan *plan, PlanState *planstate,
+									   List *ancestors, ExplainState *es);
 static void show_hash_info(HashState *hashstate, ExplainState *es);
 static void show_memoize_info(MemoizeState *mstate, List *ancestors,
 							  ExplainState *es);
@@ -1687,6 +1692,15 @@ ExplainNode(PlanState *planstate, List *ancestors,
 	if (es->format == EXPLAIN_FORMAT_TEXT)
 		appendStringInfoChar(es->str, '\n');
 
+	switch (nodeTag(plan))
+	{
+		case T_SeqScan:
+			show_semijoin_filters_info((Plan *) plan, planstate, ancestors, es);
+			break;
+		default:
+			break;
+	}
+
 	/* prepare per-worker general execution details */
 	if (es->workers_state && es->verbose)
 	{
@@ -1981,6 +1995,11 @@ ExplainNode(PlanState *planstate, List *ancestors,
 							"Merge Cond", planstate, ancestors, es);
 			show_upper_qual(((MergeJoin *) plan)->join.joinqual,
 							"Join Filter", planstate, ancestors, es);
+			if (((MergeJoinState *) planstate)->sjf)
+			{
+				show_semijoin_metadata(((MergeJoin *) plan)->mergeclauses,
+									   planstate, ancestors, es);
+			}
 			if (((MergeJoin *) plan)->join.joinqual)
 				show_instrumentation_count("Rows Removed by Join Filter", 1,
 										   planstate, es);
@@ -5041,3 +5060,119 @@ escape_yaml(StringInfo buf, const char *str)
 {
 	escape_json(buf, str);
 }
+
+/*
+ * Show a list of expressions (or ExprState's), wrapped in the specified bounding characters
+ */
+static void
+show_expression_list(List *expr_list, const char *qlabel,
+					 const char *list_start, const char *list_separator, const char *list_end,
+					 PlanState *planstate, List *ancestors, ExplainState *es)
+{
+	List	   *context;
+	ListCell   *lc;
+	char	   *exprstr;
+	bool		first = true;
+	Node	   *nth_expr = NULL;
+	Node	   *nth_list_elem = NULL;
+
+	/* Set up deparsing context */
+	context = set_deparse_context_plan(es->deparse_cxt, planstate->plan, ancestors);
+
+	/* Insert header */
+	appendStringInfoSpaces(es->str, es->indent * 2);
+	appendStringInfo(es->str, "%s: ", qlabel);
+	appendStringInfo(es->str, "%s", list_start);
+
+	/* Deparse each expressions, and append it */
+	foreach(lc, expr_list)
+	{
+		if (!first)
+		{
+			appendStringInfo(es->str, "%s", list_separator);
+		}
+		nth_expr = NULL;
+		nth_list_elem = (Node *) lfirst(lc);
+		if (nth_list_elem->type == T_ExprState)
+		{
+			nth_expr = (Node *) (((ExprState *) nth_list_elem)->expr);
+		}
+		else
+		{
+			nth_expr = nth_list_elem;
+		}
+		if (nth_expr)
+		{
+			exprstr = deparse_expression(nth_expr, context, /* useprefix */ true, false);
+			appendStringInfo(es->str, "%s", exprstr);
+		}
+		else
+		{
+			appendStringInfo(es->str, "%s", "<?>");
+		}
+		first = false;
+	}
+
+	/* Finalize with trailer */
+	appendStringInfo(es->str, "%s\n", list_end);
+}
+
+static void
+show_semijoin_metadata(List *equijoins, PlanState *planstate,
+					   List *ancestors, ExplainState *es)
+{
+	char		createStr[256];
+	Node	   *best_equijoin_clause;
+	SemijoinFilterJoinData *sj_metadata = (((MergeJoin *) planstate->plan)->sj_metadata);
+
+	Assert(planstate);
+	Assert(nodeTag(planstate) == T_MergeJoinState);
+	Assert(planstate->plan);
+	Assert(nodeTag(planstate->plan) == T_MergeJoin);
+
+	snprintf(createStr, sizeof(createStr), "%s", "SemiJoin Filter Created Based on");
+	best_equijoin_clause = (Node *) list_nth_node(OpExpr, equijoins, sj_metadata->best_mergeclause_pos);
+	show_expression(best_equijoin_clause, createStr, planstate, ancestors, true, es);
+	ExplainPropertyFloat("SemiJoin Estimated Filtering Rate", NULL, sj_metadata->filtering_rate, 4, es);
+	if (es->analyze)
+	{
+		SemiJoinFilterJoinNodeState *sjf = ((MergeJoinState *) planstate)->sjf;
+
+		ExplainPropertyInteger("SemiJoin Filter Size", "kB", (sjf->filter->m) / 8 / 1024, es);
+		ExplainPropertyInteger("SemiJoin Filter Hashes", NULL, sjf->filter->k_hash_funcs, es);
+		ExplainPropertyFloat("SemiJoin Actual Filtering Rate", NULL, ((double) sjf->elements_filtered) / sjf->elements_checked, 4, es);
+	}
+}
+
+static void
+show_semijoin_filter_info(SemijoinFilterScanData * sj_md, PlanState *planstate, List *ancestors, ExplainState *es)
+{
+	if (sj_md->is_building_node)
+	{
+		show_expression_list(sj_md->semijoin_keys,
+							 "Semijoin Filter building based on", "{ ", ", ", " }",
+							 planstate, ancestors, es);
+	}
+	else
+	{
+		show_expression_list(sj_md->semijoin_keys,
+							 "Semijoin Filter checking based on", "{ ", ", ", " }",
+							 planstate, ancestors, es);
+	}
+}
+
+static void
+show_semijoin_filters_info(Plan *plan, PlanState *planstate, List *ancestors, ExplainState *es)
+{
+	List	   *sj_md_list = plan->sj_md_list;
+
+	if (sj_md_list != NIL)
+	{
+		ListCell   *lc;
+
+		foreach(lc, sj_md_list)
+		{
+			show_semijoin_filter_info((SemijoinFilterScanData *) lfirst(lc), planstate, ancestors, es);
+		}
+	}
+}
diff --git a/src/backend/lib/bloomfilter.c b/src/backend/lib/bloomfilter.c
index 743bd7388a..52a389d70b 100644
--- a/src/backend/lib/bloomfilter.c
+++ b/src/backend/lib/bloomfilter.c
@@ -41,16 +41,6 @@
 
 #define MAX_HASH_FUNCS		10
 
-struct bloom_filter
-{
-	/* K hash functions are used, seeded by caller's seed */
-	int			k_hash_funcs;
-	uint64		seed;
-	/* m is bitset size, in bits.  Must be a power of two <= 2^32.  */
-	uint64		m;
-	unsigned char bitset[FLEXIBLE_ARRAY_MEMBER];
-};
-
 static int	my_bloom_power(uint64 target_bitset_bits);
 static int	optimal_k(uint64 bitset_bits, int64 total_elems);
 static void k_hashes(bloom_filter *filter, uint32 *hashes, unsigned char *elem,
diff --git a/src/include/lib/bloomfilter.h b/src/include/lib/bloomfilter.h
index 3b5d1821a5..68d54a8ba8 100644
--- a/src/include/lib/bloomfilter.h
+++ b/src/include/lib/bloomfilter.h
@@ -15,7 +15,15 @@
 
 #include "utils/dsa.h"
 
-typedef struct bloom_filter bloom_filter;
+typedef struct bloom_filter
+{
+	/* K hash functions are used, seeded by caller's seed */
+	int			k_hash_funcs;
+	uint64		seed;
+	/* m is bitset size, in bits.  Must be a power of two <= 2^32.  */
+	uint64		m;
+	unsigned char bitset[FLEXIBLE_ARRAY_MEMBER];
+} bloom_filter;
 
 extern bloom_filter *bloom_create(int64 total_elems, int bloom_work_mem,
 								  uint64 seed);
-- 
2.37.1

