LCOV - code coverage report
Current view: top level - src/bin/psql - variables.c (source / functions) Hit Total Coverage
Test: PostgreSQL 18devel Lines: 147 183 80.3 %
Date: 2025-04-01 14:15:22 Functions: 11 13 84.6 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*
       2             :  * psql - the PostgreSQL interactive terminal
       3             :  *
       4             :  * Copyright (c) 2000-2025, 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     3185628 : valid_variable_name(const char *name)
      25             : {
      26     3185628 :     const unsigned char *ptr = (const unsigned char *) name;
      27             : 
      28             :     /* Mustn't be zero-length */
      29     3185628 :     if (*ptr == '\0')
      30           0 :         return false;
      31             : 
      32    44511870 :     while (*ptr)
      33             :     {
      34    41326254 :         if (IS_HIGHBIT_SET(*ptr) ||
      35    41326254 :             strchr("ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz"
      36    41326254 :                    "_0123456789", *ptr) != NULL)
      37    41326242 :             ptr++;
      38             :         else
      39          12 :             return false;
      40             :     }
      41             : 
      42     3185616 :     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       19538 : CreateVariableSpace(void)
      54             : {
      55             :     struct _variable *ptr;
      56             : 
      57       19538 :     ptr = pg_malloc(sizeof *ptr);
      58       19538 :     ptr->name = NULL;
      59       19538 :     ptr->value = NULL;
      60       19538 :     ptr->substitute_hook = NULL;
      61       19538 :     ptr->assign_hook = NULL;
      62       19538 :     ptr->next = NULL;
      63             : 
      64       19538 :     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        3830 : GetVariable(VariableSpace space, const char *name)
      74             : {
      75             :     struct _variable *current;
      76             : 
      77        3830 :     if (!space)
      78           0 :         return NULL;
      79             : 
      80      147630 :     for (current = space->next; current; current = current->next)
      81             :     {
      82      147628 :         int         cmp = strcmp(current->name, name);
      83             : 
      84      147628 :         if (cmp == 0)
      85             :         {
      86             :             /* this is correct answer when value is NULL, too */
      87        3366 :             return current->value;
      88             :         }
      89      144262 :         if (cmp > 0)
      90         462 :             break;              /* it's not there */
      91             :     }
      92             : 
      93         464 :     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      265298 : ParseVariableBool(const char *value, const char *name, bool *result)
     110             : {
     111             :     size_t      len;
     112      265298 :     bool        valid = true;
     113             : 
     114             :     /* Treat "unset" as an empty string, which will lead to error below */
     115      265298 :     if (value == NULL)
     116           0 :         value = "";
     117             : 
     118      265298 :     len = strlen(value);
     119             : 
     120      265298 :     if (len > 0 && pg_strncasecmp(value, "true", len) == 0)
     121         216 :         *result = true;
     122      265082 :     else if (len > 0 && pg_strncasecmp(value, "false", len) == 0)
     123         258 :         *result = false;
     124      264824 :     else if (len > 0 && pg_strncasecmp(value, "yes", len) == 0)
     125           6 :         *result = true;
     126      264818 :     else if (len > 0 && pg_strncasecmp(value, "no", len) == 0)
     127           6 :         *result = false;
     128             :     /* 'o' is not unique enough */
     129      264812 :     else if (pg_strncasecmp(value, "on", (len > 2 ? len : 2)) == 0)
     130       57718 :         *result = true;
     131      207094 :     else if (pg_strncasecmp(value, "off", (len > 2 ? len : 2)) == 0)
     132      195586 :         *result = false;
     133       11508 :     else if (pg_strcasecmp(value, "1") == 0)
     134       11482 :         *result = true;
     135          26 :     else if (pg_strcasecmp(value, "0") == 0)
     136           8 :         *result = false;
     137             :     else
     138             :     {
     139             :         /* string is not recognized; don't clobber *result */
     140          18 :         if (name)
     141          12 :             pg_log_error("unrecognized value \"%s\" for \"%s\": Boolean expected",
     142             :                          value, name);
     143          18 :         valid = false;
     144             :     }
     145      265298 :     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       58694 : 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       58694 :     if (value == NULL)
     165           0 :         value = "";
     166             : 
     167       58694 :     errno = 0;
     168       58694 :     numval = strtol(value, &end, 0);
     169       58694 :     if (errno == 0 && *end == '\0' && end != value && numval == (int) numval)
     170             :     {
     171       58688 :         *result = (int) numval;
     172       58688 :         return true;
     173             :     }
     174             :     else
     175             :     {
     176             :         /* string is not recognized; don't clobber *result */
     177           6 :         if (name)
     178           6 :             pg_log_error("invalid value \"%s\" for \"%s\": integer expected",
     179             :                          value, name);
     180           6 :         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       19546 : 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       19546 :     if ((value == NULL) || (*value == '\0'))
     205             :     {
     206           0 :         if (name)
     207           0 :             pg_log_error("invalid input syntax for \"%s\"", name);
     208           0 :         return false;
     209             :     }
     210             : 
     211       19546 :     errno = 0;
     212       19546 :     dblval = strtod(value, &end);
     213       19546 :     if (errno == 0 && *end == '\0' && end != value)
     214             :     {
     215       19544 :         if (dblval < min)
     216             :         {
     217           0 :             if (name)
     218           0 :                 pg_log_error("invalid value \"%s\" for \"%s\": must be greater than %.2f",
     219             :                              value, name, min);
     220           0 :             return false;
     221             :         }
     222       19544 :         else if (dblval > max)
     223             :         {
     224           0 :             if (name)
     225           0 :                 pg_log_error("invalid value \"%s\" for \"%s\": must be less than %.2f",
     226             :                              value, name, max);
     227             :         }
     228       19544 :         *result = dblval;
     229       19544 :         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           2 :     else if ((errno == ERANGE) &&
     238           2 :              (dblval == 0.0 || dblval >= HUGE_VAL || dblval <= -HUGE_VAL))
     239             :     {
     240           2 :         if (name)
     241           2 :             pg_log_error("\"%s\" is out of range for \"%s\"", value, name);
     242           2 :         return false;
     243             :     }
     244             :     else
     245             :     {
     246           0 :         if (name)
     247           0 :             pg_log_error("invalid value \"%s\" for \"%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     2736254 : SetVariable(VariableSpace space, const char *name, const char *value)
     282             : {
     283             :     struct _variable *current,
     284             :                *previous;
     285             : 
     286     2736254 :     if (!space || !name)
     287           0 :         return false;
     288             : 
     289     2736254 :     if (!valid_variable_name(name))
     290             :     {
     291             :         /* Deletion of non-existent variable is not an error */
     292          12 :         if (!value)
     293           0 :             return true;
     294          12 :         pg_log_error("invalid variable name: \"%s\"", name);
     295          12 :         return false;
     296             :     }
     297             : 
     298    58720640 :     for (previous = space, current = space->next;
     299             :          current;
     300    55984398 :          previous = current, current = current->next)
     301             :     {
     302    58719990 :         int         cmp = strcmp(current->name, name);
     303             : 
     304    58719990 :         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     2359982 :             char       *new_value = value ? pg_strdup(value) : NULL;
     316             :             bool        confirmed;
     317             : 
     318     2359982 :             if (current->substitute_hook)
     319       71490 :                 new_value = current->substitute_hook(new_value);
     320             : 
     321     2359982 :             if (current->assign_hook)
     322      130104 :                 confirmed = current->assign_hook(new_value);
     323             :             else
     324     2229878 :                 confirmed = true;
     325             : 
     326     2359982 :             if (confirmed)
     327             :             {
     328     2359962 :                 pg_free(current->value);
     329     2359962 :                 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     2359962 :                 if (new_value == NULL &&
     336           6 :                     current->substitute_hook == NULL &&
     337           6 :                     current->assign_hook == NULL)
     338             :                 {
     339           6 :                     previous->next = current->next;
     340           6 :                     free(current->name);
     341           6 :                     free(current);
     342             :                 }
     343             :             }
     344             :             else
     345          20 :                 pg_free(new_value); /* current->value is left unchanged */
     346             : 
     347     2359982 :             return confirmed;
     348             :         }
     349    56360008 :         if (cmp > 0)
     350      375610 :             break;              /* it's not there */
     351             :     }
     352             : 
     353             :     /* not present, make new entry ... unless we were asked to delete */
     354      376260 :     if (value)
     355             :     {
     356      356746 :         current = pg_malloc(sizeof *current);
     357      356746 :         current->name = pg_strdup(name);
     358      356746 :         current->value = pg_strdup(value);
     359      356746 :         current->substitute_hook = NULL;
     360      356746 :         current->assign_hook = NULL;
     361      356746 :         current->next = previous->next;
     362      356746 :         previous->next = current;
     363             :     }
     364      376260 :     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      449374 : SetVariableHooks(VariableSpace space, const char *name,
     385             :                  VariableSubstituteHook shook,
     386             :                  VariableAssignHook ahook)
     387             : {
     388             :     struct _variable *current,
     389             :                *previous;
     390             : 
     391      449374 :     if (!space || !name)
     392           0 :         return;
     393             : 
     394      449374 :     if (!valid_variable_name(name))
     395           0 :         return;
     396             : 
     397     3419150 :     for (previous = space, current = space->next;
     398             :          current;
     399     2969776 :          previous = current, current = current->next)
     400             :     {
     401     3282384 :         int         cmp = strcmp(current->name, name);
     402             : 
     403     3282384 :         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     3282384 :         if (cmp > 0)
     415      312608 :             break;              /* it's not there */
     416             :     }
     417             : 
     418             :     /* not present, make new entry */
     419      449374 :     current = pg_malloc(sizeof *current);
     420      449374 :     current->name = pg_strdup(name);
     421      449374 :     current->value = NULL;
     422      449374 :     current->substitute_hook = shook;
     423      449374 :     current->assign_hook = ahook;
     424      449374 :     current->next = previous->next;
     425      449374 :     previous->next = current;
     426      449374 :     if (shook)
     427      371222 :         current->value = (*shook) (current->value);
     428      449374 :     if (ahook)
     429      449374 :         (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         894 : VariableHasHook(VariableSpace space, const char *name)
     438             : {
     439             :     struct _variable *current;
     440             : 
     441             :     Assert(space);
     442             :     Assert(name);
     443             : 
     444       44676 :     for (current = space->next; current; current = current->next)
     445             :     {
     446       44372 :         int         cmp = strcmp(current->name, name);
     447             : 
     448       44372 :         if (cmp == 0)
     449         274 :             return (current->substitute_hook != NULL ||
     450         134 :                     current->assign_hook != NULL);
     451       44232 :         if (cmp > 0)
     452         450 :             break;              /* it's not there */
     453             :     }
     454             : 
     455         754 :     return false;
     456             : }
     457             : 
     458             : /*
     459             :  * Convenience function to set a variable's value to "on".
     460             :  */
     461             : bool
     462       53556 : SetVariableBool(VariableSpace space, const char *name)
     463             : {
     464       53556 :     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           6 : PsqlVarEnumError(const char *name, const char *value, const char *suggestions)
     487             : {
     488           6 :     pg_log_error("unrecognized value \"%s\" for \"%s\"\n"
     489             :                  "Available values are: %s.",
     490             :                  value, name, suggestions);
     491           6 : }

Generated by: LCOV version 1.14