mirror of
https://github.com/postgres/postgres.git
synced 2025-06-27 23:21:58 +03:00
For simple boolean variables such as ON_ERROR_STOP, psql has for a long time recognized variant spellings of "on" and "off" (such as "1"/"0"), and it also made a point of warning you if you'd misspelled the setting. But these conveniences did not exist for other keyword-valued variables. In particular, though ECHO_HIDDEN and ON_ERROR_ROLLBACK include "on" and "off" as possible values, none of the alternative spellings for those were recognized; and to make matters worse the code would just silently assume "on" was meant for any unrecognized spelling. Several people have reported getting bitten by this, so let's fix it. In detail, this patch: * Allows all spellings recognized by ParseVariableBool() for ECHO_HIDDEN and ON_ERROR_ROLLBACK. * Reports a warning for unrecognized values for COMP_KEYWORD_CASE, ECHO, ECHO_HIDDEN, HISTCONTROL, ON_ERROR_ROLLBACK, and VERBOSITY. * Recognizes all values for all these variables case-insensitively; previously there was a mishmash of case-sensitive and case-insensitive behaviors. Back-patch to all supported branches. There is a small risk of breaking existing scripts that were accidentally failing to malfunction; but the consensus is that the chance of detecting real problems and preventing future mistakes outweighs this.
278 lines
5.6 KiB
C
278 lines
5.6 KiB
C
/*
|
|
* psql - the PostgreSQL interactive terminal
|
|
*
|
|
* Copyright (c) 2000-2011, PostgreSQL Global Development Group
|
|
*
|
|
* src/bin/psql/variables.c
|
|
*/
|
|
#include "postgres_fe.h"
|
|
#include "common.h"
|
|
#include "variables.h"
|
|
|
|
|
|
/*
|
|
* A "variable space" is represented by an otherwise-unused struct _variable
|
|
* that serves as list header.
|
|
*/
|
|
VariableSpace
|
|
CreateVariableSpace(void)
|
|
{
|
|
struct _variable *ptr;
|
|
|
|
ptr = pg_malloc(sizeof *ptr);
|
|
ptr->name = NULL;
|
|
ptr->value = NULL;
|
|
ptr->assign_hook = NULL;
|
|
ptr->next = NULL;
|
|
|
|
return ptr;
|
|
}
|
|
|
|
const char *
|
|
GetVariable(VariableSpace space, const char *name)
|
|
{
|
|
struct _variable *current;
|
|
|
|
if (!space)
|
|
return NULL;
|
|
|
|
for (current = space->next; current; current = current->next)
|
|
{
|
|
if (strcmp(current->name, name) == 0)
|
|
{
|
|
/* this is correct answer when value is NULL, too */
|
|
return current->value;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Try to interpret "value" as boolean value.
|
|
*
|
|
* 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.
|
|
*/
|
|
bool
|
|
ParseVariableBool(const char *value, const char *name)
|
|
{
|
|
size_t len;
|
|
|
|
if (value == NULL)
|
|
return false; /* not set -> assume "off" */
|
|
|
|
len = strlen(value);
|
|
|
|
if (pg_strncasecmp(value, "true", len) == 0)
|
|
return true;
|
|
else if (pg_strncasecmp(value, "false", len) == 0)
|
|
return false;
|
|
else if (pg_strncasecmp(value, "yes", len) == 0)
|
|
return true;
|
|
else if (pg_strncasecmp(value, "no", len) == 0)
|
|
return false;
|
|
/* 'o' is not unique enough */
|
|
else if (pg_strncasecmp(value, "on", (len > 2 ? len : 2)) == 0)
|
|
return true;
|
|
else if (pg_strncasecmp(value, "off", (len > 2 ? len : 2)) == 0)
|
|
return false;
|
|
else if (pg_strcasecmp(value, "1") == 0)
|
|
return true;
|
|
else if (pg_strcasecmp(value, "0") == 0)
|
|
return false;
|
|
else
|
|
{
|
|
/* NULL is treated as false, so a non-matching value is 'true' */
|
|
if (name)
|
|
psql_error("unrecognized value \"%s\" for \"%s\"; assuming \"%s\"\n",
|
|
value, name, "on");
|
|
return true;
|
|
}
|
|
/* suppress compiler warning */
|
|
return true;
|
|
}
|
|
|
|
|
|
/*
|
|
* Read numeric variable, or defaultval if it is not set, or faultval if its
|
|
* value is not a valid numeric string. If allowtrail is false, this will
|
|
* include the case where there are trailing characters after the number.
|
|
*/
|
|
int
|
|
ParseVariableNum(const char *val,
|
|
int defaultval,
|
|
int faultval,
|
|
bool allowtrail)
|
|
{
|
|
int result;
|
|
|
|
if (!val)
|
|
result = defaultval;
|
|
else if (!val[0])
|
|
result = faultval;
|
|
else
|
|
{
|
|
char *end;
|
|
|
|
result = strtol(val, &end, 0);
|
|
if (!allowtrail && *end)
|
|
result = faultval;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
int
|
|
GetVariableNum(VariableSpace space,
|
|
const char *name,
|
|
int defaultval,
|
|
int faultval,
|
|
bool allowtrail)
|
|
{
|
|
const char *val;
|
|
|
|
val = GetVariable(space, name);
|
|
return ParseVariableNum(val, defaultval, faultval, allowtrail);
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
bool
|
|
SetVariable(VariableSpace space, const char *name, const char *value)
|
|
{
|
|
struct _variable *current,
|
|
*previous;
|
|
|
|
if (!space)
|
|
return false;
|
|
|
|
if (strspn(name, VALID_VARIABLE_CHARS) != strlen(name))
|
|
return false;
|
|
|
|
if (!value)
|
|
return DeleteVariable(space, name);
|
|
|
|
for (previous = space, current = space->next;
|
|
current;
|
|
previous = current, current = current->next)
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
|
|
/* 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;
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* This both sets a hook function, and calls it on the current value (if any)
|
|
*/
|
|
bool
|
|
SetVariableAssignHook(VariableSpace space, const char *name, VariableAssignHook hook)
|
|
{
|
|
struct _variable *current,
|
|
*previous;
|
|
|
|
if (!space)
|
|
return false;
|
|
|
|
if (strspn(name, VALID_VARIABLE_CHARS) != strlen(name))
|
|
return false;
|
|
|
|
for (previous = space, current = space->next;
|
|
current;
|
|
previous = current, current = current->next)
|
|
{
|
|
if (strcmp(current->name, name) == 0)
|
|
{
|
|
/* found entry, so update */
|
|
current->assign_hook = hook;
|
|
(*hook) (current->value);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/* not present, make new entry */
|
|
current = pg_malloc(sizeof *current);
|
|
current->name = pg_strdup(name);
|
|
current->value = NULL;
|
|
current->assign_hook = hook;
|
|
current->next = NULL;
|
|
previous->next = current;
|
|
(*hook) (NULL);
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
SetVariableBool(VariableSpace space, const char *name)
|
|
{
|
|
return SetVariable(space, name, "on");
|
|
}
|
|
|
|
bool
|
|
DeleteVariable(VariableSpace space, const char *name)
|
|
{
|
|
struct _variable *current,
|
|
*previous;
|
|
|
|
if (!space)
|
|
return false;
|
|
|
|
for (previous = space, current = space->next;
|
|
current;
|
|
previous = current, current = current->next)
|
|
{
|
|
if (strcmp(current->name, name) == 0)
|
|
{
|
|
if (current->value)
|
|
free(current->value);
|
|
current->value = NULL;
|
|
/* Physically delete only if no hook function to remember */
|
|
if (current->assign_hook)
|
|
(*current->assign_hook) (NULL);
|
|
else
|
|
{
|
|
previous->next = current->next;
|
|
free(current->name);
|
|
free(current);
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|