1058 lines
26 KiB
C
1058 lines
26 KiB
C
/*--------------------------------------------------------------------
|
|
*
|
|
* guc_funcs.c
|
|
*
|
|
* SQL commands and SQL-accessible functions related to GUC variables.
|
|
*
|
|
*
|
|
* Copyright (c) 2000-2023, PostgreSQL Global Development Group
|
|
* Written by Peter Eisentraut <peter_e@gmx.net>.
|
|
*
|
|
* IDENTIFICATION
|
|
* src/backend/utils/misc/guc_funcs.c
|
|
*
|
|
*--------------------------------------------------------------------
|
|
*/
|
|
#include "postgres.h"
|
|
|
|
#include <sys/stat.h>
|
|
#include <unistd.h>
|
|
|
|
#include "access/xact.h"
|
|
#include "catalog/objectaccess.h"
|
|
#include "catalog/pg_authid.h"
|
|
#include "catalog/pg_parameter_acl.h"
|
|
#include "funcapi.h"
|
|
#include "guc_internal.h"
|
|
#include "parser/parse_type.h"
|
|
#include "utils/acl.h"
|
|
#include "utils/backend_status.h"
|
|
#include "utils/builtins.h"
|
|
#include "utils/guc_tables.h"
|
|
#include "utils/snapmgr.h"
|
|
|
|
static char *flatten_set_variable_args(const char *name, List *args);
|
|
static void ShowGUCConfigOption(const char *name, DestReceiver *dest);
|
|
static void ShowAllGUCConfig(DestReceiver *dest);
|
|
|
|
|
|
/*
|
|
* SET command
|
|
*/
|
|
void
|
|
ExecSetVariableStmt(VariableSetStmt *stmt, bool isTopLevel)
|
|
{
|
|
GucAction action = stmt->is_local ? GUC_ACTION_LOCAL : GUC_ACTION_SET;
|
|
|
|
/*
|
|
* Workers synchronize these parameters at the start of the parallel
|
|
* operation; then, we block SET during the operation.
|
|
*/
|
|
if (IsInParallelMode())
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_TRANSACTION_STATE),
|
|
errmsg("cannot set parameters during a parallel operation")));
|
|
|
|
switch (stmt->kind)
|
|
{
|
|
case VAR_SET_VALUE:
|
|
case VAR_SET_CURRENT:
|
|
if (stmt->is_local)
|
|
WarnNoTransactionBlock(isTopLevel, "SET LOCAL");
|
|
(void) set_config_option(stmt->name,
|
|
ExtractSetVariableArgs(stmt),
|
|
(superuser() ? PGC_SUSET : PGC_USERSET),
|
|
PGC_S_SESSION,
|
|
action, true, 0, false);
|
|
break;
|
|
case VAR_SET_MULTI:
|
|
|
|
/*
|
|
* Special-case SQL syntaxes. The TRANSACTION and SESSION
|
|
* CHARACTERISTICS cases effectively set more than one variable
|
|
* per statement. TRANSACTION SNAPSHOT only takes one argument,
|
|
* but we put it here anyway since it's a special case and not
|
|
* related to any GUC variable.
|
|
*/
|
|
if (strcmp(stmt->name, "TRANSACTION") == 0)
|
|
{
|
|
ListCell *head;
|
|
|
|
WarnNoTransactionBlock(isTopLevel, "SET TRANSACTION");
|
|
|
|
foreach(head, stmt->args)
|
|
{
|
|
DefElem *item = (DefElem *) lfirst(head);
|
|
|
|
if (strcmp(item->defname, "transaction_isolation") == 0)
|
|
SetPGVariable("transaction_isolation",
|
|
list_make1(item->arg), stmt->is_local);
|
|
else if (strcmp(item->defname, "transaction_read_only") == 0)
|
|
SetPGVariable("transaction_read_only",
|
|
list_make1(item->arg), stmt->is_local);
|
|
else if (strcmp(item->defname, "transaction_deferrable") == 0)
|
|
SetPGVariable("transaction_deferrable",
|
|
list_make1(item->arg), stmt->is_local);
|
|
else
|
|
elog(ERROR, "unexpected SET TRANSACTION element: %s",
|
|
item->defname);
|
|
}
|
|
}
|
|
else if (strcmp(stmt->name, "SESSION CHARACTERISTICS") == 0)
|
|
{
|
|
ListCell *head;
|
|
|
|
foreach(head, stmt->args)
|
|
{
|
|
DefElem *item = (DefElem *) lfirst(head);
|
|
|
|
if (strcmp(item->defname, "transaction_isolation") == 0)
|
|
SetPGVariable("default_transaction_isolation",
|
|
list_make1(item->arg), stmt->is_local);
|
|
else if (strcmp(item->defname, "transaction_read_only") == 0)
|
|
SetPGVariable("default_transaction_read_only",
|
|
list_make1(item->arg), stmt->is_local);
|
|
else if (strcmp(item->defname, "transaction_deferrable") == 0)
|
|
SetPGVariable("default_transaction_deferrable",
|
|
list_make1(item->arg), stmt->is_local);
|
|
else
|
|
elog(ERROR, "unexpected SET SESSION element: %s",
|
|
item->defname);
|
|
}
|
|
}
|
|
else if (strcmp(stmt->name, "TRANSACTION SNAPSHOT") == 0)
|
|
{
|
|
A_Const *con = linitial_node(A_Const, stmt->args);
|
|
|
|
if (stmt->is_local)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("SET LOCAL TRANSACTION SNAPSHOT is not implemented")));
|
|
|
|
WarnNoTransactionBlock(isTopLevel, "SET TRANSACTION");
|
|
ImportSnapshot(strVal(&con->val));
|
|
}
|
|
else
|
|
elog(ERROR, "unexpected SET MULTI element: %s",
|
|
stmt->name);
|
|
break;
|
|
case VAR_SET_DEFAULT:
|
|
if (stmt->is_local)
|
|
WarnNoTransactionBlock(isTopLevel, "SET LOCAL");
|
|
/* fall through */
|
|
case VAR_RESET:
|
|
(void) set_config_option(stmt->name,
|
|
NULL,
|
|
(superuser() ? PGC_SUSET : PGC_USERSET),
|
|
PGC_S_SESSION,
|
|
action, true, 0, false);
|
|
break;
|
|
case VAR_RESET_ALL:
|
|
ResetAllOptions();
|
|
break;
|
|
}
|
|
|
|
/* Invoke the post-alter hook for setting this GUC variable, by name. */
|
|
InvokeObjectPostAlterHookArgStr(ParameterAclRelationId, stmt->name,
|
|
ACL_SET, stmt->kind, false);
|
|
}
|
|
|
|
/*
|
|
* Get the value to assign for a VariableSetStmt, or NULL if it's RESET.
|
|
* The result is palloc'd.
|
|
*
|
|
* This is exported for use by actions such as ALTER ROLE SET.
|
|
*/
|
|
char *
|
|
ExtractSetVariableArgs(VariableSetStmt *stmt)
|
|
{
|
|
|
|
switch (stmt->kind)
|
|
{
|
|
case VAR_SET_VALUE:
|
|
return flatten_set_variable_args(stmt->name, stmt->args);
|
|
case VAR_SET_CURRENT:
|
|
{
|
|
struct config_generic *record;
|
|
char *result;
|
|
|
|
result = GetConfigOptionByName(stmt->name, NULL, false);
|
|
record = find_option(stmt->name, false, false, ERROR);
|
|
stmt->user_set = (record->scontext == PGC_USERSET);
|
|
|
|
return result;
|
|
}
|
|
default:
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* flatten_set_variable_args
|
|
* Given a parsenode List as emitted by the grammar for SET,
|
|
* convert to the flat string representation used by GUC.
|
|
*
|
|
* We need to be told the name of the variable the args are for, because
|
|
* the flattening rules vary (ugh).
|
|
*
|
|
* The result is NULL if args is NIL (i.e., SET ... TO DEFAULT), otherwise
|
|
* a palloc'd string.
|
|
*/
|
|
static char *
|
|
flatten_set_variable_args(const char *name, List *args)
|
|
{
|
|
struct config_generic *record;
|
|
int flags;
|
|
StringInfoData buf;
|
|
ListCell *l;
|
|
|
|
/* Fast path if just DEFAULT */
|
|
if (args == NIL)
|
|
return NULL;
|
|
|
|
/*
|
|
* Get flags for the variable; if it's not known, use default flags.
|
|
* (Caller might throw error later, but not our business to do so here.)
|
|
*/
|
|
record = find_option(name, false, true, WARNING);
|
|
if (record)
|
|
flags = record->flags;
|
|
else
|
|
flags = 0;
|
|
|
|
/* Complain if list input and non-list variable */
|
|
if ((flags & GUC_LIST_INPUT) == 0 &&
|
|
list_length(args) != 1)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("SET %s takes only one argument", name)));
|
|
|
|
initStringInfo(&buf);
|
|
|
|
/*
|
|
* Each list member may be a plain A_Const node, or an A_Const within a
|
|
* TypeCast; the latter case is supported only for ConstInterval arguments
|
|
* (for SET TIME ZONE).
|
|
*/
|
|
foreach(l, args)
|
|
{
|
|
Node *arg = (Node *) lfirst(l);
|
|
char *val;
|
|
TypeName *typeName = NULL;
|
|
A_Const *con;
|
|
|
|
if (l != list_head(args))
|
|
appendStringInfoString(&buf, ", ");
|
|
|
|
if (IsA(arg, TypeCast))
|
|
{
|
|
TypeCast *tc = (TypeCast *) arg;
|
|
|
|
arg = tc->arg;
|
|
typeName = tc->typeName;
|
|
}
|
|
|
|
if (!IsA(arg, A_Const))
|
|
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(arg));
|
|
con = (A_Const *) arg;
|
|
|
|
switch (nodeTag(&con->val))
|
|
{
|
|
case T_Integer:
|
|
appendStringInfo(&buf, "%d", intVal(&con->val));
|
|
break;
|
|
case T_Float:
|
|
/* represented as a string, so just copy it */
|
|
appendStringInfoString(&buf, castNode(Float, &con->val)->fval);
|
|
break;
|
|
case T_String:
|
|
val = strVal(&con->val);
|
|
if (typeName != NULL)
|
|
{
|
|
/*
|
|
* Must be a ConstInterval argument for TIME ZONE. Coerce
|
|
* to interval and back to normalize the value and account
|
|
* for any typmod.
|
|
*/
|
|
Oid typoid;
|
|
int32 typmod;
|
|
Datum interval;
|
|
char *intervalout;
|
|
|
|
typenameTypeIdAndMod(NULL, typeName, &typoid, &typmod);
|
|
Assert(typoid == INTERVALOID);
|
|
|
|
interval =
|
|
DirectFunctionCall3(interval_in,
|
|
CStringGetDatum(val),
|
|
ObjectIdGetDatum(InvalidOid),
|
|
Int32GetDatum(typmod));
|
|
|
|
intervalout =
|
|
DatumGetCString(DirectFunctionCall1(interval_out,
|
|
interval));
|
|
appendStringInfo(&buf, "INTERVAL '%s'", intervalout);
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* Plain string literal or identifier. For quote mode,
|
|
* quote it if it's not a vanilla identifier.
|
|
*/
|
|
if (flags & GUC_LIST_QUOTE)
|
|
appendStringInfoString(&buf, quote_identifier(val));
|
|
else
|
|
appendStringInfoString(&buf, val);
|
|
}
|
|
break;
|
|
default:
|
|
elog(ERROR, "unrecognized node type: %d",
|
|
(int) nodeTag(&con->val));
|
|
break;
|
|
}
|
|
}
|
|
|
|
return buf.data;
|
|
}
|
|
|
|
/*
|
|
* SetPGVariable - SET command exported as an easily-C-callable function.
|
|
*
|
|
* This provides access to SET TO value, as well as SET TO DEFAULT (expressed
|
|
* by passing args == NIL), but not SET FROM CURRENT functionality.
|
|
*/
|
|
void
|
|
SetPGVariable(const char *name, List *args, bool is_local)
|
|
{
|
|
char *argstring = flatten_set_variable_args(name, args);
|
|
|
|
/* Note SET DEFAULT (argstring == NULL) is equivalent to RESET */
|
|
(void) set_config_option(name,
|
|
argstring,
|
|
(superuser() ? PGC_SUSET : PGC_USERSET),
|
|
PGC_S_SESSION,
|
|
is_local ? GUC_ACTION_LOCAL : GUC_ACTION_SET,
|
|
true, 0, false);
|
|
}
|
|
|
|
/*
|
|
* SET command wrapped as a SQL callable function.
|
|
*/
|
|
Datum
|
|
set_config_by_name(PG_FUNCTION_ARGS)
|
|
{
|
|
char *name;
|
|
char *value;
|
|
char *new_value;
|
|
bool is_local;
|
|
|
|
if (PG_ARGISNULL(0))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
|
|
errmsg("SET requires parameter name")));
|
|
|
|
/* Get the GUC variable name */
|
|
name = TextDatumGetCString(PG_GETARG_DATUM(0));
|
|
|
|
/* Get the desired value or set to NULL for a reset request */
|
|
if (PG_ARGISNULL(1))
|
|
value = NULL;
|
|
else
|
|
value = TextDatumGetCString(PG_GETARG_DATUM(1));
|
|
|
|
/*
|
|
* Get the desired state of is_local. Default to false if provided value
|
|
* is NULL
|
|
*/
|
|
if (PG_ARGISNULL(2))
|
|
is_local = false;
|
|
else
|
|
is_local = PG_GETARG_BOOL(2);
|
|
|
|
/* Note SET DEFAULT (argstring == NULL) is equivalent to RESET */
|
|
(void) set_config_option(name,
|
|
value,
|
|
(superuser() ? PGC_SUSET : PGC_USERSET),
|
|
PGC_S_SESSION,
|
|
is_local ? GUC_ACTION_LOCAL : GUC_ACTION_SET,
|
|
true, 0, false);
|
|
|
|
/* get the new current value */
|
|
new_value = GetConfigOptionByName(name, NULL, false);
|
|
|
|
/* Convert return string to text */
|
|
PG_RETURN_TEXT_P(cstring_to_text(new_value));
|
|
}
|
|
|
|
|
|
/*
|
|
* SHOW command
|
|
*/
|
|
void
|
|
GetPGVariable(const char *name, DestReceiver *dest)
|
|
{
|
|
if (guc_name_compare(name, "all") == 0)
|
|
ShowAllGUCConfig(dest);
|
|
else
|
|
ShowGUCConfigOption(name, dest);
|
|
}
|
|
|
|
/*
|
|
* Get a tuple descriptor for SHOW's result
|
|
*/
|
|
TupleDesc
|
|
GetPGVariableResultDesc(const char *name)
|
|
{
|
|
TupleDesc tupdesc;
|
|
|
|
if (guc_name_compare(name, "all") == 0)
|
|
{
|
|
/* need a tuple descriptor representing three TEXT columns */
|
|
tupdesc = CreateTemplateTupleDesc(3);
|
|
TupleDescInitEntry(tupdesc, (AttrNumber) 1, "name",
|
|
TEXTOID, -1, 0);
|
|
TupleDescInitEntry(tupdesc, (AttrNumber) 2, "setting",
|
|
TEXTOID, -1, 0);
|
|
TupleDescInitEntry(tupdesc, (AttrNumber) 3, "description",
|
|
TEXTOID, -1, 0);
|
|
}
|
|
else
|
|
{
|
|
const char *varname;
|
|
|
|
/* Get the canonical spelling of name */
|
|
(void) GetConfigOptionByName(name, &varname, false);
|
|
|
|
/* need a tuple descriptor representing a single TEXT column */
|
|
tupdesc = CreateTemplateTupleDesc(1);
|
|
TupleDescInitEntry(tupdesc, (AttrNumber) 1, varname,
|
|
TEXTOID, -1, 0);
|
|
}
|
|
return tupdesc;
|
|
}
|
|
|
|
/*
|
|
* SHOW one variable
|
|
*/
|
|
static void
|
|
ShowGUCConfigOption(const char *name, DestReceiver *dest)
|
|
{
|
|
TupOutputState *tstate;
|
|
TupleDesc tupdesc;
|
|
const char *varname;
|
|
char *value;
|
|
|
|
/* Get the value and canonical spelling of name */
|
|
value = GetConfigOptionByName(name, &varname, false);
|
|
|
|
/* need a tuple descriptor representing a single TEXT column */
|
|
tupdesc = CreateTemplateTupleDesc(1);
|
|
TupleDescInitBuiltinEntry(tupdesc, (AttrNumber) 1, varname,
|
|
TEXTOID, -1, 0);
|
|
|
|
/* prepare for projection of tuples */
|
|
tstate = begin_tup_output_tupdesc(dest, tupdesc, &TTSOpsVirtual);
|
|
|
|
/* Send it */
|
|
do_text_output_oneline(tstate, value);
|
|
|
|
end_tup_output(tstate);
|
|
}
|
|
|
|
/*
|
|
* SHOW ALL command
|
|
*/
|
|
static void
|
|
ShowAllGUCConfig(DestReceiver *dest)
|
|
{
|
|
struct config_generic **guc_vars;
|
|
int num_vars;
|
|
TupOutputState *tstate;
|
|
TupleDesc tupdesc;
|
|
Datum values[3];
|
|
bool isnull[3] = {false, false, false};
|
|
|
|
/* collect the variables, in sorted order */
|
|
guc_vars = get_guc_variables(&num_vars);
|
|
|
|
/* need a tuple descriptor representing three TEXT columns */
|
|
tupdesc = CreateTemplateTupleDesc(3);
|
|
TupleDescInitBuiltinEntry(tupdesc, (AttrNumber) 1, "name",
|
|
TEXTOID, -1, 0);
|
|
TupleDescInitBuiltinEntry(tupdesc, (AttrNumber) 2, "setting",
|
|
TEXTOID, -1, 0);
|
|
TupleDescInitBuiltinEntry(tupdesc, (AttrNumber) 3, "description",
|
|
TEXTOID, -1, 0);
|
|
|
|
/* prepare for projection of tuples */
|
|
tstate = begin_tup_output_tupdesc(dest, tupdesc, &TTSOpsVirtual);
|
|
|
|
for (int i = 0; i < num_vars; i++)
|
|
{
|
|
struct config_generic *conf = guc_vars[i];
|
|
char *setting;
|
|
|
|
/* skip if marked NO_SHOW_ALL */
|
|
if (conf->flags & GUC_NO_SHOW_ALL)
|
|
continue;
|
|
|
|
/* return only options visible to the current user */
|
|
if (!ConfigOptionIsVisible(conf))
|
|
continue;
|
|
|
|
/* assign to the values array */
|
|
values[0] = PointerGetDatum(cstring_to_text(conf->name));
|
|
|
|
setting = ShowGUCOption(conf, true);
|
|
if (setting)
|
|
{
|
|
values[1] = PointerGetDatum(cstring_to_text(setting));
|
|
isnull[1] = false;
|
|
}
|
|
else
|
|
{
|
|
values[1] = PointerGetDatum(NULL);
|
|
isnull[1] = true;
|
|
}
|
|
|
|
if (conf->short_desc)
|
|
{
|
|
values[2] = PointerGetDatum(cstring_to_text(conf->short_desc));
|
|
isnull[2] = false;
|
|
}
|
|
else
|
|
{
|
|
values[2] = PointerGetDatum(NULL);
|
|
isnull[2] = true;
|
|
}
|
|
|
|
/* send it to dest */
|
|
do_tup_output(tstate, values, isnull);
|
|
|
|
/* clean up */
|
|
pfree(DatumGetPointer(values[0]));
|
|
if (setting)
|
|
{
|
|
pfree(setting);
|
|
pfree(DatumGetPointer(values[1]));
|
|
}
|
|
if (conf->short_desc)
|
|
pfree(DatumGetPointer(values[2]));
|
|
}
|
|
|
|
end_tup_output(tstate);
|
|
}
|
|
|
|
/*
|
|
* Return some of the flags associated to the specified GUC in the shape of
|
|
* a text array, and NULL if it does not exist. An empty array is returned
|
|
* if the GUC exists without any meaningful flags to show.
|
|
*/
|
|
Datum
|
|
pg_settings_get_flags(PG_FUNCTION_ARGS)
|
|
{
|
|
#define MAX_GUC_FLAGS 6
|
|
char *varname = TextDatumGetCString(PG_GETARG_DATUM(0));
|
|
struct config_generic *record;
|
|
int cnt = 0;
|
|
Datum flags[MAX_GUC_FLAGS];
|
|
ArrayType *a;
|
|
|
|
record = find_option(varname, false, true, ERROR);
|
|
|
|
/* return NULL if no such variable */
|
|
if (record == NULL)
|
|
PG_RETURN_NULL();
|
|
|
|
if (record->flags & GUC_EXPLAIN)
|
|
flags[cnt++] = CStringGetTextDatum("EXPLAIN");
|
|
if (record->flags & GUC_NO_RESET)
|
|
flags[cnt++] = CStringGetTextDatum("NO_RESET");
|
|
if (record->flags & GUC_NO_RESET_ALL)
|
|
flags[cnt++] = CStringGetTextDatum("NO_RESET_ALL");
|
|
if (record->flags & GUC_NO_SHOW_ALL)
|
|
flags[cnt++] = CStringGetTextDatum("NO_SHOW_ALL");
|
|
if (record->flags & GUC_NOT_IN_SAMPLE)
|
|
flags[cnt++] = CStringGetTextDatum("NOT_IN_SAMPLE");
|
|
if (record->flags & GUC_RUNTIME_COMPUTED)
|
|
flags[cnt++] = CStringGetTextDatum("RUNTIME_COMPUTED");
|
|
|
|
Assert(cnt <= MAX_GUC_FLAGS);
|
|
|
|
/* Returns the record as Datum */
|
|
a = construct_array_builtin(flags, cnt, TEXTOID);
|
|
PG_RETURN_ARRAYTYPE_P(a);
|
|
}
|
|
|
|
/*
|
|
* Return whether or not the GUC variable is visible to the current user.
|
|
*/
|
|
bool
|
|
ConfigOptionIsVisible(struct config_generic *conf)
|
|
{
|
|
if ((conf->flags & GUC_SUPERUSER_ONLY) &&
|
|
!has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS))
|
|
return false;
|
|
else
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Extract fields to show in pg_settings for given variable.
|
|
*/
|
|
static void
|
|
GetConfigOptionValues(struct config_generic *conf, const char **values)
|
|
{
|
|
char buffer[256];
|
|
|
|
/* first get the generic attributes */
|
|
|
|
/* name */
|
|
values[0] = conf->name;
|
|
|
|
/* setting: use ShowGUCOption in order to avoid duplicating the logic */
|
|
values[1] = ShowGUCOption(conf, false);
|
|
|
|
/* unit, if any (NULL is fine) */
|
|
values[2] = get_config_unit_name(conf->flags);
|
|
|
|
/* group */
|
|
values[3] = _(config_group_names[conf->group]);
|
|
|
|
/* short_desc */
|
|
values[4] = conf->short_desc != NULL ? _(conf->short_desc) : NULL;
|
|
|
|
/* extra_desc */
|
|
values[5] = conf->long_desc != NULL ? _(conf->long_desc) : NULL;
|
|
|
|
/* context */
|
|
values[6] = GucContext_Names[conf->context];
|
|
|
|
/* vartype */
|
|
values[7] = config_type_names[conf->vartype];
|
|
|
|
/* source */
|
|
values[8] = GucSource_Names[conf->source];
|
|
|
|
/* now get the type specific attributes */
|
|
switch (conf->vartype)
|
|
{
|
|
case PGC_BOOL:
|
|
{
|
|
struct config_bool *lconf = (struct config_bool *) conf;
|
|
|
|
/* min_val */
|
|
values[9] = NULL;
|
|
|
|
/* max_val */
|
|
values[10] = NULL;
|
|
|
|
/* enumvals */
|
|
values[11] = NULL;
|
|
|
|
/* boot_val */
|
|
values[12] = pstrdup(lconf->boot_val ? "on" : "off");
|
|
|
|
/* reset_val */
|
|
values[13] = pstrdup(lconf->reset_val ? "on" : "off");
|
|
}
|
|
break;
|
|
|
|
case PGC_INT:
|
|
{
|
|
struct config_int *lconf = (struct config_int *) conf;
|
|
|
|
/* min_val */
|
|
snprintf(buffer, sizeof(buffer), "%d", lconf->min);
|
|
values[9] = pstrdup(buffer);
|
|
|
|
/* max_val */
|
|
snprintf(buffer, sizeof(buffer), "%d", lconf->max);
|
|
values[10] = pstrdup(buffer);
|
|
|
|
/* enumvals */
|
|
values[11] = NULL;
|
|
|
|
/* boot_val */
|
|
snprintf(buffer, sizeof(buffer), "%d", lconf->boot_val);
|
|
values[12] = pstrdup(buffer);
|
|
|
|
/* reset_val */
|
|
snprintf(buffer, sizeof(buffer), "%d", lconf->reset_val);
|
|
values[13] = pstrdup(buffer);
|
|
}
|
|
break;
|
|
|
|
case PGC_REAL:
|
|
{
|
|
struct config_real *lconf = (struct config_real *) conf;
|
|
|
|
/* min_val */
|
|
snprintf(buffer, sizeof(buffer), "%g", lconf->min);
|
|
values[9] = pstrdup(buffer);
|
|
|
|
/* max_val */
|
|
snprintf(buffer, sizeof(buffer), "%g", lconf->max);
|
|
values[10] = pstrdup(buffer);
|
|
|
|
/* enumvals */
|
|
values[11] = NULL;
|
|
|
|
/* boot_val */
|
|
snprintf(buffer, sizeof(buffer), "%g", lconf->boot_val);
|
|
values[12] = pstrdup(buffer);
|
|
|
|
/* reset_val */
|
|
snprintf(buffer, sizeof(buffer), "%g", lconf->reset_val);
|
|
values[13] = pstrdup(buffer);
|
|
}
|
|
break;
|
|
|
|
case PGC_STRING:
|
|
{
|
|
struct config_string *lconf = (struct config_string *) conf;
|
|
|
|
/* min_val */
|
|
values[9] = NULL;
|
|
|
|
/* max_val */
|
|
values[10] = NULL;
|
|
|
|
/* enumvals */
|
|
values[11] = NULL;
|
|
|
|
/* boot_val */
|
|
if (lconf->boot_val == NULL)
|
|
values[12] = NULL;
|
|
else
|
|
values[12] = pstrdup(lconf->boot_val);
|
|
|
|
/* reset_val */
|
|
if (lconf->reset_val == NULL)
|
|
values[13] = NULL;
|
|
else
|
|
values[13] = pstrdup(lconf->reset_val);
|
|
}
|
|
break;
|
|
|
|
case PGC_ENUM:
|
|
{
|
|
struct config_enum *lconf = (struct config_enum *) conf;
|
|
|
|
/* min_val */
|
|
values[9] = NULL;
|
|
|
|
/* max_val */
|
|
values[10] = NULL;
|
|
|
|
/* enumvals */
|
|
|
|
/*
|
|
* NOTE! enumvals with double quotes in them are not
|
|
* supported!
|
|
*/
|
|
values[11] = config_enum_get_options((struct config_enum *) conf,
|
|
"{\"", "\"}", "\",\"");
|
|
|
|
/* boot_val */
|
|
values[12] = pstrdup(config_enum_lookup_by_value(lconf,
|
|
lconf->boot_val));
|
|
|
|
/* reset_val */
|
|
values[13] = pstrdup(config_enum_lookup_by_value(lconf,
|
|
lconf->reset_val));
|
|
}
|
|
break;
|
|
|
|
default:
|
|
{
|
|
/*
|
|
* should never get here, but in case we do, set 'em to NULL
|
|
*/
|
|
|
|
/* min_val */
|
|
values[9] = NULL;
|
|
|
|
/* max_val */
|
|
values[10] = NULL;
|
|
|
|
/* enumvals */
|
|
values[11] = NULL;
|
|
|
|
/* boot_val */
|
|
values[12] = NULL;
|
|
|
|
/* reset_val */
|
|
values[13] = NULL;
|
|
}
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* If the setting came from a config file, set the source location. For
|
|
* security reasons, we don't show source file/line number for
|
|
* insufficiently-privileged users.
|
|
*/
|
|
if (conf->source == PGC_S_FILE &&
|
|
has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS))
|
|
{
|
|
values[14] = conf->sourcefile;
|
|
snprintf(buffer, sizeof(buffer), "%d", conf->sourceline);
|
|
values[15] = pstrdup(buffer);
|
|
}
|
|
else
|
|
{
|
|
values[14] = NULL;
|
|
values[15] = NULL;
|
|
}
|
|
|
|
values[16] = (conf->status & GUC_PENDING_RESTART) ? "t" : "f";
|
|
}
|
|
|
|
/*
|
|
* show_config_by_name - equiv to SHOW X command but implemented as
|
|
* a function.
|
|
*/
|
|
Datum
|
|
show_config_by_name(PG_FUNCTION_ARGS)
|
|
{
|
|
char *varname = TextDatumGetCString(PG_GETARG_DATUM(0));
|
|
char *varval;
|
|
|
|
/* Get the value */
|
|
varval = GetConfigOptionByName(varname, NULL, false);
|
|
|
|
/* Convert to text */
|
|
PG_RETURN_TEXT_P(cstring_to_text(varval));
|
|
}
|
|
|
|
/*
|
|
* show_config_by_name_missing_ok - equiv to SHOW X command but implemented as
|
|
* a function. If X does not exist, suppress the error and just return NULL
|
|
* if missing_ok is true.
|
|
*/
|
|
Datum
|
|
show_config_by_name_missing_ok(PG_FUNCTION_ARGS)
|
|
{
|
|
char *varname = TextDatumGetCString(PG_GETARG_DATUM(0));
|
|
bool missing_ok = PG_GETARG_BOOL(1);
|
|
char *varval;
|
|
|
|
/* Get the value */
|
|
varval = GetConfigOptionByName(varname, NULL, missing_ok);
|
|
|
|
/* return NULL if no such variable */
|
|
if (varval == NULL)
|
|
PG_RETURN_NULL();
|
|
|
|
/* Convert to text */
|
|
PG_RETURN_TEXT_P(cstring_to_text(varval));
|
|
}
|
|
|
|
/*
|
|
* show_all_settings - equiv to SHOW ALL command but implemented as
|
|
* a Table Function.
|
|
*/
|
|
#define NUM_PG_SETTINGS_ATTS 17
|
|
|
|
Datum
|
|
show_all_settings(PG_FUNCTION_ARGS)
|
|
{
|
|
FuncCallContext *funcctx;
|
|
struct config_generic **guc_vars;
|
|
int num_vars;
|
|
TupleDesc tupdesc;
|
|
int call_cntr;
|
|
int max_calls;
|
|
AttInMetadata *attinmeta;
|
|
MemoryContext oldcontext;
|
|
|
|
/* stuff done only on the first call of the function */
|
|
if (SRF_IS_FIRSTCALL())
|
|
{
|
|
/* create a function context for cross-call persistence */
|
|
funcctx = SRF_FIRSTCALL_INIT();
|
|
|
|
/*
|
|
* switch to memory context appropriate for multiple function calls
|
|
*/
|
|
oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
|
|
|
|
/*
|
|
* need a tuple descriptor representing NUM_PG_SETTINGS_ATTS columns
|
|
* of the appropriate types
|
|
*/
|
|
tupdesc = CreateTemplateTupleDesc(NUM_PG_SETTINGS_ATTS);
|
|
TupleDescInitEntry(tupdesc, (AttrNumber) 1, "name",
|
|
TEXTOID, -1, 0);
|
|
TupleDescInitEntry(tupdesc, (AttrNumber) 2, "setting",
|
|
TEXTOID, -1, 0);
|
|
TupleDescInitEntry(tupdesc, (AttrNumber) 3, "unit",
|
|
TEXTOID, -1, 0);
|
|
TupleDescInitEntry(tupdesc, (AttrNumber) 4, "category",
|
|
TEXTOID, -1, 0);
|
|
TupleDescInitEntry(tupdesc, (AttrNumber) 5, "short_desc",
|
|
TEXTOID, -1, 0);
|
|
TupleDescInitEntry(tupdesc, (AttrNumber) 6, "extra_desc",
|
|
TEXTOID, -1, 0);
|
|
TupleDescInitEntry(tupdesc, (AttrNumber) 7, "context",
|
|
TEXTOID, -1, 0);
|
|
TupleDescInitEntry(tupdesc, (AttrNumber) 8, "vartype",
|
|
TEXTOID, -1, 0);
|
|
TupleDescInitEntry(tupdesc, (AttrNumber) 9, "source",
|
|
TEXTOID, -1, 0);
|
|
TupleDescInitEntry(tupdesc, (AttrNumber) 10, "min_val",
|
|
TEXTOID, -1, 0);
|
|
TupleDescInitEntry(tupdesc, (AttrNumber) 11, "max_val",
|
|
TEXTOID, -1, 0);
|
|
TupleDescInitEntry(tupdesc, (AttrNumber) 12, "enumvals",
|
|
TEXTARRAYOID, -1, 0);
|
|
TupleDescInitEntry(tupdesc, (AttrNumber) 13, "boot_val",
|
|
TEXTOID, -1, 0);
|
|
TupleDescInitEntry(tupdesc, (AttrNumber) 14, "reset_val",
|
|
TEXTOID, -1, 0);
|
|
TupleDescInitEntry(tupdesc, (AttrNumber) 15, "sourcefile",
|
|
TEXTOID, -1, 0);
|
|
TupleDescInitEntry(tupdesc, (AttrNumber) 16, "sourceline",
|
|
INT4OID, -1, 0);
|
|
TupleDescInitEntry(tupdesc, (AttrNumber) 17, "pending_restart",
|
|
BOOLOID, -1, 0);
|
|
|
|
/*
|
|
* Generate attribute metadata needed later to produce tuples from raw
|
|
* C strings
|
|
*/
|
|
attinmeta = TupleDescGetAttInMetadata(tupdesc);
|
|
funcctx->attinmeta = attinmeta;
|
|
|
|
/* collect the variables, in sorted order */
|
|
guc_vars = get_guc_variables(&num_vars);
|
|
|
|
/* use user_fctx to remember the array location */
|
|
funcctx->user_fctx = guc_vars;
|
|
|
|
/* total number of tuples to be returned */
|
|
funcctx->max_calls = num_vars;
|
|
|
|
MemoryContextSwitchTo(oldcontext);
|
|
}
|
|
|
|
/* stuff done on every call of the function */
|
|
funcctx = SRF_PERCALL_SETUP();
|
|
|
|
guc_vars = (struct config_generic **) funcctx->user_fctx;
|
|
call_cntr = funcctx->call_cntr;
|
|
max_calls = funcctx->max_calls;
|
|
attinmeta = funcctx->attinmeta;
|
|
|
|
while (call_cntr < max_calls) /* do when there is more left to send */
|
|
{
|
|
struct config_generic *conf = guc_vars[call_cntr];
|
|
char *values[NUM_PG_SETTINGS_ATTS];
|
|
HeapTuple tuple;
|
|
Datum result;
|
|
|
|
/* skip if marked NO_SHOW_ALL or if not visible to current user */
|
|
if ((conf->flags & GUC_NO_SHOW_ALL) ||
|
|
!ConfigOptionIsVisible(conf))
|
|
{
|
|
call_cntr = ++funcctx->call_cntr;
|
|
continue;
|
|
}
|
|
|
|
/* extract values for the current variable */
|
|
GetConfigOptionValues(conf, (const char **) values);
|
|
|
|
/* build a tuple */
|
|
tuple = BuildTupleFromCStrings(attinmeta, values);
|
|
|
|
/* make the tuple into a datum */
|
|
result = HeapTupleGetDatum(tuple);
|
|
|
|
SRF_RETURN_NEXT(funcctx, result);
|
|
}
|
|
|
|
/* do when there is no more left */
|
|
SRF_RETURN_DONE(funcctx);
|
|
}
|
|
|
|
/*
|
|
* show_all_file_settings
|
|
*
|
|
* Returns a table of all parameter settings in all configuration files
|
|
* which includes the config file pathname, the line number, a sequence number
|
|
* indicating the order in which the settings were encountered, the parameter
|
|
* name and value, a bool showing if the value could be applied, and possibly
|
|
* an associated error message. (For problems such as syntax errors, the
|
|
* parameter name/value might be NULL.)
|
|
*
|
|
* Note: no filtering is done here, instead we depend on the GRANT system
|
|
* to prevent unprivileged users from accessing this function or the view
|
|
* built on top of it.
|
|
*/
|
|
Datum
|
|
show_all_file_settings(PG_FUNCTION_ARGS)
|
|
{
|
|
#define NUM_PG_FILE_SETTINGS_ATTS 7
|
|
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
|
|
ConfigVariable *conf;
|
|
int seqno;
|
|
|
|
/* Scan the config files using current context as workspace */
|
|
conf = ProcessConfigFileInternal(PGC_SIGHUP, false, DEBUG3);
|
|
|
|
/* Build a tuplestore to return our results in */
|
|
InitMaterializedSRF(fcinfo, 0);
|
|
|
|
/* Process the results and create a tuplestore */
|
|
for (seqno = 1; conf != NULL; conf = conf->next, seqno++)
|
|
{
|
|
Datum values[NUM_PG_FILE_SETTINGS_ATTS];
|
|
bool nulls[NUM_PG_FILE_SETTINGS_ATTS];
|
|
|
|
memset(values, 0, sizeof(values));
|
|
memset(nulls, 0, sizeof(nulls));
|
|
|
|
/* sourcefile */
|
|
if (conf->filename)
|
|
values[0] = PointerGetDatum(cstring_to_text(conf->filename));
|
|
else
|
|
nulls[0] = true;
|
|
|
|
/* sourceline (not meaningful if no sourcefile) */
|
|
if (conf->filename)
|
|
values[1] = Int32GetDatum(conf->sourceline);
|
|
else
|
|
nulls[1] = true;
|
|
|
|
/* seqno */
|
|
values[2] = Int32GetDatum(seqno);
|
|
|
|
/* name */
|
|
if (conf->name)
|
|
values[3] = PointerGetDatum(cstring_to_text(conf->name));
|
|
else
|
|
nulls[3] = true;
|
|
|
|
/* setting */
|
|
if (conf->value)
|
|
values[4] = PointerGetDatum(cstring_to_text(conf->value));
|
|
else
|
|
nulls[4] = true;
|
|
|
|
/* applied */
|
|
values[5] = BoolGetDatum(conf->applied);
|
|
|
|
/* error */
|
|
if (conf->errmsg)
|
|
values[6] = PointerGetDatum(cstring_to_text(conf->errmsg));
|
|
else
|
|
nulls[6] = true;
|
|
|
|
/* shove row into tuplestore */
|
|
tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
|
|
}
|
|
|
|
return (Datum) 0;
|
|
}
|