postgres/src/bin/psql/variables.c
Tom Lane 0ea9efbe9e Split psql's lexer into two separate .l files for SQL and backslash cases.
This gets us to a point where psqlscan.l can be used by other frontend
programs for the same purpose psql uses it for, ie to detect when it's
collected a complete SQL command from input that is divided across
line boundaries.  Moreover, other programs can supply their own lexers
for backslash commands of their own choosing.  A follow-on patch will
use this in pgbench.

The end result here is roughly the same as in Kyotaro Horiguchi's
0001-Make-SQL-parser-part-of-psqlscan-independent-from-ps.patch, although
the details of the method for switching between lexers are quite different.
Basically, in this patch we share the entire PsqlScanState, YY_BUFFER_STATE
stack, *and* yyscan_t between different lexers.  The only thing we need
to do to switch to a different lexer is to make sure the start_state is
valid for the new lexer.  This works because flex doesn't keep any other
persistent state that depends on the specific lexing tables generated for
a particular .l file.  (We are assuming that both lexers are built with
the same flex version, or at least versions that are compatible with
respect to the contents of yyscan_t; but that doesn't seem likely to
be a big problem in practice, considering how slowly flex changes.)

Aside from being more efficient than Horiguchi-san's original solution,
this avoids possible corner-case changes in semantics: the original code
was capable of popping the input buffer stack while still staying in
backslash-related parsing states.  I'm not sure that that equates to any
useful user-visible behaviors, but I'm not sure it doesn't either, so
I'm loath to assume that we only need to consider the topmost buffer when
parsing a backslash command.

I've attempted to update the MSVC build scripts for the added .l file,
but will rely on the buildfarm to see if I missed anything.

Kyotaro Horiguchi and Tom Lane
2016-03-19 00:24:55 -04:00

306 lines
6.1 KiB
C

/*
* psql - the PostgreSQL interactive terminal
*
* Copyright (c) 2000-2016, PostgreSQL Global Development Group
*
* src/bin/psql/variables.c
*/
#include "postgres_fe.h"
#include "common.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.
*/
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;
}
}
/*
* 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 (!valid_variable_name(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 (!valid_variable_name(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;
}