Hi all,

This is patch for TODO item: /Improve ability to display optimizer analysis using OPTIMIZER_DEBUG
/

As per as suggestion in the mailing list which is to replace current mechanism of getting optimizer log via OPTIMIZER_DEBUG macro

to something more configurable (which doesn't require rebuilding postgres from source code). This patch replaces /OPTIMIZER_DEBUG
/

by introducing a//GUC /show_optimizer_log /which can be configured on and off to//display (or hide)//previously generated log from stdout to postmaster's log.

Please check attached patch, any feedback is appreciated.

--
Regards,
Ankit Kumar Pandey
From 420f0ed81d233bf63a10104d1164726b138bce83 Mon Sep 17 00:00:00 2001
From: Ankit Kumar Pandey <itsanki...@gmail.com>
Date: Sun, 25 Dec 2022 21:26:32 +0530
Subject: [PATCH] Improve ability to display optimizer analysis using
 OPTIMIZER_DEBUG

Currently optimizer logs are enabled as build flag. This patch adds a
GUC variable to enable or disable optimizer logs and thus does away with
build requirements.
---
 src/backend/nodes/print.c                     | 107 ++++++++++++++
 src/backend/optimizer/path/allpaths.c         | 134 ++++++++++--------
 src/backend/optimizer/plan/planner.c          |  11 +-
 src/backend/utils/misc/guc_tables.c           |  11 ++
 src/backend/utils/misc/postgresql.conf.sample |   1 +
 src/include/nodes/print.h                     |   1 +
 src/include/optimizer/optimizer.h             |   1 +
 src/include/optimizer/paths.h                 |   2 -
 8 files changed, 200 insertions(+), 68 deletions(-)

diff --git a/src/backend/nodes/print.c b/src/backend/nodes/print.c
index a5c44adc6c..c33db27142 100644
--- a/src/backend/nodes/print.c
+++ b/src/backend/nodes/print.c
@@ -418,6 +418,113 @@ print_expr(const Node *expr, const List *rtable)
 		printf("unknown expr");
 }
 
+
+
+/*
+ * format_expr
+ *	  stores an expression to given string for logging
+ */
+void
+format_expr(const Node *expr, const List *rtable, StringInfoData *str)
+{
+	if (expr == NULL)
+	{
+		appendStringInfo(str, "<>");
+		return;
+	}
+
+	if (IsA(expr, Var))
+	{
+		const Var  *var = (const Var *) expr;
+		char	   *relname,
+				   *attname;
+
+		switch (var->varno)
+		{
+			case INNER_VAR:
+				relname = "INNER";
+				attname = "?";
+				break;
+			case OUTER_VAR:
+				relname = "OUTER";
+				attname = "?";
+				break;
+			case INDEX_VAR:
+				relname = "INDEX";
+				attname = "?";
+				break;
+			default:
+				{
+					RangeTblEntry *rte;
+
+					Assert(var->varno > 0 &&
+						   (int) var->varno <= list_length(rtable));
+					rte = rt_fetch(var->varno, rtable);
+					relname = rte->eref->aliasname;
+					attname = get_rte_attribute_name(rte, var->varattno);
+				}
+				break;
+		}
+		appendStringInfo(str, "%s.%s", relname, attname);
+	}
+	else if (IsA(expr, Const))
+	{
+		const Const *c = (const Const *) expr;
+		Oid			typoutput;
+		bool		typIsVarlena;
+		char	   *outputstr;
+
+		if (c->constisnull)
+		{
+			appendStringInfo(str, "NULL");
+			return;
+		}
+
+		getTypeOutputInfo(c->consttype,
+						  &typoutput, &typIsVarlena);
+
+		outputstr = OidOutputFunctionCall(typoutput, c->constvalue);
+		appendStringInfo(str, "%s", outputstr);
+		pfree(outputstr);
+	}
+	else if (IsA(expr, OpExpr))
+	{
+		const OpExpr *e = (const OpExpr *) expr;
+		char	   *opname;
+
+		opname = get_opname(e->opno);
+		if (list_length(e->args) > 1)
+		{
+			print_expr(get_leftop((const Expr *) e), rtable);
+			appendStringInfo(str, " %s ", ((opname != NULL) ? opname : "(invalid operator)"));
+			print_expr(get_rightop((const Expr *) e), rtable);
+		}
+		else
+		{
+			appendStringInfo(str, "%s ", ((opname != NULL) ? opname : "(invalid operator)"));
+			print_expr(get_leftop((const Expr *) e), rtable);
+		}
+	}
+	else if (IsA(expr, FuncExpr))
+	{
+		const FuncExpr *e = (const FuncExpr *) expr;
+		char	   *funcname;
+		ListCell   *l;
+
+		funcname = get_func_name(e->funcid);
+		appendStringInfo(str, "%s(", ((funcname != NULL) ? funcname : "(invalid function)"));
+		foreach(l, e->args)
+		{
+			print_expr(lfirst(l), rtable);
+			if (lnext(e->args, l))
+				appendStringInfo(str, ",");
+		}
+		appendStringInfo(str, ")");
+	}
+	else
+		appendStringInfo(str, "unknown expr");
+}
+
 /*
  * print_pathkeys -
  *	  pathkeys list of PathKeys
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 0cfa3a1d6c..38cbc365b3 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -28,9 +28,7 @@
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "nodes/supportnodes.h"
-#ifdef OPTIMIZER_DEBUG
 #include "nodes/print.h"
-#endif
 #include "optimizer/appendinfo.h"
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
@@ -65,6 +63,7 @@ bool		enable_geqo = false;	/* just in case GUC doesn't set it */
 int			geqo_threshold;
 int			min_parallel_table_scan_size;
 int			min_parallel_index_scan_size;
