explain plans with information about (modified) gucs
Hi,
every now and then I have to investigate an execution plan that is
strange in some way and I can't reproduce the same behavior. Usually
it's simply due to data distribution changing since the problem was
observed (say, after a nightly batch load/update).
In many cases it however may be due to some local GUC tweaks, usually
addressing some query specific issues (say, disabling nested loops or
lowering join_collapse_limit). I've repeatedly ran into cases where the
GUC was not properly reset to the "regular" value, and it's rather
difficult to identify this is what's happening. Or cases with different
per-user settings and connection pooling (SET SESSION AUTHORIZATION /
ROLE etc.).
So I propose to extend EXPLAIN output with an additional option, which
would include information about modified GUCs in the execution plan
(disabled by default, of course):
test=# explain (gucs) select * from t;
QUERY PLAN
--------------------------------------------------------------------
Seq Scan on t (cost=0.00..35.50 rows=2550 width=4)
GUCs: application_name = 'x', client_encoding = 'UTF8',
cpu_tuple_cost = '0.01'
(2 rows)
Of course, this directly applies to auto_explain too, which gets a new
option log_gucs.
The patch is quite trivial, but there are about three open questions:
1) names of the options
I'm not particularly happy with calling the option "gucs" - it's an
acronym and many users have little idea what GUC stands for. So I think
a better name would be desirable, but I'm not sure what would that be.
Options? Parameters?
2) format of output
At this point the names/values are simply formatted into a one-line
string. That's not particularly readable, and it's not very useful for
the YAML/JSON formats I guess. So adding each modified GUC as an extra
text property would be better.
3) identifying modified (and interesting) GUCs
We certainly don't want to include all GUCs, so the question is how to
decide which GUCs are interesting. The simplest approach would be to
look for GUCs that changed in the session (source == PGC_S_SESSION), but
that does not help with SET SESSION AUTHORIZATION / ROLE cases. So we
probably want (source > PGC_S_ARGV), but probably not PGC_S_OVERRIDE
because that includes irrelevant options like wal_buffers etc.
For now I've used
/* return only options that were modified (not as in config file) */
if ((conf->source <= PGC_S_ARGV) || (conf->source == PGC_S_OVERRIDE))
continue;
which generally does the right thing, although it also includes stuff
like application_name or client_encoding. But perhaps it'd be better to
whitelist the GUCs in some way, because some of the user-defined GUCs
may be sensitive and should not be included in plans.
Opinions?
regards
--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Attachments:
explain-with-gucs.difftext/x-patch; name=explain-with-gucs.diffDownload
diff --git a/contrib/auto_explain/auto_explain.c b/contrib/auto_explain/auto_explain.c
index 646cd0d42c..ec44c59b7f 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 de09ded65b..b8cab69f71 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -31,6 +31,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"
@@ -164,6 +165,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;
@@ -547,6 +550,37 @@ ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into, ExplainState *es,
/* Create textual dump of plan tree */
ExplainPrintPlan(es, queryDesc);
+ if (es->gucs)
+ {
+ int i;
+ int num;
+ StringInfoData str;
+ struct config_generic **gucs;
+
+ gucs = get_modified_guc_options(&num);
+
+ for (i = 0; i < num; i++)
+ {
+ char *setting;
+ struct config_generic *conf = gucs[i];
+
+ if (i == 0)
+ initStringInfo(&str);
+ else
+ 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);
+ }
+
if (es->summary && planduration)
{
double plantime = INSTR_TIME_GET_DOUBLE(*planduration);
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 6fe1939881..fd4473d9d0 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -8556,6 +8556,37 @@ ShowAllGUCConfig(DestReceiver *dest)
end_tup_output(tstate);
}
+struct config_generic **
+get_modified_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++)
+ {
+ 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;
+
+ /* return only options that were modified (not as in config file) */
+ if ((conf->source <= PGC_S_ARGV) || (conf->source == PGC_S_OVERRIDE))
+ 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 d3f70fda08..05afca22aa 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_tables.h b/src/include/utils/guc_tables.h
index 6f9fdb6a5f..4942e192d6 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_modified_guc_options(int *num);
#endif /* GUC_TABLES_H */
pá 14. 12. 2018 v 12:41 odesílatel Tomas Vondra <
tomas.vondra@2ndquadrant.com> napsal:
Hi,
every now and then I have to investigate an execution plan that is
strange in some way and I can't reproduce the same behavior. Usually
it's simply due to data distribution changing since the problem was
observed (say, after a nightly batch load/update).In many cases it however may be due to some local GUC tweaks, usually
addressing some query specific issues (say, disabling nested loops or
lowering join_collapse_limit). I've repeatedly ran into cases where the
GUC was not properly reset to the "regular" value, and it's rather
difficult to identify this is what's happening. Or cases with different
per-user settings and connection pooling (SET SESSION AUTHORIZATION /
ROLE etc.).So I propose to extend EXPLAIN output with an additional option, which
would include information about modified GUCs in the execution plan
(disabled by default, of course):test=# explain (gucs) select * from t;
QUERY PLAN
--------------------------------------------------------------------
Seq Scan on t (cost=0.00..35.50 rows=2550 width=4)
GUCs: application_name = 'x', client_encoding = 'UTF8',
cpu_tuple_cost = '0.01'
(2 rows)Of course, this directly applies to auto_explain too, which gets a new
option log_gucs.The patch is quite trivial, but there are about three open questions:
1) names of the options
I'm not particularly happy with calling the option "gucs" - it's an
acronym and many users have little idea what GUC stands for. So I think
a better name would be desirable, but I'm not sure what would that be.
Options? Parameters?2) format of output
At this point the names/values are simply formatted into a one-line
string. That's not particularly readable, and it's not very useful for
the YAML/JSON formats I guess. So adding each modified GUC as an extra
text property would be better.3) identifying modified (and interesting) GUCs
We certainly don't want to include all GUCs, so the question is how to
decide which GUCs are interesting. The simplest approach would be to
look for GUCs that changed in the session (source == PGC_S_SESSION), but
that does not help with SET SESSION AUTHORIZATION / ROLE cases. So we
probably want (source > PGC_S_ARGV), but probably not PGC_S_OVERRIDE
because that includes irrelevant options like wal_buffers etc.For now I've used
/* return only options that were modified (not as in config file) */
if ((conf->source <= PGC_S_ARGV) || (conf->source == PGC_S_OVERRIDE))
continue;which generally does the right thing, although it also includes stuff
like application_name or client_encoding. But perhaps it'd be better to
whitelist the GUCs in some way, because some of the user-defined GUCs
may be sensitive and should not be included in plans.Opinions?
has sense
Pavel
Show quoted text
regards
--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
You might want to only include the GUCs that are query tuning parameters,
i.e., those returned by:
SELECT name, setting, category
FROM pg_settings
WHERE category LIKE 'Query Tuning%'
ORDER BY category, name;
-----
Jim Finnerty, AWS, Amazon Aurora PostgreSQL
--
Sent from: http://www.postgresql-archive.org/PostgreSQL-hackers-f1928748.html
On 12/14/18 2:05 PM, Jim Finnerty wrote:
You might want to only include the GUCs that are query tuning parameters,
i.e., those returned by:SELECT name, setting, category
FROM pg_settings
WHERE category LIKE 'Query Tuning%'
ORDER BY category, name;
Good idea! Thanks.
regards
--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
On 12/14/18 3:01 PM, Tomas Vondra wrote:
On 12/14/18 2:05 PM, Jim Finnerty wrote:
You might want to only include the GUCs that are query tuning parameters,
i.e., those returned by:SELECT name, setting, category
FROM pg_settings
WHERE category LIKE 'Query Tuning%'
ORDER BY category, name;Good idea! Thanks.
V2 filtering the options to QUERY_TUNING group (and subgroups).
regards
--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Attachments:
explain-with-gucs-v2.difftext/x-patch; name=explain-with-gucs-v2.diffDownload
diff --git a/contrib/auto_explain/auto_explain.c b/contrib/auto_explain/auto_explain.c
index 646cd0d42c..ec44c59b7f 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 de09ded65b..b8cab69f71 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -31,6 +31,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"
@@ -164,6 +165,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;
@@ -547,6 +550,37 @@ ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into, ExplainState *es,
/* Create textual dump of plan tree */
ExplainPrintPlan(es, queryDesc);
+ if (es->gucs)
+ {
+ int i;
+ int num;
+ StringInfoData str;
+ struct config_generic **gucs;
+
+ gucs = get_modified_guc_options(&num);
+
+ for (i = 0; i < num; i++)
+ {
+ char *setting;
+ struct config_generic *conf = gucs[i];
+
+ if (i == 0)
+ initStringInfo(&str);
+ else
+ 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);
+ }
+
if (es->summary && planduration)
{
double plantime = INSTR_TIME_GET_DOUBLE(*planduration);
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 6fe1939881..2d37760c7a 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -8556,6 +8556,45 @@ ShowAllGUCConfig(DestReceiver *dest)
end_tup_output(tstate);
}
+struct config_generic **
+get_modified_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++)
+ {
+ 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 related to query tuning */
+ if ((conf->group != QUERY_TUNING) &&
+ (conf->group != QUERY_TUNING_METHOD) &&
+ (conf->group != QUERY_TUNING_COST) &&
+ (conf->group != QUERY_TUNING_GEQO) &&
+ (conf->group != QUERY_TUNING_OTHER))
+ continue;
+
+ /* return only options that were modified (w.r.t. config file) */
+ if (conf->source <= PGC_S_ARGV)
+ 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 d3f70fda08..05afca22aa 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_tables.h b/src/include/utils/guc_tables.h
index 6f9fdb6a5f..4942e192d6 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_modified_guc_options(int *num);
#endif /* GUC_TABLES_H */
Tomas Vondra <tomas.vondra@2ndquadrant.com> writes:
... I propose to extend EXPLAIN output with an additional option, which
would include information about modified GUCs in the execution plan
(disabled by default, of course):
I'm a bit suspicious about whether this'll have any actual value,
if it's disabled by default (which I agree it needs to be, if only for
compatibility reasons). The problem you're trying to solve is basically
"I forgot that this might have an effect", but stuff that isn't shown
by default will not help you un-forget. It certainly won't fix the
form of the problem that I run into, which is people sending in EXPLAIN
plans and not mentioning their weird local settings.
We certainly don't want to include all GUCs, so the question is how to
decide which GUCs are interesting. The simplest approach would be to
look for GUCs that changed in the session (source == PGC_S_SESSION), but
that does not help with SET SESSION AUTHORIZATION / ROLE cases. So we
probably want (source > PGC_S_ARGV), but probably not PGC_S_OVERRIDE
because that includes irrelevant options like wal_buffers etc.
Don't you want to show anything that's not the built-in default?
(I agree OVERRIDE could be excluded, but that's irrelevant for query
tuning parameters.) Just because somebody injected a damfool setting
of, say, random_page_cost via the postmaster command line or
environment settings doesn't make it not damfool :-(
regards, tom lane
On 12/14/18 4:21 PM, Tom Lane wrote:
Tomas Vondra <tomas.vondra@2ndquadrant.com> writes:
... I propose to extend EXPLAIN output with an additional option, which
would include information about modified GUCs in the execution plan
(disabled by default, of course):I'm a bit suspicious about whether this'll have any actual value,
if it's disabled by default (which I agree it needs to be, if only for
compatibility reasons). The problem you're trying to solve is basically
"I forgot that this might have an effect", but stuff that isn't shown
by default will not help you un-forget. It certainly won't fix the
form of the problem that I run into, which is people sending in EXPLAIN
plans and not mentioning their weird local settings.
Not quite.
I agree we'll still have to deal with plans from users without this
info, but it's easier to ask for explain with this extra option (just
like we regularly ask for explain analyze instead of just plain
explain). I'd expect the output to be more complete than trying to
figure out which of the GUCs might have effect / been modified here.
But more importantly - my personal primary use case here is explains
from application connections generated using auto_explain, with some
application-level GUC magic. And there I can easily tweak auto_explain
config to do (auto_explain.log_gucs = true) of course.
We certainly don't want to include all GUCs, so the question is how to
decide which GUCs are interesting. The simplest approach would be to
look for GUCs that changed in the session (source == PGC_S_SESSION), but
that does not help with SET SESSION AUTHORIZATION / ROLE cases. So we
probably want (source > PGC_S_ARGV), but probably not PGC_S_OVERRIDE
because that includes irrelevant options like wal_buffers etc.Don't you want to show anything that's not the built-in default?
(I agree OVERRIDE could be excluded, but that's irrelevant for query
tuning parameters.) Just because somebody injected a damfool setting
of, say, random_page_cost via the postmaster command line or
environment settings doesn't make it not damfool :-(
Probably. My assumption here was that I can do
select * from pg_settings
and then combine it with whatever is included in the plan. But you're
right comparing it with the built-in default may be a better option.
regards
--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
On 12/14/18 4:32 PM, Tomas Vondra wrote:
On 12/14/18 4:21 PM, Tom Lane wrote:
Tomas Vondra <tomas.vondra@2ndquadrant.com> writes:
... I propose to extend EXPLAIN output with an additional option, which
would include information about modified GUCs in the execution plan
(disabled by default, of course):I'm a bit suspicious about whether this'll have any actual value,
if it's disabled by default (which I agree it needs to be, if only for
compatibility reasons). The problem you're trying to solve is basically
"I forgot that this might have an effect", but stuff that isn't shown
by default will not help you un-forget. It certainly won't fix the
form of the problem that I run into, which is people sending in EXPLAIN
plans and not mentioning their weird local settings.Not quite.
I agree we'll still have to deal with plans from users without this
info, but it's easier to ask for explain with this extra option (just
like we regularly ask for explain analyze instead of just plain
explain). I'd expect the output to be more complete than trying to
figure out which of the GUCs might have effect / been modified here.But more importantly - my personal primary use case here is explains
from application connections generated using auto_explain, with some
application-level GUC magic. And there I can easily tweak auto_explain
config to do (auto_explain.log_gucs = true) of course.We certainly don't want to include all GUCs, so the question is how to
decide which GUCs are interesting. The simplest approach would be to
look for GUCs that changed in the session (source == PGC_S_SESSION), but
that does not help with SET SESSION AUTHORIZATION / ROLE cases. So we
probably want (source > PGC_S_ARGV), but probably not PGC_S_OVERRIDE
because that includes irrelevant options like wal_buffers etc.Don't you want to show anything that's not the built-in default?
(I agree OVERRIDE could be excluded, but that's irrelevant for query
tuning parameters.) Just because somebody injected a damfool setting
of, say, random_page_cost via the postmaster command line or
environment settings doesn't make it not damfool :-(Probably. My assumption here was that I can do
select * from pg_settings
and then combine it with whatever is included in the plan. But you're
right comparing it with the built-in default may be a better option.
FWIW here is a v3 of the patch, using the built-in default, and fixing a
silly thinko resulting in the code not being executed from auto_explain.
regards
--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Attachments:
explain-with-gucs-v3.patchtext/x-patch; name=explain-with-gucs-v3.patchDownload
diff --git a/contrib/auto_explain/auto_explain.c b/contrib/auto_explain/auto_explain.c
index 646cd0d42c..ec44c59b7f 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 de09ded65b..2e92690453 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -31,6 +31,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"
@@ -164,6 +165,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;
@@ -635,6 +638,41 @@ 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.
+ */
+ if (es->gucs)
+ {
+ int i;
+ int num;
+ StringInfoData str;
+ struct config_generic **gucs;
+
+ gucs = get_modified_guc_options(&num);
+
+ for (i = 0; i < num; i++)
+ {
+ char *setting;
+ struct config_generic *conf = gucs[i];
+
+ if (i == 0)
+ initStringInfo(&str);
+ else
+ 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);
+ }
}
/*
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 6fe1939881..8223c041f7 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -8556,6 +8556,90 @@ ShowAllGUCConfig(DestReceiver *dest)
end_tup_output(tstate);
}
+struct config_generic **
+get_modified_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 related to query tuning */
+ if ((conf->group != QUERY_TUNING) &&
+ (conf->group != QUERY_TUNING_METHOD) &&
+ (conf->group != QUERY_TUNING_COST) &&
+ (conf->group != QUERY_TUNING_GEQO) &&
+ (conf->group != QUERY_TUNING_OTHER))
+ 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 d3f70fda08..05afca22aa 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_tables.h b/src/include/utils/guc_tables.h
index 6f9fdb6a5f..4942e192d6 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_modified_guc_options(int *num);
#endif /* GUC_TABLES_H */
what would you think about adding
search_path
to that list ?
Regards
PAscal
--
Sent from: http://www.postgresql-archive.org/PostgreSQL-hackers-f1928748.html
Hi,
On 12/17/18 10:56 PM, legrand legrand wrote:
what would you think about adding
search_path
to that list ?
Yeah, I've been thinking about that too. Currently it gets filtered out
because it's in the CLIENT_CONN_STATEMENT group, but the code only
includes QUERY_TUNING_*.
But we don't want to include everything from CLIENT_CONN_STATEMENT,
because that would include various kinds of timeouts, vacuuming
parameters, etc.
And the same issue applies to work_mem, which is in RESOURCES_MEM. And
of course, there's a lot of unrelated stuff in RESOURCES_MEM.
So I guess we'll need to enable QUERY_TUNING_* and then selectively a
couple of individual options from other groups.
regards
--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Tomas Vondra <tomas.vondra@2ndquadrant.com> writes:
On 12/17/18 10:56 PM, legrand legrand wrote:
what would you think about adding
search_path
to that list ?
Yeah, I've been thinking about that too. Currently it gets filtered out
because it's in the CLIENT_CONN_STATEMENT group, but the code only
includes QUERY_TUNING_*.
But we don't want to include everything from CLIENT_CONN_STATEMENT,
because that would include various kinds of timeouts, vacuuming
parameters, etc.
And the same issue applies to work_mem, which is in RESOURCES_MEM. And
of course, there's a lot of unrelated stuff in RESOURCES_MEM.
So I guess we'll need to enable QUERY_TUNING_* and then selectively a
couple of individual options from other groups.
This is putting way too much policy into the mechanism, if you ask me.
At least for the auto_explain use case, it'd make far more sense for
the user to be able to specify which GUCs he wants the query space
to be divided according to. While it's possible to imagine giving
auto_explain a control setting that's a list of GUC names, I'm not
sure how we adapt the idea for other use-cases. But the direction
you're headed here will mostly ensure that nobody is happy.
regards, tom lane
On 12/17/18 11:16 PM, Tom Lane wrote:
Tomas Vondra <tomas.vondra@2ndquadrant.com> writes:
On 12/17/18 10:56 PM, legrand legrand wrote:
what would you think about adding
search_path
to that list ?Yeah, I've been thinking about that too. Currently it gets filtered out
because it's in the CLIENT_CONN_STATEMENT group, but the code only
includes QUERY_TUNING_*.
But we don't want to include everything from CLIENT_CONN_STATEMENT,
because that would include various kinds of timeouts, vacuuming
parameters, etc.
And the same issue applies to work_mem, which is in RESOURCES_MEM. And
of course, there's a lot of unrelated stuff in RESOURCES_MEM.
So I guess we'll need to enable QUERY_TUNING_* and then selectively a
couple of individual options from other groups.This is putting way too much policy into the mechanism, if you ask me.
Doesn't that depend on how it'd be implemented? I have not envisioned to
make these decisions in explain.c, but rather to keep them in guc.c
somehow. Say in a function like this:
bool guc_affects_query_planning(config_generic *conf);
which would be a fairly simple check outlined before (QUERY_TUNING_*
plus a couple of individual GUCs). Other use cases might provide similar
filters.
An alternative would be to somehow track this in the GUC definitions
directly (similarly to how we track the group), but that seems rather
inflexible and I'm not sure how would that handle ad-hoc use cases.
At least for the auto_explain use case, it'd make far more sense for
the user to be able to specify which GUCs he wants the query space
to be divided according to. While it's possible to imagine giving
auto_explain a control setting that's a list of GUC names, I'm not
sure how we adapt the idea for other use-cases. But the direction
you're headed here will mostly ensure that nobody is happy.
I certainly don't want to base this on explicitly listing "interesting"
GUCs anywhere. That would make it pretty useless for the use case I care
about, i.e. using auto_explain to investigate slow plans, when I don't
really know what GUC the application might have changed (certainly not
in advance).
I can't really say how to adopt this to other use cases, considering
there are none proposed (and I can't think of any either).
regards
--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Hi,
On 2018-12-18 00:38:16 +0100, Tomas Vondra wrote:
On 12/17/18 11:16 PM, Tom Lane wrote:
Tomas Vondra <tomas.vondra@2ndquadrant.com> writes:
Yeah, I've been thinking about that too. Currently it gets filtered out
because it's in the CLIENT_CONN_STATEMENT group, but the code only
includes QUERY_TUNING_*.
But we don't want to include everything from CLIENT_CONN_STATEMENT,
because that would include various kinds of timeouts, vacuuming
parameters, etc.
And the same issue applies to work_mem, which is in RESOURCES_MEM. And
of course, there's a lot of unrelated stuff in RESOURCES_MEM.
So I guess we'll need to enable QUERY_TUNING_* and then selectively a
couple of individual options from other groups.This is putting way too much policy into the mechanism, if you ask me.
Doesn't that depend on how it'd be implemented? I have not envisioned to
make these decisions in explain.c, but rather to keep them in guc.c
somehow. Say in a function like this:bool guc_affects_query_planning(config_generic *conf);
which would be a fairly simple check outlined before (QUERY_TUNING_*
plus a couple of individual GUCs). Other use cases might provide similar
filters.
If we were to do that, I'd suggest implementing a GUC_* flag specified
in the definition of the GUC, rather than a separate function containing
all the knowledge (but such a function could obviously still be used to
return such a fact).
An alternative would be to somehow track this in the GUC definitions
directly (similarly to how we track the group), but that seems rather
inflexible and I'm not sure how would that handle ad-hoc use cases.
Not sure what problem you see here?
Greetings,
Andres Freund
On 12/18/18 12:43 AM, Andres Freund wrote:
Hi,
On 2018-12-18 00:38:16 +0100, Tomas Vondra wrote:
On 12/17/18 11:16 PM, Tom Lane wrote:
Tomas Vondra <tomas.vondra@2ndquadrant.com> writes:
Yeah, I've been thinking about that too. Currently it gets filtered out
because it's in the CLIENT_CONN_STATEMENT group, but the code only
includes QUERY_TUNING_*.
But we don't want to include everything from CLIENT_CONN_STATEMENT,
because that would include various kinds of timeouts, vacuuming
parameters, etc.
And the same issue applies to work_mem, which is in RESOURCES_MEM. And
of course, there's a lot of unrelated stuff in RESOURCES_MEM.
So I guess we'll need to enable QUERY_TUNING_* and then selectively a
couple of individual options from other groups.This is putting way too much policy into the mechanism, if you ask me.
Doesn't that depend on how it'd be implemented? I have not envisioned to
make these decisions in explain.c, but rather to keep them in guc.c
somehow. Say in a function like this:bool guc_affects_query_planning(config_generic *conf);
which would be a fairly simple check outlined before (QUERY_TUNING_*
plus a couple of individual GUCs). Other use cases might provide similar
filters.If we were to do that, I'd suggest implementing a GUC_* flag specified
in the definition of the GUC, rather than a separate function containing
all the knowledge (but such a function could obviously still be used to
return such a fact).
Seems reasonable.
An alternative would be to somehow track this in the GUC definitions
directly (similarly to how we track the group), but that seems rather
inflexible and I'm not sure how would that handle ad-hoc use cases.Not sure what problem you see here?
My main concern with that was how many flags could we need, if we use
this as the way to implement this and similar use cases. There are 32
bits available, and 24 of them are already used for GUC_* flags. So if
we use this as an "official" way to support similar use cases, we could
run out of space pretty fast.
The callback would also allow ad hoc filtering, which is not very
practical from extensions (e.g. you can't define a new flag, because it
might conflict with something defined in another extension).
But I think that's nonsense - so far we have not seen any such use case,
so it's pretty pointless to design for it. If that changes and some new
use case is proposed in the future, we can rethink this based on it.
I'll go with a new flag, marking all GUCs related to query planning, and
post a new patch soon.
regards
--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Attached is v4, changing how GUCs are picked for inclusion on the query
plans. Instead of picking the GUCs based on group and/or explicitly, a
new GUC_EXPLAIN flag is used for that.
I went through GUCs defined in guc.c and marked those in QUERY_TUNING*
groups accordingly, with the exception of default_statistics_target
because that seems somewhat useless without showing the value used to
actually analyze the table (and/or columns).
I've also included a couple of other GUCs, that I find to be relevant:
- parallel_leader_participation
- max_parallel_workers_per_gather
- max_parallel_workers
- search_path
- effective_io_concurrency
- work_mem
- temp_buffers
- plan_cache_mode
I think this covers the interesting GUCs pretty well, although perhaps I
missed something.
The one bit that needs fixing is escaping the GUC values when showing
them in the plan. Looking at the other places that currently escape
stuff, I see they only care about YAML/JSON/XML and leave the regular
output unescaped. I was wondering if it's OK with the current format
with all GUCs on a single line
QUERY PLAN
---------------------------------------------------
Seq Scan on t (cost=0.00..54.63 rows=13 width=4)
Filter: ('x''y'::text = (a)::text)
GUCs: enable_nestloop = 'off', work_mem = '32MB'
(3 rows)
but I suppose it is, because without the escaping a user can break
whatever format we use. So I'll do the same thing, escaping just the
structured formats (YAML et al).
The question however is whether someone has a better formatting idea?
regards
--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Attachments:
explain-with-gucs-v4.patchtext/x-patch; name=explain-with-gucs-v4.patchDownload
diff --git a/contrib/auto_explain/auto_explain.c b/contrib/auto_explain/auto_explain.c
index 646cd0d42c..ec44c59b7f 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 094e977fb5..e32497d30d 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;
@@ -634,6 +637,41 @@ 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.
+ */
+ if (es->gucs)
+ {
+ int i;
+ int num;
+ StringInfoData str;
+ struct config_generic **gucs;
+
+ gucs = get_explain_guc_options(&num);
+
+ for (i = 0; i < num; i++)
+ {
+ char *setting;
+ struct config_generic *conf = gucs[i];
+
+ if (i == 0)
+ initStringInfo(&str);
+ else
+ 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);
+ }
}
/*
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 6fe1939881..c2f1bb956d 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 d3f70fda08..05afca22aa 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 64457c792a..f316320efc 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 6f9fdb6a5f..641cb29fbe 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 */
út 1. 1. 2019 v 18:39 odesílatel Tomas Vondra <tomas.vondra@2ndquadrant.com>
napsal:
Attached is v4, changing how GUCs are picked for inclusion on the query
plans. Instead of picking the GUCs based on group and/or explicitly, a
new GUC_EXPLAIN flag is used for that.I went through GUCs defined in guc.c and marked those in QUERY_TUNING*
groups accordingly, with the exception of default_statistics_target
because that seems somewhat useless without showing the value used to
actually analyze the table (and/or columns).I've also included a couple of other GUCs, that I find to be relevant:
- parallel_leader_participation
- max_parallel_workers_per_gather
- max_parallel_workers
- search_path
- effective_io_concurrency
- work_mem
- temp_buffers
- plan_cache_mode
when plan_cache_mode is auto, you know maybe too less executed query. Maybe
you can read somewhere if plan was custom or generic.
I think this covers the interesting GUCs pretty well, although perhaps I
missed something.
seq_page_cost, random_page_cost, from_collapse_limit, join_collapse_limit,
... enable_***
Show quoted text
The one bit that needs fixing is escaping the GUC values when showing
them in the plan. Looking at the other places that currently escape
stuff, I see they only care about YAML/JSON/XML and leave the regular
output unescaped. I was wondering if it's OK with the current format
with all GUCs on a single lineQUERY PLAN
---------------------------------------------------
Seq Scan on t (cost=0.00..54.63 rows=13 width=4)
Filter: ('x''y'::text = (a)::text)
GUCs: enable_nestloop = 'off', work_mem = '32MB'
(3 rows)but I suppose it is, because without the escaping a user can break
whatever format we use. So I'll do the same thing, escaping just the
structured formats (YAML et al).The question however is whether someone has a better formatting idea?
regards
--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
On 1/1/19 6:48 PM, Pavel Stehule wrote:
út 1. 1. 2019 v 18:39 odesílatel Tomas Vondra
<tomas.vondra@2ndquadrant.com <mailto:tomas.vondra@2ndquadrant.com>> napsal:Attached is v4, changing how GUCs are picked for inclusion on the query
plans. Instead of picking the GUCs based on group and/or explicitly, a
new GUC_EXPLAIN flag is used for that.I went through GUCs defined in guc.c and marked those in QUERY_TUNING*
groups accordingly, with the exception of default_statistics_target
because that seems somewhat useless without showing the value used to
actually analyze the table (and/or columns).I've also included a couple of other GUCs, that I find to be relevant:
- parallel_leader_participation
- max_parallel_workers_per_gather
- max_parallel_workers
- search_path
- effective_io_concurrency
- work_mem
- temp_buffers
- plan_cache_modewhen plan_cache_mode is auto, you know maybe too less executed query.
Maybe you can read somewhere if plan was custom or generic.
This patch is about showing GUCs, not such additional internal info.
Also, you'll see the plan actually used.
I think this covers the interesting GUCs pretty well, although perhaps I
missed something.seq_page_cost, random_page_cost, from_collapse_limit,
join_collapse_limit, ... enable_***
All these GUCs are included, of course.
--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
út 1. 1. 2019 v 20:11 odesílatel Tomas Vondra <tomas.vondra@2ndquadrant.com>
napsal:
On 1/1/19 6:48 PM, Pavel Stehule wrote:
út 1. 1. 2019 v 18:39 odesílatel Tomas Vondra
<tomas.vondra@2ndquadrant.com <mailto:tomas.vondra@2ndquadrant.com>>napsal:
Attached is v4, changing how GUCs are picked for inclusion on the
query
plans. Instead of picking the GUCs based on group and/or explicitly,
a
new GUC_EXPLAIN flag is used for that.
I went through GUCs defined in guc.c and marked those in
QUERY_TUNING*
groups accordingly, with the exception of default_statistics_target
because that seems somewhat useless without showing the value used to
actually analyze the table (and/or columns).I've also included a couple of other GUCs, that I find to be
relevant:
- parallel_leader_participation
- max_parallel_workers_per_gather
- max_parallel_workers
- search_path
- effective_io_concurrency
- work_mem
- temp_buffers
- plan_cache_modewhen plan_cache_mode is auto, you know maybe too less executed query.
Maybe you can read somewhere if plan was custom or generic.This patch is about showing GUCs, not such additional internal info.
Also, you'll see the plan actually used.
I understand to your goal, and I understand so collecting some data inside
can be hard or impossible.
But if you collect a data important for understanding to planner
behave/decision, then some important information is inside plancache too. -
and now it is not visible from user space.
It is just my note - so not only GUC are interesting - nothing more.
I think this covers the interesting GUCs pretty well, although
perhaps I
missed something.
seq_page_cost, random_page_cost, from_collapse_limit,
join_collapse_limit, ... enable_***All these GUCs are included, of course.
ok - thank you for info.
Regards
Pavel
Show quoted text
--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
On 14/12/2018 12:41, Tomas Vondra wrote:
1) names of the options
I'm not particularly happy with calling the option "gucs" - it's an
acronym and many users have little idea what GUC stands for. So I think
a better name would be desirable, but I'm not sure what would that be.
Options? Parameters?
"settings"
(see pg_settings, SET)
--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
On 1/2/19 4:20 PM, Peter Eisentraut wrote:
On 14/12/2018 12:41, Tomas Vondra wrote:
1) names of the options
I'm not particularly happy with calling the option "gucs" - it's an
acronym and many users have little idea what GUC stands for. So I think
a better name would be desirable, but I'm not sure what would that be.
Options? Parameters?"settings"
(see pg_settings, SET)
Yup, that seems like a better choice. Thanks.
--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Hi,
every now and then I have to investigate an execution plan that is
strange in some way and I can't reproduce the same behavior. Usually
it's simply due to data distribution changing since the problem was
observed (say, after a nightly batch load/update).In many cases it however may be due to some local GUC tweaks, usually
addressing some query specific issues (say, disabling nested loops or
lowering join_collapse_limit). I've repeatedly ran into cases where the
GUC was not properly reset to the "regular" value, and it's rather
difficult to identify this is what's happening. Or cases with different
per-user settings and connection pooling (SET SESSION AUTHORIZATION /
ROLE etc.).So I propose to extend EXPLAIN output with an additional option, which
would include information about modified GUCs in the execution plan
(disabled by default, of course):test=# explain (gucs) select * from t;
QUERY PLAN
--------------------------------------------------------------------
Seq Scan on t (cost=0.00..35.50 rows=2550 width=4)
GUCs: application_name = 'x', client_encoding = 'UTF8',
cpu_tuple_cost = '0.01'
(2 rows)Of course, this directly applies to auto_explain too, which gets a new
option log_gucs.The patch is quite trivial, but there are about three open questions:
1) names of the options
I'm not particularly happy with calling the option "gucs" - it's an
acronym and many users have little idea what GUC stands for. So I think
a better name would be desirable, but I'm not sure what would that be.
Options? Parameters?2) format of output
At this point the names/values are simply formatted into a one-line
string. That's not particularly readable, and it's not very useful for
the YAML/JSON formats I guess. So adding each modified GUC as an extra
text property would be better.3) identifying modified (and interesting) GUCs
We certainly don't want to include all GUCs, so the question is how to
decide which GUCs are interesting. The simplest approach would be to
look for GUCs that changed in the session (source == PGC_S_SESSION), but
that does not help with SET SESSION AUTHORIZATION / ROLE cases. So we
probably want (source > PGC_S_ARGV), but probably not PGC_S_OVERRIDE
because that includes irrelevant options like wal_buffers etc.For now I've used
/* return only options that were modified (not as in config file) */
if ((conf->source <= PGC_S_ARGV) || (conf->source == PGC_S_OVERRIDE))
continue;which generally does the right thing, although it also includes stuff
like application_name or client_encoding. But perhaps it'd be better to
whitelist the GUCs in some way, because some of the user-defined GUCs
may be sensitive and should not be included in plans.Opinions?
regards
--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
This patch correlates with my proposal
"add session information column to pg_stat_statements"
/messages/by-id/3aa097d7-7c47-187b-5913-db8366cd4491@gmail.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.
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.
Thank you,
Sergei Agalakov
Hello Sergei,
This patch correlates with my proposal
"add session information column to pg_stat_statements"
/messages/by-id/3aa097d7-7c47-187b-5913-db8366cd4491@gmail.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
Attachments:
explain-with-gucs-v5.patchtext/x-patch; name=explain-with-gucs-v5.patchDownload
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 */
On 2019-Jan-14, Tomas Vondra wrote:
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).
I think the problem is that you'd have to know how to print the value,
which can be in one of several different C types. You'd have to grow
some more infrastructure in the GUC tables, I think, and that doesn't
seem worth the trouble. Printing as text seems enough.
--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Tomas Vondra-4 wrote
Hello Sergei,
This patch correlates with my proposal
"add session information column to pg_stat_statements"
/messages/by-id/3aa097d7-7c47-187b-5913-db8366cd4491@gmail.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.[...]
--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
This explain with GUCs feature can be included very easily for historical
data management in pg_store_plans or pg_stat_sql_plans extensions
(that both use a planid based on the normalized plan text).
Regards
PAscal
--
Sent from: http://www.postgresql-archive.org/PostgreSQL-hackers-f1928748.html
On 1/14/19 11:13 PM, Alvaro Herrera wrote:
On 2019-Jan-14, Tomas Vondra wrote:
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).I think the problem is that you'd have to know how to print the value,
which can be in one of several different C types. You'd have to grow
some more infrastructure in the GUC tables, I think, and that doesn't
seem worth the trouble. Printing as text seems enough.
I don't think the number of formats is such a big issue - the range of
formats is quite limited: PGC_BOOL, PGC_INT, PGC_REAL, PGC_STRING and
PGC_ENUM. But the show hook simply returns string, and I'm not sure it's
guaranteed it matches the raw value (afaik the assign/show hooks can do
all kinds of funny stuff).
regards
--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
[v5]
Hi Tomas,
Peter suggested upthread to use 'settings' rather than 'gucs' for the
explain option (and output?), and you seemed to agree. Are you going
to include that in a future version? Speaking of the output, v5's
default text doesn't match that of formatted text ('GUCs' / 'GUC').
Hi John,
On 1/16/19 10:08 PM, John Naylor wrote:
[v5]
Hi Tomas,
Peter suggested upthread to use 'settings' rather than 'gucs' for the
explain option (and output?), and you seemed to agree. Are you going
to include that in a future version? Speaking of the output, v5's
default text doesn't match that of formatted text ('GUCs' / 'GUC').
Attached is v6 of the patch, adopting "settings" instead of "guc".
regards
--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Attachments:
explain-with-gucs-v6.patchtext/x-patch; name=explain-with-gucs-v6.patchDownload
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..b0a129cfee 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_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 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/doc/src/sgml/ref/explain.sgml b/doc/src/sgml/ref/explain.sgml
index 8dc0d7038a..a7dca5f208 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
+ configuration parameters that were modified and are considered to affect
+ query planning. 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 ae7f038203..4309c8d137 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, "settings") == 0)
+ es->settings = 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
+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
@@ -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.
+ */
+ ExplainShowSettings(es);
}
/*
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index c216ed0922..39e609bb58 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -869,7 +869,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,
@@ -878,7 +879,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,
@@ -887,7 +889,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,
@@ -896,7 +899,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,
@@ -905,7 +909,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,
@@ -914,7 +919,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,
@@ -923,7 +929,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,
@@ -932,7 +939,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,
@@ -941,7 +949,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,
@@ -950,7 +959,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,
@@ -959,7 +969,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,
@@ -968,7 +979,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,
@@ -977,7 +989,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,
@@ -986,7 +999,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,
@@ -995,7 +1009,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,
@@ -1004,7 +1019,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,
@@ -1015,7 +1031,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,
@@ -1025,7 +1042,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,
@@ -1613,7 +1631,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,
@@ -1804,7 +1822,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,
@@ -1814,7 +1833,8 @@ static struct config_bool ConfigureNamesBool[] =
{
{"jit", PGC_USERSET, QUERY_TUNING_OTHER,
gettext_noop("Allow JIT compilation."),
- NULL
+ NULL,
+ GUC_EXPLAIN
},
&jit_enabled,
true,
@@ -1943,7 +1963,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,
@@ -1955,7 +1976,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,
@@ -1964,7 +1986,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,
@@ -1973,7 +1996,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,
@@ -1982,7 +2006,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,
@@ -1991,7 +2016,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,
@@ -2105,7 +2131,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,
@@ -2172,7 +2198,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,
@@ -2723,7 +2749,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
@@ -2968,7 +2995,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,
@@ -2978,7 +3006,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,
@@ -3068,7 +3097,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,
@@ -3079,7 +3108,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,
@@ -3090,7 +3119,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,
@@ -3155,7 +3184,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,
@@ -3165,7 +3195,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,
@@ -3175,7 +3206,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,
@@ -3185,7 +3217,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,
@@ -3195,7 +3228,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,
@@ -3205,7 +3239,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,
@@ -3215,7 +3250,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,
@@ -3225,7 +3261,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,
@@ -3235,7 +3272,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,
@@ -3245,7 +3283,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,
@@ -3256,7 +3295,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,
@@ -3266,7 +3306,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,
@@ -3276,7 +3317,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,
@@ -3691,7 +3733,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",
@@ -4144,7 +4186,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,
@@ -4370,7 +4413,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,
@@ -4394,7 +4438,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,
@@ -8578,6 +8623,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 */
On Sun, Jan 20, 2019 at 2:31 PM Tomas Vondra
<tomas.vondra@2ndquadrant.com> wrote:
Attached is v6 of the patch, adopting "settings" instead of "guc".
Ok, looks good. I tried changing config values with the .conf file,
alter system, and alter database, and tried a few queries with
auto_explain. I made a pass through the config parameters, and don't
see anything obviously left out. I have no comments on the source
code.
One thing stands out: For the docs on auto_explain, all other options
have "Only superusers can change this setting.", but log_settings
doesn't.
--
John Naylor https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
On 1/22/19 1:35 AM, John Naylor wrote:
On Sun, Jan 20, 2019 at 2:31 PM Tomas Vondra
<tomas.vondra@2ndquadrant.com> wrote:Attached is v6 of the patch, adopting "settings" instead of "guc".
Ok, looks good. I tried changing config values with the .conf file,
alter system, and alter database, and tried a few queries with
auto_explain. I made a pass through the config parameters, and don't
see anything obviously left out. I have no comments on the source
code.One thing stands out: For the docs on auto_explain, all other options
have "Only superusers can change this setting.", but log_settings
doesn't.
Yes, that's an omission in the docs. Will fix.
regards
--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
On Tue, Jan 22, 2019 at 02:29:06AM +0100, Tomas Vondra wrote:
Yes, that's an omission in the docs. Will fix.
Could you fix your patch then? I am moving it to next CF, waiting on
author.
--
Michael
Hi,
On 2019-01-15 02:39:49 +0100, Tomas Vondra wrote:
On 1/14/19 11:13 PM, Alvaro Herrera wrote:
On 2019-Jan-14, Tomas Vondra wrote:
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).I think the problem is that you'd have to know how to print the value,
which can be in one of several different C types. You'd have to grow
some more infrastructure in the GUC tables, I think, and that doesn't
seem worth the trouble. Printing as text seems enough.I don't think the number of formats is such a big issue - the range of
formats is quite limited: PGC_BOOL, PGC_INT, PGC_REAL, PGC_STRING and
PGC_ENUM. But the show hook simply returns string, and I'm not sure it's
guaranteed it matches the raw value (afaik the assign/show hooks can do
all kinds of funny stuff).
Yea, but the underlying values are quite useless for
humans. E.g. counting shared_buffers, wal_buffers etc in weird units is
not helpful. So you'd need to support the different units for each.
Greetings,
Andres Freund
On 2/14/19 8:55 PM, Andres Freund wrote:
Hi,
On 2019-01-15 02:39:49 +0100, Tomas Vondra wrote:
On 1/14/19 11:13 PM, Alvaro Herrera wrote:
On 2019-Jan-14, Tomas Vondra wrote:
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).I think the problem is that you'd have to know how to print the value,
which can be in one of several different C types. You'd have to grow
some more infrastructure in the GUC tables, I think, and that doesn't
seem worth the trouble. Printing as text seems enough.I don't think the number of formats is such a big issue - the range of
formats is quite limited: PGC_BOOL, PGC_INT, PGC_REAL, PGC_STRING and
PGC_ENUM. But the show hook simply returns string, and I'm not sure it's
guaranteed it matches the raw value (afaik the assign/show hooks can do
all kinds of funny stuff).Yea, but the underlying values are quite useless for
humans. E.g. counting shared_buffers, wal_buffers etc in weird units is
not helpful. So you'd need to support the different units for each.
True. So you agree printing the values as text (as the patch currently
does) seems enough? I guess I'm fine with that.
regards
--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
On 2019-02-15 17:10:28 +0100, Tomas Vondra wrote:
True. So you agree printing the values as text (as the patch currently
does) seems enough? I guess I'm fine with that.
I do agree, yes.
Hi,
attached is an updated patch, fixing and slightly tweaking the docs.
Barring objections, I'll get this committed later next week.
--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Attachments:
0001-Allow-including-configuration-parameters-in-explain-.patchtext/x-patch; name=0001-Allow-including-configuration-parameters-in-explain-.patchDownload
From ba0dc2578351e9c3c29cbd00860b31e3fd0acd6a Mon Sep 17 00:00:00 2001
From: Tomas Vondra <tomas@2ndquadrant.com>
Date: Sat, 23 Feb 2019 23:38:30 +0100
Subject: [PATCH] Allow including configuration parameters in explain output
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
},
¶llel_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
},
¶llel_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
},
¶llel_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
On Sun, 24 Feb 2019 at 00:06, Tomas Vondra <tomas.vondra@2ndquadrant.com> wrote:
Hi,
attached is an updated patch, fixing and slightly tweaking the docs.
Barring objections, I'll get this committed later next week.
I was having a look at this patch, and this kept me wondering,
+static void
+ExplainShowSettings(ExplainState *es)
+{
Is there some reason for not providing any explanation above this
function just like the rest of the functions in this file?
Similarly, for
struct config_generic **
get_explain_guc_options(int *num)
{
/* also bail out of there are no options */
+ if (!num)
+ return;
I think you meant 'if' instead if 'of' here.
On Mon, Mar 18, 2019 at 11:31:48AM +0100, Rafia Sabih wrote:
On Sun, 24 Feb 2019 at 00:06, Tomas Vondra <tomas.vondra@2ndquadrant.com> wrote:
Hi,
attached is an updated patch, fixing and slightly tweaking the docs.
Barring objections, I'll get this committed later next week.
I was having a look at this patch, and this kept me wondering,
+static void +ExplainShowSettings(ExplainState *es) +{ Is there some reason for not providing any explanation above this function just like the rest of the functions in this file?Similarly, for
struct config_generic **
get_explain_guc_options(int *num)
{/* also bail out of there are no options */ + if (!num) + return; I think you meant 'if' instead if 'of' here.
Thanks for the review - attached is a patch adding the missing comments,
and doing two additional minor improvements:
1) Renaming ExplainShowSettings to ExplainPrintSettings, to make it more
consistent with naming of the other functions in explain.c.
2) Actually counting GUC_EXPLAIN options, and only allocating space for
those in get_explain_guc_options, instead of using num_guc_variables. The
diffrence is quite significant (~50 vs. ~300), and considering each entry
is 8B it makes a difference because such large chunks tend to have higher
palloc overhed (due to ALLOCSET_SEPARATE_THRESHOLD).
regards
--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Attachments:
explain-with-settings-20190326.patchtext/plain; charset=us-asciiDownload
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 8e48b94d4a..2a289b8b94 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -599,8 +599,12 @@ ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into, ExplainState *es,
ExplainCloseGroup("Query", NULL, true, es);
}
+/*
+ * ExplainPrintSettings -
+ * Print summary of modified settings affecting query planning.
+ */
static void
-ExplainShowSettings(ExplainState *es)
+ExplainPrintSettings(ExplainState *es)
{
int num;
struct config_generic **gucs;
@@ -700,10 +704,10 @@ ExplainPrintPlan(ExplainState *es, QueryDesc *queryDesc)
ExplainNode(ps, NIL, NULL, NULL, es);
/*
- * If requested, include information about GUC parameters that don't
- * match the built-in defaults.
+ * If requested, include information about GUC parameters with values
+ * that don't match the built-in defaults.
*/
- ExplainShowSettings(es);
+ ExplainPrintSettings(es);
}
/*
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 7306d7c232..39f844ebc5 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -4532,6 +4532,7 @@ static struct config_generic **guc_variables;
/* Current number of variables contained in the vector */
static int num_guc_variables;
+static int num_guc_explain_variables;
/* Vector capacity */
static int size_guc_variables;
@@ -4796,6 +4797,7 @@ build_guc_variables(void)
{
int size_vars;
int num_vars = 0;
+ int num_explain_vars = 0;
struct config_generic **guc_vars;
int i;
@@ -4806,6 +4808,9 @@ build_guc_variables(void)
/* Rather than requiring vartype to be filled in by hand, do this: */
conf->gen.vartype = PGC_BOOL;
num_vars++;
+
+ if (conf->gen.flags & GUC_EXPLAIN)
+ num_explain_vars++;
}
for (i = 0; ConfigureNamesInt[i].gen.name; i++)
@@ -4814,6 +4819,9 @@ build_guc_variables(void)
conf->gen.vartype = PGC_INT;
num_vars++;
+
+ if (conf->gen.flags & GUC_EXPLAIN)
+ num_explain_vars++;
}
for (i = 0; ConfigureNamesReal[i].gen.name; i++)
@@ -4822,6 +4830,9 @@ build_guc_variables(void)
conf->gen.vartype = PGC_REAL;
num_vars++;
+
+ if (conf->gen.flags & GUC_EXPLAIN)
+ num_explain_vars++;
}
for (i = 0; ConfigureNamesString[i].gen.name; i++)
@@ -4830,6 +4841,9 @@ build_guc_variables(void)
conf->gen.vartype = PGC_STRING;
num_vars++;
+
+ if (conf->gen.flags & GUC_EXPLAIN)
+ num_explain_vars++;
}
for (i = 0; ConfigureNamesEnum[i].gen.name; i++)
@@ -4838,6 +4852,9 @@ build_guc_variables(void)
conf->gen.vartype = PGC_ENUM;
num_vars++;
+
+ if (conf->gen.flags & GUC_EXPLAIN)
+ num_explain_vars++;
}
/*
@@ -4869,6 +4886,7 @@ build_guc_variables(void)
free(guc_variables);
guc_variables = guc_vars;
num_guc_variables = num_vars;
+ num_guc_explain_variables = num_explain_vars;
size_guc_variables = size_vars;
qsort((void *) guc_variables, num_guc_variables,
sizeof(struct config_generic *), guc_var_compare);
@@ -8819,6 +8837,11 @@ ShowAllGUCConfig(DestReceiver *dest)
end_tup_output(tstate);
}
+/*
+ * Returns an array of modified GUC options to show in EXPLAIN. Only options
+ * related to query planning (marked with GUC_EXPLAIN), with values different
+ * from built-in defaults.
+ */
struct config_generic **
get_explain_guc_options(int *num)
{
@@ -8826,7 +8849,13 @@ get_explain_guc_options(int *num)
struct config_generic **result;
*num = 0;
- result = palloc(sizeof(struct config_generic *) * num_guc_variables);
+
+ /*
+ * Allocate enough space to fit all GUC_EXPLAIN options. We may not
+ * need all the space, but there are fairly few such options so we
+ * don't waste a lot of memory.
+ */
+ result = palloc(sizeof(struct config_generic *) * num_guc_explain_variables);
for (i = 0; i < num_guc_variables; i++)
{
@@ -8894,6 +8923,8 @@ get_explain_guc_options(int *num)
/* assign to the values array */
result[*num] = conf;
*num = *num + 1;
+
+ Assert(*num <= num_guc_explain_variables);
}
return result;
On Tue, 26 Mar 2019 at 21:04, Tomas Vondra <tomas.vondra@2ndquadrant.com>
wrote:
On Mon, Mar 18, 2019 at 11:31:48AM +0100, Rafia Sabih wrote:
On Sun, 24 Feb 2019 at 00:06, Tomas Vondra <tomas.vondra@2ndquadrant.com>
wrote:
Hi,
attached is an updated patch, fixing and slightly tweaking the docs.
Barring objections, I'll get this committed later next week.
I was having a look at this patch, and this kept me wondering,
+static void +ExplainShowSettings(ExplainState *es) +{ Is there some reason for not providing any explanation above this function just like the rest of the functions in this file?Similarly, for
struct config_generic **
get_explain_guc_options(int *num)
{/* also bail out of there are no options */ + if (!num) + return; I think you meant 'if' instead if 'of' here.Thanks for the review - attached is a patch adding the missing comments,
and doing two additional minor improvements:1) Renaming ExplainShowSettings to ExplainPrintSettings, to make it more
consistent with naming of the other functions in explain.c.2) Actually counting GUC_EXPLAIN options, and only allocating space for
those in get_explain_guc_options, instead of using num_guc_variables. The
diffrence is quite significant (~50 vs. ~300), and considering each entry
is 8B it makes a difference because such large chunks tend to have higher
palloc overhed (due to ALLOCSET_SEPARATE_THRESHOLD).Looks like the patch is in need of a rebase.
At commit: 1983af8e899389187026cb34c1ca9d89ea986120
P.S. reject files attached.
--
Regards,
Rafia Sabih
Attachments:
guc.c.rejtext/x-reject; charset=US-ASCII; name=guc.c.rejDownload
--- src/backend/utils/misc/guc.c
+++ src/backend/utils/misc/guc.c
@@ -8837,6 +8855,11 @@ ShowAllGUCConfig(DestReceiver *dest)
end_tup_output(tstate);
}
+/*
+ * Returns an array of modified GUC options to show in EXPLAIN. Only options
+ * related to query planning (marked with GUC_EXPLAIN), with values different
+ * from built-in defaults.
+ */
struct config_generic **
get_explain_guc_options(int *num)
{
@@ -8844,7 +8867,13 @@ get_explain_guc_options(int *num)
struct config_generic **result;
*num = 0;
- result = palloc(sizeof(struct config_generic *) * num_guc_variables);
+
+ /*
+ * Allocate enough space to fit all GUC_EXPLAIN options. We may not
+ * need all the space, but there are fairly few such options so we
+ * don't waste a lot of memory.
+ */
+ result = palloc(sizeof(struct config_generic *) * num_guc_explain_variables);
for (i = 0; i < num_guc_variables; i++)
{
@@ -8912,6 +8941,8 @@ get_explain_guc_options(int *num)
/* assign to the values array */
result[*num] = conf;
*num = *num + 1;
+
+ Assert(*num <= num_guc_explain_variables);
}
return result;
explain.c.rejtext/x-reject; charset=US-ASCII; name=explain.c.rejDownload
--- src/backend/commands/explain.c
+++ src/backend/commands/explain.c
@@ -599,8 +599,12 @@ ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into, ExplainState *es,
ExplainCloseGroup("Query", NULL, true, es);
}
+/*
+ * ExplainPrintSettings -
+ * Print summary of modified settings affecting query planning.
+ */
static void
-ExplainShowSettings(ExplainState *es)
+ExplainPrintSettings(ExplainState *es)
{
int num;
struct config_generic **gucs;
@@ -700,10 +704,10 @@ ExplainPrintPlan(ExplainState *es, QueryDesc *queryDesc)
ExplainNode(ps, NIL, NULL, NULL, es);
/*
- * If requested, include information about GUC parameters that don't
- * match the built-in defaults.
+ * If requested, include information about GUC parameters with values
+ * that don't match the built-in defaults.
*/
- ExplainShowSettings(es);
+ ExplainPrintSettings(es);
}
/*
On Wed, Mar 27, 2019 at 09:06:04AM +0100, Rafia Sabih wrote:
On Tue, 26 Mar 2019 at 21:04, Tomas Vondra <tomas.vondra@2ndquadrant.com>
wrote:On Mon, Mar 18, 2019 at 11:31:48AM +0100, Rafia Sabih wrote:
On Sun, 24 Feb 2019 at 00:06, Tomas Vondra <tomas.vondra@2ndquadrant.com>
wrote:
Hi,
attached is an updated patch, fixing and slightly tweaking the docs.
Barring objections, I'll get this committed later next week.
I was having a look at this patch, and this kept me wondering,
+static void +ExplainShowSettings(ExplainState *es) +{ Is there some reason for not providing any explanation above this function just like the rest of the functions in this file?Similarly, for
struct config_generic **
get_explain_guc_options(int *num)
{/* also bail out of there are no options */ + if (!num) + return; I think you meant 'if' instead if 'of' here.Thanks for the review - attached is a patch adding the missing comments,
and doing two additional minor improvements:1) Renaming ExplainShowSettings to ExplainPrintSettings, to make it more
consistent with naming of the other functions in explain.c.2) Actually counting GUC_EXPLAIN options, and only allocating space for
those in get_explain_guc_options, instead of using num_guc_variables. The
diffrence is quite significant (~50 vs. ~300), and considering each entry
is 8B it makes a difference because such large chunks tend to have higher
palloc overhed (due to ALLOCSET_SEPARATE_THRESHOLD).Looks like the patch is in need of a rebase.
At commit: 1983af8e899389187026cb34c1ca9d89ea986120P.S. reject files attached.
D'oh! That was a stupid mistake - I apparently attched just the delta against
the previous patch version, i.e. the improvements I described. Attaches is a
correct (and complete) patch.
I planned to get this committed today, but considering this I'll wait until
early next week to allow for feedback.
regards
--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Attachments:
explain-with-settings-20190329.patchtext/plain; charset=us-asciiDownload
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..2a289b8b94 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,72 @@ ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into, ExplainState *es,
ExplainCloseGroup("Query", NULL, true, es);
}
+/*
+ * ExplainPrintSettings -
+ * Print summary of modified settings affecting query planning.
+ */
+static void
+ExplainPrintSettings(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 +702,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 with values
+ * that don't match the built-in defaults.
+ */
+ ExplainPrintSettings(es);
}
/*
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index aa564d153a..39f844ebc5 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -881,7 +881,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,
@@ -890,7 +891,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,
@@ -899,7 +901,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,
@@ -908,7 +911,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,
@@ -917,7 +921,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,
@@ -926,7 +931,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,
@@ -935,7 +941,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,
@@ -944,7 +951,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,
@@ -953,7 +961,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,
@@ -962,7 +971,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,
@@ -971,7 +981,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,
@@ -980,7 +991,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,
@@ -989,7 +1001,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,
@@ -998,7 +1011,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,
@@ -1007,7 +1021,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,
@@ -1016,7 +1031,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,
@@ -1027,7 +1043,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,
@@ -1037,7 +1054,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,
@@ -1625,7 +1643,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,
@@ -1816,7 +1834,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,
@@ -1826,7 +1845,8 @@ static struct config_bool ConfigureNamesBool[] =
{
{"jit", PGC_USERSET, QUERY_TUNING_OTHER,
gettext_noop("Allow JIT compilation."),
- NULL
+ NULL,
+ GUC_EXPLAIN
},
&jit_enabled,
true,
@@ -1955,7 +1975,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,
@@ -1967,7 +1988,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,
@@ -1976,7 +1998,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,
@@ -1985,7 +2008,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,
@@ -1994,7 +2018,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,
@@ -2003,7 +2028,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,
@@ -2117,7 +2143,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,
@@ -2184,7 +2210,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,
@@ -2713,7 +2739,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
@@ -2958,7 +2985,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,
@@ -2968,7 +2996,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,
@@ -3058,7 +3087,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,
@@ -3069,7 +3098,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,
@@ -3080,7 +3109,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,
@@ -3145,7 +3174,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,
@@ -3155,7 +3185,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,
@@ -3165,7 +3196,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,
@@ -3175,7 +3207,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,
@@ -3185,7 +3218,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,
@@ -3195,7 +3229,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,
@@ -3205,7 +3240,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,
@@ -3215,7 +3251,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,
@@ -3225,7 +3262,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,
@@ -3235,7 +3273,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,
@@ -3246,7 +3285,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,
@@ -3256,7 +3296,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,
@@ -3266,7 +3307,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,
@@ -3714,7 +3756,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",
@@ -4167,7 +4209,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,
@@ -4403,7 +4446,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,
@@ -4427,7 +4471,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,
@@ -4487,6 +4532,7 @@ static struct config_generic **guc_variables;
/* Current number of variables contained in the vector */
static int num_guc_variables;
+static int num_guc_explain_variables;
/* Vector capacity */
static int size_guc_variables;
@@ -4751,6 +4797,7 @@ build_guc_variables(void)
{
int size_vars;
int num_vars = 0;
+ int num_explain_vars = 0;
struct config_generic **guc_vars;
int i;
@@ -4761,6 +4808,9 @@ build_guc_variables(void)
/* Rather than requiring vartype to be filled in by hand, do this: */
conf->gen.vartype = PGC_BOOL;
num_vars++;
+
+ if (conf->gen.flags & GUC_EXPLAIN)
+ num_explain_vars++;
}
for (i = 0; ConfigureNamesInt[i].gen.name; i++)
@@ -4769,6 +4819,9 @@ build_guc_variables(void)
conf->gen.vartype = PGC_INT;
num_vars++;
+
+ if (conf->gen.flags & GUC_EXPLAIN)
+ num_explain_vars++;
}
for (i = 0; ConfigureNamesReal[i].gen.name; i++)
@@ -4777,6 +4830,9 @@ build_guc_variables(void)
conf->gen.vartype = PGC_REAL;
num_vars++;
+
+ if (conf->gen.flags & GUC_EXPLAIN)
+ num_explain_vars++;
}
for (i = 0; ConfigureNamesString[i].gen.name; i++)
@@ -4785,6 +4841,9 @@ build_guc_variables(void)
conf->gen.vartype = PGC_STRING;
num_vars++;
+
+ if (conf->gen.flags & GUC_EXPLAIN)
+ num_explain_vars++;
}
for (i = 0; ConfigureNamesEnum[i].gen.name; i++)
@@ -4793,6 +4852,9 @@ build_guc_variables(void)
conf->gen.vartype = PGC_ENUM;
num_vars++;
+
+ if (conf->gen.flags & GUC_EXPLAIN)
+ num_explain_vars++;
}
/*
@@ -4824,6 +4886,7 @@ build_guc_variables(void)
free(guc_variables);
guc_variables = guc_vars;
num_guc_variables = num_vars;
+ num_guc_explain_variables = num_explain_vars;
size_guc_variables = size_vars;
qsort((void *) guc_variables, num_guc_variables,
sizeof(struct config_generic *), guc_var_compare);
@@ -8774,6 +8837,99 @@ ShowAllGUCConfig(DestReceiver *dest)
end_tup_output(tstate);
}
+/*
+ * Returns an array of modified GUC options to show in EXPLAIN. Only options
+ * related to query planning (marked with GUC_EXPLAIN), with values different
+ * from built-in defaults.
+ */
+struct config_generic **
+get_explain_guc_options(int *num)
+{
+ int i;
+ struct config_generic **result;
+
+ *num = 0;
+
+ /*
+ * Allocate enough space to fit all GUC_EXPLAIN options. We may not
+ * need all the space, but there are fairly few such options so we
+ * don't waste a lot of memory.
+ */
+ result = palloc(sizeof(struct config_generic *) * num_guc_explain_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;
+
+ Assert(*num <= num_guc_explain_variables);
+ }
+
+ 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 2712a774f7..f73edc3edc 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 */
On Fri, 29 Mar 2019 at 22:07, Tomas Vondra <tomas.vondra@2ndquadrant.com>
wrote:
On Wed, Mar 27, 2019 at 09:06:04AM +0100, Rafia Sabih wrote:
On Tue, 26 Mar 2019 at 21:04, Tomas Vondra <tomas.vondra@2ndquadrant.com>
wrote:On Mon, Mar 18, 2019 at 11:31:48AM +0100, Rafia Sabih wrote:
On Sun, 24 Feb 2019 at 00:06, Tomas Vondra <
tomas.vondra@2ndquadrant.com>
wrote:
Hi,
attached is an updated patch, fixing and slightly tweaking the docs.
Barring objections, I'll get this committed later next week.
I was having a look at this patch, and this kept me wondering,
+static void +ExplainShowSettings(ExplainState *es) +{ Is there some reason for not providing any explanation above this function just like the rest of the functions in this file?Similarly, for
struct config_generic **
get_explain_guc_options(int *num)
{/* also bail out of there are no options */ + if (!num) + return; I think you meant 'if' instead if 'of' here.Thanks for the review - attached is a patch adding the missing comments,
and doing two additional minor improvements:1) Renaming ExplainShowSettings to ExplainPrintSettings, to make it more
consistent with naming of the other functions in explain.c.2) Actually counting GUC_EXPLAIN options, and only allocating space for
those in get_explain_guc_options, instead of using num_guc_variables.The
diffrence is quite significant (~50 vs. ~300), and considering each
entry
is 8B it makes a difference because such large chunks tend to have
higher
palloc overhed (due to ALLOCSET_SEPARATE_THRESHOLD).
Looks like the patch is in need of a rebase.
At commit: 1983af8e899389187026cb34c1ca9d89ea986120P.S. reject files attached.
D'oh! That was a stupid mistake - I apparently attched just the delta
against
the previous patch version, i.e. the improvements I described. Attaches is
a
correct (and complete) patch.I planned to get this committed today, but considering this I'll wait until
early next week to allow for feedback.The patch looks good to me.
--
Regards,
Rafia Sabih
Hi,
I've committed this, with some minor documentation tweaks. I've also
fixed a minor bug in the last patch, where the group with settings was
not properly labeled in some formats (e.g. json).
Thanks to all the reviewers!
regards
--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services