I wrote:

> So I went through the psql commands which don't fail on parse errors
> for booleans
> [...]

Here's a v5 patch implementing the suggestions mentioned upthread:
all meta-commands calling ParseVariableBool() now fail
when the boolean argument can't be parsed successfully.

Also includes a minor change to SetVariableAssignHook() that now
returns the result of the hook it calls after installing it.
It doesn't make any difference in psql behavior since callers
of SetVariableAssignHook() ignore its return value, but it's
more consistent with SetVariable().

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..356467b 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -254,25 +254,30 @@ 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, &success) ?
                                TRI_YES : TRI_NO;
-
-                       free(opt1);
-                       opt1 = read_connect_arg(scan_state);
+                       if (success)
+                       {
+                               free(opt1);
+                               opt1 = read_connect_arg(scan_state);
+                       }
                }
                else
                        reuse_previous = TRI_DEFAULT;
 
-               opt2 = read_connect_arg(scan_state);
-               opt3 = read_connect_arg(scan_state);
-               opt4 = read_connect_arg(scan_state);
+               if (success)                    /* do not attempt to connect if 
reuse_previous argument was invalid */
+               {
+                       opt2 = read_connect_arg(scan_state);
+                       opt3 = read_connect_arg(scan_state);
+                       opt4 = read_connect_arg(scan_state);
 
-               success = do_connect(reuse_previous, opt1, opt2, opt3, opt4);
+                       success = do_connect(reuse_previous, opt1, opt2, opt3, 
opt4);
 
+                       free(opt2);
+                       free(opt3);
+                       free(opt4);
+               }
                free(opt1);
-               free(opt2);
-               free(opt3);
-               free(opt4);
        }
 
        /* \cd */
@@ -1548,7 +1553,11 @@ exec_command(const char *cmd,
                                                                                
                 OT_NORMAL, NULL, false);
 
                if (opt)
-                       pset.timing = ParseVariableBool(opt, "\\timing");
+               {
+                       bool    newval = ParseVariableBool(opt, "\\timing", 
&success);
+                       if (success)
+                               pset.timing = newval;
+               }
                else
                        pset.timing = !pset.timing;
                if (!pset.quiet)
@@ -2660,7 +2669,14 @@ 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);
+               {
+                       bool    valid;
+                       bool    newval = ParseVariableBool(value, param, 
&valid);
+                       if (valid)
+                               popt->topt.expanded = newval;
+                       else
+                               return false;
+               }
                else
                        popt->topt.expanded = !popt->topt.expanded;
        }
@@ -2668,8 +2684,16 @@ do_pset(const char *param, const char *value, 
printQueryOpt *popt, bool quiet)
        /* locale-aware numeric output */
        else if (strcmp(param, "numericlocale") == 0)
        {
+
                if (value)
-                       popt->topt.numericLocale = ParseVariableBool(value, 
param);
+               {
+                       bool    valid;
+                       bool    newval = ParseVariableBool(value, param, 
&valid);
+                       if (valid)
+                               popt->topt.numericLocale = newval;
+                       else
+                               return false;
+               }
                else
                        popt->topt.numericLocale = !popt->topt.numericLocale;
        }
@@ -2724,7 +2748,14 @@ 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);
+               {
+                       bool    valid;
+                       bool    newval = ParseVariableBool(value, param, 
&valid);
+                       if (valid)
+                               popt->topt.tuples_only = newval;
+                       else
+                               return false;
+               }
                else
                        popt->topt.tuples_only = !popt->topt.tuples_only;
        }
@@ -2756,10 +2787,11 @@ do_pset(const char *param, const char *value, 
printQueryOpt *popt, bool quiet)
                        popt->topt.pager = 2;
                else if (value)
                {
-                       if (ParseVariableBool(value, param))
-                               popt->topt.pager = 1;
-                       else
-                               popt->topt.pager = 0;
+                       bool    valid;
+                       bool    on = ParseVariableBool(value, param, &valid);
+                       if (!valid)
+                               return false;
+                       popt->topt.pager = on ? 1 : 0;
                }
                else if (popt->topt.pager == 1)
                        popt->topt.pager = 0;
@@ -2778,7 +2810,14 @@ 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);
+               {
+                       bool    valid;
+                       bool    newval = ParseVariableBool(value, param, 
&valid);
+                       if (valid)
+                               popt->topt.default_footer = newval;
+                       else
+                               return false;
+               }
                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..1302a32 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,8 +300,7 @@ SetVariableAssignHook(VariableSpace space, const char 
*name, VariableAssignHook
                {
                        /* found entry, so update */
                        current->assign_hook = hook;
-                       (*hook) (current->value);
-                       return true;
+                       return (*hook) (current->value);
                }
        }
 
@@ -260,8 +311,7 @@ SetVariableAssignHook(VariableSpace space, const char 
*name, VariableAssignHook
        current->assign_hook = hook;
        current->next = NULL;
        previous->next = current;
-       (*hook) (NULL);
-       return true;
+       return (*hook) (NULL);
 }
 
 bool
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

Reply via email to