
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
306 lines
6.1 KiB
C
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;
|
|
}
|