Subject: [PATCH] Allow including configuration parameters in explain output

From: Tomas Vondra <[email-masked]>
Date: Sat, 23 Feb 2019 23:38:30 +0100

When analyzing execution plans, it's useful to know which configuration
parameters affecting the planning were modified, and how.  This commit
adds SETTINGS option for EXPLAIN command, which includes GUC parameters
with non-default value and marked with GUC_EXPLAIN flag. auto_explain is
extended with log_settings option, doing the same thing.
---
 contrib/auto_explain/auto_explain.c |  13 ++
 doc/src/sgml/auto-explain.sgml      |  18 +++
 doc/src/sgml/ref/explain.sgml       |  12 ++
 src/backend/commands/explain.c      |  71 +++++++++
 src/backend/utils/misc/guc.c        | 229 +++++++++++++++++++++-------
 src/include/commands/explain.h      |   1 +
 src/include/utils/guc.h             |   2 +
 src/include/utils/guc_tables.h      |   1 +
 8 files changed, 295 insertions(+), 52 deletions(-)

diff --git a/contrib/auto_explain/auto_explain.c b/contrib/auto_explain/auto_explain.c
index 7b22927674..edc50f9368 100644
--- a/contrib/auto_explain/auto_explain.c
+++ b/contrib/auto_explain/auto_explain.c
@@ -28,6 +28,7 @@ static bool auto_explain_log_verbose = false;
 static bool auto_explain_log_buffers = false;
 static bool auto_explain_log_triggers = false;
 static bool auto_explain_log_timing = true;
+static bool auto_explain_log_settings = false;
 static int	auto_explain_log_format = EXPLAIN_FORMAT_TEXT;
 static int	auto_explain_log_level = LOG;
 static bool auto_explain_log_nested_statements = false;
@@ -112,6 +113,17 @@ _PG_init(void)
 							 NULL,
 							 NULL);
 
