Hello Sergei, > This patch correlates with my proposal > "add session information column to pg_stat_statements" > https://www.postgresql.org/message-id/3aa097d7-7c47-187b-5913-db8366cd4491%40gmail.com > They both address the problem to identify the factors that make > different execution plans for the same SQL statements. You are > interested in the current settings that affect the execution plan, I'm > concerned about historical data in pg_stat_statements. From my > experience the most often offending settings are > current_schemas/search_path and current_user. Please have in mind that > probably the same approach that you will use to extend explain plan > functionality will be eventually implemented to extend > pg_stat_statements.
Possibly, although I don't have an ambition to export the GUCs into pg_stat_statements in this patch. There's an issue with merging different values of GUCs in different executions of a statement, and it's unclear how to solve that. > I think that the list of the GUCs that are reported > by explain plan should be structured like JSON, something like > extended_settings: { "current_schemas" : ["pg_catalog", "s1", "s2", "public"], > "current_user" : "user1", > "enable_nestloop" : "off", > "work_mem" = "32MB" > } > It is less important for yours use case explain, but is important > for pg_stat_statements case. > The pg_stat_statements is often accessed by monitoring and reporting > tools, and it will be a good idea to have > the data here in the > structured and easily parsed format. Yes, that's a good point. I think it's fine to keep the current format for TEXT output, and use a structured format when the explain format is set to json or yaml. That's what we do for data about Hash nodes, for example (see show_hash_info). So I've done that in the attached v5 of the patch, which now produces something like this: test=# explain (gucs, format json) select * from t; QUERY PLAN --------------------------------- [ + { + "Plan": { + "Node Type": "Seq Scan", + "Parallel Aware": false, + "Relation Name": "t", + "Alias": "t", + "Startup Cost": 0.00, + "Total Cost": 61.00, + "Plan Rows": 2550, + "Plan Width": 4 + }, + "GUC": [ + "cpu_tuple_cost": "0.02",+ "work_mem": "1GB" + ] + } + ] (1 row) The one slightly annoying issue is that currently all the options are formatted as text, including e.g. cpu_tuple_cost. That's because the GUC options may have show hook, so I can't access the value directly (not sure if there's an option around it). regards -- Tomas Vondra http://www.2ndQuadrant.com PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
diff --git a/contrib/auto_explain/auto_explain.c b/contrib/auto_explain/auto_explain.c index 7b22927674..d3c4def942 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_gucs = 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_gucs", + "Print modified GUC values.", + NULL, + &auto_explain_log_gucs, + 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->gucs = auto_explain_log_gucs; ExplainBeginOutput(es); ExplainQueryText(es, queryDesc); diff --git a/doc/src/sgml/auto-explain.sgml b/doc/src/sgml/auto-explain.sgml index 120b168d45..852c69b7bb 100644 --- a/doc/src/sgml/auto-explain.sgml +++ b/doc/src/sgml/auto-explain.sgml @@ -169,6 +169,23 @@ LOAD 'auto_explain'; </listitem> </varlistentry> + <varlistentry> + <term> + <varname>auto_explain.log_gucs</varname> (<type>boolean</type>) + <indexterm> + <primary><varname>auto_explain.log_gucs</varname> configuration parameter</primary> + </indexterm> + </term> + <listitem> + <para> + <varname>auto_explain.log_gucs</varname> controls whether information + about modified configuration options are logged with the execution + plan. Only options modified at the database, user, client or session + level are considered modified. This parameter is off by default. + </para> + </listitem> + </varlistentry> + <varlistentry> <term> <varname>auto_explain.log_format</varname> (<type>enum</type>) diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c index ae7f038203..69ede5a5ef 100644 --- a/src/backend/commands/explain.c +++ b/src/backend/commands/explain.c @@ -30,6 +30,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" @@ -163,6 +164,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, "gucs") == 0) + es->gucs = defGetBoolean(opt); else if (strcmp(opt->defname, "timing") == 0) { timing_set = true; @@ -597,6 +600,68 @@ ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into, ExplainState *es, ExplainCloseGroup("Query", NULL, true, es); } +static void +ExplainShowGUC(ExplainState *es) +{ + int num; + struct config_generic **gucs; + + /* bail out if GUC information not requested */ + if (!es->gucs) + 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("GUC", "GUC", 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("GUC", "GUC", 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("GUCs", str.data, es); + } +} + /* * ExplainPrintPlan - * convert a QueryDesc's plan tree to text and append it to es->str @@ -634,6 +699,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. + */ + ExplainShowGUC(es); } /* diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index ae925c1650..bff9f182e9 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -862,7 +862,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, @@ -871,7 +872,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, @@ -880,7 +882,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, @@ -889,7 +892,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, @@ -898,7 +902,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, @@ -907,7 +912,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, @@ -916,7 +922,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, @@ -925,7 +932,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, @@ -934,7 +942,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, @@ -943,7 +952,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, @@ -952,7 +962,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, @@ -961,7 +972,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, @@ -970,7 +982,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, @@ -979,7 +992,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, @@ -988,7 +1002,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, @@ -997,7 +1012,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, @@ -1008,7 +1024,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, @@ -1018,7 +1035,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, @@ -1591,7 +1609,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, @@ -1782,7 +1800,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 }, ¶llel_leader_participation, true, @@ -1792,7 +1811,8 @@ static struct config_bool ConfigureNamesBool[] = { {"jit", PGC_USERSET, QUERY_TUNING_OTHER, gettext_noop("Allow JIT compilation."), - NULL + NULL, + GUC_EXPLAIN }, &jit_enabled, true, @@ -1921,7 +1941,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, @@ -1933,7 +1954,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, @@ -1942,7 +1964,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, @@ -1951,7 +1974,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, @@ -1960,7 +1984,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, @@ -1969,7 +1994,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, @@ -2083,7 +2109,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, @@ -2150,7 +2176,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, @@ -2701,7 +2727,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 @@ -2946,7 +2973,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, @@ -2956,7 +2984,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, @@ -3046,7 +3075,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, @@ -3057,7 +3086,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, @@ -3068,7 +3097,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, @@ -3133,7 +3162,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, @@ -3143,7 +3173,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, @@ -3153,7 +3184,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, @@ -3163,7 +3195,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, @@ -3173,7 +3206,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, @@ -3183,7 +3217,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 }, ¶llel_tuple_cost, DEFAULT_PARALLEL_TUPLE_COST, 0, DBL_MAX, @@ -3193,7 +3228,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 }, ¶llel_setup_cost, DEFAULT_PARALLEL_SETUP_COST, 0, DBL_MAX, @@ -3203,7 +3239,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, @@ -3213,7 +3250,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, @@ -3223,7 +3261,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, @@ -3234,7 +3273,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, @@ -3244,7 +3284,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, @@ -3254,7 +3295,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, @@ -3669,7 +3711,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", @@ -4122,7 +4164,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, @@ -4348,7 +4391,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, @@ -4372,7 +4416,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, @@ -8556,6 +8601,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..364aacd5c2 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 gucs; /* print modified GUCs */ 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 */