aboutsummaryrefslogtreecommitdiff
path: root/src/backend/utils/misc/guc.c
diff options
context:
space:
mode:
authorBruce Momjian <bruce@momjian.us>2004-05-26 15:07:41 +0000
committerBruce Momjian <bruce@momjian.us>2004-05-26 15:07:41 +0000
commit3dc37cd8d6f6fd392c6965dfb0c4fd6b9232b8dd (patch)
tree06ca541c7b84faff5336b1ae0b6bfaa34d71e9a4 /src/backend/utils/misc/guc.c
parentcfbfdc557d166ec559668d18d9769544f3c4fbbc (diff)
downloadpostgresql-3dc37cd8d6f6fd392c6965dfb0c4fd6b9232b8dd.tar.gz
postgresql-3dc37cd8d6f6fd392c6965dfb0c4fd6b9232b8dd.zip
The patch adresses the TODO list item "Allow external interfaces to
extend the GUC variable set". Plugin modules like the pl<lang> modules needs a way to declare configuration parameters. The postmaster has no knowledge of such modules when it reads the postgresql.conf file. Rather than allowing totally unknown configuration parameters, the concept of a variable "class" is introduced. Variables that belongs to a declared classes will create a placeholder value of string type and will not generate an error. When a module is loaded, it will declare variables for such a class and make those variables "consume" any placeholders that has been defined. Finally, the module will generate warnings for unrecognized placeholders defined for its class. More detail: The design is outlined after the suggestions made by Tom Lane and Joe Conway in this thread: http://archives.postgresql.org/pgsql-hackers/2004-02/msg00229.php A new string variable 'custom_variable_classes' is introduced. This variable is a comma separated string of identifiers. Each identifier denots a 'class' that will allow its members to be added without error. This variable must be defined in postmaster.conf. The lexer (guc_file.l) is changed so that it can accept a qualified name in the form <ID>.<ID> as the name of a variable. I also changed so that the 'custom_variable_classes', if found, is added first of all variables in order to remove the order of declaration issue. The guc_variables table is made more dynamic. It is originally created with 20% slack and can grow dynamically. A capacity is introduced to avoid resizing every time a new variable is added. guc_variables and num_guc_variables becomes static (hidden). The GucInfoMain now uses the new function get_guc_variables() and GetNumConfigOptions instead or using the guc_variables directly. The find_option() function, when passed a missing name, will check if the name is qualified. If the name is qualified and if the qualifier denotes a class included in the 'custom_variable_classes', a placeholder variable will be created. Such a placeholder will not participate in a list operation but will otherwise function as a normal string variable. Define<type>GucVariable() functions will be added, one for each variable type. They are inteded to be used by add-on modules like the pl<lang> mappings. Example: extern void DefineCustomBoolVariable( const char* name, const char* short_desc, const char* long_desc, bool* valueAddr, GucContext context, GucBoolAssignHook assign_hook, GucShowHook show_hook); (I created typedefs for the assign-hook and show-hook functions). A call to these functions will define a new GUC-variable. If a placeholder exists it will be replaced but it's value will be used in place of the default value. The valueAddr is assumed ot point at a default value when the define function is called. The only constraint that is imposed on a Custom variable is that its name is qualified. Finally, a function: void EmittWarningsOnPlacholders(const char* className) was added. This function should be called when a module has completed its variable definitions. At that time, no placeholders should remain for the class that the module uses. If they do, elog(INFO, ...) messages will be issued to inform the user that unrecognized variables are present. Thomas Hallgren
Diffstat (limited to 'src/backend/utils/misc/guc.c')
-rw-r--r--src/backend/utils/misc/guc.c399
1 files changed, 392 insertions, 7 deletions
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 1cfc3e0346b..1620a8607c2 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -10,7 +10,7 @@
* Written by Peter Eisentraut <peter_e@gmx.net>.
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/utils/misc/guc.c,v 1.207 2004/05/26 04:41:43 neilc Exp $
+ * $PostgreSQL: pgsql/src/backend/utils/misc/guc.c,v 1.208 2004/05/26 15:07:39 momjian Exp $
*
*--------------------------------------------------------------------
*/
@@ -20,6 +20,7 @@
#include <float.h>
#include <limits.h>
#include <unistd.h>
+#include <ctype.h>
#include "utils/guc.h"
#include "utils/guc_tables.h"
@@ -103,6 +104,8 @@ static const char *assign_log_statement(const char *newval, bool doit,
static const char *assign_log_stmtlvl(int *var, const char *newval,
bool doit, GucSource source);
static bool assign_phony_autocommit(bool newval, bool doit, GucSource source);
+static const char *assign_custom_variable_classes(const char *newval, bool doit,
+ GucSource source);
static bool assign_stage_log_stats(bool newval, bool doit, GucSource source);
static bool assign_log_stats(bool newval, bool doit, GucSource source);
@@ -167,6 +170,7 @@ static char *server_version_string;
static char *session_authorization_string;
static char *timezone_string;
static char *XactIsoLevel_string;
+static char *custom_variable_classes;
static int max_function_args;
static int max_index_keys;
static int max_identifier_length;
@@ -1728,6 +1732,16 @@ static struct config_string ConfigureNamesString[] =
XLOG_sync_method_default, assign_xlog_sync_method, NULL
},
+ {
+ {"custom_variable_classes", PGC_POSTMASTER, RESOURCES_KERNEL,
+ gettext_noop("Sets the list of known custom variable classes"),
+ NULL,
+ GUC_LIST_INPUT | GUC_LIST_QUOTE
+ },
+ &custom_variable_classes,
+ NULL, assign_custom_variable_classes, NULL
+ },
+
/* End-of-list marker */
{
{NULL, 0, 0, NULL, NULL}, NULL, NULL, NULL, NULL
@@ -1753,8 +1767,15 @@ static const char * const map_old_guc_names[] = {
/*
* Actual lookup of variables is done through this single, sorted array.
*/
-struct config_generic **guc_variables;
-int num_guc_variables;
+static struct config_generic **guc_variables;
+
+/* Current number of variables contained in the vector
+ */
+static int num_guc_variables;
+
+/* Vector capacity
+ */
+static int size_guc_variables;
static bool guc_dirty; /* TRUE if need to do commit/abort work */
@@ -1768,6 +1789,10 @@ static int guc_name_compare(const char *namea, const char *nameb);
static void ReportGUCOption(struct config_generic * record);
static char *_ShowOption(struct config_generic * record);
+struct config_generic** get_guc_variables()
+{
+ return guc_variables;
+}
/*
* Build the sorted array. This is split out so that it could be
@@ -1777,6 +1802,7 @@ static char *_ShowOption(struct config_generic * record);
void
build_guc_variables(void)
{
+ int size_vars;
int num_vars = 0;
struct config_generic **guc_vars;
int i;
@@ -1814,8 +1840,12 @@ build_guc_variables(void)
num_vars++;
}
+ /* Create table with 20% slack
+ */
+ size_vars = num_vars + num_vars / 4;
+
guc_vars = (struct config_generic **)
- malloc(num_vars * sizeof(struct config_generic *));
+ malloc(size_vars * sizeof(struct config_generic *));
if (!guc_vars)
ereport(FATAL,
(errcode(ERRCODE_OUT_OF_MEMORY),
@@ -1835,15 +1865,107 @@ build_guc_variables(void)
for (i = 0; ConfigureNamesString[i].gen.name; i++)
guc_vars[num_vars++] = &ConfigureNamesString[i].gen;
- qsort((void *) guc_vars, num_vars, sizeof(struct config_generic *),
- guc_var_compare);
-
if (guc_variables)
free(guc_variables);
guc_variables = guc_vars;
num_guc_variables = num_vars;
+ size_guc_variables = size_vars;
+ qsort((void*) guc_variables, num_guc_variables,
+ sizeof(struct config_generic*), guc_var_compare);
}
+static bool
+is_custom_class(const char *name, int dotPos)
+{
+ /* The assign_custom_variable_classes has made sure no empty
+ * identifiers or whitespace exists in the variable
+ */
+ bool result = false;
+ const char *ccs = GetConfigOption("custom_variable_classes");
+ if(ccs != NULL)
+ {
+ const char *start = ccs;
+ for(;; ++ccs)
+ {
+ int c = *ccs;
+ if(c == 0 || c == ',')
+ {
+ if(dotPos == ccs - start && strncmp(start, name, dotPos) == 0)
+ {
+ result = true;
+ break;
+ }
+ if(c == 0)
+ break;
+ start = ccs + 1;
+ }
+ }
+ }
+ return result;
+}
+
+/*
+ * Add a new GUC variable to the list of known variables. The
+ * list is expanded if needed.
+ */
+static void
+add_guc_variable(struct config_generic *var)
+{
+ if(num_guc_variables + 1 >= size_guc_variables)
+ {
+ /* Increase the vector with 20%
+ */
+ int size_vars = size_guc_variables + size_guc_variables / 4;
+ struct config_generic** guc_vars;
+
+ if(size_vars == 0)
+ size_vars = 100;
+
+ guc_vars = (struct config_generic**)
+ malloc(size_vars * sizeof(struct config_generic*));
+
+ if (guc_variables != NULL)
+ {
+ memcpy(guc_vars, guc_variables,
+ num_guc_variables * sizeof(struct config_generic*));
+ free(guc_variables);
+ }
+
+ guc_variables = guc_vars;
+ size_guc_variables = size_vars;
+ }
+ guc_variables[num_guc_variables++] = var;
+ qsort((void*) guc_variables, num_guc_variables,
+ sizeof(struct config_generic*), guc_var_compare);
+}
+
+/*
+ * Create and add a placeholder variable. Its presumed to belong
+ * to a valid custom variable class at this point.
+ */
+static struct config_string*
+add_placeholder_variable(const char *name)
+{
+ size_t sz = sizeof(struct config_string) + sizeof(char*);
+ struct config_string* var = (struct config_string*)malloc(sz);
+ struct config_generic* gen = &var->gen;
+
+ memset(var, 0, sz);
+
+ gen->name = strdup(name);
+ gen->context = PGC_USERSET;
+ gen->group = CUSTOM_OPTIONS;
+ gen->short_desc = "GUC placeholder variable";
+ gen->flags = GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE | GUC_CUSTOM_PLACEHOLDER;
+ gen->vartype = PGC_STRING;
+
+ /* The char* is allocated at the end of the struct since we have
+ * no 'static' place to point to.
+ */
+ var->variable = (char**)(var + 1);
+ add_guc_variable((struct config_generic*)var);
+ return var;
+}
/*
* Look up option NAME. If it exists, return a pointer to its record,
@@ -1852,6 +1974,7 @@ build_guc_variables(void)
static struct config_generic *
find_option(const char *name)
{
+ const char *dot;
const char **key = &name;
struct config_generic **res;
int i;
@@ -1881,6 +2004,16 @@ find_option(const char *name)
return find_option(map_old_guc_names[i+1]);
}
+ /* Check if the name is qualified, and if so, check if the qualifier
+ * maps to a custom variable class.
+ */
+ dot = strchr(name, GUC_QUALIFIER_SEPARATOR);
+ if(dot != NULL && is_custom_class(name, dot - name))
+ /*
+ * Add a placeholder variable for this name
+ */
+ return (struct config_generic*)add_placeholder_variable(name);
+
/* Unknown name */
return NULL;
}
@@ -3455,6 +3588,196 @@ set_config_by_name(PG_FUNCTION_ARGS)
PG_RETURN_TEXT_P(result_text);
}
+static void
+define_custom_variable(struct config_generic* variable)
+{
+ const char* name = variable->name;
+ const char** nameAddr = &name;
+ const char* value;
+ struct config_string* pHolder;
+ struct config_generic** res = (struct config_generic**)bsearch(
+ (void*)&nameAddr,
+ (void*)guc_variables,
+ num_guc_variables,
+ sizeof(struct config_generic*),
+ guc_var_compare);
+
+ if(res == NULL)
+ {
+ add_guc_variable(variable);
+ return;
+ }
+
+ /* This better be a placeholder
+ */
+ if(((*res)->flags & GUC_CUSTOM_PLACEHOLDER) == 0)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INTERNAL_ERROR),
+ errmsg("attempt to redefine parameter \"%s\"", name)));
+ }
+ pHolder = (struct config_string*)*res;
+
+ /* We have the same name, no sorting is necessary.
+ */
+ *res = variable;
+
+ value = *pHolder->variable;
+
+ /* Assign the variable stored in the placeholder to the real
+ * variable.
+ */
+ set_config_option(name, value,
+ pHolder->gen.context, pHolder->gen.source,
+ false, true);
+
+ /* Free up stuff occupied by the placeholder variable
+ */
+ if(value != NULL)
+ free((void*)value);
+
+ if(pHolder->reset_val != NULL && pHolder->reset_val != value)
+ free(pHolder->reset_val);
+
+ if(pHolder->session_val != NULL
+ && pHolder->session_val != value
+ && pHolder->session_val != pHolder->reset_val)
+ free(pHolder->session_val);
+
+ if(pHolder->tentative_val != NULL
+ && pHolder->tentative_val != value
+ && pHolder->tentative_val != pHolder->reset_val
+ && pHolder->tentative_val != pHolder->session_val)
+ free(pHolder->tentative_val);
+
+ free(pHolder);
+}
+
+static void init_custom_variable(
+ struct config_generic* gen,
+ const char* name,
+ const char* short_desc,
+ const char* long_desc,
+ GucContext context,
+ enum config_type type)
+{
+ gen->name = strdup(name);
+ gen->context = context;
+ gen->group = CUSTOM_OPTIONS;
+ gen->short_desc = short_desc;
+ gen->long_desc = long_desc;
+ gen->vartype = type;
+}
+
+void DefineCustomBoolVariable(
+ const char* name,
+ const char* short_desc,
+ const char* long_desc,
+ bool* valueAddr,
+ GucContext context,
+ GucBoolAssignHook assign_hook,
+ GucShowHook show_hook)
+{
+ size_t sz = sizeof(struct config_bool);
+ struct config_bool* var = (struct config_bool*)malloc(sz);
+
+ memset(var, 0, sz);
+ init_custom_variable(&var->gen, name, short_desc, long_desc, context, PGC_BOOL);
+
+ var->variable = valueAddr;
+ var->reset_val = *valueAddr;
+ var->assign_hook = assign_hook;
+ var->show_hook = show_hook;
+ define_custom_variable(&var->gen);
+}
+
+void DefineCustomIntVariable(
+ const char* name,
+ const char* short_desc,
+ const char* long_desc,
+ int* valueAddr,
+ GucContext context,
+ GucIntAssignHook assign_hook,
+ GucShowHook show_hook)
+{
+ size_t sz = sizeof(struct config_int);
+ struct config_int* var = (struct config_int*)malloc(sz);
+
+ memset(var, 0, sz);
+ init_custom_variable(&var->gen, name, short_desc, long_desc, context, PGC_INT);
+
+ var->variable = valueAddr;
+ var->reset_val = *valueAddr;
+ var->assign_hook = assign_hook;
+ var->show_hook = show_hook;
+ define_custom_variable(&var->gen);
+}
+
+void DefineCustomRealVariable(
+ const char* name,
+ const char* short_desc,
+ const char* long_desc,
+ double* valueAddr,
+ GucContext context,
+ GucRealAssignHook assign_hook,
+ GucShowHook show_hook)
+{
+ size_t sz = sizeof(struct config_real);
+ struct config_real* var = (struct config_real*)malloc(sz);
+
+ memset(var, 0, sz);
+ init_custom_variable(&var->gen, name, short_desc, long_desc, context, PGC_REAL);
+
+ var->variable = valueAddr;
+ var->reset_val = *valueAddr;
+ var->assign_hook = assign_hook;
+ var->show_hook = show_hook;
+ define_custom_variable(&var->gen);
+}
+
+void DefineCustomStringVariable(
+ const char* name,
+ const char* short_desc,
+ const char* long_desc,
+ char** valueAddr,
+ GucContext context,
+ GucStringAssignHook assign_hook,
+ GucShowHook show_hook)
+{
+ size_t sz = sizeof(struct config_string);
+ struct config_string* var = (struct config_string*)malloc(sz);
+
+ memset(var, 0, sz);
+ init_custom_variable(&var->gen, name, short_desc, long_desc, context, PGC_STRING);
+
+ var->variable = valueAddr;
+ var->reset_val = *valueAddr;
+ var->assign_hook = assign_hook;
+ var->show_hook = show_hook;
+ define_custom_variable(&var->gen);
+}
+
+extern void EmittWarningsOnPlaceholders(const char* className)
+{
+ struct config_generic** vars = guc_variables;
+ struct config_generic** last = vars + num_guc_variables;
+
+ int nameLen = strlen(className);
+ while(vars < last)
+ {
+ struct config_generic* var = *vars++;
+ if((var->flags & GUC_CUSTOM_PLACEHOLDER) != 0 &&
+ strncmp(className, var->name, nameLen) == 0 &&
+ var->name[nameLen] == GUC_QUALIFIER_SEPARATOR)
+ {
+ ereport(INFO,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("unrecognized configuration parameter \"%s\"", var->name)));
+ }
+ }
+}
+
+
/*
* SHOW command
*/
@@ -4706,6 +5029,68 @@ assign_phony_autocommit(bool newval, bool doit, GucSource source)
return true;
}
+static const char *
+assign_custom_variable_classes(const char *newval, bool doit, GucSource source)
+{
+ /* Check syntax. newval must be a comma separated list of identifiers.
+ * Whitespace is allowed but skipped.
+ */
+ bool hasSpaceAfterToken = false;
+ const char *cp = newval;
+ int symLen = 0;
+ int c;
+ StringInfoData buf;
+
+ initStringInfo(&buf);
+ while((c = *cp++) != 0)
+ {
+ if(isspace(c))
+ {
+ if(symLen > 0)
+ hasSpaceAfterToken = true;
+ continue;
+ }
+
+ if(c == ',')
+ {
+ hasSpaceAfterToken = false;
+ if(symLen > 0)
+ {
+ symLen = 0;
+ appendStringInfoChar(&buf, ',');
+ }
+ continue;
+ }
+
+ if(hasSpaceAfterToken || !isalnum(c))
+ {
+ /* Syntax error due to token following space after
+ * token or non alpha numeric character
+ */
+ pfree(buf.data);
+ ereport(WARNING,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("illegal syntax for custom_variable_classes \"%s\"", newval)));
+ return NULL;
+ }
+ symLen++;
+ appendStringInfoChar(&buf, (char)c);
+ }
+
+ if(symLen == 0 && buf.len > 0)
+ /*
+ * Remove stray ',' at end
+ */
+ buf.data[--buf.len] = 0;
+
+ if(buf.len == 0)
+ newval = NULL;
+ else if(doit)
+ newval = strdup(buf.data);
+
+ pfree(buf.data);
+ return newval;
+}
static bool
assign_stage_log_stats(bool newval, bool doit, GucSource source)