Replace the sorted array of GUC variables with a hash table.

This gets rid of bsearch() in favor of hashed lookup.  The main
advantage is that it becomes far cheaper to add new GUCs, since
we needn't re-sort the pointer array.  Adding N new GUCs had
been O(N^2 log N), but now it's closer to O(N).  We need to
sort only in SHOW ALL and equivalent functions, which are
hopefully not performance-critical to anybody.

Also, merge GetNumConfigOptions() into get_guc_variables(),
because in a world where the set of GUCs isn't fairly static
you really want to consider those two results as tied together
not independent.

Discussion: https://postgr.es/m/2982579.1662416866@sss.pgh.pa.us
This commit is contained in:
Tom Lane 2022-10-14 12:26:39 -04:00
parent 407b50f2d4
commit 3057465acf
5 changed files with 272 additions and 160 deletions

View File

@ -36,6 +36,7 @@
#include "guc_internal.h" #include "guc_internal.h"
#include "libpq/pqformat.h" #include "libpq/pqformat.h"
#include "parser/scansup.h" #include "parser/scansup.h"
#include "port/pg_bitutils.h"
#include "storage/fd.h" #include "storage/fd.h"
#include "storage/lwlock.h" #include "storage/lwlock.h"
#include "storage/shmem.h" #include "storage/shmem.h"
@ -192,16 +193,17 @@ static const char *const map_old_guc_names[] = {
static MemoryContext GUCMemoryContext; static MemoryContext GUCMemoryContext;
/* /*
* Actual lookup of variables is done through this single, sorted array. * We use a dynahash table to look up GUCs by name, or to iterate through
* all the GUCs. The gucname field is redundant with gucvar->name, but
* dynahash makes it too painful to not store the hash key separately.
*/ */
static struct config_generic **guc_variables; typedef struct
{
/* Current number of variables contained in the vector */ const char *gucname; /* hash key */
static int num_guc_variables; struct config_generic *gucvar; /* -> GUC's defining structure */
} GUCHashEntry;
/* Vector capacity */
static int size_guc_variables;
static HTAB *guc_hashtab; /* entries are GUCHashEntrys */
static bool guc_dirty; /* true if need to do commit/abort work */ static bool guc_dirty; /* true if need to do commit/abort work */
@ -213,6 +215,8 @@ static int GUCNestLevel = 0; /* 1 when in main transaction */
static int guc_var_compare(const void *a, const void *b); static int guc_var_compare(const void *a, const void *b);
static uint32 guc_name_hash(const void *key, Size keysize);
static int guc_name_match(const void *key1, const void *key2, Size keysize);
static void InitializeGUCOptionsFromEnvironment(void); static void InitializeGUCOptionsFromEnvironment(void);
static void InitializeOneGUCOption(struct config_generic *gconf); static void InitializeOneGUCOption(struct config_generic *gconf);
static void pg_timezone_abbrev_initialize(void); static void pg_timezone_abbrev_initialize(void);
@ -262,7 +266,8 @@ ProcessConfigFileInternal(GucContext context, bool applySettings, int elevel)
ConfigVariable *item, ConfigVariable *item,
*head, *head,
*tail; *tail;
int i; HASH_SEQ_STATUS status;
GUCHashEntry *hentry;
/* Parse the main config file into a list of option names and values */ /* Parse the main config file into a list of option names and values */
ConfFileWithError = ConfigFileName; ConfFileWithError = ConfigFileName;
@ -337,9 +342,10 @@ ProcessConfigFileInternal(GucContext context, bool applySettings, int elevel)
* need this so that we can tell below which ones have been removed from * need this so that we can tell below which ones have been removed from
* the file since we last processed it. * the file since we last processed it.
*/ */
for (i = 0; i < num_guc_variables; i++) hash_seq_init(&status, guc_hashtab);
while ((hentry = (GUCHashEntry *) hash_seq_search(&status)) != NULL)
{ {
struct config_generic *gconf = guc_variables[i]; struct config_generic *gconf = hentry->gucvar;
gconf->status &= ~GUC_IS_IN_FILE; gconf->status &= ~GUC_IS_IN_FILE;
} }
@ -423,9 +429,10 @@ ProcessConfigFileInternal(GucContext context, bool applySettings, int elevel)
* boot-time defaults. If such a variable can't be changed after startup, * boot-time defaults. If such a variable can't be changed after startup,
* report that and continue. * report that and continue.
*/ */
for (i = 0; i < num_guc_variables; i++) hash_seq_init(&status, guc_hashtab);
while ((hentry = (GUCHashEntry *) hash_seq_search(&status)) != NULL)
{ {
struct config_generic *gconf = guc_variables[i]; struct config_generic *gconf = hentry->gucvar;
GucStack *stack; GucStack *stack;
if (gconf->reset_source != PGC_S_FILE || if (gconf->reset_source != PGC_S_FILE ||
@ -836,17 +843,38 @@ discard_stack_value(struct config_generic *gconf, config_var_value *val)
/* /*
* Fetch the sorted array pointer (exported for help_config.c's use ONLY) * Fetch a palloc'd, sorted array of GUC struct pointers
*
* The array length is returned into *num_vars.
*/ */
struct config_generic ** struct config_generic **
get_guc_variables(void) get_guc_variables(int *num_vars)
{ {
return guc_variables; struct config_generic **result;
HASH_SEQ_STATUS status;
GUCHashEntry *hentry;
int i;
*num_vars = hash_get_num_entries(guc_hashtab);
result = palloc(sizeof(struct config_generic *) * *num_vars);
/* Extract pointers from the hash table */
i = 0;
hash_seq_init(&status, guc_hashtab);
while ((hentry = (GUCHashEntry *) hash_seq_search(&status)) != NULL)
result[i++] = hentry->gucvar;
Assert(i == *num_vars);
/* Sort by name */
qsort(result, *num_vars,
sizeof(struct config_generic *), guc_var_compare);
return result;
} }
/* /*
* Build the sorted array. This is split out so that help_config.c can * Build the GUC hash table. This is split out so that help_config.c can
* extract all the variables without running all of InitializeGUCOptions. * extract all the variables without running all of InitializeGUCOptions.
* It's not meant for use anyplace else. * It's not meant for use anyplace else.
*/ */
@ -855,7 +883,9 @@ build_guc_variables(void)
{ {
int size_vars; int size_vars;
int num_vars = 0; int num_vars = 0;
struct config_generic **guc_vars; HASHCTL hash_ctl;
GUCHashEntry *hentry;
bool found;
int i; int i;
/* /*
@ -911,74 +941,106 @@ build_guc_variables(void)
} }
/* /*
* Create table with 20% slack * Create hash table with 20% slack
*/ */
size_vars = num_vars + num_vars / 4; size_vars = num_vars + num_vars / 4;
guc_vars = (struct config_generic **) hash_ctl.keysize = sizeof(char *);
guc_malloc(FATAL, size_vars * sizeof(struct config_generic *)); hash_ctl.entrysize = sizeof(GUCHashEntry);
hash_ctl.hash = guc_name_hash;
num_vars = 0; hash_ctl.match = guc_name_match;
hash_ctl.hcxt = GUCMemoryContext;
guc_hashtab = hash_create("GUC hash table",
size_vars,
&hash_ctl,
HASH_ELEM | HASH_FUNCTION | HASH_COMPARE | HASH_CONTEXT);
for (i = 0; ConfigureNamesBool[i].gen.name; i++) for (i = 0; ConfigureNamesBool[i].gen.name; i++)
guc_vars[num_vars++] = &ConfigureNamesBool[i].gen; {
struct config_generic *gucvar = &ConfigureNamesBool[i].gen;
hentry = (GUCHashEntry *) hash_search(guc_hashtab,
&gucvar->name,
HASH_ENTER,
&found);
Assert(!found);
hentry->gucvar = gucvar;
}
for (i = 0; ConfigureNamesInt[i].gen.name; i++) for (i = 0; ConfigureNamesInt[i].gen.name; i++)
guc_vars[num_vars++] = &ConfigureNamesInt[i].gen; {
struct config_generic *gucvar = &ConfigureNamesInt[i].gen;
hentry = (GUCHashEntry *) hash_search(guc_hashtab,
&gucvar->name,
HASH_ENTER,
&found);
Assert(!found);
hentry->gucvar = gucvar;
}
for (i = 0; ConfigureNamesReal[i].gen.name; i++) for (i = 0; ConfigureNamesReal[i].gen.name; i++)
guc_vars[num_vars++] = &ConfigureNamesReal[i].gen; {
struct config_generic *gucvar = &ConfigureNamesReal[i].gen;
hentry = (GUCHashEntry *) hash_search(guc_hashtab,
&gucvar->name,
HASH_ENTER,
&found);
Assert(!found);
hentry->gucvar = gucvar;
}
for (i = 0; ConfigureNamesString[i].gen.name; i++) for (i = 0; ConfigureNamesString[i].gen.name; i++)
guc_vars[num_vars++] = &ConfigureNamesString[i].gen; {
struct config_generic *gucvar = &ConfigureNamesString[i].gen;
hentry = (GUCHashEntry *) hash_search(guc_hashtab,
&gucvar->name,
HASH_ENTER,
&found);
Assert(!found);
hentry->gucvar = gucvar;
}
for (i = 0; ConfigureNamesEnum[i].gen.name; i++) for (i = 0; ConfigureNamesEnum[i].gen.name; i++)
guc_vars[num_vars++] = &ConfigureNamesEnum[i].gen; {
struct config_generic *gucvar = &ConfigureNamesEnum[i].gen;
guc_free(guc_variables); hentry = (GUCHashEntry *) hash_search(guc_hashtab,
guc_variables = guc_vars; &gucvar->name,
num_guc_variables = num_vars; HASH_ENTER,
size_guc_variables = size_vars; &found);
qsort((void *) guc_variables, num_guc_variables, Assert(!found);
sizeof(struct config_generic *), guc_var_compare); hentry->gucvar = gucvar;
}
Assert(num_vars == hash_get_num_entries(guc_hashtab));
} }
/* /*
* Add a new GUC variable to the list of known variables. The * Add a new GUC variable to the hash of known variables. The
* list is expanded if needed. * hash is expanded if needed.
*/ */
static bool static bool
add_guc_variable(struct config_generic *var, int elevel) add_guc_variable(struct config_generic *var, int elevel)
{ {
if (num_guc_variables + 1 >= size_guc_variables) GUCHashEntry *hentry;
bool found;
hentry = (GUCHashEntry *) hash_search(guc_hashtab,
&var->name,
HASH_ENTER_NULL,
&found);
if (unlikely(hentry == NULL))
{ {
/* ereport(elevel,
* Increase the vector by 25% (errcode(ERRCODE_OUT_OF_MEMORY),
*/ errmsg("out of memory")));
int size_vars = size_guc_variables + size_guc_variables / 4; return false; /* out of memory */
struct config_generic **guc_vars;
if (size_vars == 0)
{
size_vars = 100;
guc_vars = (struct config_generic **)
guc_malloc(elevel, size_vars * sizeof(struct config_generic *));
}
else
{
guc_vars = (struct config_generic **)
guc_realloc(elevel, guc_variables, size_vars * sizeof(struct config_generic *));
}
if (guc_vars == NULL)
return false; /* out of memory */
guc_variables = guc_vars;
size_guc_variables = size_vars;
} }
guc_variables[num_guc_variables++] = var; Assert(!found);
qsort((void *) guc_variables, num_guc_variables, hentry->gucvar = var;
sizeof(struct config_generic *), guc_var_compare);
return true; return true;
} }
@ -1087,23 +1149,18 @@ struct config_generic *
find_option(const char *name, bool create_placeholders, bool skip_errors, find_option(const char *name, bool create_placeholders, bool skip_errors,
int elevel) int elevel)
{ {
const char **key = &name; GUCHashEntry *hentry;
struct config_generic **res;
int i; int i;
Assert(name); Assert(name);
/* /* Look it up using the hash table. */
* By equating const char ** with struct config_generic *, we are assuming hentry = (GUCHashEntry *) hash_search(guc_hashtab,
* the name field is first in config_generic. &name,
*/ HASH_FIND,
res = (struct config_generic **) bsearch((void *) &key, NULL);
(void *) guc_variables, if (hentry)
num_guc_variables, return hentry->gucvar;
sizeof(struct config_generic *),
guc_var_compare);
if (res)
return *res;
/* /*
* See if the name is an obsolete name for a variable. We assume that the * See if the name is an obsolete name for a variable. We assume that the
@ -1176,7 +1233,7 @@ find_option(const char *name, bool create_placeholders, bool skip_errors,
/* /*
* comparator for qsorting and bsearching guc_variables array * comparator for qsorting an array of GUC pointers
*/ */
static int static int
guc_var_compare(const void *a, const void *b) guc_var_compare(const void *a, const void *b)
@ -1195,7 +1252,7 @@ guc_name_compare(const char *namea, const char *nameb)
{ {
/* /*
* The temptation to use strcasecmp() here must be resisted, because the * The temptation to use strcasecmp() here must be resisted, because the
* array ordering has to remain stable across setlocale() calls. So, build * hash mapping has to remain stable across setlocale() calls. So, build
* our own with a simple ASCII-only downcasing. * our own with a simple ASCII-only downcasing.
*/ */
while (*namea && *nameb) while (*namea && *nameb)
@ -1217,6 +1274,42 @@ guc_name_compare(const char *namea, const char *nameb)
return 0; return 0;
} }
/*
* Hash function that's compatible with guc_name_compare
*/
static uint32
guc_name_hash(const void *key, Size keysize)
{
uint32 result = 0;
const char *name = *(const char *const *) key;
while (*name)
{
char ch = *name++;
/* Case-fold in the same way as guc_name_compare */
if (ch >= 'A' && ch <= 'Z')
ch += 'a' - 'A';
/* Merge into hash ... not very bright, but it needn't be */
result = pg_rotate_left32(result, 5);
result ^= (uint32) ch;
}
return result;
}
/*
* Dynahash match function to use in guc_hashtab
*/
static int
guc_name_match(const void *key1, const void *key2, Size keysize)
{
const char *name1 = *(const char *const *) key1;
const char *name2 = *(const char *const *) key2;
return guc_name_compare(name1, name2);
}
/* /*
* Convert a GUC name to the form that should be used in pg_parameter_acl. * Convert a GUC name to the form that should be used in pg_parameter_acl.
@ -1286,7 +1379,8 @@ check_GUC_name_for_parameter_acl(const char *name)
void void
InitializeGUCOptions(void) InitializeGUCOptions(void)
{ {
int i; HASH_SEQ_STATUS status;
GUCHashEntry *hentry;
/* /*
* Before log_line_prefix could possibly receive a nonempty setting, make * Before log_line_prefix could possibly receive a nonempty setting, make
@ -1295,7 +1389,7 @@ InitializeGUCOptions(void)
pg_timezone_initialize(); pg_timezone_initialize();
/* /*
* Create GUCMemoryContext and build sorted array of all GUC variables. * Create GUCMemoryContext and build hash table of all GUC variables.
*/ */
build_guc_variables(); build_guc_variables();
@ -1303,9 +1397,10 @@ InitializeGUCOptions(void)
* Load all variables with their compiled-in defaults, and initialize * Load all variables with their compiled-in defaults, and initialize
* status fields as needed. * status fields as needed.
*/ */
for (i = 0; i < num_guc_variables; i++) hash_seq_init(&status, guc_hashtab);
while ((hentry = (GUCHashEntry *) hash_seq_search(&status)) != NULL)
{ {
InitializeOneGUCOption(guc_variables[i]); InitializeOneGUCOption(hentry->gucvar);
} }
guc_dirty = false; guc_dirty = false;
@ -1740,11 +1835,13 @@ pg_timezone_abbrev_initialize(void)
void void
ResetAllOptions(void) ResetAllOptions(void)
{ {
int i; HASH_SEQ_STATUS status;
GUCHashEntry *hentry;
for (i = 0; i < num_guc_variables; i++) hash_seq_init(&status, guc_hashtab);
while ((hentry = (GUCHashEntry *) hash_seq_search(&status)) != NULL)
{ {
struct config_generic *gconf = guc_variables[i]; struct config_generic *gconf = hentry->gucvar;
/* Don't reset non-SET-able values */ /* Don't reset non-SET-able values */
if (gconf->context != PGC_SUSET && if (gconf->context != PGC_SUSET &&
@ -1962,7 +2059,8 @@ void
AtEOXact_GUC(bool isCommit, int nestLevel) AtEOXact_GUC(bool isCommit, int nestLevel)
{ {
bool still_dirty; bool still_dirty;
int i; HASH_SEQ_STATUS status;
GUCHashEntry *hentry;
/* /*
* Note: it's possible to get here with GUCNestLevel == nestLevel-1 during * Note: it's possible to get here with GUCNestLevel == nestLevel-1 during
@ -1981,9 +2079,10 @@ AtEOXact_GUC(bool isCommit, int nestLevel)
} }
still_dirty = false; still_dirty = false;
for (i = 0; i < num_guc_variables; i++) hash_seq_init(&status, guc_hashtab);
while ((hentry = (GUCHashEntry *) hash_seq_search(&status)) != NULL)
{ {
struct config_generic *gconf = guc_variables[i]; struct config_generic *gconf = hentry->gucvar;
GucStack *stack; GucStack *stack;
/* /*
@ -2252,7 +2351,8 @@ AtEOXact_GUC(bool isCommit, int nestLevel)
void void
BeginReportingGUCOptions(void) BeginReportingGUCOptions(void)
{ {
int i; HASH_SEQ_STATUS status;
GUCHashEntry *hentry;
/* /*
* Don't do anything unless talking to an interactive frontend. * Don't do anything unless talking to an interactive frontend.
@ -2275,9 +2375,10 @@ BeginReportingGUCOptions(void)
PGC_INTERNAL, PGC_S_OVERRIDE); PGC_INTERNAL, PGC_S_OVERRIDE);
/* Transmit initial values of interesting variables */ /* Transmit initial values of interesting variables */
for (i = 0; i < num_guc_variables; i++) hash_seq_init(&status, guc_hashtab);
while ((hentry = (GUCHashEntry *) hash_seq_search(&status)) != NULL)
{ {
struct config_generic *conf = guc_variables[i]; struct config_generic *conf = hentry->gucvar;
if (conf->flags & GUC_REPORT) if (conf->flags & GUC_REPORT)
ReportGUCOption(conf); ReportGUCOption(conf);
@ -2302,6 +2403,9 @@ BeginReportingGUCOptions(void)
void void
ReportChangedGUCOptions(void) ReportChangedGUCOptions(void)
{ {
HASH_SEQ_STATUS status;
GUCHashEntry *hentry;
/* Quick exit if not (yet) enabled */ /* Quick exit if not (yet) enabled */
if (!reporting_enabled) if (!reporting_enabled)
return; return;
@ -2321,9 +2425,10 @@ ReportChangedGUCOptions(void)
return; return;
/* Transmit new values of interesting variables */ /* Transmit new values of interesting variables */
for (int i = 0; i < num_guc_variables; i++) hash_seq_init(&status, guc_hashtab);
while ((hentry = (GUCHashEntry *) hash_seq_search(&status)) != NULL)
{ {
struct config_generic *conf = guc_variables[i]; struct config_generic *conf = hentry->gucvar;
if ((conf->flags & GUC_REPORT) && (conf->status & GUC_NEEDS_REPORT)) if ((conf->flags & GUC_REPORT) && (conf->status & GUC_NEEDS_REPORT))
ReportGUCOption(conf); ReportGUCOption(conf);
@ -4506,25 +4611,23 @@ init_custom_variable(const char *name,
/* /*
* Common code for DefineCustomXXXVariable subroutines: insert the new * Common code for DefineCustomXXXVariable subroutines: insert the new
* variable into the GUC variable array, replacing any placeholder. * variable into the GUC variable hash, replacing any placeholder.
*/ */
static void static void
define_custom_variable(struct config_generic *variable) define_custom_variable(struct config_generic *variable)
{ {
const char *name = variable->name; const char *name = variable->name;
const char **nameAddr = &name; GUCHashEntry *hentry;
struct config_string *pHolder; struct config_string *pHolder;
struct config_generic **res;
/* /*
* See if there's a placeholder by the same name. * See if there's a placeholder by the same name.
*/ */
res = (struct config_generic **) bsearch((void *) &nameAddr, hentry = (GUCHashEntry *) hash_search(guc_hashtab,
(void *) guc_variables, &name,
num_guc_variables, HASH_FIND,
sizeof(struct config_generic *), NULL);
guc_var_compare); if (hentry == NULL)
if (res == NULL)
{ {
/* /*
* No placeholder to replace, so we can just add it ... but first, * No placeholder to replace, so we can just add it ... but first,
@ -4538,13 +4641,13 @@ define_custom_variable(struct config_generic *variable)
/* /*
* This better be a placeholder * This better be a placeholder
*/ */
if (((*res)->flags & GUC_CUSTOM_PLACEHOLDER) == 0) if ((hentry->gucvar->flags & GUC_CUSTOM_PLACEHOLDER) == 0)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_INTERNAL_ERROR), (errcode(ERRCODE_INTERNAL_ERROR),
errmsg("attempt to redefine parameter \"%s\"", name))); errmsg("attempt to redefine parameter \"%s\"", name)));
Assert((*res)->vartype == PGC_STRING); Assert(hentry->gucvar->vartype == PGC_STRING);
pHolder = (struct config_string *) (*res); pHolder = (struct config_string *) hentry->gucvar;
/* /*
* First, set the variable to its default value. We must do this even * First, set the variable to its default value. We must do this even
@ -4554,10 +4657,11 @@ define_custom_variable(struct config_generic *variable)
InitializeOneGUCOption(variable); InitializeOneGUCOption(variable);
/* /*
* Replace the placeholder. We aren't changing the name, so no re-sorting * Replace the placeholder in the hash table. We aren't changing the name
* is necessary * (at least up to case-folding), so the hash value is unchanged.
*/ */
*res = variable; hentry->gucname = name;
hentry->gucvar = variable;
/* /*
* Assign the string value(s) stored in the placeholder to the real * Assign the string value(s) stored in the placeholder to the real
@ -4849,7 +4953,8 @@ void
MarkGUCPrefixReserved(const char *className) MarkGUCPrefixReserved(const char *className)
{ {
int classLen = strlen(className); int classLen = strlen(className);
int i; HASH_SEQ_STATUS status;
GUCHashEntry *hentry;
MemoryContext oldcontext; MemoryContext oldcontext;
/* /*
@ -4858,9 +4963,10 @@ MarkGUCPrefixReserved(const char *className)
* don't bother trying to free associated memory, since this shouldn't * don't bother trying to free associated memory, since this shouldn't
* happen often.) * happen often.)
*/ */
for (i = 0; i < num_guc_variables; i++) hash_seq_init(&status, guc_hashtab);
while ((hentry = (GUCHashEntry *) hash_seq_search(&status)) != NULL)
{ {
struct config_generic *var = guc_variables[i]; struct config_generic *var = hentry->gucvar;
if ((var->flags & GUC_CUSTOM_PLACEHOLDER) != 0 && if ((var->flags & GUC_CUSTOM_PLACEHOLDER) != 0 &&
strncmp(className, var->name, classLen) == 0 && strncmp(className, var->name, classLen) == 0 &&
@ -4872,9 +4978,10 @@ MarkGUCPrefixReserved(const char *className)
var->name), var->name),
errdetail("\"%s\" is now a reserved prefix.", errdetail("\"%s\" is now a reserved prefix.",
className))); className)));
num_guc_variables--; hash_search(guc_hashtab,
memmove(&guc_variables[i], &guc_variables[i + 1], &var->name,
(num_guc_variables - i) * sizeof(struct config_generic *)); HASH_REMOVE,
NULL);
} }
} }
@ -4895,6 +5002,8 @@ struct config_generic **
get_explain_guc_options(int *num) get_explain_guc_options(int *num)
{ {
struct config_generic **result; struct config_generic **result;
HASH_SEQ_STATUS status;
GUCHashEntry *hentry;
*num = 0; *num = 0;
@ -4902,12 +5011,13 @@ get_explain_guc_options(int *num)
* While only a fraction of all the GUC variables are marked GUC_EXPLAIN, * While only a fraction of all the GUC variables are marked GUC_EXPLAIN,
* it doesn't seem worth dynamically resizing this array. * it doesn't seem worth dynamically resizing this array.
*/ */
result = palloc(sizeof(struct config_generic *) * num_guc_variables); result = palloc(sizeof(struct config_generic *) * hash_get_num_entries(guc_hashtab));
for (int i = 0; i < num_guc_variables; i++) hash_seq_init(&status, guc_hashtab);
while ((hentry = (GUCHashEntry *) hash_seq_search(&status)) != NULL)
{ {
struct config_generic *conf = hentry->gucvar;
bool modified; bool modified;
struct config_generic *conf = guc_variables[i];
/* return only parameters marked for inclusion in explain */ /* return only parameters marked for inclusion in explain */
if (!(conf->flags & GUC_EXPLAIN)) if (!(conf->flags & GUC_EXPLAIN))
@ -5010,15 +5120,6 @@ GetConfigOptionByName(const char *name, const char **varname, bool missing_ok)
return ShowGUCOption(record, true); return ShowGUCOption(record, true);
} }
/*
* Return the total number of GUC variables
*/
int
GetNumConfigOptions(void)
{
return num_guc_variables;
}
/* /*
* ShowGUCOption: get string value of variable * ShowGUCOption: get string value of variable
* *
@ -5220,7 +5321,8 @@ write_nondefault_variables(GucContext context)
{ {
int elevel; int elevel;
FILE *fp; FILE *fp;
int i; HASH_SEQ_STATUS status;
GUCHashEntry *hentry;
Assert(context == PGC_POSTMASTER || context == PGC_SIGHUP); Assert(context == PGC_POSTMASTER || context == PGC_SIGHUP);
@ -5239,9 +5341,10 @@ write_nondefault_variables(GucContext context)
return; return;
} }
for (i = 0; i < num_guc_variables; i++) hash_seq_init(&status, guc_hashtab);
while ((hentry = (GUCHashEntry *) hash_seq_search(&status)) != NULL)
{ {
write_one_nondefault_variable(fp, guc_variables[i]); write_one_nondefault_variable(fp, hentry->gucvar);
} }
if (FreeFile(fp)) if (FreeFile(fp))
@ -5515,15 +5618,17 @@ Size
EstimateGUCStateSpace(void) EstimateGUCStateSpace(void)
{ {
Size size; Size size;
int i; HASH_SEQ_STATUS status;
GUCHashEntry *hentry;
/* Add space reqd for saving the data size of the guc state */ /* Add space reqd for saving the data size of the guc state */
size = sizeof(Size); size = sizeof(Size);
/* Add up the space needed for each GUC variable */ /* Add up the space needed for each GUC variable */
for (i = 0; i < num_guc_variables; i++) hash_seq_init(&status, guc_hashtab);
while ((hentry = (GUCHashEntry *) hash_seq_search(&status)) != NULL)
size = add_size(size, size = add_size(size,
estimate_variable_size(guc_variables[i])); estimate_variable_size(hentry->gucvar));
return size; return size;
} }
@ -5662,15 +5767,17 @@ SerializeGUCState(Size maxsize, char *start_address)
char *curptr; char *curptr;
Size actual_size; Size actual_size;
Size bytes_left; Size bytes_left;
int i; HASH_SEQ_STATUS status;
GUCHashEntry *hentry;
/* Reserve space for saving the actual size of the guc state */ /* Reserve space for saving the actual size of the guc state */
Assert(maxsize > sizeof(actual_size)); Assert(maxsize > sizeof(actual_size));
curptr = start_address + sizeof(actual_size); curptr = start_address + sizeof(actual_size);
bytes_left = maxsize - sizeof(actual_size); bytes_left = maxsize - sizeof(actual_size);
for (i = 0; i < num_guc_variables; i++) hash_seq_init(&status, guc_hashtab);
serialize_variable(&curptr, &bytes_left, guc_variables[i]); while ((hentry = (GUCHashEntry *) hash_seq_search(&status)) != NULL)
serialize_variable(&curptr, &bytes_left, hentry->gucvar);
/* Store actual size without assuming alignment of start_address. */ /* Store actual size without assuming alignment of start_address. */
actual_size = maxsize - bytes_left - sizeof(actual_size); actual_size = maxsize - bytes_left - sizeof(actual_size);
@ -5755,7 +5862,8 @@ RestoreGUCState(void *gucstate)
char *srcptr = (char *) gucstate; char *srcptr = (char *) gucstate;
char *srcend; char *srcend;
Size len; Size len;
int i; HASH_SEQ_STATUS status;
GUCHashEntry *hentry;
ErrorContextCallback error_context_callback; ErrorContextCallback error_context_callback;
/* /*
@ -5780,9 +5888,10 @@ RestoreGUCState(void *gucstate)
* also ensures that set_config_option won't refuse to set them because of * also ensures that set_config_option won't refuse to set them because of
* source-priority comparisons. * source-priority comparisons.
*/ */
for (i = 0; i < num_guc_variables; i++) hash_seq_init(&status, guc_hashtab);
while ((hentry = (GUCHashEntry *) hash_seq_search(&status)) != NULL)
{ {
struct config_generic *gconf = guc_variables[i]; struct config_generic *gconf = hentry->gucvar;
/* Do nothing if non-shippable or if already at PGC_S_DEFAULT. */ /* Do nothing if non-shippable or if already at PGC_S_DEFAULT. */
if (can_skip_gucvar(gconf)) if (can_skip_gucvar(gconf))

View File

@ -455,13 +455,15 @@ ShowGUCConfigOption(const char *name, DestReceiver *dest)
static void static void
ShowAllGUCConfig(DestReceiver *dest) ShowAllGUCConfig(DestReceiver *dest)
{ {
int i; struct config_generic **guc_vars;
int num_vars;
TupOutputState *tstate; TupOutputState *tstate;
TupleDesc tupdesc; TupleDesc tupdesc;
Datum values[3]; Datum values[3];
bool isnull[3] = {false, false, false}; bool isnull[3] = {false, false, false};
struct config_generic **guc_variables = get_guc_variables();
int num_guc_variables = GetNumConfigOptions(); /* collect the variables, in sorted order */
guc_vars = get_guc_variables(&num_vars);
/* need a tuple descriptor representing three TEXT columns */ /* need a tuple descriptor representing three TEXT columns */
tupdesc = CreateTemplateTupleDesc(3); tupdesc = CreateTemplateTupleDesc(3);
@ -475,9 +477,9 @@ ShowAllGUCConfig(DestReceiver *dest)
/* prepare for projection of tuples */ /* prepare for projection of tuples */
tstate = begin_tup_output_tupdesc(dest, tupdesc, &TTSOpsVirtual); tstate = begin_tup_output_tupdesc(dest, tupdesc, &TTSOpsVirtual);
for (i = 0; i < num_guc_variables; i++) for (int i = 0; i < num_vars; i++)
{ {
struct config_generic *conf = guc_variables[i]; struct config_generic *conf = guc_vars[i];
char *setting; char *setting;
if ((conf->flags & GUC_NO_SHOW_ALL) || if ((conf->flags & GUC_NO_SHOW_ALL) ||
@ -570,20 +572,13 @@ pg_settings_get_flags(PG_FUNCTION_ARGS)
} }
/* /*
* Return GUC variable value by variable number; optionally return canonical * Extract fields to show in pg_settings for given variable.
* form of name. Return value is palloc'd.
*/ */
static void static void
GetConfigOptionByNum(int varnum, const char **values, bool *noshow) GetConfigOptionValues(struct config_generic *conf, const char **values,
bool *noshow)
{ {
char buffer[256]; char buffer[256];
struct config_generic *conf;
struct config_generic **guc_variables = get_guc_variables();
/* check requested variable number valid */
Assert((varnum >= 0) && (varnum < GetNumConfigOptions()));
conf = guc_variables[varnum];
if (noshow) if (noshow)
{ {
@ -849,6 +844,8 @@ Datum
show_all_settings(PG_FUNCTION_ARGS) show_all_settings(PG_FUNCTION_ARGS)
{ {
FuncCallContext *funcctx; FuncCallContext *funcctx;
struct config_generic **guc_vars;
int num_vars;
TupleDesc tupdesc; TupleDesc tupdesc;
int call_cntr; int call_cntr;
int max_calls; int max_calls;
@ -913,8 +910,14 @@ show_all_settings(PG_FUNCTION_ARGS)
attinmeta = TupleDescGetAttInMetadata(tupdesc); attinmeta = TupleDescGetAttInMetadata(tupdesc);
funcctx->attinmeta = attinmeta; funcctx->attinmeta = attinmeta;
/* collect the variables, in sorted order */
guc_vars = get_guc_variables(&num_vars);
/* use user_fctx to remember the array location */
funcctx->user_fctx = guc_vars;
/* total number of tuples to be returned */ /* total number of tuples to be returned */
funcctx->max_calls = GetNumConfigOptions(); funcctx->max_calls = num_vars;
MemoryContextSwitchTo(oldcontext); MemoryContextSwitchTo(oldcontext);
} }
@ -922,6 +925,7 @@ show_all_settings(PG_FUNCTION_ARGS)
/* stuff done on every call of the function */ /* stuff done on every call of the function */
funcctx = SRF_PERCALL_SETUP(); funcctx = SRF_PERCALL_SETUP();
guc_vars = (struct config_generic **) funcctx->user_fctx;
call_cntr = funcctx->call_cntr; call_cntr = funcctx->call_cntr;
max_calls = funcctx->max_calls; max_calls = funcctx->max_calls;
attinmeta = funcctx->attinmeta; attinmeta = funcctx->attinmeta;
@ -938,7 +942,8 @@ show_all_settings(PG_FUNCTION_ARGS)
*/ */
do do
{ {
GetConfigOptionByNum(call_cntr, (const char **) values, &noshow); GetConfigOptionValues(guc_vars[call_cntr], (const char **) values,
&noshow);
if (noshow) if (noshow)
{ {
/* bump the counter and get the next config setting */ /* bump the counter and get the next config setting */

View File

@ -49,11 +49,10 @@ GucInfoMain(void)
int numOpts, int numOpts,
i; i;
/* Initialize the guc_variables[] array */ /* Initialize the GUC hash table */
build_guc_variables(); build_guc_variables();
guc_vars = get_guc_variables(); guc_vars = get_guc_variables(&numOpts);
numOpts = GetNumConfigOptions();
for (i = 0; i < numOpts; i++) for (i = 0; i < numOpts; i++)
{ {

View File

@ -390,7 +390,6 @@ extern int set_config_option_ext(const char *name, const char *value,
extern void AlterSystemSetConfigFile(AlterSystemStmt *altersysstmt); extern void AlterSystemSetConfigFile(AlterSystemStmt *altersysstmt);
extern char *GetConfigOptionByName(const char *name, const char **varname, extern char *GetConfigOptionByName(const char *name, const char **varname,
bool missing_ok); bool missing_ok);
extern int GetNumConfigOptions(void);
extern void ProcessGUCArray(ArrayType *array, extern void ProcessGUCArray(ArrayType *array,
GucContext context, GucSource source, GucAction action); GucContext context, GucSource source, GucAction action);

View File

@ -281,7 +281,7 @@ extern struct config_generic **get_explain_guc_options(int *num);
extern char *ShowGUCOption(struct config_generic *record, bool use_units); extern char *ShowGUCOption(struct config_generic *record, bool use_units);
/* get the current set of variables */ /* get the current set of variables */
extern struct config_generic **get_guc_variables(void); extern struct config_generic **get_guc_variables(int *num_vars);
extern void build_guc_variables(void); extern void build_guc_variables(void);