+bool		show_optimizer_log = false;
 
 /* Hook for plugins to get control in set_rel_pathlist() */
 set_rel_pathlist_hook_type set_rel_pathlist_hook = NULL;
@@ -564,9 +563,12 @@ set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
 	/* Now find the cheapest of the paths for this rel */
 	set_cheapest(rel);
 
-#ifdef OPTIMIZER_DEBUG
-	debug_print_rel(root, rel);
-#endif
+	if (show_optimizer_log)
+	{
+		debug_print_rel(root, rel);
+	}
+	
+
 }
 
 /*
@@ -3435,9 +3437,10 @@ standard_join_search(PlannerInfo *root, int levels_needed, List *initial_rels)
 			/* Find and save the cheapest paths for this rel */
 			set_cheapest(rel);
 
-#ifdef OPTIMIZER_DEBUG
-			debug_print_rel(root, rel);
-#endif
+			if (show_optimizer_log)
+			{
+				debug_print_rel(root, rel);
+			}
 		}
 	}
 
@@ -4288,9 +4291,10 @@ generate_partitionwise_join_paths(PlannerInfo *root, RelOptInfo *rel)
 		if (IS_DUMMY_REL(child_rel))
 			continue;
 
-#ifdef OPTIMIZER_DEBUG
-		debug_print_rel(root, child_rel);
-#endif
+		if (show_optimizer_log)
+		{
+			debug_print_rel(root, child_rel);
+		}
 
 		live_children = lappend(live_children, child_rel);
 	}
@@ -4312,10 +4316,9 @@ generate_partitionwise_join_paths(PlannerInfo *root, RelOptInfo *rel)
  *			DEBUG SUPPORT
  *****************************************************************************/
 
-#ifdef OPTIMIZER_DEBUG
 
 static void
-print_relids(PlannerInfo *root, Relids relids)
+print_relids(PlannerInfo *root, Relids relids, StringInfoData *str)
 {
 	int			x;
 	bool		first = true;
@@ -4324,18 +4327,18 @@ print_relids(PlannerInfo *root, Relids relids)
 	while ((x = bms_next_member(relids, x)) >= 0)
 	{
 		if (!first)
-			printf(" ");
+			appendStringInfo(str, " ");
 		if (x < root->simple_rel_array_size &&
 			root->simple_rte_array[x])
-			printf("%s", root->simple_rte_array[x]->eref->aliasname);
+			appendStringInfo(str, "%s", root->simple_rte_array[x]->eref->aliasname);
 		else
-			printf("%d", x);
+			appendStringInfo(str, "%d", x);
 		first = false;
 	}
 }
 
 static void
-print_restrictclauses(PlannerInfo *root, List *clauses)
+print_restrictclauses(PlannerInfo *root, List *clauses, StringInfoData *str)
 {
 	ListCell   *l;
 
@@ -4343,14 +4346,14 @@ print_restrictclauses(PlannerInfo *root, List *clauses)
 	{
 		RestrictInfo *c = lfirst(l);
 
-		print_expr((Node *) c->clause, root->parse->rtable);
+		format_expr((Node *) c->clause, root->parse->rtable, str);
 		if (lnext(clauses, l))
-			printf(", ");
+			appendStringInfoString(str, ", ");
 	}
 }
 
 static void
-print_path(PlannerInfo *root, Path *path, int indent)
+print_path(PlannerInfo *root, Path *path, int indent, StringInfoData *str)
 {
 	const char *ptype;
 	bool		join = false;
@@ -4522,29 +4525,29 @@ print_path(PlannerInfo *root, Path *path, int indent)
 	}
 
 	for (i = 0; i < indent; i++)
-		printf("\t");
-	printf("%s", ptype);
+		appendStringInfoString(str, "\t");
+	appendStringInfoString(str, ptype);
 
 	if (path->parent)
 	{
-		printf("(");
-		print_relids(root, path->parent->relids);
-		printf(")");
+		appendStringInfoString(str, "(");
+		print_relids(root, path->parent->relids, str);
+		appendStringInfoString(str, ")");
 	}
 	if (path->param_info)
 	{
-		printf(" required_outer (");
-		print_relids(root, path->param_info->ppi_req_outer);
-		printf(")");
+		appendStringInfoString(str, " required_outer (");
+		print_relids(root, path->param_info->ppi_req_outer, str);
+		appendStringInfoString(str, ")");
 	}
-	printf(" rows=%.0f cost=%.2f..%.2f\n",
+	appendStringInfo(str, " rows=%.0f cost=%.2f..%.2f\n",
 		   path->rows, path->startup_cost, path->total_cost);
 
 	if (path->pathkeys)
 	{
 		for (i = 0; i < indent; i++)
-			printf("\t");
-		printf("  pathkeys: ");
+			appendStringInfoString(str, "\t");
+		appendStringInfoString(str, "  pathkeys: ");
 		print_pathkeys(path->pathkeys, root->parse->rtable);
 	}
 
@@ -4553,75 +4556,86 @@ print_path(PlannerInfo *root, Path *path, int indent)
 		JoinPath   *jp = (JoinPath *) path;
 
 		for (i = 0; i < indent; i++)
-			printf("\t");
-		printf("  clauses: ");
-		print_restrictclauses(root, jp->joinrestrictinfo);
-		printf("\n");
+			appendStringInfoString(str, "\t");
+		appendStringInfoString(str, "  clauses: ");
+		print_restrictclauses(root, jp->joinrestrictinfo, str);
+		appendStringInfoString(str, "\n");
 
 		if (IsA(path, MergePath))
 		{
 			MergePath  *mp = (MergePath *) path;
 
 			for (i = 0; i < indent; i++)
-				printf("\t");
-			printf("  sortouter=%d sortinner=%d materializeinner=%d\n",
+				appendStringInfoString(str, "\t");
+			appendStringInfo(str, "  sortouter=%d sortinner=%d materializeinner=%d\n",
 				   ((mp->outersortkeys) ? 1 : 0),
 				   ((mp->innersortkeys) ? 1 : 0),
 				   ((mp->materialize_inner) ? 1 : 0));
 		}
 
-		print_path(root, jp->outerjoinpath, indent + 1);
-		print_path(root, jp->innerjoinpath, indent + 1);
+		print_path(root, jp->outerjoinpath, indent + 1, str);
+		print_path(root, jp->innerjoinpath, indent + 1, str);
 	}
 
 	if (subpath)
-		print_path(root, subpath, indent + 1);
+		print_path(root, subpath, indent + 1, str);
+
 }
 
 void
 debug_print_rel(PlannerInfo *root, RelOptInfo *rel)
 {
 	ListCell   *l;
+	StringInfoData str;
+	initStringInfo(&str);
 
-	printf("RELOPTINFO (");
-	print_relids(root, rel->relids);
-	printf("): rows=%.0f width=%d\n", rel->rows, rel->reltarget->width);
+	appendStringInfo(&str, "RELOPTINFO (");
+	print_relids(root, rel->relids, &str);
+	appendStringInfo(&str, "): rows=%.0f width=%d\n", rel->rows, rel->reltarget->width);
 
 	if (rel->baserestrictinfo)
 	{
-		printf("\tbaserestrictinfo: ");
-		print_restrictclauses(root, rel->baserestrictinfo);
-		printf("\n");
+		appendStringInfo(&str, "\tbaserestrictinfo: ");
+		print_restrictclauses(root, rel->baserestrictinfo, &str);
+		appendStringInfo(&str, "\n");
 	}
 
 	if (rel->joininfo)
 	{
-		printf("\tjoininfo: ");
-		print_restrictclauses(root, rel->joininfo);
-		printf("\n");
+		appendStringInfo(&str, "\tjoininfo: ");
+		print_restrictclauses(root, rel->joininfo, &str);
+		appendStringInfo(&str, "\n");
 	}
 
-	printf("\tpath list:\n");
+	appendStringInfo(&str, "\tpath list:\n");
 	foreach(l, rel->pathlist)
-		print_path(root, lfirst(l), 1);
+		print_path(root, lfirst(l), 1, &str);
 	if (rel->cheapest_parameterized_paths)
 	{
-		printf("\n\tcheapest parameterized paths:\n");
+		appendStringInfo(&str, "\n\tcheapest parameterized paths:\n");
 		foreach(l, rel->cheapest_parameterized_paths)
-			print_path(root, lfirst(l), 1);
+			print_path(root, lfirst(l), 1, &str);
 	}
 	if (rel->cheapest_startup_path)
 	{
-		printf("\n\tcheapest startup path:\n");
-		print_path(root, rel->cheapest_startup_path, 1);
+		appendStringInfo(&str, "\n\tcheapest startup path:\n");
+		print_path(root, rel->cheapest_startup_path, 1, &str);
 	}
 	if (rel->cheapest_total_path)
 	{
-		printf("\n\tcheapest total path:\n");
-		print_path(root, rel->cheapest_total_path, 1);
+		appendStringInfo(&str, "\n\tcheapest total path:\n");
+		print_path(root, rel->cheapest_total_path, 1, &str);
 	}
-	printf("\n");
-	fflush(stdout);
+
+	/* remove trailing newline */
+	if (str.data[str.len - 1] == '\n')
+		str.data[--str.len] = '\0';
+
+	ereport(LOG,
+		   (errmsg_internal("Optimizer statistics"),
+			errdetail_internal("%s", str.data)));
+
+	pfree(str.data);
 }
 
