LCOV - code coverage report
Current view: top level - src/bin/psql - variables.c (source / functions) Coverage Total Hit
Test: PostgreSQL 19devel Lines: 80.5 % 185 149
Test Date: 2026-02-17 17:20:33 Functions: 84.6 % 13 11
Legend: Lines:     hit not hit

            Line data    Source code
       1              : /*
       2              :  * psql - the PostgreSQL interactive terminal
       3              :  *
       4              :  * Copyright (c) 2000-2026, PostgreSQL Global Development Group
       5              :  *
       6              :  * src/bin/psql/variables.c
       7              :  */
       8              : #include "postgres_fe.h"
       9              : 
      10              : #include <math.h>
      11              : 
      12              : #include "common.h"
      13              : #include "common/logging.h"
      14              : #include "variables.h"
      15              : 
      16              : /*
      17              :  * Check whether a variable's name is allowed.
      18              :  *
      19              :  * We allow any non-ASCII character, as well as ASCII letters, digits, and
      20              :  * underscore.  Keep this in sync with the definition of variable_char in
      21              :  * psqlscan.l and psqlscanslash.l.
      22              :  */
      23              : static bool
      24      1743622 : valid_variable_name(const char *name)
      25              : {
      26      1743622 :     const unsigned char *ptr = (const unsigned char *) name;
      27              : 
      28              :     /* Mustn't be zero-length */
      29      1743622 :     if (*ptr == '\0')
      30            0 :         return false;
      31              : 
      32     24397772 :     while (*ptr)
      33              :     {
      34     22654156 :         if (IS_HIGHBIT_SET(*ptr) ||
      35     22654156 :             strchr("ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz"
      36     22654156 :                    "_0123456789", *ptr) != NULL)
      37     22654150 :             ptr++;
      38              :         else
      39            6 :             return false;
      40              :     }
      41              : 
      42      1743616 :     return true;
      43              : }
      44              : 
      45              : /*
      46              :  * A "variable space" is represented by an otherwise-unused struct _variable
      47              :  * that serves as list header.
      48              :  *
      49              :  * The list entries are kept in name order (according to strcmp).  This
      50              :  * is mainly to make the output of PrintVariables() more pleasing.
      51              :  */
      52              : VariableSpace
      53         9829 : CreateVariableSpace(void)
      54              : {
      55              :     struct _variable *ptr;
      56              : 
      57         9829 :     ptr = pg_malloc(sizeof *ptr);
      58         9829 :     ptr->name = NULL;
      59         9829 :     ptr->value = NULL;
      60         9829 :     ptr->substitute_hook = NULL;
      61         9829 :     ptr->assign_hook = NULL;
      62         9829 :     ptr->next = NULL;
      63              : 
      64         9829 :     return ptr;
      65              : }
      66              : 
      67              : /*
      68              :  * Get string value of variable, or NULL if it's not defined.
      69              :  *
      70              :  * Note: result is valid until variable is next assigned to.
      71              :  */
      72              : const char *
      73         2023 : GetVariable(VariableSpace space, const char *name)
      74              : {
      75              :     struct _variable *current;
      76              : 
      77         2023 :     if (!space)
      78            0 :         return NULL;
      79              : 
      80        77694 :     for (current = space->next; current; current = current->next)
      81              :     {
      82        77693 :         int         cmp = strcmp(current->name, name);
      83              : 
      84        77693 :         if (cmp == 0)
      85              :         {
      86              :             /* this is correct answer when value is NULL, too */
      87         1773 :             return current->value;
      88              :         }
      89        75920 :         if (cmp > 0)
      90          249 :             break;              /* it's not there */
      91              :     }
      92              : 
      93          250 :     return NULL;
      94              : }
      95              : 
      96              : /*
      97              :  * Try to interpret "value" as a boolean value, and if successful,
      98              :  * store it in *result.  Otherwise don't clobber *result.
      99              :  *
     100              :  * Valid values are: true, false, yes, no, on, off, 1, 0; as well as unique
     101              :  * prefixes thereof.
     102              :  *
     103              :  * "name" is the name of the variable we're assigning to, to use in error
     104              :  * report if any.  Pass name == NULL to suppress the error report.
     105              :  *
     106              :  * Return true when "value" is syntactically valid, false otherwise.
     107              :  */
     108              : bool
     109       135568 : ParseVariableBool(const char *value, const char *name, bool *result)
     110              : {
     111              :     size_t      len;
     112       135568 :     bool        valid = true;
     113              : 
     114              :     /* Treat "unset" as an empty string, which will lead to error below */
     115       135568 :     if (value == NULL)
     116            0 :         value = "";
     117              : 
     118       135568 :     len = strlen(value);
     119              : 
     120       135568 :     if (len > 0 && pg_strncasecmp(value, "true", len) == 0)
     121          118 :         *result = true;
     122       135450 :     else if (len > 0 && pg_strncasecmp(value, "false", len) == 0)
     123          145 :         *result = false;
     124       135305 :     else if (len > 0 && pg_strncasecmp(value, "yes", len) == 0)
     125            3 :         *result = true;
     126       135302 :     else if (len > 0 && pg_strncasecmp(value, "no", len) == 0)
     127            3 :         *result = false;
     128              :     /* 'o' is not unique enough */
     129       135299 :     else if (pg_strncasecmp(value, "on", (len > 2 ? len : 2)) == 0)
     130        30198 :         *result = true;
     131       105101 :     else if (pg_strncasecmp(value, "off", (len > 2 ? len : 2)) == 0)
     132        98394 :         *result = false;
     133         6707 :     else if (pg_strcasecmp(value, "1") == 0)
     134         6694 :         *result = true;
     135           13 :     else if (pg_strcasecmp(value, "0") == 0)
     136            4 :         *result = false;
     137              :     else
     138              :     {
     139              :         /* string is not recognized; don't clobber *result */
     140            9 :         if (name)
     141            6 :             pg_log_error("unrecognized value \"%s\" for \"%s\": Boolean expected",
     142              :                          value, name);
     143            9 :         valid = false;
     144              :     }
     145       135568 :     return valid;
     146              : }
     147              : 
     148              : /*
     149              :  * Try to interpret "value" as an integer value, and if successful,
     150              :  * store it in *result.  Otherwise don't clobber *result.
     151              :  *
     152              :  * "name" is the name of the variable we're assigning to, to use in error
     153              :  * report if any.  Pass name == NULL to suppress the error report.
     154              :  *
     155              :  * Return true when "value" is syntactically valid, false otherwise.
     156              :  */
     157              : bool
     158        29527 : ParseVariableNum(const char *value, const char *name, int *result)
     159              : {
     160              :     char       *end;
     161              :     long        numval;
     162              : 
     163              :     /* Treat "unset" as an empty string, which will lead to error below */
     164        29527 :     if (value == NULL)
     165            0 :         value = "";
     166              : 
     167        29527 :     errno = 0;
     168        29527 :     numval = strtol(value, &end, 0);
     169        29527 :     if (errno == 0 && *end == '\0' && end != value && numval == (int) numval)
     170              :     {
     171        29524 :         *result = (int) numval;
     172        29524 :         return true;
     173              :     }
     174              :     else
     175              :     {
     176              :         /* string is not recognized; don't clobber *result */
     177            3 :         if (name)
     178            3 :             pg_log_error("invalid value \"%s\" for \"%s\": integer expected",
     179              :                          value, name);
     180            3 :         return false;
     181              :     }
     182              : }
     183              : 
     184              : /*
     185              :  * Try to interpret "value" as a double value, and if successful store it in
     186              :  * *result. If unsuccessful, *result isn't clobbered. "name" is the variable
     187              :  * which is being assigned, the value of which is only used to produce a good
     188              :  * error message. Pass NULL as the name to suppress the error message.  The
     189              :  * value must be within the range [min,max] in order to be considered valid.
     190              :  *
     191              :  * Returns true, with *result containing the interpreted value, if "value" is
     192              :  * syntactically valid, else false (with *result unchanged).
     193              :  */
     194              : bool
     195         9833 : ParseVariableDouble(const char *value, const char *name, double *result, double min, double max)
     196              : {
     197              :     char       *end;
     198              :     double      dblval;
     199              : 
     200              :     /*
     201              :      * Empty-string input has historically been treated differently by strtod
     202              :      * on various platforms, so handle that by specifically checking for it.
     203              :      */
     204         9833 :     if ((value == NULL) || (*value == '\0'))
     205              :     {
     206            0 :         if (name)
     207            0 :             pg_log_error("invalid input syntax for variable \"%s\"", name);
     208            0 :         return false;
     209              :     }
     210              : 
     211         9833 :     errno = 0;
     212         9833 :     dblval = strtod(value, &end);
     213         9833 :     if (errno == 0 && *end == '\0' && end != value)
     214              :     {
     215         9832 :         if (dblval < min)
     216              :         {
     217            0 :             if (name)
     218            0 :                 pg_log_error("invalid value \"%s\" for variable \"%s\": must be greater than %.2f",
     219              :                              value, name, min);
     220            0 :             return false;
     221              :         }
     222         9832 :         else if (dblval > max)
     223              :         {
     224            0 :             if (name)
     225            0 :                 pg_log_error("invalid value \"%s\" for variable \"%s\": must be less than %.2f",
     226              :                              value, name, max);
     227              :         }
     228         9832 :         *result = dblval;
     229         9832 :         return true;
     230              :     }
     231              : 
     232              :     /*
     233              :      * Cater for platforms which treat values which aren't zero, but that are
     234              :      * too close to zero to have full precision, by checking for zero or real
     235              :      * out-of-range values.
     236              :      */
     237            1 :     else if ((errno == ERANGE) &&
     238            1 :              (dblval == 0.0 || dblval >= HUGE_VAL || dblval <= -HUGE_VAL))
     239              :     {
     240            1 :         if (name)
     241            1 :             pg_log_error("value \"%s\" is out of range for variable \"%s\"", value, name);
     242            1 :         return false;
     243              :     }
     244              :     else
     245              :     {
     246            0 :         if (name)
     247            0 :             pg_log_error("invalid value \"%s\" for variable \"%s\"", value, name);
     248            0 :         return false;
     249              :     }
     250              : }
     251              : 
     252              : /*
     253              :  * Print values of all variables.
     254              :  */
     255              : void
     256            0 : PrintVariables(VariableSpace space)
     257              : {
     258              :     struct _variable *ptr;
     259              : 
     260            0 :     if (!space)
     261            0 :         return;
     262              : 
     263            0 :     for (ptr = space->next; ptr; ptr = ptr->next)
     264              :     {
     265            0 :         if (ptr->value)
     266            0 :             printf("%s = '%s'\n", ptr->name, ptr->value);
     267            0 :         if (cancel_pressed)
     268            0 :             break;
     269              :     }
     270              : }
     271              : 
     272              : /*
     273              :  * Set the variable named "name" to value "value",
     274              :  * or delete it if "value" is NULL.
     275              :  *
     276              :  * Returns true if successful, false if not; in the latter case a suitable
     277              :  * error message has been printed, except for the unexpected case of
     278              :  * space or name being NULL.
     279              :  */
     280              : bool
     281      1517555 : SetVariable(VariableSpace space, const char *name, const char *value)
     282              : {
     283              :     struct _variable *current,
     284              :                *previous;
     285              : 
     286      1517555 :     if (!space || !name)
     287            0 :         return false;
     288              : 
     289      1517555 :     if (!valid_variable_name(name))
     290              :     {
     291              :         /* Deletion of non-existent variable is not an error */
     292            6 :         if (!value)
     293            0 :             return true;
     294            6 :         pg_log_error("invalid variable name: \"%s\"", name);
     295            6 :         return false;
     296              :     }
     297              : 
     298      1517549 :     for (previous = space, current = space->next;
     299     32742977 :          current;
     300     31225428 :          previous = current, current = current->next)
     301              :     {
     302     32742619 :         int         cmp = strcmp(current->name, name);
     303              : 
     304     32742619 :         if (cmp == 0)
     305              :         {
     306              :             /*
     307              :              * Found entry, so update, unless assign hook returns false.
     308              :              *
     309              :              * We must duplicate the passed value to start with.  This
     310              :              * simplifies the API for substitute hooks.  Moreover, some assign
     311              :              * hooks assume that the passed value has the same lifespan as the
     312              :              * variable.  Having to free the string again on failure is a
     313              :              * small price to pay for keeping these APIs simple.
     314              :              */
     315      1318199 :             char       *new_value = value ? pg_strdup(value) : NULL;
     316              :             bool        confirmed;
     317              : 
     318      1318199 :             if (current->substitute_hook)
     319        38111 :                 new_value = current->substitute_hook(new_value);
     320              : 
     321      1318199 :             if (current->assign_hook)
     322        67598 :                 confirmed = current->assign_hook(new_value);
     323              :             else
     324      1250601 :                 confirmed = true;
     325              : 
     326      1318199 :             if (confirmed)
     327              :             {
     328      1318189 :                 pg_free(current->value);
     329      1318189 :                 current->value = new_value;
     330              : 
     331              :                 /*
     332              :                  * If we deleted the value, and there are no hooks to
     333              :                  * remember, we can discard the variable altogether.
     334              :                  */
     335      1318189 :                 if (new_value == NULL &&
     336            3 :                     current->substitute_hook == NULL &&
     337            3 :                     current->assign_hook == NULL)
     338              :                 {
     339            3 :                     previous->next = current->next;
     340            3 :                     free(current->name);
     341            3 :                     free(current);
     342              :                 }
     343              :             }
     344              :             else
     345           10 :                 pg_free(new_value); /* current->value is left unchanged */
     346              : 
     347      1318199 :             return confirmed;
     348              :         }
     349     31424420 :         if (cmp > 0)
     350       198992 :             break;              /* it's not there */
     351              :     }
     352              : 
     353              :     /* not present, make new entry ... unless we were asked to delete */
     354       199350 :     if (value)
     355              :     {
     356       179761 :         current = pg_malloc(sizeof *current);
     357       179761 :         current->name = pg_strdup(name);
     358       179761 :         current->value = pg_strdup(value);
     359       179761 :         current->substitute_hook = NULL;
     360       179761 :         current->assign_hook = NULL;
     361       179761 :         current->next = previous->next;
     362       179761 :         previous->next = current;
     363              :     }
     364       199350 :     return true;
     365              : }
     366              : 
     367              : /*
     368              :  * Attach substitute and/or assign hook functions to the named variable.
     369              :  * If you need only one hook, pass NULL for the other.
     370              :  *
     371              :  * If the variable doesn't already exist, create it with value NULL, just so
     372              :  * we have a place to store the hook function(s).  (The substitute hook might
     373              :  * immediately change the NULL to something else; if not, this state is
     374              :  * externally the same as the variable not being defined.)
     375              :  *
     376              :  * The substitute hook, if given, is immediately called on the variable's
     377              :  * value.  Then the assign hook, if given, is called on the variable's value.
     378              :  * This is meant to let it update any derived psql state.  If the assign hook
     379              :  * doesn't like the current value, it will print a message to that effect,
     380              :  * but we'll ignore it.  Generally we do not expect any such failure here,
     381              :  * because this should get called before any user-supplied value is assigned.
     382              :  */
     383              : void
     384       226067 : SetVariableHooks(VariableSpace space, const char *name,
     385              :                  VariableSubstituteHook shook,
     386              :                  VariableAssignHook ahook)
     387              : {
     388              :     struct _variable *current,
     389              :                *previous;
     390              : 
     391       226067 :     if (!space || !name)
     392            0 :         return;
     393              : 
     394       226067 :     if (!valid_variable_name(name))
     395            0 :         return;
     396              : 
     397       226067 :     for (previous = space, current = space->next;
     398      1720075 :          current;
     399      1494008 :          previous = current, current = current->next)
     400              :     {
     401      1651272 :         int         cmp = strcmp(current->name, name);
     402              : 
     403      1651272 :         if (cmp == 0)
     404              :         {
     405              :             /* found entry, so update */
     406            0 :             current->substitute_hook = shook;
     407            0 :             current->assign_hook = ahook;
     408            0 :             if (shook)
     409            0 :                 current->value = (*shook) (current->value);
     410            0 :             if (ahook)
     411            0 :                 (void) (*ahook) (current->value);
     412            0 :             return;
     413              :         }
     414      1651272 :         if (cmp > 0)
     415       157264 :             break;              /* it's not there */
     416              :     }
     417              : 
     418              :     /* not present, make new entry */
     419       226067 :     current = pg_malloc(sizeof *current);
     420       226067 :     current->name = pg_strdup(name);
     421       226067 :     current->value = NULL;
     422       226067 :     current->substitute_hook = shook;
     423       226067 :     current->assign_hook = ahook;
     424       226067 :     current->next = previous->next;
     425       226067 :     previous->next = current;
     426       226067 :     if (shook)
     427       186751 :         current->value = (*shook) (current->value);
     428       226067 :     if (ahook)
     429       226067 :         (void) (*ahook) (current->value);
     430              : }
     431              : 
     432              : /*
     433              :  * Return true iff the named variable has substitute and/or assign hook
     434              :  * functions.
     435              :  */
     436              : bool
     437          477 : VariableHasHook(VariableSpace space, const char *name)
     438              : {
     439              :     struct _variable *current;
     440              : 
     441              :     Assert(space);
     442              :     Assert(name);
     443              : 
     444        23569 :     for (current = space->next; current; current = current->next)
     445              :     {
     446        23393 :         int         cmp = strcmp(current->name, name);
     447              : 
     448        23393 :         if (cmp == 0)
     449          143 :             return (current->substitute_hook != NULL ||
     450           70 :                     current->assign_hook != NULL);
     451        23320 :         if (cmp > 0)
     452          228 :             break;              /* it's not there */
     453              :     }
     454              : 
     455          404 :     return false;
     456              : }
     457              : 
     458              : /*
     459              :  * Convenience function to set a variable's value to "on".
     460              :  */
     461              : bool
     462        28010 : SetVariableBool(VariableSpace space, const char *name)
     463              : {
     464        28010 :     return SetVariable(space, name, "on");
     465              : }
     466              : 
     467              : /*
     468              :  * Attempt to delete variable.
     469              :  *
     470              :  * If unsuccessful, print a message and return "false".
     471              :  * Deleting a nonexistent variable is not an error.
     472              :  */
     473              : bool
     474            0 : DeleteVariable(VariableSpace space, const char *name)
     475              : {
     476            0 :     return SetVariable(space, name, NULL);
     477              : }
     478              : 
     479              : /*
     480              :  * Emit error with suggestions for variables or commands
     481              :  * accepting enum-style arguments.
     482              :  * This function just exists to standardize the wording.
     483              :  * suggestions should follow the format "fee, fi, fo, fum".
     484              :  */
     485              : void
     486            3 : PsqlVarEnumError(const char *name, const char *value, const char *suggestions)
     487              : {
     488            3 :     pg_log_error("unrecognized value \"%s\" for \"%s\"\n"
     489              :                  "Available values are: %s.",
     490              :                  value, name, suggestions);
     491            3 : }
        

Generated by: LCOV version 2.0-1