1 | /*------------------------------------------------------------------------- |
2 | * |
3 | * variable.c |
4 | * Routines for handling specialized SET variables. |
5 | * |
6 | * |
7 | * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group |
8 | * Portions Copyright (c) 1994, Regents of the University of California |
9 | * |
10 | * |
11 | * IDENTIFICATION |
12 | * src/backend/commands/variable.c |
13 | * |
14 | *------------------------------------------------------------------------- |
15 | */ |
16 | |
17 | #include "postgres.h" |
18 | |
19 | #include <ctype.h> |
20 | |
21 | #include "access/htup_details.h" |
22 | #include "access/parallel.h" |
23 | #include "access/xact.h" |
24 | #include "access/xlog.h" |
25 | #include "catalog/pg_authid.h" |
26 | #include "commands/variable.h" |
27 | #include "miscadmin.h" |
28 | #include "utils/acl.h" |
29 | #include "utils/builtins.h" |
30 | #include "utils/syscache.h" |
31 | #include "utils/snapmgr.h" |
32 | #include "utils/timestamp.h" |
33 | #include "utils/varlena.h" |
34 | #include "mb/pg_wchar.h" |
35 | |
36 | /* |
37 | * DATESTYLE |
38 | */ |
39 | |
40 | /* |
41 | * check_datestyle: GUC check_hook for datestyle |
42 | */ |
43 | bool |
44 | check_datestyle(char **newval, void **, GucSource source) |
45 | { |
46 | int newDateStyle = DateStyle; |
47 | int newDateOrder = DateOrder; |
48 | bool have_style = false; |
49 | bool have_order = false; |
50 | bool ok = true; |
51 | char *rawstring; |
52 | int *; |
53 | char *result; |
54 | List *elemlist; |
55 | ListCell *l; |
56 | |
57 | /* Need a modifiable copy of string */ |
58 | rawstring = pstrdup(*newval); |
59 | |
60 | /* Parse string into list of identifiers */ |
61 | if (!SplitIdentifierString(rawstring, ',', &elemlist)) |
62 | { |
63 | /* syntax error in list */ |
64 | GUC_check_errdetail("List syntax is invalid." ); |
65 | pfree(rawstring); |
66 | list_free(elemlist); |
67 | return false; |
68 | } |
69 | |
70 | foreach(l, elemlist) |
71 | { |
72 | char *tok = (char *) lfirst(l); |
73 | |
74 | /* Ugh. Somebody ought to write a table driven version -- mjl */ |
75 | |
76 | if (pg_strcasecmp(tok, "ISO" ) == 0) |
77 | { |
78 | if (have_style && newDateStyle != USE_ISO_DATES) |
79 | ok = false; /* conflicting styles */ |
80 | newDateStyle = USE_ISO_DATES; |
81 | have_style = true; |
82 | } |
83 | else if (pg_strcasecmp(tok, "SQL" ) == 0) |
84 | { |
85 | if (have_style && newDateStyle != USE_SQL_DATES) |
86 | ok = false; /* conflicting styles */ |
87 | newDateStyle = USE_SQL_DATES; |
88 | have_style = true; |
89 | } |
90 | else if (pg_strncasecmp(tok, "POSTGRES" , 8) == 0) |
91 | { |
92 | if (have_style && newDateStyle != USE_POSTGRES_DATES) |
93 | ok = false; /* conflicting styles */ |
94 | newDateStyle = USE_POSTGRES_DATES; |
95 | have_style = true; |
96 | } |
97 | else if (pg_strcasecmp(tok, "GERMAN" ) == 0) |
98 | { |
99 | if (have_style && newDateStyle != USE_GERMAN_DATES) |
100 | ok = false; /* conflicting styles */ |
101 | newDateStyle = USE_GERMAN_DATES; |
102 | have_style = true; |
103 | /* GERMAN also sets DMY, unless explicitly overridden */ |
104 | if (!have_order) |
105 | newDateOrder = DATEORDER_DMY; |
106 | } |
107 | else if (pg_strcasecmp(tok, "YMD" ) == 0) |
108 | { |
109 | if (have_order && newDateOrder != DATEORDER_YMD) |
110 | ok = false; /* conflicting orders */ |
111 | newDateOrder = DATEORDER_YMD; |
112 | have_order = true; |
113 | } |
114 | else if (pg_strcasecmp(tok, "DMY" ) == 0 || |
115 | pg_strncasecmp(tok, "EURO" , 4) == 0) |
116 | { |
117 | if (have_order && newDateOrder != DATEORDER_DMY) |
118 | ok = false; /* conflicting orders */ |
119 | newDateOrder = DATEORDER_DMY; |
120 | have_order = true; |
121 | } |
122 | else if (pg_strcasecmp(tok, "MDY" ) == 0 || |
123 | pg_strcasecmp(tok, "US" ) == 0 || |
124 | pg_strncasecmp(tok, "NONEURO" , 7) == 0) |
125 | { |
126 | if (have_order && newDateOrder != DATEORDER_MDY) |
127 | ok = false; /* conflicting orders */ |
128 | newDateOrder = DATEORDER_MDY; |
129 | have_order = true; |
130 | } |
131 | else if (pg_strcasecmp(tok, "DEFAULT" ) == 0) |
132 | { |
133 | /* |
134 | * Easiest way to get the current DEFAULT state is to fetch the |
135 | * DEFAULT string from guc.c and recursively parse it. |
136 | * |
137 | * We can't simply "return check_datestyle(...)" because we need |
138 | * to handle constructs like "DEFAULT, ISO". |
139 | */ |
140 | char *subval; |
141 | void * = NULL; |
142 | |
143 | subval = strdup(GetConfigOptionResetString("datestyle" )); |
144 | if (!subval) |
145 | { |
146 | ok = false; |
147 | break; |
148 | } |
149 | if (!check_datestyle(&subval, &subextra, source)) |
150 | { |
151 | free(subval); |
152 | ok = false; |
153 | break; |
154 | } |
155 | myextra = (int *) subextra; |
156 | if (!have_style) |
157 | newDateStyle = myextra[0]; |
158 | if (!have_order) |
159 | newDateOrder = myextra[1]; |
160 | free(subval); |
161 | free(subextra); |
162 | } |
163 | else |
164 | { |
165 | GUC_check_errdetail("Unrecognized key word: \"%s\"." , tok); |
166 | pfree(rawstring); |
167 | list_free(elemlist); |
168 | return false; |
169 | } |
170 | } |
171 | |
172 | pfree(rawstring); |
173 | list_free(elemlist); |
174 | |
175 | if (!ok) |
176 | { |
177 | GUC_check_errdetail("Conflicting \"datestyle\" specifications." ); |
178 | return false; |
179 | } |
180 | |
181 | /* |
182 | * Prepare the canonical string to return. GUC wants it malloc'd. |
183 | */ |
184 | result = (char *) malloc(32); |
185 | if (!result) |
186 | return false; |
187 | |
188 | switch (newDateStyle) |
189 | { |
190 | case USE_ISO_DATES: |
191 | strcpy(result, "ISO" ); |
192 | break; |
193 | case USE_SQL_DATES: |
194 | strcpy(result, "SQL" ); |
195 | break; |
196 | case USE_GERMAN_DATES: |
197 | strcpy(result, "German" ); |
198 | break; |
199 | default: |
200 | strcpy(result, "Postgres" ); |
201 | break; |
202 | } |
203 | switch (newDateOrder) |
204 | { |
205 | case DATEORDER_YMD: |
206 | strcat(result, ", YMD" ); |
207 | break; |
208 | case DATEORDER_DMY: |
209 | strcat(result, ", DMY" ); |
210 | break; |
211 | default: |
212 | strcat(result, ", MDY" ); |
213 | break; |
214 | } |
215 | |
216 | free(*newval); |
217 | *newval = result; |
218 | |
219 | /* |
220 | * Set up the "extra" struct actually used by assign_datestyle. |
221 | */ |
222 | myextra = (int *) malloc(2 * sizeof(int)); |
223 | if (!myextra) |
224 | return false; |
225 | myextra[0] = newDateStyle; |
226 | myextra[1] = newDateOrder; |
227 | *extra = (void *) myextra; |
228 | |
229 | return true; |
230 | } |
231 | |
232 | /* |
233 | * assign_datestyle: GUC assign_hook for datestyle |
234 | */ |
235 | void |
236 | assign_datestyle(const char *newval, void *) |
237 | { |
238 | int * = (int *) extra; |
239 | |
240 | DateStyle = myextra[0]; |
241 | DateOrder = myextra[1]; |
242 | } |
243 | |
244 | |
245 | /* |
246 | * TIMEZONE |
247 | */ |
248 | |
249 | /* |
250 | * check_timezone: GUC check_hook for timezone |
251 | */ |
252 | bool |
253 | check_timezone(char **newval, void **, GucSource source) |
254 | { |
255 | pg_tz *new_tz; |
256 | long gmtoffset; |
257 | char *endptr; |
258 | double hours; |
259 | |
260 | if (pg_strncasecmp(*newval, "interval" , 8) == 0) |
261 | { |
262 | /* |
263 | * Support INTERVAL 'foo'. This is for SQL spec compliance, not |
264 | * because it has any actual real-world usefulness. |
265 | */ |
266 | const char *valueptr = *newval; |
267 | char *val; |
268 | Interval *interval; |
269 | |
270 | valueptr += 8; |
271 | while (isspace((unsigned char) *valueptr)) |
272 | valueptr++; |
273 | if (*valueptr++ != '\'') |
274 | return false; |
275 | val = pstrdup(valueptr); |
276 | /* Check and remove trailing quote */ |
277 | endptr = strchr(val, '\''); |
278 | if (!endptr || endptr[1] != '\0') |
279 | { |
280 | pfree(val); |
281 | return false; |
282 | } |
283 | *endptr = '\0'; |
284 | |
285 | /* |
286 | * Try to parse it. XXX an invalid interval format will result in |
287 | * ereport(ERROR), which is not desirable for GUC. We did what we |
288 | * could to guard against this in flatten_set_variable_args, but a |
289 | * string coming in from postgresql.conf might contain anything. |
290 | */ |
291 | interval = DatumGetIntervalP(DirectFunctionCall3(interval_in, |
292 | CStringGetDatum(val), |
293 | ObjectIdGetDatum(InvalidOid), |
294 | Int32GetDatum(-1))); |
295 | |
296 | pfree(val); |
297 | if (interval->month != 0) |
298 | { |
299 | GUC_check_errdetail("Cannot specify months in time zone interval." ); |
300 | pfree(interval); |
301 | return false; |
302 | } |
303 | if (interval->day != 0) |
304 | { |
305 | GUC_check_errdetail("Cannot specify days in time zone interval." ); |
306 | pfree(interval); |
307 | return false; |
308 | } |
309 | |
310 | /* Here we change from SQL to Unix sign convention */ |
311 | gmtoffset = -(interval->time / USECS_PER_SEC); |
312 | new_tz = pg_tzset_offset(gmtoffset); |
313 | |
314 | pfree(interval); |
315 | } |
316 | else |
317 | { |
318 | /* |
319 | * Try it as a numeric number of hours (possibly fractional). |
320 | */ |
321 | hours = strtod(*newval, &endptr); |
322 | if (endptr != *newval && *endptr == '\0') |
323 | { |
324 | /* Here we change from SQL to Unix sign convention */ |
325 | gmtoffset = -hours * SECS_PER_HOUR; |
326 | new_tz = pg_tzset_offset(gmtoffset); |
327 | } |
328 | else |
329 | { |
330 | /* |
331 | * Otherwise assume it is a timezone name, and try to load it. |
332 | */ |
333 | new_tz = pg_tzset(*newval); |
334 | |
335 | if (!new_tz) |
336 | { |
337 | /* Doesn't seem to be any great value in errdetail here */ |
338 | return false; |
339 | } |
340 | |
341 | if (!pg_tz_acceptable(new_tz)) |
342 | { |
343 | GUC_check_errmsg("time zone \"%s\" appears to use leap seconds" , |
344 | *newval); |
345 | GUC_check_errdetail("PostgreSQL does not support leap seconds." ); |
346 | return false; |
347 | } |
348 | } |
349 | } |
350 | |
351 | /* Test for failure in pg_tzset_offset, which we assume is out-of-range */ |
352 | if (!new_tz) |
353 | { |
354 | GUC_check_errdetail("UTC timezone offset is out of range." ); |
355 | return false; |
356 | } |
357 | |
358 | /* |
359 | * Pass back data for assign_timezone to use |
360 | */ |
361 | *extra = malloc(sizeof(pg_tz *)); |
362 | if (!*extra) |
363 | return false; |
364 | *((pg_tz **) *extra) = new_tz; |
365 | |
366 | return true; |
367 | } |
368 | |
369 | /* |
370 | * assign_timezone: GUC assign_hook for timezone |
371 | */ |
372 | void |
373 | assign_timezone(const char *newval, void *) |
374 | { |
375 | session_timezone = *((pg_tz **) extra); |
376 | } |
377 | |
378 | /* |
379 | * show_timezone: GUC show_hook for timezone |
380 | */ |
381 | const char * |
382 | show_timezone(void) |
383 | { |
384 | const char *tzn; |
385 | |
386 | /* Always show the zone's canonical name */ |
387 | tzn = pg_get_timezone_name(session_timezone); |
388 | |
389 | if (tzn != NULL) |
390 | return tzn; |
391 | |
392 | return "unknown" ; |
393 | } |
394 | |
395 | |
396 | /* |
397 | * LOG_TIMEZONE |
398 | * |
399 | * For log_timezone, we don't support the interval-based methods of setting a |
400 | * zone, which are only there for SQL spec compliance not because they're |
401 | * actually useful. |
402 | */ |
403 | |
404 | /* |
405 | * check_log_timezone: GUC check_hook for log_timezone |
406 | */ |
407 | bool |
408 | check_log_timezone(char **newval, void **, GucSource source) |
409 | { |
410 | pg_tz *new_tz; |
411 | |
412 | /* |
413 | * Assume it is a timezone name, and try to load it. |
414 | */ |
415 | new_tz = pg_tzset(*newval); |
416 | |
417 | if (!new_tz) |
418 | { |
419 | /* Doesn't seem to be any great value in errdetail here */ |
420 | return false; |
421 | } |
422 | |
423 | if (!pg_tz_acceptable(new_tz)) |
424 | { |
425 | GUC_check_errmsg("time zone \"%s\" appears to use leap seconds" , |
426 | *newval); |
427 | GUC_check_errdetail("PostgreSQL does not support leap seconds." ); |
428 | return false; |
429 | } |
430 | |
431 | /* |
432 | * Pass back data for assign_log_timezone to use |
433 | */ |
434 | *extra = malloc(sizeof(pg_tz *)); |
435 | if (!*extra) |
436 | return false; |
437 | *((pg_tz **) *extra) = new_tz; |
438 | |
439 | return true; |
440 | } |
441 | |
442 | /* |
443 | * assign_log_timezone: GUC assign_hook for log_timezone |
444 | */ |
445 | void |
446 | assign_log_timezone(const char *newval, void *) |
447 | { |
448 | log_timezone = *((pg_tz **) extra); |
449 | } |
450 | |
451 | /* |
452 | * show_log_timezone: GUC show_hook for log_timezone |
453 | */ |
454 | const char * |
455 | show_log_timezone(void) |
456 | { |
457 | const char *tzn; |
458 | |
459 | /* Always show the zone's canonical name */ |
460 | tzn = pg_get_timezone_name(log_timezone); |
461 | |
462 | if (tzn != NULL) |
463 | return tzn; |
464 | |
465 | return "unknown" ; |
466 | } |
467 | |
468 | |
469 | /* |
470 | * SET TRANSACTION READ ONLY and SET TRANSACTION READ WRITE |
471 | * |
472 | * We allow idempotent changes (r/w -> r/w and r/o -> r/o) at any time, and |
473 | * we also always allow changes from read-write to read-only. However, |
474 | * read-only may be changed to read-write only when in a top-level transaction |
475 | * that has not yet taken an initial snapshot. Can't do it in a hot standby, |
476 | * either. |
477 | * |
478 | * If we are not in a transaction at all, just allow the change; it means |
479 | * nothing since XactReadOnly will be reset by the next StartTransaction(). |
480 | * The IsTransactionState() test protects us against trying to check |
481 | * RecoveryInProgress() in contexts where shared memory is not accessible. |
482 | * (Similarly, if we're restoring state in a parallel worker, just allow |
483 | * the change.) |
484 | */ |
485 | bool |
486 | check_transaction_read_only(bool *newval, void **, GucSource source) |
487 | { |
488 | if (*newval == false && XactReadOnly && IsTransactionState() && !InitializingParallelWorker) |
489 | { |
490 | /* Can't go to r/w mode inside a r/o transaction */ |
491 | if (IsSubTransaction()) |
492 | { |
493 | GUC_check_errcode(ERRCODE_ACTIVE_SQL_TRANSACTION); |
494 | GUC_check_errmsg("cannot set transaction read-write mode inside a read-only transaction" ); |
495 | return false; |
496 | } |
497 | /* Top level transaction can't change to r/w after first snapshot. */ |
498 | if (FirstSnapshotSet) |
499 | { |
500 | GUC_check_errcode(ERRCODE_ACTIVE_SQL_TRANSACTION); |
501 | GUC_check_errmsg("transaction read-write mode must be set before any query" ); |
502 | return false; |
503 | } |
504 | /* Can't go to r/w mode while recovery is still active */ |
505 | if (RecoveryInProgress()) |
506 | { |
507 | GUC_check_errcode(ERRCODE_FEATURE_NOT_SUPPORTED); |
508 | GUC_check_errmsg("cannot set transaction read-write mode during recovery" ); |
509 | return false; |
510 | } |
511 | } |
512 | |
513 | return true; |
514 | } |
515 | |
516 | /* |
517 | * SET TRANSACTION ISOLATION LEVEL |
518 | * |
519 | * We allow idempotent changes at any time, but otherwise this can only be |
520 | * changed in a toplevel transaction that has not yet taken a snapshot. |
521 | * |
522 | * As in check_transaction_read_only, allow it if not inside a transaction. |
523 | */ |
524 | bool |
525 | check_XactIsoLevel(int *newval, void **, GucSource source) |
526 | { |
527 | int newXactIsoLevel = *newval; |
528 | |
529 | if (newXactIsoLevel != XactIsoLevel && IsTransactionState()) |
530 | { |
531 | if (FirstSnapshotSet) |
532 | { |
533 | GUC_check_errcode(ERRCODE_ACTIVE_SQL_TRANSACTION); |
534 | GUC_check_errmsg("SET TRANSACTION ISOLATION LEVEL must be called before any query" ); |
535 | return false; |
536 | } |
537 | /* We ignore a subtransaction setting it to the existing value. */ |
538 | if (IsSubTransaction()) |
539 | { |
540 | GUC_check_errcode(ERRCODE_ACTIVE_SQL_TRANSACTION); |
541 | GUC_check_errmsg("SET TRANSACTION ISOLATION LEVEL must not be called in a subtransaction" ); |
542 | return false; |
543 | } |
544 | /* Can't go to serializable mode while recovery is still active */ |
545 | if (newXactIsoLevel == XACT_SERIALIZABLE && RecoveryInProgress()) |
546 | { |
547 | GUC_check_errcode(ERRCODE_FEATURE_NOT_SUPPORTED); |
548 | GUC_check_errmsg("cannot use serializable mode in a hot standby" ); |
549 | GUC_check_errhint("You can use REPEATABLE READ instead." ); |
550 | return false; |
551 | } |
552 | } |
553 | |
554 | return true; |
555 | } |
556 | |
557 | /* |
558 | * SET TRANSACTION [NOT] DEFERRABLE |
559 | */ |
560 | |
561 | bool |
562 | check_transaction_deferrable(bool *newval, void **, GucSource source) |
563 | { |
564 | if (IsSubTransaction()) |
565 | { |
566 | GUC_check_errcode(ERRCODE_ACTIVE_SQL_TRANSACTION); |
567 | GUC_check_errmsg("SET TRANSACTION [NOT] DEFERRABLE cannot be called within a subtransaction" ); |
568 | return false; |
569 | } |
570 | if (FirstSnapshotSet) |
571 | { |
572 | GUC_check_errcode(ERRCODE_ACTIVE_SQL_TRANSACTION); |
573 | GUC_check_errmsg("SET TRANSACTION [NOT] DEFERRABLE must be called before any query" ); |
574 | return false; |
575 | } |
576 | |
577 | return true; |
578 | } |
579 | |
580 | /* |
581 | * Random number seed |
582 | * |
583 | * We can't roll back the random sequence on error, and we don't want |
584 | * config file reloads to affect it, so we only want interactive SET SEED |
585 | * commands to set it. We use the "extra" storage to ensure that rollbacks |
586 | * don't try to do the operation again. |
587 | */ |
588 | |
589 | bool |
590 | check_random_seed(double *newval, void **, GucSource source) |
591 | { |
592 | *extra = malloc(sizeof(int)); |
593 | if (!*extra) |
594 | return false; |
595 | /* Arm the assign only if source of value is an interactive SET */ |
596 | *((int *) *extra) = (source >= PGC_S_INTERACTIVE); |
597 | |
598 | return true; |
599 | } |
600 | |
601 | void |
602 | assign_random_seed(double newval, void *) |
603 | { |
604 | /* We'll do this at most once for any setting of the GUC variable */ |
605 | if (*((int *) extra)) |
606 | DirectFunctionCall1(setseed, Float8GetDatum(newval)); |
607 | *((int *) extra) = 0; |
608 | } |
609 | |
610 | const char * |
611 | show_random_seed(void) |
612 | { |
613 | return "unavailable" ; |
614 | } |
615 | |
616 | |
617 | /* |
618 | * SET CLIENT_ENCODING |
619 | */ |
620 | |
621 | bool |
622 | check_client_encoding(char **newval, void **, GucSource source) |
623 | { |
624 | int encoding; |
625 | const char *canonical_name; |
626 | |
627 | /* Look up the encoding by name */ |
628 | encoding = pg_valid_client_encoding(*newval); |
629 | if (encoding < 0) |
630 | return false; |
631 | |
632 | /* Get the canonical name (no aliases, uniform case) */ |
633 | canonical_name = pg_encoding_to_char(encoding); |
634 | |
635 | /* |
636 | * If we are not within a transaction then PrepareClientEncoding will not |
637 | * be able to look up the necessary conversion procs. If we are still |
638 | * starting up, it will return "OK" anyway, and InitializeClientEncoding |
639 | * will fix things once initialization is far enough along. After |
640 | * startup, we'll fail. This would only happen if someone tries to change |
641 | * client_encoding in postgresql.conf and then SIGHUP existing sessions. |
642 | * It seems like a bad idea for client_encoding to change that way anyhow, |
643 | * so we don't go out of our way to support it. |
644 | * |
645 | * Note: in the postmaster, or any other process that never calls |
646 | * InitializeClientEncoding, PrepareClientEncoding will always succeed, |
647 | * and so will SetClientEncoding; but they won't do anything, which is OK. |
648 | */ |
649 | if (PrepareClientEncoding(encoding) < 0) |
650 | { |
651 | if (IsTransactionState()) |
652 | { |
653 | /* Must be a genuine no-such-conversion problem */ |
654 | GUC_check_errcode(ERRCODE_FEATURE_NOT_SUPPORTED); |
655 | GUC_check_errdetail("Conversion between %s and %s is not supported." , |
656 | canonical_name, |
657 | GetDatabaseEncodingName()); |
658 | } |
659 | else |
660 | { |
661 | /* Provide a useful complaint */ |
662 | GUC_check_errdetail("Cannot change \"client_encoding\" now." ); |
663 | } |
664 | return false; |
665 | } |
666 | |
667 | /* |
668 | * Replace the user-supplied string with the encoding's canonical name. |
669 | * This gets rid of aliases and case-folding variations. |
670 | * |
671 | * XXX Although canonicalizing seems like a good idea in the abstract, it |
672 | * breaks pre-9.1 JDBC drivers, which expect that if they send "UNICODE" |
673 | * as the client_encoding setting then it will read back the same way. As |
674 | * a workaround, don't replace the string if it's "UNICODE". Remove that |
675 | * hack when pre-9.1 JDBC drivers are no longer in use. |
676 | */ |
677 | if (strcmp(*newval, canonical_name) != 0 && |
678 | strcmp(*newval, "UNICODE" ) != 0) |
679 | { |
680 | free(*newval); |
681 | *newval = strdup(canonical_name); |
682 | if (!*newval) |
683 | return false; |
684 | } |
685 | |
686 | /* |
687 | * Save the encoding's ID in *extra, for use by assign_client_encoding. |
688 | */ |
689 | *extra = malloc(sizeof(int)); |
690 | if (!*extra) |
691 | return false; |
692 | *((int *) *extra) = encoding; |
693 | |
694 | return true; |
695 | } |
696 | |
697 | void |
698 | assign_client_encoding(const char *newval, void *) |
699 | { |
700 | int encoding = *((int *) extra); |
701 | |
702 | /* |
703 | * Parallel workers send data to the leader, not the client. They always |
704 | * send data using the database encoding. |
705 | */ |
706 | if (IsParallelWorker()) |
707 | { |
708 | /* |
709 | * During parallel worker startup, we want to accept the leader's |
710 | * client_encoding setting so that anyone who looks at the value in |
711 | * the worker sees the same value that they would see in the leader. |
712 | */ |
713 | if (InitializingParallelWorker) |
714 | return; |
715 | |
716 | /* |
717 | * A change other than during startup, for example due to a SET clause |
718 | * attached to a function definition, should be rejected, as there is |
719 | * nothing we can do inside the worker to make it take effect. |
720 | */ |
721 | ereport(ERROR, |
722 | (errcode(ERRCODE_INVALID_TRANSACTION_STATE), |
723 | errmsg("cannot change client_encoding during a parallel operation" ))); |
724 | } |
725 | |
726 | /* We do not expect an error if PrepareClientEncoding succeeded */ |
727 | if (SetClientEncoding(encoding) < 0) |
728 | elog(LOG, "SetClientEncoding(%d) failed" , encoding); |
729 | } |
730 | |
731 | |
732 | /* |
733 | * SET SESSION AUTHORIZATION |
734 | */ |
735 | |
736 | typedef struct |
737 | { |
738 | /* This is the "extra" state for both SESSION AUTHORIZATION and ROLE */ |
739 | Oid roleid; |
740 | bool is_superuser; |
741 | } ; |
742 | |
743 | bool |
744 | check_session_authorization(char **newval, void **, GucSource source) |
745 | { |
746 | HeapTuple roleTup; |
747 | Form_pg_authid roleform; |
748 | Oid roleid; |
749 | bool is_superuser; |
750 | role_auth_extra *; |
751 | |
752 | /* Do nothing for the boot_val default of NULL */ |
753 | if (*newval == NULL) |
754 | return true; |
755 | |
756 | if (!IsTransactionState()) |
757 | { |
758 | /* |
759 | * Can't do catalog lookups, so fail. The result of this is that |
760 | * session_authorization cannot be set in postgresql.conf, which seems |
761 | * like a good thing anyway, so we don't work hard to avoid it. |
762 | */ |
763 | return false; |
764 | } |
765 | |
766 | /* Look up the username */ |
767 | roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(*newval)); |
768 | if (!HeapTupleIsValid(roleTup)) |
769 | { |
770 | GUC_check_errmsg("role \"%s\" does not exist" , *newval); |
771 | return false; |
772 | } |
773 | |
774 | roleform = (Form_pg_authid) GETSTRUCT(roleTup); |
775 | roleid = roleform->oid; |
776 | is_superuser = roleform->rolsuper; |
777 | |
778 | ReleaseSysCache(roleTup); |
779 | |
780 | /* Set up "extra" struct for assign_session_authorization to use */ |
781 | myextra = (role_auth_extra *) malloc(sizeof(role_auth_extra)); |
782 | if (!myextra) |
783 | return false; |
784 | myextra->roleid = roleid; |
785 | myextra->is_superuser = is_superuser; |
786 | *extra = (void *) myextra; |
787 | |
788 | return true; |
789 | } |
790 | |
791 | void |
792 | assign_session_authorization(const char *newval, void *) |
793 | { |
794 | role_auth_extra * = (role_auth_extra *) extra; |
795 | |
796 | /* Do nothing for the boot_val default of NULL */ |
797 | if (!myextra) |
798 | return; |
799 | |
800 | SetSessionAuthorization(myextra->roleid, myextra->is_superuser); |
801 | } |
802 | |
803 | |
804 | /* |
805 | * SET ROLE |
806 | * |
807 | * The SQL spec requires "SET ROLE NONE" to unset the role, so we hardwire |
808 | * a translation of "none" to InvalidOid. Otherwise this is much like |
809 | * SET SESSION AUTHORIZATION. |
810 | */ |
811 | extern char *role_string; /* in guc.c */ |
812 | |
813 | bool |
814 | check_role(char **newval, void **, GucSource source) |
815 | { |
816 | HeapTuple roleTup; |
817 | Oid roleid; |
818 | bool is_superuser; |
819 | role_auth_extra *; |
820 | Form_pg_authid roleform; |
821 | |
822 | if (strcmp(*newval, "none" ) == 0) |
823 | { |
824 | /* hardwired translation */ |
825 | roleid = InvalidOid; |
826 | is_superuser = false; |
827 | } |
828 | else |
829 | { |
830 | if (!IsTransactionState()) |
831 | { |
832 | /* |
833 | * Can't do catalog lookups, so fail. The result of this is that |
834 | * role cannot be set in postgresql.conf, which seems like a good |
835 | * thing anyway, so we don't work hard to avoid it. |
836 | */ |
837 | return false; |
838 | } |
839 | |
840 | /* Look up the username */ |
841 | roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(*newval)); |
842 | if (!HeapTupleIsValid(roleTup)) |
843 | { |
844 | GUC_check_errmsg("role \"%s\" does not exist" , *newval); |
845 | return false; |
846 | } |
847 | |
848 | roleform = (Form_pg_authid) GETSTRUCT(roleTup); |
849 | roleid = roleform->oid; |
850 | is_superuser = roleform->rolsuper; |
851 | |
852 | ReleaseSysCache(roleTup); |
853 | |
854 | /* |
855 | * Verify that session user is allowed to become this role, but skip |
856 | * this in parallel mode, where we must blindly recreate the parallel |
857 | * leader's state. |
858 | */ |
859 | if (!InitializingParallelWorker && |
860 | !is_member_of_role(GetSessionUserId(), roleid)) |
861 | { |
862 | GUC_check_errcode(ERRCODE_INSUFFICIENT_PRIVILEGE); |
863 | GUC_check_errmsg("permission denied to set role \"%s\"" , |
864 | *newval); |
865 | return false; |
866 | } |
867 | } |
868 | |
869 | /* Set up "extra" struct for assign_role to use */ |
870 | myextra = (role_auth_extra *) malloc(sizeof(role_auth_extra)); |
871 | if (!myextra) |
872 | return false; |
873 | myextra->roleid = roleid; |
874 | myextra->is_superuser = is_superuser; |
875 | *extra = (void *) myextra; |
876 | |
877 | return true; |
878 | } |
879 | |
880 | void |
881 | assign_role(const char *newval, void *) |
882 | { |
883 | role_auth_extra * = (role_auth_extra *) extra; |
884 | |
885 | SetCurrentRoleId(myextra->roleid, myextra->is_superuser); |
886 | } |
887 | |
888 | const char * |
889 | show_role(void) |
890 | { |
891 | /* |
892 | * Check whether SET ROLE is active; if not return "none". This is a |
893 | * kluge to deal with the fact that SET SESSION AUTHORIZATION logically |
894 | * resets SET ROLE to NONE, but we cannot set the GUC role variable from |
895 | * assign_session_authorization (because we haven't got enough info to |
896 | * call set_config_option). |
897 | */ |
898 | if (!OidIsValid(GetCurrentRoleId())) |
899 | return "none" ; |
900 | |
901 | /* Otherwise we can just use the GUC string */ |
902 | return role_string ? role_string : "none" ; |
903 | } |
904 | |