Age Owner Branch data TLA 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
5391 tgl@sss.pgh.pa.us 24 :CBC 2212252 : valid_variable_name(const char *name)
25 : : {
26 : 2212252 : const unsigned char *ptr = (const unsigned char *) name;
27 : :
28 : : /* Mustn't be zero-length */
29 [ - + ]: 2212252 : if (*ptr == '\0')
5391 tgl@sss.pgh.pa.us 30 :UBC 0 : return false;
31 : :
5391 tgl@sss.pgh.pa.us 32 [ + + ]:CBC 31157997 : while (*ptr)
33 : : {
34 [ + - ]: 28945753 : if (IS_HIGHBIT_SET(*ptr) ||
35 : 28945753 : strchr("ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz"
36 [ + + ]: 28945753 : "_0123456789", *ptr) != NULL)
37 : 28945745 : ptr++;
38 : : else
39 : 8 : return false;
40 : : }
41 : :
42 : 2212244 : 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
9704 bruce@momjian.us 53 : 10797 : CreateVariableSpace(void)
54 : : {
55 : : struct _variable *ptr;
56 : :
92 michael@paquier.xyz 57 :GNC 10797 : ptr = pg_malloc_object(struct _variable);
7214 tgl@sss.pgh.pa.us 58 :CBC 10797 : ptr->name = NULL;
59 : 10797 : ptr->value = NULL;
3405 60 : 10797 : ptr->substitute_hook = NULL;
7214 61 : 10797 : ptr->assign_hook = NULL;
62 : 10797 : ptr->next = NULL;
63 : :
9704 bruce@momjian.us 64 : 10797 : 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 : 2754 : GetVariable(VariableSpace space, const char *name)
74 : : {
75 : : struct _variable *current;
76 : :
77 [ - + ]: 2754 : if (!space)
9704 bruce@momjian.us 78 :UBC 0 : return NULL;
79 : :
7283 tgl@sss.pgh.pa.us 80 [ + + ]:CBC 106648 : for (current = space->next; current; current = current->next)
81 : : {
3405 82 : 106647 : int cmp = strcmp(current->name, name);
83 : :
84 [ + + ]: 106647 : if (cmp == 0)
85 : : {
86 : : /* this is correct answer when value is NULL, too */
9704 bruce@momjian.us 87 : 2421 : return current->value;
88 : : }
3405 tgl@sss.pgh.pa.us 89 [ + + ]: 104226 : if (cmp > 0)
90 : 332 : break; /* it's not there */
91 : : }
92 : :
9704 bruce@momjian.us 93 : 333 : 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
3407 tgl@sss.pgh.pa.us 109 : 149451 : ParseVariableBool(const char *value, const char *name, bool *result)
110 : : {
111 : : size_t len;
112 : 149451 : bool valid = true;
113 : :
114 : : /* Treat "unset" as an empty string, which will lead to error below */
6597 bruce@momjian.us 115 [ - + ]: 149451 : if (value == NULL)
3405 tgl@sss.pgh.pa.us 116 :UBC 0 : value = "";
117 : :
6597 bruce@momjian.us 118 :CBC 149451 : len = strlen(value);
119 : :
3407 tgl@sss.pgh.pa.us 120 [ + - + + ]: 149451 : if (len > 0 && pg_strncasecmp(value, "true", len) == 0)
121 : 162 : *result = true;
122 [ + - + + ]: 149289 : else if (len > 0 && pg_strncasecmp(value, "false", len) == 0)
123 : 210 : *result = false;
124 [ + - + + ]: 149079 : else if (len > 0 && pg_strncasecmp(value, "yes", len) == 0)
125 : 4 : *result = true;
126 [ + - + + ]: 149075 : else if (len > 0 && pg_strncasecmp(value, "no", len) == 0)
127 : 4 : *result = false;
128 : : /* 'o' is not unique enough */
6597 bruce@momjian.us 129 [ + + ]: 149071 : else if (pg_strncasecmp(value, "on", (len > 2 ? len : 2)) == 0)
3407 tgl@sss.pgh.pa.us 130 : 33777 : *result = true;
6597 bruce@momjian.us 131 [ + + ]: 115294 : else if (pg_strncasecmp(value, "off", (len > 2 ? len : 2)) == 0)
3407 tgl@sss.pgh.pa.us 132 : 108111 : *result = false;
6597 bruce@momjian.us 133 [ + + ]: 7183 : else if (pg_strcasecmp(value, "1") == 0)
3407 tgl@sss.pgh.pa.us 134 : 7166 : *result = true;
6597 bruce@momjian.us 135 [ + + ]: 17 : else if (pg_strcasecmp(value, "0") == 0)
3407 tgl@sss.pgh.pa.us 136 : 5 : *result = false;
137 : : else
138 : : {
139 : : /* string is not recognized; don't clobber *result */
4168 140 [ + + ]: 12 : if (name)
2616 peter@eisentraut.org 141 : 8 : pg_log_error("unrecognized value \"%s\" for \"%s\": Boolean expected",
142 : : value, name);
3407 tgl@sss.pgh.pa.us 143 : 12 : valid = false;
144 : : }
145 : 149451 : 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 : 32444 : 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 [ - + ]: 32444 : if (value == NULL)
3405 tgl@sss.pgh.pa.us 165 :UBC 0 : value = "";
166 : :
3407 tgl@sss.pgh.pa.us 167 :CBC 32444 : errno = 0;
168 : 32444 : numval = strtol(value, &end, 0);
169 [ + - + + : 32444 : if (errno == 0 && *end == '\0' && end != value && numval == (int) numval)
+ - + - ]
170 : : {
171 : 32440 : *result = (int) numval;
172 : 32440 : return true;
173 : : }
174 : : else
175 : : {
176 : : /* string is not recognized; don't clobber *result */
177 [ + - ]: 4 : if (name)
2616 peter@eisentraut.org 178 : 4 : pg_log_error("invalid value \"%s\" for \"%s\": integer expected",
179 : : value, name);
3407 tgl@sss.pgh.pa.us 180 : 4 : 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
431 dgustafsson@postgres 195 : 10801 : 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 [ + - - + ]: 10801 : if ((value == NULL) || (*value == '\0'))
205 : : {
431 dgustafsson@postgres 206 [ # # ]:UBC 0 : if (name)
348 peter@eisentraut.org 207 : 0 : pg_log_error("invalid input syntax for variable \"%s\"", name);
431 dgustafsson@postgres 208 : 0 : return false;
209 : : }
210 : :
431 dgustafsson@postgres 211 :CBC 10801 : errno = 0;
212 : 10801 : dblval = strtod(value, &end);
213 [ + + + - : 10801 : if (errno == 0 && *end == '\0' && end != value)
+ - ]
214 : : {
215 [ - + ]: 10800 : if (dblval < min)
216 : : {
431 dgustafsson@postgres 217 [ # # ]:UBC 0 : if (name)
348 peter@eisentraut.org 218 : 0 : pg_log_error("invalid value \"%s\" for variable \"%s\": must be greater than %.2f",
219 : : value, name, min);
431 dgustafsson@postgres 220 : 0 : return false;
221 : : }
431 dgustafsson@postgres 222 [ - + ]:CBC 10800 : else if (dblval > max)
223 : : {
431 dgustafsson@postgres 224 [ # # ]:UBC 0 : if (name)
348 peter@eisentraut.org 225 : 0 : pg_log_error("invalid value \"%s\" for variable \"%s\": must be less than %.2f",
226 : : value, name, max);
12 dgustafsson@postgres 227 : 0 : return false;
228 : : }
431 dgustafsson@postgres 229 :CBC 10800 : *result = dblval;
230 : 10800 : return true;
231 : : }
232 : :
233 : : /*
234 : : * Cater for platforms which treat values which aren't zero, but that are
235 : : * too close to zero to have full precision, by checking for zero or real
236 : : * out-of-range values.
237 : : */
429 238 [ + - + - ]: 1 : else if ((errno == ERANGE) &&
431 239 [ - + - - ]: 1 : (dblval == 0.0 || dblval >= HUGE_VAL || dblval <= -HUGE_VAL))
240 : : {
241 [ + - ]: 1 : if (name)
348 peter@eisentraut.org 242 : 1 : pg_log_error("value \"%s\" is out of range for variable \"%s\"", value, name);
431 dgustafsson@postgres 243 : 1 : return false;
244 : : }
245 : : else
246 : : {
431 dgustafsson@postgres 247 [ # # ]:UBC 0 : if (name)
348 peter@eisentraut.org 248 : 0 : pg_log_error("invalid value \"%s\" for variable \"%s\"", value, name);
431 dgustafsson@postgres 249 : 0 : return false;
250 : : }
251 : : }
252 : :
253 : : /*
254 : : * Print values of all variables.
255 : : */
256 : : void
8472 bruce@momjian.us 257 : 0 : PrintVariables(VariableSpace space)
258 : : {
259 : : struct _variable *ptr;
260 : :
7283 tgl@sss.pgh.pa.us 261 [ # # ]: 0 : if (!space)
262 : 0 : return;
263 : :
8335 bruce@momjian.us 264 [ # # ]: 0 : for (ptr = space->next; ptr; ptr = ptr->next)
265 : : {
7214 tgl@sss.pgh.pa.us 266 [ # # ]: 0 : if (ptr->value)
267 : 0 : printf("%s = '%s'\n", ptr->name, ptr->value);
7290 268 [ # # ]: 0 : if (cancel_pressed)
269 : 0 : break;
270 : : }
271 : : }
272 : :
273 : : /*
274 : : * Set the variable named "name" to value "value",
275 : : * or delete it if "value" is NULL.
276 : : *
277 : : * Returns true if successful, false if not; in the latter case a suitable
278 : : * error message has been printed, except for the unexpected case of
279 : : * space or name being NULL.
280 : : */
281 : : bool
9704 bruce@momjian.us 282 :CBC 1963921 : SetVariable(VariableSpace space, const char *name, const char *value)
283 : : {
284 : : struct _variable *current,
285 : : *previous;
286 : :
3407 tgl@sss.pgh.pa.us 287 [ + - - + ]: 1963921 : if (!space || !name)
9704 bruce@momjian.us 288 :UBC 0 : return false;
289 : :
5391 tgl@sss.pgh.pa.us 290 [ + + ]:CBC 1963921 : if (!valid_variable_name(name))
291 : : {
292 : : /* Deletion of non-existent variable is not an error */
3405 293 [ - + ]: 8 : if (!value)
3405 tgl@sss.pgh.pa.us 294 :UBC 0 : return true;
2616 peter@eisentraut.org 295 :CBC 8 : pg_log_error("invalid variable name: \"%s\"", name);
9704 bruce@momjian.us 296 : 8 : return false;
297 : : }
298 : :
7283 tgl@sss.pgh.pa.us 299 : 1963913 : for (previous = space, current = space->next;
300 [ + + ]: 42563538 : current;
301 : 40599625 : previous = current, current = current->next)
302 : : {
3405 303 : 42563047 : int cmp = strcmp(current->name, name);
304 : :
305 [ + + ]: 42563047 : if (cmp == 0)
306 : : {
307 : : /*
308 : : * Found entry, so update, unless assign hook returns false.
309 : : *
310 : : * We must duplicate the passed value to start with. This
311 : : * simplifies the API for substitute hooks. Moreover, some assign
312 : : * hooks assume that the passed value has the same lifespan as the
313 : : * variable. Having to free the string again on failure is a
314 : : * small price to pay for keeping these APIs simple.
315 : : */
316 [ + + ]: 1746220 : char *new_value = value ? pg_strdup(value) : NULL;
317 : : bool confirmed;
318 : :
319 [ + + ]: 1746220 : if (current->substitute_hook)
3187 peter_e@gmx.net 320 : 42511 : new_value = current->substitute_hook(new_value);
321 : :
7214 tgl@sss.pgh.pa.us 322 [ + + ]: 1746220 : if (current->assign_hook)
3187 peter_e@gmx.net 323 : 74902 : confirmed = current->assign_hook(new_value);
324 : : else
3407 tgl@sss.pgh.pa.us 325 : 1671318 : confirmed = true;
326 : :
327 [ + + ]: 1746220 : if (confirmed)
328 : : {
1443 peter@eisentraut.org 329 : 1746207 : pg_free(current->value);
3407 tgl@sss.pgh.pa.us 330 : 1746207 : current->value = new_value;
331 : :
332 : : /*
333 : : * If we deleted the value, and there are no hooks to
334 : : * remember, we can discard the variable altogether.
335 : : */
3405 336 [ + + ]: 1746207 : if (new_value == NULL &&
337 [ + - ]: 4 : current->substitute_hook == NULL &&
338 [ + - ]: 4 : current->assign_hook == NULL)
339 : : {
340 : 4 : previous->next = current->next;
341 : 4 : free(current->name);
342 : 4 : free(current);
343 : : }
344 : : }
345 : : else
3265 346 : 13 : pg_free(new_value); /* current->value is left unchanged */
347 : :
3407 348 : 1746220 : return confirmed;
349 : : }
3405 350 [ + + ]: 40816827 : if (cmp > 0)
351 : 217202 : break; /* it's not there */
352 : : }
353 : :
354 : : /* not present, make new entry ... unless we were asked to delete */
355 [ + + ]: 217693 : if (value)
356 : : {
92 michael@paquier.xyz 357 :GNC 196331 : current = pg_malloc_object(struct _variable);
3405 tgl@sss.pgh.pa.us 358 :CBC 196331 : current->name = pg_strdup(name);
359 : 196331 : current->value = pg_strdup(value);
360 : 196331 : current->substitute_hook = NULL;
361 : 196331 : current->assign_hook = NULL;
362 : 196331 : current->next = previous->next;
363 : 196331 : previous->next = current;
364 : : }
7214 365 : 217693 : return true;
366 : : }
367 : :
368 : : /*
369 : : * Attach substitute and/or assign hook functions to the named variable.
370 : : * If you need only one hook, pass NULL for the other.
371 : : *
372 : : * If the variable doesn't already exist, create it with value NULL, just so
373 : : * we have a place to store the hook function(s). (The substitute hook might
374 : : * immediately change the NULL to something else; if not, this state is
375 : : * externally the same as the variable not being defined.)
376 : : *
377 : : * The substitute hook, if given, is immediately called on the variable's
378 : : * value. Then the assign hook, if given, is called on the variable's value.
379 : : * This is meant to let it update any derived psql state. If the assign hook
380 : : * doesn't like the current value, it will print a message to that effect,
381 : : * but we'll ignore it. Generally we do not expect any such failure here,
382 : : * because this should get called before any user-supplied value is assigned.
383 : : */
384 : : void
3405 385 : 248331 : SetVariableHooks(VariableSpace space, const char *name,
386 : : VariableSubstituteHook shook,
387 : : VariableAssignHook ahook)
388 : : {
389 : : struct _variable *current,
390 : : *previous;
391 : :
3407 392 [ + - - + ]: 248331 : if (!space || !name)
3407 tgl@sss.pgh.pa.us 393 :UBC 0 : return;
394 : :
5391 tgl@sss.pgh.pa.us 395 [ - + ]:CBC 248331 : if (!valid_variable_name(name))
3407 tgl@sss.pgh.pa.us 396 :UBC 0 : return;
397 : :
7214 tgl@sss.pgh.pa.us 398 :CBC 248331 : for (previous = space, current = space->next;
399 [ + + ]: 1889475 : current;
400 : 1641144 : previous = current, current = current->next)
401 : : {
3405 402 : 1813896 : int cmp = strcmp(current->name, name);
403 : :
404 [ - + ]: 1813896 : if (cmp == 0)
405 : : {
406 : : /* found entry, so update */
3405 tgl@sss.pgh.pa.us 407 :UBC 0 : current->substitute_hook = shook;
408 : 0 : current->assign_hook = ahook;
409 [ # # ]: 0 : if (shook)
410 : 0 : current->value = (*shook) (current->value);
411 [ # # ]: 0 : if (ahook)
412 : 0 : (void) (*ahook) (current->value);
3407 413 : 0 : return;
414 : : }
3405 tgl@sss.pgh.pa.us 415 [ + + ]:CBC 1813896 : if (cmp > 0)
416 : 172752 : break; /* it's not there */
417 : : }
418 : :
419 : : /* not present, make new entry */
92 michael@paquier.xyz 420 :GNC 248331 : current = pg_malloc_object(struct _variable);
7214 tgl@sss.pgh.pa.us 421 :CBC 248331 : current->name = pg_strdup(name);
422 : 248331 : current->value = NULL;
3405 423 : 248331 : current->substitute_hook = shook;
424 : 248331 : current->assign_hook = ahook;
425 : 248331 : current->next = previous->next;
7214 426 : 248331 : previous->next = current;
3405 427 [ + + ]: 248331 : if (shook)
428 : 205143 : current->value = (*shook) (current->value);
429 [ + - ]: 248331 : if (ahook)
430 : 248331 : (void) (*ahook) (current->value);
431 : : }
432 : :
433 : : /*
434 : : * Return true iff the named variable has substitute and/or assign hook
435 : : * functions.
436 : : */
437 : : bool
2028 noah@leadboat.com 438 : 660 : VariableHasHook(VariableSpace space, const char *name)
439 : : {
440 : : struct _variable *current;
441 : :
442 [ - + ]: 660 : Assert(space);
443 [ - + ]: 660 : Assert(name);
444 : :
445 [ + + ]: 32839 : for (current = space->next; current; current = current->next)
446 : : {
447 : 32601 : int cmp = strcmp(current->name, name);
448 : :
449 [ + + ]: 32601 : if (cmp == 0)
450 [ + + ]: 196 : return (current->substitute_hook != NULL ||
451 [ - + ]: 96 : current->assign_hook != NULL);
452 [ + + ]: 32501 : if (cmp > 0)
453 : 322 : break; /* it's not there */
454 : : }
455 : :
456 : 560 : return false;
457 : : }
458 : :
459 : : /*
460 : : * Convenience function to set a variable's value to "on".
461 : : */
462 : : bool
9603 peter_e@gmx.net 463 : 30991 : SetVariableBool(VariableSpace space, const char *name)
464 : : {
8372 tgl@sss.pgh.pa.us 465 : 30991 : return SetVariable(space, name, "on");
466 : : }
467 : :
468 : : /*
469 : : * Attempt to delete variable.
470 : : *
471 : : * If unsuccessful, print a message and return "false".
472 : : * Deleting a nonexistent variable is not an error.
473 : : */
474 : : bool
9704 bruce@momjian.us 475 :UBC 0 : DeleteVariable(VariableSpace space, const char *name)
476 : : {
3405 tgl@sss.pgh.pa.us 477 : 0 : return SetVariable(space, name, NULL);
478 : : }
479 : :
480 : : /*
481 : : * Emit error with suggestions for variables or commands
482 : : * accepting enum-style arguments.
483 : : * This function just exists to standardize the wording.
484 : : * suggestions should follow the format "fee, fi, fo, fum".
485 : : */
486 : : void
3407 tgl@sss.pgh.pa.us 487 :CBC 4 : PsqlVarEnumError(const char *name, const char *value, const char *suggestions)
488 : : {
2616 peter@eisentraut.org 489 : 4 : pg_log_error("unrecognized value \"%s\" for \"%s\"\n"
490 : : "Available values are: %s.",
491 : : value, name, suggestions);
3407 tgl@sss.pgh.pa.us 492 : 4 : }
|