+	DefineCustomBoolVariable("auto_explain.log_settings",
+							 "Log modified configuration parameters affecting query planning.",
+							 NULL,
+							 &auto_explain_log_settings,
+							 false,
+							 PGC_SUSET,
+							 0,
+							 NULL,
+							 NULL,
+							 NULL);
+
 	DefineCustomBoolVariable("auto_explain.log_verbose",
 							 "Use EXPLAIN VERBOSE for plan logging.",
 							 NULL,
@@ -356,6 +368,7 @@ explain_ExecutorEnd(QueryDesc *queryDesc)
 			es->timing = (es->analyze && auto_explain_log_timing);
 			es->summary = es->analyze;
 			es->format = auto_explain_log_format;
+			es->settings = auto_explain_log_settings;
 
 			ExplainBeginOutput(es);
 			ExplainQueryText(es, queryDesc);
diff --git a/doc/src/sgml/auto-explain.sgml b/doc/src/sgml/auto-explain.sgml
index 120b168d45..296ae2de80 100644
--- a/doc/src/sgml/auto-explain.sgml
+++ b/doc/src/sgml/auto-explain.sgml
@@ -169,6 +169,24 @@ LOAD 'auto_explain';
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term>
+     <varname>auto_explain.log_settings</varname> (<type>boolean</type>)
+     <indexterm>
+      <primary><varname>auto_explain.log_settings</varname> configuration parameter</primary>
+     </indexterm>
+    </term>
+    <listitem>
+     <para>
+      <varname>auto_explain.log_settings</varname> controls whether information
+      about modified configuration options affecting query planning are logged
+      with the execution plan.  Only options affecting query planning with value
+      different from the built-in default value are considered.  This parameter is
+      off by default.  Only superusers can change this setting.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term>
      <varname>auto_explain.log_format</varname> (<type>enum</type>)
diff --git a/doc/src/sgml/ref/explain.sgml b/doc/src/sgml/ref/explain.sgml
index 8dc0d7038a..385d10411f 100644
--- a/doc/src/sgml/ref/explain.sgml
+++ b/doc/src/sgml/ref/explain.sgml
@@ -39,6 +39,7 @@ EXPLAIN [ ANALYZE ] [ VERBOSE ] <replaceable class="parameter">statement</replac
     ANALYZE [ <replaceable class="parameter">boolean</replaceable> ]
     VERBOSE [ <replaceable class="parameter">boolean</replaceable> ]
     COSTS [ <replaceable class="parameter">boolean</replaceable> ]
+    SETTINGS [ <replaceable class="parameter">boolean</replaceable> ]
     BUFFERS [ <replaceable class="parameter">boolean</replaceable> ]
     TIMING [ <replaceable class="parameter">boolean</replaceable> ]
     SUMMARY [ <replaceable class="parameter">boolean</replaceable> ]
@@ -152,6 +153,17 @@ ROLLBACK;
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>SETTINGS</literal></term>
+    <listitem>
+     <para>
+      Include information on configuration parameters.  Specifically, include
+      options affecting query planning with value different from the built-in
+      default value.  This parameter defaults to <literal>FALSE</literal>.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>BUFFERS</literal></term>
     <listitem>
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 1831ea81cf..8e48b94d4a 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -29,6 +29,7 @@
 #include "storage/bufmgr.h"
 #include "tcop/tcopprot.h"
 #include "utils/builtins.h"
+#include "utils/guc_tables.h"
 #include "utils/json.h"
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
@@ -162,6 +163,8 @@ ExplainQuery(ParseState *pstate, ExplainStmt *stmt, const char *queryString,
 			es->costs = defGetBoolean(opt);
 		else if (strcmp(opt->defname, "buffers") == 0)
 			es->buffers = defGetBoolean(opt);
+		else if (strcmp(opt->defname, "settings") == 0)
+			es->settings = defGetBoolean(opt);
 		else if (strcmp(opt->defname, "timing") == 0)
 		{
 			timing_set = true;
@@ -596,6 +599,68 @@ ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into, ExplainState *es,
 	ExplainCloseGroup("Query", NULL, true, es);
 }
 
+static void
+ExplainShowSettings(ExplainState *es)
+{
+	int		num;
+	struct config_generic **gucs;
+
+	/* bail out if GUC information not requested */
+	if (!es->settings)
+		return;
+
+	gucs = get_explain_guc_options(&num);
+
+	/* also bail out of there are no options */
+	if (!num)
+		return;
+
+	if (es->format != EXPLAIN_FORMAT_TEXT)
+	{
+		int		i;
+
+		ExplainOpenGroup("Settings", "Settings", false, es);
+
+		for (i = 0; i < num; i++)
+		{
+			char *setting;
+			struct config_generic *conf = gucs[i];
+
+			setting = GetConfigOptionByName(conf->name, NULL, true);
+
+			ExplainPropertyText(conf->name, setting, es);
+		}
+
+		ExplainCloseGroup("Settings", "Settings", false, es);
+	}
+	else
+	{
+		int		i;
+		StringInfoData	str;
+
+		initStringInfo(&str);
+
+		for (i = 0; i < num; i++)
+		{
+			char *setting;
+			struct config_generic *conf = gucs[i];
+
+			if (i > 0)
+				appendStringInfoString(&str, ", ");
+
+			setting = GetConfigOptionByName(conf->name, NULL, true);
+
+			if (setting)
+				appendStringInfo(&str, "%s = '%s'", conf->name, setting);
+			else
+				appendStringInfo(&str, "%s = NULL", conf->name);
+		}
+
+		if (num > 0)
+			ExplainPropertyText("Settings", str.data, es);
+	}
+}
+
 /*
  * ExplainPrintPlan -
  *	  convert a QueryDesc's plan tree to text and append it to es->str
@@ -633,6 +698,12 @@ ExplainPrintPlan(ExplainState *es, QueryDesc *queryDesc)
 	if (IsA(ps, GatherState) &&((Gather *) ps->plan)->invisible)
 		ps = outerPlanState(ps);
 	ExplainNode(ps, NIL, NULL, NULL, es);
+
+	/*
+	 * If requested, include information about GUC parameters that don't
+	 * match the built-in defaults.
+	 */
+	ExplainShowSettings(es);
 }
 
 /*
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 156d147c85..8df5156953 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -884,7 +884,8 @@ static struct config_bool ConfigureNamesBool[] =
 	{
 		{"enable_seqscan", PGC_USERSET, QUERY_TUNING_METHOD,
 			gettext_noop("Enables the planner's use of sequential-scan plans."),
-			NULL
+			NULL,
+			GUC_EXPLAIN
 		},
 		&enable_seqscan,
 		true,
@@ -893,7 +894,8 @@ static struct config_bool ConfigureNamesBool[] =
 	{
 		{"enable_indexscan", PGC_USERSET, QUERY_TUNING_METHOD,
 			gettext_noop("Enables the planner's use of index-scan plans."),
-			NULL
+			NULL,
+			GUC_EXPLAIN
 		},
 		&enable_indexscan,
 		true,
@@ -902,7 +904,8 @@ static struct config_bool ConfigureNamesBool[] =
 	{
 		{"enable_indexonlyscan", PGC_USERSET, QUERY_TUNING_METHOD,
 			gettext_noop("Enables the planner's use of index-only-scan plans."),
-			NULL
+			NULL,
+			GUC_EXPLAIN
 		},
 		&enable_indexonlyscan,
 		true,
@@ -911,7 +914,8 @@ static struct config_bool ConfigureNamesBool[] =
 	{
 		{"enable_bitmapscan", PGC_USERSET, QUERY_TUNING_METHOD,
 			gettext_noop("Enables the planner's use of bitmap-scan plans."),
-			NULL
+			NULL,
+			GUC_EXPLAIN
 		},
 		&enable_bitmapscan,
 		true,
@@ -920,7 +924,8 @@ static struct config_bool ConfigureNamesBool[] =
 	{
 		{"enable_tidscan", PGC_USERSET, QUERY_TUNING_METHOD,
 			gettext_noop("Enables the planner's use of TID scan plans."),
-			NULL
+			NULL,
+			GUC_EXPLAIN
 		},
 		&enable_tidscan,
 		true,
@@ -929,7 +934,8 @@ static struct config_bool ConfigureNamesBool[] =
 	{
 		{"enable_sort", PGC_USERSET, QUERY_TUNING_METHOD,
 			gettext_noop("Enables the planner's use of explicit sort steps."),
-			NULL
+			NULL,
+			GUC_EXPLAIN
 		},
 		&enable_sort,
 		true,
@@ -938,7 +944,8 @@ static struct config_bool ConfigureNamesBool[] =
 	{
 		{"enable_hashagg", PGC_USERSET, QUERY_TUNING_METHOD,
 			gettext_noop("Enables the planner's use of hashed aggregation plans."),
-			NULL
+			NULL,
+			GUC_EXPLAIN
 		},
 		&enable_hashagg,
 		true,
@@ -947,7 +954,8 @@ static struct config_bool ConfigureNamesBool[] =
 	{
 		{"enable_material", PGC_USERSET, QUERY_TUNING_METHOD,
 			gettext_noop("Enables the planner's use of materialization."),
-			NULL
+			NULL,
+			GUC_EXPLAIN
 		},
 		&enable_material,
 		true,
@@ -956,7 +964,8 @@ static struct config_bool ConfigureNamesBool[] =
 	{
 		{"enable_nestloop", PGC_USERSET, QUERY_TUNING_METHOD,
 			gettext_noop("Enables the planner's use of nested-loop join plans."),
-			NULL
+			NULL,
+			GUC_EXPLAIN
 		},
 		&enable_nestloop,
 		true,
@@ -965,7 +974,8 @@ static struct config_bool ConfigureNamesBool[] =
 	{
 		{"enable_mergejoin", PGC_USERSET, QUERY_TUNING_METHOD,
 			gettext_noop("Enables the planner's use of merge join plans."),
-			NULL
+			NULL,
+			GUC_EXPLAIN
 		},
 		&enable_mergejoin,
 		true,
@@ -974,7 +984,8 @@ static struct config_bool ConfigureNamesBool[] =
 	{
 		{"enable_hashjoin", PGC_USERSET, QUERY_TUNING_METHOD,
 			gettext_noop("Enables the planner's use of hash join plans."),
-			NULL
+			NULL,
+			GUC_EXPLAIN
 		},
 		&enable_hashjoin,
 		true,
@@ -983,7 +994,8 @@ static struct config_bool ConfigureNamesBool[] =
 	{
 		{"enable_gathermerge", PGC_USERSET, QUERY_TUNING_METHOD,
 			gettext_noop("Enables the planner's use of gather merge plans."),
-			NULL
+			NULL,
+			GUC_EXPLAIN
 		},
 		&enable_gathermerge,
 		true,
@@ -992,7 +1004,8 @@ static struct config_bool ConfigureNamesBool[] =
 	{
 		{"enable_partitionwise_join", PGC_USERSET, QUERY_TUNING_METHOD,
 			gettext_noop("Enables partitionwise join."),
-			NULL
+			NULL,
+			GUC_EXPLAIN
 		},
 		&enable_partitionwise_join,
 		false,
@@ -1001,7 +1014,8 @@ static struct config_bool ConfigureNamesBool[] =
 	{
 		{"enable_partitionwise_aggregate", PGC_USERSET, QUERY_TUNING_METHOD,
 			gettext_noop("Enables partitionwise aggregation and grouping."),
-			NULL
+			NULL,
+			GUC_EXPLAIN
 		},
 		&enable_partitionwise_aggregate,
 		false,
@@ -1010,7 +1024,8 @@ static struct config_bool ConfigureNamesBool[] =
 	{
 		{"enable_parallel_append", PGC_USERSET, QUERY_TUNING_METHOD,
 			gettext_noop("Enables the planner's use of parallel append plans."),
-			NULL
+			NULL,
+			GUC_EXPLAIN
 		},
 		&enable_parallel_append,
 		true,
@@ -1019,7 +1034,8 @@ static struct config_bool ConfigureNamesBool[] =
 	{
 		{"enable_parallel_hash", PGC_USERSET, QUERY_TUNING_METHOD,
 			gettext_noop("Enables the planner's use of parallel hash plans."),
-			NULL
+			NULL,
+			GUC_EXPLAIN
 		},
 		&enable_parallel_hash,
 		true,
@@ -1030,7 +1046,8 @@ static struct config_bool ConfigureNamesBool[] =
 			gettext_noop("Enable plan-time and run-time partition pruning."),
 			gettext_noop("Allows the query planner and executor to compare partition "
 						 "bounds to conditions in the query to determine which "
-						 "partitions must be scanned.")
+						 "partitions must be scanned."),
+			GUC_EXPLAIN
 		},
 		&enable_partition_pruning,
 		true,
@@ -1040,7 +1057,8 @@ static struct config_bool ConfigureNamesBool[] =
 		{"geqo", PGC_USERSET, QUERY_TUNING_GEQO,
 			gettext_noop("Enables genetic query optimization."),
 			gettext_noop("This algorithm attempts to do planning without "
-						 "exhaustive searching.")
+						 "exhaustive searching."),
+			GUC_EXPLAIN
 		},
 		&enable_geqo,
 		true,
@@ -1628,7 +1646,7 @@ static struct config_bool ConfigureNamesBool[] =
 			"optimize_bounded_sort", PGC_USERSET, QUERY_TUNING_METHOD,
 			gettext_noop("Enable bounded sorting using heap sort."),
 			NULL,
-			GUC_NOT_IN_SAMPLE
+			GUC_NOT_IN_SAMPLE | GUC_EXPLAIN
 		},
 		&optimize_bounded_sort,
 		true,
@@ -1819,7 +1837,8 @@ static struct config_bool ConfigureNamesBool[] =
 	{
 		{"parallel_leader_participation", PGC_USERSET, RESOURCES_ASYNCHRONOUS,
 			gettext_noop("Controls whether Gather and Gather Merge also run subplans."),
-			gettext_noop("Should gather nodes also run subplans, or just gather tuples?")
+			gettext_noop("Should gather nodes also run subplans, or just gather tuples?"),
+			GUC_EXPLAIN
 		},
 		&parallel_leader_participation,
 		true,
@@ -1829,7 +1848,8 @@ static struct config_bool ConfigureNamesBool[] =
 	{
 		{"jit", PGC_USERSET, QUERY_TUNING_OTHER,
 			gettext_noop("Allow JIT compilation."),
-			NULL
+			NULL,
+			GUC_EXPLAIN
 		},
 		&jit_enabled,
 		true,
@@ -1958,7 +1978,8 @@ static struct config_int ConfigureNamesInt[] =
 						 "are not collapsed."),
 			gettext_noop("The planner will merge subqueries into upper "
 						 "queries if the resulting FROM list would have no more than "
-						 "this many items.")
+						 "this many items."),
+			GUC_EXPLAIN
 		},
 		&from_collapse_limit,
 		8, 1, INT_MAX,
@@ -1970,7 +1991,8 @@ static struct config_int ConfigureNamesInt[] =
 						 "constructs are not flattened."),
 			gettext_noop("The planner will flatten explicit JOIN "
 						 "constructs into lists of FROM items whenever a "
-						 "list of no more than this many items would result.")
+						 "list of no more than this many items would result."),
+			GUC_EXPLAIN
 		},
 		&join_collapse_limit,
 		8, 1, INT_MAX,
@@ -1979,7 +2001,8 @@ static struct config_int ConfigureNamesInt[] =
 	{
 		{"geqo_threshold", PGC_USERSET, QUERY_TUNING_GEQO,
 			gettext_noop("Sets the threshold of FROM items beyond which GEQO is used."),
-			NULL
+			NULL,
+			GUC_EXPLAIN
 		},
 		&geqo_threshold,
 		12, 2, INT_MAX,
@@ -1988,7 +2011,8 @@ static struct config_int ConfigureNamesInt[] =
 	{
 		{"geqo_effort", PGC_USERSET, QUERY_TUNING_GEQO,
 			gettext_noop("GEQO: effort is used to set the default for other GEQO parameters."),
-			NULL
+			NULL,
+			GUC_EXPLAIN
 		},
 		&Geqo_effort,
 		DEFAULT_GEQO_EFFORT, MIN_GEQO_EFFORT, MAX_GEQO_EFFORT,
@@ -1997,7 +2021,8 @@ static struct config_int ConfigureNamesInt[] =
 	{
 		{"geqo_pool_size", PGC_USERSET, QUERY_TUNING_GEQO,
 			gettext_noop("GEQO: number of individuals in the population."),
-			gettext_noop("Zero selects a suitable default value.")
+			gettext_noop("Zero selects a suitable default value."),
+			GUC_EXPLAIN
 		},
 		&Geqo_pool_size,
 		0, 0, INT_MAX,
@@ -2006,7 +2031,8 @@ static struct config_int ConfigureNamesInt[] =
 	{
 		{"geqo_generations", PGC_USERSET, QUERY_TUNING_GEQO,
 			gettext_noop("GEQO: number of iterations of the algorithm."),
-			gettext_noop("Zero selects a suitable default value.")
+			gettext_noop("Zero selects a suitable default value."),
+			GUC_EXPLAIN
 		},
 		&Geqo_generations,
 		0, 0, INT_MAX,
@@ -2120,7 +2146,7 @@ static struct config_int ConfigureNamesInt[] =
 		{"temp_buffers", PGC_USERSET, RESOURCES_MEM,
 			gettext_noop("Sets the maximum number of temporary buffers used by each session."),
 			NULL,
-			GUC_UNIT_BLOCKS
+			GUC_UNIT_BLOCKS | GUC_EXPLAIN
 		},
 		&num_temp_buffers,
 		1024, 100, INT_MAX / 2,
@@ -2187,7 +2213,7 @@ static struct config_int ConfigureNamesInt[] =
 			gettext_noop("This much memory can be used by each internal "
 						 "sort operation and hash table before switching to "
 						 "temporary disk files."),
-			GUC_UNIT_KB
+			GUC_UNIT_KB | GUC_EXPLAIN
 		},
 		&work_mem,
 		4096, 64, MAX_KILOBYTES,
@@ -2738,7 +2764,8 @@ static struct config_int ConfigureNamesInt[] =
 			PGC_USERSET,
 			RESOURCES_ASYNCHRONOUS,
 			gettext_noop("Number of simultaneous requests that can be handled efficiently by the disk subsystem."),
-			gettext_noop("For RAID arrays, this should be approximately the number of drive spindles in the array.")
+			gettext_noop("For RAID arrays, this should be approximately the number of drive spindles in the array."),
+			GUC_EXPLAIN
 		},
 		&effective_io_concurrency,
 #ifdef USE_PREFETCH
@@ -2983,7 +3010,8 @@ static struct config_int ConfigureNamesInt[] =
 	{
 		{"max_parallel_workers_per_gather", PGC_USERSET, RESOURCES_ASYNCHRONOUS,
 			gettext_noop("Sets the maximum number of parallel processes per executor node."),
-			NULL
+			NULL,
+			GUC_EXPLAIN
 		},
 		&max_parallel_workers_per_gather,
 		2, 0, MAX_PARALLEL_WORKER_LIMIT,
@@ -2993,7 +3021,8 @@ static struct config_int ConfigureNamesInt[] =
 	{
 		{"max_parallel_workers", PGC_USERSET, RESOURCES_ASYNCHRONOUS,
 			gettext_noop("Sets the maximum number of parallel workers that can be active at one time."),
-			NULL
+			NULL,
+			GUC_EXPLAIN
 		},
 		&max_parallel_workers,
 		8, 0, MAX_PARALLEL_WORKER_LIMIT,
@@ -3083,7 +3112,7 @@ static struct config_int ConfigureNamesInt[] =
 			gettext_noop("Sets the planner's assumption about the total size of the data caches."),
 			gettext_noop("That is, the total size of the caches (kernel cache and shared buffers) used for PostgreSQL data files. "
 						 "This is measured in disk pages, which are normally 8 kB each."),
-			GUC_UNIT_BLOCKS,
+			GUC_UNIT_BLOCKS | GUC_EXPLAIN,
 		},
 		&effective_cache_size,
 		DEFAULT_EFFECTIVE_CACHE_SIZE, 1, INT_MAX,
@@ -3094,7 +3123,7 @@ static struct config_int ConfigureNamesInt[] =
 		{"min_parallel_table_scan_size", PGC_USERSET, QUERY_TUNING_COST,
 			gettext_noop("Sets the minimum amount of table data for a parallel scan."),
 			gettext_noop("If the planner estimates that it will read a number of table pages too small to reach this limit, a parallel scan will not be considered."),
-			GUC_UNIT_BLOCKS,
+			GUC_UNIT_BLOCKS | GUC_EXPLAIN,
 		},
 		&min_parallel_table_scan_size,
 		(8 * 1024 * 1024) / BLCKSZ, 0, INT_MAX / 3,
@@ -3105,7 +3134,7 @@ static struct config_int ConfigureNamesInt[] =
 		{"min_parallel_index_scan_size", PGC_USERSET, QUERY_TUNING_COST,
 			gettext_noop("Sets the minimum amount of index data for a parallel scan."),
 			gettext_noop("If the planner estimates that it will read a number of index pages too small to reach this limit, a parallel scan will not be considered."),
-			GUC_UNIT_BLOCKS,
+			GUC_UNIT_BLOCKS | GUC_EXPLAIN,
 		},
 		&min_parallel_index_scan_size,
 		(512 * 1024) / BLCKSZ, 0, INT_MAX / 3,
@@ -3170,7 +3199,8 @@ static struct config_real ConfigureNamesReal[] =
 		{"seq_page_cost", PGC_USERSET, QUERY_TUNING_COST,
 			gettext_noop("Sets the planner's estimate of the cost of a "
 						 "sequentially fetched disk page."),
-			NULL
+			NULL,
+			GUC_EXPLAIN
 		},
 		&seq_page_cost,
 		DEFAULT_SEQ_PAGE_COST, 0, DBL_MAX,
@@ -3180,7 +3210,8 @@ static struct config_real ConfigureNamesReal[] =
 		{"random_page_cost", PGC_USERSET, QUERY_TUNING_COST,
 			gettext_noop("Sets the planner's estimate of the cost of a "
 						 "nonsequentially fetched disk page."),
-			NULL
+			NULL,
+			GUC_EXPLAIN
 		},
 		&random_page_cost,
 		DEFAULT_RANDOM_PAGE_COST, 0, DBL_MAX,
@@ -3190,7 +3221,8 @@ static struct config_real ConfigureNamesReal[] =
 		{"cpu_tuple_cost", PGC_USERSET, QUERY_TUNING_COST,
 			gettext_noop("Sets the planner's estimate of the cost of "
 						 "processing each tuple (row)."),
-			NULL
+			NULL,
+			GUC_EXPLAIN
 		},
 		&cpu_tuple_cost,
 		DEFAULT_CPU_TUPLE_COST, 0, DBL_MAX,
@@ -3200,7 +3232,8 @@ static struct config_real ConfigureNamesReal[] =
 		{"cpu_index_tuple_cost", PGC_USERSET, QUERY_TUNING_COST,
 			gettext_noop("Sets the planner's estimate of the cost of "
 						 "processing each index entry during an index scan."),
-			NULL
+			NULL,
+			GUC_EXPLAIN
 		},
 		&cpu_index_tuple_cost,
 		DEFAULT_CPU_INDEX_TUPLE_COST, 0, DBL_MAX,
@@ -3210,7 +3243,8 @@ static struct config_real ConfigureNamesReal[] =
 		{"cpu_operator_cost", PGC_USERSET, QUERY_TUNING_COST,
 			gettext_noop("Sets the planner's estimate of the cost of "
 						 "processing each operator or function call."),
-			NULL
+			NULL,
+			GUC_EXPLAIN
 		},
 		&cpu_operator_cost,
 		DEFAULT_CPU_OPERATOR_COST, 0, DBL_MAX,
@@ -3220,7 +3254,8 @@ static struct config_real ConfigureNamesReal[] =
 		{"parallel_tuple_cost", PGC_USERSET, QUERY_TUNING_COST,
 			gettext_noop("Sets the planner's estimate of the cost of "
 						 "passing each tuple (row) from worker to master backend."),
-			NULL
+			NULL,
+			GUC_EXPLAIN
 		},
 		&parallel_tuple_cost,
 		DEFAULT_PARALLEL_TUPLE_COST, 0, DBL_MAX,
@@ -3230,7 +3265,8 @@ static struct config_real ConfigureNamesReal[] =
 		{"parallel_setup_cost", PGC_USERSET, QUERY_TUNING_COST,
 			gettext_noop("Sets the planner's estimate of the cost of "
 						 "starting up worker processes for parallel query."),
-			NULL
+			NULL,
+			GUC_EXPLAIN
 		},
 		&parallel_setup_cost,
 		DEFAULT_PARALLEL_SETUP_COST, 0, DBL_MAX,
@@ -3240,7 +3276,8 @@ static struct config_real ConfigureNamesReal[] =
 	{
 		{"jit_above_cost", PGC_USERSET, QUERY_TUNING_COST,
 			gettext_noop("Perform JIT compilation if query is more expensive."),
-			gettext_noop("-1 disables JIT compilation.")
+			gettext_noop("-1 disables JIT compilation."),
+			GUC_EXPLAIN
 		},
 		&jit_above_cost,
 		100000, -1, DBL_MAX,
@@ -3250,7 +3287,8 @@ static struct config_real ConfigureNamesReal[] =
 	{
 		{"jit_optimize_above_cost", PGC_USERSET, QUERY_TUNING_COST,
 			gettext_noop("Optimize JITed functions if query is more expensive."),
-			gettext_noop("-1 disables optimization.")
+			gettext_noop("-1 disables optimization."),
+			GUC_EXPLAIN
 		},
 		&jit_optimize_above_cost,
 		500000, -1, DBL_MAX,
@@ -3260,7 +3298,8 @@ static struct config_real ConfigureNamesReal[] =
 	{
 		{"jit_inline_above_cost", PGC_USERSET, QUERY_TUNING_COST,
 			gettext_noop("Perform JIT inlining if query is more expensive."),
-			gettext_noop("-1 disables inlining.")
+			gettext_noop("-1 disables inlining."),
+			GUC_EXPLAIN
 		},
 		&jit_inline_above_cost,
 		500000, -1, DBL_MAX,
@@ -3271,7 +3310,8 @@ static struct config_real ConfigureNamesReal[] =
 		{"cursor_tuple_fraction", PGC_USERSET, QUERY_TUNING_OTHER,
 			gettext_noop("Sets the planner's estimate of the fraction of "
 						 "a cursor's rows that will be retrieved."),
-			NULL
+			NULL,
+			GUC_EXPLAIN
 		},
 		&cursor_tuple_fraction,
 		DEFAULT_CURSOR_TUPLE_FRACTION, 0.0, 1.0,
@@ -3281,7 +3321,8 @@ static struct config_real ConfigureNamesReal[] =
 	{
 		{"geqo_selection_bias", PGC_USERSET, QUERY_TUNING_GEQO,
 			gettext_noop("GEQO: selective pressure within the population."),
-			NULL
+			NULL,
+			GUC_EXPLAIN
 		},
 		&Geqo_selection_bias,
 		DEFAULT_GEQO_SELECTION_BIAS,
@@ -3291,7 +3332,8 @@ static struct config_real ConfigureNamesReal[] =
 	{
 		{"geqo_seed", PGC_USERSET, QUERY_TUNING_GEQO,
 			gettext_noop("GEQO: seed for random path selection."),
-			NULL
+			NULL,
+			GUC_EXPLAIN
 		},
 		&Geqo_seed,
 		0.0, 0.0, 1.0,
@@ -3706,7 +3748,7 @@ static struct config_string ConfigureNamesString[] =
 		{"search_path", PGC_USERSET, CLIENT_CONN_STATEMENT,
 			gettext_noop("Sets the schema search order for names that are not schema-qualified."),
 			NULL,
-			GUC_LIST_INPUT | GUC_LIST_QUOTE
+			GUC_LIST_INPUT | GUC_LIST_QUOTE | GUC_EXPLAIN
 		},
 		&namespace_search_path,
 		"\"$user\", public",
@@ -4159,7 +4201,8 @@ static struct config_enum ConfigureNamesEnum[] =
 		{"constraint_exclusion", PGC_USERSET, QUERY_TUNING_OTHER,
 			gettext_noop("Enables the planner to use constraints to optimize queries."),
 			gettext_noop("Table scans will be skipped if their constraints"
-						 " guarantee that no rows match the query.")
+						 " guarantee that no rows match the query."),
+			GUC_EXPLAIN
 		},
 		&constraint_exclusion,
 		CONSTRAINT_EXCLUSION_PARTITION, constraint_exclusion_options,
@@ -4395,7 +4438,8 @@ static struct config_enum ConfigureNamesEnum[] =
 	{
 		{"force_parallel_mode", PGC_USERSET, QUERY_TUNING_OTHER,
 			gettext_noop("Forces use of parallel query facilities."),
-			gettext_noop("If possible, run query using a parallel worker and with parallel restrictions.")
+			gettext_noop("If possible, run query using a parallel worker and with parallel restrictions."),
+			GUC_EXPLAIN
 		},
 		&force_parallel_mode,
 		FORCE_PARALLEL_OFF, force_parallel_mode_options,
@@ -4419,7 +4463,8 @@ static struct config_enum ConfigureNamesEnum[] =
 			gettext_noop("Controls the planner's selection of custom or generic plan."),
 			gettext_noop("Prepared statements can have custom and generic plans, and the planner "
 						 "will attempt to choose which is better.  This can be set to override "
-						 "the default behavior.")
+						 "the default behavior."),
+			GUC_EXPLAIN
 		},
 		&plan_cache_mode,
 		PLAN_CACHE_MODE_AUTO, plan_cache_mode_options,
@@ -8603,6 +8648,86 @@ ShowAllGUCConfig(DestReceiver *dest)
 	end_tup_output(tstate);
 }
 
+struct config_generic **
+get_explain_guc_options(int *num)
+{
+	int		i;
+	struct config_generic **result;
+
+	*num = 0;
+	result = palloc(sizeof(struct config_generic *) * num_guc_variables);
+
+	for (i = 0; i < num_guc_variables; i++)
+	{
+		bool modified;
+		struct config_generic *conf = guc_variables[i];
+
+		/* return only options visible to the user */
+		if ((conf->flags & GUC_NO_SHOW_ALL) ||
+			((conf->flags & GUC_SUPERUSER_ONLY) &&
+			 !is_member_of_role(GetUserId(), DEFAULT_ROLE_READ_ALL_SETTINGS)))
+			continue;
+
+		/* only parameters explicitly marked for inclusion in explain */
+		if (!(conf->flags & GUC_EXPLAIN))
+			continue;
+
+		/* return only options that were modified (w.r.t. config file) */
+		modified = false;
+
+		switch (conf->vartype)
+		{
+			case PGC_BOOL:
+			{
+				struct config_bool *lconf = (struct config_bool *) conf;
+				modified = (lconf->boot_val != *(lconf->variable));
+			}
+			break;
+
+			case PGC_INT:
+				{
+					struct config_int *lconf = (struct config_int *) conf;
+					modified = (lconf->boot_val != *(lconf->variable));
+				}
+				break;
+
+			case PGC_REAL:
+				{
+					struct config_real *lconf = (struct config_real *) conf;
+					modified = (lconf->boot_val != *(lconf->variable));
+				}
+				break;
+
+			case PGC_STRING:
+				{
+					struct config_string *lconf = (struct config_string *) conf;
+					modified = (strcmp(lconf->boot_val, *(lconf->variable)) != 0);
+				}
+				break;
+
+			case PGC_ENUM:
+				{
+					struct config_enum *lconf = (struct config_enum *) conf;
+					modified = (lconf->boot_val != *(lconf->variable));
+				}
+				break;
+
+			default:
+				elog(ERROR, "unexcpected GUC type: %d", conf->vartype);
+		}
+
+		/* skip GUC variables that match the built-in default */
+		if (!modified)
+			continue;
+
+		/* assign to the values array */
+		result[*num] = conf;
+		*num = *num + 1;
+	}
+
+	return result;
+}
+
 /*
  * Return GUC variable value by name; optionally return canonical form of
  * name.  If the GUC is unset, then throw an error unless missing_ok is true,
diff --git a/src/include/commands/explain.h b/src/include/commands/explain.h
index e8854db459..db48f29501 100644
--- a/src/include/commands/explain.h
+++ b/src/include/commands/explain.h
@@ -35,6 +35,7 @@ typedef struct ExplainState
 	bool		buffers;		/* print buffer usage */
 	bool		timing;			/* print detailed node timing */
 	bool		summary;		/* print total planning and execution timing */
