Peter Eisentraut wrote: > Joachim Wieland wrote: > > Attached is the long-awaited guc patch that makes values fall back > > to their default values when they got removed (or commented) from > > the configuration file. This has always been a source of confusion. > > I have applied your patch with some of the discussed corrections. > The use of memory allocation in the GUC code might still need some > review.
Reverted for further fixes. Attached is the latest patch I was working with. -- Peter Eisentraut http://developer.postgresql.org/~petere/
diff -cr ./src/backend/access/transam/xact.c ../cvs-pgsql/src/backend/access/transam/xact.c *** ./src/backend/access/transam/xact.c 2007-03-13 15:16:36.000000000 +0100 --- ../cvs-pgsql/src/backend/access/transam/xact.c 2007-03-13 13:28:26.000000000 +0100 *************** *** 1513,1518 **** --- 1513,1521 ---- /* NOTIFY commit must come before lower-level cleanup */ AtCommit_Notify(); + /* GUC processing could abort transaction */ + AtEOXact_GUC(true, false); + /* * Update flat files if we changed pg_database, pg_authid or * pg_auth_members. This should be the last step before commit. *************** *** 1623,1629 **** /* Check we've released all catcache entries */ AtEOXact_CatCache(true); - AtEOXact_GUC(true, false); AtEOXact_SPI(true); AtEOXact_on_commit_actions(true); AtEOXact_Namespace(true); --- 1626,1631 ---- diff -cr ./src/backend/utils/misc/guc.c ../cvs-pgsql/src/backend/utils/misc/guc.c *** ./src/backend/utils/misc/guc.c 2007-03-13 15:17:40.000000000 +0100 --- ../cvs-pgsql/src/backend/utils/misc/guc.c 2007-03-13 13:28:29.000000000 +0100 *************** *** 2439,2445 **** if (oldval == NULL || oldval == *(conf->variable) || oldval == conf->reset_val || ! oldval == conf->tentative_val) return; for (stack = conf->gen.stack; stack; stack = stack->prev) { --- 2439,2446 ---- if (oldval == NULL || oldval == *(conf->variable) || oldval == conf->reset_val || ! oldval == conf->tentative_val || ! oldval == conf->boot_val) return; for (stack = conf->gen.stack; stack; stack = stack->prev) { *************** *** 2462,2468 **** if (strval == *(conf->variable) || strval == conf->reset_val || ! strval == conf->tentative_val) return true; for (stack = conf->gen.stack; stack; stack = stack->prev) { --- 2463,2470 ---- if (strval == *(conf->variable) || strval == conf->reset_val || ! strval == conf->tentative_val || ! strval == conf->boot_val) return true; for (stack = conf->gen.stack; stack; stack = stack->prev) { *************** *** 2631,2636 **** --- 2633,2737 ---- return true; } + static int + guc_get_index(const char *name) + { + int i; + + for (i = 0; i < num_guc_variables; i++) + if (pg_strcasecmp(name, guc_variables[i]->name) == 0) + return i; + + return -1; + } + + static void + guc_delete_variable(const char *name) + { + struct config_generic *gen; + int idx; + GucStack *stack, *prev; + struct config_string *conf; + + idx = guc_get_index(name); + Assert(idx >= 0); + + gen = guc_variables[idx]; + + /* + * Even though this function could delete other types of variables as well, + * at the moment we only call it for custom variables that always have type + * string. + */ + Assert(gen->group == CUSTOM_OPTIONS); + Assert(gen->vartype == PGC_STRING); + + conf = (struct config_string *) gen; + set_string_field(conf, &conf->reset_val, NULL); + set_string_field(conf, &conf->tentative_val, NULL); + for (stack = conf->gen.stack; stack; stack = prev) + { + set_string_field(conf, &stack->tentative_val.stringval, NULL); + set_string_field(conf, &stack->value.stringval, NULL); + prev = stack->prev; + pfree(stack); + } + + /* no pfree() here, gen has been allocated via guc_malloc */ + free(gen); + guc_variables[idx] = guc_variables[num_guc_variables - 1]; + num_guc_variables--; + qsort((void *) guc_variables, num_guc_variables, + sizeof(struct config_generic *), guc_var_compare); + } + + static void + guc_delete_custom_variable(const char *name) + { + struct config_generic *gen; + struct config_string *conf; + int idx; + + idx = guc_get_index(name); + Assert(idx >= 0); + + gen = guc_variables[idx]; + + Assert(gen->group == CUSTOM_OPTIONS); + Assert(gen->vartype == PGC_STRING); + + conf = (struct config_string *) gen; + + /* + * Here we check whether it is safe to really delete the variable + * or if we have to wait for the end of the transaction. We do + * not remove the physical entry of a custom variable if it has + * been SET to another value in the transaction but has been + * removed from the configuration file. + */ + if (gen->stack) + { + /* + * Make sure this custom variable (which has a definition in + * the configuration file) behaves as any other custom + * variable from now on. We have deleted its former + * definition; that's why "RESET foo.bar" should not fall back + * to this deleted definition from the configuration file. + * The analogy is that any other custom variable that gets + * introduced in a session has no reset value either. And a + * variable that has been SET within a transaction and has + * then been deleted from the configuration file should behave + * as if it had been introduced in the session. + */ + Assert(gen->vartype == PGC_STRING); + gen->reset_source = PGC_S_DEFAULT; + set_string_field(conf, &conf->reset_val, NULL); + } + else + guc_delete_variable(name); + } + + /* * Create and add a placeholder variable. It's presumed to belong * to a valid custom variable class at this point. *************** *** 2809,2847 **** struct config_bool *conf = (struct config_bool *) gconf; if (conf->assign_hook) ! if (!(*conf->assign_hook) (conf->reset_val, true, PGC_S_DEFAULT)) elog(FATAL, "failed to initialize %s to %d", ! conf->gen.name, (int) conf->reset_val); ! *conf->variable = conf->reset_val; break; } case PGC_INT: { struct config_int *conf = (struct config_int *) gconf; ! Assert(conf->reset_val >= conf->min); ! Assert(conf->reset_val <= conf->max); if (conf->assign_hook) ! if (!(*conf->assign_hook) (conf->reset_val, true, PGC_S_DEFAULT)) elog(FATAL, "failed to initialize %s to %d", ! conf->gen.name, conf->reset_val); ! *conf->variable = conf->reset_val; break; } case PGC_REAL: { struct config_real *conf = (struct config_real *) gconf; ! Assert(conf->reset_val >= conf->min); ! Assert(conf->reset_val <= conf->max); if (conf->assign_hook) ! if (!(*conf->assign_hook) (conf->reset_val, true, PGC_S_DEFAULT)) elog(FATAL, "failed to initialize %s to %g", ! conf->gen.name, conf->reset_val); ! *conf->variable = conf->reset_val; break; } case PGC_STRING: --- 2910,2948 ---- 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: *************** *** 3296,3303 **** } } ! /* * Do GUC processing at transaction or subtransaction commit or abort. */ void AtEOXact_GUC(bool isCommit, bool isSubXact) --- 3397,3421 ---- } } ! /*------ * Do GUC processing at transaction or subtransaction commit or abort. + * + * GUC can abort the transaction in exactly one case, namely when you + * delete a custom variable class while a still-open transaction has + * SET a custom variable within this class. + * + * Consider the following example. In the configuration file we could + * have: + * + * custom_variable_classes = "foo" + * + * begin; + * set foo.bar to 1; + * <delete foo.bar from configuration file and send SIGHUP> + * commit; + * + * This will result in an error because foo.bar is no longer available + * but commit would have to guarantee that the value is preserved. */ void AtEOXact_GUC(bool isCommit, bool isSubXact) *************** *** 3335,3340 **** --- 3453,3485 ---- continue; Assert(stack->nest_level == my_level); + if (!isSubXact && gconf->group == CUSTOM_OPTIONS) + { + char *dot = strchr(gconf->name, GUC_QUALIFIER_SEPARATOR); + Assert(dot != NULL); + if (!is_custom_class(gconf->name, dot - gconf->name)) + { + if (!isCommit) + { + /* We do not commit the transaction. Delete the variable. */ + guc_delete_variable(gconf->name); + /* Call the loop with the same i again, which is + * the next variable. */ + i--; + continue; + } + else + { + /* We commit the transaction. Throw an error that + * the respective custom class does not exist. */ + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("no valid custom variable class available for " + "parameter \"%s\"", gconf->name))); + } + } + } + /* * We will pop the stack entry. Start by restoring outer xact status * (since we may want to modify it below). Be careful to use *************** *** 3998,4016 **** } /* ! * Should we set reset/stacked values? (If so, the behavior is not ! * transactional.) */ ! makeDefault = changeVal && (source <= PGC_S_OVERRIDE) && (value != NULL); /* ! * Ignore attempted set if overridden by previously processed setting. ! * However, if changeVal is false then plow ahead anyway since we are ! * trying to find out if the value is potentially good, not actually use ! * it. Also keep going if makeDefault is true, since we may want to set ! * the reset/stacked values even if we can't set the variable itself. */ ! if (record->source > source) { if (changeVal && !makeDefault) { --- 4143,4168 ---- } /* ! * Should we set reset/stacked values? (If so, the behavior is not ! * transactional.) This is done either when we get a default ! * value from the database's/user's/client's default settings or ! * when we reset a value to its default. */ ! makeDefault = changeVal && (source <= PGC_S_OVERRIDE) ! && ((value != NULL) || source == PGC_S_DEFAULT); /* ! * Ignore attempted set if overridden by previously processed ! * setting. However, if changeVal is false then plow ahead anyway ! * since we are trying to find out if the value is potentially ! * good, not actually use it. Also keep going if makeDefault is ! * true, since we may want to set the reset/stacked values even if ! * we can't set the variable itself. There's one exception to ! * this rule: if we want to apply the default value to variables ! * that were removed from the configuration file. This is ! * indicated by source == PGC_S_DEFAULT. */ ! if (record->source > source && source != PGC_S_DEFAULT) { if (changeVal && !makeDefault) { *************** *** 4042,4047 **** --- 4194,4207 ---- return false; } } + /* + * If value == NULL and source == PGC_S_DEFAULT then + * we reset some value to its default (removed from + * configuration file). + */ + else if (source == PGC_S_DEFAULT) + newval = conf->boot_val; + /* else we handle a "RESET varname" command */ else { newval = conf->reset_val; *************** *** 4126,4131 **** --- 4286,4299 ---- return false; } } + /* + * If value == NULL and source == PGC_S_DEFAULT then + * we reset some value to its default (removed from + * configuration file). + */ + else if (source == PGC_S_DEFAULT) + newval = conf->boot_val; + /* else we handle a "RESET varname" command */ else { newval = conf->reset_val; *************** *** 4210,4215 **** --- 4378,4391 ---- return false; } } + /* + * If value == NULL and source == PGC_S_DEFAULT then + * we reset some value to its default (removed from + * configuration file). + */ + else if (source == PGC_S_DEFAULT) + newval = conf->boot_val; + /* else we handle a "RESET varname" command */ else { newval = conf->reset_val; *************** *** 4288,4293 **** --- 4464,4486 ---- if (conf->gen.flags & GUC_IS_NAME) truncate_identifier(newval, strlen(newval), true); } + /* + * If value == NULL and source == PGC_S_DEFAULT then + * we reset some value to its default (removed from + * configuration file). + */ + else if (source == PGC_S_DEFAULT) + { + if (conf->boot_val == NULL) + newval = NULL; + else + { + newval = guc_strdup(elevel, conf->boot_val); + if (newval == NULL) + return false; + } + } + /* else we handle a "RESET varname" command */ else if (conf->reset_val) { /* *************** *** 6304,6309 **** --- 6497,6509 ---- int c; StringInfoData buf; + /* + * Resetting custom_variable_classes by removing it from the + * configuration file will lead to newval = NULL + */ + if (newval == NULL) + return guc_strdup(ERROR, ""); + initStringInfo(&buf); while ((c = *cp++) != 0) { *************** *** 6348,6354 **** if (buf.len == 0) newval = NULL; else if (doit) ! newval = strdup(buf.data); pfree(buf.data); return newval; --- 6548,6554 ---- if (buf.len == 0) newval = NULL; else if (doit) ! newval = guc_strdup(ERROR, buf.data); pfree(buf.data); return newval; diff -cr ./src/backend/utils/misc/guc-file.l ../cvs-pgsql/src/backend/utils/misc/guc-file.l *** ./src/backend/utils/misc/guc-file.l 2007-03-13 15:16:36.000000000 +0100 --- ../cvs-pgsql/src/backend/utils/misc/guc-file.l 2007-03-13 13:28:29.000000000 +0100 *************** *** 54,59 **** --- 54,61 ---- static void free_name_value_list(struct name_value_pair * list); static char *GUC_scanstr(const char *s); + static char *custom_variable_classes_backup; + %} %option 8bit *************** *** 116,121 **** --- 118,127 ---- { int elevel; struct name_value_pair *item, *head, *tail; + int i; + bool *in_conffile = NULL; + const char *var; + bool success = false; Assert(context == PGC_POSTMASTER || context == PGC_SIGHUP); *************** *** 132,137 **** --- 138,155 ---- head = tail = NULL; + /* + * We do not know whether we will read the configuration file + * without error. If we encounter an error we have to restore the + * previous value of the custom_variable_classes variable for + * consistency. Therefore we have to save its value. + */ + var = GetConfigOption("custom_variable_classes"); + if (var) + custom_variable_classes_backup = pstrdup(var); + set_config_option("custom_variable_classes", NULL, context, + PGC_S_DEFAULT, false, true); + if (!ParseConfigFile(ConfigFileName, NULL, 0, context, elevel, &head, &tail)) *************** *** 140,159 **** /* Check if all options are valid */ for (item = head; item; item = item->next) { if (!set_config_option(item->name, item->value, context, PGC_S_FILE, false, false)) goto cleanup_list; } ! /* If we got here all the options checked out okay, so apply them. */ for (item = head; item; item = item->next) { set_config_option(item->name, item->value, context, PGC_S_FILE, false, true); ! } cleanup_list: free_name_value_list(head); } --- 158,355 ---- /* Check if all options are valid */ for (item = head; item; item = item->next) { + char *sep = strchr(item->name, GUC_QUALIFIER_SEPARATOR); + if (sep && !is_custom_class(item->name, sep - item->name)) + { + ereport(elevel, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("unrecognized configuration parameter \"%s\"", + item->name))); + goto cleanup_list; + } + if (!set_config_option(item->name, item->value, context, PGC_S_FILE, false, false)) goto cleanup_list; } ! ! /* ! * Mark all variables as not showing up in the config file. The ! * allocation has to take place after ParseConfigFile() since this ! * function can change num_guc_variables due to custom variables. ! * It would be easier to add a new field or status bit to struct ! * conf_generic, but that way we would expose internal information ! * that is just needed here in the following few lines. The price ! * to pay for this separation are a few more loops over the set of ! * configuration options, but those are expected to be rather few ! * and we only have to pay the cost at SIGHUP. We initialize ! * in_conffile only here because set_config_option() makes ! * guc_variables grow with custom variables. ! */ ! in_conffile = guc_malloc(elevel, num_guc_variables * sizeof(bool)); ! if (!in_conffile) ! goto cleanup_list; ! for (i = 0; i < num_guc_variables; i++) ! in_conffile[i] = false; ! for (item = head; item; item = item->next) { + /* + * After set_config_option() the variable name item->name is + * known to exist. + */ + Assert(guc_get_index(item->name) >= 0); + in_conffile[guc_get_index(item->name)] = true; + } + + for (i = 0; i < num_guc_variables; i++) + { + struct config_generic *gconf = guc_variables[i]; + if (!in_conffile[i] && strchr(gconf->name, GUC_QUALIFIER_SEPARATOR)) + { + /* handle custom variables here */ + int j; + + /* + * guc_delete_variable will re-sort the array of + * configuration options and decrease num_guc_variables by + * one. + */ + guc_delete_custom_variable(gconf->name); + + /* + * Since the array is always sorted we just have to shift + * all boolean values after position i by one position to + * the left. + */ + for (j = i; j < num_guc_variables; j++) + in_conffile[j] = in_conffile[j+1]; + + /* + * Decrease the loop variable to re-test the entry at the + * current position that is now a different one. + */ + i--; + + continue; + } + else if (!in_conffile[i] && gconf->source == PGC_S_FILE) + { + if (gconf->context < PGC_SIGHUP) + ereport(elevel, + (errcode(ERRCODE_CANT_CHANGE_RUNTIME_PARAM), + errmsg("parameter \"%s\" cannot be changed after server start; configuration file change ignored", + gconf->name))); + else + { + /* prepare */ + GucStack *stack; + if (gconf->reset_source == PGC_S_FILE) + gconf->reset_source = PGC_S_DEFAULT; + for (stack = gconf->stack; stack; stack = stack->prev) + if (stack->source == PGC_S_FILE) + stack->source = PGC_S_DEFAULT; + /* apply the default */ + set_config_option(gconf->name, NULL, context, + PGC_S_DEFAULT, false, true); + } + } + else if (!in_conffile[i] && gconf->reset_source == PGC_S_FILE) + { + /*------ + * Change the reset_val to default_val. Here's an + * example: In the configuration file we have + * + * seq_page_cost = 3.00 + * + * Now we execute in a session + * + * SET seq_page_cost TO 4.00; + * + * Then we remove this option from the configuration file + * and send SIGHUP. Now when you execute + * + * RESET seq_page_cost; + * + * it should fall back to 1.00 (the default value for + * seq_page_cost) and not to 3.00 (which is the current + * reset_val). + */ + + switch (gconf->vartype) + { + case PGC_BOOL: + { + struct config_bool *conf; + conf = (struct config_bool *) gconf; + conf->reset_val = conf->boot_val; + break; + } + case PGC_INT: + { + struct config_int *conf; + conf = (struct config_int *) gconf; + conf->reset_val = conf->boot_val; + break; + } + case PGC_REAL: + { + struct config_real *conf; + conf = (struct config_real *) gconf; + conf->reset_val = conf->boot_val; + break; + } + case PGC_STRING: + { + struct config_string *conf; + conf = (struct config_string *) gconf; + /* + * We can cast away the const here because we + * won't free the address. It is protected by + * set_string_field() and string_field_used(). + */ + conf->reset_val = (char *) conf->boot_val; + break; + } + } + } + } + + /* If we got here all the options checked out okay, so apply them. */ + for (item = head; item; item = item->next) set_config_option(item->name, item->value, context, PGC_S_FILE, false, true); ! ! /* ! * Reset variables to the value of environment variables ! * (PGC_S_ENV_VAR overrules PGC_S_FILE). PGPORT is ignored, ! * because it cannot be changed without restart. ! */ ! var = getenv("PGDATESTYLE"); ! if (var != NULL) ! set_config_option("datestyle", var, context, ! PGC_S_ENV_VAR, false, true); ! ! var = getenv("PGCLIENTENCODING"); ! if (var != NULL) ! set_config_option("client_encoding", var, context, ! PGC_S_ENV_VAR, false, true); ! ! success = true; cleanup_list: + free(in_conffile); free_name_value_list(head); + if (!success) + set_config_option("custom_variable_classes", + custom_variable_classes_backup, + context, PGC_S_FILE, false, true); + if (custom_variable_classes_backup) + { + pfree(custom_variable_classes_backup); + custom_variable_classes_backup = NULL; + } } *************** *** 312,325 **** { pfree(opt_name); pfree(opt_value); ! /* we assume error message was logged already */ OK = false; goto cleanup_exit; } - pfree(opt_name); - pfree(opt_value); } ! else { /* append to list */ struct name_value_pair *item; --- 508,521 ---- { pfree(opt_name); pfree(opt_value); ! ! /* We assume the error message was logged already. */ OK = false; goto cleanup_exit; } } ! ! if (pg_strcasecmp(opt_name, "include") != 0) { /* append to list */ struct name_value_pair *item; diff -cr ./src/include/utils/guc_tables.h ../cvs-pgsql/src/include/utils/guc_tables.h *** ./src/include/utils/guc_tables.h 2007-03-13 15:16:36.000000000 +0100 --- ../cvs-pgsql/src/include/utils/guc_tables.h 2007-03-13 13:28:33.000000000 +0100 *************** *** 154,164 **** /* these fields must be set correctly in initial value: */ /* (all but reset_val are constants) */ bool *variable; ! bool reset_val; GucBoolAssignHook assign_hook; GucShowHook show_hook; /* variable fields, initialized at runtime: */ bool tentative_val; }; struct config_int --- 154,165 ---- /* these fields must be set correctly in initial value: */ /* (all but reset_val are constants) */ bool *variable; ! bool boot_val; GucBoolAssignHook assign_hook; GucShowHook show_hook; /* variable fields, initialized at runtime: */ bool tentative_val; + bool reset_val; }; struct config_int *************** *** 167,179 **** /* these fields must be set correctly in initial value: */ /* (all but reset_val are constants) */ int *variable; ! int reset_val; int min; int max; GucIntAssignHook assign_hook; GucShowHook show_hook; /* variable fields, initialized at runtime: */ int tentative_val; }; struct config_real --- 168,181 ---- /* these fields must be set correctly in initial value: */ /* (all but reset_val are constants) */ int *variable; ! int boot_val; int min; int max; GucIntAssignHook assign_hook; GucShowHook show_hook; /* variable fields, initialized at runtime: */ int tentative_val; + int reset_val; }; struct config_real *************** *** 182,194 **** /* these fields must be set correctly in initial value: */ /* (all but reset_val are constants) */ double *variable; ! double reset_val; double min; double max; GucRealAssignHook assign_hook; GucShowHook show_hook; /* variable fields, initialized at runtime: */ double tentative_val; }; struct config_string --- 184,197 ---- /* these fields must be set correctly in initial value: */ /* (all but reset_val are constants) */ double *variable; ! double boot_val; double min; double max; GucRealAssignHook assign_hook; GucShowHook show_hook; /* variable fields, initialized at runtime: */ double tentative_val; + double reset_val; }; struct config_string Nur in ./src/test/regress/expected: largeobject_1.out.
---------------------------(end of broadcast)--------------------------- TIP 2: Don't 'kill -9' the postmaster