diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 4e51e90906c558079921f800ffad8ef0acc31e76..b9c8fccde43235b390614c107b64f304b662780d 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -455,8 +455,8 @@ EOF any, by an equal sign on the command line. To unset a variable, leave off the equal sign. To set a variable with an empty value, use the equal sign but leave off the value. These assignments are - done during a very early stage of start-up, so variables reserved - for internal purposes might get overwritten later. + done during command line processing, so variables that reflect + connection state will get overwritten later. </para> </listitem> </varlistentry> @@ -2692,7 +2692,7 @@ lo_import 152801 class="parameter">name</replaceable> to <replaceable class="parameter">value</replaceable>, or if more than one value is given, to the concatenation of all of them. If only one - argument is given, the variable is set with an empty value. To + argument is given, the variable is set to an empty-string value. To unset a variable, use the <command>\unset</command> command. </para> @@ -2709,9 +2709,11 @@ lo_import 152801 </para> <para> - Although you are welcome to set any variable to anything you - want, <application>psql</application> treats several variables - as special. They are documented in the section about variables. + Certain variables are special, in that they + control <application>psql</application>'s behavior or are + automatically set to reflect connection state. These variables are + documented in <xref linkend="APP-PSQL-variables" + endterm="APP-PSQL-variables-title">, below. </para> <note> @@ -2835,6 +2837,14 @@ testdb=> <userinput>\setenv LESS -imx4F</userinput> Unsets (deletes) the <application>psql</> variable <replaceable class="parameter">name</replaceable>. </para> + + <para> + Most variables that control <application>psql</application>'s behavior + cannot be unset; instead, an <literal>\unset</> command is interpreted + as setting them to their default values. + See <xref linkend="APP-PSQL-variables" + endterm="APP-PSQL-variables-title">, below. + </para> </listitem> </varlistentry> @@ -3053,7 +3063,7 @@ bar <para> If you call <command>\set</command> without a second argument, the - variable is set, with an empty string as value. To unset (i.e., delete) + variable is set to an empty-string value. To unset (i.e., delete) a variable, use the command <command>\unset</command>. To show the values of all variables, call <command>\set</command> without any argument. </para> @@ -3082,8 +3092,23 @@ bar By convention, all specially treated variables' names consist of all upper-case ASCII letters (and possibly digits and underscores). To ensure maximum compatibility in the future, avoid - using such variable names for your own purposes. A list of all specially - treated variables follows. + using such variable names for your own purposes. + </para> + + <para> + Variables that control <application>psql</application>'s behavior + generally cannot be unset or set to invalid values. An <literal>\unset</> + command is allowed but is interpreted as setting the variable to its + default value. A <literal>\set</> command without a second argument is + interpreted as setting the variable to <literal>on</>, for control + variables that accept that value, and is rejected for others. Also, + control variables that accept the values <literal>on</> + and <literal>off</> will also accept other common spellings of Boolean + values, such as <literal>true</> and <literal>false</>. + </para> + + <para> + The specially treated variables are: </para> <variablelist> @@ -3153,7 +3178,7 @@ bar <para> The name of the database you are currently connected to. This is set every time you connect to a database (including program - start-up), but can be unset. + start-up), but can be changed or unset. </para> </listitem> </varlistentry> @@ -3171,8 +3196,8 @@ bar as it is sent to the server. The switch to select this behavior is <option>-e</option>. If set to <literal>errors</literal>, then only failed queries are displayed on standard error output. The switch - for this behavior is <option>-b</option>. If unset, or if set to - <literal>none</literal>, then no queries are displayed. + for this behavior is <option>-b</option>. If set to + <literal>none</literal> (the default), then no queries are displayed. </para> </listitem> </varlistentry> @@ -3187,8 +3212,9 @@ bar <productname>PostgreSQL</productname> internals and provide similar functionality in your own programs. (To select this behavior on program start-up, use the switch <option>-E</option>.) If you set - the variable to the value <literal>noexec</literal>, the queries are + this variable to the value <literal>noexec</literal>, the queries are just shown but are not actually sent to the server and executed. + The default value is <literal>off</>. </para> </listitem> </varlistentry> @@ -3200,7 +3226,7 @@ bar The current client character set encoding. This is set every time you connect to a database (including program start-up), and when you change the encoding - with <literal>\encoding</>, but it can be unset. + with <literal>\encoding</>, but it can be changed or unset. </para> </listitem> </varlistentry> @@ -3209,7 +3235,7 @@ bar <term><varname>FETCH_COUNT</varname></term> <listitem> <para> - If this variable is set to an integer value > 0, + If this variable is set to an integer value greater than zero, the results of <command>SELECT</command> queries are fetched and displayed in groups of that many rows, rather than the default behavior of collecting the entire result set before @@ -3220,6 +3246,13 @@ bar Keep in mind that when using this feature, a query might fail after having already displayed some rows. </para> + + <para> + <varname>FETCH_COUNT</varname> is ignored if it is unset or does not + have a positive value. It cannot be set to a value that is not + syntactically an integer. + </para> + <tip> <para> Although you can use any output format with this feature, @@ -3241,7 +3274,7 @@ bar list. If set to a value of <literal>ignoredups</literal>, lines matching the previous history line are not entered. A value of <literal>ignoreboth</literal> combines the two options. If - unset, or if set to <literal>none</literal> (the default), all lines + set to <literal>none</literal> (the default), all lines read in interactive mode are saved on the history list. </para> <note> @@ -3257,8 +3290,12 @@ bar <term><varname>HISTFILE</varname></term> <listitem> <para> - The file name that will be used to store the history list. The default - value is <filename>~/.psql_history</filename>. For example, putting: + The file name that will be used to store the history list. If unset, + the file name is taken from the <envar>PSQL_HISTORY</envar> + environment variable. If that is not set either, the default + is <filename>~/.psql_history</filename>, + or <filename>%APPDATA%\postgresql\psql_history</filename> on Windows. + For example, putting: <programlisting> \set HISTFILE ~/.psql_history- :DBNAME </programlisting> @@ -3279,8 +3316,10 @@ bar <term><varname>HISTSIZE</varname></term> <listitem> <para> - The number of commands to store in the command history. The - default value is 500. + The maximum number of commands to store in the command history. + If unset, at most 500 commands are stored by default. + If set to a value that is negative or not an integer, no limit is + applied. </para> <note> <para> @@ -3297,7 +3336,7 @@ bar <para> The database server host you are currently connected to. This is set every time you connect to a database (including program - start-up), but can be unset. + start-up), but can be changed or unset. </para> </listitem> </varlistentry> @@ -3350,7 +3389,7 @@ bar generates an error, the error is ignored and the transaction continues. When set to <literal>interactive</>, such errors are only ignored in interactive sessions, and not when reading script - files. When unset or set to <literal>off</>, a statement in a + files. When set to <literal>off</> (the default), a statement in a transaction block that generates an error aborts the entire transaction. The error rollback mode works by issuing an implicit <command>SAVEPOINT</> for you, just before each command @@ -3385,7 +3424,7 @@ bar <para> The database server port to which you are currently connected. This is set every time you connect to a database (including - program start-up), but can be unset. + program start-up), but can be changed or unset. </para> </listitem> </varlistentry> @@ -3458,7 +3497,7 @@ bar <para> The database user you are currently connected as. This is set every time you connect to a database (including program - start-up), but can be unset. + start-up), but can be changed or unset. </para> </listitem> </varlistentry> @@ -3481,7 +3520,7 @@ bar <listitem> <para> This variable is set at program start-up to - reflect <application>psql</>'s version. It can be unset or changed. + reflect <application>psql</>'s version. It can be changed or unset. </para> </listitem> </varlistentry> @@ -4015,6 +4054,7 @@ PSQL_EDITOR_LINENUMBER_ARG='--line ' </para> <para> The location of the history file can be set explicitly via + the <varname>HISTFILE</varname> <application>psql</> variable or the <envar>PSQL_HISTORY</envar> environment variable. </para> </listitem> diff --git a/src/bin/psql/startup.c b/src/bin/psql/startup.c index 0574b5bdfb18f01c49afad55e5894d0b8e295dde..a3654e62722cc4201f76078287982f3396b88e96 100644 --- a/src/bin/psql/startup.c +++ b/src/bin/psql/startup.c @@ -166,10 +166,8 @@ main(int argc, char *argv[]) SetVariable(pset.vars, "VERSION", PG_VERSION_STR); - /* Default values for variables */ + /* Default values for variables (that don't match the result of \unset) */ SetVariableBool(pset.vars, "AUTOCOMMIT"); - SetVariable(pset.vars, "VERBOSITY", "default"); - SetVariable(pset.vars, "SHOW_CONTEXT", "errors"); SetVariable(pset.vars, "PROMPT1", DEFAULT_PROMPT1); SetVariable(pset.vars, "PROMPT2", DEFAULT_PROMPT2); SetVariable(pset.vars, "PROMPT3", DEFAULT_PROMPT3); @@ -578,17 +576,13 @@ parse_psql_options(int argc, char *argv[], struct adhoc_opts * options) if (!equal_loc) { if (!DeleteVariable(pset.vars, value)) - { - fprintf(stderr, _("%s: could not delete variable \"%s\"\n"), - pset.progname, value); - exit(EXIT_FAILURE); - } + exit(EXIT_FAILURE); /* error already printed */ } else { *equal_loc = '\0'; if (!SetVariable(pset.vars, value, equal_loc + 1)) - exit(EXIT_FAILURE); + exit(EXIT_FAILURE); /* error already printed */ } free(value); @@ -777,11 +771,28 @@ showVersion(void) /* - * Assign hooks for psql variables. + * Substitute hooks and assign hooks for psql variables. * * This isn't an amazingly good place for them, but neither is anywhere else. */ +static char * +bool_substitute_hook(char *newval) +{ + if (newval == NULL) + { + /* "\unset FOO" becomes "\set FOO off" */ + newval = pg_strdup("off"); + } + else if (newval[0] == '\0') + { + /* "\set FOO" becomes "\set FOO on" */ + pg_free(newval); + newval = pg_strdup("on"); + } + return newval; +} + static bool autocommit_hook(const char *newval) { @@ -822,12 +833,19 @@ fetch_count_hook(const char *newval) return true; } +static char * +echo_substitute_hook(char *newval) +{ + if (newval == NULL) + newval = pg_strdup("none"); + return newval; +} + static bool echo_hook(const char *newval) { - if (newval == NULL) - pset.echo = PSQL_ECHO_NONE; - else if (pg_strcasecmp(newval, "queries") == 0) + Assert(newval != NULL); /* else substitute hook messed up */ + if (pg_strcasecmp(newval, "queries") == 0) pset.echo = PSQL_ECHO_QUERIES; else if (pg_strcasecmp(newval, "errors") == 0) pset.echo = PSQL_ECHO_ERRORS; @@ -846,9 +864,8 @@ echo_hook(const char *newval) 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) + Assert(newval != NULL); /* else substitute hook messed up */ + if (pg_strcasecmp(newval, "noexec") == 0) pset.echo_hidden = PSQL_ECHO_HIDDEN_NOEXEC; else { @@ -868,9 +885,8 @@ echo_hidden_hook(const char *newval) 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) + Assert(newval != NULL); /* else substitute hook messed up */ + if (pg_strcasecmp(newval, "interactive") == 0) pset.on_error_rollback = PSQL_ERROR_ROLLBACK_INTERACTIVE; else { @@ -887,12 +903,19 @@ on_error_rollback_hook(const char *newval) return true; } +static char * +comp_keyword_case_substitute_hook(char *newval) +{ + if (newval == NULL) + newval = pg_strdup("preserve-upper"); + return newval; +} + static bool comp_keyword_case_hook(const char *newval) { - if (newval == NULL) - pset.comp_case = PSQL_COMP_CASE_PRESERVE_UPPER; - else if (pg_strcasecmp(newval, "preserve-upper") == 0) + Assert(newval != NULL); /* else substitute hook messed up */ + if (pg_strcasecmp(newval, "preserve-upper") == 0) pset.comp_case = PSQL_COMP_CASE_PRESERVE_UPPER; else if (pg_strcasecmp(newval, "preserve-lower") == 0) pset.comp_case = PSQL_COMP_CASE_PRESERVE_LOWER; @@ -909,12 +932,19 @@ comp_keyword_case_hook(const char *newval) return true; } +static char * +histcontrol_substitute_hook(char *newval) +{ + if (newval == NULL) + newval = pg_strdup("none"); + return newval; +} + static bool histcontrol_hook(const char *newval) { - if (newval == NULL) - pset.histcontrol = hctl_none; - else if (pg_strcasecmp(newval, "ignorespace") == 0) + Assert(newval != NULL); /* else substitute hook messed up */ + if (pg_strcasecmp(newval, "ignorespace") == 0) pset.histcontrol = hctl_ignorespace; else if (pg_strcasecmp(newval, "ignoredups") == 0) pset.histcontrol = hctl_ignoredups; @@ -952,12 +982,19 @@ prompt3_hook(const char *newval) return true; } +static char * +verbosity_substitute_hook(char *newval) +{ + if (newval == NULL) + newval = pg_strdup("default"); + return newval; +} + static bool verbosity_hook(const char *newval) { - if (newval == NULL) - pset.verbosity = PQERRORS_DEFAULT; - else if (pg_strcasecmp(newval, "default") == 0) + Assert(newval != NULL); /* else substitute hook messed up */ + if (pg_strcasecmp(newval, "default") == 0) pset.verbosity = PQERRORS_DEFAULT; else if (pg_strcasecmp(newval, "terse") == 0) pset.verbosity = PQERRORS_TERSE; @@ -974,12 +1011,19 @@ verbosity_hook(const char *newval) return true; } +static char * +show_context_substitute_hook(char *newval) +{ + if (newval == NULL) + newval = pg_strdup("errors"); + return newval; +} + static bool show_context_hook(const char *newval) { - if (newval == NULL) - pset.show_context = PQSHOW_CONTEXT_ERRORS; - else if (pg_strcasecmp(newval, "never") == 0) + Assert(newval != NULL); /* else substitute hook messed up */ + if (pg_strcasecmp(newval, "never") == 0) pset.show_context = PQSHOW_CONTEXT_NEVER; else if (pg_strcasecmp(newval, "errors") == 0) pset.show_context = PQSHOW_CONTEXT_ERRORS; @@ -1002,20 +1046,52 @@ EstablishVariableSpace(void) { pset.vars = CreateVariableSpace(); - SetVariableAssignHook(pset.vars, "AUTOCOMMIT", autocommit_hook); - SetVariableAssignHook(pset.vars, "ON_ERROR_STOP", on_error_stop_hook); - SetVariableAssignHook(pset.vars, "QUIET", quiet_hook); - SetVariableAssignHook(pset.vars, "SINGLELINE", singleline_hook); - SetVariableAssignHook(pset.vars, "SINGLESTEP", singlestep_hook); - SetVariableAssignHook(pset.vars, "FETCH_COUNT", fetch_count_hook); - SetVariableAssignHook(pset.vars, "ECHO", echo_hook); - SetVariableAssignHook(pset.vars, "ECHO_HIDDEN", echo_hidden_hook); - SetVariableAssignHook(pset.vars, "ON_ERROR_ROLLBACK", on_error_rollback_hook); - SetVariableAssignHook(pset.vars, "COMP_KEYWORD_CASE", comp_keyword_case_hook); - SetVariableAssignHook(pset.vars, "HISTCONTROL", histcontrol_hook); - SetVariableAssignHook(pset.vars, "PROMPT1", prompt1_hook); - SetVariableAssignHook(pset.vars, "PROMPT2", prompt2_hook); - SetVariableAssignHook(pset.vars, "PROMPT3", prompt3_hook); - SetVariableAssignHook(pset.vars, "VERBOSITY", verbosity_hook); - SetVariableAssignHook(pset.vars, "SHOW_CONTEXT", show_context_hook); + SetVariableHooks(pset.vars, "AUTOCOMMIT", + bool_substitute_hook, + autocommit_hook); + SetVariableHooks(pset.vars, "ON_ERROR_STOP", + bool_substitute_hook, + on_error_stop_hook); + SetVariableHooks(pset.vars, "QUIET", + bool_substitute_hook, + quiet_hook); + SetVariableHooks(pset.vars, "SINGLELINE", + bool_substitute_hook, + singleline_hook); + SetVariableHooks(pset.vars, "SINGLESTEP", + bool_substitute_hook, + singlestep_hook); + SetVariableHooks(pset.vars, "FETCH_COUNT", + NULL, + fetch_count_hook); + SetVariableHooks(pset.vars, "ECHO", + echo_substitute_hook, + echo_hook); + SetVariableHooks(pset.vars, "ECHO_HIDDEN", + bool_substitute_hook, + echo_hidden_hook); + SetVariableHooks(pset.vars, "ON_ERROR_ROLLBACK", + bool_substitute_hook, + on_error_rollback_hook); + SetVariableHooks(pset.vars, "COMP_KEYWORD_CASE", + comp_keyword_case_substitute_hook, + comp_keyword_case_hook); + SetVariableHooks(pset.vars, "HISTCONTROL", + histcontrol_substitute_hook, + histcontrol_hook); + SetVariableHooks(pset.vars, "PROMPT1", + NULL, + prompt1_hook); + SetVariableHooks(pset.vars, "PROMPT2", + NULL, + prompt2_hook); + SetVariableHooks(pset.vars, "PROMPT3", + NULL, + prompt3_hook); + SetVariableHooks(pset.vars, "VERBOSITY", + verbosity_substitute_hook, + verbosity_hook); + SetVariableHooks(pset.vars, "SHOW_CONTEXT", + show_context_substitute_hook, + show_context_hook); } diff --git a/src/bin/psql/variables.c b/src/bin/psql/variables.c index 91e4ae8095302237e7ba5133bc6391b07bd61005..b9b8fcb41db1ed07576d101ab73f01b0579481e7 100644 --- a/src/bin/psql/variables.c +++ b/src/bin/psql/variables.c @@ -52,6 +52,7 @@ CreateVariableSpace(void) ptr = pg_malloc(sizeof *ptr); ptr->name = NULL; ptr->value = NULL; + ptr->substitute_hook = NULL; ptr->assign_hook = NULL; ptr->next = NULL; @@ -101,11 +102,9 @@ ParseVariableBool(const char *value, const char *name, bool *result) size_t len; bool valid = true; + /* Treat "unset" as an empty string, which will lead to error below */ if (value == NULL) - { - *result = false; /* not set -> assume "off" */ - return valid; - } + value = ""; len = strlen(value); @@ -152,8 +151,10 @@ ParseVariableNum(const char *value, const char *name, int *result) char *end; long numval; + /* Treat "unset" as an empty string, which will lead to error below */ if (value == NULL) - return false; + value = ""; + errno = 0; numval = strtol(value, &end, 0); if (errno == 0 && *end == '\0' && end != value && numval == (int) numval) @@ -235,13 +236,13 @@ SetVariable(VariableSpace space, const char *name, const char *value) if (!valid_variable_name(name)) { + /* Deletion of non-existent variable is not an error */ + if (!value) + return true; psql_error("invalid variable name: \"%s\"\n", name); return false; } - if (!value) - return DeleteVariable(space, name); - for (previous = space, current = space->next; current; previous = current, current = current->next) @@ -249,14 +250,20 @@ SetVariable(VariableSpace space, const char *name, const char *value) if (strcmp(current->name, name) == 0) { /* - * Found entry, so update, unless hook returns false. The hook - * may need the passed value to have the same lifespan as the - * variable, so allocate it right away, even though we'll have to - * free it again if the hook returns false. + * Found entry, so update, unless assign hook returns false. + * + * We must duplicate the passed value to start with. This + * simplifies the API for substitute hooks. Moreover, some assign + * hooks assume that the passed value has the same lifespan as the + * variable. Having to free the string again on failure is a + * small price to pay for keeping these APIs simple. */ - char *new_value = pg_strdup(value); + char *new_value = value ? pg_strdup(value) : NULL; bool confirmed; + if (current->substitute_hook) + new_value = (*current->substitute_hook) (new_value); + if (current->assign_hook) confirmed = (*current->assign_hook) (new_value); else @@ -267,39 +274,61 @@ SetVariable(VariableSpace space, const char *name, const char *value) if (current->value) pg_free(current->value); current->value = new_value; + + /* + * If we deleted the value, and there are no hooks to + * remember, we can discard the variable altogether. + */ + if (new_value == NULL && + current->substitute_hook == NULL && + current->assign_hook == NULL) + { + previous->next = current->next; + free(current->name); + free(current); + } } - else + else if (new_value) pg_free(new_value); /* current->value is left unchanged */ return confirmed; } } - /* not present, make new entry */ - current = pg_malloc(sizeof *current); - current->name = pg_strdup(name); - current->value = pg_strdup(value); - current->assign_hook = NULL; - current->next = NULL; - previous->next = current; + /* not present, make new entry ... unless we were asked to delete */ + if (value) + { + current = pg_malloc(sizeof *current); + current->name = pg_strdup(name); + current->value = pg_strdup(value); + current->substitute_hook = NULL; + current->assign_hook = NULL; + current->next = NULL; + previous->next = current; + } return true; } /* - * Attach an assign hook function to the named variable. + * Attach substitute and/or assign hook functions to the named variable. + * If you need only one hook, pass NULL for the other. * - * If the variable doesn't already exist, create it with value NULL, - * just so we have a place to store the hook function. (Externally, - * this isn't different from it not being defined.) + * If the variable doesn't already exist, create it with value NULL, just so + * we have a place to store the hook function(s). (The substitute hook might + * immediately change the NULL to something else; if not, this state is + * externally the same as the variable not being defined.) * - * The hook is immediately called on the variable's current value. This is - * meant to let it update any derived psql state. If the hook doesn't like - * the current value, it will print a message to that effect, but we'll ignore - * it. Generally we do not expect any such failure here, because this should - * get called before any user-supplied value is assigned. + * The substitute hook, if given, is immediately called on the variable's + * value. Then the assign hook, if given, is called on the variable's value. + * This is meant to let it update any derived psql state. If the assign hook + * doesn't like the current value, it will print a message to that effect, + * but we'll ignore it. Generally we do not expect any such failure here, + * because this should get called before any user-supplied value is assigned. */ void -SetVariableAssignHook(VariableSpace space, const char *name, VariableAssignHook hook) +SetVariableHooks(VariableSpace space, const char *name, + VariableSubstituteHook shook, + VariableAssignHook ahook) { struct _variable *current, *previous; @@ -317,8 +346,12 @@ SetVariableAssignHook(VariableSpace space, const char *name, VariableAssignHook if (strcmp(current->name, name) == 0) { /* found entry, so update */ - current->assign_hook = hook; - (void) (*hook) (current->value); + current->substitute_hook = shook; + current->assign_hook = ahook; + if (shook) + current->value = (*shook) (current->value); + if (ahook) + (void) (*ahook) (current->value); return; } } @@ -327,10 +360,14 @@ SetVariableAssignHook(VariableSpace space, const char *name, VariableAssignHook current = pg_malloc(sizeof *current); current->name = pg_strdup(name); current->value = NULL; - current->assign_hook = hook; + current->substitute_hook = shook; + current->assign_hook = ahook; current->next = NULL; previous->next = current; - (void) (*hook) (NULL); + if (shook) + current->value = (*shook) (current->value); + if (ahook) + (void) (*ahook) (current->value); } /* @@ -351,42 +388,7 @@ SetVariableBool(VariableSpace space, const char *name) bool DeleteVariable(VariableSpace space, const char *name) { - struct _variable *current, - *previous; - - if (!space) - return true; - - for (previous = space, current = space->next; - current; - previous = current, current = current->next) - { - if (strcmp(current->name, name) == 0) - { - if (current->assign_hook) - { - /* Allow deletion only if hook is okay with NULL value */ - if (!(*current->assign_hook) (NULL)) - return false; /* message printed by hook */ - if (current->value) - free(current->value); - current->value = NULL; - /* Don't delete entry, or we'd forget the hook function */ - } - else - { - /* We can delete the entry as well as its value */ - if (current->value) - free(current->value); - previous->next = current->next; - free(current->name); - free(current); - } - return true; - } - } - - return true; + return SetVariable(space, name, NULL); } /* diff --git a/src/bin/psql/variables.h b/src/bin/psql/variables.h index 274b4af5537ab27ca6d626a35688d4972b09e961..84be7805098b996ac7eaa27b720099b39a2fc9d3 100644 --- a/src/bin/psql/variables.h +++ b/src/bin/psql/variables.h @@ -18,28 +18,52 @@ * prevent invalid values from being assigned, and can update internal C * variables to keep them in sync with the variable's current value. * - * A hook function is called before any attempted assignment, with the + * An assign hook function is called before any attempted assignment, with the * proposed new value of the variable (or with NULL, if an \unset is being * attempted). If it returns false, the assignment doesn't occur --- it * should print an error message with psql_error() to tell the user why. * - * When a hook function is installed with SetVariableAssignHook(), it is + * When an assign hook function is installed with SetVariableHooks(), it is * called with the variable's current value (or with NULL, if it wasn't set * yet). But its return value is ignored in this case. The hook should be * set before any possibly-invalid value can be assigned. */ typedef bool (*VariableAssignHook) (const char *newval); +/* + * Variables can also be given "substitute hook" functions. The substitute + * hook can replace values (including NULL) with other values, allowing + * normalization of variable contents. For example, for a boolean variable, + * we wish to interpret "\unset FOO" as "\set FOO off", and we can do that + * by installing a substitute hook. (We can use the same substitute hook + * for all bool or nearly-bool variables, which is why this responsibility + * isn't part of the assign hook.) + * + * The substitute hook is called before any attempted assignment, and before + * the assign hook if any, passing the proposed new value of the variable as a + * malloc'd string (or NULL, if an \unset is being attempted). It can return + * the same value, or a different malloc'd string, or modify the string + * in-place. It should free the passed-in value if it's not returning it. + * The substitute hook generally should not complain about erroneous values; + * that's a job for the assign hook. + * + * When a substitute hook is installed with SetVariableHooks(), it is applied + * to the variable's current value (typically NULL, if it wasn't set yet). + * That also happens before applying the assign hook. + */ +typedef char *(*VariableSubstituteHook) (char *newval); + /* * Data structure representing one variable. * * 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. + * keeping the struct around so as not to forget about its hook function(s). */ struct _variable { char *name; char *value; + VariableSubstituteHook substitute_hook; VariableAssignHook assign_hook; struct _variable *next; }; @@ -65,10 +89,13 @@ int GetVariableNum(VariableSpace space, void PrintVariables(VariableSpace space); bool SetVariable(VariableSpace space, const char *name, const char *value); -void SetVariableAssignHook(VariableSpace space, const char *name, VariableAssignHook hook); bool SetVariableBool(VariableSpace space, const char *name); bool DeleteVariable(VariableSpace space, const char *name); +void SetVariableHooks(VariableSpace space, const char *name, + VariableSubstituteHook shook, + VariableAssignHook ahook); + void PsqlVarEnumError(const char *name, const char *value, const char *suggestions); #endif /* VARIABLES_H */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index 420825aa56d9dc9c2bd25203dc33150f008ffc25..026a4f0c83351ff2e397d0ce6c0b2feb8b6b2321 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -11,6 +11,23 @@ invalid variable name: "invalid/name" unrecognized value "foo" for "AUTOCOMMIT": boolean expected \set FETCH_COUNT foo invalid value "foo" for "FETCH_COUNT": integer expected +-- check handling of built-in boolean variable +\echo :ON_ERROR_ROLLBACK +off +\set ON_ERROR_ROLLBACK +\echo :ON_ERROR_ROLLBACK +on +\set ON_ERROR_ROLLBACK foo +unrecognized value "foo" for "ON_ERROR_ROLLBACK" +Available values are: on, off, interactive. +\echo :ON_ERROR_ROLLBACK +on +\set ON_ERROR_ROLLBACK on +\echo :ON_ERROR_ROLLBACK +on +\unset ON_ERROR_ROLLBACK +\echo :ON_ERROR_ROLLBACK +off -- \gset select 10 as test01, 20 as test02, 'Hello' as test03 \gset pref01_ \echo :pref01_test01 :pref01_test02 :pref01_test03 diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index 79624b9193adeea9a48f1308c5efa9668794f482..d823d11b958b96a60195ceedcd06aba1f3f8711b 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -10,6 +10,16 @@ -- fail: invalid value for special variable \set AUTOCOMMIT foo \set FETCH_COUNT foo +-- check handling of built-in boolean variable +\echo :ON_ERROR_ROLLBACK +\set ON_ERROR_ROLLBACK +\echo :ON_ERROR_ROLLBACK +\set ON_ERROR_ROLLBACK foo +\echo :ON_ERROR_ROLLBACK +\set ON_ERROR_ROLLBACK on +\echo :ON_ERROR_ROLLBACK +\unset ON_ERROR_ROLLBACK +\echo :ON_ERROR_ROLLBACK -- \gset