Stephen Frost wrote: > Just fyi, there seems to be some issues with this patch because setting > my PROMPT1 and PROMPT2 variables result in rather odd behavior.
Good catch! The issue is that the patch broke the assumption of prompt hooks that their argument points to a long-lived buffer. The attached v4 fixes the bug by passing to hooks a pointer to the final strdup'ed value in VariableSpace rather than temp space, as commented in SetVariable(). Also I've changed something else in ParseVariableBool(). The code assumes "false" when value==NULL, but when value is an empty string, the result was true and considered valid, due to the following test being positive when len==0 (both with HEAD or the v3 patch from this thread): if (pg_strncasecmp(value, "true", len) == 0) return true; It happens that "" as a value yields the same result than "true" for this test so it passes, but it seems rather unintentional. The resulting problem from the POV of the user is that we can do that, for instance: test=> \set AUTOCOMMIT without error message or feedback, and that leaves us without much clue about autocommit being enabled: test=> \echo :AUTOCOMMIT test=> So I've changed ParseVariableBool() in v4 to reject empty string as an invalid boolean (but not NULL since the startup logic requires NULL meaning false in the early initialization of these variables). "make check" seems OK with that, I hope it doesn't cause any regression elsewhere. Best regards, -- Daniel Vérité PostgreSQL-powered mailer: http://www.manitou-mail.org Twitter: @DanielVerite
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index a9a2fdb..61b2304 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -254,7 +254,7 @@ exec_command(const char *cmd, if (opt1 != NULL && strncmp(opt1, prefix, sizeof(prefix) - 1) == 0) { reuse_previous = - ParseVariableBool(opt1 + sizeof(prefix) - 1, prefix) ? + ParseVariableBool(opt1 + sizeof(prefix) - 1, prefix, NULL) ? TRI_YES : TRI_NO; free(opt1); @@ -1548,7 +1548,7 @@ exec_command(const char *cmd, OT_NORMAL, NULL, false); if (opt) - pset.timing = ParseVariableBool(opt, "\\timing"); + pset.timing = ParseVariableBool(opt, "\\timing", NULL); else pset.timing = !pset.timing; if (!pset.quiet) @@ -2660,7 +2660,7 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) if (value && pg_strcasecmp(value, "auto") == 0) popt->topt.expanded = 2; else if (value) - popt->topt.expanded = ParseVariableBool(value, param); + popt->topt.expanded = ParseVariableBool(value, param, NULL); else popt->topt.expanded = !popt->topt.expanded; } @@ -2669,7 +2669,7 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) else if (strcmp(param, "numericlocale") == 0) { if (value) - popt->topt.numericLocale = ParseVariableBool(value, param); + popt->topt.numericLocale = ParseVariableBool(value, param, NULL); else popt->topt.numericLocale = !popt->topt.numericLocale; } @@ -2724,7 +2724,7 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) else if (strcmp(param, "t") == 0 || strcmp(param, "tuples_only") == 0) { if (value) - popt->topt.tuples_only = ParseVariableBool(value, param); + popt->topt.tuples_only = ParseVariableBool(value, param, NULL); else popt->topt.tuples_only = !popt->topt.tuples_only; } @@ -2756,7 +2756,7 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) popt->topt.pager = 2; else if (value) { - if (ParseVariableBool(value, param)) + if (ParseVariableBool(value, param, NULL)) popt->topt.pager = 1; else popt->topt.pager = 0; @@ -2778,7 +2778,7 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) else if (strcmp(param, "footer") == 0) { if (value) - popt->topt.default_footer = ParseVariableBool(value, param); + popt->topt.default_footer = ParseVariableBool(value, param, NULL); else popt->topt.default_footer = !popt->topt.default_footer; } diff --git a/src/bin/psql/startup.c b/src/bin/psql/startup.c index 7ce05fb..ba094f0 100644 --- a/src/bin/psql/startup.c +++ b/src/bin/psql/startup.c @@ -786,43 +786,68 @@ showVersion(void) * This isn't an amazingly good place for them, but neither is anywhere else. */ -static void +/* + * Hook to set an internal flag from a user-supplied string value. + * If the syntax is correct, affect *flag and return true. + * Otherwise, keep *flag untouched and return false. + */ +static bool +generic_boolean_hook(const char *newval, const char* varname, bool *flag) +{ + bool isvalid; + bool val = ParseVariableBool(newval, varname, &isvalid); + if (isvalid) + *flag = val; + return isvalid; +} + +static bool autocommit_hook(const char *newval) { - pset.autocommit = ParseVariableBool(newval, "AUTOCOMMIT"); + return generic_boolean_hook(newval, "AUTOCOMMIT", &pset.autocommit); } -static void +static bool on_error_stop_hook(const char *newval) { - pset.on_error_stop = ParseVariableBool(newval, "ON_ERROR_STOP"); + return generic_boolean_hook(newval, "ON_ERROR_STOP", &pset.on_error_stop); } -static void +static bool quiet_hook(const char *newval) { - pset.quiet = ParseVariableBool(newval, "QUIET"); + return generic_boolean_hook(newval, "QUIET", &pset.quiet); } -static void +static bool singleline_hook(const char *newval) { - pset.singleline = ParseVariableBool(newval, "SINGLELINE"); + return generic_boolean_hook(newval, "SINGLELINE", &pset.singleline); } -static void +static bool singlestep_hook(const char *newval) { - pset.singlestep = ParseVariableBool(newval, "SINGLESTEP"); + return generic_boolean_hook(newval, "SINGLESTEP", &pset.singlestep); } -static void +static bool fetch_count_hook(const char *newval) { - pset.fetch_count = ParseVariableNum(newval, -1, -1, false); + bool valid; + int value = ParseCheckVariableNum(newval, -1, &valid); + if (valid) + pset.fetch_count = value; + else + { + psql_error("invalid value \"%s\" for \"%s\"\n", + newval, "FETCH_COUNT"); + return false; + } + return true; } -static void +static bool echo_hook(const char *newval) { if (newval == NULL) @@ -837,39 +862,52 @@ echo_hook(const char *newval) pset.echo = PSQL_ECHO_NONE; else { - psql_error("unrecognized value \"%s\" for \"%s\"; assuming \"%s\"\n", - newval, "ECHO", "none"); - pset.echo = PSQL_ECHO_NONE; + psql_error("unrecognized value \"%s\" for \"%s\"\n", + newval, "ECHO"); + return false; } + return true; } -static void +static bool echo_hidden_hook(const char *newval) { if (newval == NULL) pset.echo_hidden = PSQL_ECHO_HIDDEN_OFF; else if (pg_strcasecmp(newval, "noexec") == 0) pset.echo_hidden = PSQL_ECHO_HIDDEN_NOEXEC; - else if (ParseVariableBool(newval, "ECHO_HIDDEN")) - pset.echo_hidden = PSQL_ECHO_HIDDEN_ON; - else /* ParseVariableBool printed msg if needed */ - pset.echo_hidden = PSQL_ECHO_HIDDEN_OFF; + else + { + bool isvalid; + bool val = ParseVariableBool(newval, "ECHO_HIDDEN", &isvalid); + if (!isvalid) + return false; /* ParseVariableBool printed msg */ + pset.echo_hidden = val ? PSQL_ECHO_HIDDEN_ON : PSQL_ECHO_HIDDEN_OFF; + } + return true; } -static void +static bool on_error_rollback_hook(const char *newval) { if (newval == NULL) pset.on_error_rollback = PSQL_ERROR_ROLLBACK_OFF; else if (pg_strcasecmp(newval, "interactive") == 0) pset.on_error_rollback = PSQL_ERROR_ROLLBACK_INTERACTIVE; - else if (ParseVariableBool(newval, "ON_ERROR_ROLLBACK")) - pset.on_error_rollback = PSQL_ERROR_ROLLBACK_ON; - else /* ParseVariableBool printed msg if needed */ - pset.on_error_rollback = PSQL_ERROR_ROLLBACK_OFF; + else + { + bool isvalid; + bool val = ParseVariableBool(newval, "ON_ERROR_ROLLBACK", &isvalid); + if (isvalid) + pset.on_error_rollback = val ? PSQL_ERROR_ROLLBACK_ON : PSQL_ERROR_ROLLBACK_OFF; + else + /* ParseVariableBool printed msg if needed */ + return false; + } + return true; } -static void +static bool comp_keyword_case_hook(const char *newval) { if (newval == NULL) @@ -884,13 +922,14 @@ comp_keyword_case_hook(const char *newval) pset.comp_case = PSQL_COMP_CASE_LOWER; else { - psql_error("unrecognized value \"%s\" for \"%s\"; assuming \"%s\"\n", - newval, "COMP_KEYWORD_CASE", "preserve-upper"); - pset.comp_case = PSQL_COMP_CASE_PRESERVE_UPPER; + psql_error("unrecognized value \"%s\" for \"%s\"\n", + newval, "COMP_KEYWORD_CASE"); + return false; } + return true; } -static void +static bool histcontrol_hook(const char *newval) { if (newval == NULL) @@ -905,31 +944,35 @@ histcontrol_hook(const char *newval) pset.histcontrol = hctl_none; else { - psql_error("unrecognized value \"%s\" for \"%s\"; assuming \"%s\"\n", - newval, "HISTCONTROL", "none"); - pset.histcontrol = hctl_none; + psql_error("unrecognized value \"%s\" for \"%s\"\n", + newval, "HISTCONTROL"); + return false; } + return true; } -static void +static bool prompt1_hook(const char *newval) { pset.prompt1 = newval ? newval : ""; + return true; } -static void +static bool prompt2_hook(const char *newval) { pset.prompt2 = newval ? newval : ""; + return true; } -static void +static bool prompt3_hook(const char *newval) { pset.prompt3 = newval ? newval : ""; + return true; } -static void +static bool verbosity_hook(const char *newval) { if (newval == NULL) @@ -942,16 +985,17 @@ verbosity_hook(const char *newval) pset.verbosity = PQERRORS_VERBOSE; else { - psql_error("unrecognized value \"%s\" for \"%s\"; assuming \"%s\"\n", - newval, "VERBOSITY", "default"); - pset.verbosity = PQERRORS_DEFAULT; + psql_error("unrecognized value \"%s\" for \"%s\"\n", + newval, "VERBOSITY"); + return false; } if (pset.db) PQsetErrorVerbosity(pset.db, pset.verbosity); + return true; } -static void +static bool show_context_hook(const char *newval) { if (newval == NULL) @@ -964,13 +1008,14 @@ show_context_hook(const char *newval) pset.show_context = PQSHOW_CONTEXT_ALWAYS; else { - psql_error("unrecognized value \"%s\" for \"%s\"; assuming \"%s\"\n", - newval, "SHOW_CONTEXT", "errors"); - pset.show_context = PQSHOW_CONTEXT_ERRORS; + psql_error("unrecognized value \"%s\" for \"%s\"\n", + newval, "SHOW_CONTEXT"); + return false; } if (pset.db) PQsetErrorContextVisibility(pset.db, pset.show_context); + return true; } diff --git a/src/bin/psql/variables.c b/src/bin/psql/variables.c index f43f418..8532af4 100644 --- a/src/bin/psql/variables.c +++ b/src/bin/psql/variables.c @@ -86,24 +86,28 @@ GetVariable(VariableSpace space, const char *name) * * "name" is the name of the variable we're assigning to, to use in error * report if any. Pass name == NULL to suppress the error report. + * + * "*valid" reports whether "value" was syntactically valid, unless valid == NULL */ bool -ParseVariableBool(const char *value, const char *name) +ParseVariableBool(const char *value, const char *name, bool *valid) { size_t len; + if (valid) + *valid = true; if (value == NULL) return false; /* not set -> assume "off" */ len = strlen(value); - if (pg_strncasecmp(value, "true", len) == 0) + if (len > 0 && pg_strncasecmp(value, "true", len) == 0) return true; - else if (pg_strncasecmp(value, "false", len) == 0) + else if (len > 0 && pg_strncasecmp(value, "false", len) == 0) return false; - else if (pg_strncasecmp(value, "yes", len) == 0) + else if (len > 0 && pg_strncasecmp(value, "yes", len) == 0) return true; - else if (pg_strncasecmp(value, "no", len) == 0) + else if (len > 0 && pg_strncasecmp(value, "no", len) == 0) return false; /* 'o' is not unique enough */ else if (pg_strncasecmp(value, "on", (len > 2 ? len : 2)) == 0) @@ -116,10 +120,16 @@ ParseVariableBool(const char *value, const char *name) return false; else { - /* NULL is treated as false, so a non-matching value is 'true' */ + /* + * NULL is treated as false, so a non-matching value is 'true'. + * A caller that cares about syntactic conformance should + * check *valid to know whether the value was recognized. + */ if (name) - psql_error("unrecognized value \"%s\" for \"%s\"; assuming \"%s\"\n", - value, name, "on"); + psql_error("unrecognized value \"%s\" for \"%s\": boolean expected\n", + value, name); + if (valid) + *valid = false; return true; } } @@ -154,6 +164,35 @@ ParseVariableNum(const char *val, return result; } +/* + * Read numeric variable, or defaultval if it is not set. + * Trailing contents are not allowed. + * "valid" points to a bool reporting whether the value was valid. + */ +int +ParseCheckVariableNum(const char *val, + int defaultval, + bool *valid) +{ + int result; + + if (!val) + { + *valid = true; + result = defaultval; + } + else + { + char *end; + + errno = 0; + result = strtol(val, &end, 0); + *valid = (val[0] != '\0' && errno == 0 && *end == '\0'); + } + + return result; +} + int GetVariableNum(VariableSpace space, const char *name, @@ -205,13 +244,26 @@ SetVariable(VariableSpace space, const char *name, const char *value) { if (strcmp(current->name, name) == 0) { - /* found entry, so update */ - if (current->value) - free(current->value); - current->value = pg_strdup(value); - if (current->assign_hook) - (*current->assign_hook) (current->value); - return true; + /* + * Found entry, so update, unless a hook returns false. + * The hook needs the value in a buffer with the same lifespan + * as the variable, so allocate it right away, even if it needs + * to be freed just after when the hook returns false. + */ + char *new_value = pg_strdup(value); + + bool confirmed = current->assign_hook ? + (*current->assign_hook) (new_value) : true; + + if (confirmed) + { + pg_free(current->value); + current->value = new_value; + } + else + pg_free(new_value); /* and current->value is left unchanged */ + + return confirmed; } } @@ -248,7 +300,7 @@ SetVariableAssignHook(VariableSpace space, const char *name, VariableAssignHook { /* found entry, so update */ current->assign_hook = hook; - (*hook) (current->value); + (*hook) (current->value); /* ignore return value */ return true; } } @@ -260,7 +312,7 @@ SetVariableAssignHook(VariableSpace space, const char *name, VariableAssignHook current->assign_hook = hook; current->next = NULL; previous->next = current; - (*hook) (NULL); + (void)(*hook) (NULL); /* ignore return value */ return true; } diff --git a/src/bin/psql/variables.h b/src/bin/psql/variables.h index d7a05a1..38396ba 100644 --- a/src/bin/psql/variables.h +++ b/src/bin/psql/variables.h @@ -20,7 +20,7 @@ * Note: if value == NULL then the variable is logically unset, but we are * keeping the struct around so as not to forget about its hook function. */ -typedef void (*VariableAssignHook) (const char *newval); +typedef bool (*VariableAssignHook) (const char *newval); struct _variable { @@ -35,11 +35,14 @@ typedef struct _variable *VariableSpace; VariableSpace CreateVariableSpace(void); const char *GetVariable(VariableSpace space, const char *name); -bool ParseVariableBool(const char *value, const char *name); +bool ParseVariableBool(const char *value, const char *name, bool *valid); int ParseVariableNum(const char *val, int defaultval, int faultval, bool allowtrail); +int ParseCheckVariableNum(const char *val, + int defaultval, + bool *valid); int GetVariableNum(VariableSpace space, const char *name, int defaultval,
-- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers