/src/postgres/src/backend/utils/misc/guc_funcs.c
Line | Count | Source |
1 | | /*-------------------------------------------------------------------- |
2 | | * |
3 | | * guc_funcs.c |
4 | | * |
5 | | * SQL commands and SQL-accessible functions related to GUC variables. |
6 | | * |
7 | | * |
8 | | * Copyright (c) 2000-2026, PostgreSQL Global Development Group |
9 | | * Written by Peter Eisentraut <peter_e@gmx.net>. |
10 | | * |
11 | | * IDENTIFICATION |
12 | | * src/backend/utils/misc/guc_funcs.c |
13 | | * |
14 | | *-------------------------------------------------------------------- |
15 | | */ |
16 | | #include "postgres.h" |
17 | | |
18 | | #include <sys/stat.h> |
19 | | #include <unistd.h> |
20 | | |
21 | | #include "access/xact.h" |
22 | | #include "catalog/objectaccess.h" |
23 | | #include "catalog/pg_authid.h" |
24 | | #include "catalog/pg_parameter_acl.h" |
25 | | #include "catalog/pg_type_d.h" |
26 | | #include "funcapi.h" |
27 | | #include "guc_internal.h" |
28 | | #include "miscadmin.h" |
29 | | #include "parser/parse_type.h" |
30 | | #include "utils/acl.h" |
31 | | #include "utils/builtins.h" |
32 | | #include "utils/guc_tables.h" |
33 | | #include "utils/snapmgr.h" |
34 | | #include "utils/tuplestore.h" |
35 | | |
36 | | static char *flatten_set_variable_args(const char *name, List *args); |
37 | | static void ShowGUCConfigOption(const char *name, DestReceiver *dest); |
38 | | static void ShowAllGUCConfig(DestReceiver *dest); |
39 | | |
40 | | |
41 | | /* |
42 | | * SET command |
43 | | */ |
44 | | void |
45 | | ExecSetVariableStmt(VariableSetStmt *stmt, bool isTopLevel) |
46 | 0 | { |
47 | 0 | GucAction action = stmt->is_local ? GUC_ACTION_LOCAL : GUC_ACTION_SET; |
48 | | |
49 | | /* |
50 | | * Workers synchronize these parameters at the start of the parallel |
51 | | * operation; then, we block SET during the operation. |
52 | | */ |
53 | 0 | if (IsInParallelMode()) |
54 | 0 | ereport(ERROR, |
55 | 0 | (errcode(ERRCODE_INVALID_TRANSACTION_STATE), |
56 | 0 | errmsg("cannot set parameters during a parallel operation"))); |
57 | | |
58 | 0 | switch (stmt->kind) |
59 | 0 | { |
60 | 0 | case VAR_SET_VALUE: |
61 | 0 | case VAR_SET_CURRENT: |
62 | 0 | if (stmt->is_local) |
63 | 0 | WarnNoTransactionBlock(isTopLevel, "SET LOCAL"); |
64 | 0 | (void) set_config_option(stmt->name, |
65 | 0 | ExtractSetVariableArgs(stmt), |
66 | 0 | (superuser() ? PGC_SUSET : PGC_USERSET), |
67 | 0 | PGC_S_SESSION, |
68 | 0 | action, true, 0, false); |
69 | 0 | break; |
70 | 0 | case VAR_SET_MULTI: |
71 | | |
72 | | /* |
73 | | * Special-case SQL syntaxes. The TRANSACTION and SESSION |
74 | | * CHARACTERISTICS cases effectively set more than one variable |
75 | | * per statement. TRANSACTION SNAPSHOT only takes one argument, |
76 | | * but we put it here anyway since it's a special case and not |
77 | | * related to any GUC variable. |
78 | | */ |
79 | 0 | if (strcmp(stmt->name, "TRANSACTION") == 0) |
80 | 0 | { |
81 | 0 | ListCell *head; |
82 | |
|
83 | 0 | WarnNoTransactionBlock(isTopLevel, "SET TRANSACTION"); |
84 | |
|
85 | 0 | foreach(head, stmt->args) |
86 | 0 | { |
87 | 0 | DefElem *item = (DefElem *) lfirst(head); |
88 | |
|
89 | 0 | if (strcmp(item->defname, "transaction_isolation") == 0) |
90 | 0 | SetPGVariable("transaction_isolation", |
91 | 0 | list_make1(item->arg), stmt->is_local); |
92 | 0 | else if (strcmp(item->defname, "transaction_read_only") == 0) |
93 | 0 | SetPGVariable("transaction_read_only", |
94 | 0 | list_make1(item->arg), stmt->is_local); |
95 | 0 | else if (strcmp(item->defname, "transaction_deferrable") == 0) |
96 | 0 | SetPGVariable("transaction_deferrable", |
97 | 0 | list_make1(item->arg), stmt->is_local); |
98 | 0 | else |
99 | 0 | elog(ERROR, "unexpected SET TRANSACTION element: %s", |
100 | 0 | item->defname); |
101 | 0 | } |
102 | 0 | } |
103 | 0 | else if (strcmp(stmt->name, "SESSION CHARACTERISTICS") == 0) |
104 | 0 | { |
105 | 0 | ListCell *head; |
106 | |
|
107 | 0 | foreach(head, stmt->args) |
108 | 0 | { |
109 | 0 | DefElem *item = (DefElem *) lfirst(head); |
110 | |
|
111 | 0 | if (strcmp(item->defname, "transaction_isolation") == 0) |
112 | 0 | SetPGVariable("default_transaction_isolation", |
113 | 0 | list_make1(item->arg), stmt->is_local); |
114 | 0 | else if (strcmp(item->defname, "transaction_read_only") == 0) |
115 | 0 | SetPGVariable("default_transaction_read_only", |
116 | 0 | list_make1(item->arg), stmt->is_local); |
117 | 0 | else if (strcmp(item->defname, "transaction_deferrable") == 0) |
118 | 0 | SetPGVariable("default_transaction_deferrable", |
119 | 0 | list_make1(item->arg), stmt->is_local); |
120 | 0 | else |
121 | 0 | elog(ERROR, "unexpected SET SESSION element: %s", |
122 | 0 | item->defname); |
123 | 0 | } |
124 | 0 | } |
125 | 0 | else if (strcmp(stmt->name, "TRANSACTION SNAPSHOT") == 0) |
126 | 0 | { |
127 | 0 | A_Const *con = linitial_node(A_Const, stmt->args); |
128 | |
|
129 | 0 | if (stmt->is_local) |
130 | 0 | ereport(ERROR, |
131 | 0 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
132 | 0 | errmsg("SET LOCAL TRANSACTION SNAPSHOT is not implemented"))); |
133 | | |
134 | 0 | WarnNoTransactionBlock(isTopLevel, "SET TRANSACTION"); |
135 | 0 | ImportSnapshot(strVal(&con->val)); |
136 | 0 | } |
137 | 0 | else |
138 | 0 | elog(ERROR, "unexpected SET MULTI element: %s", |
139 | 0 | stmt->name); |
140 | 0 | break; |
141 | 0 | case VAR_SET_DEFAULT: |
142 | 0 | if (stmt->is_local) |
143 | 0 | WarnNoTransactionBlock(isTopLevel, "SET LOCAL"); |
144 | 0 | pg_fallthrough; |
145 | 0 | case VAR_RESET: |
146 | 0 | (void) set_config_option(stmt->name, |
147 | 0 | NULL, |
148 | 0 | (superuser() ? PGC_SUSET : PGC_USERSET), |
149 | 0 | PGC_S_SESSION, |
150 | 0 | action, true, 0, false); |
151 | 0 | break; |
152 | 0 | case VAR_RESET_ALL: |
153 | 0 | ResetAllOptions(); |
154 | 0 | break; |
155 | 0 | } |
156 | | |
157 | | /* Invoke the post-alter hook for setting this GUC variable, by name. */ |
158 | 0 | InvokeObjectPostAlterHookArgStr(ParameterAclRelationId, stmt->name, |
159 | 0 | ACL_SET, stmt->kind, false); |
160 | 0 | } |
161 | | |
162 | | /* |
163 | | * Get the value to assign for a VariableSetStmt, or NULL if it's RESET. |
164 | | * The result is palloc'd. |
165 | | * |
166 | | * This is exported for use by actions such as ALTER ROLE SET. |
167 | | */ |
168 | | char * |
169 | | ExtractSetVariableArgs(VariableSetStmt *stmt) |
170 | 0 | { |
171 | 0 | switch (stmt->kind) |
172 | 0 | { |
173 | 0 | case VAR_SET_VALUE: |
174 | 0 | return flatten_set_variable_args(stmt->name, stmt->args); |
175 | 0 | case VAR_SET_CURRENT: |
176 | 0 | return GetConfigOptionByName(stmt->name, NULL, false); |
177 | 0 | default: |
178 | 0 | return NULL; |
179 | 0 | } |
180 | 0 | } |
181 | | |
182 | | /* |
183 | | * flatten_set_variable_args |
184 | | * Given a parsenode List as emitted by the grammar for SET, |
185 | | * convert to the flat string representation used by GUC. |
186 | | * |
187 | | * We need to be told the name of the variable the args are for, because |
188 | | * the flattening rules vary (ugh). |
189 | | * |
190 | | * The result is NULL if args is NIL (i.e., SET ... TO DEFAULT), otherwise |
191 | | * a palloc'd string. |
192 | | */ |
193 | | static char * |
194 | | flatten_set_variable_args(const char *name, List *args) |
195 | 0 | { |
196 | 0 | struct config_generic *record; |
197 | 0 | int flags; |
198 | 0 | StringInfoData buf; |
199 | 0 | ListCell *l; |
200 | | |
201 | | /* Fast path if just DEFAULT */ |
202 | 0 | if (args == NIL) |
203 | 0 | return NULL; |
204 | | |
205 | | /* |
206 | | * Get flags for the variable; if it's not known, use default flags. |
207 | | * (Caller might throw error later, but not our business to do so here.) |
208 | | */ |
209 | 0 | record = find_option(name, false, true, WARNING); |
210 | 0 | if (record) |
211 | 0 | flags = record->flags; |
212 | 0 | else |
213 | 0 | flags = 0; |
214 | | |
215 | | /* |
216 | | * Handle special cases for list input. |
217 | | */ |
218 | 0 | if (flags & GUC_LIST_INPUT) |
219 | 0 | { |
220 | | /* NULL represents an empty list. */ |
221 | 0 | if (list_length(args) == 1) |
222 | 0 | { |
223 | 0 | Node *arg = (Node *) linitial(args); |
224 | |
|
225 | 0 | if (IsA(arg, A_Const) && |
226 | 0 | ((A_Const *) arg)->isnull) |
227 | 0 | return pstrdup(""); |
228 | 0 | } |
229 | 0 | } |
230 | 0 | else |
231 | 0 | { |
232 | | /* Complain if list input and non-list variable. */ |
233 | 0 | if (list_length(args) != 1) |
234 | 0 | ereport(ERROR, |
235 | 0 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
236 | 0 | errmsg("SET %s takes only one argument", name))); |
237 | 0 | } |
238 | | |
239 | 0 | initStringInfo(&buf); |
240 | | |
241 | | /* |
242 | | * Each list member may be a plain A_Const node, or an A_Const within a |
243 | | * TypeCast; the latter case is supported only for ConstInterval arguments |
244 | | * (for SET TIME ZONE). |
245 | | */ |
246 | 0 | foreach(l, args) |
247 | 0 | { |
248 | 0 | Node *arg = (Node *) lfirst(l); |
249 | 0 | char *val; |
250 | 0 | TypeName *typeName = NULL; |
251 | 0 | A_Const *con; |
252 | |
|
253 | 0 | if (l != list_head(args)) |
254 | 0 | appendStringInfoString(&buf, ", "); |
255 | |
|
256 | 0 | if (IsA(arg, TypeCast)) |
257 | 0 | { |
258 | 0 | TypeCast *tc = (TypeCast *) arg; |
259 | |
|
260 | 0 | arg = tc->arg; |
261 | 0 | typeName = tc->typeName; |
262 | 0 | } |
263 | |
|
264 | 0 | if (!IsA(arg, A_Const)) |
265 | 0 | elog(ERROR, "unrecognized node type: %d", (int) nodeTag(arg)); |
266 | 0 | con = (A_Const *) arg; |
267 | | |
268 | | /* Complain if NULL is used with a non-list variable. */ |
269 | 0 | if (con->isnull) |
270 | 0 | ereport(ERROR, |
271 | 0 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
272 | 0 | errmsg("NULL is an invalid value for %s", name))); |
273 | | |
274 | 0 | switch (nodeTag(&con->val)) |
275 | 0 | { |
276 | 0 | case T_Integer: |
277 | 0 | appendStringInfo(&buf, "%d", intVal(&con->val)); |
278 | 0 | break; |
279 | 0 | case T_Float: |
280 | | /* represented as a string, so just copy it */ |
281 | 0 | appendStringInfoString(&buf, castNode(Float, &con->val)->fval); |
282 | 0 | break; |
283 | 0 | case T_String: |
284 | 0 | val = strVal(&con->val); |
285 | 0 | if (typeName != NULL) |
286 | 0 | { |
287 | | /* |
288 | | * Must be a ConstInterval argument for TIME ZONE. Coerce |
289 | | * to interval and back to normalize the value and account |
290 | | * for any typmod. |
291 | | */ |
292 | 0 | Oid typoid; |
293 | 0 | int32 typmod; |
294 | 0 | Datum interval; |
295 | 0 | char *intervalout; |
296 | | |
297 | | /* gram.y ensures this is only reachable for TIME ZONE */ |
298 | 0 | Assert(!(flags & GUC_LIST_QUOTE)); |
299 | |
|
300 | 0 | typenameTypeIdAndMod(NULL, typeName, &typoid, &typmod); |
301 | 0 | Assert(typoid == INTERVALOID); |
302 | |
|
303 | 0 | interval = |
304 | 0 | DirectFunctionCall3(interval_in, |
305 | 0 | CStringGetDatum(val), |
306 | 0 | ObjectIdGetDatum(InvalidOid), |
307 | 0 | Int32GetDatum(typmod)); |
308 | |
|
309 | 0 | intervalout = |
310 | 0 | DatumGetCString(DirectFunctionCall1(interval_out, |
311 | 0 | interval)); |
312 | 0 | appendStringInfo(&buf, "INTERVAL '%s'", intervalout); |
313 | 0 | } |
314 | 0 | else |
315 | 0 | { |
316 | | /* |
317 | | * Plain string literal or identifier. For quote mode, |
318 | | * quote it if it's not a vanilla identifier. |
319 | | */ |
320 | 0 | if (flags & GUC_LIST_QUOTE) |
321 | 0 | appendStringInfoString(&buf, quote_identifier(val)); |
322 | 0 | else |
323 | 0 | appendStringInfoString(&buf, val); |
324 | 0 | } |
325 | 0 | break; |
326 | 0 | default: |
327 | 0 | elog(ERROR, "unrecognized node type: %d", |
328 | 0 | (int) nodeTag(&con->val)); |
329 | 0 | break; |
330 | 0 | } |
331 | 0 | } |
332 | | |
333 | 0 | return buf.data; |
334 | 0 | } |
335 | | |
336 | | /* |
337 | | * SetPGVariable - SET command exported as an easily-C-callable function. |
338 | | * |
339 | | * This provides access to SET TO value, as well as SET TO DEFAULT (expressed |
340 | | * by passing args == NIL), but not SET FROM CURRENT functionality. |
341 | | */ |
342 | | void |
343 | | SetPGVariable(const char *name, List *args, bool is_local) |
344 | 0 | { |
345 | 0 | char *argstring = flatten_set_variable_args(name, args); |
346 | | |
347 | | /* Note SET DEFAULT (argstring == NULL) is equivalent to RESET */ |
348 | 0 | (void) set_config_option(name, |
349 | 0 | argstring, |
350 | 0 | (superuser() ? PGC_SUSET : PGC_USERSET), |
351 | 0 | PGC_S_SESSION, |
352 | 0 | is_local ? GUC_ACTION_LOCAL : GUC_ACTION_SET, |
353 | 0 | true, 0, false); |
354 | 0 | } |
355 | | |
356 | | /* |
357 | | * SET command wrapped as a SQL callable function. |
358 | | */ |
359 | | Datum |
360 | | set_config_by_name(PG_FUNCTION_ARGS) |
361 | 0 | { |
362 | 0 | char *name; |
363 | 0 | char *value; |
364 | 0 | char *new_value; |
365 | 0 | bool is_local; |
366 | |
|
367 | 0 | if (PG_ARGISNULL(0)) |
368 | 0 | ereport(ERROR, |
369 | 0 | (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), |
370 | 0 | errmsg("SET requires parameter name"))); |
371 | | |
372 | | /* Get the GUC variable name */ |
373 | 0 | name = TextDatumGetCString(PG_GETARG_DATUM(0)); |
374 | | |
375 | | /* Get the desired value or set to NULL for a reset request */ |
376 | 0 | if (PG_ARGISNULL(1)) |
377 | 0 | value = NULL; |
378 | 0 | else |
379 | 0 | value = TextDatumGetCString(PG_GETARG_DATUM(1)); |
380 | | |
381 | | /* |
382 | | * Get the desired state of is_local. Default to false if provided value |
383 | | * is NULL |
384 | | */ |
385 | 0 | if (PG_ARGISNULL(2)) |
386 | 0 | is_local = false; |
387 | 0 | else |
388 | 0 | is_local = PG_GETARG_BOOL(2); |
389 | | |
390 | | /* Note SET DEFAULT (argstring == NULL) is equivalent to RESET */ |
391 | 0 | (void) set_config_option(name, |
392 | 0 | value, |
393 | 0 | (superuser() ? PGC_SUSET : PGC_USERSET), |
394 | 0 | PGC_S_SESSION, |
395 | 0 | is_local ? GUC_ACTION_LOCAL : GUC_ACTION_SET, |
396 | 0 | true, 0, false); |
397 | | |
398 | | /* get the new current value */ |
399 | 0 | new_value = GetConfigOptionByName(name, NULL, false); |
400 | | |
401 | | /* Convert return string to text */ |
402 | 0 | PG_RETURN_TEXT_P(cstring_to_text(new_value)); |
403 | 0 | } |
404 | | |
405 | | |
406 | | /* |
407 | | * SHOW command |
408 | | */ |
409 | | void |
410 | | GetPGVariable(const char *name, DestReceiver *dest) |
411 | 0 | { |
412 | 0 | if (guc_name_compare(name, "all") == 0) |
413 | 0 | ShowAllGUCConfig(dest); |
414 | 0 | else |
415 | 0 | ShowGUCConfigOption(name, dest); |
416 | 0 | } |
417 | | |
418 | | /* |
419 | | * Get a tuple descriptor for SHOW's result |
420 | | */ |
421 | | TupleDesc |
422 | | GetPGVariableResultDesc(const char *name) |
423 | 0 | { |
424 | 0 | TupleDesc tupdesc; |
425 | |
|
426 | 0 | if (guc_name_compare(name, "all") == 0) |
427 | 0 | { |
428 | | /* need a tuple descriptor representing three TEXT columns */ |
429 | 0 | tupdesc = CreateTemplateTupleDesc(3); |
430 | 0 | TupleDescInitEntry(tupdesc, (AttrNumber) 1, "name", |
431 | 0 | TEXTOID, -1, 0); |
432 | 0 | TupleDescInitEntry(tupdesc, (AttrNumber) 2, "setting", |
433 | 0 | TEXTOID, -1, 0); |
434 | 0 | TupleDescInitEntry(tupdesc, (AttrNumber) 3, "description", |
435 | 0 | TEXTOID, -1, 0); |
436 | 0 | } |
437 | 0 | else |
438 | 0 | { |
439 | 0 | const char *varname; |
440 | | |
441 | | /* Get the canonical spelling of name */ |
442 | 0 | (void) GetConfigOptionByName(name, &varname, false); |
443 | | |
444 | | /* need a tuple descriptor representing a single TEXT column */ |
445 | 0 | tupdesc = CreateTemplateTupleDesc(1); |
446 | 0 | TupleDescInitEntry(tupdesc, (AttrNumber) 1, varname, |
447 | 0 | TEXTOID, -1, 0); |
448 | 0 | } |
449 | 0 | TupleDescFinalize(tupdesc); |
450 | 0 | return tupdesc; |
451 | 0 | } |
452 | | |
453 | | /* |
454 | | * SHOW one variable |
455 | | */ |
456 | | static void |
457 | | ShowGUCConfigOption(const char *name, DestReceiver *dest) |
458 | 0 | { |
459 | 0 | TupOutputState *tstate; |
460 | 0 | TupleDesc tupdesc; |
461 | 0 | const char *varname; |
462 | 0 | char *value; |
463 | | |
464 | | /* Get the value and canonical spelling of name */ |
465 | 0 | value = GetConfigOptionByName(name, &varname, false); |
466 | | |
467 | | /* need a tuple descriptor representing a single TEXT column */ |
468 | 0 | tupdesc = CreateTemplateTupleDesc(1); |
469 | 0 | TupleDescInitBuiltinEntry(tupdesc, (AttrNumber) 1, varname, |
470 | 0 | TEXTOID, -1, 0); |
471 | 0 | TupleDescFinalize(tupdesc); |
472 | | |
473 | | /* prepare for projection of tuples */ |
474 | 0 | tstate = begin_tup_output_tupdesc(dest, tupdesc, &TTSOpsVirtual); |
475 | | |
476 | | /* Send it */ |
477 | 0 | do_text_output_oneline(tstate, value); |
478 | |
|
479 | 0 | end_tup_output(tstate); |
480 | 0 | } |
481 | | |
482 | | /* |
483 | | * SHOW ALL command |
484 | | */ |
485 | | static void |
486 | | ShowAllGUCConfig(DestReceiver *dest) |
487 | 0 | { |
488 | 0 | struct config_generic **guc_vars; |
489 | 0 | int num_vars; |
490 | 0 | TupOutputState *tstate; |
491 | 0 | TupleDesc tupdesc; |
492 | 0 | Datum values[3]; |
493 | 0 | bool isnull[3] = {false, false, false}; |
494 | | |
495 | | /* collect the variables, in sorted order */ |
496 | 0 | guc_vars = get_guc_variables(&num_vars); |
497 | | |
498 | | /* need a tuple descriptor representing three TEXT columns */ |
499 | 0 | tupdesc = CreateTemplateTupleDesc(3); |
500 | 0 | TupleDescInitBuiltinEntry(tupdesc, (AttrNumber) 1, "name", |
501 | 0 | TEXTOID, -1, 0); |
502 | 0 | TupleDescInitBuiltinEntry(tupdesc, (AttrNumber) 2, "setting", |
503 | 0 | TEXTOID, -1, 0); |
504 | 0 | TupleDescInitBuiltinEntry(tupdesc, (AttrNumber) 3, "description", |
505 | 0 | TEXTOID, -1, 0); |
506 | 0 | TupleDescFinalize(tupdesc); |
507 | | |
508 | | /* prepare for projection of tuples */ |
509 | 0 | tstate = begin_tup_output_tupdesc(dest, tupdesc, &TTSOpsVirtual); |
510 | |
|
511 | 0 | for (int i = 0; i < num_vars; i++) |
512 | 0 | { |
513 | 0 | struct config_generic *conf = guc_vars[i]; |
514 | 0 | char *setting; |
515 | | |
516 | | /* skip if marked NO_SHOW_ALL */ |
517 | 0 | if (conf->flags & GUC_NO_SHOW_ALL) |
518 | 0 | continue; |
519 | | |
520 | | /* return only options visible to the current user */ |
521 | 0 | if (!ConfigOptionIsVisible(conf)) |
522 | 0 | continue; |
523 | | |
524 | | /* assign to the values array */ |
525 | 0 | values[0] = PointerGetDatum(cstring_to_text(conf->name)); |
526 | |
|
527 | 0 | setting = ShowGUCOption(conf, true); |
528 | 0 | if (setting) |
529 | 0 | { |
530 | 0 | values[1] = PointerGetDatum(cstring_to_text(setting)); |
531 | 0 | isnull[1] = false; |
532 | 0 | } |
533 | 0 | else |
534 | 0 | { |
535 | 0 | values[1] = PointerGetDatum(NULL); |
536 | 0 | isnull[1] = true; |
537 | 0 | } |
538 | |
|
539 | 0 | if (conf->short_desc) |
540 | 0 | { |
541 | 0 | values[2] = PointerGetDatum(cstring_to_text(conf->short_desc)); |
542 | 0 | isnull[2] = false; |
543 | 0 | } |
544 | 0 | else |
545 | 0 | { |
546 | 0 | values[2] = PointerGetDatum(NULL); |
547 | 0 | isnull[2] = true; |
548 | 0 | } |
549 | | |
550 | | /* send it to dest */ |
551 | 0 | do_tup_output(tstate, values, isnull); |
552 | | |
553 | | /* clean up */ |
554 | 0 | pfree(DatumGetPointer(values[0])); |
555 | 0 | if (setting) |
556 | 0 | { |
557 | 0 | pfree(setting); |
558 | 0 | pfree(DatumGetPointer(values[1])); |
559 | 0 | } |
560 | 0 | if (conf->short_desc) |
561 | 0 | pfree(DatumGetPointer(values[2])); |
562 | 0 | } |
563 | |
|
564 | 0 | end_tup_output(tstate); |
565 | 0 | } |
566 | | |
567 | | /* |
568 | | * Return some of the flags associated to the specified GUC in the shape of |
569 | | * a text array, and NULL if it does not exist. An empty array is returned |
570 | | * if the GUC exists without any meaningful flags to show. |
571 | | */ |
572 | | Datum |
573 | | pg_settings_get_flags(PG_FUNCTION_ARGS) |
574 | 0 | { |
575 | 0 | #define MAX_GUC_FLAGS 6 |
576 | 0 | char *varname = TextDatumGetCString(PG_GETARG_DATUM(0)); |
577 | 0 | struct config_generic *record; |
578 | 0 | int cnt = 0; |
579 | 0 | Datum flags[MAX_GUC_FLAGS]; |
580 | 0 | ArrayType *a; |
581 | |
|
582 | 0 | record = find_option(varname, false, true, ERROR); |
583 | | |
584 | | /* return NULL if no such variable */ |
585 | 0 | if (record == NULL) |
586 | 0 | PG_RETURN_NULL(); |
587 | | |
588 | 0 | if (record->flags & GUC_EXPLAIN) |
589 | 0 | flags[cnt++] = CStringGetTextDatum("EXPLAIN"); |
590 | 0 | if (record->flags & GUC_NO_RESET) |
591 | 0 | flags[cnt++] = CStringGetTextDatum("NO_RESET"); |
592 | 0 | if (record->flags & GUC_NO_RESET_ALL) |
593 | 0 | flags[cnt++] = CStringGetTextDatum("NO_RESET_ALL"); |
594 | 0 | if (record->flags & GUC_NO_SHOW_ALL) |
595 | 0 | flags[cnt++] = CStringGetTextDatum("NO_SHOW_ALL"); |
596 | 0 | if (record->flags & GUC_NOT_IN_SAMPLE) |
597 | 0 | flags[cnt++] = CStringGetTextDatum("NOT_IN_SAMPLE"); |
598 | 0 | if (record->flags & GUC_RUNTIME_COMPUTED) |
599 | 0 | flags[cnt++] = CStringGetTextDatum("RUNTIME_COMPUTED"); |
600 | |
|
601 | 0 | Assert(cnt <= MAX_GUC_FLAGS); |
602 | | |
603 | | /* Returns the record as Datum */ |
604 | 0 | a = construct_array_builtin(flags, cnt, TEXTOID); |
605 | 0 | PG_RETURN_ARRAYTYPE_P(a); |
606 | 0 | } |
607 | | |
608 | | /* |
609 | | * Return whether or not the GUC variable is visible to the current user. |
610 | | */ |
611 | | bool |
612 | | ConfigOptionIsVisible(const struct config_generic *conf) |
613 | 0 | { |
614 | 0 | if ((conf->flags & GUC_SUPERUSER_ONLY) && |
615 | 0 | !has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS)) |
616 | 0 | return false; |
617 | 0 | else |
618 | 0 | return true; |
619 | 0 | } |
620 | | |
621 | | /* |
622 | | * Extract fields to show in pg_settings for given variable. |
623 | | */ |
624 | | static void |
625 | | GetConfigOptionValues(const struct config_generic *conf, const char **values) |
626 | 0 | { |
627 | 0 | char buffer[256]; |
628 | | |
629 | | /* first get the generic attributes */ |
630 | | |
631 | | /* name */ |
632 | 0 | values[0] = conf->name; |
633 | | |
634 | | /* setting: use ShowGUCOption in order to avoid duplicating the logic */ |
635 | 0 | values[1] = ShowGUCOption(conf, false); |
636 | | |
637 | | /* unit, if any (NULL is fine) */ |
638 | 0 | values[2] = get_config_unit_name(conf->flags); |
639 | | |
640 | | /* group */ |
641 | 0 | values[3] = _(config_group_names[conf->group]); |
642 | | |
643 | | /* short_desc */ |
644 | 0 | values[4] = conf->short_desc != NULL ? _(conf->short_desc) : NULL; |
645 | | |
646 | | /* extra_desc */ |
647 | 0 | values[5] = conf->long_desc != NULL ? _(conf->long_desc) : NULL; |
648 | | |
649 | | /* context */ |
650 | 0 | values[6] = GucContext_Names[conf->context]; |
651 | | |
652 | | /* vartype */ |
653 | 0 | values[7] = config_type_names[conf->vartype]; |
654 | | |
655 | | /* source */ |
656 | 0 | values[8] = GucSource_Names[conf->source]; |
657 | | |
658 | | /* now get the type specific attributes */ |
659 | 0 | switch (conf->vartype) |
660 | 0 | { |
661 | 0 | case PGC_BOOL: |
662 | 0 | { |
663 | 0 | const struct config_bool *lconf = &conf->_bool; |
664 | | |
665 | | /* min_val */ |
666 | 0 | values[9] = NULL; |
667 | | |
668 | | /* max_val */ |
669 | 0 | values[10] = NULL; |
670 | | |
671 | | /* enumvals */ |
672 | 0 | values[11] = NULL; |
673 | | |
674 | | /* boot_val */ |
675 | 0 | values[12] = pstrdup(lconf->boot_val ? "on" : "off"); |
676 | | |
677 | | /* reset_val */ |
678 | 0 | values[13] = pstrdup(lconf->reset_val ? "on" : "off"); |
679 | 0 | } |
680 | 0 | break; |
681 | | |
682 | 0 | case PGC_INT: |
683 | 0 | { |
684 | 0 | const struct config_int *lconf = &conf->_int; |
685 | 0 | const char *fmt; |
686 | | |
687 | | /* choose display format */ |
688 | 0 | if (conf->flags & GUC_SHOW_IN_OCTAL) |
689 | 0 | fmt = "0%03o"; |
690 | 0 | else |
691 | 0 | fmt = "%d"; |
692 | | |
693 | | /* min_val */ |
694 | 0 | snprintf(buffer, sizeof(buffer), fmt, lconf->min); |
695 | 0 | values[9] = pstrdup(buffer); |
696 | | |
697 | | /* max_val */ |
698 | 0 | snprintf(buffer, sizeof(buffer), fmt, lconf->max); |
699 | 0 | values[10] = pstrdup(buffer); |
700 | | |
701 | | /* enumvals */ |
702 | 0 | values[11] = NULL; |
703 | | |
704 | | /* boot_val */ |
705 | 0 | snprintf(buffer, sizeof(buffer), fmt, lconf->boot_val); |
706 | 0 | values[12] = pstrdup(buffer); |
707 | | |
708 | | /* reset_val */ |
709 | 0 | snprintf(buffer, sizeof(buffer), fmt, lconf->reset_val); |
710 | 0 | values[13] = pstrdup(buffer); |
711 | 0 | } |
712 | 0 | break; |
713 | | |
714 | 0 | case PGC_REAL: |
715 | 0 | { |
716 | 0 | const struct config_real *lconf = &conf->_real; |
717 | | |
718 | | /* min_val */ |
719 | 0 | snprintf(buffer, sizeof(buffer), "%g", lconf->min); |
720 | 0 | values[9] = pstrdup(buffer); |
721 | | |
722 | | /* max_val */ |
723 | 0 | snprintf(buffer, sizeof(buffer), "%g", lconf->max); |
724 | 0 | values[10] = pstrdup(buffer); |
725 | | |
726 | | /* enumvals */ |
727 | 0 | values[11] = NULL; |
728 | | |
729 | | /* boot_val */ |
730 | 0 | snprintf(buffer, sizeof(buffer), "%g", lconf->boot_val); |
731 | 0 | values[12] = pstrdup(buffer); |
732 | | |
733 | | /* reset_val */ |
734 | 0 | snprintf(buffer, sizeof(buffer), "%g", lconf->reset_val); |
735 | 0 | values[13] = pstrdup(buffer); |
736 | 0 | } |
737 | 0 | break; |
738 | | |
739 | 0 | case PGC_STRING: |
740 | 0 | { |
741 | 0 | const struct config_string *lconf = &conf->_string; |
742 | | |
743 | | /* min_val */ |
744 | 0 | values[9] = NULL; |
745 | | |
746 | | /* max_val */ |
747 | 0 | values[10] = NULL; |
748 | | |
749 | | /* enumvals */ |
750 | 0 | values[11] = NULL; |
751 | | |
752 | | /* boot_val */ |
753 | 0 | if (lconf->boot_val == NULL) |
754 | 0 | values[12] = NULL; |
755 | 0 | else |
756 | 0 | values[12] = pstrdup(lconf->boot_val); |
757 | | |
758 | | /* reset_val */ |
759 | 0 | if (lconf->reset_val == NULL) |
760 | 0 | values[13] = NULL; |
761 | 0 | else |
762 | 0 | values[13] = pstrdup(lconf->reset_val); |
763 | 0 | } |
764 | 0 | break; |
765 | | |
766 | 0 | case PGC_ENUM: |
767 | 0 | { |
768 | 0 | const struct config_enum *lconf = &conf->_enum; |
769 | | |
770 | | /* min_val */ |
771 | 0 | values[9] = NULL; |
772 | | |
773 | | /* max_val */ |
774 | 0 | values[10] = NULL; |
775 | | |
776 | | /* enumvals */ |
777 | | |
778 | | /* |
779 | | * NOTE! enumvals with double quotes in them are not |
780 | | * supported! |
781 | | */ |
782 | 0 | values[11] = config_enum_get_options(lconf, |
783 | 0 | "{\"", "\"}", "\",\""); |
784 | | |
785 | | /* boot_val */ |
786 | 0 | values[12] = pstrdup(config_enum_lookup_by_value(conf, |
787 | 0 | lconf->boot_val)); |
788 | | |
789 | | /* reset_val */ |
790 | 0 | values[13] = pstrdup(config_enum_lookup_by_value(conf, |
791 | 0 | lconf->reset_val)); |
792 | 0 | } |
793 | 0 | break; |
794 | | |
795 | 0 | default: |
796 | 0 | { |
797 | | /* |
798 | | * should never get here, but in case we do, set 'em to NULL |
799 | | */ |
800 | | |
801 | | /* min_val */ |
802 | 0 | values[9] = NULL; |
803 | | |
804 | | /* max_val */ |
805 | 0 | values[10] = NULL; |
806 | | |
807 | | /* enumvals */ |
808 | 0 | values[11] = NULL; |
809 | | |
810 | | /* boot_val */ |
811 | 0 | values[12] = NULL; |
812 | | |
813 | | /* reset_val */ |
814 | 0 | values[13] = NULL; |
815 | 0 | } |
816 | 0 | break; |
817 | 0 | } |
818 | | |
819 | | /* |
820 | | * If the setting came from a config file, set the source location. For |
821 | | * security reasons, we don't show source file/line number for |
822 | | * insufficiently-privileged users. |
823 | | */ |
824 | 0 | if (conf->source == PGC_S_FILE && |
825 | 0 | has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS)) |
826 | 0 | { |
827 | 0 | values[14] = conf->sourcefile; |
828 | 0 | snprintf(buffer, sizeof(buffer), "%d", conf->sourceline); |
829 | 0 | values[15] = pstrdup(buffer); |
830 | 0 | } |
831 | 0 | else |
832 | 0 | { |
833 | 0 | values[14] = NULL; |
834 | 0 | values[15] = NULL; |
835 | 0 | } |
836 | |
|
837 | 0 | values[16] = (conf->status & GUC_PENDING_RESTART) ? "t" : "f"; |
838 | 0 | } |
839 | | |
840 | | /* |
841 | | * show_config_by_name - equiv to SHOW X command but implemented as |
842 | | * a function. |
843 | | */ |
844 | | Datum |
845 | | show_config_by_name(PG_FUNCTION_ARGS) |
846 | 0 | { |
847 | 0 | char *varname = TextDatumGetCString(PG_GETARG_DATUM(0)); |
848 | 0 | char *varval; |
849 | | |
850 | | /* Get the value */ |
851 | 0 | varval = GetConfigOptionByName(varname, NULL, false); |
852 | | |
853 | | /* Convert to text */ |
854 | 0 | PG_RETURN_TEXT_P(cstring_to_text(varval)); |
855 | 0 | } |
856 | | |
857 | | /* |
858 | | * show_config_by_name_missing_ok - equiv to SHOW X command but implemented as |
859 | | * a function. If X does not exist, suppress the error and just return NULL |
860 | | * if missing_ok is true. |
861 | | */ |
862 | | Datum |
863 | | show_config_by_name_missing_ok(PG_FUNCTION_ARGS) |
864 | 0 | { |
865 | 0 | char *varname = TextDatumGetCString(PG_GETARG_DATUM(0)); |
866 | 0 | bool missing_ok = PG_GETARG_BOOL(1); |
867 | 0 | char *varval; |
868 | | |
869 | | /* Get the value */ |
870 | 0 | varval = GetConfigOptionByName(varname, NULL, missing_ok); |
871 | | |
872 | | /* return NULL if no such variable */ |
873 | 0 | if (varval == NULL) |
874 | 0 | PG_RETURN_NULL(); |
875 | | |
876 | | /* Convert to text */ |
877 | 0 | PG_RETURN_TEXT_P(cstring_to_text(varval)); |
878 | 0 | } |
879 | | |
880 | | /* |
881 | | * show_all_settings - equiv to SHOW ALL command but implemented as |
882 | | * a Table Function. |
883 | | */ |
884 | 0 | #define NUM_PG_SETTINGS_ATTS 17 |
885 | | |
886 | | Datum |
887 | | show_all_settings(PG_FUNCTION_ARGS) |
888 | 0 | { |
889 | 0 | FuncCallContext *funcctx; |
890 | 0 | struct config_generic **guc_vars; |
891 | 0 | int num_vars; |
892 | 0 | TupleDesc tupdesc; |
893 | 0 | int call_cntr; |
894 | 0 | int max_calls; |
895 | 0 | AttInMetadata *attinmeta; |
896 | 0 | MemoryContext oldcontext; |
897 | | |
898 | | /* stuff done only on the first call of the function */ |
899 | 0 | if (SRF_IS_FIRSTCALL()) |
900 | 0 | { |
901 | | /* create a function context for cross-call persistence */ |
902 | 0 | funcctx = SRF_FIRSTCALL_INIT(); |
903 | | |
904 | | /* |
905 | | * switch to memory context appropriate for multiple function calls |
906 | | */ |
907 | 0 | oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); |
908 | | |
909 | | /* |
910 | | * need a tuple descriptor representing NUM_PG_SETTINGS_ATTS columns |
911 | | * of the appropriate types |
912 | | */ |
913 | 0 | tupdesc = CreateTemplateTupleDesc(NUM_PG_SETTINGS_ATTS); |
914 | 0 | TupleDescInitEntry(tupdesc, (AttrNumber) 1, "name", |
915 | 0 | TEXTOID, -1, 0); |
916 | 0 | TupleDescInitEntry(tupdesc, (AttrNumber) 2, "setting", |
917 | 0 | TEXTOID, -1, 0); |
918 | 0 | TupleDescInitEntry(tupdesc, (AttrNumber) 3, "unit", |
919 | 0 | TEXTOID, -1, 0); |
920 | 0 | TupleDescInitEntry(tupdesc, (AttrNumber) 4, "category", |
921 | 0 | TEXTOID, -1, 0); |
922 | 0 | TupleDescInitEntry(tupdesc, (AttrNumber) 5, "short_desc", |
923 | 0 | TEXTOID, -1, 0); |
924 | 0 | TupleDescInitEntry(tupdesc, (AttrNumber) 6, "extra_desc", |
925 | 0 | TEXTOID, -1, 0); |
926 | 0 | TupleDescInitEntry(tupdesc, (AttrNumber) 7, "context", |
927 | 0 | TEXTOID, -1, 0); |
928 | 0 | TupleDescInitEntry(tupdesc, (AttrNumber) 8, "vartype", |
929 | 0 | TEXTOID, -1, 0); |
930 | 0 | TupleDescInitEntry(tupdesc, (AttrNumber) 9, "source", |
931 | 0 | TEXTOID, -1, 0); |
932 | 0 | TupleDescInitEntry(tupdesc, (AttrNumber) 10, "min_val", |
933 | 0 | TEXTOID, -1, 0); |
934 | 0 | TupleDescInitEntry(tupdesc, (AttrNumber) 11, "max_val", |
935 | 0 | TEXTOID, -1, 0); |
936 | 0 | TupleDescInitEntry(tupdesc, (AttrNumber) 12, "enumvals", |
937 | 0 | TEXTARRAYOID, -1, 0); |
938 | 0 | TupleDescInitEntry(tupdesc, (AttrNumber) 13, "boot_val", |
939 | 0 | TEXTOID, -1, 0); |
940 | 0 | TupleDescInitEntry(tupdesc, (AttrNumber) 14, "reset_val", |
941 | 0 | TEXTOID, -1, 0); |
942 | 0 | TupleDescInitEntry(tupdesc, (AttrNumber) 15, "sourcefile", |
943 | 0 | TEXTOID, -1, 0); |
944 | 0 | TupleDescInitEntry(tupdesc, (AttrNumber) 16, "sourceline", |
945 | 0 | INT4OID, -1, 0); |
946 | 0 | TupleDescInitEntry(tupdesc, (AttrNumber) 17, "pending_restart", |
947 | 0 | BOOLOID, -1, 0); |
948 | |
|
949 | 0 | TupleDescFinalize(tupdesc); |
950 | | |
951 | | /* |
952 | | * Generate attribute metadata needed later to produce tuples from raw |
953 | | * C strings |
954 | | */ |
955 | 0 | attinmeta = TupleDescGetAttInMetadata(tupdesc); |
956 | 0 | funcctx->attinmeta = attinmeta; |
957 | | |
958 | | /* collect the variables, in sorted order */ |
959 | 0 | guc_vars = get_guc_variables(&num_vars); |
960 | | |
961 | | /* use user_fctx to remember the array location */ |
962 | 0 | funcctx->user_fctx = guc_vars; |
963 | | |
964 | | /* total number of tuples to be returned */ |
965 | 0 | funcctx->max_calls = num_vars; |
966 | |
|
967 | 0 | MemoryContextSwitchTo(oldcontext); |
968 | 0 | } |
969 | | |
970 | | /* stuff done on every call of the function */ |
971 | 0 | funcctx = SRF_PERCALL_SETUP(); |
972 | |
|
973 | 0 | guc_vars = (struct config_generic **) funcctx->user_fctx; |
974 | 0 | call_cntr = funcctx->call_cntr; |
975 | 0 | max_calls = funcctx->max_calls; |
976 | 0 | attinmeta = funcctx->attinmeta; |
977 | |
|
978 | 0 | while (call_cntr < max_calls) /* do when there is more left to send */ |
979 | 0 | { |
980 | 0 | struct config_generic *conf = guc_vars[call_cntr]; |
981 | 0 | char *values[NUM_PG_SETTINGS_ATTS]; |
982 | 0 | HeapTuple tuple; |
983 | 0 | Datum result; |
984 | | |
985 | | /* skip if marked NO_SHOW_ALL or if not visible to current user */ |
986 | 0 | if ((conf->flags & GUC_NO_SHOW_ALL) || |
987 | 0 | !ConfigOptionIsVisible(conf)) |
988 | 0 | { |
989 | 0 | call_cntr = ++funcctx->call_cntr; |
990 | 0 | continue; |
991 | 0 | } |
992 | | |
993 | | /* extract values for the current variable */ |
994 | 0 | GetConfigOptionValues(conf, (const char **) values); |
995 | | |
996 | | /* build a tuple */ |
997 | 0 | tuple = BuildTupleFromCStrings(attinmeta, values); |
998 | | |
999 | | /* make the tuple into a datum */ |
1000 | 0 | result = HeapTupleGetDatum(tuple); |
1001 | |
|
1002 | 0 | SRF_RETURN_NEXT(funcctx, result); |
1003 | 0 | } |
1004 | | |
1005 | | /* do when there is no more left */ |
1006 | 0 | SRF_RETURN_DONE(funcctx); |
1007 | 0 | } |
1008 | | |
1009 | | /* |
1010 | | * show_all_file_settings |
1011 | | * |
1012 | | * Returns a table of all parameter settings in all configuration files |
1013 | | * which includes the config file pathname, the line number, a sequence number |
1014 | | * indicating the order in which the settings were encountered, the parameter |
1015 | | * name and value, a bool showing if the value could be applied, and possibly |
1016 | | * an associated error message. (For problems such as syntax errors, the |
1017 | | * parameter name/value might be NULL.) |
1018 | | * |
1019 | | * Note: no filtering is done here, instead we depend on the GRANT system |
1020 | | * to prevent unprivileged users from accessing this function or the view |
1021 | | * built on top of it. |
1022 | | */ |
1023 | | Datum |
1024 | | show_all_file_settings(PG_FUNCTION_ARGS) |
1025 | 0 | { |
1026 | 0 | #define NUM_PG_FILE_SETTINGS_ATTS 7 |
1027 | 0 | ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; |
1028 | 0 | ConfigVariable *conf; |
1029 | | |
1030 | | /* Scan the config files using current context as workspace */ |
1031 | 0 | conf = ProcessConfigFileInternal(PGC_SIGHUP, false, DEBUG3); |
1032 | | |
1033 | | /* Build a tuplestore to return our results in */ |
1034 | 0 | InitMaterializedSRF(fcinfo, 0); |
1035 | | |
1036 | | /* Process the results and create a tuplestore */ |
1037 | 0 | for (int seqno = 1; conf != NULL; conf = conf->next, seqno++) |
1038 | 0 | { |
1039 | 0 | Datum values[NUM_PG_FILE_SETTINGS_ATTS]; |
1040 | 0 | bool nulls[NUM_PG_FILE_SETTINGS_ATTS]; |
1041 | |
|
1042 | 0 | memset(values, 0, sizeof(values)); |
1043 | 0 | memset(nulls, 0, sizeof(nulls)); |
1044 | | |
1045 | | /* sourcefile */ |
1046 | 0 | if (conf->filename) |
1047 | 0 | values[0] = PointerGetDatum(cstring_to_text(conf->filename)); |
1048 | 0 | else |
1049 | 0 | nulls[0] = true; |
1050 | | |
1051 | | /* sourceline (not meaningful if no sourcefile) */ |
1052 | 0 | if (conf->filename) |
1053 | 0 | values[1] = Int32GetDatum(conf->sourceline); |
1054 | 0 | else |
1055 | 0 | nulls[1] = true; |
1056 | | |
1057 | | /* seqno */ |
1058 | 0 | values[2] = Int32GetDatum(seqno); |
1059 | | |
1060 | | /* name */ |
1061 | 0 | if (conf->name) |
1062 | 0 | values[3] = PointerGetDatum(cstring_to_text(conf->name)); |
1063 | 0 | else |
1064 | 0 | nulls[3] = true; |
1065 | | |
1066 | | /* setting */ |
1067 | 0 | if (conf->value) |
1068 | 0 | values[4] = PointerGetDatum(cstring_to_text(conf->value)); |
1069 | 0 | else |
1070 | 0 | nulls[4] = true; |
1071 | | |
1072 | | /* applied */ |
1073 | 0 | values[5] = BoolGetDatum(conf->applied); |
1074 | | |
1075 | | /* error */ |
1076 | 0 | if (conf->errmsg) |
1077 | 0 | values[6] = PointerGetDatum(cstring_to_text(conf->errmsg)); |
1078 | 0 | else |
1079 | 0 | nulls[6] = true; |
1080 | | |
1081 | | /* shove row into tuplestore */ |
1082 | 0 | tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); |
1083 | 0 | } |
1084 | |
|
1085 | 0 | return (Datum) 0; |
1086 | 0 | } |