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