From 2594cf0e8c04406ffff19b1651c5a406d376657c Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Thu, 7 Apr 2011 00:11:01 -0400 Subject: [PATCH] Revise the API for GUC variable assign hooks. The previous functions of assign hooks are now split between check hooks and assign hooks, where the former can fail but the latter shouldn't. Aside from being conceptually clearer, this approach exposes the "canonicalized" form of the variable value to guc.c without having to do an actual assignment. And that lets us fix the problem recently noted by Bernd Helmle that the auto-tune patch for wal_buffers resulted in bogus log messages about "parameter "wal_buffers" cannot be changed without restarting the server". There may be some speed advantage too, because this design lets hook functions avoid re-parsing variable values when restoring a previous state after a rollback (they can store a pre-parsed representation of the value instead). This patch also resolves a longstanding annoyance about custom error messages from variable assign hooks: they should modify, not appear separately from, guc.c's own message about "invalid parameter value". --- contrib/auth_delay/auth_delay.c | 3 +- contrib/auto_explain/auto_explain.c | 6 + .../pg_stat_statements/pg_stat_statements.c | 4 + contrib/sepgsql/hooks.c | 2 + src/backend/access/transam/xlog.c | 96 +- src/backend/catalog/namespace.c | 45 +- src/backend/commands/tablespace.c | 85 +- src/backend/commands/variable.c | 932 ++++--- src/backend/nls.mk | 5 +- src/backend/replication/syncrep.c | 14 +- src/backend/tcop/postgres.c | 28 +- src/backend/utils/adt/datetime.c | 33 +- src/backend/utils/adt/pg_locale.c | 99 +- src/backend/utils/cache/ts_cache.c | 32 +- src/backend/utils/error/elog.c | 56 + src/backend/utils/mb/mbutils.c | 145 +- src/backend/utils/misc/README | 197 +- src/backend/utils/misc/guc-file.l | 22 +- src/backend/utils/misc/guc.c | 2324 ++++++++++------- src/backend/utils/misc/tzparser.c | 152 +- src/include/commands/variable.h | 39 +- src/include/mb/pg_wchar.h | 3 +- src/include/replication/syncrep.h | 2 +- src/include/tcop/tcopprot.h | 3 +- src/include/tsearch/ts_cache.h | 3 +- src/include/utils/datetime.h | 16 +- src/include/utils/elog.h | 9 + src/include/utils/guc.h | 75 +- src/include/utils/guc_tables.h | 34 +- src/include/utils/pg_locale.h | 16 +- src/include/utils/tzparser.h | 4 +- src/pl/plperl/plperl.c | 8 +- src/pl/plpgsql/src/pl_handler.c | 2 +- src/test/regress/expected/guc.out | 3 +- src/timezone/pgtz.c | 36 +- 35 files changed, 2645 insertions(+), 1888 deletions(-) diff --git a/contrib/auth_delay/auth_delay.c b/contrib/auth_delay/auth_delay.c index 199de9bb39..ca388c4498 100644 --- a/contrib/auth_delay/auth_delay.c +++ b/contrib/auth_delay/auth_delay.c @@ -53,7 +53,7 @@ auth_delay_checks(Port *port, int status) void _PG_init(void) { - /* Define custome GUC variables */ + /* Define custom GUC variables */ DefineCustomIntVariable("auth_delay.milliseconds", "Milliseconds to delay before reporting authentication failure", NULL, @@ -63,6 +63,7 @@ _PG_init(void) PGC_SIGHUP, GUC_UNIT_MS, NULL, + NULL, NULL); /* Install Hooks */ original_client_auth_hook = ClientAuthentication_hook; diff --git a/contrib/auto_explain/auto_explain.c b/contrib/auto_explain/auto_explain.c index 78e604e7bc..b320698748 100644 --- a/contrib/auto_explain/auto_explain.c +++ b/contrib/auto_explain/auto_explain.c @@ -74,6 +74,7 @@ _PG_init(void) PGC_SUSET, GUC_UNIT_MS, NULL, + NULL, NULL); DefineCustomBoolVariable("auto_explain.log_analyze", @@ -84,6 +85,7 @@ _PG_init(void) PGC_SUSET, 0, NULL, + NULL, NULL); DefineCustomBoolVariable("auto_explain.log_verbose", @@ -94,6 +96,7 @@ _PG_init(void) PGC_SUSET, 0, NULL, + NULL, NULL); DefineCustomBoolVariable("auto_explain.log_buffers", @@ -104,6 +107,7 @@ _PG_init(void) PGC_SUSET, 0, NULL, + NULL, NULL); DefineCustomEnumVariable("auto_explain.log_format", @@ -115,6 +119,7 @@ _PG_init(void) PGC_SUSET, 0, NULL, + NULL, NULL); DefineCustomBoolVariable("auto_explain.log_nested_statements", @@ -125,6 +130,7 @@ _PG_init(void) PGC_SUSET, 0, NULL, + NULL, NULL); EmitWarningsOnPlaceholders("auto_explain"); diff --git a/contrib/pg_stat_statements/pg_stat_statements.c b/contrib/pg_stat_statements/pg_stat_statements.c index 0390ec4c8e..87cf8c55cf 100644 --- a/contrib/pg_stat_statements/pg_stat_statements.c +++ b/contrib/pg_stat_statements/pg_stat_statements.c @@ -219,6 +219,7 @@ _PG_init(void) PGC_POSTMASTER, 0, NULL, + NULL, NULL); DefineCustomEnumVariable("pg_stat_statements.track", @@ -230,6 +231,7 @@ _PG_init(void) PGC_SUSET, 0, NULL, + NULL, NULL); DefineCustomBoolVariable("pg_stat_statements.track_utility", @@ -240,6 +242,7 @@ _PG_init(void) PGC_SUSET, 0, NULL, + NULL, NULL); DefineCustomBoolVariable("pg_stat_statements.save", @@ -250,6 +253,7 @@ _PG_init(void) PGC_SIGHUP, 0, NULL, + NULL, NULL); EmitWarningsOnPlaceholders("pg_stat_statements"); diff --git a/contrib/sepgsql/hooks.c b/contrib/sepgsql/hooks.c index 27e85d25b1..5dc8a3ecaa 100644 --- a/contrib/sepgsql/hooks.c +++ b/contrib/sepgsql/hooks.c @@ -394,6 +394,7 @@ _PG_init(void) PGC_SIGHUP, GUC_NOT_IN_SAMPLE, NULL, + NULL, NULL); /* @@ -412,6 +413,7 @@ _PG_init(void) PGC_USERSET, GUC_NOT_IN_SAMPLE, NULL, + NULL, NULL); /* diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index b22e498f8b..b31c79ebbd 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -4887,36 +4887,60 @@ GetSystemIdentifier(void) /* * Auto-tune the number of XLOG buffers. * - * If the user-set value of wal_buffers is -1, we auto-tune to about 3% of - * shared_buffers, with a maximum of one XLOG segment and a minimum of 8 - * blocks (8 was the default value prior to PostgreSQL 9.1, when auto-tuning - * was added). We also clamp manually-set values to at least 4 blocks; prior - * to PostgreSQL 9.1, a minimum of 4 was enforced by guc.c, but since that - * is no longer possible, we just silently treat such values as a request for - * the minimum. + * The preferred setting for wal_buffers is about 3% of shared_buffers, with + * a maximum of one XLOG segment (there is little reason to think that more + * is helpful, at least so long as we force an fsync when switching log files) + * and a minimum of 8 blocks (which was the default value prior to PostgreSQL + * 9.1, when auto-tuning was added). + * + * This should not be called until NBuffers has received its final value. */ -static void -XLOGTuneNumBuffers(void) +static int +XLOGChooseNumBuffers(void) { - int xbuffers = XLOGbuffers; - char buf[32]; + int xbuffers; - if (xbuffers == -1) - { - xbuffers = NBuffers / 32; - if (xbuffers > XLOG_SEG_SIZE / XLOG_BLCKSZ) - xbuffers = XLOG_SEG_SIZE / XLOG_BLCKSZ; - if (xbuffers < 8) - xbuffers = 8; - } - else if (xbuffers < 4) - xbuffers = 4; + xbuffers = NBuffers / 32; + if (xbuffers > XLOG_SEG_SIZE / XLOG_BLCKSZ) + xbuffers = XLOG_SEG_SIZE / XLOG_BLCKSZ; + if (xbuffers < 8) + xbuffers = 8; + return xbuffers; +} - if (xbuffers != XLOGbuffers) +/* + * GUC check_hook for wal_buffers + */ +bool +check_wal_buffers(int *newval, void **extra, GucSource source) +{ + /* + * -1 indicates a request for auto-tune. + */ + if (*newval == -1) { - snprintf(buf, sizeof(buf), "%d", xbuffers); - SetConfigOption("wal_buffers", buf, PGC_POSTMASTER, PGC_S_OVERRIDE); + /* + * If we haven't yet changed the boot_val default of -1, just let it + * be. We'll fix it when XLOGShmemSize is called. + */ + if (XLOGbuffers == -1) + return true; + + /* Otherwise, substitute the auto-tune value */ + *newval = XLOGChooseNumBuffers(); } + + /* + * We clamp manually-set values to at least 4 blocks. Prior to PostgreSQL + * 9.1, a minimum of 4 was enforced by guc.c, but since that is no longer + * the case, we just silently treat such values as a request for the + * minimum. (We could throw an error instead, but that doesn't seem very + * helpful.) + */ + if (*newval < 4) + *newval = 4; + + return true; } /* @@ -4927,8 +4951,19 @@ XLOGShmemSize(void) { Size size; - /* Figure out how many XLOG buffers we need. */ - XLOGTuneNumBuffers(); + /* + * If the value of wal_buffers is -1, use the preferred auto-tune value. + * This isn't an amazingly clean place to do this, but we must wait till + * NBuffers has received its final value, and must do it before using + * the value of XLOGbuffers to do anything important. + */ + if (XLOGbuffers == -1) + { + char buf[32]; + + snprintf(buf, sizeof(buf), "%d", XLOGChooseNumBuffers()); + SetConfigOption("wal_buffers", buf, PGC_POSTMASTER, PGC_S_OVERRIDE); + } Assert(XLOGbuffers > 0); /* XLogCtl */ @@ -8653,12 +8688,9 @@ get_sync_bit(int method) /* * GUC support */ -bool -assign_xlog_sync_method(int new_sync_method, bool doit, GucSource source) +void +assign_xlog_sync_method(int new_sync_method, void *extra) { - if (!doit) - return true; - if (sync_method != new_sync_method) { /* @@ -8678,8 +8710,6 @@ assign_xlog_sync_method(int new_sync_method, bool doit, GucSource source) XLogFileClose(); } } - - return true; } diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c index 77c9805aed..734581e485 100644 --- a/src/backend/catalog/namespace.c +++ b/src/backend/catalog/namespace.c @@ -3468,31 +3468,33 @@ ResetTempTableNamespace(void) * Routines for handling the GUC variable 'search_path'. */ -/* assign_hook: validate new search_path, do extra actions as needed */ -const char * -assign_search_path(const char *newval, bool doit, GucSource source) +/* check_hook: validate new search_path, if possible */ +bool +check_search_path(char **newval, void **extra, GucSource source) { + bool result = true; char *rawname; List *namelist; ListCell *l; /* Need a modifiable copy of string */ - rawname = pstrdup(newval); + rawname = pstrdup(*newval); /* Parse string into list of identifiers */ if (!SplitIdentifierString(rawname, ',', &namelist)) { /* syntax error in name list */ + GUC_check_errdetail("List syntax is invalid."); pfree(rawname); list_free(namelist); - return NULL; + return false; } /* * If we aren't inside a transaction, we cannot do database access so * cannot verify the individual names. Must accept the list on faith. */ - if (source >= PGC_S_INTERACTIVE && IsTransactionState()) + if (IsTransactionState()) { /* * Verify that all the names are either valid namespace names or @@ -3504,7 +3506,7 @@ assign_search_path(const char *newval, bool doit, GucSource source) * DATABASE SET or ALTER USER SET command. It could be that the * intended use of the search path is for some other database, so we * should not error out if it mentions schemas not present in the - * current database. We reduce the message to NOTICE instead. + * current database. We issue a NOTICE instead. */ foreach(l, namelist) { @@ -3516,24 +3518,37 @@ assign_search_path(const char *newval, bool doit, GucSource source) continue; if (!SearchSysCacheExists1(NAMESPACENAME, CStringGetDatum(curname))) - ereport((source == PGC_S_TEST) ? NOTICE : ERROR, - (errcode(ERRCODE_UNDEFINED_SCHEMA), - errmsg("schema \"%s\" does not exist", curname))); + { + if (source == PGC_S_TEST) + ereport(NOTICE, + (errcode(ERRCODE_UNDEFINED_SCHEMA), + errmsg("schema \"%s\" does not exist", curname))); + else + { + GUC_check_errdetail("schema \"%s\" does not exist", curname); + result = false; + break; + } + } } } pfree(rawname); list_free(namelist); + return result; +} + +/* assign_hook: do extra actions as needed */ +void +assign_search_path(const char *newval, void *extra) +{ /* * We mark the path as needing recomputation, but don't do anything until * it's needed. This avoids trying to do database access during GUC - * initialization. + * initialization, or outside a transaction. */ - if (doit) - baseSearchPathValid = false; - - return newval; + baseSearchPathValid = false; } /* diff --git a/src/backend/commands/tablespace.c b/src/backend/commands/tablespace.c index b5a2d9d005..42a704beb1 100644 --- a/src/backend/commands/tablespace.c +++ b/src/backend/commands/tablespace.c @@ -1023,9 +1023,9 @@ AlterTableSpaceOptions(AlterTableSpaceOptionsStmt *stmt) * Routines for handling the GUC variable 'default_tablespace'. */ -/* assign_hook: validate new default_tablespace, do extra actions as needed */ -const char * -assign_default_tablespace(const char *newval, bool doit, GucSource source) +/* check_hook: validate new default_tablespace */ +bool +check_default_tablespace(char **newval, void **extra, GucSource source) { /* * If we aren't inside a transaction, we cannot do database access so @@ -1033,18 +1033,16 @@ assign_default_tablespace(const char *newval, bool doit, GucSource source) */ if (IsTransactionState()) { - if (newval[0] != '\0' && - !OidIsValid(get_tablespace_oid(newval, true))) + if (**newval != '\0' && + !OidIsValid(get_tablespace_oid(*newval, true))) { - ereport(GUC_complaint_elevel(source), - (errcode(ERRCODE_UNDEFINED_OBJECT), - errmsg("tablespace \"%s\" does not exist", - newval))); - return NULL; + GUC_check_errdetail("Tablespace \"%s\" does not exist.", + *newval); + return false; } } - return newval; + return true; } /* @@ -1100,23 +1098,30 @@ GetDefaultTablespace(char relpersistence) * Routines for handling the GUC variable 'temp_tablespaces'. */ -/* assign_hook: validate new temp_tablespaces, do extra actions as needed */ -const char * -assign_temp_tablespaces(const char *newval, bool doit, GucSource source) +typedef struct +{ + int numSpcs; + Oid tblSpcs[1]; /* VARIABLE LENGTH ARRAY */ +} temp_tablespaces_extra; + +/* check_hook: validate new temp_tablespaces */ +bool +check_temp_tablespaces(char **newval, void **extra, GucSource source) { char *rawname; List *namelist; /* Need a modifiable copy of string */ - rawname = pstrdup(newval); + rawname = pstrdup(*newval); /* Parse string into list of identifiers */ if (!SplitIdentifierString(rawname, ',', &namelist)) { /* syntax error in name list */ + GUC_check_errdetail("List syntax is invalid."); pfree(rawname); list_free(namelist); - return NULL; + return false; } /* @@ -1126,17 +1131,13 @@ assign_temp_tablespaces(const char *newval, bool doit, GucSource source) */ if (IsTransactionState()) { - /* - * If we error out below, or if we are called multiple times in one - * transaction, we'll leak a bit of TopTransactionContext memory. - * Doesn't seem worth worrying about. - */ + temp_tablespaces_extra *myextra; Oid *tblSpcs; int numSpcs; ListCell *l; - tblSpcs = (Oid *) MemoryContextAlloc(TopTransactionContext, - list_length(namelist) * sizeof(Oid)); + /* temporary workspace until we are done verifying the list */ + tblSpcs = (Oid *) palloc(list_length(namelist) * sizeof(Oid)); numSpcs = 0; foreach(l, namelist) { @@ -1169,7 +1170,7 @@ assign_temp_tablespaces(const char *newval, bool doit, GucSource source) continue; } - /* Check permissions similarly */ + /* Check permissions, similarly complaining only if interactive */ aclresult = pg_tablespace_aclcheck(curoid, GetUserId(), ACL_CREATE); if (aclresult != ACLCHECK_OK) @@ -1182,17 +1183,41 @@ assign_temp_tablespaces(const char *newval, bool doit, GucSource source) tblSpcs[numSpcs++] = curoid; } - /* If actively "doing it", give the new list to fd.c */ - if (doit) - SetTempTablespaces(tblSpcs, numSpcs); - else - pfree(tblSpcs); + /* Now prepare an "extra" struct for assign_temp_tablespaces */ + myextra = malloc(offsetof(temp_tablespaces_extra, tblSpcs) + + numSpcs * sizeof(Oid)); + if (!myextra) + return false; + myextra->numSpcs = numSpcs; + memcpy(myextra->tblSpcs, tblSpcs, numSpcs * sizeof(Oid)); + *extra = (void *) myextra; + + pfree(tblSpcs); } pfree(rawname); list_free(namelist); - return newval; + return true; +} + +/* assign_hook: do extra actions as needed */ +void +assign_temp_tablespaces(const char *newval, void *extra) +{ + temp_tablespaces_extra *myextra = (temp_tablespaces_extra *) extra; + + /* + * If check_temp_tablespaces was executed inside a transaction, then pass + * the list it made to fd.c. Otherwise, clear fd.c's list; we must be + * still outside a transaction, or else restoring during transaction exit, + * and in either case we can just let the next PrepareTempTablespaces call + * make things sane. + */ + if (myextra) + SetTempTablespaces(myextra->tblSpcs, myextra->numSpcs); + else + SetTempTablespaces(NULL, 0); } /* diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c index 2a61ea3bc7..2cec713089 100644 --- a/src/backend/commands/variable.c +++ b/src/backend/commands/variable.c @@ -33,10 +33,10 @@ */ /* - * assign_datestyle: GUC assign_hook for datestyle + * check_datestyle: GUC check_hook for datestyle */ -const char * -assign_datestyle(const char *value, bool doit, GucSource source) +bool +check_datestyle(char **newval, void **extra, GucSource source) { int newDateStyle = DateStyle; int newDateOrder = DateOrder; @@ -44,23 +44,22 @@ assign_datestyle(const char *value, bool doit, GucSource source) bool have_order = false; bool ok = true; char *rawstring; + int *myextra; char *result; List *elemlist; ListCell *l; /* Need a modifiable copy of string */ - rawstring = pstrdup(value); + rawstring = pstrdup(*newval); /* Parse string into list of identifiers */ if (!SplitIdentifierString(rawstring, ',', &elemlist)) { /* syntax error in list */ + GUC_check_errdetail("List syntax is invalid."); pfree(rawstring); list_free(elemlist); - ereport(GUC_complaint_elevel(source), - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("invalid list syntax for parameter \"datestyle\""))); - return NULL; + return false; } foreach(l, elemlist) @@ -130,38 +129,38 @@ assign_datestyle(const char *value, bool doit, GucSource source) * Easiest way to get the current DEFAULT state is to fetch the * DEFAULT string from guc.c and recursively parse it. * - * We can't simply "return assign_datestyle(...)" because we need + * We can't simply "return check_datestyle(...)" because we need * to handle constructs like "DEFAULT, ISO". */ - int saveDateStyle = DateStyle; - int saveDateOrder = DateOrder; - const char *subval; + char *subval; + void *subextra = NULL; - subval = assign_datestyle(GetConfigOptionResetString("datestyle"), - true, source); - if (!have_style) - newDateStyle = DateStyle; - if (!have_order) - newDateOrder = DateOrder; - DateStyle = saveDateStyle; - DateOrder = saveDateOrder; + subval = strdup(GetConfigOptionResetString("datestyle")); if (!subval) { ok = false; break; } - /* Here we know that our own return value is always malloc'd */ - /* when doit is true */ - free((char *) subval); + if (!check_datestyle(&subval, &subextra, source)) + { + free(subval); + ok = false; + break; + } + myextra = (int *) subextra; + if (!have_style) + newDateStyle = myextra[0]; + if (!have_order) + newDateOrder = myextra[1]; + free(subval); + free(subextra); } else { - ereport(GUC_complaint_elevel(source), - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("unrecognized \"datestyle\" key word: \"%s\"", - tok))); - ok = false; - break; + GUC_check_errdetail("Unrecognized key word: \"%s\".", tok); + pfree(rawstring); + list_free(elemlist); + return false; } } @@ -170,24 +169,16 @@ assign_datestyle(const char *value, bool doit, GucSource source) if (!ok) { - ereport(GUC_complaint_elevel(source), - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("conflicting \"datestyle\" specifications"))); - return NULL; + GUC_check_errdetail("Conflicting \"datestyle\" specifications."); + return false; } - /* - * If we aren't going to do the assignment, just return OK indicator. - */ - if (!doit) - return value; - /* * Prepare the canonical string to return. GUC wants it malloc'd. */ result = (char *) malloc(32); if (!result) - return NULL; + return false; switch (newDateStyle) { @@ -217,14 +208,32 @@ assign_datestyle(const char *value, bool doit, GucSource source) break; } - /* - * Finally, it's safe to assign to the global variables; the assignment - * cannot fail now. - */ - DateStyle = newDateStyle; - DateOrder = newDateOrder; + free(*newval); + *newval = result; - return result; + /* + * Set up the "extra" struct actually used by assign_datestyle. + */ + myextra = (int *) malloc(2 * sizeof(int)); + if (!myextra) + return false; + myextra[0] = newDateStyle; + myextra[1] = newDateOrder; + *extra = (void *) myextra; + + return true; +} + +/* + * assign_datestyle: GUC assign_hook for datestyle + */ +void +assign_datestyle(const char *newval, void *extra) +{ + int *myextra = (int *) extra; + + DateStyle = myextra[0]; + DateOrder = myextra[1]; } @@ -232,22 +241,58 @@ assign_datestyle(const char *value, bool doit, GucSource source) * TIMEZONE */ -/* - * assign_timezone: GUC assign_hook for timezone - */ -const char * -assign_timezone(const char *value, bool doit, GucSource source) +typedef struct { - char *result; + pg_tz *session_timezone; + int CTimeZone; + bool HasCTZSet; +} timezone_extra; + +/* + * check_timezone: GUC check_hook for timezone + */ +bool +check_timezone(char **newval, void **extra, GucSource source) +{ + timezone_extra myextra; char *endptr; double hours; - /* - * Check for INTERVAL 'foo' - */ - if (pg_strncasecmp(value, "interval", 8) == 0) + if (*newval == NULL) { - const char *valueptr = value; + /* + * The boot_val given for TimeZone in guc.c is NULL. When we see this + * we just do nothing. If this isn't overridden from the config file + * then pg_timezone_initialize() will eventually select a default + * value from the environment. This hack has two purposes: to avoid + * wasting cycles loading values that might soon be overridden from + * the config file, and to avoid trying to read the timezone files + * during InitializeGUCOptions(). The latter doesn't work in an + * EXEC_BACKEND subprocess because my_exec_path hasn't been set yet + * and so we can't locate PGSHAREDIR. + */ + Assert(source == PGC_S_DEFAULT); + return true; + } + + /* + * Initialize the "extra" struct that will be passed to assign_timezone. + * We don't want to change any of the three global variables except as + * specified by logic below. To avoid leaking memory during failure + * returns, we set up the struct contents in a local variable, and only + * copy it to *extra at the end. + */ + myextra.session_timezone = session_timezone; + myextra.CTimeZone = CTimeZone; + myextra.HasCTZSet = HasCTZSet; + + if (pg_strncasecmp(*newval, "interval", 8) == 0) + { + /* + * Support INTERVAL 'foo'. This is for SQL spec compliance, not + * because it has any actual real-world usefulness. + */ + const char *valueptr = *newval; char *val; Interval *interval; @@ -255,14 +300,14 @@ assign_timezone(const char *value, bool doit, GucSource source) while (isspace((unsigned char) *valueptr)) valueptr++; if (*valueptr++ != '\'') - return NULL; + return false; val = pstrdup(valueptr); /* Check and remove trailing quote */ endptr = strchr(val, '\''); if (!endptr || endptr[1] != '\0') { pfree(val); - return NULL; + return false; } *endptr = '\0'; @@ -280,31 +325,25 @@ assign_timezone(const char *value, bool doit, GucSource source) pfree(val); if (interval->month != 0) { - ereport(GUC_complaint_elevel(source), - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("invalid interval value for time zone: month not allowed"))); + GUC_check_errdetail("Cannot specify months in time zone interval."); pfree(interval); - return NULL; + return false; } if (interval->day != 0) { - ereport(GUC_complaint_elevel(source), - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("invalid interval value for time zone: day not allowed"))); + GUC_check_errdetail("Cannot specify days in time zone interval."); pfree(interval); - return NULL; + return false; } - if (doit) - { - /* Here we change from SQL to Unix sign convention */ -#ifdef HAVE_INT64_TIMESTAMP - CTimeZone = -(interval->time / USECS_PER_SEC); -#else - CTimeZone = -interval->time; -#endif - HasCTZSet = true; - } + /* Here we change from SQL to Unix sign convention */ +#ifdef HAVE_INT64_TIMESTAMP + myextra.CTimeZone = -(interval->time / USECS_PER_SEC); +#else + myextra.CTimeZone = -interval->time; +#endif + myextra.HasCTZSet = true; + pfree(interval); } else @@ -312,38 +351,12 @@ assign_timezone(const char *value, bool doit, GucSource source) /* * Try it as a numeric number of hours (possibly fractional). */ - hours = strtod(value, &endptr); - if (endptr != value && *endptr == '\0') + hours = strtod(*newval, &endptr); + if (endptr != *newval && *endptr == '\0') { - if (doit) - { - /* Here we change from SQL to Unix sign convention */ - CTimeZone = -hours * SECS_PER_HOUR; - HasCTZSet = true; - } - } - else if (pg_strcasecmp(value, "UNKNOWN") == 0) - { - /* - * UNKNOWN is the value shown as the "default" for TimeZone in - * guc.c. We interpret it as being a complete no-op; we don't - * change the timezone setting. Note that if there is a known - * timezone setting, we will return that name rather than UNKNOWN - * as the canonical spelling. - * - * During GUC initialization, since the timezone library isn't set - * up yet, pg_get_timezone_name will return NULL and we will leave - * the setting as UNKNOWN. If this isn't overridden from the - * config file then pg_timezone_initialize() will eventually - * select a default value from the environment. - */ - if (doit) - { - const char *curzone = pg_get_timezone_name(session_timezone); - - if (curzone) - value = curzone; - } + /* Here we change from SQL to Unix sign convention */ + myextra.CTimeZone = -hours * SECS_PER_HOUR; + myextra.HasCTZSet = true; } else { @@ -352,61 +365,83 @@ assign_timezone(const char *value, bool doit, GucSource source) */ pg_tz *new_tz; - new_tz = pg_tzset(value); + new_tz = pg_tzset(*newval); if (!new_tz) { - ereport(GUC_complaint_elevel(source), - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("unrecognized time zone name: \"%s\"", - value))); - return NULL; + /* Doesn't seem to be any great value in errdetail here */ + return false; } if (!tz_acceptable(new_tz)) { - ereport(GUC_complaint_elevel(source), - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("time zone \"%s\" appears to use leap seconds", - value), - errdetail("PostgreSQL does not support leap seconds."))); - return NULL; + GUC_check_errmsg("time zone \"%s\" appears to use leap seconds", + *newval); + GUC_check_errdetail("PostgreSQL does not support leap seconds."); + return false; } - if (doit) - { - /* Save the changed TZ */ - session_timezone = new_tz; - HasCTZSet = false; - } + myextra.session_timezone = new_tz; + myextra.HasCTZSet = false; } } /* - * If we aren't going to do the assignment, just return OK indicator. + * Prepare the canonical string to return. GUC wants it malloc'd. + * + * Note: the result string should be something that we'd accept as input. + * We use the numeric format for interval cases, because it's simpler to + * reload. In the named-timezone case, *newval is already OK and need not + * be changed; it might not have the canonical casing, but that's taken + * care of by show_timezone. */ - if (!doit) - return value; + if (myextra.HasCTZSet) + { + char *result = (char *) malloc(64); + + if (!result) + return false; + snprintf(result, 64, "%.5f", + (double) (-myextra.CTimeZone) / (double) SECS_PER_HOUR); + free(*newval); + *newval = result; + } /* - * Prepare the canonical string to return. GUC wants it malloc'd. + * Pass back data for assign_timezone to use */ - if (HasCTZSet) - { - result = (char *) malloc(64); - if (!result) - return NULL; - snprintf(result, 64, "%.5f", - (double) (-CTimeZone) / (double) SECS_PER_HOUR); - } - else - result = strdup(value); + *extra = malloc(sizeof(timezone_extra)); + if (!*extra) + return false; + memcpy(*extra, &myextra, sizeof(timezone_extra)); - return result; + return true; +} + +/* + * assign_timezone: GUC assign_hook for timezone + */ +void +assign_timezone(const char *newval, void *extra) +{ + timezone_extra *myextra = (timezone_extra *) extra; + + /* Do nothing for the boot_val default of NULL */ + if (!myextra) + return; + + session_timezone = myextra->session_timezone; + CTimeZone = myextra->CTimeZone; + HasCTZSet = myextra->HasCTZSet; } /* * show_timezone: GUC show_hook for timezone + * + * We wouldn't need this, except that historically interval values have been + * shown without an INTERVAL prefix, so the display format isn't what would + * be accepted as input. Otherwise we could have check_timezone return the + * preferred string to begin with. */ const char * show_timezone(void) @@ -447,83 +482,66 @@ show_timezone(void) */ /* - * assign_log_timezone: GUC assign_hook for log_timezone + * check_log_timezone: GUC check_hook for log_timezone */ -const char * -assign_log_timezone(const char *value, bool doit, GucSource source) +bool +check_log_timezone(char **newval, void **extra, GucSource source) { - char *result; + pg_tz *new_tz; - if (pg_strcasecmp(value, "UNKNOWN") == 0) + if (*newval == NULL) { /* - * UNKNOWN is the value shown as the "default" for log_timezone in - * guc.c. We interpret it as being a complete no-op; we don't change - * the timezone setting. Note that if there is a known timezone - * setting, we will return that name rather than UNKNOWN as the - * canonical spelling. - * - * During GUC initialization, since the timezone library isn't set up - * yet, pg_get_timezone_name will return NULL and we will leave the - * setting as UNKNOWN. If this isn't overridden from the config file - * then pg_timezone_initialize() will eventually select a default + * The boot_val given for log_timezone in guc.c is NULL. When we see + * this we just do nothing. If this isn't overridden from the config + * file then pg_timezone_initialize() will eventually select a default * value from the environment. */ - if (doit) - { - const char *curzone = pg_get_timezone_name(log_timezone); - - if (curzone) - value = curzone; - } + Assert(source == PGC_S_DEFAULT); + return true; } - else + + /* + * Otherwise assume it is a timezone name, and try to load it. + */ + new_tz = pg_tzset(*newval); + + if (!new_tz) { - /* - * Otherwise assume it is a timezone name, and try to load it. - */ - pg_tz *new_tz; + /* Doesn't seem to be any great value in errdetail here */ + return false; + } - new_tz = pg_tzset(value); - - if (!new_tz) - { - ereport(GUC_complaint_elevel(source), - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("unrecognized time zone name: \"%s\"", - value))); - return NULL; - } - - if (!tz_acceptable(new_tz)) - { - ereport(GUC_complaint_elevel(source), - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("time zone \"%s\" appears to use leap seconds", - value), - errdetail("PostgreSQL does not support leap seconds."))); - return NULL; - } - - if (doit) - { - /* Save the changed TZ */ - log_timezone = new_tz; - } + if (!tz_acceptable(new_tz)) + { + GUC_check_errmsg("time zone \"%s\" appears to use leap seconds", + *newval); + GUC_check_errdetail("PostgreSQL does not support leap seconds."); + return false; } /* - * If we aren't going to do the assignment, just return OK indicator. + * Pass back data for assign_log_timezone to use */ - if (!doit) - return value; + *extra = malloc(sizeof(pg_tz *)); + if (!*extra) + return false; + memcpy(*extra, &new_tz, sizeof(pg_tz *)); - /* - * Prepare the canonical string to return. GUC wants it malloc'd. - */ - result = strdup(value); + return true; +} - return result; +/* + * assign_log_timezone: GUC assign_hook for log_timezone + */ +void +assign_log_timezone(const char *newval, void *extra) +{ + /* Do nothing for the boot_val default of NULL */ + if (!extra) + return; + + log_timezone = *((pg_tz **) extra); } /* @@ -548,38 +566,33 @@ show_log_timezone(void) * * We allow idempotent changes (r/w -> r/w and r/o -> r/o) at any time, and * we also always allow changes from read-write to read-only. However, - * read-only to read-write may be changed only when source == PGC_S_OVERRIDE - * (i.e. we're aborting a read only transaction and restoring the previous - * setting) or in a top-level transaction that has not yet taken an initial - * snapshot. + * read-only may be changed to read-write only when in a top-level transaction + * that has not yet taken an initial snapshot. Can't do it in a hot standby + * slave, either. */ bool -assign_transaction_read_only(bool newval, bool doit, GucSource source) +check_transaction_read_only(bool *newval, void **extra, GucSource source) { - if (source != PGC_S_OVERRIDE && newval == false && XactReadOnly) + if (*newval == false && XactReadOnly) { /* Can't go to r/w mode inside a r/o transaction */ if (IsSubTransaction()) { - ereport(GUC_complaint_elevel(source), - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("cannot set transaction read-write mode inside a read-only transaction"))); + GUC_check_errcode(ERRCODE_ACTIVE_SQL_TRANSACTION); + GUC_check_errmsg("cannot set transaction read-write mode inside a read-only transaction"); return false; } /* Top level transaction can't change to r/w after first snapshot. */ if (FirstSnapshotSet) { - ereport(GUC_complaint_elevel(source), - (errcode(ERRCODE_ACTIVE_SQL_TRANSACTION), - errmsg("transaction read-write mode must be set before any query"))); + GUC_check_errcode(ERRCODE_ACTIVE_SQL_TRANSACTION); + GUC_check_errmsg("transaction read-write mode must be set before any query"); return false; } /* Can't go to r/w mode while recovery is still active */ if (RecoveryInProgress()) { - ereport(GUC_complaint_elevel(source), - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("cannot set transaction read-write mode during recovery"))); + GUC_check_errmsg("cannot set transaction read-write mode during recovery"); return false; } } @@ -591,76 +604,78 @@ assign_transaction_read_only(bool newval, bool doit, GucSource source) * SET TRANSACTION ISOLATION LEVEL * * We allow idempotent changes at any time, but otherwise this can only be - * changed from a toplevel transaction that has not yet taken a snapshot, or - * when source == PGC_S_OVERRIDE (i.e. we're aborting a transaction and - * restoring the previously set value). + * changed in a toplevel transaction that has not yet taken a snapshot. */ -const char * -assign_XactIsoLevel(const char *value, bool doit, GucSource source) +bool +check_XactIsoLevel(char **newval, void **extra, GucSource source) { - /* source == PGC_S_OVERRIDE means do it anyway, eg at xact abort */ - if (source != PGC_S_OVERRIDE && strcmp(value, XactIsoLevel_string) != 0) + int newXactIsoLevel; + + if (strcmp(*newval, "serializable") == 0) + { + newXactIsoLevel = XACT_SERIALIZABLE; + } + else if (strcmp(*newval, "repeatable read") == 0) + { + newXactIsoLevel = XACT_REPEATABLE_READ; + } + else if (strcmp(*newval, "read committed") == 0) + { + newXactIsoLevel = XACT_READ_COMMITTED; + } + else if (strcmp(*newval, "read uncommitted") == 0) + { + newXactIsoLevel = XACT_READ_UNCOMMITTED; + } + else if (strcmp(*newval, "default") == 0) + { + newXactIsoLevel = DefaultXactIsoLevel; + } + else + return false; + + if (newXactIsoLevel != XactIsoLevel) { if (FirstSnapshotSet) { - ereport(GUC_complaint_elevel(source), - (errcode(ERRCODE_ACTIVE_SQL_TRANSACTION), - errmsg("SET TRANSACTION ISOLATION LEVEL must be called before any query"))); - return NULL; + GUC_check_errcode(ERRCODE_ACTIVE_SQL_TRANSACTION); + GUC_check_errmsg("SET TRANSACTION ISOLATION LEVEL must be called before any query"); + return false; } /* We ignore a subtransaction setting it to the existing value. */ if (IsSubTransaction()) { - ereport(GUC_complaint_elevel(source), - (errcode(ERRCODE_ACTIVE_SQL_TRANSACTION), - errmsg("SET TRANSACTION ISOLATION LEVEL must not be called in a subtransaction"))); - return NULL; + GUC_check_errcode(ERRCODE_ACTIVE_SQL_TRANSACTION); + GUC_check_errmsg("SET TRANSACTION ISOLATION LEVEL must not be called in a subtransaction"); + return false; } /* Can't go to serializable mode while recovery is still active */ - if (RecoveryInProgress() && strcmp(value, "serializable") == 0) + if (newXactIsoLevel == XACT_SERIALIZABLE && RecoveryInProgress()) { - ereport(GUC_complaint_elevel(source), - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("cannot use serializable mode in a hot standby"), - errhint("You can use REPEATABLE READ instead."))); + GUC_check_errmsg("cannot use serializable mode in a hot standby"); + GUC_check_errhint("You can use REPEATABLE READ instead."); return false; } } - if (strcmp(value, "serializable") == 0) - { - if (doit) - XactIsoLevel = XACT_SERIALIZABLE; - } - else if (strcmp(value, "repeatable read") == 0) - { - if (doit) - XactIsoLevel = XACT_REPEATABLE_READ; - } - else if (strcmp(value, "read committed") == 0) - { - if (doit) - XactIsoLevel = XACT_READ_COMMITTED; - } - else if (strcmp(value, "read uncommitted") == 0) - { - if (doit) - XactIsoLevel = XACT_READ_UNCOMMITTED; - } - else if (strcmp(value, "default") == 0) - { - if (doit) - XactIsoLevel = DefaultXactIsoLevel; - } - else - return NULL; + *extra = malloc(sizeof(int)); + if (!*extra) + return false; + *((int *) *extra) = newXactIsoLevel; - return value; + return true; +} + +void +assign_XactIsoLevel(const char *newval, void *extra) +{ + XactIsoLevel = *((int *) extra); } const char * show_XactIsoLevel(void) { + /* We need this because we don't want to show "default". */ switch (XactIsoLevel) { case XACT_READ_UNCOMMITTED: @@ -681,25 +696,18 @@ show_XactIsoLevel(void) */ bool -assign_transaction_deferrable(bool newval, bool doit, GucSource source) +check_transaction_deferrable(bool *newval, void **extra, GucSource source) { - /* source == PGC_S_OVERRIDE means do it anyway, eg at xact abort */ - if (source == PGC_S_OVERRIDE) - return true; - if (IsSubTransaction()) { - ereport(GUC_complaint_elevel(source), - (errcode(ERRCODE_ACTIVE_SQL_TRANSACTION), - errmsg("SET TRANSACTION [NOT] DEFERRABLE cannot be called within a subtransaction"))); + GUC_check_errcode(ERRCODE_ACTIVE_SQL_TRANSACTION); + GUC_check_errmsg("SET TRANSACTION [NOT] DEFERRABLE cannot be called within a subtransaction"); return false; } - if (FirstSnapshotSet) { - ereport(GUC_complaint_elevel(source), - (errcode(ERRCODE_ACTIVE_SQL_TRANSACTION), - errmsg("SET TRANSACTION [NOT] DEFERRABLE must be called before any query"))); + GUC_check_errcode(ERRCODE_ACTIVE_SQL_TRANSACTION); + GUC_check_errmsg("SET TRANSACTION [NOT] DEFERRABLE must be called before any query"); return false; } @@ -708,17 +716,34 @@ assign_transaction_deferrable(bool newval, bool doit, GucSource source) /* * Random number seed + * + * We can't roll back the random sequence on error, and we don't want + * config file reloads to affect it, so we only want interactive SET SEED + * commands to set it. We use the "extra" storage to ensure that rollbacks + * don't try to do the operation again. */ bool -assign_random_seed(double value, bool doit, GucSource source) +check_random_seed(double *newval, void **extra, GucSource source) { - /* Can't really roll back on error, so ignore non-interactive setting */ - if (doit && source >= PGC_S_INTERACTIVE) - DirectFunctionCall1(setseed, Float8GetDatum(value)); + *extra = malloc(sizeof(int)); + if (!*extra) + return false; + /* Arm the assign only if source of value is an interactive SET */ + *((int *) *extra) = (source >= PGC_S_INTERACTIVE); + return true; } +void +assign_random_seed(double newval, void *extra) +{ + /* We'll do this at most once for any setting of the GUC variable */ + if (*((int *) extra)) + DirectFunctionCall1(setseed, Float8GetDatum(newval)); + *((int *) extra) = 0; +} + const char * show_random_seed(void) { @@ -727,214 +752,189 @@ show_random_seed(void) /* - * encoding handling functions + * SET CLIENT_ENCODING */ -const char * -assign_client_encoding(const char *value, bool doit, GucSource source) +bool +check_client_encoding(char **newval, void **extra, GucSource source) { int encoding; - encoding = pg_valid_client_encoding(value); + /* Look up the encoding by name */ + encoding = pg_valid_client_encoding(*newval); if (encoding < 0) - return NULL; + return false; /* - * Note: if we are in startup phase then SetClientEncoding may not be able - * to really set the encoding. In this case we will assume that the - * encoding is okay, and InitializeClientEncoding() will fix things once - * initialization is complete. + * If we are not within a transaction then PrepareClientEncoding will not + * be able to look up the necessary conversion procs. If we are still + * starting up, it will return "OK" anyway, and InitializeClientEncoding + * will fix things once initialization is far enough along. After + * startup, we'll fail. This would only happen if someone tries to change + * client_encoding in postgresql.conf and then SIGHUP existing sessions. + * It seems like a bad idea for client_encoding to change that way anyhow, + * so we don't go out of our way to support it. + * + * Note: in the postmaster, or any other process that never calls + * InitializeClientEncoding, PrepareClientEncoding will always succeed, + * and so will SetClientEncoding; but they won't do anything, which is OK. */ - if (SetClientEncoding(encoding, doit) < 0) + if (PrepareClientEncoding(encoding) < 0) { - ereport(GUC_complaint_elevel(source), - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("conversion between %s and %s is not supported", - value, GetDatabaseEncodingName()))); - return NULL; + if (IsTransactionState()) + { + /* Must be a genuine no-such-conversion problem */ + GUC_check_errcode(ERRCODE_FEATURE_NOT_SUPPORTED); + GUC_check_errdetail("Conversion between %s and %s is not supported.", + pg_encoding_to_char(encoding), + GetDatabaseEncodingName()); + } + else + { + /* Provide a useful complaint */ + GUC_check_errdetail("Cannot change \"client_encoding\" now."); + } + return false; } - return value; + + /* + * Return the encoding's canonical name, and save its ID in *extra. + */ + free(*newval); + *newval = strdup(pg_encoding_to_char(encoding)); + if (!*newval) + return false; + + *extra = malloc(sizeof(int)); + if (!*extra) + return false; + *((int *) *extra) = encoding; + + return true; +} + +void +assign_client_encoding(const char *newval, void *extra) +{ + int encoding = *((int *) extra); + + /* We do not expect an error if PrepareClientEncoding succeeded */ + if (SetClientEncoding(encoding) < 0) + elog(LOG, "SetClientEncoding(%d) failed", encoding); } /* * SET SESSION AUTHORIZATION - * - * When resetting session auth after an error, we can't expect to do catalog - * lookups. Hence, the stored form of the value must provide a numeric oid - * that can be re-used directly. We store the string in the form of - * NAMEDATALEN 'x's, followed by T or F to indicate superuserness, followed - * by the numeric oid, followed by a comma, followed by the role name. - * This cannot be confused with a plain role name because of the NAMEDATALEN - * limit on names, so we can tell whether we're being passed an initial - * role name or a saved/restored value. (NOTE: we rely on guc.c to have - * properly truncated any incoming value, but not to truncate already-stored - * values. See GUC_IS_NAME processing.) */ -extern char *session_authorization_string; /* in guc.c */ -const char * -assign_session_authorization(const char *value, bool doit, GucSource source) +typedef struct { - Oid roleid = InvalidOid; - bool is_superuser = false; - const char *actual_rolename = NULL; - char *result; + /* This is the "extra" state for both SESSION AUTHORIZATION and ROLE */ + Oid roleid; + bool is_superuser; +} role_auth_extra; - if (strspn(value, "x") == NAMEDATALEN && - (value[NAMEDATALEN] == 'T' || value[NAMEDATALEN] == 'F')) +bool +check_session_authorization(char **newval, void **extra, GucSource source) +{ + HeapTuple roleTup; + Oid roleid; + bool is_superuser; + role_auth_extra *myextra; + + /* Do nothing for the boot_val default of NULL */ + if (*newval == NULL) + return true; + + if (!IsTransactionState()) { - /* might be a saved userid string */ - Oid savedoid; - char *endptr; - - savedoid = (Oid) strtoul(value + NAMEDATALEN + 1, &endptr, 10); - - if (endptr != value + NAMEDATALEN + 1 && *endptr == ',') - { - /* syntactically valid, so break out the data */ - roleid = savedoid; - is_superuser = (value[NAMEDATALEN] == 'T'); - actual_rolename = endptr + 1; - } + /* + * Can't do catalog lookups, so fail. The result of this is that + * session_authorization cannot be set in postgresql.conf, which + * seems like a good thing anyway, so we don't work hard to avoid it. + */ + return false; } - if (roleid == InvalidOid) + /* Look up the username */ + roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(*newval)); + if (!HeapTupleIsValid(roleTup)) { - /* not a saved ID, so look it up */ - HeapTuple roleTup; - - if (!IsTransactionState()) - { - /* - * Can't do catalog lookups, so fail. The upshot of this is that - * session_authorization cannot be set in postgresql.conf, which - * seems like a good thing anyway. - */ - return NULL; - } - - roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(value)); - if (!HeapTupleIsValid(roleTup)) - { - ereport(GUC_complaint_elevel(source), - (errcode(ERRCODE_UNDEFINED_OBJECT), - errmsg("role \"%s\" does not exist", value))); - return NULL; - } - - roleid = HeapTupleGetOid(roleTup); - is_superuser = ((Form_pg_authid) GETSTRUCT(roleTup))->rolsuper; - actual_rolename = value; - - ReleaseSysCache(roleTup); + GUC_check_errmsg("role \"%s\" does not exist", *newval); + return false; } - if (doit) - SetSessionAuthorization(roleid, is_superuser); + roleid = HeapTupleGetOid(roleTup); + is_superuser = ((Form_pg_authid) GETSTRUCT(roleTup))->rolsuper; - result = (char *) malloc(NAMEDATALEN + 32 + strlen(actual_rolename)); - if (!result) - return NULL; + ReleaseSysCache(roleTup); - memset(result, 'x', NAMEDATALEN); + /* Set up "extra" struct for assign_session_authorization to use */ + myextra = (role_auth_extra *) malloc(sizeof(role_auth_extra)); + if (!myextra) + return false; + myextra->roleid = roleid; + myextra->is_superuser = is_superuser; + *extra = (void *) myextra; - sprintf(result + NAMEDATALEN, "%c%u,%s", - is_superuser ? 'T' : 'F', - roleid, - actual_rolename); - - return result; + return true; } -const char * -show_session_authorization(void) +void +assign_session_authorization(const char *newval, void *extra) { - /* - * Extract the user name from the stored string; see - * assign_session_authorization - */ - const char *value = session_authorization_string; - Oid savedoid; - char *endptr; + role_auth_extra *myextra = (role_auth_extra *) extra; - /* If session_authorization hasn't been set in this process, return "" */ - if (value == NULL || value[0] == '\0') - return ""; + /* Do nothing for the boot_val default of NULL */ + if (!myextra) + return; - Assert(strspn(value, "x") == NAMEDATALEN && - (value[NAMEDATALEN] == 'T' || value[NAMEDATALEN] == 'F')); - - savedoid = (Oid) strtoul(value + NAMEDATALEN + 1, &endptr, 10); - - Assert(endptr != value + NAMEDATALEN + 1 && *endptr == ','); - - return endptr + 1; + SetSessionAuthorization(myextra->roleid, myextra->is_superuser); } /* * SET ROLE * - * When resetting session auth after an error, we can't expect to do catalog - * lookups. Hence, the stored form of the value must provide a numeric oid - * that can be re-used directly. We implement this exactly like SET - * SESSION AUTHORIZATION. - * * The SQL spec requires "SET ROLE NONE" to unset the role, so we hardwire - * a translation of "none" to InvalidOid. + * a translation of "none" to InvalidOid. Otherwise this is much like + * SET SESSION AUTHORIZATION. */ extern char *role_string; /* in guc.c */ -const char * -assign_role(const char *value, bool doit, GucSource source) +bool +check_role(char **newval, void **extra, GucSource source) { - Oid roleid = InvalidOid; - bool is_superuser = false; - const char *actual_rolename = value; - char *result; + HeapTuple roleTup; + Oid roleid; + bool is_superuser; + role_auth_extra *myextra; - if (strspn(value, "x") == NAMEDATALEN && - (value[NAMEDATALEN] == 'T' || value[NAMEDATALEN] == 'F')) + if (strcmp(*newval, "none") == 0) { - /* might be a saved userid string */ - Oid savedoid; - char *endptr; - - savedoid = (Oid) strtoul(value + NAMEDATALEN + 1, &endptr, 10); - - if (endptr != value + NAMEDATALEN + 1 && *endptr == ',') - { - /* syntactically valid, so break out the data */ - roleid = savedoid; - is_superuser = (value[NAMEDATALEN] == 'T'); - actual_rolename = endptr + 1; - } + /* hardwired translation */ + roleid = InvalidOid; + is_superuser = false; } - - if (roleid == InvalidOid && - strcmp(actual_rolename, "none") != 0) + else { - /* not a saved ID, so look it up */ - HeapTuple roleTup; - if (!IsTransactionState()) { /* - * Can't do catalog lookups, so fail. The upshot of this is that + * Can't do catalog lookups, so fail. The result of this is that * role cannot be set in postgresql.conf, which seems like a good - * thing anyway. + * thing anyway, so we don't work hard to avoid it. */ - return NULL; + return false; } - roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(value)); + /* Look up the username */ + roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(*newval)); if (!HeapTupleIsValid(roleTup)) { - ereport(GUC_complaint_elevel(source), - (errcode(ERRCODE_UNDEFINED_OBJECT), - errmsg("role \"%s\" does not exist", value))); - return NULL; + GUC_check_errmsg("role \"%s\" does not exist", *newval); + return false; } roleid = HeapTupleGetOid(roleTup); @@ -947,61 +947,45 @@ assign_role(const char *value, bool doit, GucSource source) */ if (!is_member_of_role(GetSessionUserId(), roleid)) { - ereport(GUC_complaint_elevel(source), - (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - errmsg("permission denied to set role \"%s\"", - value))); - return NULL; + GUC_check_errcode(ERRCODE_INSUFFICIENT_PRIVILEGE); + GUC_check_errmsg("permission denied to set role \"%s\"", + *newval); + return false; } } - if (doit) - SetCurrentRoleId(roleid, is_superuser); + /* Set up "extra" struct for assign_role to use */ + myextra = (role_auth_extra *) malloc(sizeof(role_auth_extra)); + if (!myextra) + return false; + myextra->roleid = roleid; + myextra->is_superuser = is_superuser; + *extra = (void *) myextra; - result = (char *) malloc(NAMEDATALEN + 32 + strlen(actual_rolename)); - if (!result) - return NULL; + return true; +} - memset(result, 'x', NAMEDATALEN); +void +assign_role(const char *newval, void *extra) +{ + role_auth_extra *myextra = (role_auth_extra *) extra; - sprintf(result + NAMEDATALEN, "%c%u,%s", - is_superuser ? 'T' : 'F', - roleid, - actual_rolename); - - return result; + SetCurrentRoleId(myextra->roleid, myextra->is_superuser); } const char * show_role(void) { /* - * Extract the role name from the stored string; see assign_role + * Check whether SET ROLE is active; if not return "none". This is a + * kluge to deal with the fact that SET SESSION AUTHORIZATION logically + * resets SET ROLE to NONE, but we cannot set the GUC role variable from + * assign_session_authorization (because we haven't got enough info to + * call set_config_option). */ - const char *value = role_string; - Oid savedoid; - char *endptr; - - /* This special case only applies if no SET ROLE has been done */ - if (value == NULL || strcmp(value, "none") == 0) + if (!OidIsValid(GetCurrentRoleId())) return "none"; - Assert(strspn(value, "x") == NAMEDATALEN && - (value[NAMEDATALEN] == 'T' || value[NAMEDATALEN] == 'F')); - - savedoid = (Oid) strtoul(value + NAMEDATALEN + 1, &endptr, 10); - - Assert(endptr != value + NAMEDATALEN + 1 && *endptr == ','); - - /* - * Check that the stored string still matches the effective setting, else - * return "none". This is a kluge to deal with the fact that SET SESSION - * AUTHORIZATION logically resets SET ROLE to NONE, but we cannot set the - * GUC role variable from assign_session_authorization (because we haven't - * got enough info to call set_config_option). - */ - if (savedoid != GetCurrentRoleId()) - return "none"; - - return endptr + 1; + /* Otherwise we can just use the GUC string */ + return role_string ? role_string : "none"; } diff --git a/src/backend/nls.mk b/src/backend/nls.mk index eec8a5e2b0..cf09335a01 100644 --- a/src/backend/nls.mk +++ b/src/backend/nls.mk @@ -2,7 +2,10 @@ CATALOG_NAME := postgres AVAIL_LANGUAGES := de es fr ja pt_BR tr GETTEXT_FILES := + gettext-files -GETTEXT_TRIGGERS:= _ errmsg errmsg_plural:1,2 errdetail errdetail_log errdetail_plural:1,2 errhint errcontext write_stderr yyerror parser_yyerror +GETTEXT_TRIGGERS:= _ errmsg errmsg_plural:1,2 errdetail errdetail_log \ + errdetail_plural:1,2 errhint errcontext \ + GUC_check_errmsg GUC_check_errdetail GUC_check_errhint \ + write_stderr yyerror parser_yyerror gettext-files: distprep find $(srcdir)/ $(srcdir)/../port/ -name '*.c' -print >$@ diff --git a/src/backend/replication/syncrep.c b/src/backend/replication/syncrep.c index 0d33814651..192aac9aea 100644 --- a/src/backend/replication/syncrep.c +++ b/src/backend/replication/syncrep.c @@ -639,25 +639,23 @@ SyncRepQueueIsOrderedByLSN(void) * =========================================================== */ -const char * -assign_synchronous_standby_names(const char *newval, bool doit, GucSource source) +bool +check_synchronous_standby_names(char **newval, void **extra, GucSource source) { char *rawstring; List *elemlist; /* Need a modifiable copy of string */ - rawstring = pstrdup(newval); + rawstring = pstrdup(*newval); /* Parse string into list of identifiers */ if (!SplitIdentifierString(rawstring, ',', &elemlist)) { /* syntax error in list */ + GUC_check_errdetail("List syntax is invalid."); pfree(rawstring); list_free(elemlist); - ereport(GUC_complaint_elevel(source), - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("invalid list syntax for parameter \"synchronous_standby_names\""))); - return NULL; + return false; } /* @@ -671,5 +669,5 @@ assign_synchronous_standby_names(const char *newval, bool doit, GucSource source pfree(rawstring); list_free(elemlist); - return newval; + return true; } diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c index 79c0f68966..cc7945aeb6 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -2804,7 +2804,8 @@ RecoveryConflictInterrupt(ProcSignalReason reason) break; default: - elog(FATAL, "Unknown conflict mode"); + elog(FATAL, "unrecognized conflict mode: %d", + (int) reason); } Assert(RecoveryConflictPending && (QueryCancelPending || ProcDiePending)); @@ -3062,27 +3063,32 @@ check_stack_depth(void) #endif /* IA64 */ } -/* GUC assign hook for max_stack_depth */ +/* GUC check hook for max_stack_depth */ bool -assign_max_stack_depth(int newval, bool doit, GucSource source) +check_max_stack_depth(int *newval, void **extra, GucSource source) { - long newval_bytes = newval * 1024L; + long newval_bytes = *newval * 1024L; long stack_rlimit = get_stack_depth_rlimit(); if (stack_rlimit > 0 && newval_bytes > stack_rlimit - STACK_DEPTH_SLOP) { - ereport(GUC_complaint_elevel(source), - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("\"max_stack_depth\" must not exceed %ldkB", - (stack_rlimit - STACK_DEPTH_SLOP) / 1024L), - errhint("Increase the platform's stack depth limit via \"ulimit -s\" or local equivalent."))); + GUC_check_errdetail("\"max_stack_depth\" must not exceed %ldkB.", + (stack_rlimit - STACK_DEPTH_SLOP) / 1024L); + GUC_check_errhint("Increase the platform's stack depth limit via \"ulimit -s\" or local equivalent."); return false; } - if (doit) - max_stack_depth_bytes = newval_bytes; return true; } +/* GUC assign hook for max_stack_depth */ +void +assign_max_stack_depth(int newval, void *extra) +{ + long newval_bytes = newval * 1024L; + + max_stack_depth_bytes = newval_bytes; +} + /* * set_debug_options --- apply "-d N" command line option diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c index f0fe2e31a2..0410b8384e 100644 --- a/src/backend/utils/adt/datetime.c +++ b/src/backend/utils/adt/datetime.c @@ -4140,20 +4140,17 @@ CheckDateTokenTables(void) /* * This function gets called during timezone config file load or reload * to create the final array of timezone tokens. The argument array - * is already sorted in name order. This data is in a temporary memory - * context and must be copied to somewhere permanent. + * is already sorted in name order. The data is converted to datetkn + * format and installed in *tbl, which must be allocated by the caller. */ void -InstallTimeZoneAbbrevs(tzEntry *abbrevs, int n) +ConvertTimeZoneAbbrevs(TimeZoneAbbrevTable *tbl, + struct tzEntry *abbrevs, int n) { - datetkn *newtbl; + datetkn *newtbl = tbl->abbrevs; int i; - /* - * Copy the data into TopMemoryContext and convert to datetkn format. - */ - newtbl = (datetkn *) MemoryContextAlloc(TopMemoryContext, - n * sizeof(datetkn)); + tbl->numabbrevs = n; for (i = 0; i < n; i++) { strncpy(newtbl[i].token, abbrevs[i].abbrev, TOKMAXLEN); @@ -4163,12 +4160,20 @@ InstallTimeZoneAbbrevs(tzEntry *abbrevs, int n) /* Check the ordering, if testing */ Assert(CheckDateTokenTable("timezone offset", newtbl, n)); +} - /* Now safe to replace existing table (if any) */ - if (timezonetktbl) - pfree(timezonetktbl); - timezonetktbl = newtbl; - sztimezonetktbl = n; +/* + * Install a TimeZoneAbbrevTable as the active table. + * + * Caller is responsible that the passed table doesn't go away while in use. + */ +void +InstallTimeZoneAbbrevs(TimeZoneAbbrevTable *tbl) +{ + int i; + + timezonetktbl = tbl->abbrevs; + sztimezonetktbl = tbl->numabbrevs; /* clear date cache in case it contains any stale timezone names */ for (i = 0; i < MAXDATEFIELDS; i++) diff --git a/src/backend/utils/adt/pg_locale.c b/src/backend/utils/adt/pg_locale.c index 163856d5b1..cbf74a07f2 100644 --- a/src/backend/utils/adt/pg_locale.c +++ b/src/backend/utils/adt/pg_locale.c @@ -236,52 +236,53 @@ check_locale(int category, const char *value) return ret; } -/* GUC assign hooks */ /* - * This is common code for several locale categories. This doesn't - * actually set the locale permanently, it only tests if the locale is - * valid. (See explanation at the top of this file.) + * GUC check/assign hooks + * + * For most locale categories, the assign hook doesn't actually set the locale + * permanently, just reset flags so that the next use will cache the + * appropriate values. (See explanation at the top of this file.) * * Note: we accept value = "" as selecting the postmaster's environment * value, whatever it was (so long as the environment setting is legal). * This will have been locked down by an earlier call to pg_perm_setlocale. */ -static const char * -locale_xxx_assign(int category, const char *value, bool doit, GucSource source) +bool +check_locale_monetary(char **newval, void **extra, GucSource source) { - if (!check_locale(category, value)) - value = NULL; /* set failure return marker */ - - /* need to reload cache next time? */ - if (doit && value != NULL) - { - CurrentLocaleConvValid = false; - CurrentLCTimeValid = false; - } - - return value; + return check_locale(LC_MONETARY, *newval); } - -const char * -locale_monetary_assign(const char *value, bool doit, GucSource source) +void +assign_locale_monetary(const char *newval, void *extra) { - return locale_xxx_assign(LC_MONETARY, value, doit, source); + CurrentLocaleConvValid = false; } -const char * -locale_numeric_assign(const char *value, bool doit, GucSource source) +bool +check_locale_numeric(char **newval, void **extra, GucSource source) { - return locale_xxx_assign(LC_NUMERIC, value, doit, source); + return check_locale(LC_NUMERIC, *newval); } -const char * -locale_time_assign(const char *value, bool doit, GucSource source) +void +assign_locale_numeric(const char *newval, void *extra) { - return locale_xxx_assign(LC_TIME, value, doit, source); + CurrentLocaleConvValid = false; } +bool +check_locale_time(char **newval, void **extra, GucSource source) +{ + return check_locale(LC_TIME, *newval); +} + +void +assign_locale_time(const char *newval, void *extra) +{ + CurrentLCTimeValid = false; +} /* * We allow LC_MESSAGES to actually be set globally. @@ -293,31 +294,39 @@ locale_time_assign(const char *value, bool doit, GucSource source) * The idea there is just to accept the environment setting *if possible* * during startup, until we can read the proper value from postgresql.conf. */ -const char * -locale_messages_assign(const char *value, bool doit, GucSource source) +bool +check_locale_messages(char **newval, void **extra, GucSource source) { - if (*value == '\0' && source != PGC_S_DEFAULT) - return NULL; + if (**newval == '\0') + { + if (source == PGC_S_DEFAULT) + return true; + else + return false; + } /* * LC_MESSAGES category does not exist everywhere, but accept it anyway * - * On Windows, we can't even check the value, so the non-doit case is a - * no-op + * On Windows, we can't even check the value, so accept blindly + */ +#if defined(LC_MESSAGES) && !defined(WIN32) + return check_locale(LC_MESSAGES, *newval); +#else + return true; +#endif +} + +void +assign_locale_messages(const char *newval, void *extra) +{ + /* + * LC_MESSAGES category does not exist everywhere, but accept it anyway. + * We ignore failure, as per comment above. */ #ifdef LC_MESSAGES - if (doit) - { - if (!pg_perm_setlocale(LC_MESSAGES, value)) - if (source != PGC_S_DEFAULT) - return NULL; - } -#ifndef WIN32 - else - value = locale_xxx_assign(LC_MESSAGES, value, false, source); -#endif /* WIN32 */ -#endif /* LC_MESSAGES */ - return value; + (void) pg_perm_setlocale(LC_MESSAGES, newval); +#endif } diff --git a/src/backend/utils/cache/ts_cache.c b/src/backend/utils/cache/ts_cache.c index 6a99e78c6a..fc93551d06 100644 --- a/src/backend/utils/cache/ts_cache.c +++ b/src/backend/utils/cache/ts_cache.c @@ -587,8 +587,9 @@ getTSCurrentConfig(bool emitError) return TSCurrentConfigCache; } -const char * -assignTSCurrentConfig(const char *newval, bool doit, GucSource source) +/* GUC check_hook for default_text_search_config */ +bool +check_TSCurrentConfig(char **newval, void **extra, GucSource source) { /* * If we aren't inside a transaction, we cannot do database access so @@ -601,10 +602,10 @@ assignTSCurrentConfig(const char *newval, bool doit, GucSource source) Form_pg_ts_config cfg; char *buf; - cfgId = get_ts_config_oid(stringToQualifiedNameList(newval), true); + cfgId = get_ts_config_oid(stringToQualifiedNameList(*newval), true); if (!OidIsValid(cfgId)) - return NULL; + return false; /* * Modify the actually stored value to be fully qualified, to ensure @@ -622,17 +623,20 @@ assignTSCurrentConfig(const char *newval, bool doit, GucSource source) ReleaseSysCache(tuple); /* GUC wants it malloc'd not palloc'd */ - newval = strdup(buf); + free(*newval); + *newval = strdup(buf); pfree(buf); - - if (doit && newval) - TSCurrentConfigCache = cfgId; - } - else - { - if (doit) - TSCurrentConfigCache = InvalidOid; + if (!newval) + return false; } - return newval; + return true; +} + +/* GUC assign_hook for default_text_search_config */ +void +assign_TSCurrentConfig(const char *newval, void *extra) +{ + /* Just reset the cache to force a lookup on first use */ + TSCurrentConfigCache = InvalidOid; } diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c index e7bc046d87..9e58735aee 100644 --- a/src/backend/utils/error/elog.c +++ b/src/backend/utils/error/elog.c @@ -1156,6 +1156,62 @@ elog_finish(int elevel, const char *fmt,...) errfinish(0); } + +/* + * Functions to allow construction of error message strings separately from + * the ereport() call itself. + * + * The expected calling convention is + * + * pre_format_elog_string(errno, domain), var = format_elog_string(format,...) + * + * which can be hidden behind a macro such as GUC_check_errdetail(). We + * assume that any functions called in the arguments of format_elog_string() + * cannot result in re-entrant use of these functions --- otherwise the wrong + * text domain might be used, or the wrong errno substituted for %m. This is + * okay for the current usage with GUC check hooks, but might need further + * effort someday. + * + * The result of format_elog_string() is stored in ErrorContext, and will + * therefore survive until FlushErrorState() is called. + */ +static int save_format_errnumber; +static const char *save_format_domain; + +void +pre_format_elog_string(int errnumber, const char *domain) +{ + /* Save errno before evaluation of argument functions can change it */ + save_format_errnumber = errnumber; + /* Save caller's text domain */ + save_format_domain = domain; +} + +char * +format_elog_string(const char *fmt, ...) +{ + ErrorData errdata; + ErrorData *edata; + MemoryContext oldcontext; + + /* Initialize a mostly-dummy error frame */ + edata = &errdata; + MemSet(edata, 0, sizeof(ErrorData)); + /* the default text domain is the backend's */ + edata->domain = save_format_domain ? save_format_domain : PG_TEXTDOMAIN("postgres"); + /* set the errno to be used to interpret %m */ + edata->saved_errno = save_format_errnumber; + + oldcontext = MemoryContextSwitchTo(ErrorContext); + + EVALUATE_MESSAGE(message, false, true); + + MemoryContextSwitchTo(oldcontext); + + return edata->message; +} + + /* * Actual output of the top-of-stack error message * diff --git a/src/backend/utils/mb/mbutils.c b/src/backend/utils/mb/mbutils.c index a60188df10..b128177803 100644 --- a/src/backend/utils/mb/mbutils.c +++ b/src/backend/utils/mb/mbutils.c @@ -77,12 +77,16 @@ static int cliplen(const char *str, int len, int limit); /* - * Set the client encoding and save fmgrinfo for the conversion - * function if necessary. Returns 0 if okay, -1 if not (bad encoding - * or can't support conversion) + * Prepare for a future call to SetClientEncoding. Success should mean + * that SetClientEncoding is guaranteed to succeed for this encoding request. + * + * (But note that success before backend_startup_complete does not guarantee + * success after ...) + * + * Returns 0 if okay, -1 if not (bad encoding or can't support conversion) */ int -SetClientEncoding(int encoding, bool doit) +PrepareClientEncoding(int encoding) { int current_server_encoding; ListCell *lc; @@ -92,11 +96,7 @@ SetClientEncoding(int encoding, bool doit) /* Can't do anything during startup, per notes above */ if (!backend_startup_complete) - { - if (doit) - pending_client_encoding = encoding; return 0; - } current_server_encoding = GetDatabaseEncoding(); @@ -106,15 +106,7 @@ SetClientEncoding(int encoding, bool doit) if (current_server_encoding == encoding || current_server_encoding == PG_SQL_ASCII || encoding == PG_SQL_ASCII) - { - if (doit) - { - ClientEncoding = &pg_enc2name_tbl[encoding]; - ToServerConvProc = NULL; - ToClientConvProc = NULL; - } return 0; - } if (IsTransactionState()) { @@ -138,12 +130,6 @@ SetClientEncoding(int encoding, bool doit) if (!OidIsValid(to_client_proc)) return -1; - /* - * Done if not wanting to actually apply setting. - */ - if (!doit) - return 0; - /* * Load the fmgr info into TopMemoryContext (could still fail here) */ @@ -162,30 +148,9 @@ SetClientEncoding(int encoding, bool doit) MemoryContextSwitchTo(oldcontext); /* - * Everything is okay, so apply the setting. + * We cannot yet remove any older entry for the same encoding pair, + * since it could still be in use. SetClientEncoding will clean up. */ - ClientEncoding = &pg_enc2name_tbl[encoding]; - ToServerConvProc = &convinfo->to_server_info; - ToClientConvProc = &convinfo->to_client_info; - - /* - * Remove any older entry for the same encoding pair (this is just to - * avoid memory leakage). - */ - foreach(lc, ConvProcList) - { - ConvProcInfo *oldinfo = (ConvProcInfo *) lfirst(lc); - - if (oldinfo == convinfo) - continue; - if (oldinfo->s_encoding == convinfo->s_encoding && - oldinfo->c_encoding == convinfo->c_encoding) - { - ConvProcList = list_delete_ptr(ConvProcList, oldinfo); - pfree(oldinfo); - break; /* need not look further */ - } - } return 0; /* success */ } @@ -205,15 +170,7 @@ SetClientEncoding(int encoding, bool doit) if (oldinfo->s_encoding == current_server_encoding && oldinfo->c_encoding == encoding) - { - if (doit) - { - ClientEncoding = &pg_enc2name_tbl[encoding]; - ToServerConvProc = &oldinfo->to_server_info; - ToClientConvProc = &oldinfo->to_client_info; - } return 0; - } } return -1; /* it's not cached, so fail */ @@ -221,8 +178,83 @@ SetClientEncoding(int encoding, bool doit) } /* - * Initialize client encoding if necessary. - * called from InitPostgres() once during backend startup. + * Set the active client encoding and set up the conversion-function pointers. + * PrepareClientEncoding should have been called previously for this encoding. + * + * Returns 0 if okay, -1 if not (bad encoding or can't support conversion) + */ +int +SetClientEncoding(int encoding) +{ + int current_server_encoding; + bool found; + ListCell *lc; + + if (!PG_VALID_FE_ENCODING(encoding)) + return -1; + + /* Can't do anything during startup, per notes above */ + if (!backend_startup_complete) + { + pending_client_encoding = encoding; + return 0; + } + + current_server_encoding = GetDatabaseEncoding(); + + /* + * Check for cases that require no conversion function. + */ + if (current_server_encoding == encoding || + current_server_encoding == PG_SQL_ASCII || + encoding == PG_SQL_ASCII) + { + ClientEncoding = &pg_enc2name_tbl[encoding]; + ToServerConvProc = NULL; + ToClientConvProc = NULL; + return 0; + } + + /* + * Search the cache for the entry previously prepared by + * PrepareClientEncoding; if there isn't one, we lose. While at it, + * release any duplicate entries so that repeated Prepare/Set cycles + * don't leak memory. + */ + found = false; + foreach(lc, ConvProcList) + { + ConvProcInfo *convinfo = (ConvProcInfo *) lfirst(lc); + + if (convinfo->s_encoding == current_server_encoding && + convinfo->c_encoding == encoding) + { + if (!found) + { + /* Found newest entry, so set up */ + ClientEncoding = &pg_enc2name_tbl[encoding]; + ToServerConvProc = &convinfo->to_server_info; + ToClientConvProc = &convinfo->to_client_info; + found = true; + } + else + { + /* Duplicate entry, release it */ + ConvProcList = list_delete_ptr(ConvProcList, convinfo); + pfree(convinfo); + } + } + } + + if (found) + return 0; /* success */ + else + return -1; /* it's not cached, so fail */ +} + +/* + * Initialize client encoding conversions. + * Called from InitPostgres() once during backend startup. */ void InitializeClientEncoding(void) @@ -230,7 +262,8 @@ InitializeClientEncoding(void) Assert(!backend_startup_complete); backend_startup_complete = true; - if (SetClientEncoding(pending_client_encoding, true) < 0) + if (PrepareClientEncoding(pending_client_encoding) < 0 || + SetClientEncoding(pending_client_encoding) < 0) { /* * Oops, the requested conversion is not available. We couldn't fail diff --git a/src/backend/utils/misc/README b/src/backend/utils/misc/README index 881862a30b..70244ced18 100644 --- a/src/backend/utils/misc/README +++ b/src/backend/utils/misc/README @@ -1,10 +1,10 @@ src/backend/utils/misc/README -Guc Implementation Notes +GUC Implementation Notes ======================== The GUC (Grand Unified Configuration) module implements configuration -variables of multiple types (currently boolean, enum, int, float, and string). +variables of multiple types (currently boolean, enum, int, real, and string). Variable settings can come from various places, with a priority ordering determining which setting is used. @@ -12,65 +12,112 @@ determining which setting is used. Per-Variable Hooks ------------------ -Each variable known to GUC can optionally have an assign_hook and/or -a show_hook to provide customized behavior. Assign hooks are used to -perform validity checking on variable values (above and beyond what -GUC can do). They are also used to update any derived state that needs -to change when a GUC variable is set. Show hooks are used to modify -the default SHOW display for a variable. +Each variable known to GUC can optionally have a check_hook, an +assign_hook, and/or a show_hook to provide customized behavior. +Check hooks are used to perform validity checking on variable values +(above and beyond what GUC can do), to compute derived settings when +nontrivial work is needed to do that, and optionally to "canonicalize" +user-supplied values. Assign hooks are used to update any derived state +that needs to change when a GUC variable is set. Show hooks are used to +modify the default SHOW display for a variable. + + +If a check_hook is provided, it points to a function of the signature + bool check_hook(datatype *newvalue, void **extra, GucSource source) +The "newvalue" argument is of type bool *, int *, double *, or char ** +for bool, int/enum, real, or string variables respectively. The check +function should validate the proposed new value, and return true if it is +OK or false if not. The function can optionally do a few other things: + +* When rejecting a bad proposed value, it may be useful to append some +additional information to the generic "invalid value for parameter FOO" +complaint that guc.c will emit. To do that, call + void GUC_check_errdetail(const char *format, ...) +where the format string and additional arguments follow the rules for +errdetail() arguments. The resulting string will be emitted as the +DETAIL line of guc.c's error report, so it should follow the message style +guidelines for DETAIL messages. There is also + void GUC_check_errhint(const char *format, ...) +which can be used in the same way to append a HINT message. +Occasionally it may even be appropriate to override guc.c's generic primary +message or error code, which can be done with + void GUC_check_errcode(int sqlerrcode) + void GUC_check_errmsg(const char *format, ...) +In general, check_hooks should avoid throwing errors directly if possible, +though this may be impractical to avoid for some corner cases such as +out-of-memory. + +* Since the newvalue is pass-by-reference, the function can modify it. +This might be used for example to canonicalize the spelling of a string +value, round off a buffer size to the nearest supported value, or replace +a special value such as "-1" with a computed default value. If the +function wishes to replace a string value, it must malloc (not palloc) +the replacement value, and be sure to free() the previous value. + +* Derived information, such as the role OID represented by a user name, +can be stored for use by the assign hook. To do this, malloc (not palloc) +storage space for the information, and return its address at *extra. +guc.c will automatically free() this space when the associated GUC setting +is no longer of interest. *extra is initialized to NULL before call, so +it can be ignored if not needed. + +The "source" argument indicates the source of the proposed new value, +If it is >= PGC_S_INTERACTIVE, then we are performing an interactive +assignment (e.g., a SET command). But when source < PGC_S_INTERACTIVE, +we are reading a non-interactive option source, such as postgresql.conf. +This is sometimes needed to determine whether a setting should be +allowed. The check_hook might also look at the current actual value of +the variable to determine what is allowed. + +Note that check hooks are sometimes called just to validate a value, +without any intention of actually changing the setting. Therefore the +check hook must *not* take any action based on the assumption that an +assignment will occur. + If an assign_hook is provided, it points to a function of the signature - bool assign_hook(newvalue, bool doit, GucSource source) -where the type of "newvalue" matches the kind of variable. This function -is called immediately before actually setting the variable's value (so it -can look at the actual variable to determine the old value). If the -function returns "true" then the assignment is completed; if it returns -"false" then newvalue is considered invalid and the assignment is not -performed. If "doit" is false then the function should simply check -validity of newvalue and not change any derived state. The "source" parameter -indicates where the new value came from. If it is >= PGC_S_INTERACTIVE, -then we are performing an interactive assignment (e.g., a SET command), and -ereport(ERROR) is safe to do. But when source < PGC_S_INTERACTIVE, we are -reading a non-interactive option source, such as postgresql.conf. In this -case the assign_hook should *not* ereport but should just return false if it -doesn't like the newvalue. + void assign_hook(datatype newvalue, void *extra) +where the type of "newvalue" matches the kind of variable, and "extra" +is the derived-information pointer returned by the check_hook (always +NULL if there is no check_hook). This function is called immediately +before actually setting the variable's value (so it can look at the actual +variable to determine the old value, for example to avoid doing work when +the value isn't really changing). -If an assign_hook returns false then guc.c will report a generic "invalid -value for option FOO" error message. If you feel the need to provide a more -specific error message, ereport() it using "GUC_complaint_elevel(source)" -as the error level. Note that this might return either ERROR or a lower level -such as LOG, so the ereport call might or might not return. If it does -return, return false out of the assign_hook. +Note that there is no provision for a failure result code. assign_hooks +should never fail except under the most dire circumstances, since a failure +may for example result in GUC settings not being rolled back properly during +transaction abort. In general, try to do anything that could conceivably +fail in a check_hook instead, and pass along the results in an "extra" +struct, so that the assign hook has little to do beyond copying the data to +someplace. This applies particularly to catalog lookups: any required +lookups must be done in the check_hook, since the assign_hook may be +executed during transaction rollback when lookups will be unsafe. -For string variables, the signature for assign hooks is a bit different: - const char *assign_hook(const char *newvalue, - bool doit, - GucSource source) -The meanings of the parameters are the same as for the other types of GUC -variables, but the return value is handled differently: - NULL --- assignment fails (like returning false for other datatypes) - newvalue --- assignment succeeds, assign the newvalue as-is - malloc'd (not palloc'd!!!) string --- assign that value instead -The third choice is allowed in case the assign_hook wants to return a -"canonical" version of the new value. For example, the assign_hook for -datestyle always returns a string that includes both output and input -datestyle options, although the input might have specified only one. +Note that check_hooks are sometimes called outside any transaction, too. +This happens when processing the wired-in "bootstrap" value, values coming +from the postmaster command line or environment, or values coming from +postgresql.conf. Therefore, any catalog lookups done in a check_hook +should be guarded with an IsTransactionState() test, and there must be a +fallback path to allow derived values to be computed during the first +subsequent use of the GUC setting within a transaction. A typical +arrangement is for the catalog values computed by the check_hook and +installed by the assign_hook to be used only for the remainder of the +transaction in which the new setting is made. Each subsequent transaction +looks up the values afresh on first use. This arrangement is useful to +prevent use of stale catalog values, independently of the problem of +needing to check GUC values outside a transaction. -Note that a string variable's assign_hook will NEVER be called with a NULL -value for newvalue, since there would be no way to distinguish success -and failure returns. If the boot_val or reset_val for a string variable -is NULL, it will just be assigned without calling the assign_hook. -Therefore, a NULL boot_val should never be used in combination with an -assign_hook that has side-effects, as the side-effects wouldn't happen -during a RESET that re-institutes the boot-time setting. If a show_hook is provided, it points to a function of the signature const char *show_hook(void) This hook allows variable-specific computation of the value displayed -by SHOW. +by SHOW (and other SQL features for showing GUC variable values). +The return value can point to a static buffer, since show functions are +not used re-entrantly. -Saving/Restoring Guc Variable Values +Saving/Restoring GUC Variable Values ------------------------------------ Prior values of configuration variables must be remembered in order to deal @@ -200,27 +247,49 @@ these has a current source priority <= PGC_S_FILE. (It is thus possible for reset_val to track the config-file setting even if there is currently a different interactive value of the actual variable.) -The assign_hook and show_hook routines work only with the actual variable, -and are not directly aware of the additional values maintained by GUC. -This is not a problem for normal usage, since we can assign first to the -actual variable and then (if that succeeds) to the additional values as -needed. However, for SIGHUP rereads we may not want to assign to the -actual variable. Our procedure in that case is to call the assign_hook -with doit = false so that the value is validated, but no derived state is -changed. +The check_hook, assign_hook and show_hook routines work only with the +actual variable, and are not directly aware of the additional values +maintained by GUC. -String Memory Handling ----------------------- +GUC Memory Handling +------------------- -String option values are allocated with strdup, not with the -pstrdup/palloc mechanisms. We would need to keep them in a permanent -context anyway, and strdup gives us more control over handling +String variable values are allocated with malloc/strdup, not with the +palloc/pstrdup mechanisms. We would need to keep them in a permanent +context anyway, and malloc gives us more control over handling out-of-memory failures. We allow a string variable's actual value, reset_val, boot_val, and stacked values to point at the same storage. This makes it slightly harder to free space (we must test whether a value to be freed isn't equal to any of the other pointers in the GUC entry or associated stack items). The main -advantage is that we never need to strdup during transaction commit/abort, +advantage is that we never need to malloc during transaction commit/abort, so cannot cause an out-of-memory failure there. + +"Extra" structs returned by check_hook routines are managed in the same +way as string values. Note that we support "extra" structs for all types +of GUC variables, although they are mainly useful with strings. + + +GUC and Null String Variables +----------------------------- + +A GUC string variable can have a boot_val of NULL. guc.c handles this +unsurprisingly, assigning the NULL to the underlying C variable. Any code +using such a variable, as well as any hook functions for it, must then be +prepared to deal with a NULL value. + +However, it is not possible to assign a NULL value to a GUC string +variable in any other way: values coming from SET, postgresql.conf, etc, +might be empty strings, but they'll never be NULL. And SHOW displays +a NULL the same as an empty string. It is therefore not appropriate to +treat a NULL value as a distinct user-visible setting. A typical use +for a NULL boot_val is to denote that a value hasn't yet been set for +a variable that will receive a real value later in startup. + +If it's undesirable for code using the underlying C variable to have to +worry about NULL values ever, the variable can be given a non-null static +initializer as well as a non-null boot_val. guc.c will overwrite the +static initializer pointer with a copy of the boot_val during +InitializeGUCOptions, but the variable will never contain a NULL. diff --git a/src/backend/utils/misc/guc-file.l b/src/backend/utils/misc/guc-file.l index e660395898..10ef12eb24 100644 --- a/src/backend/utils/misc/guc-file.l +++ b/src/backend/utils/misc/guc-file.l @@ -141,7 +141,8 @@ ProcessConfigFile(GucContext context) */ cvc_struct = (struct config_string *) find_option("custom_variable_classes", false, elevel); - if (cvc_struct && cvc_struct->gen.reset_source > PGC_S_FILE) + Assert(cvc_struct); + if (cvc_struct->gen.reset_source > PGC_S_FILE) { cvc = guc_strdup(elevel, cvc_struct->reset_val); if (cvc == NULL) @@ -151,19 +152,18 @@ ProcessConfigFile(GucContext context) guc_name_compare(head->name, "custom_variable_classes") == 0) { /* - * Need to canonicalize the value via the assign hook. Casting away - * const is a bit ugly, but we know the result is malloc'd. + * Need to canonicalize the value by calling the check hook. */ - cvc = (char *) assign_custom_variable_classes(head->value, - false, PGC_S_FILE); + void *extra = NULL; + + cvc = guc_strdup(elevel, head->value); if (cvc == NULL) - { - ereport(elevel, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("invalid value for parameter \"%s\": \"%s\"", - head->name, head->value))); goto cleanup_list; - } + if (!call_string_check_hook(cvc_struct, &cvc, &extra, + PGC_S_FILE, elevel)) + goto cleanup_list; + if (extra) + free(extra); } /* diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index 2151fde361..5e4904aeb7 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -104,7 +104,7 @@ * backend ID as a 3-byte signed integer. Even if that limitation were * removed, we still could not exceed INT_MAX/4 because some places compute * 4*MaxBackends without any overflow check. This is rechecked in - * assign_maxconnections, since MaxBackends is computed as MaxConnections + * check_maxconnections, since MaxBackends is computed as MaxConnections * plus autovacuum_max_workers plus one (for the autovacuum launcher). */ #define MAX_BACKENDS 0x7fffff @@ -143,11 +143,29 @@ extern bool trace_syncscan; extern bool optimize_bounded_sort; #endif +static int GUC_check_errcode_value; + +/* global variables for check hook support */ +char *GUC_check_errmsg_string; +char *GUC_check_errdetail_string; +char *GUC_check_errhint_string; + + static void set_config_sourcefile(const char *name, char *sourcefile, int sourceline); +static bool call_bool_check_hook(struct config_bool *conf, bool *newval, + void **extra, GucSource source, int elevel); +static bool call_int_check_hook(struct config_int *conf, int *newval, + void **extra, GucSource source, int elevel); +static bool call_real_check_hook(struct config_real *conf, double *newval, + void **extra, GucSource source, int elevel); +static bool call_string_check_hook(struct config_string *conf, char **newval, + void **extra, GucSource source, int elevel); +static bool call_enum_check_hook(struct config_enum *conf, int *newval, + void **extra, GucSource source, int elevel); -static const char *assign_log_destination(const char *value, - bool doit, GucSource source); +static bool check_log_destination(char **newval, void **extra, GucSource source); +static void assign_log_destination(const char *newval, void *extra); #ifdef HAVE_SYSLOG static int syslog_facility = LOG_LOCAL0; @@ -155,36 +173,36 @@ static int syslog_facility = LOG_LOCAL0; static int syslog_facility = 0; #endif -static bool assign_syslog_facility(int newval, - bool doit, GucSource source); -static const char *assign_syslog_ident(const char *ident, - bool doit, GucSource source); - -static bool assign_session_replication_role(int newval, bool doit, - GucSource source); -static const char *show_num_temp_buffers(void); -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_debug_assertions(bool newval, bool doit, GucSource source); -static bool assign_bonjour(bool newval, bool doit, GucSource source); -static bool assign_ssl(bool 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); -static const char *assign_canonical_path(const char *newval, bool doit, GucSource source); -static const char *assign_timezone_abbreviations(const char *newval, bool doit, GucSource source); +static void assign_syslog_facility(int newval, void *extra); +static void assign_syslog_ident(const char *newval, void *extra); +static void assign_session_replication_role(int newval, void *extra); +static bool check_temp_buffers(int *newval, void **extra, GucSource source); +static bool check_phony_autocommit(bool *newval, void **extra, GucSource source); +static bool check_custom_variable_classes(char **newval, void **extra, GucSource source); +static bool check_debug_assertions(bool *newval, void **extra, GucSource source); +static bool check_bonjour(bool *newval, void **extra, GucSource source); +static bool check_ssl(bool *newval, void **extra, GucSource source); +static bool check_stage_log_stats(bool *newval, void **extra, GucSource source); +static bool check_log_stats(bool *newval, void **extra, GucSource source); +static bool check_canonical_path(char **newval, void **extra, GucSource source); +static bool check_timezone_abbreviations(char **newval, void **extra, GucSource source); +static void assign_timezone_abbreviations(const char *newval, void *extra); static const char *show_archive_command(void); -static bool assign_tcp_keepalives_idle(int newval, bool doit, GucSource source); -static bool assign_tcp_keepalives_interval(int newval, bool doit, GucSource source); -static bool assign_tcp_keepalives_count(int newval, bool doit, GucSource source); +static void assign_tcp_keepalives_idle(int newval, void *extra); +static void assign_tcp_keepalives_interval(int newval, void *extra); +static void assign_tcp_keepalives_count(int newval, void *extra); static const char *show_tcp_keepalives_idle(void); static const char *show_tcp_keepalives_interval(void); static const char *show_tcp_keepalives_count(void); -static bool assign_maxconnections(int newval, bool doit, GucSource source); -static bool assign_autovacuum_max_workers(int newval, bool doit, GucSource source); -static bool assign_effective_io_concurrency(int newval, bool doit, GucSource source); -static const char *assign_pgstat_temp_directory(const char *newval, bool doit, GucSource source); -static const char *assign_application_name(const char *newval, bool doit, GucSource source); +static bool check_maxconnections(int *newval, void **extra, GucSource source); +static void assign_maxconnections(int newval, void *extra); +static bool check_autovacuum_max_workers(int *newval, void **extra, GucSource source); +static void assign_autovacuum_max_workers(int newval, void *extra); +static bool check_effective_io_concurrency(int *newval, void **extra, GucSource source); +static void assign_effective_io_concurrency(int newval, void *extra); +static void assign_pgstat_temp_directory(const char *newval, void *extra); +static bool check_application_name(char **newval, void **extra, GucSource source); +static void assign_application_name(const char *newval, void *extra); static const char *show_unix_socket_permissions(void); static const char *show_log_file_mode(void); @@ -407,7 +425,7 @@ int log_min_duration_statement = -1; int log_temp_files = -1; int trace_recovery_messages = LOG; -int num_temp_buffers = 1000; +int num_temp_buffers = 1024; char *data_directory; char *ConfigFileName; @@ -444,6 +462,8 @@ static int server_version_num; static char *timezone_string; static char *log_timezone_string; static char *timezone_abbreviations_string; +static char *XactIsoLevel_string; +static char *session_authorization_string; static char *custom_variable_classes; static int max_function_args; static int max_index_keys; @@ -455,10 +475,8 @@ static int wal_segment_size; static bool integer_datetimes; static int effective_io_concurrency; -/* should be static, but commands/variable.c needs to get at these */ +/* should be static, but commands/variable.c needs to get at this */ char *role_string; -char *session_authorization_string; -char *XactIsoLevel_string; /* @@ -643,7 +661,8 @@ static struct config_bool ConfigureNamesBool[] = NULL }, &enable_seqscan, - true, NULL, NULL + true, + NULL, NULL, NULL }, { {"enable_indexscan", PGC_USERSET, QUERY_TUNING_METHOD, @@ -651,7 +670,8 @@ static struct config_bool ConfigureNamesBool[] = NULL }, &enable_indexscan, - true, NULL, NULL + true, + NULL, NULL, NULL }, { {"enable_bitmapscan", PGC_USERSET, QUERY_TUNING_METHOD, @@ -659,7 +679,8 @@ static struct config_bool ConfigureNamesBool[] = NULL }, &enable_bitmapscan, - true, NULL, NULL + true, + NULL, NULL, NULL }, { {"enable_tidscan", PGC_USERSET, QUERY_TUNING_METHOD, @@ -667,7 +688,8 @@ static struct config_bool ConfigureNamesBool[] = NULL }, &enable_tidscan, - true, NULL, NULL + true, + NULL, NULL, NULL }, { {"enable_sort", PGC_USERSET, QUERY_TUNING_METHOD, @@ -675,7 +697,8 @@ static struct config_bool ConfigureNamesBool[] = NULL }, &enable_sort, - true, NULL, NULL + true, + NULL, NULL, NULL }, { {"enable_hashagg", PGC_USERSET, QUERY_TUNING_METHOD, @@ -683,7 +706,8 @@ static struct config_bool ConfigureNamesBool[] = NULL }, &enable_hashagg, - true, NULL, NULL + true, + NULL, NULL, NULL }, { {"enable_material", PGC_USERSET, QUERY_TUNING_METHOD, @@ -691,7 +715,8 @@ static struct config_bool ConfigureNamesBool[] = NULL }, &enable_material, - true, NULL, NULL + true, + NULL, NULL, NULL }, { {"enable_nestloop", PGC_USERSET, QUERY_TUNING_METHOD, @@ -699,7 +724,8 @@ static struct config_bool ConfigureNamesBool[] = NULL }, &enable_nestloop, - true, NULL, NULL + true, + NULL, NULL, NULL }, { {"enable_mergejoin", PGC_USERSET, QUERY_TUNING_METHOD, @@ -707,7 +733,8 @@ static struct config_bool ConfigureNamesBool[] = NULL }, &enable_mergejoin, - true, NULL, NULL + true, + NULL, NULL, NULL }, { {"enable_hashjoin", PGC_USERSET, QUERY_TUNING_METHOD, @@ -715,7 +742,8 @@ static struct config_bool ConfigureNamesBool[] = NULL }, &enable_hashjoin, - true, NULL, NULL + true, + NULL, NULL, NULL }, { {"geqo", PGC_USERSET, QUERY_TUNING_GEQO, @@ -724,7 +752,8 @@ static struct config_bool ConfigureNamesBool[] = "exhaustive searching.") }, &enable_geqo, - true, NULL, NULL + true, + NULL, NULL, NULL }, { /* Not for general use --- used by SET SESSION AUTHORIZATION */ @@ -734,7 +763,8 @@ static struct config_bool ConfigureNamesBool[] = GUC_REPORT | GUC_NO_SHOW_ALL | GUC_NO_RESET_ALL | GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE }, &session_auth_is_superuser, - false, NULL, NULL + false, + NULL, NULL, NULL }, { {"bonjour", PGC_POSTMASTER, CONN_AUTH_SETTINGS, @@ -742,7 +772,8 @@ static struct config_bool ConfigureNamesBool[] = NULL }, &enable_bonjour, - false, assign_bonjour, NULL + false, + check_bonjour, NULL, NULL }, { {"ssl", PGC_POSTMASTER, CONN_AUTH_SECURITY, @@ -750,7 +781,8 @@ static struct config_bool ConfigureNamesBool[] = NULL }, &EnableSSL, - false, assign_ssl, NULL + false, + check_ssl, NULL, NULL }, { {"fsync", PGC_SIGHUP, WAL_SETTINGS, @@ -761,7 +793,8 @@ static struct config_bool ConfigureNamesBool[] = "an operating system or hardware crash.") }, &enableFsync, - true, NULL, NULL + true, + NULL, NULL, NULL }, { {"zero_damaged_pages", PGC_SUSET, DEVELOPER_OPTIONS, @@ -774,7 +807,8 @@ static struct config_bool ConfigureNamesBool[] = GUC_NOT_IN_SAMPLE }, &zero_damaged_pages, - false, NULL, NULL + false, + NULL, NULL, NULL }, { {"full_page_writes", PGC_SIGHUP, WAL_SETTINGS, @@ -786,7 +820,8 @@ static struct config_bool ConfigureNamesBool[] = "is possible.") }, &fullPageWrites, - true, NULL, NULL + true, + NULL, NULL, NULL }, { {"silent_mode", PGC_POSTMASTER, LOGGING_WHERE, @@ -795,7 +830,8 @@ static struct config_bool ConfigureNamesBool[] = "background and any controlling terminals are dissociated.") }, &SilentMode, - false, NULL, NULL + false, + NULL, NULL, NULL }, { {"log_checkpoints", PGC_SIGHUP, LOGGING_WHAT, @@ -803,7 +839,8 @@ static struct config_bool ConfigureNamesBool[] = NULL }, &log_checkpoints, - false, NULL, NULL + false, + NULL, NULL, NULL }, { {"log_connections", PGC_BACKEND, LOGGING_WHAT, @@ -811,7 +848,8 @@ static struct config_bool ConfigureNamesBool[] = NULL }, &Log_connections, - false, NULL, NULL + false, + NULL, NULL, NULL }, { {"log_disconnections", PGC_BACKEND, LOGGING_WHAT, @@ -819,7 +857,8 @@ static struct config_bool ConfigureNamesBool[] = NULL }, &Log_disconnections, - false, NULL, NULL + false, + NULL, NULL, NULL }, { {"debug_assertions", PGC_USERSET, DEVELOPER_OPTIONS, @@ -833,7 +872,7 @@ static struct config_bool ConfigureNamesBool[] = #else false, #endif - assign_debug_assertions, NULL + check_debug_assertions, NULL, NULL }, { @@ -842,7 +881,8 @@ static struct config_bool ConfigureNamesBool[] = NULL }, &ExitOnAnyError, - false, NULL, NULL + false, + NULL, NULL, NULL }, { {"restart_after_crash", PGC_SIGHUP, ERROR_HANDLING_OPTIONS, @@ -850,7 +890,8 @@ static struct config_bool ConfigureNamesBool[] = NULL }, &restart_after_crash, - true, NULL, NULL + true, + NULL, NULL, NULL }, { @@ -859,7 +900,8 @@ static struct config_bool ConfigureNamesBool[] = NULL }, &log_duration, - false, NULL, NULL + false, + NULL, NULL, NULL }, { {"debug_print_parse", PGC_USERSET, LOGGING_WHAT, @@ -867,7 +909,8 @@ static struct config_bool ConfigureNamesBool[] = NULL }, &Debug_print_parse, - false, NULL, NULL + false, + NULL, NULL, NULL }, { {"debug_print_rewritten", PGC_USERSET, LOGGING_WHAT, @@ -875,7 +918,8 @@ static struct config_bool ConfigureNamesBool[] = NULL }, &Debug_print_rewritten, - false, NULL, NULL + false, + NULL, NULL, NULL }, { {"debug_print_plan", PGC_USERSET, LOGGING_WHAT, @@ -883,7 +927,8 @@ static struct config_bool ConfigureNamesBool[] = NULL }, &Debug_print_plan, - false, NULL, NULL + false, + NULL, NULL, NULL }, { {"debug_pretty_print", PGC_USERSET, LOGGING_WHAT, @@ -891,7 +936,8 @@ static struct config_bool ConfigureNamesBool[] = NULL }, &Debug_pretty_print, - true, NULL, NULL + true, + NULL, NULL, NULL }, { {"log_parser_stats", PGC_SUSET, STATS_MONITORING, @@ -899,7 +945,8 @@ static struct config_bool ConfigureNamesBool[] = NULL }, &log_parser_stats, - false, assign_stage_log_stats, NULL + false, + check_stage_log_stats, NULL, NULL }, { {"log_planner_stats", PGC_SUSET, STATS_MONITORING, @@ -907,7 +954,8 @@ static struct config_bool ConfigureNamesBool[] = NULL }, &log_planner_stats, - false, assign_stage_log_stats, NULL + false, + check_stage_log_stats, NULL, NULL }, { {"log_executor_stats", PGC_SUSET, STATS_MONITORING, @@ -915,7 +963,8 @@ static struct config_bool ConfigureNamesBool[] = NULL }, &log_executor_stats, - false, assign_stage_log_stats, NULL + false, + check_stage_log_stats, NULL, NULL }, { {"log_statement_stats", PGC_SUSET, STATS_MONITORING, @@ -923,7 +972,8 @@ static struct config_bool ConfigureNamesBool[] = NULL }, &log_statement_stats, - false, assign_log_stats, NULL + false, + check_log_stats, NULL, NULL }, #ifdef BTREE_BUILD_STATS { @@ -933,7 +983,8 @@ static struct config_bool ConfigureNamesBool[] = GUC_NOT_IN_SAMPLE }, &log_btree_build_stats, - false, NULL, NULL + false, + NULL, NULL, NULL }, #endif @@ -945,7 +996,8 @@ static struct config_bool ConfigureNamesBool[] = "the time at which that command began execution.") }, &pgstat_track_activities, - true, NULL, NULL + true, + NULL, NULL, NULL }, { {"track_counts", PGC_SUSET, STATS_COLLECTOR, @@ -953,7 +1005,8 @@ static struct config_bool ConfigureNamesBool[] = NULL }, &pgstat_track_counts, - true, NULL, NULL + true, + NULL, NULL, NULL }, { @@ -962,7 +1015,8 @@ static struct config_bool ConfigureNamesBool[] = gettext_noop("Enables updating of the process title every time a new SQL command is received by the server.") }, &update_process_title, - true, NULL, NULL + true, + NULL, NULL, NULL }, { @@ -971,7 +1025,8 @@ static struct config_bool ConfigureNamesBool[] = NULL }, &autovacuum_start_daemon, - true, NULL, NULL + true, + NULL, NULL, NULL }, { @@ -981,7 +1036,8 @@ static struct config_bool ConfigureNamesBool[] = GUC_NOT_IN_SAMPLE }, &Trace_notify, - false, NULL, NULL + false, + NULL, NULL, NULL }, #ifdef LOCK_DEBUG @@ -992,7 +1048,8 @@ static struct config_bool ConfigureNamesBool[] = GUC_NOT_IN_SAMPLE }, &Trace_locks, - false, NULL, NULL + false, + NULL, NULL, NULL }, { {"trace_userlocks", PGC_SUSET, DEVELOPER_OPTIONS, @@ -1001,7 +1058,8 @@ static struct config_bool ConfigureNamesBool[] = GUC_NOT_IN_SAMPLE }, &Trace_userlocks, - false, NULL, NULL + false, + NULL, NULL, NULL }, { {"trace_lwlocks", PGC_SUSET, DEVELOPER_OPTIONS, @@ -1010,7 +1068,8 @@ static struct config_bool ConfigureNamesBool[] = GUC_NOT_IN_SAMPLE }, &Trace_lwlocks, - false, NULL, NULL + false, + NULL, NULL, NULL }, { {"debug_deadlocks", PGC_SUSET, DEVELOPER_OPTIONS, @@ -1019,7 +1078,8 @@ static struct config_bool ConfigureNamesBool[] = GUC_NOT_IN_SAMPLE }, &Debug_deadlocks, - false, NULL, NULL + false, + NULL, NULL, NULL }, #endif @@ -1029,7 +1089,8 @@ static struct config_bool ConfigureNamesBool[] = NULL }, &log_lock_waits, - false, NULL, NULL + false, + NULL, NULL, NULL }, { @@ -1041,7 +1102,8 @@ static struct config_bool ConfigureNamesBool[] = "setup it might impose a non-negligible performance penalty.") }, &log_hostname, - false, NULL, NULL + false, + NULL, NULL, NULL }, { {"sql_inheritance", PGC_USERSET, COMPAT_OPTIONS_PREVIOUS, @@ -1049,7 +1111,8 @@ static struct config_bool ConfigureNamesBool[] = NULL }, &SQL_inheritance, - true, NULL, NULL + true, + NULL, NULL, NULL }, { {"password_encryption", PGC_USERSET, CONN_AUTH_SECURITY, @@ -1059,7 +1122,8 @@ static struct config_bool ConfigureNamesBool[] = "this parameter determines whether the password is to be encrypted.") }, &Password_encryption, - true, NULL, NULL + true, + NULL, NULL, NULL }, { {"transform_null_equals", PGC_USERSET, COMPAT_OPTIONS_CLIENT, @@ -1071,7 +1135,8 @@ static struct config_bool ConfigureNamesBool[] = "return null (unknown).") }, &Transform_null_equals, - false, NULL, NULL + false, + NULL, NULL, NULL }, { {"db_user_namespace", PGC_SIGHUP, CONN_AUTH_SECURITY, @@ -1079,7 +1144,8 @@ static struct config_bool ConfigureNamesBool[] = NULL }, &Db_user_namespace, - false, NULL, NULL + false, + NULL, NULL, NULL }, { /* only here for backwards compatibility */ @@ -1089,7 +1155,8 @@ static struct config_bool ConfigureNamesBool[] = GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE }, &phony_autocommit, - true, assign_phony_autocommit, NULL + true, + check_phony_autocommit, NULL, NULL }, { {"default_transaction_read_only", PGC_USERSET, CLIENT_CONN_STATEMENT, @@ -1097,7 +1164,8 @@ static struct config_bool ConfigureNamesBool[] = NULL }, &DefaultXactReadOnly, - false, NULL, NULL + false, + NULL, NULL, NULL }, { {"transaction_read_only", PGC_USERSET, CLIENT_CONN_STATEMENT, @@ -1106,7 +1174,8 @@ static struct config_bool ConfigureNamesBool[] = GUC_NO_RESET_ALL | GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE }, &XactReadOnly, - false, assign_transaction_read_only, NULL + false, + check_transaction_read_only, NULL, NULL }, { {"default_transaction_deferrable", PGC_USERSET, CLIENT_CONN_STATEMENT, @@ -1114,7 +1183,8 @@ static struct config_bool ConfigureNamesBool[] = NULL }, &DefaultXactDeferrable, - false, NULL, NULL + false, + NULL, NULL, NULL }, { {"transaction_deferrable", PGC_USERSET, CLIENT_CONN_STATEMENT, @@ -1123,7 +1193,8 @@ static struct config_bool ConfigureNamesBool[] = GUC_NO_RESET_ALL | GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE }, &XactDeferrable, - false, assign_transaction_deferrable, NULL + false, + check_transaction_deferrable, NULL, NULL }, { {"check_function_bodies", PGC_USERSET, CLIENT_CONN_STATEMENT, @@ -1131,7 +1202,8 @@ static struct config_bool ConfigureNamesBool[] = NULL }, &check_function_bodies, - true, NULL, NULL + true, + NULL, NULL, NULL }, { {"array_nulls", PGC_USERSET, COMPAT_OPTIONS_PREVIOUS, @@ -1141,7 +1213,8 @@ static struct config_bool ConfigureNamesBool[] = "otherwise it is taken literally.") }, &Array_nulls, - true, NULL, NULL + true, + NULL, NULL, NULL }, { {"default_with_oids", PGC_USERSET, COMPAT_OPTIONS_PREVIOUS, @@ -1149,7 +1222,8 @@ static struct config_bool ConfigureNamesBool[] = NULL }, &default_with_oids, - false, NULL, NULL + false, + NULL, NULL, NULL }, { {"logging_collector", PGC_POSTMASTER, LOGGING_WHERE, @@ -1157,7 +1231,8 @@ static struct config_bool ConfigureNamesBool[] = NULL }, &Logging_collector, - false, NULL, NULL + false, + NULL, NULL, NULL }, { {"log_truncate_on_rotation", PGC_SIGHUP, LOGGING_WHERE, @@ -1165,7 +1240,8 @@ static struct config_bool ConfigureNamesBool[] = NULL }, &Log_truncate_on_rotation, - false, NULL, NULL + false, + NULL, NULL, NULL }, #ifdef TRACE_SORT @@ -1176,7 +1252,8 @@ static struct config_bool ConfigureNamesBool[] = GUC_NOT_IN_SAMPLE }, &trace_sort, - false, NULL, NULL + false, + NULL, NULL, NULL }, #endif @@ -1189,7 +1266,8 @@ static struct config_bool ConfigureNamesBool[] = GUC_NOT_IN_SAMPLE }, &trace_syncscan, - false, NULL, NULL + false, + NULL, NULL, NULL }, #endif @@ -1203,7 +1281,8 @@ static struct config_bool ConfigureNamesBool[] = GUC_NOT_IN_SAMPLE }, &optimize_bounded_sort, - true, NULL, NULL + true, + NULL, NULL, NULL }, #endif @@ -1215,7 +1294,8 @@ static struct config_bool ConfigureNamesBool[] = GUC_NOT_IN_SAMPLE }, &XLOG_DEBUG, - false, NULL, NULL + false, + NULL, NULL, NULL }, #endif @@ -1227,10 +1307,11 @@ static struct config_bool ConfigureNamesBool[] = }, &integer_datetimes, #ifdef HAVE_INT64_TIMESTAMP - true, NULL, NULL + true, #else - false, NULL, NULL + false, #endif + NULL, NULL, NULL }, { @@ -1239,7 +1320,8 @@ static struct config_bool ConfigureNamesBool[] = NULL }, &pg_krb_caseins_users, - false, NULL, NULL + false, + NULL, NULL, NULL }, { @@ -1248,7 +1330,8 @@ static struct config_bool ConfigureNamesBool[] = NULL }, &escape_string_warning, - true, NULL, NULL + true, + NULL, NULL, NULL }, { @@ -1258,7 +1341,8 @@ static struct config_bool ConfigureNamesBool[] = GUC_REPORT }, &standard_conforming_strings, - true, NULL, NULL + true, + NULL, NULL, NULL }, { @@ -1267,7 +1351,8 @@ static struct config_bool ConfigureNamesBool[] = NULL }, &synchronize_seqscans, - true, NULL, NULL + true, + NULL, NULL, NULL }, { @@ -1276,7 +1361,8 @@ static struct config_bool ConfigureNamesBool[] = NULL }, &XLogArchiveMode, - false, NULL, NULL + false, + NULL, NULL, NULL }, { @@ -1285,7 +1371,8 @@ static struct config_bool ConfigureNamesBool[] = NULL }, &EnableHotStandby, - false, NULL, NULL + false, + NULL, NULL, NULL }, { @@ -1294,7 +1381,8 @@ static struct config_bool ConfigureNamesBool[] = NULL }, &hot_standby_feedback, - false, NULL, NULL + false, + NULL, NULL, NULL }, { @@ -1304,7 +1392,8 @@ static struct config_bool ConfigureNamesBool[] = GUC_NOT_IN_SAMPLE }, &allowSystemTableMods, - false, NULL, NULL + false, + NULL, NULL, NULL }, { @@ -1315,7 +1404,8 @@ static struct config_bool ConfigureNamesBool[] = GUC_NOT_IN_SAMPLE }, &IgnoreSystemIndexes, - false, NULL, NULL + false, + NULL, NULL, NULL }, { @@ -1325,7 +1415,8 @@ static struct config_bool ConfigureNamesBool[] = "for compatibility with PostgreSQL releases prior to 9.0.") }, &lo_compat_privileges, - false, NULL, NULL + false, + NULL, NULL, NULL }, { @@ -1334,12 +1425,13 @@ static struct config_bool ConfigureNamesBool[] = NULL, }, "e_all_identifiers, - false, NULL, NULL + false, + NULL, NULL, NULL }, /* End-of-list marker */ { - {NULL, 0, 0, NULL, NULL}, NULL, false, NULL, NULL + {NULL, 0, 0, NULL, NULL}, NULL, false, NULL, NULL, NULL } }; @@ -1354,7 +1446,8 @@ static struct config_int ConfigureNamesInt[] = GUC_UNIT_S }, &XLogArchiveTimeout, - 0, 0, INT_MAX, NULL, NULL + 0, 0, INT_MAX, + NULL, NULL, NULL }, { {"post_auth_delay", PGC_BACKEND, DEVELOPER_OPTIONS, @@ -1363,7 +1456,8 @@ static struct config_int ConfigureNamesInt[] = GUC_NOT_IN_SAMPLE | GUC_UNIT_S }, &PostAuthDelay, - 0, 0, INT_MAX, NULL, NULL + 0, 0, INT_MAX, + NULL, NULL, NULL }, { {"default_statistics_target", PGC_USERSET, QUERY_TUNING_OTHER, @@ -1372,7 +1466,8 @@ static struct config_int ConfigureNamesInt[] = "column-specific target set via ALTER TABLE SET STATISTICS.") }, &default_statistics_target, - 100, 1, 10000, NULL, NULL + 100, 1, 10000, + NULL, NULL, NULL }, { {"from_collapse_limit", PGC_USERSET, QUERY_TUNING_OTHER, @@ -1383,7 +1478,8 @@ static struct config_int ConfigureNamesInt[] = "this many items.") }, &from_collapse_limit, - 8, 1, INT_MAX, NULL, NULL + 8, 1, INT_MAX, + NULL, NULL, NULL }, { {"join_collapse_limit", PGC_USERSET, QUERY_TUNING_OTHER, @@ -1394,7 +1490,8 @@ static struct config_int ConfigureNamesInt[] = "list of no more than this many items would result.") }, &join_collapse_limit, - 8, 1, INT_MAX, NULL, NULL + 8, 1, INT_MAX, + NULL, NULL, NULL }, { {"geqo_threshold", PGC_USERSET, QUERY_TUNING_GEQO, @@ -1402,7 +1499,8 @@ static struct config_int ConfigureNamesInt[] = NULL }, &geqo_threshold, - 12, 2, INT_MAX, NULL, NULL + 12, 2, INT_MAX, + NULL, NULL, NULL }, { {"geqo_effort", PGC_USERSET, QUERY_TUNING_GEQO, @@ -1410,7 +1508,8 @@ static struct config_int ConfigureNamesInt[] = NULL }, &Geqo_effort, - DEFAULT_GEQO_EFFORT, MIN_GEQO_EFFORT, MAX_GEQO_EFFORT, NULL, NULL + DEFAULT_GEQO_EFFORT, MIN_GEQO_EFFORT, MAX_GEQO_EFFORT, + NULL, NULL, NULL }, { {"geqo_pool_size", PGC_USERSET, QUERY_TUNING_GEQO, @@ -1418,7 +1517,8 @@ static struct config_int ConfigureNamesInt[] = gettext_noop("Zero selects a suitable default value.") }, &Geqo_pool_size, - 0, 0, INT_MAX, NULL, NULL + 0, 0, INT_MAX, + NULL, NULL, NULL }, { {"geqo_generations", PGC_USERSET, QUERY_TUNING_GEQO, @@ -1426,7 +1526,8 @@ static struct config_int ConfigureNamesInt[] = gettext_noop("Zero selects a suitable default value.") }, &Geqo_generations, - 0, 0, INT_MAX, NULL, NULL + 0, 0, INT_MAX, + NULL, NULL, NULL }, { @@ -1437,7 +1538,8 @@ static struct config_int ConfigureNamesInt[] = GUC_UNIT_MS }, &DeadlockTimeout, - 1000, 1, INT_MAX, NULL, NULL + 1000, 1, INT_MAX, + NULL, NULL, NULL }, { @@ -1447,7 +1549,8 @@ static struct config_int ConfigureNamesInt[] = GUC_UNIT_MS }, &max_standby_archive_delay, - 30 * 1000, -1, INT_MAX, NULL, NULL + 30 * 1000, -1, INT_MAX, + NULL, NULL, NULL }, { @@ -1457,7 +1560,8 @@ static struct config_int ConfigureNamesInt[] = GUC_UNIT_MS }, &max_standby_streaming_delay, - 30 * 1000, -1, INT_MAX, NULL, NULL + 30 * 1000, -1, INT_MAX, + NULL, NULL, NULL }, { @@ -1467,7 +1571,8 @@ static struct config_int ConfigureNamesInt[] = GUC_UNIT_S }, &wal_receiver_status_interval, - 10, 0, INT_MAX/1000, NULL, NULL + 10, 0, INT_MAX/1000, + NULL, NULL, NULL }, { @@ -1476,7 +1581,8 @@ static struct config_int ConfigureNamesInt[] = NULL }, &MaxConnections, - 100, 1, MAX_BACKENDS, assign_maxconnections, NULL + 100, 1, MAX_BACKENDS, + check_maxconnections, assign_maxconnections, NULL }, { @@ -1485,7 +1591,8 @@ static struct config_int ConfigureNamesInt[] = NULL }, &ReservedBackends, - 3, 0, MAX_BACKENDS, NULL, NULL + 3, 0, MAX_BACKENDS, + NULL, NULL, NULL }, /* @@ -1499,7 +1606,8 @@ static struct config_int ConfigureNamesInt[] = GUC_UNIT_BLOCKS }, &NBuffers, - 1024, 16, INT_MAX / 2, NULL, NULL + 1024, 16, INT_MAX / 2, + NULL, NULL, NULL }, { @@ -1509,7 +1617,8 @@ static struct config_int ConfigureNamesInt[] = GUC_UNIT_BLOCKS }, &num_temp_buffers, - 1024, 100, INT_MAX / 2, NULL, show_num_temp_buffers + 1024, 100, INT_MAX / 2, + check_temp_buffers, NULL, NULL }, { @@ -1518,7 +1627,8 @@ static struct config_int ConfigureNamesInt[] = NULL }, &PostPortNumber, - DEF_PGPORT, 1, 65535, NULL, NULL + DEF_PGPORT, 1, 65535, + NULL, NULL, NULL }, { @@ -1532,7 +1642,8 @@ static struct config_int ConfigureNamesInt[] = "start with a 0 (zero).)") }, &Unix_socket_permissions, - 0777, 0000, 0777, NULL, show_unix_socket_permissions + 0777, 0000, 0777, + NULL, NULL, show_unix_socket_permissions }, { @@ -1545,7 +1656,8 @@ static struct config_int ConfigureNamesInt[] = "start with a 0 (zero).)") }, &Log_file_mode, - 0600, 0000, 0777, NULL, show_log_file_mode + 0600, 0000, 0777, + NULL, NULL, show_log_file_mode }, { @@ -1557,7 +1669,8 @@ static struct config_int ConfigureNamesInt[] = GUC_UNIT_KB }, &work_mem, - 1024, 64, MAX_KILOBYTES, NULL, NULL + 1024, 64, MAX_KILOBYTES, + NULL, NULL, NULL }, { @@ -1567,7 +1680,8 @@ static struct config_int ConfigureNamesInt[] = GUC_UNIT_KB }, &maintenance_work_mem, - 16384, 1024, MAX_KILOBYTES, NULL, NULL + 16384, 1024, MAX_KILOBYTES, + NULL, NULL, NULL }, /* @@ -1582,7 +1696,8 @@ static struct config_int ConfigureNamesInt[] = GUC_UNIT_KB }, &max_stack_depth, - 100, 100, MAX_KILOBYTES, assign_max_stack_depth, NULL + 100, 100, MAX_KILOBYTES, + check_max_stack_depth, assign_max_stack_depth, NULL }, { @@ -1591,7 +1706,8 @@ static struct config_int ConfigureNamesInt[] = NULL }, &VacuumCostPageHit, - 1, 0, 10000, NULL, NULL + 1, 0, 10000, + NULL, NULL, NULL }, { @@ -1600,7 +1716,8 @@ static struct config_int ConfigureNamesInt[] = NULL }, &VacuumCostPageMiss, - 10, 0, 10000, NULL, NULL + 10, 0, 10000, + NULL, NULL, NULL }, { @@ -1609,7 +1726,8 @@ static struct config_int ConfigureNamesInt[] = NULL }, &VacuumCostPageDirty, - 20, 0, 10000, NULL, NULL + 20, 0, 10000, + NULL, NULL, NULL }, { @@ -1618,7 +1736,8 @@ static struct config_int ConfigureNamesInt[] = NULL }, &VacuumCostLimit, - 200, 1, 10000, NULL, NULL + 200, 1, 10000, + NULL, NULL, NULL }, { @@ -1628,7 +1747,8 @@ static struct config_int ConfigureNamesInt[] = GUC_UNIT_MS }, &VacuumCostDelay, - 0, 0, 100, NULL, NULL + 0, 0, 100, + NULL, NULL, NULL }, { @@ -1638,7 +1758,8 @@ static struct config_int ConfigureNamesInt[] = GUC_UNIT_MS }, &autovacuum_vac_cost_delay, - 20, -1, 100, NULL, NULL + 20, -1, 100, + NULL, NULL, NULL }, { @@ -1647,7 +1768,8 @@ static struct config_int ConfigureNamesInt[] = NULL }, &autovacuum_vac_cost_limit, - -1, -1, 10000, NULL, NULL + -1, -1, 10000, + NULL, NULL, NULL }, { @@ -1656,7 +1778,8 @@ static struct config_int ConfigureNamesInt[] = NULL }, &max_files_per_process, - 1000, 25, INT_MAX, NULL, NULL + 1000, 25, INT_MAX, + NULL, NULL, NULL }, /* @@ -1668,7 +1791,8 @@ static struct config_int ConfigureNamesInt[] = NULL }, &max_prepared_xacts, - 0, 0, MAX_BACKENDS, NULL, NULL + 0, 0, MAX_BACKENDS, + NULL, NULL, NULL }, #ifdef LOCK_DEBUG @@ -1679,7 +1803,8 @@ static struct config_int ConfigureNamesInt[] = GUC_NOT_IN_SAMPLE }, &Trace_lock_oidmin, - FirstNormalObjectId, 0, INT_MAX, NULL, NULL + FirstNormalObjectId, 0, INT_MAX, + NULL, NULL, NULL }, { {"trace_lock_table", PGC_SUSET, DEVELOPER_OPTIONS, @@ -1688,7 +1813,8 @@ static struct config_int ConfigureNamesInt[] = GUC_NOT_IN_SAMPLE }, &Trace_lock_table, - 0, 0, INT_MAX, NULL, NULL + 0, 0, INT_MAX, + NULL, NULL, NULL }, #endif @@ -1699,7 +1825,8 @@ static struct config_int ConfigureNamesInt[] = GUC_UNIT_MS }, &StatementTimeout, - 0, 0, INT_MAX, NULL, NULL + 0, 0, INT_MAX, + NULL, NULL, NULL }, { @@ -1708,7 +1835,8 @@ static struct config_int ConfigureNamesInt[] = NULL }, &vacuum_freeze_min_age, - 50000000, 0, 1000000000, NULL, NULL + 50000000, 0, 1000000000, + NULL, NULL, NULL }, { @@ -1717,7 +1845,8 @@ static struct config_int ConfigureNamesInt[] = NULL }, &vacuum_freeze_table_age, - 150000000, 0, 2000000000, NULL, NULL + 150000000, 0, 2000000000, + NULL, NULL, NULL }, { @@ -1726,7 +1855,8 @@ static struct config_int ConfigureNamesInt[] = NULL }, &vacuum_defer_cleanup_age, - 0, 0, 1000000, NULL, NULL + 0, 0, 1000000, + NULL, NULL, NULL }, /* @@ -1740,7 +1870,8 @@ static struct config_int ConfigureNamesInt[] = "objects will need to be locked at any one time.") }, &max_locks_per_xact, - 64, 10, INT_MAX, NULL, NULL + 64, 10, INT_MAX, + NULL, NULL, NULL }, { @@ -1751,7 +1882,8 @@ static struct config_int ConfigureNamesInt[] = "objects will need to be locked at any one time.") }, &max_predicate_locks_per_xact, - 64, 10, INT_MAX, NULL, NULL + 64, 10, INT_MAX, + NULL, NULL, NULL }, { @@ -1761,7 +1893,8 @@ static struct config_int ConfigureNamesInt[] = GUC_UNIT_S }, &AuthenticationTimeout, - 60, 1, 600, NULL, NULL + 60, 1, 600, + NULL, NULL, NULL }, { @@ -1772,7 +1905,8 @@ static struct config_int ConfigureNamesInt[] = GUC_NOT_IN_SAMPLE | GUC_UNIT_S }, &PreAuthDelay, - 0, 0, 60, NULL, NULL + 0, 0, 60, + NULL, NULL, NULL }, { @@ -1781,7 +1915,8 @@ static struct config_int ConfigureNamesInt[] = NULL }, &wal_keep_segments, - 0, 0, INT_MAX, NULL, NULL + 0, 0, INT_MAX, + NULL, NULL, NULL }, { @@ -1790,7 +1925,8 @@ static struct config_int ConfigureNamesInt[] = NULL }, &CheckPointSegments, - 3, 1, INT_MAX, NULL, NULL + 3, 1, INT_MAX, + NULL, NULL, NULL }, { @@ -1800,7 +1936,8 @@ static struct config_int ConfigureNamesInt[] = GUC_UNIT_S }, &CheckPointTimeout, - 300, 30, 3600, NULL, NULL + 300, 30, 3600, + NULL, NULL, NULL }, { @@ -1813,7 +1950,8 @@ static struct config_int ConfigureNamesInt[] = GUC_UNIT_S }, &CheckPointWarning, - 30, 0, INT_MAX, NULL, NULL + 30, 0, INT_MAX, + NULL, NULL, NULL }, { @@ -1823,7 +1961,8 @@ static struct config_int ConfigureNamesInt[] = GUC_UNIT_XBLOCKS }, &XLOGbuffers, - -1, -1, INT_MAX, NULL, NULL + -1, -1, INT_MAX, + check_wal_buffers, NULL, NULL }, { @@ -1833,7 +1972,8 @@ static struct config_int ConfigureNamesInt[] = GUC_UNIT_MS }, &WalWriterDelay, - 200, 1, 10000, NULL, NULL + 200, 1, 10000, + NULL, NULL, NULL }, { @@ -1843,7 +1983,8 @@ static struct config_int ConfigureNamesInt[] = NULL }, &max_wal_senders, - 0, 0, MAX_BACKENDS, NULL, NULL + 0, 0, MAX_BACKENDS, + NULL, NULL, NULL }, { @@ -1853,7 +1994,8 @@ static struct config_int ConfigureNamesInt[] = GUC_UNIT_MS }, &WalSndDelay, - 1000, 1, 10000, NULL, NULL + 1000, 1, 10000, + NULL, NULL, NULL }, { @@ -1863,7 +2005,8 @@ static struct config_int ConfigureNamesInt[] = GUC_UNIT_MS }, &replication_timeout, - 60 * 1000, 0, INT_MAX, NULL, NULL + 60 * 1000, 0, INT_MAX, + NULL, NULL, NULL }, { @@ -1873,7 +2016,8 @@ static struct config_int ConfigureNamesInt[] = NULL }, &CommitDelay, - 0, 0, 100000, NULL, NULL + 0, 0, 100000, + NULL, NULL, NULL }, { @@ -1883,7 +2027,8 @@ static struct config_int ConfigureNamesInt[] = NULL }, &CommitSiblings, - 5, 0, 1000, NULL, NULL + 5, 0, 1000, + NULL, NULL, NULL }, { @@ -1894,7 +2039,8 @@ static struct config_int ConfigureNamesInt[] = "(FLT_DIG or DBL_DIG as appropriate).") }, &extra_float_digits, - 0, -15, 3, NULL, NULL + 0, -15, 3, + NULL, NULL, NULL }, { @@ -1905,7 +2051,8 @@ static struct config_int ConfigureNamesInt[] = GUC_UNIT_MS }, &log_min_duration_statement, - -1, -1, INT_MAX, NULL, NULL + -1, -1, INT_MAX, + NULL, NULL, NULL }, { @@ -1916,7 +2063,8 @@ static struct config_int ConfigureNamesInt[] = GUC_UNIT_MS }, &Log_autovacuum_min_duration, - -1, -1, INT_MAX, NULL, NULL + -1, -1, INT_MAX, + NULL, NULL, NULL }, { @@ -1926,7 +2074,8 @@ static struct config_int ConfigureNamesInt[] = GUC_UNIT_MS }, &BgWriterDelay, - 200, 10, 10000, NULL, NULL + 200, 10, 10000, + NULL, NULL, NULL }, { @@ -1935,7 +2084,8 @@ static struct config_int ConfigureNamesInt[] = NULL }, &bgwriter_lru_maxpages, - 100, 0, 1000, NULL, NULL + 100, 0, 1000, + NULL, NULL, NULL }, { @@ -1955,7 +2105,7 @@ static struct config_int ConfigureNamesInt[] = #else 0, 0, 0, #endif - assign_effective_io_concurrency, NULL + check_effective_io_concurrency, assign_effective_io_concurrency, NULL }, { @@ -1965,7 +2115,8 @@ static struct config_int ConfigureNamesInt[] = GUC_UNIT_MIN }, &Log_RotationAge, - HOURS_PER_DAY * MINS_PER_HOUR, 0, INT_MAX / MINS_PER_HOUR, NULL, NULL + HOURS_PER_DAY * MINS_PER_HOUR, 0, INT_MAX / MINS_PER_HOUR, + NULL, NULL, NULL }, { @@ -1975,7 +2126,8 @@ static struct config_int ConfigureNamesInt[] = GUC_UNIT_KB }, &Log_RotationSize, - 10 * 1024, 0, INT_MAX / 1024, NULL, NULL + 10 * 1024, 0, INT_MAX / 1024, + NULL, NULL, NULL }, { @@ -1985,7 +2137,8 @@ static struct config_int ConfigureNamesInt[] = GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE }, &max_function_args, - FUNC_MAX_ARGS, FUNC_MAX_ARGS, FUNC_MAX_ARGS, NULL, NULL + FUNC_MAX_ARGS, FUNC_MAX_ARGS, FUNC_MAX_ARGS, + NULL, NULL, NULL }, { @@ -1995,7 +2148,8 @@ static struct config_int ConfigureNamesInt[] = GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE }, &max_index_keys, - INDEX_MAX_KEYS, INDEX_MAX_KEYS, INDEX_MAX_KEYS, NULL, NULL + INDEX_MAX_KEYS, INDEX_MAX_KEYS, INDEX_MAX_KEYS, + NULL, NULL, NULL }, { @@ -2005,7 +2159,8 @@ static struct config_int ConfigureNamesInt[] = GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE }, &max_identifier_length, - NAMEDATALEN - 1, NAMEDATALEN - 1, NAMEDATALEN - 1, NULL, NULL + NAMEDATALEN - 1, NAMEDATALEN - 1, NAMEDATALEN - 1, + NULL, NULL, NULL }, { @@ -2015,7 +2170,8 @@ static struct config_int ConfigureNamesInt[] = GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE }, &block_size, - BLCKSZ, BLCKSZ, BLCKSZ, NULL, NULL + BLCKSZ, BLCKSZ, BLCKSZ, + NULL, NULL, NULL }, { @@ -2025,7 +2181,8 @@ static struct config_int ConfigureNamesInt[] = GUC_UNIT_BLOCKS | GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE }, &segment_size, - RELSEG_SIZE, RELSEG_SIZE, RELSEG_SIZE, NULL, NULL + RELSEG_SIZE, RELSEG_SIZE, RELSEG_SIZE, + NULL, NULL, NULL }, { @@ -2035,7 +2192,8 @@ static struct config_int ConfigureNamesInt[] = GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE }, &wal_block_size, - XLOG_BLCKSZ, XLOG_BLCKSZ, XLOG_BLCKSZ, NULL, NULL + XLOG_BLCKSZ, XLOG_BLCKSZ, XLOG_BLCKSZ, + NULL, NULL, NULL }, { @@ -2048,7 +2206,7 @@ static struct config_int ConfigureNamesInt[] = (XLOG_SEG_SIZE / XLOG_BLCKSZ), (XLOG_SEG_SIZE / XLOG_BLCKSZ), (XLOG_SEG_SIZE / XLOG_BLCKSZ), - NULL, NULL + NULL, NULL, NULL }, { @@ -2058,7 +2216,8 @@ static struct config_int ConfigureNamesInt[] = GUC_UNIT_S }, &autovacuum_naptime, - 60, 1, INT_MAX / 1000, NULL, NULL + 60, 1, INT_MAX / 1000, + NULL, NULL, NULL }, { {"autovacuum_vacuum_threshold", PGC_SIGHUP, AUTOVACUUM, @@ -2066,7 +2225,8 @@ static struct config_int ConfigureNamesInt[] = NULL }, &autovacuum_vac_thresh, - 50, 0, INT_MAX, NULL, NULL + 50, 0, INT_MAX, + NULL, NULL, NULL }, { {"autovacuum_analyze_threshold", PGC_SIGHUP, AUTOVACUUM, @@ -2074,7 +2234,8 @@ static struct config_int ConfigureNamesInt[] = NULL }, &autovacuum_anl_thresh, - 50, 0, INT_MAX, NULL, NULL + 50, 0, INT_MAX, + NULL, NULL, NULL }, { /* see varsup.c for why this is PGC_POSTMASTER not PGC_SIGHUP */ @@ -2084,7 +2245,8 @@ static struct config_int ConfigureNamesInt[] = }, &autovacuum_freeze_max_age, /* see pg_resetxlog if you change the upper-limit value */ - 200000000, 100000000, 2000000000, NULL, NULL + 200000000, 100000000, 2000000000, + NULL, NULL, NULL }, { /* see max_connections */ @@ -2093,7 +2255,8 @@ static struct config_int ConfigureNamesInt[] = NULL }, &autovacuum_max_workers, - 3, 1, MAX_BACKENDS, assign_autovacuum_max_workers, NULL + 3, 1, MAX_BACKENDS, + check_autovacuum_max_workers, assign_autovacuum_max_workers, NULL }, { @@ -2103,7 +2266,8 @@ static struct config_int ConfigureNamesInt[] = GUC_UNIT_S }, &tcp_keepalives_idle, - 0, 0, INT_MAX, assign_tcp_keepalives_idle, show_tcp_keepalives_idle + 0, 0, INT_MAX, + NULL, assign_tcp_keepalives_idle, show_tcp_keepalives_idle }, { @@ -2113,7 +2277,8 @@ static struct config_int ConfigureNamesInt[] = GUC_UNIT_S }, &tcp_keepalives_interval, - 0, 0, INT_MAX, assign_tcp_keepalives_interval, show_tcp_keepalives_interval + 0, 0, INT_MAX, + NULL, assign_tcp_keepalives_interval, show_tcp_keepalives_interval }, { @@ -2123,7 +2288,8 @@ static struct config_int ConfigureNamesInt[] = GUC_UNIT_KB, }, &ssl_renegotiation_limit, - 512 * 1024, 0, MAX_KILOBYTES, NULL, NULL + 512 * 1024, 0, MAX_KILOBYTES, + NULL, NULL, NULL }, { @@ -2134,7 +2300,8 @@ static struct config_int ConfigureNamesInt[] = "system default."), }, &tcp_keepalives_count, - 0, 0, INT_MAX, assign_tcp_keepalives_count, show_tcp_keepalives_count + 0, 0, INT_MAX, + NULL, assign_tcp_keepalives_count, show_tcp_keepalives_count }, { @@ -2144,7 +2311,8 @@ static struct config_int ConfigureNamesInt[] = 0 }, &GinFuzzySearchLimit, - 0, 0, INT_MAX, NULL, NULL + 0, 0, INT_MAX, + NULL, NULL, NULL }, { @@ -2156,7 +2324,8 @@ static struct config_int ConfigureNamesInt[] = GUC_UNIT_BLOCKS, }, &effective_cache_size, - DEFAULT_EFFECTIVE_CACHE_SIZE, 1, INT_MAX, NULL, NULL + DEFAULT_EFFECTIVE_CACHE_SIZE, 1, INT_MAX, + NULL, NULL, NULL }, { @@ -2167,7 +2336,8 @@ static struct config_int ConfigureNamesInt[] = GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE }, &server_version_num, - PG_VERSION_NUM, PG_VERSION_NUM, PG_VERSION_NUM, NULL, NULL + PG_VERSION_NUM, PG_VERSION_NUM, PG_VERSION_NUM, + NULL, NULL, NULL }, { @@ -2177,7 +2347,8 @@ static struct config_int ConfigureNamesInt[] = GUC_UNIT_KB }, &log_temp_files, - -1, -1, INT_MAX, NULL, NULL + -1, -1, INT_MAX, + NULL, NULL, NULL }, { @@ -2186,12 +2357,13 @@ static struct config_int ConfigureNamesInt[] = NULL, }, &pgstat_track_activity_query_size, - 1024, 100, 102400, NULL, NULL + 1024, 100, 102400, + NULL, NULL, NULL }, /* End-of-list marker */ { - {NULL, 0, 0, NULL, NULL}, NULL, 0, 0, 0, NULL, NULL + {NULL, 0, 0, NULL, NULL}, NULL, 0, 0, 0, NULL, NULL, NULL } }; @@ -2205,7 +2377,8 @@ static struct config_real ConfigureNamesReal[] = NULL }, &seq_page_cost, - DEFAULT_SEQ_PAGE_COST, 0, DBL_MAX, NULL, NULL + DEFAULT_SEQ_PAGE_COST, 0, DBL_MAX, + NULL, NULL, NULL }, { {"random_page_cost", PGC_USERSET, QUERY_TUNING_COST, @@ -2214,7 +2387,8 @@ static struct config_real ConfigureNamesReal[] = NULL }, &random_page_cost, - DEFAULT_RANDOM_PAGE_COST, 0, DBL_MAX, NULL, NULL + DEFAULT_RANDOM_PAGE_COST, 0, DBL_MAX, + NULL, NULL, NULL }, { {"cpu_tuple_cost", PGC_USERSET, QUERY_TUNING_COST, @@ -2223,7 +2397,8 @@ static struct config_real ConfigureNamesReal[] = NULL }, &cpu_tuple_cost, - DEFAULT_CPU_TUPLE_COST, 0, DBL_MAX, NULL, NULL + DEFAULT_CPU_TUPLE_COST, 0, DBL_MAX, + NULL, NULL, NULL }, { {"cpu_index_tuple_cost", PGC_USERSET, QUERY_TUNING_COST, @@ -2232,7 +2407,8 @@ static struct config_real ConfigureNamesReal[] = NULL }, &cpu_index_tuple_cost, - DEFAULT_CPU_INDEX_TUPLE_COST, 0, DBL_MAX, NULL, NULL + DEFAULT_CPU_INDEX_TUPLE_COST, 0, DBL_MAX, + NULL, NULL, NULL }, { {"cpu_operator_cost", PGC_USERSET, QUERY_TUNING_COST, @@ -2241,7 +2417,8 @@ static struct config_real ConfigureNamesReal[] = NULL }, &cpu_operator_cost, - DEFAULT_CPU_OPERATOR_COST, 0, DBL_MAX, NULL, NULL + DEFAULT_CPU_OPERATOR_COST, 0, DBL_MAX, + NULL, NULL, NULL }, { @@ -2251,7 +2428,8 @@ static struct config_real ConfigureNamesReal[] = NULL }, &cursor_tuple_fraction, - DEFAULT_CURSOR_TUPLE_FRACTION, 0.0, 1.0, NULL, NULL + DEFAULT_CURSOR_TUPLE_FRACTION, 0.0, 1.0, + NULL, NULL, NULL }, { @@ -2260,8 +2438,9 @@ static struct config_real ConfigureNamesReal[] = NULL }, &Geqo_selection_bias, - DEFAULT_GEQO_SELECTION_BIAS, MIN_GEQO_SELECTION_BIAS, - MAX_GEQO_SELECTION_BIAS, NULL, NULL + DEFAULT_GEQO_SELECTION_BIAS, + MIN_GEQO_SELECTION_BIAS, MAX_GEQO_SELECTION_BIAS, + NULL, NULL, NULL }, { {"geqo_seed", PGC_USERSET, QUERY_TUNING_GEQO, @@ -2269,7 +2448,8 @@ static struct config_real ConfigureNamesReal[] = NULL }, &Geqo_seed, - 0.0, 0.0, 1.0, NULL, NULL + 0.0, 0.0, 1.0, + NULL, NULL, NULL }, { @@ -2278,7 +2458,8 @@ static struct config_real ConfigureNamesReal[] = NULL }, &bgwriter_lru_multiplier, - 2.0, 0.0, 10.0, NULL, NULL + 2.0, 0.0, 10.0, + NULL, NULL, NULL }, { @@ -2288,7 +2469,8 @@ static struct config_real ConfigureNamesReal[] = GUC_NO_SHOW_ALL | GUC_NO_RESET_ALL | GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE }, &phony_random_seed, - 0.0, -1.0, 1.0, assign_random_seed, show_random_seed + 0.0, -1.0, 1.0, + check_random_seed, assign_random_seed, show_random_seed }, { @@ -2297,7 +2479,8 @@ static struct config_real ConfigureNamesReal[] = NULL }, &autovacuum_vac_scale, - 0.2, 0.0, 100.0, NULL, NULL + 0.2, 0.0, 100.0, + NULL, NULL, NULL }, { {"autovacuum_analyze_scale_factor", PGC_SIGHUP, AUTOVACUUM, @@ -2305,7 +2488,8 @@ static struct config_real ConfigureNamesReal[] = NULL }, &autovacuum_anl_scale, - 0.1, 0.0, 100.0, NULL, NULL + 0.1, 0.0, 100.0, + NULL, NULL, NULL }, { @@ -2314,12 +2498,13 @@ static struct config_real ConfigureNamesReal[] = NULL }, &CheckPointCompletionTarget, - 0.5, 0.0, 1.0, NULL, NULL + 0.5, 0.0, 1.0, + NULL, NULL, NULL }, /* End-of-list marker */ { - {NULL, 0, 0, NULL, NULL}, NULL, 0.0, 0.0, 0.0, NULL, NULL + {NULL, 0, 0, NULL, NULL}, NULL, 0.0, 0.0, 0.0, NULL, NULL, NULL } }; @@ -2332,7 +2517,8 @@ static struct config_string ConfigureNamesString[] = NULL }, &XLogArchiveCommand, - "", NULL, show_archive_command + "", + NULL, NULL, show_archive_command }, { @@ -2342,7 +2528,8 @@ static struct config_string ConfigureNamesString[] = GUC_IS_NAME | GUC_REPORT }, &client_encoding_string, - "SQL_ASCII", assign_client_encoding, NULL + "SQL_ASCII", + check_client_encoding, assign_client_encoding, NULL }, { @@ -2351,7 +2538,8 @@ static struct config_string ConfigureNamesString[] = gettext_noop("If blank, no prefix is used.") }, &Log_line_prefix, - "", NULL, NULL + "", + NULL, NULL, NULL }, { @@ -2360,7 +2548,8 @@ static struct config_string ConfigureNamesString[] = NULL }, &log_timezone_string, - "UNKNOWN", assign_log_timezone, show_log_timezone + NULL, + check_log_timezone, assign_log_timezone, show_log_timezone }, { @@ -2371,7 +2560,8 @@ static struct config_string ConfigureNamesString[] = GUC_LIST_INPUT | GUC_REPORT }, &datestyle_string, - "ISO, MDY", assign_datestyle, NULL + "ISO, MDY", + check_datestyle, assign_datestyle, NULL }, { @@ -2381,7 +2571,8 @@ static struct config_string ConfigureNamesString[] = GUC_IS_NAME }, &default_tablespace, - "", assign_default_tablespace, NULL + "", + check_default_tablespace, NULL, NULL }, { @@ -2391,7 +2582,8 @@ static struct config_string ConfigureNamesString[] = GUC_LIST_INPUT | GUC_LIST_QUOTE }, &temp_tablespaces, - "", assign_temp_tablespaces, NULL + "", + check_temp_tablespaces, assign_temp_tablespaces, NULL }, { @@ -2404,7 +2596,8 @@ static struct config_string ConfigureNamesString[] = GUC_SUPERUSER_ONLY }, &Dynamic_library_path, - "$libdir", NULL, NULL + "$libdir", + NULL, NULL, NULL }, { @@ -2414,7 +2607,8 @@ static struct config_string ConfigureNamesString[] = GUC_SUPERUSER_ONLY }, &pg_krb_server_keyfile, - PG_KRB_SRVTAB, NULL, NULL + PG_KRB_SRVTAB, + NULL, NULL, NULL }, { @@ -2423,7 +2617,8 @@ static struct config_string ConfigureNamesString[] = NULL }, &pg_krb_srvnam, - PG_KRB_SRVNAM, NULL, NULL + PG_KRB_SRVNAM, + NULL, NULL, NULL }, { @@ -2432,7 +2627,8 @@ static struct config_string ConfigureNamesString[] = NULL }, &bonjour_name, - "", NULL, NULL + "", + NULL, NULL, NULL }, /* See main.c about why defaults for LC_foo are not all alike */ @@ -2444,7 +2640,8 @@ static struct config_string ConfigureNamesString[] = GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE }, &locale_collate, - "C", NULL, NULL + "C", + NULL, NULL, NULL }, { @@ -2454,7 +2651,8 @@ static struct config_string ConfigureNamesString[] = GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE }, &locale_ctype, - "C", NULL, NULL + "C", + NULL, NULL, NULL }, { @@ -2463,7 +2661,8 @@ static struct config_string ConfigureNamesString[] = NULL }, &locale_messages, - "", locale_messages_assign, NULL + "", + check_locale_messages, assign_locale_messages, NULL }, { @@ -2472,7 +2671,8 @@ static struct config_string ConfigureNamesString[] = NULL }, &locale_monetary, - "C", locale_monetary_assign, NULL + "C", + check_locale_monetary, assign_locale_monetary, NULL }, { @@ -2481,7 +2681,8 @@ static struct config_string ConfigureNamesString[] = NULL }, &locale_numeric, - "C", locale_numeric_assign, NULL + "C", + check_locale_numeric, assign_locale_numeric, NULL }, { @@ -2490,7 +2691,8 @@ static struct config_string ConfigureNamesString[] = NULL }, &locale_time, - "C", locale_time_assign, NULL + "C", + check_locale_time, assign_locale_time, NULL }, { @@ -2500,7 +2702,8 @@ static struct config_string ConfigureNamesString[] = GUC_LIST_INPUT | GUC_LIST_QUOTE | GUC_SUPERUSER_ONLY }, &shared_preload_libraries_string, - "", NULL, NULL + "", + NULL, NULL, NULL }, { @@ -2510,7 +2713,8 @@ static struct config_string ConfigureNamesString[] = GUC_LIST_INPUT | GUC_LIST_QUOTE }, &local_preload_libraries_string, - "", NULL, NULL + "", + NULL, NULL, NULL }, { @@ -2520,7 +2724,8 @@ static struct config_string ConfigureNamesString[] = GUC_LIST_INPUT | GUC_LIST_QUOTE }, &namespace_search_path, - "\"$user\",public", assign_search_path, NULL + "\"$user\",public", + check_search_path, assign_search_path, NULL }, { @@ -2531,7 +2736,8 @@ static struct config_string ConfigureNamesString[] = GUC_IS_NAME | GUC_REPORT | GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE }, &server_encoding_string, - "SQL_ASCII", NULL, NULL + "SQL_ASCII", + NULL, NULL, NULL }, { @@ -2542,7 +2748,8 @@ static struct config_string ConfigureNamesString[] = GUC_REPORT | GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE }, &server_version_string, - PG_VERSION, NULL, NULL + PG_VERSION, + NULL, NULL, NULL }, { @@ -2553,7 +2760,8 @@ static struct config_string ConfigureNamesString[] = GUC_IS_NAME | GUC_NO_SHOW_ALL | GUC_NO_RESET_ALL | GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE | GUC_NOT_WHILE_SEC_REST }, &role_string, - "none", assign_role, show_role + "none", + check_role, assign_role, show_role }, { @@ -2564,7 +2772,8 @@ static struct config_string ConfigureNamesString[] = GUC_IS_NAME | GUC_REPORT | GUC_NO_SHOW_ALL | GUC_NO_RESET_ALL | GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE | GUC_NOT_WHILE_SEC_REST }, &session_authorization_string, - NULL, assign_session_authorization, show_session_authorization + NULL, + check_session_authorization, assign_session_authorization, NULL }, { @@ -2576,7 +2785,8 @@ static struct config_string ConfigureNamesString[] = GUC_LIST_INPUT }, &log_destination_string, - "stderr", assign_log_destination, NULL + "stderr", + check_log_destination, assign_log_destination, NULL }, { {"log_directory", PGC_SIGHUP, LOGGING_WHERE, @@ -2586,7 +2796,8 @@ static struct config_string ConfigureNamesString[] = GUC_SUPERUSER_ONLY }, &Log_directory, - "pg_log", assign_canonical_path, NULL + "pg_log", + check_canonical_path, NULL, NULL }, { {"log_filename", PGC_SIGHUP, LOGGING_WHERE, @@ -2595,7 +2806,8 @@ static struct config_string ConfigureNamesString[] = GUC_SUPERUSER_ONLY }, &Log_filename, - "postgresql-%Y-%m-%d_%H%M%S.log", NULL, NULL + "postgresql-%Y-%m-%d_%H%M%S.log", + NULL, NULL, NULL }, { @@ -2605,7 +2817,8 @@ static struct config_string ConfigureNamesString[] = NULL }, &syslog_ident_str, - "postgres", assign_syslog_ident, NULL + "postgres", + NULL, assign_syslog_ident, NULL }, { @@ -2615,7 +2828,8 @@ static struct config_string ConfigureNamesString[] = GUC_REPORT }, &timezone_string, - "UNKNOWN", assign_timezone, show_timezone + NULL, + check_timezone, assign_timezone, show_timezone }, { {"timezone_abbreviations", PGC_USERSET, CLIENT_CONN_LOCALE, @@ -2623,7 +2837,8 @@ static struct config_string ConfigureNamesString[] = NULL }, &timezone_abbreviations_string, - "UNKNOWN", assign_timezone_abbreviations, NULL + NULL, + check_timezone_abbreviations, assign_timezone_abbreviations, NULL }, { @@ -2633,7 +2848,8 @@ static struct config_string ConfigureNamesString[] = GUC_NO_RESET_ALL | GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE }, &XactIsoLevel_string, - NULL, assign_XactIsoLevel, show_XactIsoLevel + "default", + check_XactIsoLevel, assign_XactIsoLevel, show_XactIsoLevel }, { @@ -2643,7 +2859,8 @@ static struct config_string ConfigureNamesString[] = "that starts the server.") }, &Unix_socket_group, - "", NULL, NULL + "", + NULL, NULL, NULL }, { @@ -2653,7 +2870,8 @@ static struct config_string ConfigureNamesString[] = GUC_SUPERUSER_ONLY }, &UnixSocketDir, - "", assign_canonical_path, NULL + "", + check_canonical_path, NULL, NULL }, { @@ -2663,7 +2881,8 @@ static struct config_string ConfigureNamesString[] = GUC_LIST_INPUT }, &ListenAddresses, - "localhost", NULL, NULL + "localhost", + NULL, NULL, NULL }, { @@ -2673,7 +2892,8 @@ static struct config_string ConfigureNamesString[] = GUC_LIST_INPUT | GUC_LIST_QUOTE }, &custom_variable_classes, - NULL, assign_custom_variable_classes, NULL + NULL, + check_custom_variable_classes, NULL, NULL }, { @@ -2683,6 +2903,7 @@ static struct config_string ConfigureNamesString[] = GUC_SUPERUSER_ONLY }, &data_directory, + NULL, NULL, NULL, NULL }, @@ -2693,6 +2914,7 @@ static struct config_string ConfigureNamesString[] = GUC_DISALLOW_IN_FILE | GUC_SUPERUSER_ONLY }, &ConfigFileName, + NULL, NULL, NULL, NULL }, @@ -2703,6 +2925,7 @@ static struct config_string ConfigureNamesString[] = GUC_SUPERUSER_ONLY }, &HbaFileName, + NULL, NULL, NULL, NULL }, @@ -2713,6 +2936,7 @@ static struct config_string ConfigureNamesString[] = GUC_SUPERUSER_ONLY }, &IdentFileName, + NULL, NULL, NULL, NULL }, @@ -2723,7 +2947,8 @@ static struct config_string ConfigureNamesString[] = GUC_SUPERUSER_ONLY }, &external_pid_file, - NULL, assign_canonical_path, NULL + NULL, + check_canonical_path, NULL, NULL }, { @@ -2733,7 +2958,8 @@ static struct config_string ConfigureNamesString[] = GUC_SUPERUSER_ONLY }, &pgstat_temp_directory, - "pg_stat_tmp", assign_pgstat_temp_directory, NULL + "pg_stat_tmp", + check_canonical_path, assign_pgstat_temp_directory, NULL }, { @@ -2743,7 +2969,8 @@ static struct config_string ConfigureNamesString[] = GUC_LIST_INPUT }, &SyncRepStandbyNames, - "", assign_synchronous_standby_names, NULL + "", + check_synchronous_standby_names, NULL, NULL }, { @@ -2752,7 +2979,8 @@ static struct config_string ConfigureNamesString[] = NULL }, &TSCurrentConfig, - "pg_catalog.simple", assignTSCurrentConfig, NULL + "pg_catalog.simple", + check_TSCurrentConfig, assign_TSCurrentConfig, NULL }, { @@ -2767,7 +2995,7 @@ static struct config_string ConfigureNamesString[] = #else "none", #endif - NULL, NULL + NULL, NULL, NULL }, { @@ -2777,12 +3005,13 @@ static struct config_string ConfigureNamesString[] = GUC_IS_NAME | GUC_REPORT | GUC_NOT_IN_SAMPLE }, &application_name, - "", assign_application_name, NULL + "", + check_application_name, assign_application_name, NULL }, /* End-of-list marker */ { - {NULL, 0, 0, NULL, NULL}, NULL, NULL, NULL, NULL + {NULL, 0, 0, NULL, NULL}, NULL, NULL, NULL, NULL, NULL } }; @@ -2795,7 +3024,8 @@ static struct config_enum ConfigureNamesEnum[] = NULL }, &backslash_quote, - BACKSLASH_QUOTE_SAFE_ENCODING, backslash_quote_options, NULL, NULL + BACKSLASH_QUOTE_SAFE_ENCODING, backslash_quote_options, + NULL, NULL, NULL }, { @@ -2804,7 +3034,8 @@ static struct config_enum ConfigureNamesEnum[] = NULL }, &bytea_output, - BYTEA_OUTPUT_HEX, bytea_output_options, NULL, NULL + BYTEA_OUTPUT_HEX, bytea_output_options, + NULL, NULL, NULL }, { @@ -2814,7 +3045,8 @@ static struct config_enum ConfigureNamesEnum[] = " the level, the fewer messages are sent.") }, &client_min_messages, - NOTICE, client_message_level_options, NULL, NULL + NOTICE, client_message_level_options, + NULL, NULL, NULL }, { @@ -2825,7 +3057,7 @@ static struct config_enum ConfigureNamesEnum[] = }, &constraint_exclusion, CONSTRAINT_EXCLUSION_PARTITION, constraint_exclusion_options, - NULL, NULL + NULL, NULL, NULL }, { @@ -2834,7 +3066,8 @@ static struct config_enum ConfigureNamesEnum[] = NULL }, &DefaultXactIsoLevel, - XACT_READ_COMMITTED, isolation_level_options, NULL, NULL + XACT_READ_COMMITTED, isolation_level_options, + NULL, NULL, NULL }, { @@ -2844,7 +3077,8 @@ static struct config_enum ConfigureNamesEnum[] = GUC_REPORT }, &IntervalStyle, - INTSTYLE_POSTGRES, intervalstyle_options, NULL, NULL + INTSTYLE_POSTGRES, intervalstyle_options, + NULL, NULL, NULL }, { @@ -2853,7 +3087,8 @@ static struct config_enum ConfigureNamesEnum[] = NULL }, &Log_error_verbosity, - PGERROR_DEFAULT, log_error_verbosity_options, NULL, NULL + PGERROR_DEFAULT, log_error_verbosity_options, + NULL, NULL, NULL }, { @@ -2863,7 +3098,8 @@ static struct config_enum ConfigureNamesEnum[] = " the level, the fewer messages are sent.") }, &log_min_messages, - WARNING, server_message_level_options, NULL, NULL + WARNING, server_message_level_options, + NULL, NULL, NULL }, { @@ -2873,7 +3109,8 @@ static struct config_enum ConfigureNamesEnum[] = " the level, the fewer messages are sent.") }, &log_min_error_statement, - ERROR, server_message_level_options, NULL, NULL + ERROR, server_message_level_options, + NULL, NULL, NULL }, { @@ -2882,7 +3119,8 @@ static struct config_enum ConfigureNamesEnum[] = NULL }, &log_statement, - LOGSTMT_NONE, log_statement_options, NULL, NULL + LOGSTMT_NONE, log_statement_options, + NULL, NULL, NULL }, { @@ -2896,7 +3134,8 @@ static struct config_enum ConfigureNamesEnum[] = #else 0, #endif - syslog_facility_options, assign_syslog_facility, NULL + syslog_facility_options, + NULL, assign_syslog_facility, NULL }, { @@ -2906,7 +3145,7 @@ static struct config_enum ConfigureNamesEnum[] = }, &SessionReplicationRole, SESSION_REPLICATION_ROLE_ORIGIN, session_replication_role_options, - assign_session_replication_role, NULL + NULL, assign_session_replication_role, NULL }, { @@ -2916,7 +3155,7 @@ static struct config_enum ConfigureNamesEnum[] = }, &synchronous_commit, SYNCHRONOUS_COMMIT_ON, synchronous_commit_options, - NULL, NULL + NULL, NULL, NULL }, { @@ -2930,7 +3169,8 @@ static struct config_enum ConfigureNamesEnum[] = * client_message_level_options allows too many values, really, * but it's not worth having a separate options array for this. */ - LOG, client_message_level_options, NULL, NULL + LOG, client_message_level_options, + NULL, NULL, NULL }, { @@ -2939,7 +3179,8 @@ static struct config_enum ConfigureNamesEnum[] = NULL }, &pgstat_track_functions, - TRACK_FUNC_OFF, track_function_options, NULL, NULL + TRACK_FUNC_OFF, track_function_options, + NULL, NULL, NULL }, { @@ -2948,7 +3189,8 @@ static struct config_enum ConfigureNamesEnum[] = NULL }, &wal_level, - WAL_LEVEL_MINIMAL, wal_level_options, NULL + WAL_LEVEL_MINIMAL, wal_level_options, + NULL, NULL, NULL }, { @@ -2958,7 +3200,7 @@ static struct config_enum ConfigureNamesEnum[] = }, &sync_method, DEFAULT_SYNC_METHOD, sync_method_options, - assign_xlog_sync_method, NULL + NULL, assign_xlog_sync_method, NULL }, { @@ -2967,7 +3209,8 @@ static struct config_enum ConfigureNamesEnum[] = NULL }, &xmlbinary, - XMLBINARY_BASE64, xmlbinary_options, NULL, NULL + XMLBINARY_BASE64, xmlbinary_options, + NULL, NULL, NULL }, { @@ -2977,13 +3220,14 @@ static struct config_enum ConfigureNamesEnum[] = NULL }, &xmloption, - XMLOPTION_CONTENT, xmloption_options, NULL, NULL + XMLOPTION_CONTENT, xmloption_options, + NULL, NULL, NULL }, /* End-of-list marker */ { - {NULL, 0, 0, NULL, NULL}, NULL, 0, NULL, NULL, NULL + {NULL, 0, 0, NULL, NULL}, NULL, 0, NULL, NULL, NULL, NULL } }; @@ -3030,7 +3274,6 @@ static void ReportGUCOption(struct config_generic * record); static void ShowGUCConfigOption(const char *name, DestReceiver *dest); static void ShowAllGUCConfig(DestReceiver *dest); static char *_ShowOption(struct config_generic * record, bool use_units); -static bool is_newvalue_equal(struct config_generic * record, const char *newvalue); static bool validate_option_array_item(const char *name, const char *value, bool skipIfNoPermissions); @@ -3078,37 +3321,6 @@ guc_strdup(int elevel, const char *src) } -/* - * Support for assigning to a field of a string GUC item. Free the prior - * value if it's not referenced anywhere else in the item (including stacked - * states). - */ -static void -set_string_field(struct config_string * conf, char **field, char *newval) -{ - char *oldval = *field; - GucStack *stack; - - /* Do the assignment */ - *field = newval; - - /* Exit if any duplicate references, or if old value was NULL anyway */ - if (oldval == NULL || - oldval == *(conf->variable) || - oldval == conf->reset_val || - oldval == conf->boot_val) - return; - for (stack = conf->gen.stack; stack; stack = stack->prev) - { - if (oldval == stack->prior.stringval || - oldval == stack->masked.stringval) - return; - } - - /* Not used anymore, so free it */ - free(oldval); -} - /* * Detect whether strval is referenced anywhere in a GUC string item */ @@ -3123,51 +3335,135 @@ string_field_used(struct config_string * conf, char *strval) return true; for (stack = conf->gen.stack; stack; stack = stack->prev) { - if (strval == stack->prior.stringval || - strval == stack->masked.stringval) + if (strval == stack->prior.val.stringval || + strval == stack->masked.val.stringval) return true; } return false; } /* - * Support for copying a variable's active value into a stack entry + * Support for assigning to a field of a string GUC item. Free the prior + * value if it's not referenced anywhere else in the item (including stacked + * states). */ static void -set_stack_value(struct config_generic * gconf, union config_var_value * val) +set_string_field(struct config_string * conf, char **field, char *newval) +{ + char *oldval = *field; + + /* Do the assignment */ + *field = newval; + + /* Free old value if it's not NULL and isn't referenced anymore */ + if (oldval && !string_field_used(conf, oldval)) + free(oldval); +} + +/* + * Detect whether an "extra" struct is referenced anywhere in a GUC item + */ +static bool +extra_field_used(struct config_generic * gconf, void *extra) +{ + GucStack *stack; + + if (extra == gconf->extra) + return true; + switch (gconf->vartype) + { + case PGC_BOOL: + if (extra == ((struct config_bool *) gconf)->reset_extra) + return true; + break; + case PGC_INT: + if (extra == ((struct config_int *) gconf)->reset_extra) + return true; + break; + case PGC_REAL: + if (extra == ((struct config_real *) gconf)->reset_extra) + return true; + break; + case PGC_STRING: + if (extra == ((struct config_string *) gconf)->reset_extra) + return true; + break; + case PGC_ENUM: + if (extra == ((struct config_enum *) gconf)->reset_extra) + return true; + break; + } + for (stack = gconf->stack; stack; stack = stack->prev) + { + if (extra == stack->prior.extra || + extra == stack->masked.extra) + return true; + } + + return false; +} + +/* + * Support for assigning to an "extra" field of a GUC item. Free the prior + * value if it's not referenced anywhere else in the item (including stacked + * states). + */ +static void +set_extra_field(struct config_generic * gconf, void **field, void *newval) +{ + void *oldval = *field; + + /* Do the assignment */ + *field = newval; + + /* Free old value if it's not NULL and isn't referenced anymore */ + if (oldval && !extra_field_used(gconf, oldval)) + free(oldval); +} + +/* + * Support for copying a variable's active value into a stack entry. + * The "extra" field associated with the active value is copied, too. + * + * NB: be sure stringval and extra fields of a new stack entry are + * initialized to NULL before this is used, else we'll try to free() them. + */ +static void +set_stack_value(struct config_generic * gconf, config_var_value *val) { switch (gconf->vartype) { case PGC_BOOL: - val->boolval = + val->val.boolval = *((struct config_bool *) gconf)->variable; break; case PGC_INT: - val->intval = + val->val.intval = *((struct config_int *) gconf)->variable; break; case PGC_REAL: - val->realval = + val->val.realval = *((struct config_real *) gconf)->variable; break; case PGC_STRING: - /* we assume stringval is NULL if not valid */ set_string_field((struct config_string *) gconf, - &(val->stringval), + &(val->val.stringval), *((struct config_string *) gconf)->variable); break; case PGC_ENUM: - val->enumval = + val->val.enumval = *((struct config_enum *) gconf)->variable; break; } + set_extra_field(gconf, &(val->extra), gconf->extra); } /* - * Support for discarding a no-longer-needed value in a stack entry + * Support for discarding a no-longer-needed value in a stack entry. + * The "extra" field associated with the stack entry is cleared, too. */ static void -discard_stack_value(struct config_generic * gconf, union config_var_value * val) +discard_stack_value(struct config_generic * gconf, config_var_value *val) { switch (gconf->vartype) { @@ -3179,10 +3475,11 @@ discard_stack_value(struct config_generic * gconf, union config_var_value * val) break; case PGC_STRING: set_string_field((struct config_string *) gconf, - &(val->stringval), + &(val->val.stringval), NULL); break; } + set_extra_field(gconf, &(val->extra), NULL); } @@ -3594,6 +3891,9 @@ InitializeGUCOptions(void) /* * Initialize one GUC option variable to its compiled-in default. + * + * Note: the reason for calling check_hooks is not that we think the boot_val + * might fail, but that the hooks might wish to compute an "extra" struct. */ static void InitializeOneGUCOption(struct config_generic * gconf) @@ -3602,6 +3902,7 @@ InitializeOneGUCOption(struct config_generic * gconf) gconf->reset_source = PGC_S_DEFAULT; gconf->source = PGC_S_DEFAULT; gconf->stack = NULL; + gconf->extra = NULL; gconf->sourcefile = NULL; gconf->sourceline = 0; @@ -3610,96 +3911,91 @@ InitializeOneGUCOption(struct config_generic * gconf) case PGC_BOOL: { struct config_bool *conf = (struct config_bool *) gconf; + bool newval = conf->boot_val; + void *extra = NULL; + if (!call_bool_check_hook(conf, &newval, &extra, + PGC_S_DEFAULT, LOG)) + elog(FATAL, "failed to initialize %s to %d", + conf->gen.name, (int) newval); 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; + (*conf->assign_hook) (newval, extra); + *conf->variable = conf->reset_val = newval; + conf->gen.extra = conf->reset_extra = extra; break; } case PGC_INT: { struct config_int *conf = (struct config_int *) gconf; + int newval = conf->boot_val; + void *extra = NULL; - Assert(conf->boot_val >= conf->min); - Assert(conf->boot_val <= conf->max); + Assert(newval >= conf->min); + Assert(newval <= conf->max); + if (!call_int_check_hook(conf, &newval, &extra, + PGC_S_DEFAULT, LOG)) + elog(FATAL, "failed to initialize %s to %d", + conf->gen.name, newval); 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; + (*conf->assign_hook) (newval, extra); + *conf->variable = conf->reset_val = newval; + conf->gen.extra = conf->reset_extra = extra; break; } case PGC_REAL: { struct config_real *conf = (struct config_real *) gconf; + double newval = conf->boot_val; + void *extra = NULL; - Assert(conf->boot_val >= conf->min); - Assert(conf->boot_val <= conf->max); + Assert(newval >= conf->min); + Assert(newval <= conf->max); + if (!call_real_check_hook(conf, &newval, &extra, + PGC_S_DEFAULT, LOG)) + elog(FATAL, "failed to initialize %s to %g", + conf->gen.name, newval); 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; + (*conf->assign_hook) (newval, extra); + *conf->variable = conf->reset_val = newval; + conf->gen.extra = conf->reset_extra = extra; break; } case PGC_STRING: { struct config_string *conf = (struct config_string *) gconf; - char *str; + char *newval; + void *extra = NULL; - *conf->variable = NULL; - conf->reset_val = NULL; - - if (conf->boot_val == NULL) - { - /* leave the value NULL, do not call assign hook */ - break; - } - - str = guc_strdup(FATAL, conf->boot_val); - conf->reset_val = str; + /* non-NULL boot_val must always get strdup'd */ + if (conf->boot_val != NULL) + newval = guc_strdup(FATAL, conf->boot_val); + else + newval = NULL; + if (!call_string_check_hook(conf, &newval, &extra, + PGC_S_DEFAULT, LOG)) + elog(FATAL, "failed to initialize %s to \"%s\"", + conf->gen.name, newval ? newval : ""); if (conf->assign_hook) - { - const char *newstr; - - newstr = (*conf->assign_hook) (str, true, - PGC_S_DEFAULT); - if (newstr == NULL) - { - elog(FATAL, "failed to initialize %s to \"%s\"", - conf->gen.name, str); - } - else if (newstr != str) - { - free(str); - - /* - * See notes in set_config_option about casting - */ - str = (char *) newstr; - conf->reset_val = str; - } - } - *conf->variable = str; + (*conf->assign_hook) (newval, extra); + *conf->variable = conf->reset_val = newval; + conf->gen.extra = conf->reset_extra = extra; break; } case PGC_ENUM: { struct config_enum *conf = (struct config_enum *) gconf; + int newval = conf->boot_val; + void *extra = NULL; + if (!call_enum_check_hook(conf, &newval, &extra, + PGC_S_DEFAULT, LOG)) + elog(FATAL, "failed to initialize %s to %d", + conf->gen.name, newval); if (conf->assign_hook) - if (!(*conf->assign_hook) (conf->boot_val, true, - PGC_S_DEFAULT)) - elog(FATAL, "failed to initialize %s to %s", - conf->gen.name, - config_enum_lookup_by_value(conf, conf->boot_val)); - *conf->variable = conf->reset_val = conf->boot_val; + (*conf->assign_hook) (newval, extra); + *conf->variable = conf->reset_val = newval; + conf->gen.extra = conf->reset_extra = extra; break; } } @@ -3888,11 +4184,11 @@ ResetAllOptions(void) struct config_bool *conf = (struct config_bool *) gconf; if (conf->assign_hook) - if (!(*conf->assign_hook) (conf->reset_val, true, - PGC_S_SESSION)) - elog(ERROR, "failed to reset %s to %d", - conf->gen.name, (int) conf->reset_val); + (*conf->assign_hook) (conf->reset_val, + conf->reset_extra); *conf->variable = conf->reset_val; + set_extra_field(&conf->gen, &conf->gen.extra, + conf->reset_extra); break; } case PGC_INT: @@ -3900,11 +4196,11 @@ ResetAllOptions(void) struct config_int *conf = (struct config_int *) gconf; if (conf->assign_hook) - if (!(*conf->assign_hook) (conf->reset_val, true, - PGC_S_SESSION)) - elog(ERROR, "failed to reset %s to %d", - conf->gen.name, conf->reset_val); + (*conf->assign_hook) (conf->reset_val, + conf->reset_extra); *conf->variable = conf->reset_val; + set_extra_field(&conf->gen, &conf->gen.extra, + conf->reset_extra); break; } case PGC_REAL: @@ -3912,40 +4208,23 @@ ResetAllOptions(void) struct config_real *conf = (struct config_real *) gconf; if (conf->assign_hook) - if (!(*conf->assign_hook) (conf->reset_val, true, - PGC_S_SESSION)) - elog(ERROR, "failed to reset %s to %g", - conf->gen.name, conf->reset_val); + (*conf->assign_hook) (conf->reset_val, + conf->reset_extra); *conf->variable = conf->reset_val; + set_extra_field(&conf->gen, &conf->gen.extra, + conf->reset_extra); break; } case PGC_STRING: { struct config_string *conf = (struct config_string *) gconf; - char *str; - /* We need not strdup here */ - str = conf->reset_val; - - if (conf->assign_hook && str) - { - const char *newstr; - - newstr = (*conf->assign_hook) (str, true, - PGC_S_SESSION); - if (newstr == NULL) - elog(ERROR, "failed to reset %s to \"%s\"", - conf->gen.name, str); - else if (newstr != str) - { - /* - * See notes in set_config_option about casting - */ - str = (char *) newstr; - } - } - - set_string_field(conf, conf->variable, str); + if (conf->assign_hook) + (*conf->assign_hook) (conf->reset_val, + conf->reset_extra); + set_string_field(conf, conf->variable, conf->reset_val); + set_extra_field(&conf->gen, &conf->gen.extra, + conf->reset_extra); break; } case PGC_ENUM: @@ -3953,12 +4232,11 @@ ResetAllOptions(void) struct config_enum *conf = (struct config_enum *) gconf; if (conf->assign_hook) - if (!(*conf->assign_hook) (conf->reset_val, true, - PGC_S_SESSION)) - elog(ERROR, "failed to reset %s to %s", - conf->gen.name, - config_enum_lookup_by_value(conf, conf->reset_val)); + (*conf->assign_hook) (conf->reset_val, + conf->reset_extra); *conf->variable = conf->reset_val; + set_extra_field(&conf->gen, &conf->gen.extra, + conf->reset_extra); break; } } @@ -4209,7 +4487,7 @@ AtEOXact_GUC(bool isCommit, int nestLevel) if (restorePrior || restoreMasked) { /* Perform appropriate restoration of the stacked value */ - union config_var_value newvalue; + config_var_value newvalue; GucSource newsource; if (restoreMasked) @@ -4228,16 +4506,17 @@ AtEOXact_GUC(bool isCommit, int nestLevel) case PGC_BOOL: { struct config_bool *conf = (struct config_bool *) gconf; - bool newval = newvalue.boolval; + bool newval = newvalue.val.boolval; + void *newextra = newvalue.extra; - if (*conf->variable != newval) + if (*conf->variable != newval || + conf->gen.extra != newextra) { if (conf->assign_hook) - if (!(*conf->assign_hook) (newval, - true, PGC_S_OVERRIDE)) - elog(LOG, "failed to commit %s as %d", - conf->gen.name, (int) newval); + (*conf->assign_hook) (newval, newextra); *conf->variable = newval; + set_extra_field(&conf->gen, &conf->gen.extra, + newextra); changed = true; } break; @@ -4245,16 +4524,17 @@ AtEOXact_GUC(bool isCommit, int nestLevel) case PGC_INT: { struct config_int *conf = (struct config_int *) gconf; - int newval = newvalue.intval; + int newval = newvalue.val.intval; + void *newextra = newvalue.extra; - if (*conf->variable != newval) + if (*conf->variable != newval || + conf->gen.extra != newextra) { if (conf->assign_hook) - if (!(*conf->assign_hook) (newval, - true, PGC_S_OVERRIDE)) - elog(LOG, "failed to commit %s as %d", - conf->gen.name, newval); + (*conf->assign_hook) (newval, newextra); *conf->variable = newval; + set_extra_field(&conf->gen, &conf->gen.extra, + newextra); changed = true; } break; @@ -4262,16 +4542,17 @@ AtEOXact_GUC(bool isCommit, int nestLevel) case PGC_REAL: { struct config_real *conf = (struct config_real *) gconf; - double newval = newvalue.realval; + double newval = newvalue.val.realval; + void *newextra = newvalue.extra; - if (*conf->variable != newval) + if (*conf->variable != newval || + conf->gen.extra != newextra) { if (conf->assign_hook) - if (!(*conf->assign_hook) (newval, - true, PGC_S_OVERRIDE)) - elog(LOG, "failed to commit %s as %g", - conf->gen.name, newval); + (*conf->assign_hook) (newval, newextra); *conf->variable = newval; + set_extra_field(&conf->gen, &conf->gen.extra, + newextra); changed = true; } break; @@ -4279,33 +4560,17 @@ AtEOXact_GUC(bool isCommit, int nestLevel) case PGC_STRING: { struct config_string *conf = (struct config_string *) gconf; - char *newval = newvalue.stringval; + char *newval = newvalue.val.stringval; + void *newextra = newvalue.extra; - if (*conf->variable != newval) + if (*conf->variable != newval || + conf->gen.extra != newextra) { - if (conf->assign_hook && newval) - { - const char *newstr; - - newstr = (*conf->assign_hook) (newval, true, - PGC_S_OVERRIDE); - if (newstr == NULL) - elog(LOG, "failed to commit %s as \"%s\"", - conf->gen.name, newval); - else if (newstr != newval) - { - /* - * If newval should now be freed, - * it'll be taken care of below. - * - * See notes in set_config_option - * about casting - */ - newval = (char *) newstr; - } - } - + if (conf->assign_hook) + (*conf->assign_hook) (newval, newextra); set_string_field(conf, conf->variable, newval); + set_extra_field(&conf->gen, &conf->gen.extra, + newextra); changed = true; } @@ -4315,30 +4580,36 @@ AtEOXact_GUC(bool isCommit, int nestLevel) * we have type-specific code anyway, might as * well inline it. */ - set_string_field(conf, &stack->prior.stringval, NULL); - set_string_field(conf, &stack->masked.stringval, NULL); + set_string_field(conf, &stack->prior.val.stringval, NULL); + set_string_field(conf, &stack->masked.val.stringval, NULL); break; } case PGC_ENUM: { struct config_enum *conf = (struct config_enum *) gconf; - int newval = newvalue.enumval; + int newval = newvalue.val.enumval; + void *newextra = newvalue.extra; - if (*conf->variable != newval) + if (*conf->variable != newval || + conf->gen.extra != newextra) { if (conf->assign_hook) - if (!(*conf->assign_hook) (newval, - true, PGC_S_OVERRIDE)) - elog(LOG, "failed to commit %s as %s", - conf->gen.name, - config_enum_lookup_by_value(conf, newval)); + (*conf->assign_hook) (newval, newextra); *conf->variable = newval; + set_extra_field(&conf->gen, &conf->gen.extra, + newextra); changed = true; } break; } } + /* + * Release stacked extra values if not used anymore. + */ + set_extra_field(gconf, &(stack->prior.extra), NULL); + set_extra_field(gconf, &(stack->masked.extra), NULL); + gconf->source = newsource; } @@ -4748,33 +5019,6 @@ config_enum_get_options(struct config_enum * record, const char *prefix, return retstr.data; } -/* - * Call a GucStringAssignHook function, being careful to free the - * "newval" string if the hook ereports. - * - * This is split out of set_config_option just to avoid the "volatile" - * qualifiers that would otherwise have to be plastered all over. - */ -static const char * -call_string_assign_hook(GucStringAssignHook assign_hook, - char *newval, bool doit, GucSource source) -{ - const char *result; - - PG_TRY(); - { - result = (*assign_hook) (newval, doit, source); - } - PG_CATCH(); - { - free(newval); - PG_RE_THROW(); - } - PG_END_TRY(); - - return result; -} - /* * Sets option `name' to given value. The value should be a string @@ -4810,6 +5054,7 @@ set_config_option(const char *name, const char *value, { struct config_generic *record; int elevel; + bool prohibitValueChange = false; bool makeDefault; if (context == PGC_SIGHUP || source == PGC_S_DEFAULT) @@ -4846,15 +5091,21 @@ set_config_option(const char *name, const char *value, /* * Check if the option can be set at this time. See guc.h for the precise - * rules. Note that we don't want to throw errors if we're in the SIGHUP - * context. In that case we just ignore the attempt and return true. + * rules. */ switch (record->context) { case PGC_INTERNAL: if (context == PGC_SIGHUP) + { + /* + * Historically we've just silently ignored attempts to set + * PGC_INTERNAL variables from the config file. Maybe it'd + * be better to use the prohibitValueChange logic for this? + */ return true; - if (context != PGC_INTERNAL) + } + else if (context != PGC_INTERNAL) { ereport(elevel, (errcode(ERRCODE_CANT_CHANGE_RUNTIME_PARAM), @@ -4867,19 +5118,23 @@ set_config_option(const char *name, const char *value, if (context == PGC_SIGHUP) { /* - * We are reading a PGC_POSTMASTER var from postgresql.conf. - * We can't change the setting, so give a warning if the DBA - * tries to change it. (Throwing an error would be more - * consistent, but seems overly rigid.) + * We are re-reading a PGC_POSTMASTER variable from + * postgresql.conf. We can't change the setting, so we should + * give a warning if the DBA tries to change it. However, + * because of variant formats, canonicalization by check + * hooks, etc, we can't just compare the given string directly + * to what's stored. Set a flag to check below after we have + * the final storable value. + * + * During the "checking" pass we just do nothing, to avoid + * printing the warning twice. */ - if (changeVal && !is_newvalue_equal(record, value)) - ereport(elevel, - (errcode(ERRCODE_CANT_CHANGE_RUNTIME_PARAM), - errmsg("parameter \"%s\" cannot be changed without restarting the server", - name))); - return true; + if (!changeVal) + return true; + + prohibitValueChange = true; } - if (context != PGC_POSTMASTER) + else if (context != PGC_POSTMASTER) { ereport(elevel, (errcode(ERRCODE_CANT_CHANGE_RUNTIME_PARAM), @@ -5022,6 +5277,7 @@ set_config_option(const char *name, const char *value, { struct config_bool *conf = (struct config_bool *) record; bool newval; + void *newextra = NULL; if (value) { @@ -5033,32 +5289,45 @@ set_config_option(const char *name, const char *value, name))); return false; } + if (!call_bool_check_hook(conf, &newval, &newextra, + source, elevel)) + return false; } else if (source == PGC_S_DEFAULT) + { newval = conf->boot_val; + if (!call_bool_check_hook(conf, &newval, &newextra, + source, elevel)) + return false; + } else { newval = conf->reset_val; + newextra = conf->reset_extra; source = conf->gen.reset_source; } - /* Save old value to support transaction abort */ - if (changeVal && !makeDefault) - push_old_value(&conf->gen, action); - - if (conf->assign_hook) - if (!(*conf->assign_hook) (newval, changeVal, source)) - { + if (prohibitValueChange) + { + if (*conf->variable != newval) ereport(elevel, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("invalid value for parameter \"%s\": %d", - name, (int) newval))); - return false; - } + (errcode(ERRCODE_CANT_CHANGE_RUNTIME_PARAM), + errmsg("parameter \"%s\" cannot be changed without restarting the server", + name))); + return false; + } if (changeVal) { + /* Save old value to support transaction abort */ + if (!makeDefault) + push_old_value(&conf->gen, action); + + if (conf->assign_hook) + (*conf->assign_hook) (newval, newextra); *conf->variable = newval; + set_extra_field(&conf->gen, &conf->gen.extra, + newextra); conf->gen.source = source; } if (makeDefault) @@ -5068,17 +5337,25 @@ set_config_option(const char *name, const char *value, if (conf->gen.reset_source <= source) { conf->reset_val = newval; + set_extra_field(&conf->gen, &conf->reset_extra, + newextra); conf->gen.reset_source = source; } for (stack = conf->gen.stack; stack; stack = stack->prev) { if (stack->source <= source) { - stack->prior.boolval = newval; + stack->prior.val.boolval = newval; + set_extra_field(&conf->gen, &stack->prior.extra, + newextra); stack->source = source; } } } + + /* Perhaps we didn't install newextra anywhere */ + if (newextra && !extra_field_used(&conf->gen, newextra)) + free(newextra); break; } @@ -5086,6 +5363,7 @@ set_config_option(const char *name, const char *value, { struct config_int *conf = (struct config_int *) record; int newval; + void *newextra = NULL; if (value) { @@ -5108,32 +5386,45 @@ set_config_option(const char *name, const char *value, newval, name, conf->min, conf->max))); return false; } + if (!call_int_check_hook(conf, &newval, &newextra, + source, elevel)) + return false; } else if (source == PGC_S_DEFAULT) + { newval = conf->boot_val; + if (!call_int_check_hook(conf, &newval, &newextra, + source, elevel)) + return false; + } else { newval = conf->reset_val; + newextra = conf->reset_extra; source = conf->gen.reset_source; } - /* Save old value to support transaction abort */ - if (changeVal && !makeDefault) - push_old_value(&conf->gen, action); - - if (conf->assign_hook) - if (!(*conf->assign_hook) (newval, changeVal, source)) - { + if (prohibitValueChange) + { + if (*conf->variable != newval) ereport(elevel, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("invalid value for parameter \"%s\": %d", - name, newval))); - return false; - } + (errcode(ERRCODE_CANT_CHANGE_RUNTIME_PARAM), + errmsg("parameter \"%s\" cannot be changed without restarting the server", + name))); + return false; + } if (changeVal) { + /* Save old value to support transaction abort */ + if (!makeDefault) + push_old_value(&conf->gen, action); + + if (conf->assign_hook) + (*conf->assign_hook) (newval, newextra); *conf->variable = newval; + set_extra_field(&conf->gen, &conf->gen.extra, + newextra); conf->gen.source = source; } if (makeDefault) @@ -5143,17 +5434,25 @@ set_config_option(const char *name, const char *value, if (conf->gen.reset_source <= source) { conf->reset_val = newval; + set_extra_field(&conf->gen, &conf->reset_extra, + newextra); conf->gen.reset_source = source; } for (stack = conf->gen.stack; stack; stack = stack->prev) { if (stack->source <= source) { - stack->prior.intval = newval; + stack->prior.val.intval = newval; + set_extra_field(&conf->gen, &stack->prior.extra, + newextra); stack->source = source; } } } + + /* Perhaps we didn't install newextra anywhere */ + if (newextra && !extra_field_used(&conf->gen, newextra)) + free(newextra); break; } @@ -5161,6 +5460,7 @@ set_config_option(const char *name, const char *value, { struct config_real *conf = (struct config_real *) record; double newval; + void *newextra = NULL; if (value) { @@ -5180,32 +5480,45 @@ set_config_option(const char *name, const char *value, newval, name, conf->min, conf->max))); return false; } + if (!call_real_check_hook(conf, &newval, &newextra, + source, elevel)) + return false; } else if (source == PGC_S_DEFAULT) + { newval = conf->boot_val; + if (!call_real_check_hook(conf, &newval, &newextra, + source, elevel)) + return false; + } else { newval = conf->reset_val; + newextra = conf->reset_extra; source = conf->gen.reset_source; } - /* Save old value to support transaction abort */ - if (changeVal && !makeDefault) - push_old_value(&conf->gen, action); - - if (conf->assign_hook) - if (!(*conf->assign_hook) (newval, changeVal, source)) - { + if (prohibitValueChange) + { + if (*conf->variable != newval) ereport(elevel, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("invalid value for parameter \"%s\": %g", - name, newval))); - return false; - } + (errcode(ERRCODE_CANT_CHANGE_RUNTIME_PARAM), + errmsg("parameter \"%s\" cannot be changed without restarting the server", + name))); + return false; + } if (changeVal) { + /* Save old value to support transaction abort */ + if (!makeDefault) + push_old_value(&conf->gen, action); + + if (conf->assign_hook) + (*conf->assign_hook) (newval, newextra); *conf->variable = newval; + set_extra_field(&conf->gen, &conf->gen.extra, + newextra); conf->gen.source = source; } if (makeDefault) @@ -5215,17 +5528,25 @@ set_config_option(const char *name, const char *value, if (conf->gen.reset_source <= source) { conf->reset_val = newval; + set_extra_field(&conf->gen, &conf->reset_extra, + newextra); conf->gen.reset_source = source; } for (stack = conf->gen.stack; stack; stack = stack->prev) { if (stack->source <= source) { - stack->prior.realval = newval; + stack->prior.val.realval = newval; + set_extra_field(&conf->gen, &stack->prior.extra, + newextra); stack->source = source; } } } + + /* Perhaps we didn't install newextra anywhere */ + if (newextra && !extra_field_used(&conf->gen, newextra)) + free(newextra); break; } @@ -5233,95 +5554,88 @@ set_config_option(const char *name, const char *value, { struct config_string *conf = (struct config_string *) record; char *newval; + void *newextra = NULL; if (value) { + /* + * The value passed by the caller could be transient, + * so we always strdup it. + */ newval = guc_strdup(elevel, value); if (newval == NULL) return false; /* - * The only sort of "parsing" check we need to do is apply + * The only built-in "parsing" check we have is to apply * truncation if GUC_IS_NAME. */ if (conf->gen.flags & GUC_IS_NAME) truncate_identifier(newval, strlen(newval), true); + + if (!call_string_check_hook(conf, &newval, &newextra, + source, elevel)) + { + free(newval); + return false; + } } else if (source == PGC_S_DEFAULT) { - if (conf->boot_val == NULL) - newval = NULL; - else + /* non-NULL boot_val must always get strdup'd */ + if (conf->boot_val != NULL) { newval = guc_strdup(elevel, conf->boot_val); if (newval == NULL) return false; } + else + newval = NULL; + + if (!call_string_check_hook(conf, &newval, &newextra, + source, elevel)) + { + free(newval); + return false; + } } else { /* - * We could possibly avoid strdup here, but easier to make - * this case work the same as the normal assignment case; - * note the possible free of newval below. + * strdup not needed, since reset_val is already under + * guc.c's control */ - if (conf->reset_val == NULL) - newval = NULL; - else - { - newval = guc_strdup(elevel, conf->reset_val); - if (newval == NULL) - return false; - } + newval = conf->reset_val; + newextra = conf->reset_extra; source = conf->gen.reset_source; } - /* Save old value to support transaction abort */ - if (changeVal && !makeDefault) - push_old_value(&conf->gen, action); - - if (conf->assign_hook && newval) + if (prohibitValueChange) { - const char *hookresult; - - /* - * If the hook ereports, we have to make sure we free - * newval, else it will be a permanent memory leak. - */ - hookresult = call_string_assign_hook(conf->assign_hook, - newval, - changeVal, - source); - if (hookresult == NULL) - { - free(newval); + /* newval shouldn't be NULL, so we're a bit sloppy here */ + if (*conf->variable == NULL || newval == NULL || + strcmp(*conf->variable, newval) != 0) ereport(elevel, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("invalid value for parameter \"%s\": \"%s\"", - name, value ? value : ""))); - return false; - } - else if (hookresult != newval) - { - free(newval); - - /* - * Having to cast away const here is annoying, but the - * alternative is to declare assign_hooks as returning - * char*, which would mean they'd have to cast away - * const, or as both taking and returning char*, which - * doesn't seem attractive either --- we don't want - * them to scribble on the passed str. - */ - newval = (char *) hookresult; - } + (errcode(ERRCODE_CANT_CHANGE_RUNTIME_PARAM), + errmsg("parameter \"%s\" cannot be changed without restarting the server", + name))); + return false; } if (changeVal) { + /* Save old value to support transaction abort */ + if (!makeDefault) + push_old_value(&conf->gen, action); + + if (conf->assign_hook) + (*conf->assign_hook) (newval, newextra); set_string_field(conf, conf->variable, newval); + set_extra_field(&conf->gen, &conf->gen.extra, + newextra); conf->gen.source = source; } + if (makeDefault) { GucStack *stack; @@ -5329,27 +5643,37 @@ set_config_option(const char *name, const char *value, if (conf->gen.reset_source <= source) { set_string_field(conf, &conf->reset_val, newval); + set_extra_field(&conf->gen, &conf->reset_extra, + newextra); conf->gen.reset_source = source; } for (stack = conf->gen.stack; stack; stack = stack->prev) { if (stack->source <= source) { - set_string_field(conf, &stack->prior.stringval, + set_string_field(conf, &stack->prior.val.stringval, newval); + set_extra_field(&conf->gen, &stack->prior.extra, + newextra); stack->source = source; } } } + /* Perhaps we didn't install newval anywhere */ if (newval && !string_field_used(conf, newval)) free(newval); + /* Perhaps we didn't install newextra anywhere */ + if (newextra && !extra_field_used(&conf->gen, newextra)) + free(newextra); break; } + case PGC_ENUM: { struct config_enum *conf = (struct config_enum *) record; int newval; + void *newextra = NULL; if (value) { @@ -5371,33 +5695,45 @@ set_config_option(const char *name, const char *value, pfree(hintmsg); return false; } + if (!call_enum_check_hook(conf, &newval, &newextra, + source, elevel)) + return false; } else if (source == PGC_S_DEFAULT) + { newval = conf->boot_val; + if (!call_enum_check_hook(conf, &newval, &newextra, + source, elevel)) + return false; + } else { newval = conf->reset_val; + newextra = conf->reset_extra; source = conf->gen.reset_source; } - /* Save old value to support transaction abort */ - if (changeVal && !makeDefault) - push_old_value(&conf->gen, action); - - if (conf->assign_hook) - if (!(*conf->assign_hook) (newval, changeVal, source)) - { + if (prohibitValueChange) + { + if (*conf->variable != newval) ereport(elevel, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("invalid value for parameter \"%s\": \"%s\"", - name, - config_enum_lookup_by_value(conf, newval)))); - return false; - } + (errcode(ERRCODE_CANT_CHANGE_RUNTIME_PARAM), + errmsg("parameter \"%s\" cannot be changed without restarting the server", + name))); + return false; + } if (changeVal) { + /* Save old value to support transaction abort */ + if (!makeDefault) + push_old_value(&conf->gen, action); + + if (conf->assign_hook) + (*conf->assign_hook) (newval, newextra); *conf->variable = newval; + set_extra_field(&conf->gen, &conf->gen.extra, + newextra); conf->gen.source = source; } if (makeDefault) @@ -5407,17 +5743,25 @@ set_config_option(const char *name, const char *value, if (conf->gen.reset_source <= source) { conf->reset_val = newval; + set_extra_field(&conf->gen, &conf->reset_extra, + newextra); conf->gen.reset_source = source; } for (stack = conf->gen.stack; stack; stack = stack->prev) { if (stack->source <= source) { - stack->prior.enumval = newval; + stack->prior.val.enumval = newval; + set_extra_field(&conf->gen, &stack->prior.extra, + newextra); stack->source = source; } } } + + /* Perhaps we didn't install newextra anywhere */ + if (newextra && !extra_field_used(&conf->gen, newextra)) + free(newextra); break; } } @@ -5577,51 +5921,6 @@ GetConfigOptionResetString(const char *name) } -/* - * GUC_complaint_elevel - * Get the ereport error level to use in an assign_hook's error report. - * - * This should be used by assign hooks that want to emit a custom error - * report (in addition to the generic "invalid value for option FOO" that - * guc.c will provide). Note that the result might be ERROR or a lower - * level, so the caller must be prepared for control to return from ereport, - * or not. If control does return, return false/NULL from the hook function. - * - * At some point it'd be nice to replace this with a mechanism that allows - * the custom message to become the DETAIL line of guc.c's generic message. - */ -int -GUC_complaint_elevel(GucSource source) -{ - int elevel; - - if (source == PGC_S_FILE) - { - /* - * To avoid cluttering the log, only the postmaster bleats loudly - * about problems with the config file. - */ - elevel = IsUnderPostmaster ? DEBUG3 : LOG; - } - else if (source == PGC_S_OVERRIDE) - { - /* - * If we're a postmaster child, this is probably "undo" during - * transaction abort, so we don't want to clutter the log. There's a - * small chance of a real problem with an OVERRIDE setting, though, so - * suppressing the message entirely wouldn't be desirable. - */ - elevel = IsUnderPostmaster ? DEBUG5 : LOG; - } - else if (source < PGC_S_INTERACTIVE) - elevel = LOG; - else - elevel = ERROR; - - return elevel; -} - - /* * flatten_set_variable_args * Given a parsenode List as emitted by the grammar for SET, @@ -6115,6 +6414,7 @@ DefineCustomBoolVariable(const char *name, bool bootValue, GucContext context, int flags, + GucBoolCheckHook check_hook, GucBoolAssignHook assign_hook, GucShowHook show_hook) { @@ -6126,6 +6426,7 @@ DefineCustomBoolVariable(const char *name, var->variable = valueAddr; var->boot_val = bootValue; var->reset_val = bootValue; + var->check_hook = check_hook; var->assign_hook = assign_hook; var->show_hook = show_hook; define_custom_variable(&var->gen); @@ -6141,6 +6442,7 @@ DefineCustomIntVariable(const char *name, int maxValue, GucContext context, int flags, + GucIntCheckHook check_hook, GucIntAssignHook assign_hook, GucShowHook show_hook) { @@ -6154,6 +6456,7 @@ DefineCustomIntVariable(const char *name, var->reset_val = bootValue; var->min = minValue; var->max = maxValue; + var->check_hook = check_hook; var->assign_hook = assign_hook; var->show_hook = show_hook; define_custom_variable(&var->gen); @@ -6169,6 +6472,7 @@ DefineCustomRealVariable(const char *name, double maxValue, GucContext context, int flags, + GucRealCheckHook check_hook, GucRealAssignHook assign_hook, GucShowHook show_hook) { @@ -6182,6 +6486,7 @@ DefineCustomRealVariable(const char *name, var->reset_val = bootValue; var->min = minValue; var->max = maxValue; + var->check_hook = check_hook; var->assign_hook = assign_hook; var->show_hook = show_hook; define_custom_variable(&var->gen); @@ -6195,6 +6500,7 @@ DefineCustomStringVariable(const char *name, const char *bootValue, GucContext context, int flags, + GucStringCheckHook check_hook, GucStringAssignHook assign_hook, GucShowHook show_hook) { @@ -6205,9 +6511,7 @@ DefineCustomStringVariable(const char *name, PGC_STRING, sizeof(struct config_string)); var->variable = valueAddr; var->boot_val = bootValue; - /* we could probably do without strdup, but keep it like normal case */ - if (var->boot_val) - var->reset_val = guc_strdup(ERROR, var->boot_val); + var->check_hook = check_hook; var->assign_hook = assign_hook; var->show_hook = show_hook; define_custom_variable(&var->gen); @@ -6222,6 +6526,7 @@ DefineCustomEnumVariable(const char *name, const struct config_enum_entry * options, GucContext context, int flags, + GucEnumCheckHook check_hook, GucEnumAssignHook assign_hook, GucShowHook show_hook) { @@ -6234,6 +6539,7 @@ DefineCustomEnumVariable(const char *name, var->boot_val = bootValue; var->reset_val = bootValue; var->options = options; + var->check_hook = check_hook; var->assign_hook = assign_hook; var->show_hook = show_hook; define_custom_variable(&var->gen); @@ -6995,67 +7301,6 @@ _ShowOption(struct config_generic * record, bool use_units) } -/* - * Attempt (badly) to detect if a proposed new GUC setting is the same - * as the current value. - * - * XXX this does not really work because it doesn't account for the - * effects of canonicalization of string values by assign_hooks. - */ -static bool -is_newvalue_equal(struct config_generic * record, const char *newvalue) -{ - /* newvalue == NULL isn't supported */ - Assert(newvalue != NULL); - - switch (record->vartype) - { - case PGC_BOOL: - { - struct config_bool *conf = (struct config_bool *) record; - bool newval; - - return parse_bool(newvalue, &newval) - && *conf->variable == newval; - } - case PGC_INT: - { - struct config_int *conf = (struct config_int *) record; - int newval; - - return parse_int(newvalue, &newval, record->flags, NULL) - && *conf->variable == newval; - } - case PGC_REAL: - { - struct config_real *conf = (struct config_real *) record; - double newval; - - return parse_real(newvalue, &newval) - && *conf->variable == newval; - } - case PGC_STRING: - { - struct config_string *conf = (struct config_string *) record; - - return *conf->variable != NULL && - strcmp(*conf->variable, newvalue) == 0; - } - - case PGC_ENUM: - { - struct config_enum *conf = (struct config_enum *) record; - int newval; - - return config_enum_lookup_by_name(conf, newvalue, &newval) && - *conf->variable == newval; - } - } - - return false; -} - - #ifdef EXEC_BACKEND /* @@ -7673,30 +7918,222 @@ validate_option_array_item(const char *name, const char *value, /* - * assign_hook and show_hook subroutines + * Called by check_hooks that want to override the normal + * ERRCODE_INVALID_PARAMETER_VALUE SQLSTATE for check hook failures. + * + * Note that GUC_check_errmsg() etc are just macros that result in a direct + * assignment to the associated variables. That is ugly, but forced by the + * limitations of C's macro mechanisms. + */ +void +GUC_check_errcode(int sqlerrcode) +{ + GUC_check_errcode_value = sqlerrcode; +} + + +/* + * Convenience functions to manage calling a variable's check_hook. + * These mostly take care of the protocol for letting check hooks supply + * portions of the error report on failure. */ -static const char * -assign_log_destination(const char *value, bool doit, GucSource source) +static bool +call_bool_check_hook(struct config_bool *conf, bool *newval, void **extra, + GucSource source, int elevel) +{ + /* Quick success if no hook */ + if (!conf->check_hook) + return true; + + /* Reset variables that might be set by hook */ + GUC_check_errcode_value = ERRCODE_INVALID_PARAMETER_VALUE; + GUC_check_errmsg_string = NULL; + GUC_check_errdetail_string = NULL; + GUC_check_errhint_string = NULL; + + if (!(*conf->check_hook) (newval, extra, source)) + { + ereport(elevel, + (errcode(GUC_check_errcode_value), + GUC_check_errmsg_string ? + errmsg("%s", GUC_check_errmsg_string) : + errmsg("invalid value for parameter \"%s\": %d", + conf->gen.name, (int) *newval), + GUC_check_errdetail_string ? + errdetail("%s", GUC_check_errdetail_string) : 0, + GUC_check_errhint_string ? + errhint("%s", GUC_check_errhint_string) : 0)); + /* Flush any strings created in ErrorContext */ + FlushErrorState(); + return false; + } + + return true; +} + +static bool +call_int_check_hook(struct config_int *conf, int *newval, void **extra, + GucSource source, int elevel) +{ + /* Quick success if no hook */ + if (!conf->check_hook) + return true; + + /* Reset variables that might be set by hook */ + GUC_check_errcode_value = ERRCODE_INVALID_PARAMETER_VALUE; + GUC_check_errmsg_string = NULL; + GUC_check_errdetail_string = NULL; + GUC_check_errhint_string = NULL; + + if (!(*conf->check_hook) (newval, extra, source)) + { + ereport(elevel, + (errcode(GUC_check_errcode_value), + GUC_check_errmsg_string ? + errmsg("%s", GUC_check_errmsg_string) : + errmsg("invalid value for parameter \"%s\": %d", + conf->gen.name, *newval), + GUC_check_errdetail_string ? + errdetail("%s", GUC_check_errdetail_string) : 0, + GUC_check_errhint_string ? + errhint("%s", GUC_check_errhint_string) : 0)); + /* Flush any strings created in ErrorContext */ + FlushErrorState(); + return false; + } + + return true; +} + +static bool +call_real_check_hook(struct config_real *conf, double *newval, void **extra, + GucSource source, int elevel) +{ + /* Quick success if no hook */ + if (!conf->check_hook) + return true; + + /* Reset variables that might be set by hook */ + GUC_check_errcode_value = ERRCODE_INVALID_PARAMETER_VALUE; + GUC_check_errmsg_string = NULL; + GUC_check_errdetail_string = NULL; + GUC_check_errhint_string = NULL; + + if (!(*conf->check_hook) (newval, extra, source)) + { + ereport(elevel, + (errcode(GUC_check_errcode_value), + GUC_check_errmsg_string ? + errmsg("%s", GUC_check_errmsg_string) : + errmsg("invalid value for parameter \"%s\": %g", + conf->gen.name, *newval), + GUC_check_errdetail_string ? + errdetail("%s", GUC_check_errdetail_string) : 0, + GUC_check_errhint_string ? + errhint("%s", GUC_check_errhint_string) : 0)); + /* Flush any strings created in ErrorContext */ + FlushErrorState(); + return false; + } + + return true; +} + +static bool +call_string_check_hook(struct config_string *conf, char **newval, void **extra, + GucSource source, int elevel) +{ + /* Quick success if no hook */ + if (!conf->check_hook) + return true; + + /* Reset variables that might be set by hook */ + GUC_check_errcode_value = ERRCODE_INVALID_PARAMETER_VALUE; + GUC_check_errmsg_string = NULL; + GUC_check_errdetail_string = NULL; + GUC_check_errhint_string = NULL; + + if (!(*conf->check_hook) (newval, extra, source)) + { + ereport(elevel, + (errcode(GUC_check_errcode_value), + GUC_check_errmsg_string ? + errmsg("%s", GUC_check_errmsg_string) : + errmsg("invalid value for parameter \"%s\": \"%s\"", + conf->gen.name, *newval ? *newval : ""), + GUC_check_errdetail_string ? + errdetail("%s", GUC_check_errdetail_string) : 0, + GUC_check_errhint_string ? + errhint("%s", GUC_check_errhint_string) : 0)); + /* Flush any strings created in ErrorContext */ + FlushErrorState(); + return false; + } + + return true; +} + +static bool +call_enum_check_hook(struct config_enum *conf, int *newval, void **extra, + GucSource source, int elevel) +{ + /* Quick success if no hook */ + if (!conf->check_hook) + return true; + + /* Reset variables that might be set by hook */ + GUC_check_errcode_value = ERRCODE_INVALID_PARAMETER_VALUE; + GUC_check_errmsg_string = NULL; + GUC_check_errdetail_string = NULL; + GUC_check_errhint_string = NULL; + + if (!(*conf->check_hook) (newval, extra, source)) + { + ereport(elevel, + (errcode(GUC_check_errcode_value), + GUC_check_errmsg_string ? + errmsg("%s", GUC_check_errmsg_string) : + errmsg("invalid value for parameter \"%s\": \"%s\"", + conf->gen.name, + config_enum_lookup_by_value(conf, *newval)), + GUC_check_errdetail_string ? + errdetail("%s", GUC_check_errdetail_string) : 0, + GUC_check_errhint_string ? + errhint("%s", GUC_check_errhint_string) : 0)); + /* Flush any strings created in ErrorContext */ + FlushErrorState(); + return false; + } + + return true; +} + + +/* + * check_hook, assign_hook and show_hook subroutines + */ + +static bool +check_log_destination(char **newval, void **extra, GucSource source) { char *rawstring; List *elemlist; ListCell *l; int newlogdest = 0; + int *myextra; /* Need a modifiable copy of string */ - rawstring = pstrdup(value); + rawstring = pstrdup(*newval); /* Parse string into list of identifiers */ if (!SplitIdentifierString(rawstring, ',', &elemlist)) { /* syntax error in list */ + GUC_check_errdetail("List syntax is invalid."); pfree(rawstring); list_free(elemlist); - ereport(GUC_complaint_elevel(source), - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("invalid list syntax for parameter \"log_destination\""))); - return NULL; + return false; } foreach(l, elemlist) @@ -7717,105 +8154,103 @@ assign_log_destination(const char *value, bool doit, GucSource source) #endif else { - ereport(GUC_complaint_elevel(source), - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("unrecognized \"log_destination\" key word: \"%s\"", - tok))); + GUC_check_errdetail("Unrecognized key word: \"%s\".", tok); pfree(rawstring); list_free(elemlist); - return NULL; + return false; } } - if (doit) - Log_destination = newlogdest; - pfree(rawstring); list_free(elemlist); - return value; -} - -static bool -assign_syslog_facility(int newval, bool doit, GucSource source) -{ -#ifdef HAVE_SYSLOG - if (doit) - set_syslog_parameters(syslog_ident_str ? syslog_ident_str : "postgres", - newval); -#endif - /* Without syslog support, just ignore it */ + myextra = (int *) guc_malloc(ERROR, sizeof(int)); + *myextra = newlogdest; + *extra = (void *) myextra; return true; } -static const char * -assign_syslog_ident(const char *ident, bool doit, GucSource source) +static void +assign_log_destination(const char *newval, void *extra) +{ + Log_destination = *((int *) extra); +} + +static void +assign_syslog_facility(int newval, void *extra) { #ifdef HAVE_SYSLOG - if (doit) - set_syslog_parameters(ident, syslog_facility); + set_syslog_parameters(syslog_ident_str ? syslog_ident_str : "postgres", + newval); +#endif + /* Without syslog support, just ignore it */ +} + +static void +assign_syslog_ident(const char *newval, void *extra) +{ +#ifdef HAVE_SYSLOG + set_syslog_parameters(newval, syslog_facility); #endif /* Without syslog support, it will always be set to "none", so ignore */ - - return ident; } -static bool -assign_session_replication_role(int newval, bool doit, GucSource source) +static void +assign_session_replication_role(int newval, void *extra) { /* * Must flush the plan cache when changing replication role; but don't * flush unnecessarily. */ - if (doit && SessionReplicationRole != newval) - { + if (SessionReplicationRole != newval) ResetPlanCache(); - } - - return true; -} - -static const char * -show_num_temp_buffers(void) -{ - /* - * We show the GUC var until local buffers have been initialized, and - * NLocBuffer afterwards. - */ - static char nbuf[32]; - - sprintf(nbuf, "%d", NLocBuffer ? NLocBuffer : num_temp_buffers); - return nbuf; } static bool -assign_phony_autocommit(bool newval, bool doit, GucSource source) +check_temp_buffers(int *newval, void **extra, GucSource source) { - if (!newval) + /* + * Once local buffers have been initialized, it's too late to change this. + */ + if (NLocBuffer && NLocBuffer != *newval) { - ereport(GUC_complaint_elevel(source), - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("SET AUTOCOMMIT TO OFF is no longer supported"))); + GUC_check_errdetail("\"temp_buffers\" cannot be changed after any temp tables have been accessed in the session."); return false; } return true; } -static const char * -assign_custom_variable_classes(const char *newval, bool doit, GucSource source) +static bool +check_phony_autocommit(bool *newval, void **extra, GucSource source) +{ + if (!*newval) + { + GUC_check_errcode(ERRCODE_FEATURE_NOT_SUPPORTED); + GUC_check_errmsg("SET AUTOCOMMIT TO OFF is no longer supported"); + return false; + } + return true; +} + +static bool +check_custom_variable_classes(char **newval, void **extra, GucSource source) { /* * Check syntax. newval must be a comma separated list of identifiers. * Whitespace is allowed but removed from the result. */ bool hasSpaceAfterToken = false; - const char *cp = newval; + const char *cp = *newval; int symLen = 0; char c; StringInfoData buf; + /* Default NULL is OK */ + if (cp == NULL) + return true; + initStringInfo(&buf); while ((c = *cp++) != '\0') { @@ -7844,7 +8279,7 @@ assign_custom_variable_classes(const char *newval, bool doit, GucSource source) * non-identifier character */ pfree(buf.data); - return NULL; + return false; } appendStringInfoChar(&buf, c); symLen++; @@ -7855,21 +8290,20 @@ assign_custom_variable_classes(const char *newval, bool doit, GucSource source) buf.data[--buf.len] = '\0'; /* GUC wants the result malloc'd */ - newval = guc_strdup(LOG, buf.data); + free(*newval); + *newval = guc_strdup(LOG, buf.data); pfree(buf.data); - return newval; + return true; } static bool -assign_debug_assertions(bool newval, bool doit, GucSource source) +check_debug_assertions(bool *newval, void **extra, GucSource source) { #ifndef USE_ASSERT_CHECKING - if (newval) + if (*newval) { - ereport(GUC_complaint_elevel(source), - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("assertion checking is not supported by this build"))); + GUC_check_errmsg("assertion checking is not supported by this build"); return false; } #endif @@ -7877,14 +8311,12 @@ assign_debug_assertions(bool newval, bool doit, GucSource source) } static bool -assign_bonjour(bool newval, bool doit, GucSource source) +check_bonjour(bool *newval, void **extra, GucSource source) { #ifndef USE_BONJOUR - if (newval) + if (*newval) { - ereport(GUC_complaint_elevel(source), - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("Bonjour is not supported by this build"))); + GUC_check_errmsg("Bonjour is not supported by this build"); return false; } #endif @@ -7892,14 +8324,12 @@ assign_bonjour(bool newval, bool doit, GucSource source) } static bool -assign_ssl(bool newval, bool doit, GucSource source) +check_ssl(bool *newval, void **extra, GucSource source) { #ifndef USE_SSL - if (newval) + if (*newval) { - ereport(GUC_complaint_elevel(source), - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("SSL is not supported by this build"))); + GUC_check_errmsg("SSL is not supported by this build"); return false; } #endif @@ -7907,57 +8337,48 @@ assign_ssl(bool newval, bool doit, GucSource source) } static bool -assign_stage_log_stats(bool newval, bool doit, GucSource source) +check_stage_log_stats(bool *newval, void **extra, GucSource source) { - if (newval && log_statement_stats) + if (*newval && log_statement_stats) { - ereport(GUC_complaint_elevel(source), - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("cannot enable parameter when \"log_statement_stats\" is true"))); - /* source == PGC_S_OVERRIDE means do it anyway, eg at xact abort */ - if (source != PGC_S_OVERRIDE) - return false; + GUC_check_errdetail("Cannot enable parameter when \"log_statement_stats\" is true."); + return false; } return true; } static bool -assign_log_stats(bool newval, bool doit, GucSource source) +check_log_stats(bool *newval, void **extra, GucSource source) { - if (newval && + if (*newval && (log_parser_stats || log_planner_stats || log_executor_stats)) { - ereport(GUC_complaint_elevel(source), - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("cannot enable \"log_statement_stats\" when " - "\"log_parser_stats\", \"log_planner_stats\", " - "or \"log_executor_stats\" is true"))); - /* source == PGC_S_OVERRIDE means do it anyway, eg at xact abort */ - if (source != PGC_S_OVERRIDE) - return false; + GUC_check_errdetail("Cannot enable \"log_statement_stats\" when " + "\"log_parser_stats\", \"log_planner_stats\", " + "or \"log_executor_stats\" is true."); + return false; } return true; } -static const char * -assign_canonical_path(const char *newval, bool doit, GucSource source) -{ - if (doit) - { - char *canon_val = guc_strdup(ERROR, newval); - - canonicalize_path(canon_val); - return canon_val; - } - else - return newval; -} - -static const char * -assign_timezone_abbreviations(const char *newval, bool doit, GucSource source) +static bool +check_canonical_path(char **newval, void **extra, GucSource source) { /* - * The powerup value shown above for timezone_abbreviations is "UNKNOWN". + * Since canonicalize_path never enlarges the string, we can just + * modify newval in-place. But watch out for NULL, which is the + * default value for external_pid_file. + */ + if (*newval) + canonicalize_path(*newval); + return true; +} + +static bool +check_timezone_abbreviations(char **newval, void **extra, GucSource source) +{ + /* + * The boot_val given above for timezone_abbreviations is NULL. * When we see this we just do nothing. If this value isn't overridden * from the config file then pg_timezone_abbrev_initialize() will * eventually replace it with "Default". This hack has two purposes: to @@ -7965,34 +8386,32 @@ assign_timezone_abbreviations(const char *newval, bool doit, GucSource source) * the config file, and to avoid trying to read the timezone abbrev files * during InitializeGUCOptions(). The latter doesn't work in an * EXEC_BACKEND subprocess because my_exec_path hasn't been set yet and so - * we can't locate PGSHAREDIR. (Essentially the same hack is used to - * delay initializing TimeZone ... if we have any more, we should try to - * clean up and centralize this mechanism ...) + * we can't locate PGSHAREDIR. */ - if (strcmp(newval, "UNKNOWN") == 0) + if (*newval == NULL) { - return newval; + Assert(source == PGC_S_DEFAULT); + return true; } - /* Loading abbrev file is expensive, so only do it when value changes */ - if (timezone_abbreviations_string == NULL || - strcmp(timezone_abbreviations_string, newval) != 0) - { - int elevel; + /* OK, load the file and produce a malloc'd TimeZoneAbbrevTable */ + *extra = load_tzoffsets(*newval); - /* - * If reading config file, only the postmaster should bleat loudly - * about problems. Otherwise, it's just this one process doing it, - * and we use WARNING message level. - */ - if (source == PGC_S_FILE) - elevel = IsUnderPostmaster ? DEBUG3 : LOG; - else - elevel = WARNING; - if (!load_tzoffsets(newval, doit, elevel)) - return NULL; - } - return newval; + /* tzparser.c returns NULL on failure, reporting via GUC_check_errmsg */ + if (!*extra) + return false; + + return true; +} + +static void +assign_timezone_abbreviations(const char *newval, void *extra) +{ + /* Do nothing for the boot_val default of NULL */ + if (!extra) + return; + + InstallTimeZoneAbbrevs((TimeZoneAbbrevTable *) extra); } /* @@ -8004,10 +8423,10 @@ assign_timezone_abbreviations(const char *newval, bool doit, GucSource source) void pg_timezone_abbrev_initialize(void) { - if (strcmp(timezone_abbreviations_string, "UNKNOWN") == 0) + if (timezone_abbreviations_string == NULL) { SetConfigOption("timezone_abbreviations", "Default", - PGC_POSTMASTER, PGC_S_ARGV); + PGC_POSTMASTER, PGC_S_DEFAULT); } } @@ -8020,54 +8439,61 @@ show_archive_command(void) return "(disabled)"; } -static bool -assign_tcp_keepalives_idle(int newval, bool doit, GucSource source) +static void +assign_tcp_keepalives_idle(int newval, void *extra) { - if (doit) - return (pq_setkeepalivesidle(newval, MyProcPort) == STATUS_OK); - - return true; + /* + * The kernel API provides no way to test a value without setting it; + * and once we set it we might fail to unset it. So there seems little + * point in fully implementing the check-then-assign GUC API for these + * variables. Instead we just do the assignment on demand. pqcomm.c + * reports any problems via elog(LOG). + * + * This approach means that the GUC value might have little to do with + * the actual kernel value, so we use a show_hook that retrieves the + * kernel value rather than trusting GUC's copy. + */ + (void) pq_setkeepalivesidle(newval, MyProcPort); } static const char * show_tcp_keepalives_idle(void) { + /* See comments in assign_tcp_keepalives_idle */ static char nbuf[16]; snprintf(nbuf, sizeof(nbuf), "%d", pq_getkeepalivesidle(MyProcPort)); return nbuf; } -static bool -assign_tcp_keepalives_interval(int newval, bool doit, GucSource source) +static void +assign_tcp_keepalives_interval(int newval, void *extra) { - if (doit) - return (pq_setkeepalivesinterval(newval, MyProcPort) == STATUS_OK); - - return true; + /* See comments in assign_tcp_keepalives_idle */ + (void) pq_setkeepalivesinterval(newval, MyProcPort); } static const char * show_tcp_keepalives_interval(void) { + /* See comments in assign_tcp_keepalives_idle */ static char nbuf[16]; snprintf(nbuf, sizeof(nbuf), "%d", pq_getkeepalivesinterval(MyProcPort)); return nbuf; } -static bool -assign_tcp_keepalives_count(int newval, bool doit, GucSource source) +static void +assign_tcp_keepalives_count(int newval, void *extra) { - if (doit) - return (pq_setkeepalivescount(newval, MyProcPort) == STATUS_OK); - - return true; + /* See comments in assign_tcp_keepalives_idle */ + (void) pq_setkeepalivescount(newval, MyProcPort); } static const char * show_tcp_keepalives_count(void) { + /* See comments in assign_tcp_keepalives_idle */ static char nbuf[16]; snprintf(nbuf, sizeof(nbuf), "%d", pq_getkeepalivescount(MyProcPort)); @@ -8075,31 +8501,35 @@ show_tcp_keepalives_count(void) } static bool -assign_maxconnections(int newval, bool doit, GucSource source) +check_maxconnections(int *newval, void **extra, GucSource source) { - if (newval + autovacuum_max_workers + 1 > MAX_BACKENDS) + if (*newval + autovacuum_max_workers + 1 > MAX_BACKENDS) return false; - - if (doit) - MaxBackends = newval + autovacuum_max_workers + 1; - return true; } -static bool -assign_autovacuum_max_workers(int newval, bool doit, GucSource source) +static void +assign_maxconnections(int newval, void *extra) { - if (MaxConnections + newval + 1 > MAX_BACKENDS) - return false; - - if (doit) - MaxBackends = MaxConnections + newval + 1; - - return true; + MaxBackends = newval + autovacuum_max_workers + 1; } static bool -assign_effective_io_concurrency(int newval, bool doit, GucSource source) +check_autovacuum_max_workers(int *newval, void **extra, GucSource source) +{ + if (MaxConnections + *newval + 1 > MAX_BACKENDS) + return false; + return true; +} + +static void +assign_autovacuum_max_workers(int newval, void *extra) +{ + MaxBackends = MaxConnections + newval + 1; +} + +static bool +check_effective_io_concurrency(int *newval, void **extra, GucSource source) { #ifdef USE_PREFETCH double new_prefetch_pages = 0.0; @@ -8108,6 +8538,8 @@ assign_effective_io_concurrency(int newval, bool doit, GucSource source) /*---------- * The user-visible GUC parameter is the number of drives (spindles), * which we need to translate to a number-of-pages-to-prefetch target. + * The target value is stashed in *extra and then assigned to the actual + * variable by assign_effective_io_concurrency. * * The expected number of prefetch pages needed to keep N drives busy is: * @@ -8132,18 +8564,21 @@ assign_effective_io_concurrency(int newval, bool doit, GucSource source) * Experimental results show that both of these formulas aren't aggressive * enough, but we don't really have any better proposals. * - * Note that if newval = 0 (disabled), we must set target = 0. + * Note that if *newval = 0 (disabled), we must set target = 0. *---------- */ - for (i = 1; i <= newval; i++) - new_prefetch_pages += (double) newval / (double) i; + for (i = 1; i <= *newval; i++) + new_prefetch_pages += (double) *newval / (double) i; /* This range check shouldn't fail, but let's be paranoid */ if (new_prefetch_pages >= 0.0 && new_prefetch_pages < (double) INT_MAX) { - if (doit) - target_prefetch_pages = (int) rint(new_prefetch_pages); + int *myextra = (int *) guc_malloc(ERROR, sizeof(int)); + + *myextra = (int) rint(new_prefetch_pages); + *extra = (void *) myextra; + return true; } else @@ -8153,57 +8588,54 @@ assign_effective_io_concurrency(int newval, bool doit, GucSource source) #endif /* USE_PREFETCH */ } -static const char * -assign_pgstat_temp_directory(const char *newval, bool doit, GucSource source) +static void +assign_effective_io_concurrency(int newval, void *extra) { - if (doit) - { - char *canon_val = guc_strdup(ERROR, newval); - char *tname; - char *fname; - - canonicalize_path(canon_val); - - tname = guc_malloc(ERROR, strlen(canon_val) + 12); /* /pgstat.tmp */ - sprintf(tname, "%s/pgstat.tmp", canon_val); - fname = guc_malloc(ERROR, strlen(canon_val) + 13); /* /pgstat.stat */ - sprintf(fname, "%s/pgstat.stat", canon_val); - - if (pgstat_stat_tmpname) - free(pgstat_stat_tmpname); - pgstat_stat_tmpname = tname; - if (pgstat_stat_filename) - free(pgstat_stat_filename); - pgstat_stat_filename = fname; - - return canon_val; - } - else - return newval; +#ifdef USE_PREFETCH + target_prefetch_pages = *((int *) extra); +#endif /* USE_PREFETCH */ } -static const char * -assign_application_name(const char *newval, bool doit, GucSource source) +static void +assign_pgstat_temp_directory(const char *newval, void *extra) { - if (doit) + /* check_canonical_path already canonicalized newval for us */ + char *tname; + char *fname; + + tname = guc_malloc(ERROR, strlen(newval) + 12); /* /pgstat.tmp */ + sprintf(tname, "%s/pgstat.tmp", newval); + fname = guc_malloc(ERROR, strlen(newval) + 13); /* /pgstat.stat */ + sprintf(fname, "%s/pgstat.stat", newval); + + if (pgstat_stat_tmpname) + free(pgstat_stat_tmpname); + pgstat_stat_tmpname = tname; + if (pgstat_stat_filename) + free(pgstat_stat_filename); + pgstat_stat_filename = fname; +} + +static bool +check_application_name(char **newval, void **extra, GucSource source) +{ + /* Only allow clean ASCII chars in the application name */ + char *p; + + for (p = *newval; *p; p++) { - /* Only allow clean ASCII chars in the application name */ - char *repval = guc_strdup(ERROR, newval); - char *p; - - for (p = repval; *p; p++) - { - if (*p < 32 || *p > 126) - *p = '?'; - } - - /* Update the pg_stat_activity view */ - pgstat_report_appname(repval); - - return repval; + if (*p < 32 || *p > 126) + *p = '?'; } - else - return newval; + + return true; +} + +static void +assign_application_name(const char *newval, void *extra) +{ + /* Update the pg_stat_activity view */ + pgstat_report_appname(newval); } static const char * diff --git a/src/backend/utils/misc/tzparser.c b/src/backend/utils/misc/tzparser.c index e63ce58e69..b2f6dd3a2b 100644 --- a/src/backend/utils/misc/tzparser.c +++ b/src/backend/utils/misc/tzparser.c @@ -3,10 +3,12 @@ * tzparser.c * Functions for parsing timezone offset files * - * Note: we generally should not throw any errors in this file, but instead - * try to return an error code. This is not completely bulletproof at - * present --- in particular out-of-memory will throw an error. Could - * probably fix with PG_TRY if necessary. + * Note: this code is invoked from the check_hook for the GUC variable + * timezone_abbreviations. Therefore, it should report problems using + * GUC_check_errmsg() and related functions, and try to avoid throwing + * elog(ERROR). This is not completely bulletproof at present --- in + * particular out-of-memory will throw an error. Could probably fix with + * PG_TRY if necessary. * * * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group @@ -24,15 +26,13 @@ #include "miscadmin.h" #include "storage/fd.h" -#include "utils/datetime.h" +#include "utils/guc.h" #include "utils/memutils.h" #include "utils/tzparser.h" #define WHITESPACE " \t\n\r" -static int tz_elevel; /* to avoid passing this around a lot */ - static bool validateTzEntry(tzEntry *tzentry); static bool splitTzLine(const char *filename, int lineno, char *line, tzEntry *tzentry); @@ -58,20 +58,16 @@ validateTzEntry(tzEntry *tzentry) */ if (strlen(tzentry->abbrev) > TOKMAXLEN) { - ereport(tz_elevel, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("time zone abbreviation \"%s\" is too long (maximum %d characters) in time zone file \"%s\", line %d", - tzentry->abbrev, TOKMAXLEN, - tzentry->filename, tzentry->lineno))); + GUC_check_errmsg("time zone abbreviation \"%s\" is too long (maximum %d characters) in time zone file \"%s\", line %d", + tzentry->abbrev, TOKMAXLEN, + tzentry->filename, tzentry->lineno); return false; } if (tzentry->offset % 900 != 0) { - ereport(tz_elevel, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("time zone offset %d is not a multiple of 900 sec (15 min) in time zone file \"%s\", line %d", - tzentry->offset, - tzentry->filename, tzentry->lineno))); + GUC_check_errmsg("time zone offset %d is not a multiple of 900 sec (15 min) in time zone file \"%s\", line %d", + tzentry->offset, + tzentry->filename, tzentry->lineno); return false; } @@ -81,11 +77,9 @@ validateTzEntry(tzEntry *tzentry) if (tzentry->offset > 14 * 60 * 60 || tzentry->offset < -14 * 60 * 60) { - ereport(tz_elevel, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("time zone offset %d is out of range in time zone file \"%s\", line %d", - tzentry->offset, - tzentry->filename, tzentry->lineno))); + GUC_check_errmsg("time zone offset %d is out of range in time zone file \"%s\", line %d", + tzentry->offset, + tzentry->filename, tzentry->lineno); return false; } @@ -118,10 +112,8 @@ splitTzLine(const char *filename, int lineno, char *line, tzEntry *tzentry) abbrev = strtok(line, WHITESPACE); if (!abbrev) { - ereport(tz_elevel, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("missing time zone abbreviation in time zone file \"%s\", line %d", - filename, lineno))); + GUC_check_errmsg("missing time zone abbreviation in time zone file \"%s\", line %d", + filename, lineno); return false; } tzentry->abbrev = abbrev; @@ -129,19 +121,15 @@ splitTzLine(const char *filename, int lineno, char *line, tzEntry *tzentry) offset = strtok(NULL, WHITESPACE); if (!offset) { - ereport(tz_elevel, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("missing time zone offset in time zone file \"%s\", line %d", - filename, lineno))); + GUC_check_errmsg("missing time zone offset in time zone file \"%s\", line %d", + filename, lineno); return false; } tzentry->offset = strtol(offset, &offset_endptr, 10); if (offset_endptr == offset || *offset_endptr != '\0') { - ereport(tz_elevel, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("invalid number for time zone offset in time zone file \"%s\", line %d", - filename, lineno))); + GUC_check_errmsg("invalid number for time zone offset in time zone file \"%s\", line %d", + filename, lineno); return false; } @@ -163,10 +151,8 @@ splitTzLine(const char *filename, int lineno, char *line, tzEntry *tzentry) if (remain[0] != '#') /* must be a comment */ { - ereport(tz_elevel, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("invalid syntax in time zone file \"%s\", line %d", - filename, lineno))); + GUC_check_errmsg("invalid syntax in time zone file \"%s\", line %d", + filename, lineno); return false; } return true; @@ -229,13 +215,11 @@ addToArray(tzEntry **base, int *arraysize, int n, return n; } /* same abbrev but something is different, complain */ - ereport(tz_elevel, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("time zone abbreviation \"%s\" is multiply defined", - entry->abbrev), - errdetail("Entry in time zone file \"%s\", line %d, conflicts with entry in file \"%s\", line %d.", - midptr->filename, midptr->lineno, - entry->filename, entry->lineno))); + GUC_check_errmsg("time zone abbreviation \"%s\" is multiply defined", + entry->abbrev); + GUC_check_errdetail("Entry in time zone file \"%s\", line %d, conflicts with entry in file \"%s\", line %d.", + midptr->filename, midptr->lineno, + entry->filename, entry->lineno); return -1; } } @@ -296,12 +280,10 @@ ParseTzFile(const char *filename, int depth, { if (!isalpha((unsigned char) *p)) { - /* at level 0, we need no ereport since guc.c will say enough */ + /* at level 0, just use guc.c's regular "invalid value" message */ if (depth > 0) - ereport(tz_elevel, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("invalid time zone file name \"%s\"", - filename))); + GUC_check_errmsg("invalid time zone file name \"%s\"", + filename); return -1; } } @@ -313,10 +295,8 @@ ParseTzFile(const char *filename, int depth, */ if (depth > 3) { - ereport(tz_elevel, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("time zone file recursion limit exceeded in file \"%s\"", - filename))); + GUC_check_errmsg("time zone file recursion limit exceeded in file \"%s\"", + filename); return -1; } @@ -340,12 +320,10 @@ ParseTzFile(const char *filename, int depth, tzdir = AllocateDir(file_path); if (tzdir == NULL) { - ereport(tz_elevel, - (errcode_for_file_access(), - errmsg("could not open directory \"%s\": %m", - file_path), - errhint("This may indicate an incomplete PostgreSQL installation, or that the file \"%s\" has been moved away from its proper location.", - my_exec_path))); + GUC_check_errmsg("could not open directory \"%s\": %m", + file_path); + GUC_check_errhint("This may indicate an incomplete PostgreSQL installation, or that the file \"%s\" has been moved away from its proper location.", + my_exec_path); return -1; } FreeDir(tzdir); @@ -356,10 +334,8 @@ ParseTzFile(const char *filename, int depth, * complaint is enough */ if (errno != ENOENT || depth > 0) - ereport(tz_elevel, - (errcode_for_file_access(), - errmsg("could not read time zone file \"%s\": %m", - filename))); + GUC_check_errmsg("could not read time zone file \"%s\": %m", + filename); return -1; } @@ -371,10 +347,8 @@ ParseTzFile(const char *filename, int depth, { if (ferror(tzFile)) { - ereport(tz_elevel, - (errcode_for_file_access(), - errmsg("could not read time zone file \"%s\": %m", - filename))); + GUC_check_errmsg("could not read time zone file \"%s\": %m", + filename); return -1; } /* else we're at EOF after all */ @@ -383,10 +357,8 @@ ParseTzFile(const char *filename, int depth, if (strlen(tzbuf) == sizeof(tzbuf) - 1) { /* the line is too long for tzbuf */ - ereport(tz_elevel, - (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), - errmsg("line is too long in time zone file \"%s\", line %d", - filename, lineno))); + GUC_check_errmsg("line is too long in time zone file \"%s\", line %d", + filename, lineno); return -1; } @@ -408,10 +380,8 @@ ParseTzFile(const char *filename, int depth, includeFile = strtok(includeFile, WHITESPACE); if (!includeFile || !*includeFile) { - ereport(tz_elevel, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("@INCLUDE without file name in time zone file \"%s\", line %d", - filename, lineno))); + GUC_check_errmsg("@INCLUDE without file name in time zone file \"%s\", line %d", + filename, lineno); return -1; } n = ParseTzFile(includeFile, depth + 1, @@ -444,23 +414,20 @@ ParseTzFile(const char *filename, int depth, /* * load_tzoffsets --- read and parse the specified timezone offset file * - * filename: name specified by user - * doit: whether to actually apply the new values, or just check - * elevel: elog reporting level (will be less than ERROR) - * - * Returns TRUE if OK, FALSE if not; should avoid erroring out + * On success, return a filled-in TimeZoneAbbrevTable, which must have been + * malloc'd not palloc'd. On failure, return NULL, using GUC_check_errmsg + * and friends to give details of the problem. */ -bool -load_tzoffsets(const char *filename, bool doit, int elevel) +TimeZoneAbbrevTable * +load_tzoffsets(const char *filename) { + TimeZoneAbbrevTable *result = NULL; MemoryContext tmpContext; MemoryContext oldContext; tzEntry *array; int arraysize; int n; - tz_elevel = elevel; - /* * Create a temp memory context to work in. This makes it easy to clean * up afterwards. @@ -479,13 +446,20 @@ load_tzoffsets(const char *filename, bool doit, int elevel) /* Parse the file(s) */ n = ParseTzFile(filename, 0, &array, &arraysize, 0); - /* If no errors and we should apply the result, pass it to datetime.c */ - if (n >= 0 && doit) - InstallTimeZoneAbbrevs(array, n); + /* If no errors so far, allocate result and let datetime.c convert data */ + if (n >= 0) + { + result = malloc(offsetof(TimeZoneAbbrevTable, abbrevs) + + n * sizeof(datetkn)); + if (!result) + GUC_check_errmsg("out of memory"); + else + ConvertTimeZoneAbbrevs(result, array, n); + } /* Clean up */ MemoryContextSwitchTo(oldContext); MemoryContextDelete(tmpContext); - return (n >= 0); + return result; } diff --git a/src/include/commands/variable.h b/src/include/commands/variable.h index 39bccbd5bf..1d904ff984 100644 --- a/src/include/commands/variable.h +++ b/src/include/commands/variable.h @@ -13,31 +13,28 @@ #include "utils/guc.h" -extern const char *assign_datestyle(const char *value, - bool doit, GucSource source); -extern const char *assign_timezone(const char *value, - bool doit, GucSource source); +extern bool check_datestyle(char **newval, void **extra, GucSource source); +extern void assign_datestyle(const char *newval, void *extra); +extern bool check_timezone(char **newval, void **extra, GucSource source); +extern void assign_timezone(const char *newval, void *extra); extern const char *show_timezone(void); -extern const char *assign_log_timezone(const char *value, - bool doit, GucSource source); +extern bool check_log_timezone(char **newval, void **extra, GucSource source); +extern void assign_log_timezone(const char *newval, void *extra); extern const char *show_log_timezone(void); -extern bool assign_transaction_read_only(bool value, - bool doit, GucSource source); -extern const char *assign_XactIsoLevel(const char *value, - bool doit, GucSource source); +extern bool check_transaction_read_only(bool *newval, void **extra, GucSource source); +extern bool check_XactIsoLevel(char **newval, void **extra, GucSource source); +extern void assign_XactIsoLevel(const char *newval, void *extra); extern const char *show_XactIsoLevel(void); -extern bool assign_transaction_deferrable(bool newval, bool doit, - GucSource source); -extern bool assign_random_seed(double value, - bool doit, GucSource source); +extern bool check_transaction_deferrable(bool *newval, void **extra, GucSource source); +extern bool check_random_seed(double *newval, void **extra, GucSource source); +extern void assign_random_seed(double newval, void *extra); extern const char *show_random_seed(void); -extern const char *assign_client_encoding(const char *value, - bool doit, GucSource source); -extern const char *assign_role(const char *value, - bool doit, GucSource source); +extern bool check_client_encoding(char **newval, void **extra, GucSource source); +extern void assign_client_encoding(const char *newval, void *extra); +extern bool check_session_authorization(char **newval, void **extra, GucSource source); +extern void assign_session_authorization(const char *newval, void *extra); +extern bool check_role(char **newval, void **extra, GucSource source); +extern void assign_role(const char *newval, void *extra); extern const char *show_role(void); -extern const char *assign_session_authorization(const char *value, - bool doit, GucSource source); -extern const char *show_session_authorization(void); #endif /* VARIABLE_H */ diff --git a/src/include/mb/pg_wchar.h b/src/include/mb/pg_wchar.h index 85a7b2f87d..8efc6d3046 100644 --- a/src/include/mb/pg_wchar.h +++ b/src/include/mb/pg_wchar.h @@ -397,7 +397,8 @@ extern size_t wchar2char(char *to, const wchar_t *from, size_t tolen, Oid collat extern size_t char2wchar(wchar_t *to, size_t tolen, const char *from, size_t fromlen, Oid collation); #endif -extern int SetClientEncoding(int encoding, bool doit); +extern int PrepareClientEncoding(int encoding); +extern int SetClientEncoding(int encoding); extern void InitializeClientEncoding(void); extern int pg_get_client_encoding(void); extern const char *pg_get_client_encoding_name(void); diff --git a/src/include/replication/syncrep.h b/src/include/replication/syncrep.h index 9022b96585..728e2c8f2d 100644 --- a/src/include/replication/syncrep.h +++ b/src/include/replication/syncrep.h @@ -45,6 +45,6 @@ extern void SyncRepUpdateSyncStandbysDefined(void); /* called by various procs */ extern int SyncRepWakeQueue(bool all); -extern const char *assign_synchronous_standby_names(const char *newval, bool doit, GucSource source); +extern bool check_synchronous_standby_names(char **newval, void **extra, GucSource source); #endif /* _SYNCREP_H */ diff --git a/src/include/tcop/tcopprot.h b/src/include/tcop/tcopprot.h index bf21cd9d06..d5192d9855 100644 --- a/src/include/tcop/tcopprot.h +++ b/src/include/tcop/tcopprot.h @@ -57,7 +57,8 @@ extern PlannedStmt *pg_plan_query(Query *querytree, int cursorOptions, extern List *pg_plan_queries(List *querytrees, int cursorOptions, ParamListInfo boundParams); -extern bool assign_max_stack_depth(int newval, bool doit, GucSource source); +extern bool check_max_stack_depth(int *newval, void **extra, GucSource source); +extern void assign_max_stack_depth(int newval, void *extra); extern void die(SIGNAL_ARGS); extern void quickdie(SIGNAL_ARGS); diff --git a/src/include/tsearch/ts_cache.h b/src/include/tsearch/ts_cache.h index 5ae38b2a43..01550eddfe 100644 --- a/src/include/tsearch/ts_cache.h +++ b/src/include/tsearch/ts_cache.h @@ -93,6 +93,7 @@ extern TSDictionaryCacheEntry *lookup_ts_dictionary_cache(Oid dictId); extern TSConfigCacheEntry *lookup_ts_config_cache(Oid cfgId); extern Oid getTSCurrentConfig(bool emitError); -extern const char *assignTSCurrentConfig(const char *newval, bool doit, GucSource source); +extern bool check_TSCurrentConfig(char **newval, void **extra, GucSource source); +extern void assign_TSCurrentConfig(const char *newval, void *extra); #endif /* TS_CACHE_H */ diff --git a/src/include/utils/datetime.h b/src/include/utils/datetime.h index 11672458f4..9911d20793 100644 --- a/src/include/utils/datetime.h +++ b/src/include/utils/datetime.h @@ -20,7 +20,9 @@ #include #include "utils/timestamp.h" -#include "utils/tzparser.h" + +/* this struct is declared in utils/tzparser.h: */ +struct tzEntry; /* ---------------------------------------------------------------- @@ -203,6 +205,13 @@ typedef struct char value; /* this may be unsigned, alas */ } datetkn; +/* one of its uses is in tables of time zone abbreviations */ +typedef struct TimeZoneAbbrevTable +{ + int numabbrevs; + datetkn abbrevs[1]; /* VARIABLE LENGTH ARRAY */ +} TimeZoneAbbrevTable; + /* FMODULO() * Macro to replace modf(), which is broken on some platforms. @@ -317,7 +326,10 @@ extern int DecodeUnits(int field, char *lowtoken, int *val); extern int j2day(int jd); extern bool CheckDateTokenTables(void); -extern void InstallTimeZoneAbbrevs(tzEntry *abbrevs, int n); + +extern void ConvertTimeZoneAbbrevs(TimeZoneAbbrevTable *tbl, + struct tzEntry *abbrevs, int n); +extern void InstallTimeZoneAbbrevs(TimeZoneAbbrevTable *tbl); extern Datum pg_timezone_abbrevs(PG_FUNCTION_ARGS); extern Datum pg_timezone_names(PG_FUNCTION_ARGS); diff --git a/src/include/utils/elog.h b/src/include/utils/elog.h index 71a1cdbd78..1f2a9f53c1 100644 --- a/src/include/utils/elog.h +++ b/src/include/utils/elog.h @@ -200,6 +200,15 @@ elog_finish(int elevel, const char *fmt,...) __attribute__((format(printf, 2, 3))); +/* Support for constructing error strings separately from ereport() calls */ + +extern void pre_format_elog_string(int errnumber, const char *domain); +extern char *format_elog_string(const char *fmt,...) +/* This extension allows gcc to check the format string for consistency with + the supplied arguments. */ +__attribute__((format(printf, 1, 2))); + + /* Support for attaching context information to error reports */ typedef struct ErrorContextCallback diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h index c07f263ea0..452310f2bd 100644 --- a/src/include/utils/guc.h +++ b/src/include/utils/guc.h @@ -97,7 +97,8 @@ typedef enum } GucSource; /* - * Parsing the configuation file will return a list of name-value pairs + * Parsing the configuration file will return a list of name-value pairs + * with source location info. */ typedef struct ConfigVariable { @@ -117,7 +118,9 @@ extern bool ParseConfigFp(FILE *fp, const char *config_file, extern void FreeConfigVariables(ConfigVariable *list); /* - * Enum values are made up of an array of name-value pairs + * The possible values of an enum variable are specified by an array of + * name-value pairs. The "hidden" flag means the value is accepted but + * won't be displayed when guc.c is asked for a list of acceptable values. */ struct config_enum_entry { @@ -126,15 +129,26 @@ struct config_enum_entry bool hidden; }; +/* + * Signatures for per-variable check/assign/show hook functions + */ +typedef bool (*GucBoolCheckHook) (bool *newval, void **extra, GucSource source); +typedef bool (*GucIntCheckHook) (int *newval, void **extra, GucSource source); +typedef bool (*GucRealCheckHook) (double *newval, void **extra, GucSource source); +typedef bool (*GucStringCheckHook) (char **newval, void **extra, GucSource source); +typedef bool (*GucEnumCheckHook) (int *newval, void **extra, GucSource source); -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 bool (*GucEnumAssignHook) (int newval, bool doit, GucSource source); +typedef void (*GucBoolAssignHook) (bool newval, void *extra); +typedef void (*GucIntAssignHook) (int newval, void *extra); +typedef void (*GucRealAssignHook) (double newval, void *extra); +typedef void (*GucStringAssignHook) (const char *newval, void *extra); +typedef void (*GucEnumAssignHook) (int newval, void *extra); typedef const char *(*GucShowHook) (void); +/* + * Miscellaneous + */ typedef enum { /* Types of set_config_option actions */ @@ -201,7 +215,6 @@ extern char *ConfigFileName; extern char *HbaFileName; extern char *IdentFileName; extern char *external_pid_file; -extern char *XactIsoLevel_string; extern char *application_name; @@ -209,6 +222,9 @@ extern int tcp_keepalives_idle; extern int tcp_keepalives_interval; extern int tcp_keepalives_count; +/* + * Functions exported by guc.c + */ extern void SetConfigOption(const char *name, const char *value, GucContext context, GucSource source); @@ -220,6 +236,7 @@ extern void DefineCustomBoolVariable( bool bootValue, GucContext context, int flags, + GucBoolCheckHook check_hook, GucBoolAssignHook assign_hook, GucShowHook show_hook); @@ -233,6 +250,7 @@ extern void DefineCustomIntVariable( int maxValue, GucContext context, int flags, + GucIntCheckHook check_hook, GucIntAssignHook assign_hook, GucShowHook show_hook); @@ -246,6 +264,7 @@ extern void DefineCustomRealVariable( double maxValue, GucContext context, int flags, + GucRealCheckHook check_hook, GucRealAssignHook assign_hook, GucShowHook show_hook); @@ -257,6 +276,7 @@ extern void DefineCustomStringVariable( const char *bootValue, GucContext context, int flags, + GucStringCheckHook check_hook, GucStringAssignHook assign_hook, GucShowHook show_hook); @@ -269,6 +289,7 @@ extern void DefineCustomEnumVariable( const struct config_enum_entry * options, GucContext context, int flags, + GucEnumCheckHook check_hook, GucEnumAssignHook assign_hook, GucShowHook show_hook); @@ -308,8 +329,6 @@ extern ArrayType *GUCArrayAdd(ArrayType *array, const char *name, const char *va extern ArrayType *GUCArrayDelete(ArrayType *array, const char *name); extern ArrayType *GUCArrayReset(ArrayType *array); -extern int GUC_complaint_elevel(GucSource source); - extern void pg_timezone_abbrev_initialize(void); #ifdef EXEC_BACKEND @@ -317,6 +336,27 @@ extern void write_nondefault_variables(GucContext context); extern void read_nondefault_variables(void); #endif +/* Support for messages reported from GUC check hooks */ + +extern PGDLLIMPORT char *GUC_check_errmsg_string; +extern PGDLLIMPORT char *GUC_check_errdetail_string; +extern PGDLLIMPORT char *GUC_check_errhint_string; + +extern void GUC_check_errcode(int sqlerrcode); + +#define GUC_check_errmsg \ + pre_format_elog_string(errno, TEXTDOMAIN), \ + GUC_check_errmsg_string = format_elog_string + +#define GUC_check_errdetail \ + pre_format_elog_string(errno, TEXTDOMAIN), \ + GUC_check_errdetail_string = format_elog_string + +#define GUC_check_errhint \ + pre_format_elog_string(errno, TEXTDOMAIN), \ + GUC_check_errhint_string = format_elog_string + + /* * The following functions are not in guc.c, but are declared here to avoid * having to include guc.h in some widely used headers that it really doesn't @@ -324,17 +364,16 @@ extern void read_nondefault_variables(void); */ /* in commands/tablespace.c */ -extern const char *assign_default_tablespace(const char *newval, - bool doit, GucSource source); -extern const char *assign_temp_tablespaces(const char *newval, - bool doit, GucSource source); +extern bool check_default_tablespace(char **newval, void **extra, GucSource source); +extern bool check_temp_tablespaces(char **newval, void **extra, GucSource source); +extern void assign_temp_tablespaces(const char *newval, void *extra); /* in catalog/namespace.c */ -extern const char *assign_search_path(const char *newval, - bool doit, GucSource source); +extern bool check_search_path(char **newval, void **extra, GucSource source); +extern void assign_search_path(const char *newval, void *extra); /* in access/transam/xlog.c */ -extern bool assign_xlog_sync_method(int newval, - bool doit, GucSource source); +extern bool check_wal_buffers(int *newval, void **extra, GucSource source); +extern void assign_xlog_sync_method(int new_sync_method, void *extra); #endif /* GUC_H */ diff --git a/src/include/utils/guc_tables.h b/src/include/utils/guc_tables.h index 073b77d28c..a1ca012ef9 100644 --- a/src/include/utils/guc_tables.h +++ b/src/include/utils/guc_tables.h @@ -28,7 +28,7 @@ enum config_type PGC_ENUM }; -union config_var_value +union config_var_val { bool boolval; int intval; @@ -37,6 +37,16 @@ union config_var_value int enumval; }; +/* + * The actual value of a GUC variable can include a malloc'd opaque struct + * "extra", which is created by its check_hook and used by its assign_hook. + */ +typedef struct config_var_value +{ + union config_var_val val; + void *extra; +} config_var_value; + /* * Groupings to help organize all the run-time options for display */ @@ -105,8 +115,8 @@ typedef struct guc_stack int nest_level; /* nesting depth at which we made entry */ GucStackState state; /* see enum above */ GucSource source; /* source of the prior value */ - union config_var_value prior; /* previous value of variable */ - union config_var_value masked; /* SET value in a GUC_SET_LOCAL entry */ + config_var_value prior; /* previous value of variable */ + config_var_value masked; /* SET value in a GUC_SET_LOCAL entry */ /* masked value's source must be PGC_S_SESSION, so no need to store it */ } GucStack; @@ -116,6 +126,11 @@ typedef struct guc_stack * The short description should be less than 80 chars in length. Some * applications may use the long description as well, and will append * it to the short description. (separated by a newline or '. ') + * + * Note that sourcefile/sourceline are kept here, and not pushed into stacked + * values, although in principle they belong with some stacked value if the + * active value is session- or transaction-local. This is to avoid bloating + * stack entries. We know they are only relevant when source == PGC_S_FILE. */ struct config_generic { @@ -132,7 +147,8 @@ struct config_generic GucSource reset_source; /* source of the reset_value */ GucSource source; /* source of the current actual value */ GucStack *stack; /* stacked prior values */ - char *sourcefile; /* file this settings is from (NULL if not + void *extra; /* "extra" pointer for current actual value */ + char *sourcefile; /* file current setting is from (NULL if not * file) */ int sourceline; /* line in source file */ }; @@ -155,10 +171,12 @@ struct config_bool /* constant fields, must be set correctly in initial value: */ bool *variable; bool boot_val; + GucBoolCheckHook check_hook; GucBoolAssignHook assign_hook; GucShowHook show_hook; /* variable fields, initialized at runtime: */ bool reset_val; + void *reset_extra; }; struct config_int @@ -169,10 +187,12 @@ struct config_int int boot_val; int min; int max; + GucIntCheckHook check_hook; GucIntAssignHook assign_hook; GucShowHook show_hook; /* variable fields, initialized at runtime: */ int reset_val; + void *reset_extra; }; struct config_real @@ -183,10 +203,12 @@ struct config_real double boot_val; double min; double max; + GucRealCheckHook check_hook; GucRealAssignHook assign_hook; GucShowHook show_hook; /* variable fields, initialized at runtime: */ double reset_val; + void *reset_extra; }; struct config_string @@ -195,10 +217,12 @@ struct config_string /* constant fields, must be set correctly in initial value: */ char **variable; const char *boot_val; + GucStringCheckHook check_hook; GucStringAssignHook assign_hook; GucShowHook show_hook; /* variable fields, initialized at runtime: */ char *reset_val; + void *reset_extra; }; struct config_enum @@ -208,10 +232,12 @@ struct config_enum int *variable; int boot_val; const struct config_enum_entry *options; + GucEnumCheckHook check_hook; GucEnumAssignHook assign_hook; GucShowHook show_hook; /* variable fields, initialized at runtime: */ int reset_val; + void *reset_extra; }; /* constant tables corresponding to enums above and in guc.h */ diff --git a/src/include/utils/pg_locale.h b/src/include/utils/pg_locale.h index 4c72fd0dc6..25b9d50915 100644 --- a/src/include/utils/pg_locale.h +++ b/src/include/utils/pg_locale.h @@ -33,14 +33,14 @@ extern char *localized_abbrev_months[]; extern char *localized_full_months[]; -extern const char *locale_messages_assign(const char *value, - bool doit, GucSource source); -extern const char *locale_monetary_assign(const char *value, - bool doit, GucSource source); -extern const char *locale_numeric_assign(const char *value, - bool doit, GucSource source); -extern const char *locale_time_assign(const char *value, - bool doit, GucSource source); +extern bool check_locale_messages(char **newval, void **extra, GucSource source); +extern void assign_locale_messages(const char *newval, void *extra); +extern bool check_locale_monetary(char **newval, void **extra, GucSource source); +extern void assign_locale_monetary(const char *newval, void *extra); +extern bool check_locale_numeric(char **newval, void **extra, GucSource source); +extern void assign_locale_numeric(const char *newval, void *extra); +extern bool check_locale_time(char **newval, void **extra, GucSource source); +extern void assign_locale_time(const char *newval, void *extra); extern bool check_locale(int category, const char *locale); extern char *pg_perm_setlocale(int category, const char *locale); diff --git a/src/include/utils/tzparser.h b/src/include/utils/tzparser.h index ba18819ef5..2065078116 100644 --- a/src/include/utils/tzparser.h +++ b/src/include/utils/tzparser.h @@ -13,6 +13,8 @@ #ifndef TZPARSER_H #define TZPARSER_H +#include "utils/datetime.h" + /* * The result of parsing a timezone configuration file is an array of * these structs, in order by abbrev. We export this because datetime.c @@ -30,6 +32,6 @@ typedef struct tzEntry } tzEntry; -extern bool load_tzoffsets(const char *filename, bool doit, int elevel); +extern TimeZoneAbbrevTable *load_tzoffsets(const char *filename); #endif /* TZPARSER_H */ diff --git a/src/pl/plperl/plperl.c b/src/pl/plperl/plperl.c index 9a94b3f085..35bf72ca30 100644 --- a/src/pl/plperl/plperl.c +++ b/src/pl/plperl/plperl.c @@ -363,7 +363,7 @@ _PG_init(void) &plperl_use_strict, false, PGC_USERSET, 0, - NULL, NULL); + NULL, NULL, NULL); /* * plperl.on_init is marked PGC_SIGHUP to support the idea that it might @@ -377,7 +377,7 @@ _PG_init(void) &plperl_on_init, NULL, PGC_SIGHUP, 0, - NULL, NULL); + NULL, NULL, NULL); /* * plperl.on_plperl_init is marked PGC_SUSET to avoid issues whereby a @@ -399,7 +399,7 @@ _PG_init(void) &plperl_on_plperl_init, NULL, PGC_SUSET, 0, - NULL, NULL); + NULL, NULL, NULL); DefineCustomStringVariable("plperl.on_plperlu_init", gettext_noop("Perl initialization code to execute once when plperlu is first used."), @@ -407,7 +407,7 @@ _PG_init(void) &plperl_on_plperlu_init, NULL, PGC_SUSET, 0, - NULL, NULL); + NULL, NULL, NULL); EmitWarningsOnPlaceholders("plperl"); diff --git a/src/pl/plpgsql/src/pl_handler.c b/src/pl/plpgsql/src/pl_handler.c index 2389849a22..e6e71432ff 100644 --- a/src/pl/plpgsql/src/pl_handler.c +++ b/src/pl/plpgsql/src/pl_handler.c @@ -63,7 +63,7 @@ _PG_init(void) PLPGSQL_RESOLVE_ERROR, variable_conflict_options, PGC_SUSET, 0, - NULL, NULL); + NULL, NULL, NULL); EmitWarningsOnPlaceholders("plpgsql"); diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out index a0e24e84cb..98671a2bcd 100644 --- a/src/test/regress/expected/guc.out +++ b/src/test/regress/expected/guc.out @@ -622,7 +622,8 @@ alter function report_guc(text) set search_path = no_such_schema; NOTICE: schema "no_such_schema" does not exist -- with error occurring here select report_guc('work_mem'), current_setting('work_mem'); -ERROR: schema "no_such_schema" does not exist +ERROR: invalid value for parameter "search_path": "no_such_schema" +DETAIL: schema "no_such_schema" does not exist alter function report_guc(text) reset search_path set work_mem = '2MB'; select report_guc('work_mem'), current_setting('work_mem'); report_guc | current_setting diff --git a/src/timezone/pgtz.c b/src/timezone/pgtz.c index d09d4ad736..382a4e04f5 100644 --- a/src/timezone/pgtz.c +++ b/src/timezone/pgtz.c @@ -1444,27 +1444,39 @@ pg_timezone_initialize(void) { pg_tz *def_tz = NULL; - /* Do we need to try to figure the session timezone? */ - if (pg_strcasecmp(GetConfigOption("timezone", false), "UNKNOWN") == 0) + /* + * Make sure that session_timezone and log_timezone are set. + * (session_timezone could still be NULL even if a timezone value was set + * in postgresql.conf, if that setting was interval-based rather than + * timezone-based.) + */ + if (!session_timezone) { - /* Select setting */ def_tz = select_default_timezone(); session_timezone = def_tz; - /* Tell GUC about the value. Will redundantly call pg_tzset() */ - SetConfigOption("timezone", pg_get_timezone_name(def_tz), - PGC_POSTMASTER, PGC_S_ARGV); } - - /* What about the log timezone? */ - if (pg_strcasecmp(GetConfigOption("log_timezone", false), "UNKNOWN") == 0) + if (!log_timezone) { - /* Select setting, but don't duplicate work */ + /* Don't duplicate work */ if (!def_tz) def_tz = select_default_timezone(); log_timezone = def_tz; + } + + /* Now, set the timezone GUC if it's not already set */ + if (GetConfigOption("timezone", false) == NULL) + { /* Tell GUC about the value. Will redundantly call pg_tzset() */ - SetConfigOption("log_timezone", pg_get_timezone_name(def_tz), - PGC_POSTMASTER, PGC_S_ARGV); + SetConfigOption("timezone", pg_get_timezone_name(session_timezone), + PGC_POSTMASTER, PGC_S_ENV_VAR); + } + + /* Likewise for log timezone */ + if (GetConfigOption("log_timezone", false) == NULL) + { + /* Tell GUC about the value. Will redundantly call pg_tzset() */ + SetConfigOption("log_timezone", pg_get_timezone_name(log_timezone), + PGC_POSTMASTER, PGC_S_ENV_VAR); } }