postgresql/src/bin/psql/variables.c

397 lines
9.7 KiB
C

/*
* psql - the PostgreSQL interactive terminal
*
* Copyright (c) 2000-2019, 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);
}
/*
* 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);
}