diff options
Diffstat (limited to 'src/bin/psql/variables.c')
-rw-r--r-- | src/bin/psql/variables.c | 422 |
1 files changed, 422 insertions, 0 deletions
diff --git a/src/bin/psql/variables.c b/src/bin/psql/variables.c new file mode 100644 index 0000000..92a34f8 --- /dev/null +++ b/src/bin/psql/variables.c @@ -0,0 +1,422 @@ +/* + * psql - the PostgreSQL interactive terminal + * + * Copyright (c) 2000-2021, PostgreSQL Global Development Group + * + * src/bin/psql/variables.c + */ +#include "postgres_fe.h" + +#include "common.h" +#include "common/logging.h" +#include "variables.h" + +/* + * Check whether a variable's name is allowed. + * + * We allow any non-ASCII character, as well as ASCII letters, digits, and + * underscore. Keep this in sync with the definition of variable_char in + * psqlscan.l and psqlscanslash.l. + */ +static bool +valid_variable_name(const char *name) +{ + const unsigned char *ptr = (const unsigned char *) name; + + /* Mustn't be zero-length */ + if (*ptr == '\0') + return false; + + while (*ptr) + { + if (IS_HIGHBIT_SET(*ptr) || + strchr("ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" + "_0123456789", *ptr) != NULL) + ptr++; + else + return false; + } + + return true; +} + +/* + * A "variable space" is represented by an otherwise-unused struct _variable + * that serves as list header. + * + * The list entries are kept in name order (according to strcmp). This + * is mainly to make the results of PrintVariables() more pleasing. + */ +VariableSpace +CreateVariableSpace(void) +{ + struct _variable *ptr; + + ptr = pg_malloc(sizeof *ptr); + ptr->name = NULL; + ptr->value = NULL; + ptr->substitute_hook = NULL; + ptr->assign_hook = NULL; + ptr->next = NULL; + + return ptr; +} + +/* + * Get string value of variable, or NULL if it's not defined. + * + * Note: result is valid until variable is next assigned to. + */ +const char * +GetVariable(VariableSpace space, const char *name) +{ + struct _variable *current; + + if (!space) + return NULL; + + for (current = space->next; current; current = current->next) + { + int cmp = strcmp(current->name, name); + + if (cmp == 0) + { + /* this is correct answer when value is NULL, too */ + return current->value; + } + if (cmp > 0) + break; /* it's not there */ + } + + return NULL; +} + +/* + * Try to interpret "value" as a boolean value, and if successful, + * store it in *result. Otherwise don't clobber *result. + * + * Valid values are: true, false, yes, no, on, off, 1, 0; as well as unique + * prefixes thereof. + * + * "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. + * + * Return true when "value" is syntactically valid, false otherwise. + */ +bool +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) + value = ""; + + len = strlen(value); + + if (len > 0 && pg_strncasecmp(value, "true", len) == 0) + *result = true; + else if (len > 0 && pg_strncasecmp(value, "false", len) == 0) + *result = false; + else if (len > 0 && pg_strncasecmp(value, "yes", len) == 0) + *result = true; + else if (len > 0 && pg_strncasecmp(value, "no", len) == 0) + *result = false; + /* 'o' is not unique enough */ + else if (pg_strncasecmp(value, "on", (len > 2 ? len : 2)) == 0) + *result = true; + else if (pg_strncasecmp(value, "off", (len > 2 ? len : 2)) == 0) + *result = false; + else if (pg_strcasecmp(value, "1") == 0) + *result = true; + else if (pg_strcasecmp(value, "0") == 0) + *result = false; + else + { + /* string is not recognized; don't clobber *result */ + if (name) + pg_log_error("unrecognized value \"%s\" for \"%s\": Boolean expected", + value, name); + valid = false; + } + return valid; +} + +/* + * Try to interpret "value" as an integer value, and if successful, + * store it in *result. Otherwise don't clobber *result. + * + * "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. + * + * Return true when "value" is syntactically valid, false otherwise. + */ +bool +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) + value = ""; + + errno = 0; + numval = strtol(value, &end, 0); + if (errno == 0 && *end == '\0' && end != value && numval == (int) numval) + { + *result = (int) numval; + return true; + } + else + { + /* string is not recognized; don't clobber *result */ + if (name) + pg_log_error("invalid value \"%s\" for \"%s\": integer expected", + value, name); + return false; + } +} + +/* + * Print values of all variables. + */ +void +PrintVariables(VariableSpace space) +{ + struct _variable *ptr; + + if (!space) + return; + + for (ptr = space->next; ptr; ptr = ptr->next) + { + if (ptr->value) + printf("%s = '%s'\n", ptr->name, ptr->value); + if (cancel_pressed) + break; + } +} + +/* + * Set the variable named "name" to value "value", + * or delete it if "value" is NULL. + * + * Returns true if successful, false if not; in the latter case a suitable + * error message has been printed, except for the unexpected case of + * space or name being NULL. + */ +bool +SetVariable(VariableSpace space, const char *name, const char *value) +{ + struct _variable *current, + *previous; + + if (!space || !name) + return false; + + if (!valid_variable_name(name)) + { + /* Deletion of non-existent variable is not an error */ + if (!value) + return true; + pg_log_error("invalid variable name: \"%s\"", name); + return false; + } + + for (previous = space, current = space->next; + current; + previous = current, current = current->next) + { + int cmp = strcmp(current->name, name); + + if (cmp == 0) + { + /* + * 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 = 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 + confirmed = true; + + if (confirmed) + { + 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 if (new_value) + pg_free(new_value); /* current->value is left unchanged */ + + return confirmed; + } + if (cmp > 0) + break; /* it's not there */ + } + + /* 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 = previous->next; + previous->next = current; + } + return true; +} + +/* + * 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(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 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 +SetVariableHooks(VariableSpace space, const char *name, + VariableSubstituteHook shook, + VariableAssignHook ahook) +{ + struct _variable *current, + *previous; + + if (!space || !name) + return; + + if (!valid_variable_name(name)) + return; + + for (previous = space, current = space->next; + current; + previous = current, current = current->next) + { + int cmp = strcmp(current->name, name); + + if (cmp == 0) + { + /* found entry, so update */ + current->substitute_hook = shook; + current->assign_hook = ahook; + if (shook) + current->value = (*shook) (current->value); + if (ahook) + (void) (*ahook) (current->value); + return; + } + if (cmp > 0) + break; /* it's not there */ + } + + /* not present, make new entry */ + current = pg_malloc(sizeof *current); + current->name = pg_strdup(name); + current->value = NULL; + current->substitute_hook = shook; + current->assign_hook = ahook; + current->next = previous->next; + previous->next = current; + if (shook) + current->value = (*shook) (current->value); + if (ahook) + (void) (*ahook) (current->value); +} + +/* + * Return true iff the named variable has substitute and/or assign hook + * functions. + */ +bool +VariableHasHook(VariableSpace space, const char *name) +{ + struct _variable *current; + + Assert(space); + Assert(name); + + for (current = space->next; current; current = current->next) + { + int cmp = strcmp(current->name, name); + + if (cmp == 0) + return (current->substitute_hook != NULL || + current->assign_hook != NULL); + if (cmp > 0) + break; /* it's not there */ + } + + return false; +} + +/* + * Convenience function to set a variable's value to "on". + */ +bool +SetVariableBool(VariableSpace space, const char *name) +{ + return SetVariable(space, name, "on"); +} + +/* + * Attempt to delete variable. + * + * If unsuccessful, print a message and return "false". + * Deleting a nonexistent variable is not an error. + */ +bool +DeleteVariable(VariableSpace space, const char *name) +{ + return SetVariable(space, name, NULL); +} + +/* + * Emit error with suggestions for variables or commands + * accepting enum-style arguments. + * This function just exists to standardize the wording. + * suggestions should follow the format "fee, fi, fo, fum". + */ +void +PsqlVarEnumError(const char *name, const char *value, const char *suggestions) +{ + pg_log_error("unrecognized value \"%s\" for \"%s\"\n" + "Available values are: %s.", + value, name, suggestions); +} |