Age Owner Branch data TLA Line data Source code
1 : : /*
2 : : * psql - the PostgreSQL interactive terminal
3 : : *
4 : : * Copyright (c) 2000-2024, PostgreSQL Global Development Group
5 : : *
6 : : * src/bin/psql/variables.c
7 : : */
8 : : #include "postgres_fe.h"
9 : :
10 : : #include "common.h"
11 : : #include "common/logging.h"
12 : : #include "variables.h"
13 : :
14 : : /*
15 : : * Check whether a variable's name is allowed.
16 : : *
17 : : * We allow any non-ASCII character, as well as ASCII letters, digits, and
18 : : * underscore. Keep this in sync with the definition of variable_char in
19 : : * psqlscan.l and psqlscanslash.l.
20 : : */
21 : : static bool
4615 tgl@sss.pgh.pa.us 22 :CBC 933711 : valid_variable_name(const char *name)
23 : : {
24 : 933711 : const unsigned char *ptr = (const unsigned char *) name;
25 : :
26 : : /* Mustn't be zero-length */
27 [ - + ]: 933711 : if (*ptr == '\0')
4615 tgl@sss.pgh.pa.us 28 :UBC 0 : return false;
29 : :
4615 tgl@sss.pgh.pa.us 30 [ + + ]:CBC 9259347 : while (*ptr)
31 : : {
32 [ + - ]: 8325642 : if (IS_HIGHBIT_SET(*ptr) ||
33 : 8325642 : strchr("ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz"
34 [ + + ]: 8325642 : "_0123456789", *ptr) != NULL)
35 : 8325636 : ptr++;
36 : : else
37 : 6 : return false;
38 : : }
39 : :
40 : 933705 : return true;
41 : : }
42 : :
43 : : /*
44 : : * A "variable space" is represented by an otherwise-unused struct _variable
45 : : * that serves as list header.
46 : : *
47 : : * The list entries are kept in name order (according to strcmp). This
48 : : * is mainly to make the output of PrintVariables() more pleasing.
49 : : */
50 : : VariableSpace
8928 bruce@momjian.us 51 : 8197 : CreateVariableSpace(void)
52 : : {
53 : : struct _variable *ptr;
54 : :
6438 tgl@sss.pgh.pa.us 55 : 8197 : ptr = pg_malloc(sizeof *ptr);
56 : 8197 : ptr->name = NULL;
57 : 8197 : ptr->value = NULL;
2629 58 : 8197 : ptr->substitute_hook = NULL;
6438 59 : 8197 : ptr->assign_hook = NULL;
60 : 8197 : ptr->next = NULL;
61 : :
8928 bruce@momjian.us 62 : 8197 : return ptr;
63 : : }
64 : :
65 : : /*
66 : : * Get string value of variable, or NULL if it's not defined.
67 : : *
68 : : * Note: result is valid until variable is next assigned to.
69 : : */
70 : : const char *
71 : 1755 : GetVariable(VariableSpace space, const char *name)
72 : : {
73 : : struct _variable *current;
74 : :
75 [ - + ]: 1755 : if (!space)
8928 bruce@momjian.us 76 :UBC 0 : return NULL;
77 : :
6507 tgl@sss.pgh.pa.us 78 [ + + ]:CBC 60929 : for (current = space->next; current; current = current->next)
79 : : {
2629 80 : 60928 : int cmp = strcmp(current->name, name);
81 : :
82 [ + + ]: 60928 : if (cmp == 0)
83 : : {
84 : : /* this is correct answer when value is NULL, too */
8928 bruce@momjian.us 85 : 1529 : return current->value;
86 : : }
2629 tgl@sss.pgh.pa.us 87 [ + + ]: 59399 : if (cmp > 0)
88 : 225 : break; /* it's not there */
89 : : }
90 : :
8928 bruce@momjian.us 91 : 226 : return NULL;
92 : : }
93 : :
94 : : /*
95 : : * Try to interpret "value" as a boolean value, and if successful,
96 : : * store it in *result. Otherwise don't clobber *result.
97 : : *
98 : : * Valid values are: true, false, yes, no, on, off, 1, 0; as well as unique
99 : : * prefixes thereof.
100 : : *
101 : : * "name" is the name of the variable we're assigning to, to use in error
102 : : * report if any. Pass name == NULL to suppress the error report.
103 : : *
104 : : * Return true when "value" is syntactically valid, false otherwise.
105 : : */
106 : : bool
2631 tgl@sss.pgh.pa.us 107 : 112862 : ParseVariableBool(const char *value, const char *name, bool *result)
108 : : {
109 : : size_t len;
110 : 112862 : bool valid = true;
111 : :
112 : : /* Treat "unset" as an empty string, which will lead to error below */
5821 bruce@momjian.us 113 [ - + ]: 112862 : if (value == NULL)
2629 tgl@sss.pgh.pa.us 114 :UBC 0 : value = "";
115 : :
5821 bruce@momjian.us 116 :CBC 112862 : len = strlen(value);
117 : :
2631 tgl@sss.pgh.pa.us 118 [ + - + + ]: 112862 : if (len > 0 && pg_strncasecmp(value, "true", len) == 0)
119 : 108 : *result = true;
120 [ + - + + ]: 112754 : else if (len > 0 && pg_strncasecmp(value, "false", len) == 0)
121 : 125 : *result = false;
122 [ + - + + ]: 112629 : else if (len > 0 && pg_strncasecmp(value, "yes", len) == 0)
123 : 3 : *result = true;
124 [ + - + + ]: 112626 : else if (len > 0 && pg_strncasecmp(value, "no", len) == 0)
125 : 3 : *result = false;
126 : : /* 'o' is not unique enough */
5821 bruce@momjian.us 127 [ + + ]: 112623 : else if (pg_strncasecmp(value, "on", (len > 2 ? len : 2)) == 0)
2631 tgl@sss.pgh.pa.us 128 : 25397 : *result = true;
5821 bruce@momjian.us 129 [ + + ]: 87226 : else if (pg_strncasecmp(value, "off", (len > 2 ? len : 2)) == 0)
2631 tgl@sss.pgh.pa.us 130 : 82073 : *result = false;
5821 bruce@momjian.us 131 [ + + ]: 5153 : else if (pg_strcasecmp(value, "1") == 0)
2631 tgl@sss.pgh.pa.us 132 : 5140 : *result = true;
5821 bruce@momjian.us 133 [ + + ]: 13 : else if (pg_strcasecmp(value, "0") == 0)
2631 tgl@sss.pgh.pa.us 134 : 4 : *result = false;
135 : : else
136 : : {
137 : : /* string is not recognized; don't clobber *result */
3392 138 [ + + ]: 9 : if (name)
1840 peter@eisentraut.org 139 : 6 : pg_log_error("unrecognized value \"%s\" for \"%s\": Boolean expected",
140 : : value, name);
2631 tgl@sss.pgh.pa.us 141 : 9 : valid = false;
142 : : }
143 : 112862 : return valid;
144 : : }
145 : :
146 : : /*
147 : : * Try to interpret "value" as an integer value, and if successful,
148 : : * store it in *result. Otherwise don't clobber *result.
149 : : *
150 : : * "name" is the name of the variable we're assigning to, to use in error
151 : : * report if any. Pass name == NULL to suppress the error report.
152 : : *
153 : : * Return true when "value" is syntactically valid, false otherwise.
154 : : */
155 : : bool
156 : 24619 : ParseVariableNum(const char *value, const char *name, int *result)
157 : : {
158 : : char *end;
159 : : long numval;
160 : :
161 : : /* Treat "unset" as an empty string, which will lead to error below */
162 [ - + ]: 24619 : if (value == NULL)
2629 tgl@sss.pgh.pa.us 163 :UBC 0 : value = "";
164 : :
2631 tgl@sss.pgh.pa.us 165 :CBC 24619 : errno = 0;
166 : 24619 : numval = strtol(value, &end, 0);
167 [ + - + + : 24619 : if (errno == 0 && *end == '\0' && end != value && numval == (int) numval)
+ - + - ]
168 : : {
169 : 24616 : *result = (int) numval;
170 : 24616 : return true;
171 : : }
172 : : else
173 : : {
174 : : /* string is not recognized; don't clobber *result */
175 [ + - ]: 3 : if (name)
1840 peter@eisentraut.org 176 : 3 : pg_log_error("invalid value \"%s\" for \"%s\": integer expected",
177 : : value, name);
2631 tgl@sss.pgh.pa.us 178 : 3 : return false;
179 : : }
180 : : }
181 : :
182 : : /*
183 : : * Print values of all variables.
184 : : */
185 : : void
7696 bruce@momjian.us 186 :UBC 0 : PrintVariables(VariableSpace space)
187 : : {
188 : : struct _variable *ptr;
189 : :
6507 tgl@sss.pgh.pa.us 190 [ # # ]: 0 : if (!space)
191 : 0 : return;
192 : :
7559 bruce@momjian.us 193 [ # # ]: 0 : for (ptr = space->next; ptr; ptr = ptr->next)
194 : : {
6438 tgl@sss.pgh.pa.us 195 [ # # ]: 0 : if (ptr->value)
196 : 0 : printf("%s = '%s'\n", ptr->name, ptr->value);
6514 197 [ # # ]: 0 : if (cancel_pressed)
198 : 0 : break;
199 : : }
200 : : }
201 : :
202 : : /*
203 : : * Set the variable named "name" to value "value",
204 : : * or delete it if "value" is NULL.
205 : : *
206 : : * Returns true if successful, false if not; in the latter case a suitable
207 : : * error message has been printed, except for the unexpected case of
208 : : * space or name being NULL.
209 : : */
210 : : bool
8928 bruce@momjian.us 211 :CBC 753377 : SetVariable(VariableSpace space, const char *name, const char *value)
212 : : {
213 : : struct _variable *current,
214 : : *previous;
215 : :
2631 tgl@sss.pgh.pa.us 216 [ + - - + ]: 753377 : if (!space || !name)
8928 bruce@momjian.us 217 :UBC 0 : return false;
218 : :
4615 tgl@sss.pgh.pa.us 219 [ + + ]:CBC 753377 : if (!valid_variable_name(name))
220 : : {
221 : : /* Deletion of non-existent variable is not an error */
2629 222 [ - + ]: 6 : if (!value)
2629 tgl@sss.pgh.pa.us 223 :UBC 0 : return true;
1840 peter@eisentraut.org 224 :CBC 6 : pg_log_error("invalid variable name: \"%s\"", name);
8928 bruce@momjian.us 225 : 6 : return false;
226 : : }
227 : :
6507 tgl@sss.pgh.pa.us 228 : 753371 : for (previous = space, current = space->next;
229 [ + + ]: 15046982 : current;
230 : 14293611 : previous = current, current = current->next)
231 : : {
2629 232 : 15022081 : int cmp = strcmp(current->name, name);
233 : :
234 [ + + ]: 15022081 : if (cmp == 0)
235 : : {
236 : : /*
237 : : * Found entry, so update, unless assign hook returns false.
238 : : *
239 : : * We must duplicate the passed value to start with. This
240 : : * simplifies the API for substitute hooks. Moreover, some assign
241 : : * hooks assume that the passed value has the same lifespan as the
242 : : * variable. Having to free the string again on failure is a
243 : : * small price to pay for keeping these APIs simple.
244 : : */
245 [ + + ]: 628926 : char *new_value = value ? pg_strdup(value) : NULL;
246 : : bool confirmed;
247 : :
248 [ + + ]: 628926 : if (current->substitute_hook)
2411 peter_e@gmx.net 249 : 31654 : new_value = current->substitute_hook(new_value);
250 : :
6438 tgl@sss.pgh.pa.us 251 [ + + ]: 628926 : if (current->assign_hook)
2411 peter_e@gmx.net 252 : 56245 : confirmed = current->assign_hook(new_value);
253 : : else
2631 tgl@sss.pgh.pa.us 254 : 572681 : confirmed = true;
255 : :
256 [ + + ]: 628926 : if (confirmed)
257 : : {
667 peter@eisentraut.org 258 : 628917 : pg_free(current->value);
2631 tgl@sss.pgh.pa.us 259 : 628917 : current->value = new_value;
260 : :
261 : : /*
262 : : * If we deleted the value, and there are no hooks to
263 : : * remember, we can discard the variable altogether.
264 : : */
2629 265 [ + + ]: 628917 : if (new_value == NULL &&
266 [ + - ]: 3 : current->substitute_hook == NULL &&
267 [ + - ]: 3 : current->assign_hook == NULL)
268 : : {
269 : 3 : previous->next = current->next;
270 : 3 : free(current->name);
271 : 3 : free(current);
272 : : }
273 : : }
274 : : else
2489 275 : 9 : pg_free(new_value); /* current->value is left unchanged */
276 : :
2631 277 : 628926 : return confirmed;
278 : : }
2629 279 [ + + ]: 14393155 : if (cmp > 0)
280 : 99544 : break; /* it's not there */
281 : : }
282 : :
283 : : /* not present, make new entry ... unless we were asked to delete */
284 [ + + ]: 124445 : if (value)
285 : : {
286 : 124442 : current = pg_malloc(sizeof *current);
287 : 124442 : current->name = pg_strdup(name);
288 : 124442 : current->value = pg_strdup(value);
289 : 124442 : current->substitute_hook = NULL;
290 : 124442 : current->assign_hook = NULL;
291 : 124442 : current->next = previous->next;
292 : 124442 : previous->next = current;
293 : : }
6438 294 : 124445 : return true;
295 : : }
296 : :
297 : : /*
298 : : * Attach substitute and/or assign hook functions to the named variable.
299 : : * If you need only one hook, pass NULL for the other.
300 : : *
301 : : * If the variable doesn't already exist, create it with value NULL, just so
302 : : * we have a place to store the hook function(s). (The substitute hook might
303 : : * immediately change the NULL to something else; if not, this state is
304 : : * externally the same as the variable not being defined.)
305 : : *
306 : : * The substitute hook, if given, is immediately called on the variable's
307 : : * value. Then the assign hook, if given, is called on the variable's value.
308 : : * This is meant to let it update any derived psql state. If the assign hook
309 : : * doesn't like the current value, it will print a message to that effect,
310 : : * but we'll ignore it. Generally we do not expect any such failure here,
311 : : * because this should get called before any user-supplied value is assigned.
312 : : */
313 : : void
2629 314 : 180334 : SetVariableHooks(VariableSpace space, const char *name,
315 : : VariableSubstituteHook shook,
316 : : VariableAssignHook ahook)
317 : : {
318 : : struct _variable *current,
319 : : *previous;
320 : :
2631 321 [ + - - + ]: 180334 : if (!space || !name)
2631 tgl@sss.pgh.pa.us 322 :UBC 0 : return;
323 : :
4615 tgl@sss.pgh.pa.us 324 [ - + ]:CBC 180334 : if (!valid_variable_name(name))
2631 tgl@sss.pgh.pa.us 325 :UBC 0 : return;
326 : :
6438 tgl@sss.pgh.pa.us 327 :CBC 180334 : for (previous = space, current = space->next;
328 [ + + ]: 1245944 : current;
329 : 1065610 : previous = current, current = current->next)
330 : : {
2629 331 : 1196762 : int cmp = strcmp(current->name, name);
332 : :
333 [ - + ]: 1196762 : if (cmp == 0)
334 : : {
335 : : /* found entry, so update */
2629 tgl@sss.pgh.pa.us 336 :UBC 0 : current->substitute_hook = shook;
337 : 0 : current->assign_hook = ahook;
338 [ # # ]: 0 : if (shook)
339 : 0 : current->value = (*shook) (current->value);
340 [ # # ]: 0 : if (ahook)
341 : 0 : (void) (*ahook) (current->value);
2631 342 : 0 : return;
343 : : }
2629 tgl@sss.pgh.pa.us 344 [ + + ]:CBC 1196762 : if (cmp > 0)
345 : 131152 : break; /* it's not there */
346 : : }
347 : :
348 : : /* not present, make new entry */
6438 349 : 180334 : current = pg_malloc(sizeof *current);
350 : 180334 : current->name = pg_strdup(name);
351 : 180334 : current->value = NULL;
2629 352 : 180334 : current->substitute_hook = shook;
353 : 180334 : current->assign_hook = ahook;
354 : 180334 : current->next = previous->next;
6438 355 : 180334 : previous->next = current;
2629 356 [ + + ]: 180334 : if (shook)
357 : 147546 : current->value = (*shook) (current->value);
358 [ + - ]: 180334 : if (ahook)
359 : 180334 : (void) (*ahook) (current->value);
360 : : }
361 : :
362 : : /*
363 : : * Return true iff the named variable has substitute and/or assign hook
364 : : * functions.
365 : : */
366 : : bool
1252 noah@leadboat.com 367 : 389 : VariableHasHook(VariableSpace space, const char *name)
368 : : {
369 : : struct _variable *current;
370 : :
371 [ - + ]: 389 : Assert(space);
372 [ - + ]: 389 : Assert(name);
373 : :
374 [ + + ]: 17140 : for (current = space->next; current; current = current->next)
375 : : {
376 : 16998 : int cmp = strcmp(current->name, name);
377 : :
378 [ + + ]: 16998 : if (cmp == 0)
379 [ + + ]: 137 : return (current->substitute_hook != NULL ||
380 [ - + ]: 67 : current->assign_hook != NULL);
381 [ + + ]: 16928 : if (cmp > 0)
382 : 177 : break; /* it's not there */
383 : : }
384 : :
385 : 319 : return false;
386 : : }
387 : :
388 : : /*
389 : : * Convenience function to set a variable's value to "on".
390 : : */
391 : : bool
8827 peter_e@gmx.net 392 : 23376 : SetVariableBool(VariableSpace space, const char *name)
393 : : {
7596 tgl@sss.pgh.pa.us 394 : 23376 : return SetVariable(space, name, "on");
395 : : }
396 : :
397 : : /*
398 : : * Attempt to delete variable.
399 : : *
400 : : * If unsuccessful, print a message and return "false".
401 : : * Deleting a nonexistent variable is not an error.
402 : : */
403 : : bool
8928 bruce@momjian.us 404 :UBC 0 : DeleteVariable(VariableSpace space, const char *name)
405 : : {
2629 tgl@sss.pgh.pa.us 406 : 0 : return SetVariable(space, name, NULL);
407 : : }
408 : :
409 : : /*
410 : : * Emit error with suggestions for variables or commands
411 : : * accepting enum-style arguments.
412 : : * This function just exists to standardize the wording.
413 : : * suggestions should follow the format "fee, fi, fo, fum".
414 : : */
415 : : void
2631 tgl@sss.pgh.pa.us 416 :CBC 3 : PsqlVarEnumError(const char *name, const char *value, const char *suggestions)
417 : : {
1840 peter@eisentraut.org 418 : 3 : pg_log_error("unrecognized value \"%s\" for \"%s\"\n"
419 : : "Available values are: %s.",
420 : : value, name, suggestions);
2631 tgl@sss.pgh.pa.us 421 : 3 : }
|