diff options
Diffstat (limited to 'src/backend/commands')
-rw-r--r-- | src/backend/commands/tablespace.c | 85 | ||||
-rw-r--r-- | src/backend/commands/variable.c | 906 |
2 files changed, 500 insertions, 491 deletions
diff --git a/src/backend/commands/tablespace.c b/src/backend/commands/tablespace.c index b5a2d9d005e..42a704beb16 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 2a61ea3bc7c..2cec7130896 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; } + free(*newval); + *newval = result; + /* - * Finally, it's safe to assign to the global variables; the assignment - * cannot fail now. + * Set up the "extra" struct actually used by assign_datestyle. */ - DateStyle = newDateStyle; - DateOrder = newDateOrder; + 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; - return result; + DateStyle = myextra[0]; + DateOrder = myextra[1]; } @@ -232,22 +241,58 @@ assign_datestyle(const char *value, bool doit, GucSource source) * TIMEZONE */ +typedef struct +{ + pg_tz *session_timezone; + int CTimeZone; + bool HasCTZSet; +} timezone_extra; + /* - * assign_timezone: GUC assign_hook for timezone + * check_timezone: GUC check_hook for timezone */ -const char * -assign_timezone(const char *value, bool doit, GucSource source) +bool +check_timezone(char **newval, void **extra, GucSource source) { - char *result; + timezone_extra myextra; char *endptr; double hours; + if (*newval == NULL) + { + /* + * 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; + } + /* - * Check for INTERVAL 'foo' + * 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. */ - if (pg_strncasecmp(value, "interval", 8) == 0) + myextra.session_timezone = session_timezone; + myextra.CTimeZone = CTimeZone; + myextra.HasCTZSet = HasCTZSet; + + if (pg_strncasecmp(*newval, "interval", 8) == 0) { - const char *valueptr = value; + /* + * 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 */ + + /* Here we change from SQL to Unix sign convention */ #ifdef HAVE_INT64_TIMESTAMP - CTimeZone = -(interval->time / USECS_PER_SEC); + myextra.CTimeZone = -(interval->time / USECS_PER_SEC); #else - CTimeZone = -interval->time; + myextra.CTimeZone = -interval->time; #endif + myextra.HasCTZSet = true; - 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') - { - 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) + hours = strtod(*newval, &endptr); + if (endptr != *newval && *endptr == '\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. - */ - if (!doit) - return value; - - /* * 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 (HasCTZSet) + if (myextra.HasCTZSet) { - result = (char *) malloc(64); + char *result = (char *) malloc(64); + if (!result) - return NULL; + return false; snprintf(result, 64, "%.5f", - (double) (-CTimeZone) / (double) SECS_PER_HOUR); + (double) (-myextra.CTimeZone) / (double) SECS_PER_HOUR); + free(*newval); + *newval = result; } - else - result = strdup(value); - return result; + /* + * Pass back data for assign_timezone to use + */ + *extra = malloc(sizeof(timezone_extra)); + if (!*extra) + return false; + memcpy(*extra, &myextra, sizeof(timezone_extra)); + + 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. - */ - pg_tz *new_tz; - - 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; - } + /* + * Otherwise assume it is a timezone name, and try to load it. + */ + new_tz = pg_tzset(*newval); - 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 (!new_tz) + { + /* Doesn't seem to be any great value in errdetail here */ + return false; + } - 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; +} + +/* + * 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; - return result; + 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 true; +} - return value; +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')) - { - /* might be a saved userid string */ - Oid savedoid; - char *endptr; +bool +check_session_authorization(char **newval, void **extra, GucSource source) +{ + HeapTuple roleTup; + Oid roleid; + bool is_superuser; + role_auth_extra *myextra; - savedoid = (Oid) strtoul(value + NAMEDATALEN + 1, &endptr, 10); + /* Do nothing for the boot_val default of NULL */ + if (*newval == NULL) + return true; - if (endptr != value + NAMEDATALEN + 1 && *endptr == ',') - { - /* syntactically valid, so break out the data */ - roleid = savedoid; - is_superuser = (value[NAMEDATALEN] == 'T'); - actual_rolename = endptr + 1; - } + if (!IsTransactionState()) + { + /* + * 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; - - memset(result, 'x', NAMEDATALEN); + ReleaseSysCache(roleTup); - sprintf(result + NAMEDATALEN, "%c%u,%s", - is_superuser ? 'T' : 'F', - roleid, - actual_rolename); + /* 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; - 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); - - result = (char *) malloc(NAMEDATALEN + 32 + strlen(actual_rolename)); - if (!result) - return NULL; + /* 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; - memset(result, 'x', NAMEDATALEN); + return true; +} - sprintf(result + NAMEDATALEN, "%c%u,%s", - is_superuser ? 'T' : 'F', - roleid, - actual_rolename); +void +assign_role(const char *newval, void *extra) +{ + role_auth_extra *myextra = (role_auth_extra *) extra; - return result; + SetCurrentRoleId(myextra->roleid, myextra->is_superuser); } const char * show_role(void) { /* - * Extract the role name from the stored string; see assign_role - */ - 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) - 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). + * 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). */ - if (savedoid != GetCurrentRoleId()) + if (!OidIsValid(GetCurrentRoleId())) return "none"; - return endptr + 1; + /* Otherwise we can just use the GUC string */ + return role_string ? role_string : "none"; } |