Index: doc/src/sgml/runtime.sgml =================================================================== RCS file: /projects/cvsroot/pgsql-server/doc/src/sgml/runtime.sgml,v retrieving revision 1.263 diff -u -r1.263 runtime.sgml --- doc/src/sgml/runtime.sgml 29 Apr 2004 04:37:09 -0000 1.263 +++ doc/src/sgml/runtime.sgml 9 May 2004 20:37:42 -0000 @@ -2924,6 +2924,60 @@ + + Customized Options + + + The following was designed to allow options not normally known to + PostgreSQL to be declared in the posgresql.conf + file and/or manipulated using the SET in a controlled + manner so that add-on modules to the postgres proper (such as lanugage + mappings for triggers and functions) can be configured in a unified way. + + + + + + custom_variable_classes (string) + custom_variable_classes + + + This variable specifies one or several classes to be used for custom + variables. A custom variable is a variable not normally known to + the PostgreSQL proper but used by some add + on module. + + + + Aribtrary variables can be defined for each class specified here. Those + variables will be treated as placeholders and have no meaning until the + module that defines them is loaded. When a module for a specific class is + loaded, it will add the proper variable definitions for the class + associated with it, convert any placeholder values according to those + definitions, and issue warnings for any placeholders that then remains. + + + + Here is an example what custom variables might look like: + + +custom_variable_class = 'plr,pljava' +plr.foo = '/usr/lib/R' +pljava.baz = 1 +plruby.var = true <== this one would generate an error + + + + + This option can only be set at server start or in the + postgresql.conf configuration file. + + + + + + + Developer Options Index: src/backend/utils/misc/guc-file.l =================================================================== RCS file: /projects/cvsroot/pgsql-server/src/backend/utils/misc/guc-file.l,v retrieving revision 1.21 diff -u -r1.21 guc-file.l --- src/backend/utils/misc/guc-file.l 24 Feb 2004 22:06:32 -0000 1.21 +++ src/backend/utils/misc/guc-file.l 9 May 2004 20:37:42 -0000 @@ -34,6 +34,7 @@ GUC_REAL = 4, GUC_EQUALS = 5, GUC_UNQUOTED_STRING = 6, + GUC_QUALIFIED_ID = 7, GUC_EOL = 99, GUC_ERROR = 100 }; @@ -65,6 +66,7 @@ LETTER_OR_DIGIT [A-Za-z_0-9\200-\377] ID {LETTER}{LETTER_OR_DIGIT}* +QUALIFIED_ID {ID}"."{ID} UNQUOTED_STRING {LETTER}({LETTER_OR_DIGIT}|[-._:/])* STRING \'([^'\n]|\\.)*\' @@ -76,6 +78,7 @@ #.*$ /* eat comment */ {ID} return GUC_ID; +{QUALIFIED_ID} return GUC_QUALIFIED_ID; {STRING} return GUC_STRING; {UNQUOTED_STRING} return GUC_UNQUOTED_STRING; {INTEGER} return GUC_INTEGER; @@ -180,7 +183,7 @@ case 0: /* no previous input */ if (token == GUC_EOL) /* empty line */ continue; - if (token != GUC_ID) + if (token != GUC_ID && token != GUC_QUALIFIED_ID) goto parse_error; opt_name = strdup(yytext); if (opt_name == NULL) @@ -216,6 +219,24 @@ case 2: /* now we'd like an end of line */ if (token != GUC_EOL) goto parse_error; + + if (strcmp(opt_name, "custom_variable_classes") == 0) + { + /* This variable must be added first as it controls the validity + * of other variables + */ + if (!set_config_option(opt_name, opt_value, context, + PGC_S_FILE, false, true)) + { + FreeFile(fp); + free(filename); + goto cleanup_exit; + } + + /* Don't include in list */ + parse_state = 0; + break; + } /* append to list */ item = malloc(sizeof *item); Index: src/backend/utils/misc/guc.c =================================================================== RCS file: /projects/cvsroot/pgsql-server/src/backend/utils/misc/guc.c,v retrieving revision 1.205 diff -u -r1.205 guc.c --- src/backend/utils/misc/guc.c 8 May 2004 02:11:46 -0000 1.205 +++ src/backend/utils/misc/guc.c 9 May 2004 20:37:43 -0000 @@ -20,6 +20,7 @@ #include #include #include +#include #include "utils/guc.h" #include "utils/guc_tables.h" @@ -103,6 +104,8 @@ 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 *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 @@ 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 @@ /* * 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 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 @@ void build_guc_variables(void) { + int size_vars; int num_vars = 0; struct config_generic **guc_vars; int i; @@ -1814,8 +1840,12 @@ 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,105 @@ 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; + if(size_vars == 0) + size_vars = 100; + + struct config_generic** 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 +1972,7 @@ static struct config_generic * find_option(const char *name) { + const char *dot; const char **key = &name; struct config_generic **res; int i; @@ -1881,6 +2002,16 @@ 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; } @@ -3459,6 +3590,196 @@ 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 */ @@ -4470,7 +4791,7 @@ else { if (source >= PGC_S_INTERACTIVE) ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + ((ERRCODE_INVALID_PARAMETER_VALUE), errmsg("unrecognised \"log_destination\" key word: \"%s\"", tok))); pfree(rawstring); @@ -4710,6 +5031,68 @@ 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) Index: src/backend/utils/misc/help_config.c =================================================================== RCS file: /projects/cvsroot/pgsql-server/src/backend/utils/misc/help_config.c,v retrieving revision 1.9 diff -u -r1.9 help_config.c --- src/backend/utils/misc/help_config.c 29 Nov 2003 19:52:04 -0000 1.9 +++ src/backend/utils/misc/help_config.c 9 May 2004 20:37:43 -0000 @@ -47,13 +47,15 @@ GucInfoMain(void) { int i; + struct config_generic **guc_vars = get_guc_variables(); + int numOpts = GetNumConfigOptions(); /* Initialize the guc_variables[] array */ build_guc_variables(); - for (i = 0; i < num_guc_variables; i++) + for (i = 0; i < numOpts; i++) { - mixedStruct *var = (mixedStruct *) guc_variables[i]; + mixedStruct *var = (mixedStruct *) guc_vars[i]; if (displayStruct(var)) printMixedStruct(var); Index: src/include/utils/guc.h =================================================================== RCS file: /projects/cvsroot/pgsql-server/src/include/utils/guc.h,v retrieving revision 1.45 diff -u -r1.45 guc.h --- src/include/utils/guc.h 7 Apr 2004 05:05:50 -0000 1.45 +++ src/include/utils/guc.h 9 May 2004 20:37:44 -0000 @@ -102,6 +102,15 @@ PGC_S_SESSION /* SET command */ } GucSource; +typedef const char* (*GucStringAssignHook)(const char *newval, bool doit, GucSource source); +typedef bool (*GucBoolAssignHook)(bool newval, bool doit, GucSource source); +typedef bool (*GucIntAssignHook)(int newval, bool doit, GucSource source); +typedef bool (*GucRealAssignHook)(double newval, bool doit, GucSource source); + +typedef const char* (*GucShowHook)(void); + +#define GUC_QUALIFIER_SEPARATOR '.' + /* GUC vars that are actually declared in guc.c, rather than elsewhere */ extern bool log_duration; extern bool Debug_print_plan; @@ -129,6 +138,45 @@ extern void SetConfigOption(const char *name, const char *value, GucContext context, GucSource source); + +extern void DefineCustomBoolVariable( + const char* name, + const char* short_desc, + const char* long_desc, + bool* valueAddr, + GucContext context, + GucBoolAssignHook assign_hook, + GucShowHook show_hook); + +extern void DefineCustomIntVariable( + const char* name, + const char* short_desc, + const char* long_desc, + int* valueAddr, + GucContext context, + GucIntAssignHook assign_hook, + GucShowHook show_hook); + +extern void DefineCustomRealVariable( + const char* name, + const char* short_desc, + const char* long_desc, + double* valueAddr, + GucContext context, + GucRealAssignHook assign_hook, + GucShowHook show_hook); + +extern void DefineCustomStringVariable( + const char* name, + const char* short_desc, + const char* long_desc, + char** valueAddr, + GucContext context, + GucStringAssignHook assign_hook, + GucShowHook show_hook); + +extern void EmittWarningsOnPlaceholders(const char* className); + extern const char *GetConfigOption(const char *name); extern const char *GetConfigOptionResetString(const char *name); extern void ProcessConfigFile(GucContext context); Index: src/include/utils/guc_tables.h =================================================================== RCS file: /projects/cvsroot/pgsql-server/src/include/utils/guc_tables.h,v retrieving revision 1.10 diff -u -r1.10 guc_tables.h --- src/include/utils/guc_tables.h 5 Apr 2004 03:02:11 -0000 1.10 +++ src/include/utils/guc_tables.h 9 May 2004 20:37:44 -0000 @@ -51,7 +51,8 @@ COMPAT_OPTIONS_PREVIOUS, COMPAT_OPTIONS_CLIENT, DEVELOPER_OPTIONS, - COMPILE_OPTIONS + COMPILE_OPTIONS, + CUSTOM_OPTIONS }; /* @@ -98,6 +99,7 @@ #define GUC_REPORT 0x0010 /* auto-report changes to client */ #define GUC_NOT_IN_SAMPLE 0x0020 /* not in postgresql.conf.sample */ #define GUC_DISALLOW_IN_FILE 0x0040 /* can't set in postgresql.conf */ +#define GUC_CUSTOM_PLACEHOLDER 0x0080 /* placeholder for a custom variable */ /* bit values in status field */ #define GUC_HAVE_TENTATIVE 0x0001 /* tentative value is defined */ @@ -113,8 +115,8 @@ /* (all but reset_val are constants) */ bool *variable; bool reset_val; - bool (*assign_hook) (bool newval, bool doit, GucSource source); - const char *(*show_hook) (void); + GucBoolAssignHook assign_hook; + GucShowHook show_hook; /* variable fields, initialized at runtime: */ bool session_val; bool tentative_val; @@ -129,8 +131,8 @@ int reset_val; int min; int max; - bool (*assign_hook) (int newval, bool doit, GucSource source); - const char *(*show_hook) (void); + GucIntAssignHook assign_hook; + GucShowHook show_hook; /* variable fields, initialized at runtime: */ int session_val; int tentative_val; @@ -145,8 +147,8 @@ double reset_val; double min; double max; - bool (*assign_hook) (double newval, bool doit, GucSource source); - const char *(*show_hook) (void); + GucRealAssignHook assign_hook; + GucShowHook show_hook; /* variable fields, initialized at runtime: */ double session_val; double tentative_val; @@ -159,8 +161,8 @@ /* (all are constants) */ char **variable; const char *boot_val; - const char *(*assign_hook) (const char *newval, bool doit, GucSource source); - const char *(*show_hook) (void); + GucStringAssignHook assign_hook; + GucShowHook show_hook; /* variable fields, initialized at runtime: */ char *reset_val; char *session_val; @@ -173,9 +175,8 @@ extern const char *const GucContext_Names[]; extern const char *const GucSource_Names[]; -/* the current set of variables */ -extern struct config_generic **guc_variables; -extern int num_guc_variables; +/* get the current set of variables */ +extern struct config_generic **get_guc_variables(void); extern void build_guc_variables(void);