+	bool		settings;		/* print modified settings */
 	ExplainFormat format;		/* output format */
 	/* state for output formatting --- not reset for each new plan tree */
 	int			indent;			/* current indentation level */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index c07e7b945e..90ef3889ae 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -227,6 +227,8 @@ typedef enum
 #define GUC_UNIT_MIN		   0x30000	/* value is in minutes */
 #define GUC_UNIT_TIME		   0xF0000	/* mask for time-related units */
 
+#define GUC_EXPLAIN			  0x100000	/* include in explain */
+
 #define GUC_UNIT				(GUC_UNIT_MEMORY | GUC_UNIT_TIME)

diff --git a/src/include/utils/guc_tables.h b/src/include/utils/guc_tables.h
index a0970b2e1c..2a74b30b2f 100644
--- a/src/include/utils/guc_tables.h
+++ b/src/include/utils/guc_tables.h
@@ -267,5 +267,6 @@ extern void build_guc_variables(void);
 extern const char *config_enum_lookup_by_value(struct config_enum *record, int val);
 extern bool config_enum_lookup_by_name(struct config_enum *record,
 						   const char *value, int *retval);
+extern struct config_generic **get_explain_guc_options(int *num);
 
 #endif							/* GUC_TABLES_H */
-- 
2.20.1

--------------F19466366F3DBE1F52576175--