diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/bin/psql/startup.c | 170 | ||||
-rw-r--r-- | src/bin/psql/variables.c | 144 | ||||
-rw-r--r-- | src/bin/psql/variables.h | 35 | ||||
-rw-r--r-- | src/test/regress/expected/psql.out | 17 | ||||
-rw-r--r-- | src/test/regress/sql/psql.sql | 10 |
5 files changed, 254 insertions, 122 deletions
diff --git a/src/bin/psql/startup.c b/src/bin/psql/startup.c index 0574b5bdfb1..a3654e62722 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 91e4ae80953..b9b8fcb41db 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 274b4af5537..84be7805098 100644 --- a/src/bin/psql/variables.h +++ b/src/bin/psql/variables.h @@ -18,12 +18,12 @@ * 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. @@ -31,15 +31,39 @@ 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 420825aa56d..026a4f0c833 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 79624b9193a..d823d11b958 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 |