| /*-------------------------------------------------------------------- |
| * |
| * 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 "cdb/cdbdisp_query.h" |
| #include "cdb/cdbvars.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/faultinjector.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); |
| static void DispatchSetPGVariable(const char *name, List *args, bool is_local); |
| |
| |
| /* |
| * 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 && |
| Gp_role != GP_ROLE_EXECUTE && !IsBootstrapProcessingMode()) |
| WarnNoTransactionBlock(isTopLevel, "SET LOCAL"); |
| |
| SIMPLE_FAULT_INJECTOR("set_variable_fault"); |
| |
| (void) set_config_option(stmt->name, |
| ExtractSetVariableArgs(stmt), |
| (superuser() ? PGC_SUSET : PGC_USERSET), |
| PGC_S_SESSION, |
| action, true, 0, false); |
| DispatchSetPGVariable(stmt->name, stmt->args, stmt->is_local); |
| 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; |
| |
| if (Gp_role != GP_ROLE_EXECUTE) |
| 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: |
| SIMPLE_FAULT_INJECTOR("reset_variable_fault"); |
| (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; |
| } |
| |
| |
| if (stmt->kind == VAR_SET_DEFAULT || |
| stmt->kind == VAR_RESET || |
| stmt->kind == VAR_RESET_ALL) |
| { |
| if (Gp_role == GP_ROLE_DISPATCH) |
| { |
| /* |
| * RESET must be dispatched different, because it can't |
| * be in a user transaction |
| */ |
| StringInfoData buffer; |
| |
| initStringInfo(&buffer); |
| |
| if (stmt->kind == VAR_RESET_ALL) |
| appendStringInfo(&buffer, "RESET ALL"); |
| else |
| appendStringInfo(&buffer, "RESET %s", stmt->name); |
| |
| CdbDispatchSetCommand(buffer.data, false); |
| } |
| } |
| |
| /* 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: |
| return GetConfigOptionByName(stmt->name, NULL, false); |
| 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) |
| { |
| SetPGVariableOptDispatch(name, args, is_local, true); |
| } |
| |
| /* GPDB: Like SetPGVariable, but with extra 'gp_dispatch' parameter. */ |
| void |
| SetPGVariableOptDispatch(const char *name, List *args, bool is_local, bool gp_dispatch) |
| { |
| 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); |
| |
| if (gp_dispatch) |
| DispatchSetPGVariable(name, args, is_local); |
| } |
| |
| /* |
| * 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); |
| |
| if (Gp_role == GP_ROLE_DISPATCH && !IsBootstrapProcessingMode()) |
| { |
| StringInfoData buffer; |
| char *quoted_name; |
| char *quoted_value = NULL; |
| |
| initStringInfo(&buffer); |
| |
| quoted_name = quote_literal_cstr(name); |
| if (value) |
| quoted_value = quote_literal_cstr(value); |
| |
| appendStringInfo(&buffer, "SELECT pg_catalog.set_config(%s, %s, %s)", |
| quoted_name, |
| quoted_value ? quoted_value : "NULL", |
| is_local ? "true" : "false"); |
| |
| if (quoted_value) |
| pfree(quoted_value); |
| pfree(quoted_name); |
| |
| CdbDispatchSetCommand(buffer.data, false /* cancelOnError */ ); |
| } |
| |
| /* 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; |
| } |
| |
| |
| static void |
| DispatchSetPGVariable(const char *name, List *args, bool is_local) |
| { |
| ListCell *l; |
| StringInfoData buffer; |
| |
| if (Gp_role != GP_ROLE_DISPATCH || IsBootstrapProcessingMode()) |
| return; |
| |
| /* |
| * client_encoding is always kept at SQL_ASCII in QE processes. (See also |
| * cdbconn_doConnectStart().) |
| */ |
| if (strcmp(name, "client_encoding") == 0) |
| return; |
| |
| initStringInfo( &buffer ); |
| |
| if (args == NIL) |
| { |
| appendStringInfo(&buffer, "RESET %s", name); |
| } |
| else |
| { |
| appendStringInfo(&buffer, "SET "); |
| if (is_local) |
| appendStringInfo(&buffer, "LOCAL "); |
| |
| appendStringInfo(&buffer, "%s TO ", quote_identifier(name)); |
| |
| /* |
| * GPDB: We handle the timezone GUC specially. This is because the |
| * timezone GUC can be set with the SET TIME ZONE .. syntax which is an |
| * alias for SET timezone. Instead of dispatching the SET TIME ZONE .. |
| * as a special case, we dispatch the already set time zone from the QD |
| * with the usual SET syntax flavor (SET timezone TO <>). |
| * Please refer to Issue: #9055 for additional detail. |
| * #9055 - https://github.com/greenplum-db/gpdb/issues/9055 |
| */ |
| if (strcmp(name, "timezone") == 0) |
| appendStringInfo(&buffer, "%s", |
| quote_literal_cstr(GetConfigOptionByName("timezone", |
| NULL, |
| false))); |
| else |
| { |
| foreach(l, args) |
| { |
| Node *arg = (Node *) lfirst(l); |
| char *val; |
| A_Const *con; |
| |
| if (l != list_head(args)) |
| appendStringInfo(&buffer, ", "); |
| |
| if (IsA(arg, TypeCast)) |
| { |
| TypeCast *tc = (TypeCast *) arg; |
| arg = tc->arg; |
| } |
| |
| con = (A_Const *) arg; |
| |
| if (!IsA(con, A_Const)) |
| elog(ERROR, "unrecognized node type: %d", (int) nodeTag(arg)); |
| |
| switch (nodeTag(&con->val)) |
| { |
| case T_Integer: |
| appendStringInfo(&buffer, "%d", intVal(&con->val)); |
| break; |
| case T_Float: |
| /* represented as a string, so just copy it */ |
| appendStringInfo(&buffer, "%f", floatVal(&con->val)); |
| break; |
| case T_String: |
| val = strVal(&con->val); |
| |
| /* |
| * Plain string literal or identifier. Quote it. |
| */ |
| appendStringInfo(&buffer, "%s", quote_literal_cstr(val)); |
| |
| break; |
| default: |
| elog(ERROR, "unrecognized node type: %d", |
| (int) nodeTag(&con->val)); |
| break; |
| } |
| } |
| } |
| } |
| |
| CdbDispatchSetCommand(buffer.data, false); |
| } |