-#endif							/* OPTIMIZER_DEBUG */
+						/* OPTIMIZER_DEBUG */
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 8a41e1e6d3..e8e897ea8f 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -38,9 +38,7 @@
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
-#ifdef OPTIMIZER_DEBUG
 #include "nodes/print.h"
-#endif
 #include "nodes/supportnodes.h"
 #include "optimizer/appendinfo.h"
 #include "optimizer/clauses.h"
@@ -1143,10 +1141,11 @@ preprocess_expression(PlannerInfo *root, Node *expr, int kind)
 	{
 		expr = (Node *) canonicalize_qual((Expr *) expr, false);
 
-#ifdef OPTIMIZER_DEBUG
-		printf("After canonicalize_qual()\n");
-		pprint(expr);
-#endif
+		if (show_optimizer_log)
+		{
+			elog_node_display(LOG, "Result after canonicalize_qual", expr, true);
+		}
+
 	}
 
 	/*
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 436afe1d21..724e457acb 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -1007,6 +1007,17 @@ struct config_bool ConfigureNamesBool[] =
 		true,
 		NULL, NULL, NULL
 	},
+	
+	{
+		{"show_optimizer_log", PGC_USERSET, QUERY_TUNING_OTHER,
+			gettext_noop("Shows debugging logs from optimizer"),
+			NULL,
+			GUC_EXPLAIN
+		},
+		&show_optimizer_log,
+		false,
+		NULL, NULL, NULL
+	},
 	{
 		/* Not for general use --- used by SET SESSION AUTHORIZATION */
 		{"is_superuser", PGC_INTERNAL, UNGROUPED,
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 5afdeb04de..eb3fbfd462 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -433,6 +433,7 @@
 #plan_cache_mode = auto			# auto, force_generic_plan or
 					# force_custom_plan
 #recursive_worktable_factor = 10.0	# range 0.001-1000000
+#show_optimizer_log = off			# on to show debug info from optimizer
 
 
 #------------------------------------------------------------------------------
diff --git a/src/include/nodes/print.h b/src/include/nodes/print.h
index be5e2287f3..5d7bb97f44 100644
--- a/src/include/nodes/print.h
+++ b/src/include/nodes/print.h
@@ -27,6 +27,7 @@ extern char *format_node_dump(const char *dump);
 extern char *pretty_format_node_dump(const char *dump);
 extern void print_rt(const List *rtable);
 extern void print_expr(const Node *expr, const List *rtable);
+extern void format_expr(const Node *expr, const List *rtable, StringInfoData *str);
 extern void print_pathkeys(const List *pathkeys, const List *rtable);
 extern void print_tl(const List *tlist, const List *rtable);
 extern void print_slot(TupleTableSlot *slot);
diff --git a/src/include/optimizer/optimizer.h b/src/include/optimizer/optimizer.h
index 409005bae9..a2cf9eb22e 100644
--- a/src/include/optimizer/optimizer.h
+++ b/src/include/optimizer/optimizer.h
@@ -88,6 +88,7 @@ extern PGDLLIMPORT double parallel_tuple_cost;
 extern PGDLLIMPORT double parallel_setup_cost;
 extern PGDLLIMPORT double recursive_worktable_factor;
 extern PGDLLIMPORT int effective_cache_size;
+extern PGDLLIMPORT bool show_optimizer_log;
 
 extern double clamp_row_est(double nrows);
 extern long clamp_cardinality_to_long(Cardinality x);
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index 41f765d342..7802f00a5b 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -63,9 +63,7 @@ extern void create_partial_bitmap_paths(PlannerInfo *root, RelOptInfo *rel,
 extern void generate_partitionwise_join_paths(PlannerInfo *root,
 											  RelOptInfo *rel);
 
-#ifdef OPTIMIZER_DEBUG
 extern void debug_print_rel(PlannerInfo *root, RelOptInfo *rel);
-#endif
 
 /*
  * indxpath.c
-- 
2.37.2

Reply via email to