/*
* psql - the PostgreSQL interactive terminal
*
* Copyright ( c ) 2000 - 2025 , PostgreSQL Global Development Group
*
* src / bin / psql / variables . c
*/
#include "postgres_fe.h"
#include <math.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 output 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 ;
}
}
/*
* Try to interpret " value " as a double value , and if successful store it in
* * result . If unsuccessful , * result isn ' t clobbered . " name " is the variable
* which is being assigned , the value of which is only used to produce a good
* error message . Pass NULL as the name to suppress the error message . The
* value must be within the range [ min , max ] in order to be considered valid .
*
* Returns true , with * result containing the interpreted value , if " value " is
* syntactically valid , else false ( with * result unchanged ) .
*/
bool
ParseVariableDouble(const char *value, const char *name, double *result, double min, double max)
{
char *end;
double dblval;
/*
* Empty - string input has historically been treated differently by strtod
* on various platforms , so handle that by specifically checking for it .
*/
if ((value == NULL) || (*value == '\0' ))
{
if (name)
pg_log_error("invalid input syntax for variable \" %s\"" , name);
return false ;
}
errno = 0 ;
dblval = strtod(value, &end);
if (errno == 0 && *end == '\0' && end != value)
{
if (dblval < min)
{
if (name)
pg_log_error("invalid value \" %s\" for variable \" %s\": must be greater than %.2f" ,
value, name, min);
return false ;
}
else if (dblval > max)
{
if (name)
pg_log_error("invalid value \" %s\" for variable \" %s\": must be less than %.2f" ,
value, name, max);
}
*result = dblval;
return true ;
}
/*
* Cater for platforms which treat values which aren ' t zero , but that are
* too close to zero to have full precision , by checking for zero or real
* out - of - range values .
*/
else if ((errno == ERANGE) &&
(dblval == 0 .0 || dblval >= HUGE_VAL || dblval <= -HUGE_VAL))
{
if (name)
pg_log_error("value \" %s\" is out of range for variable \" %s\"" , value, name);
return false ;
}
else
{
if (name)
pg_log_error("invalid value \" %s\" for variable \" %s\"" , 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)
{
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
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);
}
Messung V0.5 in Prozent C=91 H=95 G=92
¤ Dauer der Verarbeitung: 0.15 Sekunden
(vorverarbeitet am 2026-08-08)
¤
*© Formatika GbR, Deutschland