On Mon, 2008-11-10 at 18:02 +0900, ITAGAKI Takahiro wrote: > That's because explain.log_analyze requires executor instruments, > and it's not free. I think we'd better to have an option to avoid > the overheads... Oops, I found my bug when force_instrument is > turned on. It should be enabled only when > (explain_log_min_duration >= 0 && explain_log_analyze). > > I send a new patch to fix it. A documentation about > explain.log_nested_statements is also added to the sgml file. >
Great. I attached a patch with some minor documentation changes. There seems to be no auto_explain view, so I assumed that section of the docs was old, and I removed it. Let me know if you intend to include the view. Thanks! This patch is ready to go, as far as I'm concerned. Regards, Jeff Davis
diff --git a/contrib/Makefile b/contrib/Makefile index 30f75c7..b25af3c 100644 --- a/contrib/Makefile +++ b/contrib/Makefile @@ -6,6 +6,7 @@ include $(top_builddir)/src/Makefile.global WANTED_DIRS = \ adminpack \ + auto_explain \ btree_gist \ chkpass \ citext \ diff --git a/contrib/auto_explain/Makefile b/contrib/auto_explain/Makefile new file mode 100644 index 0000000..0d0ccc2 --- /dev/null +++ b/contrib/auto_explain/Makefile @@ -0,0 +1,13 @@ +MODULE_big = auto_explain +OBJS = auto_explain.o + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = contrib/auto_explain +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/contrib/auto_explain/auto_explain.c b/contrib/auto_explain/auto_explain.c new file mode 100644 index 0000000..af994e7 --- /dev/null +++ b/contrib/auto_explain/auto_explain.c @@ -0,0 +1,215 @@ +/* + * auto_explain.c + */ +#include "postgres.h" + +#include "commands/explain.h" +#include "executor/instrument.h" +#include "miscadmin.h" +#include "utils/guc_tables.h" + +PG_MODULE_MAGIC; + +#define GUCNAME(name) ("explain." name) + +static int explain_log_min_duration = -1; /* msec or -1 */ +static bool explain_log_analyze = false; +static bool explain_log_verbose = false; +static bool explain_log_nested = false; + +static bool toplevel = true; +static ExecutorRun_hook_type prev_ExecutorRun = NULL; + +void _PG_init(void); +void _PG_fini(void); + +static void explain_ExecutorRun(QueryDesc *queryDesc, + ScanDirection direction, + long count); +static bool assign_log_min_duration(int newval, bool doit, GucSource source); +static bool assign_log_analyze(bool newval, bool doit, GucSource source); +static bool assign_log_verbose(bool newval, bool doit, GucSource source); +static bool assign_log_nested(bool newval, bool doit, GucSource source); + +static struct config_int def_log_min_duration = +{ + { + GUCNAME("log_min_duration"), + PGC_USERSET, + STATS_MONITORING, + "Sets the minimum execution time above which plans will be logged.", + "Zero prints all plans. -1 turns this feature off.", + GUC_UNIT_MS + }, + &explain_log_min_duration, + -1, -1, INT_MAX / 1000, assign_log_min_duration, NULL +}; + +static struct config_bool def_log_analyze = +{ + { + GUCNAME("log_analyze"), + PGC_USERSET, + STATS_MONITORING, + "Use EXPLAIN ANALYZE for plan logging." + }, + &explain_log_analyze, + false, assign_log_analyze, NULL +}; + +static struct config_bool def_log_verbose = +{ + { + GUCNAME("log_verbose"), + PGC_USERSET, + STATS_MONITORING, + "Use EXPLAIN VERBOSE for plan logging." + }, + &explain_log_verbose, + false, assign_log_verbose, NULL +}; + +static struct config_bool def_log_nested_statements = +{ + { + GUCNAME("log_nested_statements"), + PGC_USERSET, + STATS_MONITORING, + "Log nested statements." + }, + &explain_log_nested, + false, assign_log_nested, NULL +}; + +void +_PG_init(void) +{ + DefineCustomVariable(PGC_INT, &def_log_min_duration); + DefineCustomVariable(PGC_BOOL, &def_log_analyze); + DefineCustomVariable(PGC_BOOL, &def_log_verbose); + DefineCustomVariable(PGC_BOOL, &def_log_nested_statements); + + /* install ExecutorRun_hook */ + prev_ExecutorRun = ExecutorRun_hook; + ExecutorRun_hook = explain_ExecutorRun; +} + +void +_PG_fini(void) +{ + /* uninstall ExecutorRun_hook */ + ExecutorRun_hook = prev_ExecutorRun; +} + +void +explain_ExecutorRun(QueryDesc *queryDesc, ScanDirection direction, long count) +{ + if ((toplevel || explain_log_nested) && explain_log_min_duration >= 0) + { + instr_time starttime; + instr_time duration; + double msec; + + /* Disable our hooks temporarily during the top-level query. */ + toplevel = false; + PG_TRY(); + { + INSTR_TIME_SET_CURRENT(starttime); + + if (prev_ExecutorRun) + prev_ExecutorRun(queryDesc, direction, count); + else + standard_ExecutorRun(queryDesc, direction, count); + + INSTR_TIME_SET_CURRENT(duration); + INSTR_TIME_SUBTRACT(duration, starttime); + msec = INSTR_TIME_GET_MILLISEC(duration); + + /* Log plan if duration is exceeded. */ + if (msec > explain_log_min_duration) + { + StringInfoData buf; + + initStringInfo(&buf); + ExplainOneResult(&buf, queryDesc, + queryDesc->doInstrument && explain_log_analyze, + explain_log_verbose); + + /* Remove last line break */ + if (buf.len > 0 && buf.data[buf.len - 1] == '\n') + { + buf.data[buf.len - 1] = '\0'; + buf.len--; + } + ereport(LOG, + (errmsg("duration: %.3f ms plan:\n%s", + msec, buf.data))); + + pfree(buf.data); + } + + toplevel = true; + } + PG_CATCH(); + { + toplevel = true; + PG_RE_THROW(); + } + PG_END_TRY(); + } + else + { + /* ignore recursive executions, that are typically function calls */ + if (prev_ExecutorRun) + prev_ExecutorRun(queryDesc, direction, count); + else + standard_ExecutorRun(queryDesc, direction, count); + } +} + +/* Emulate PGC_SUSET for custom variables. */ +static bool +suset_assign(GucSource source, const char *name) +{ + if (source >= PGC_S_CLIENT && !superuser()) + { + ereport(GUC_complaint_elevel(source), + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("permission denied to set parameter \"%s\"", name))); + return false; + } + + return true; +} + +static bool +assign_log_min_duration(int newval, bool doit, GucSource source) +{ + if (!suset_assign(source, GUCNAME("log_min_duration"))) + return false; + if (doit) + force_instrument = (newval >= 0 && explain_log_analyze); + return true; +} + +static bool +assign_log_analyze(bool newval, bool doit, GucSource source) +{ + if (!suset_assign(source, GUCNAME("log_analyze"))) + return false; + if (doit) + force_instrument = (explain_log_min_duration >= 0 && newval); + return true; +} + +static bool +assign_log_verbose(bool newval, bool doit, GucSource source) +{ + return suset_assign(source, GUCNAME("log_verbose")); +} + +static bool +assign_log_nested(bool newval, bool doit, GucSource source) +{ + return suset_assign(source, GUCNAME("log_nested_statements")); +} diff --git a/doc/src/sgml/auto-explain.sgml b/doc/src/sgml/auto-explain.sgml new file mode 100644 index 0000000..94b7daf --- /dev/null +++ b/doc/src/sgml/auto-explain.sgml @@ -0,0 +1,141 @@ +<sect1 id="autoexplain"> + <title>auto_explain</title> + + <indexterm zone="autoexplain"> + <primary>auto_explain</primary> + </indexterm> + + <para> + The <filename>auto_explain</filename> module provides a means for + logging execution plans automatically, without having to run <command>EXPLAIN</>. + </para> + + <para> + You can <command>LOAD</> this module dynamically or preload it automatically with + <varname>shared_preload_libraries</> or <varname>local_preload_libraries</>. + </para> + + <sect2> + <title>Configuration parameters</title> + + <variablelist> + <varlistentry> + <term> + <varname>explain.log_min_duration</varname> (<type>integer</type>) + </term> + <listitem> + <para> + <varname>explain.log_min_duration</varname> is the minimum execution time + in milliseconds that will cause the plan to be logged. Setting this to zero + logs all plans. Minus-one (the default) disables logging of plans. + For example, if you set it to <literal>250ms</literal> then all plans + that run 250ms or longer in executor will be logged. + Enabling this parameter can be helpful in tracking down unoptimized queries + in your applications. Only superusers can change this setting. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term> + <varname>explain.log_analyze</varname> (<type>boolean</type>) + </term> + + <listitem> + <para> + <varname>explain.log_analyze</varname> causes <command>EXPLAIN ANALYZE</> + to be used rather than normal <command>EXPLAIN</> when an execution plan + is logged. This parameter is off by default. Only superusers can change + this setting. + </para> + <para> + NOTE: If you set the parameter on, instrument timers are enabled even if + you don't use EXPLAIN ANALYZE. This creates some overhead for all + statements executed. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term> + <varname>explain.log_verbose</varname> (<type>boolean</type>) + </term> + + <listitem> + <para> + <varname>explain.log_verbose</varname> causes <command>EXPLAIN VERBOSE</> + to be used rather than normal <command>EXPLAIN</> when an execution plan + is logged. This parameter is off by default. Only superusers can change + this setting. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term> + <varname>explain.log_nested_statements</varname> (<type>boolean</type>) + </term> + + <listitem> + <para> + <varname>explain.log_nested_statements</varname> causes the execution plans + of nested statements (statements executed inside a function) to be logged + as well. If the value is off, only top-level plans are logged. This + parameter is off by default. Only superusers can change this setting. + </para> + </listitem> + </varlistentry> + </variablelist> + + <para> + If you set these explain.* parameters in your postgresql.conf, + you also need to add 'explain' in <varname>custom_variable_classes</>. + </para> + + <programlisting> + # postgresql.conf + shared_preload_libraries = 'auto_explain' + + custom_variable_classes = 'explain' + explain.log_min_duration = 3s + </programlisting> + </sect2> + + <sect2> + <title>Example</title> + + <programlisting> + postgres=# LOAD 'auto_explain'; + postgres=# SET explain.log_min_duration = 0; + postgres=# SELECT count(*) + FROM pg_class, pg_index + WHERE oid = indrelid AND indisunique; + </programlisting> + </sect2> + + <sect2> + <title>Sample Log Output</title> + <programlisting> + LOG: duration: 0.986 ms plan: + Aggregate (cost=14.90..14.91 rows=1 width=0) + -> Hash Join (cost=3.91..14.70 rows=81 width=0) + Hash Cond: (pg_class.oid = pg_index.indrelid) + -> Seq Scan on pg_class (cost=0.00..8.27 rows=227 width=4) + -> Hash (cost=2.90..2.90 rows=81 width=4) + -> Seq Scan on pg_index (cost=0.00..2.90 rows=81 width=4) + Filter: indisunique + STATEMENT: SELECT count(*) + FROM pg_class, pg_index + WHERE oid = indrelid AND indisunique; + </programlisting> + </sect2> + + <sect2> + <title>Authors</title> + + <para> + Takahiro Itagaki <email>[EMAIL PROTECTED]</email> + </para> + </sect2> + +</sect1> diff --git a/doc/src/sgml/contrib.sgml b/doc/src/sgml/contrib.sgml index 72d8828..0108da3 100644 --- a/doc/src/sgml/contrib.sgml +++ b/doc/src/sgml/contrib.sgml @@ -79,6 +79,7 @@ psql -d dbname -f <replaceable>SHAREDIR</>/contrib/<replaceable>module</>.sql </para> &adminpack; + &auto-explain; &btree-gist; &chkpass; &citext; diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml index b0538b6..68db2b9 100644 --- a/doc/src/sgml/filelist.sgml +++ b/doc/src/sgml/filelist.sgml @@ -92,6 +92,7 @@ <!-- contrib information --> <!entity contrib SYSTEM "contrib.sgml"> <!entity adminpack SYSTEM "adminpack.sgml"> +<!entity auto-explain SYSTEM "auto-explain.sgml"> <!entity btree-gist SYSTEM "btree-gist.sgml"> <!entity chkpass SYSTEM "chkpass.sgml"> <!entity citext SYSTEM "citext.sgml"> diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c index c0c53cf..4d46a34 100644 --- a/src/backend/commands/explain.c +++ b/src/backend/commands/explain.c @@ -224,7 +224,6 @@ ExplainOnePlan(PlannedStmt *plannedstmt, ParamListInfo params, QueryDesc *queryDesc; instr_time starttime; double totaltime = 0; - ExplainState *es; StringInfoData buf; int eflags; @@ -265,17 +264,8 @@ ExplainOnePlan(PlannedStmt *plannedstmt, ParamListInfo params, totaltime += elapsed_time(&starttime); } - es = (ExplainState *) palloc0(sizeof(ExplainState)); - - es->printTList = stmt->verbose; - es->printAnalyze = stmt->analyze; - es->pstmt = queryDesc->plannedstmt; - es->rtable = queryDesc->plannedstmt->rtable; - initStringInfo(&buf); - explain_outNode(&buf, - queryDesc->plannedstmt->planTree, queryDesc->planstate, - NULL, 0, es); + ExplainOneResult(&buf, queryDesc, stmt->analyze, stmt->verbose); /* * If we ran the command, run any AFTER triggers it queued. (Note this @@ -290,7 +280,7 @@ ExplainOnePlan(PlannedStmt *plannedstmt, ParamListInfo params, } /* Print info about runtime of triggers */ - if (es->printAnalyze) + if (stmt->analyze) { ResultRelInfo *rInfo; bool show_relname; @@ -335,7 +325,26 @@ ExplainOnePlan(PlannedStmt *plannedstmt, ParamListInfo params, do_text_output_multiline(tstate, buf.data); pfree(buf.data); - pfree(es); +} + +/* + * ExplainOneResult - + * converts a Plan node into ascii string and appends it to 'str' + */ +void +ExplainOneResult(StringInfo str, QueryDesc *queryDesc, + bool analyze, bool verbose) +{ + ExplainState es = { 0 }; + + es.printTList = verbose; + es.printAnalyze = analyze; + es.pstmt = queryDesc->plannedstmt; + es.rtable = queryDesc->plannedstmt->rtable; + + explain_outNode(str, + queryDesc->plannedstmt->planTree, queryDesc->planstate, + NULL, 0, &es); } /* diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c index a933d3c..ff2df9b 100644 --- a/src/backend/tcop/pquery.c +++ b/src/backend/tcop/pquery.c @@ -32,7 +32,7 @@ * if there are several). */ Portal ActivePortal = NULL; - +bool force_instrument = false; static void ProcessQuery(PlannedStmt *plan, ParamListInfo params, @@ -76,7 +76,7 @@ CreateQueryDesc(PlannedStmt *plannedstmt, qd->crosscheck_snapshot = RegisterSnapshot(crosscheck_snapshot); qd->dest = dest; /* output dest */ qd->params = params; /* parameter values passed into query */ - qd->doInstrument = doInstrument; /* instrumentation wanted? */ + qd->doInstrument = force_instrument || doInstrument; /* instrumentation wanted? */ /* null these fields until set by ExecutorStart */ qd->tupDesc = NULL; diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index fd7543b..f8a7e64 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -169,6 +169,7 @@ static const char *assign_pgstat_temp_directory(const char *newval, bool doit, G static char *config_enum_get_options(struct config_enum *record, const char *prefix, const char *suffix); +static void initialize_option(struct config_generic *gconf); /* @@ -3194,112 +3195,7 @@ InitializeGUCOptions(void) for (i = 0; i < num_guc_variables; i++) { struct config_generic *gconf = guc_variables[i]; - - gconf->status = 0; - gconf->reset_source = PGC_S_DEFAULT; - gconf->source = PGC_S_DEFAULT; - gconf->stack = NULL; - gconf->sourcefile = NULL; - gconf->sourceline = 0; - - switch (gconf->vartype) - { - case PGC_BOOL: - { - struct config_bool *conf = (struct config_bool *) gconf; - - if (conf->assign_hook) - if (!(*conf->assign_hook) (conf->boot_val, true, - PGC_S_DEFAULT)) - elog(FATAL, "failed to initialize %s to %d", - conf->gen.name, (int) conf->boot_val); - *conf->variable = conf->reset_val = conf->boot_val; - break; - } - case PGC_INT: - { - struct config_int *conf = (struct config_int *) gconf; - - Assert(conf->boot_val >= conf->min); - Assert(conf->boot_val <= conf->max); - if (conf->assign_hook) - if (!(*conf->assign_hook) (conf->boot_val, true, - PGC_S_DEFAULT)) - elog(FATAL, "failed to initialize %s to %d", - conf->gen.name, conf->boot_val); - *conf->variable = conf->reset_val = conf->boot_val; - break; - } - case PGC_REAL: - { - struct config_real *conf = (struct config_real *) gconf; - - Assert(conf->boot_val >= conf->min); - Assert(conf->boot_val <= conf->max); - if (conf->assign_hook) - if (!(*conf->assign_hook) (conf->boot_val, true, - PGC_S_DEFAULT)) - elog(FATAL, "failed to initialize %s to %g", - conf->gen.name, conf->boot_val); - *conf->variable = conf->reset_val = conf->boot_val; - break; - } - case PGC_STRING: - { - struct config_string *conf = (struct config_string *) gconf; - char *str; - - *conf->variable = NULL; - conf->reset_val = NULL; - - if (conf->boot_val == NULL) - { - /* leave the value NULL, do not call assign hook */ - break; - } - - str = guc_strdup(FATAL, conf->boot_val); - conf->reset_val = str; - - if (conf->assign_hook) - { - const char *newstr; - - newstr = (*conf->assign_hook) (str, true, - PGC_S_DEFAULT); - if (newstr == NULL) - { - elog(FATAL, "failed to initialize %s to \"%s\"", - conf->gen.name, str); - } - else if (newstr != str) - { - free(str); - - /* - * See notes in set_config_option about casting - */ - str = (char *) newstr; - conf->reset_val = str; - } - } - *conf->variable = str; - break; - } - case PGC_ENUM: - { - struct config_enum *conf = (struct config_enum *) gconf; - - if (conf->assign_hook) - if (!(*conf->assign_hook) (conf->boot_val, true, - PGC_S_DEFAULT)) - elog(FATAL, "failed to initialize %s to %s", - conf->gen.name, - config_enum_lookup_by_value(conf, conf->boot_val)); - *conf->variable = conf->reset_val = conf->boot_val; - break; - } - } + initialize_option(gconf); } guc_dirty = false; @@ -3355,6 +3251,115 @@ InitializeGUCOptions(void) } } +static void +initialize_option(struct config_generic *gconf) +{ + gconf->status = 0; + gconf->reset_source = PGC_S_DEFAULT; + gconf->source = PGC_S_DEFAULT; + gconf->stack = NULL; + gconf->sourcefile = NULL; + gconf->sourceline = 0; + + switch (gconf->vartype) + { + case PGC_BOOL: + { + struct config_bool *conf = (struct config_bool *) gconf; + + if (conf->assign_hook) + if (!(*conf->assign_hook) (conf->boot_val, true, + PGC_S_DEFAULT)) + elog(FATAL, "failed to initialize %s to %d", + conf->gen.name, (int) conf->boot_val); + *conf->variable = conf->reset_val = conf->boot_val; + break; + } + case PGC_INT: + { + struct config_int *conf = (struct config_int *) gconf; + + Assert(conf->boot_val >= conf->min); + Assert(conf->boot_val <= conf->max); + if (conf->assign_hook) + if (!(*conf->assign_hook) (conf->boot_val, true, + PGC_S_DEFAULT)) + elog(FATAL, "failed to initialize %s to %d", + conf->gen.name, conf->boot_val); + *conf->variable = conf->reset_val = conf->boot_val; + break; + } + case PGC_REAL: + { + struct config_real *conf = (struct config_real *) gconf; + + Assert(conf->boot_val >= conf->min); + Assert(conf->boot_val <= conf->max); + if (conf->assign_hook) + if (!(*conf->assign_hook) (conf->boot_val, true, + PGC_S_DEFAULT)) + elog(FATAL, "failed to initialize %s to %g", + conf->gen.name, conf->boot_val); + *conf->variable = conf->reset_val = conf->boot_val; + break; + } + case PGC_STRING: + { + struct config_string *conf = (struct config_string *) gconf; + char *str; + + *conf->variable = NULL; + conf->reset_val = NULL; + + if (conf->boot_val == NULL) + { + /* leave the value NULL, do not call assign hook */ + break; + } + + str = guc_strdup(FATAL, conf->boot_val); + conf->reset_val = str; + + if (conf->assign_hook) + { + const char *newstr; + + newstr = (*conf->assign_hook) (str, true, + PGC_S_DEFAULT); + if (newstr == NULL) + { + elog(FATAL, "failed to initialize %s to \"%s\"", + conf->gen.name, str); + } + else if (newstr != str) + { + free(str); + + /* + * See notes in set_config_option about casting + */ + str = (char *) newstr; + conf->reset_val = str; + } + } + *conf->variable = str; + break; + } + case PGC_ENUM: + { + struct config_enum *conf = (struct config_enum *) gconf; + + if (conf->assign_hook) + if (!(*conf->assign_hook) (conf->boot_val, true, + PGC_S_DEFAULT)) + elog(FATAL, "failed to initialize %s to %s", + conf->gen.name, + config_enum_lookup_by_value(conf, conf->boot_val)); + *conf->variable = conf->reset_val = conf->boot_val; + break; + } + } +} /* * Select the configuration files and data directory to be used, and @@ -5656,6 +5661,7 @@ define_custom_variable(struct config_generic * variable) if (res == NULL) { /* No placeholder to replace, so just add it */ + initialize_option(variable); add_guc_variable(variable, ERROR); return; } @@ -5690,6 +5696,8 @@ define_custom_variable(struct config_generic * variable) set_config_option(name, value, pHolder->gen.context, pHolder->gen.source, GUC_ACTION_SET, true); + else + initialize_option(variable); /* * Free up as much as we conveniently can of the placeholder structure @@ -5823,6 +5831,29 @@ DefineCustomEnumVariable(const char *name, define_custom_variable(&var->gen); } +static const int config_varsize[] = +{ + sizeof(struct config_bool), + sizeof(struct config_int), + sizeof(struct config_real), + sizeof(struct config_string), + sizeof(struct config_enum), +}; + +void +DefineCustomVariable(enum config_type type, const void *variable) +{ + int size = config_varsize[type]; + const struct config_generic *var = variable; + struct config_generic *gen; + + gen = (struct config_generic *) guc_malloc(ERROR, size); + memcpy(gen, var, size); + gen->name = guc_strdup(ERROR, var->name); + gen->vartype = type; + define_custom_variable(gen); +} + void EmitWarningsOnPlaceholders(const char *className) { diff --git a/src/include/commands/explain.h b/src/include/commands/explain.h index 5b808d5..cf07915 100644 --- a/src/include/commands/explain.h +++ b/src/include/commands/explain.h @@ -41,4 +41,7 @@ extern void ExplainOneUtility(Node *utilityStmt, ExplainStmt *stmt, extern void ExplainOnePlan(PlannedStmt *plannedstmt, ParamListInfo params, ExplainStmt *stmt, TupOutputState *tstate); +extern void ExplainOneResult(StringInfo str, QueryDesc *queryDesc, + bool analyze, bool verbose); + #endif /* EXPLAIN_H */ diff --git a/src/include/executor/execdesc.h b/src/include/executor/execdesc.h index 2e2c159..854721c 100644 --- a/src/include/executor/execdesc.h +++ b/src/include/executor/execdesc.h @@ -20,6 +20,8 @@ #include "tcop/dest.h" +extern PGDLLIMPORT bool force_instrument; + /* ---------------- * query descriptor: * diff --git a/src/include/utils/guc_tables.h b/src/include/utils/guc_tables.h index 8791660..3982a27 100644 --- a/src/include/utils/guc_tables.h +++ b/src/include/utils/guc_tables.h @@ -242,5 +242,6 @@ extern const char *config_enum_lookup_by_value(struct config_enum *record, int v extern bool config_enum_lookup_by_name(struct config_enum *record, const char *value, int *retval); +extern void DefineCustomVariable(enum config_type type, const void *variable); #endif /* GUC_TABLES_H */
-- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers