mirror of
https://github.com/postgres/postgres.git
synced 2026-04-15 22:10:45 -04:00
auto_explain: Add new GUC, auto_explain.log_extension_options.
The associated value should look like something that could be part of an EXPLAIN options list, but restricted to EXPLAIN options added by extensions. For example, if pg_overexplain is loaded, you could set auto_explain.log_extension_options = 'DEBUG, RANGE_TABLE'. You can also specify arguments to these options in the same manner as normal e.g. 'DEBUG 1, RANGE_TABLE false'. Reviewed-by: Matheus Alcantara <matheusssilv97@gmail.com> Reviewed-by: Lukas Fittl <lukas@fittl.com> Discussion: http://postgr.es/m/CA+Tgmob-0W8306mvrJX5Urtqt1AAasu8pi4yLrZ1XfwZU-Uj1w@mail.gmail.com
This commit is contained in:
parent
d516974840
commit
e972dff6c3
8 changed files with 514 additions and 2 deletions
|
|
@ -6,7 +6,8 @@ OBJS = \
|
|||
auto_explain.o
|
||||
PGFILEDESC = "auto_explain - logging facility for execution plans"
|
||||
|
||||
REGRESS = alter_reset
|
||||
EXTRA_INSTALL = contrib/pg_overexplain
|
||||
REGRESS = alter_reset extension_options
|
||||
|
||||
TAP_TESTS = 1
|
||||
|
||||
|
|
|
|||
|
|
@ -15,12 +15,17 @@
|
|||
#include <limits.h>
|
||||
|
||||
#include "access/parallel.h"
|
||||
#include "commands/defrem.h"
|
||||
#include "commands/explain.h"
|
||||
#include "commands/explain_format.h"
|
||||
#include "commands/explain_state.h"
|
||||
#include "common/pg_prng.h"
|
||||
#include "executor/instrument.h"
|
||||
#include "nodes/makefuncs.h"
|
||||
#include "nodes/value.h"
|
||||
#include "parser/scansup.h"
|
||||
#include "utils/guc.h"
|
||||
#include "utils/varlena.h"
|
||||
|
||||
PG_MODULE_MAGIC_EXT(
|
||||
.name = "auto_explain",
|
||||
|
|
@ -41,6 +46,31 @@ static int auto_explain_log_format = EXPLAIN_FORMAT_TEXT;
|
|||
static int auto_explain_log_level = LOG;
|
||||
static bool auto_explain_log_nested_statements = false;
|
||||
static double auto_explain_sample_rate = 1;
|
||||
static char *auto_explain_log_extension_options = NULL;
|
||||
|
||||
/*
|
||||
* Parsed form of one option from auto_explain.log_extension_options.
|
||||
*/
|
||||
typedef struct auto_explain_option
|
||||
{
|
||||
char *name;
|
||||
char *value;
|
||||
NodeTag type;
|
||||
} auto_explain_option;
|
||||
|
||||
/*
|
||||
* Parsed form of the entirety of auto_explain.log_extension_options, stored
|
||||
* as GUC extra. The options[] array will have pointers into the string
|
||||
* following the array.
|
||||
*/
|
||||
typedef struct auto_explain_extension_options
|
||||
{
|
||||
int noptions;
|
||||
auto_explain_option options[FLEXIBLE_ARRAY_MEMBER];
|
||||
/* a null-terminated copy of the GUC string follows the array */
|
||||
} auto_explain_extension_options;
|
||||
|
||||
static auto_explain_extension_options *extension_options = NULL;
|
||||
|
||||
static const struct config_enum_entry format_options[] = {
|
||||
{"text", EXPLAIN_FORMAT_TEXT, false},
|
||||
|
|
@ -88,6 +118,15 @@ static void explain_ExecutorRun(QueryDesc *queryDesc,
|
|||
static void explain_ExecutorFinish(QueryDesc *queryDesc);
|
||||
static void explain_ExecutorEnd(QueryDesc *queryDesc);
|
||||
|
||||
static bool check_log_extension_options(char **newval, void **extra,
|
||||
GucSource source);
|
||||
static void assign_log_extension_options(const char *newval, void *extra);
|
||||
static void apply_extension_options(ExplainState *es,
|
||||
auto_explain_extension_options *ext);
|
||||
static char *auto_explain_scan_literal(char **endp, char **nextp);
|
||||
static int auto_explain_split_options(char *rawstring,
|
||||
auto_explain_option *options,
|
||||
int maxoptions, char **errmsg);
|
||||
|
||||
/*
|
||||
* Module load callback
|
||||
|
|
@ -232,6 +271,17 @@ _PG_init(void)
|
|||
NULL,
|
||||
NULL);
|
||||
|
||||
DefineCustomStringVariable("auto_explain.log_extension_options",
|
||||
"Extension EXPLAIN options to be added.",
|
||||
NULL,
|
||||
&auto_explain_log_extension_options,
|
||||
NULL,
|
||||
PGC_SUSET,
|
||||
0,
|
||||
check_log_extension_options,
|
||||
assign_log_extension_options,
|
||||
NULL);
|
||||
|
||||
DefineCustomRealVariable("auto_explain.sample_rate",
|
||||
"Fraction of queries to process.",
|
||||
NULL,
|
||||
|
|
@ -398,6 +448,8 @@ explain_ExecutorEnd(QueryDesc *queryDesc)
|
|||
es->format = auto_explain_log_format;
|
||||
es->settings = auto_explain_log_settings;
|
||||
|
||||
apply_extension_options(es, extension_options);
|
||||
|
||||
ExplainBeginOutput(es);
|
||||
ExplainQueryText(es, queryDesc);
|
||||
ExplainQueryParameters(es, queryDesc->params, auto_explain_log_parameter_max_length);
|
||||
|
|
@ -406,6 +458,12 @@ explain_ExecutorEnd(QueryDesc *queryDesc)
|
|||
ExplainPrintTriggers(es, queryDesc);
|
||||
if (es->costs)
|
||||
ExplainPrintJITSummary(es, queryDesc);
|
||||
if (explain_per_plan_hook)
|
||||
(*explain_per_plan_hook) (queryDesc->plannedstmt,
|
||||
NULL, es,
|
||||
queryDesc->sourceText,
|
||||
queryDesc->params,
|
||||
queryDesc->estate->es_queryEnv);
|
||||
ExplainEndOutput(es);
|
||||
|
||||
/* Remove last line break */
|
||||
|
|
@ -439,3 +497,332 @@ explain_ExecutorEnd(QueryDesc *queryDesc)
|
|||
else
|
||||
standard_ExecutorEnd(queryDesc);
|
||||
}
|
||||
|
||||
/*
|
||||
* GUC check hook for auto_explain.log_extension_options.
|
||||
*/
|
||||
static bool
|
||||
check_log_extension_options(char **newval, void **extra, GucSource source)
|
||||
{
|
||||
char *rawstring;
|
||||
auto_explain_extension_options *result;
|
||||
auto_explain_option *options;
|
||||
int maxoptions = 8;
|
||||
Size rawstring_len;
|
||||
Size allocsize;
|
||||
char *errmsg;
|
||||
|
||||
/* NULL or empty string means no options. */
|
||||
if (*newval == NULL || (*newval)[0] == '\0')
|
||||
{
|
||||
*extra = NULL;
|
||||
return true;
|
||||
}
|
||||
|
||||
rawstring_len = strlen(*newval) + 1;
|
||||
|
||||
retry:
|
||||
/* Try to allocate an auto_explain_extension_options object. */
|
||||
allocsize = offsetof(auto_explain_extension_options, options) +
|
||||
sizeof(auto_explain_option) * maxoptions +
|
||||
rawstring_len;
|
||||
result = (auto_explain_extension_options *) guc_malloc(LOG, allocsize);
|
||||
if (result == NULL)
|
||||
return false;
|
||||
|
||||
/* Copy the string after the options array. */
|
||||
rawstring = (char *) &result->options[maxoptions];
|
||||
memcpy(rawstring, *newval, rawstring_len);
|
||||
|
||||
/* Parse. */
|
||||
options = result->options;
|
||||
result->noptions = auto_explain_split_options(rawstring, options,
|
||||
maxoptions, &errmsg);
|
||||
if (result->noptions < 0)
|
||||
{
|
||||
GUC_check_errdetail("%s", errmsg);
|
||||
guc_free(result);
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Retry with a larger array if needed.
|
||||
*
|
||||
* It should be impossible for this to loop more than once, because
|
||||
* auto_explain_split_options tells us how many entries are needed.
|
||||
*/
|
||||
if (result->noptions > maxoptions)
|
||||
{
|
||||
maxoptions = result->noptions;
|
||||
guc_free(result);
|
||||
goto retry;
|
||||
}
|
||||
|
||||
/* Validate each option against its registered check handler. */
|
||||
for (int i = 0; i < result->noptions; i++)
|
||||
{
|
||||
if (!GUCCheckExplainExtensionOption(options[i].name, options[i].value,
|
||||
options[i].type))
|
||||
{
|
||||
guc_free(result);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
*extra = result;
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* GUC assign hook for auto_explain.log_extension_options.
|
||||
*/
|
||||
static void
|
||||
assign_log_extension_options(const char *newval, void *extra)
|
||||
{
|
||||
extension_options = (auto_explain_extension_options *) extra;
|
||||
}
|
||||
|
||||
/*
|
||||
* Apply parsed extension options to an ExplainState.
|
||||
*/
|
||||
static void
|
||||
apply_extension_options(ExplainState *es, auto_explain_extension_options *ext)
|
||||
{
|
||||
if (ext == NULL)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < ext->noptions; i++)
|
||||
{
|
||||
auto_explain_option *opt = &ext->options[i];
|
||||
DefElem *def;
|
||||
Node *arg;
|
||||
|
||||
if (opt->value == NULL)
|
||||
arg = NULL;
|
||||
else if (opt->type == T_Integer)
|
||||
arg = (Node *) makeInteger(strtol(opt->value, NULL, 0));
|
||||
else if (opt->type == T_Float)
|
||||
arg = (Node *) makeFloat(opt->value);
|
||||
else
|
||||
arg = (Node *) makeString(opt->value);
|
||||
|
||||
def = makeDefElem(opt->name, arg, -1);
|
||||
ApplyExtensionExplainOption(es, def, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* auto_explain_scan_literal - In-place scanner for single-quoted string
|
||||
* literals.
|
||||
*
|
||||
* This is the single-quote analog of scan_quoted_identifier from varlena.c.
|
||||
*/
|
||||
static char *
|
||||
auto_explain_scan_literal(char **endp, char **nextp)
|
||||
{
|
||||
char *token = *nextp + 1;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
*endp = strchr(*nextp + 1, '\'');
|
||||
if (*endp == NULL)
|
||||
return NULL; /* mismatched quotes */
|
||||
if ((*endp)[1] != '\'')
|
||||
break; /* found end of literal */
|
||||
/* Collapse adjacent quotes into one quote, and look again */
|
||||
memmove(*endp, *endp + 1, strlen(*endp));
|
||||
*nextp = *endp;
|
||||
}
|
||||
/* *endp now points at the terminating quote */
|
||||
*nextp = *endp + 1;
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
/*
|
||||
* auto_explain_split_options - Parse an option string into an array of
|
||||
* auto_explain_option structs.
|
||||
*
|
||||
* Much of this logic is similar to SplitIdentifierString and friends, but our
|
||||
* needs are different enough that we roll our own parsing logic. The goal here
|
||||
* is to accept the same syntax that the main parser would accept inside of
|
||||
* an EXPLAIN option list. While we can't do that perfectly without adding a
|
||||
* lot more code, the goal of this implementation is to be close enough that
|
||||
* users don't really notice the differences.
|
||||
*
|
||||
* The input string is modified in place (null-terminated, downcased, quotes
|
||||
* collapsed). All name and value pointers in the output array refer into
|
||||
* this string, so the caller must ensure the string outlives the array.
|
||||
*
|
||||
* Returns the full number of options in the input string, but stores no
|
||||
* more than maxoptions into the caller-provided array. If a syntax error
|
||||
* occurs, returns -1 and sets *errmsg.
|
||||
*/
|
||||
static int
|
||||
auto_explain_split_options(char *rawstring, auto_explain_option *options,
|
||||
int maxoptions, char **errmsg)
|
||||
{
|
||||
char *nextp = rawstring;
|
||||
int noptions = 0;
|
||||
bool done = false;
|
||||
|
||||
*errmsg = NULL;
|
||||
|
||||
while (scanner_isspace(*nextp))
|
||||
nextp++; /* skip leading whitespace */
|
||||
|
||||
if (*nextp == '\0')
|
||||
return 0; /* empty string is fine */
|
||||
|
||||
while (!done)
|
||||
{
|
||||
char *name;
|
||||
char *name_endp;
|
||||
char *value = NULL;
|
||||
char *value_endp = NULL;
|
||||
NodeTag type = T_Invalid;
|
||||
|
||||
/* Parse the option name. */
|
||||
name = scan_identifier(&name_endp, &nextp, ',', true);
|
||||
if (name == NULL || name_endp == name)
|
||||
{
|
||||
*errmsg = "option name missing or empty";
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Skip whitespace after the option name. */
|
||||
while (scanner_isspace(*nextp))
|
||||
nextp++;
|
||||
|
||||
/*
|
||||
* Determine whether we have an option value. A comma or end of
|
||||
* string means no value; otherwise we have one.
|
||||
*/
|
||||
if (*nextp != '\0' && *nextp != ',')
|
||||
{
|
||||
if (*nextp == '\'')
|
||||
{
|
||||
/* Single-quoted string literal. */
|
||||
type = T_String;
|
||||
value = auto_explain_scan_literal(&value_endp, &nextp);
|
||||
if (value == NULL)
|
||||
{
|
||||
*errmsg = "unterminated single-quoted string";
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
else if (isdigit((unsigned char) *nextp) ||
|
||||
((*nextp == '+' || *nextp == '-') &&
|
||||
isdigit((unsigned char) nextp[1])))
|
||||
{
|
||||
char *endptr;
|
||||
long intval;
|
||||
char saved;
|
||||
|
||||
/* Remember the start of the next token, and find the end. */
|
||||
value = nextp;
|
||||
while (*nextp && *nextp != ',' && !scanner_isspace(*nextp))
|
||||
nextp++;
|
||||
value_endp = nextp;
|
||||
|
||||
/* Temporarily '\0'-terminate so we can use strtol/strtod. */
|
||||
saved = *value_endp;
|
||||
*value_endp = '\0';
|
||||
|
||||
/*
|
||||
* Integer, float, or neither?
|
||||
*
|
||||
* NB: Since we use strtol and strtod here rather than
|
||||
* pg_strtoint64_safe, some syntax that would be accepted by
|
||||
* the main parser is not accepted here, e.g. 100_000. On the
|
||||
* plus side, strtol and strtod won't allocate, and
|
||||
* pg_strtoint64_safe might. For now, it seems better to keep
|
||||
* things simple here.
|
||||
*/
|
||||
errno = 0;
|
||||
intval = strtol(value, &endptr, 0);
|
||||
if (errno == 0 && *endptr == '\0' && endptr != value &&
|
||||
intval == (int) intval)
|
||||
type = T_Integer;
|
||||
else
|
||||
{
|
||||
type = T_Float;
|
||||
(void) strtod(value, &endptr);
|
||||
if (*endptr != '\0')
|
||||
{
|
||||
*value_endp = saved;
|
||||
*errmsg = "invalid numeric value";
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Remove temporary terminator. */
|
||||
*value_endp = saved;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Identifier, possibly double-quoted. */
|
||||
type = T_String;
|
||||
value = scan_identifier(&value_endp, &nextp, ',', true);
|
||||
if (value == NULL)
|
||||
{
|
||||
/*
|
||||
* scan_identifier will return NULL if it finds an
|
||||
* unterminated double-quoted identifier or it finds no
|
||||
* identifier at all because the next character is
|
||||
* whitespace or the separator character, here a comma.
|
||||
* But the latter case is impossible here because the code
|
||||
* above has skipped whitespace and checked for commas.
|
||||
*/
|
||||
*errmsg = "unterminated double-quoted string";
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Skip trailing whitespace. */
|
||||
while (scanner_isspace(*nextp))
|
||||
nextp++;
|
||||
|
||||
/* Expect comma or end of string. */
|
||||
if (*nextp == ',')
|
||||
{
|
||||
nextp++;
|
||||
while (scanner_isspace(*nextp))
|
||||
nextp++;
|
||||
if (*nextp == '\0')
|
||||
{
|
||||
*errmsg = "trailing comma in option list";
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
else if (*nextp == '\0')
|
||||
done = true;
|
||||
else
|
||||
{
|
||||
*errmsg = "expected comma or end of option list";
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Now safe to null-terminate the name and value. We couldn't do this
|
||||
* earlier because in the unquoted case, the null terminator position
|
||||
* may coincide with a character that the scanning logic above still
|
||||
* needed to read.
|
||||
*/
|
||||
*name_endp = '\0';
|
||||
if (value_endp != NULL)
|
||||
*value_endp = '\0';
|
||||
|
||||
/* Always count this option, and store the details if there is room. */
|
||||
if (noptions < maxoptions)
|
||||
{
|
||||
options[noptions].name = name;
|
||||
options[noptions].type = type;
|
||||
options[noptions].value = value;
|
||||
}
|
||||
noptions++;
|
||||
}
|
||||
|
||||
return noptions;
|
||||
}
|
||||
|
|
|
|||
49
contrib/auto_explain/expected/extension_options.out
Normal file
49
contrib/auto_explain/expected/extension_options.out
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
--
|
||||
-- Tests for auto_explain.log_extension_options.
|
||||
--
|
||||
LOAD 'auto_explain';
|
||||
LOAD 'pg_overexplain';
|
||||
-- Various legal values with assorted quoting and whitespace choices.
|
||||
SET auto_explain.log_extension_options = '';
|
||||
SET auto_explain.log_extension_options = 'debug, RANGE_TABLE';
|
||||
SET auto_explain.log_extension_options = 'debug TRUE ';
|
||||
SET auto_explain.log_extension_options = ' debug 1,RAnge_table "off"';
|
||||
SET auto_explain.log_extension_options = $$"debug" tRuE, range_table 'false'$$;
|
||||
-- Syntax errors.
|
||||
SET auto_explain.log_extension_options = ',';
|
||||
ERROR: invalid value for parameter "auto_explain.log_extension_options": ","
|
||||
DETAIL: option name missing or empty
|
||||
SET auto_explain.log_extension_options = ', range_table';
|
||||
ERROR: invalid value for parameter "auto_explain.log_extension_options": ", range_table"
|
||||
DETAIL: option name missing or empty
|
||||
SET auto_explain.log_extension_options = 'range_table, ';
|
||||
ERROR: invalid value for parameter "auto_explain.log_extension_options": "range_table, "
|
||||
DETAIL: trailing comma in option list
|
||||
SET auto_explain.log_extension_options = 'range_table true false';
|
||||
ERROR: invalid value for parameter "auto_explain.log_extension_options": "range_table true false"
|
||||
DETAIL: expected comma or end of option list
|
||||
SET auto_explain.log_extension_options = '"range_table';
|
||||
ERROR: invalid value for parameter "auto_explain.log_extension_options": ""range_table"
|
||||
DETAIL: option name missing or empty
|
||||
SET auto_explain.log_extension_options = 'range_table 3.1415nine';
|
||||
ERROR: invalid value for parameter "auto_explain.log_extension_options": "range_table 3.1415nine"
|
||||
DETAIL: invalid numeric value
|
||||
SET auto_explain.log_extension_options = 'range_table "true';
|
||||
ERROR: invalid value for parameter "auto_explain.log_extension_options": "range_table "true"
|
||||
DETAIL: unterminated double-quoted string
|
||||
SET auto_explain.log_extension_options = $$range_table 'true$$;
|
||||
ERROR: invalid value for parameter "auto_explain.log_extension_options": "range_table 'true"
|
||||
DETAIL: unterminated single-quoted string
|
||||
SET auto_explain.log_extension_options = $$'$$;
|
||||
ERROR: unrecognized EXPLAIN option "'"
|
||||
-- Unacceptable option values.
|
||||
SET auto_explain.log_extension_options = 'range_table maybe';
|
||||
ERROR: EXPLAIN option "range_table" requires a Boolean value
|
||||
SET auto_explain.log_extension_options = 'range_table 2';
|
||||
ERROR: EXPLAIN option "range_table" requires a Boolean value
|
||||
SET auto_explain.log_extension_options = 'range_table "0"';
|
||||
ERROR: EXPLAIN option "range_table" requires a Boolean value
|
||||
SET auto_explain.log_extension_options = 'range_table 3.14159';
|
||||
ERROR: EXPLAIN option "range_table" requires a Boolean value
|
||||
-- Supply enough options to force the option array to be reallocated.
|
||||
SET auto_explain.log_extension_options = 'debug, debug, debug, debug, debug, debug, debug, debug, debug, debug false';
|
||||
|
|
@ -23,6 +23,7 @@ tests += {
|
|||
'regress': {
|
||||
'sql': [
|
||||
'alter_reset',
|
||||
'extension_options',
|
||||
],
|
||||
},
|
||||
'tap': {
|
||||
|
|
|
|||
33
contrib/auto_explain/sql/extension_options.sql
Normal file
33
contrib/auto_explain/sql/extension_options.sql
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
--
|
||||
-- Tests for auto_explain.log_extension_options.
|
||||
--
|
||||
|
||||
LOAD 'auto_explain';
|
||||
LOAD 'pg_overexplain';
|
||||
|
||||
-- Various legal values with assorted quoting and whitespace choices.
|
||||
SET auto_explain.log_extension_options = '';
|
||||
SET auto_explain.log_extension_options = 'debug, RANGE_TABLE';
|
||||
SET auto_explain.log_extension_options = 'debug TRUE ';
|
||||
SET auto_explain.log_extension_options = ' debug 1,RAnge_table "off"';
|
||||
SET auto_explain.log_extension_options = $$"debug" tRuE, range_table 'false'$$;
|
||||
|
||||
-- Syntax errors.
|
||||
SET auto_explain.log_extension_options = ',';
|
||||
SET auto_explain.log_extension_options = ', range_table';
|
||||
SET auto_explain.log_extension_options = 'range_table, ';
|
||||
SET auto_explain.log_extension_options = 'range_table true false';
|
||||
SET auto_explain.log_extension_options = '"range_table';
|
||||
SET auto_explain.log_extension_options = 'range_table 3.1415nine';
|
||||
SET auto_explain.log_extension_options = 'range_table "true';
|
||||
SET auto_explain.log_extension_options = $$range_table 'true$$;
|
||||
SET auto_explain.log_extension_options = $$'$$;
|
||||
|
||||
-- Unacceptable option values.
|
||||
SET auto_explain.log_extension_options = 'range_table maybe';
|
||||
SET auto_explain.log_extension_options = 'range_table 2';
|
||||
SET auto_explain.log_extension_options = 'range_table "0"';
|
||||
SET auto_explain.log_extension_options = 'range_table 3.14159';
|
||||
|
||||
-- Supply enough options to force the option array to be reallocated.
|
||||
SET auto_explain.log_extension_options = 'debug, debug, debug, debug, debug, debug, debug, debug, debug, debug false';
|
||||
|
|
@ -30,7 +30,7 @@ sub query_log
|
|||
my $node = PostgreSQL::Test::Cluster->new('main');
|
||||
$node->init(auth_extra => [ '--create-role' => 'regress_user1' ]);
|
||||
$node->append_conf('postgresql.conf',
|
||||
"session_preload_libraries = 'auto_explain'");
|
||||
"session_preload_libraries = 'pg_overexplain,auto_explain'");
|
||||
$node->append_conf('postgresql.conf', "auto_explain.log_min_duration = 0");
|
||||
$node->append_conf('postgresql.conf', "auto_explain.log_analyze = on");
|
||||
$node->start;
|
||||
|
|
@ -172,6 +172,22 @@ like(
|
|||
qr/"Node Type": "Index Scan"[^}]*"Index Name": "pg_class_relname_nsp_index"/s,
|
||||
"index scan logged, json mode");
|
||||
|
||||
# Extension options.
|
||||
$log_contents = query_log(
|
||||
$node,
|
||||
"SELECT 1;",
|
||||
{ "auto_explain.log_extension_options" => "debug" });
|
||||
|
||||
like(
|
||||
$log_contents,
|
||||
qr/Parallel Safe:/,
|
||||
"extension option produces per-node output");
|
||||
|
||||
like(
|
||||
$log_contents,
|
||||
qr/Command Type: select/,
|
||||
"extension option produces per-plan output");
|
||||
|
||||
# Check that PGC_SUSET parameters can be set by non-superuser if granted,
|
||||
# otherwise not
|
||||
|
||||
|
|
|
|||
|
|
@ -245,6 +245,29 @@ LOAD 'auto_explain';
|
|||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry id="auto-explain-configuration-parameters-log-extension-options">
|
||||
<term>
|
||||
<varname>auto_explain.log_extension_options</varname> (<type>string</type>)
|
||||
<indexterm>
|
||||
<primary><varname>auto_explain.log_extension_options</varname> configuration parameter</primary>
|
||||
</indexterm>
|
||||
</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Loadable modules can extend the <literal>EXPLAIN</literal> command with
|
||||
additional options that affect the output format. Such options can also
|
||||
be specified here. The value of this parameter is a comma-separated
|
||||
list of options, each of which is an option name followed optionally by
|
||||
an associated value. The module that provides the
|
||||
<literal>EXPLAIN</literal> option, such as
|
||||
<link linkend="pgplanadvice"><literal>pg_plan_advice</literal></link> or
|
||||
<link linkend="pgoverexplain"><literal>pg_overexplain</literal></link>,
|
||||
should be loaded before this parameter is set.
|
||||
Only superusers can change this setting.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry id="auto-explain-configuration-parameters-log-level">
|
||||
<term>
|
||||
<varname>auto_explain.log_level</varname> (<type>enum</type>)
|
||||
|
|
|
|||
|
|
@ -3585,6 +3585,8 @@ astreamer_verify
|
|||
astreamer_waldump
|
||||
astreamer_zstd_frame
|
||||
auth_password_hook_typ
|
||||
auto_explain_extension_options
|
||||
auto_explain_option
|
||||
autovac_table
|
||||
av_relation
|
||||
avc_cache
|
||||
|
|
|
|||
Loading…
Reference in a new issue