From 662abcc9c3c312955577e02f521576ac8949152f Mon Sep 17 00:00:00 2001 From: Alexander Korotkov Date: Sun, 20 Nov 2022 03:29:09 +0300 Subject: [PATCH] ALTER ROLE ... SET ... TO ... USER SET --- doc/src/sgml/ref/alter_database.sgml | 15 ++- doc/src/sgml/ref/alter_role.sgml | 22 ++++- src/backend/catalog/pg_db_role_setting.c | 4 +- src/backend/commands/functioncmds.c | 2 +- src/backend/parser/gram.y | 20 ++++ src/backend/utils/misc/guc.c | 111 ++++++++++++++++------- src/backend/utils/misc/guc_funcs.c | 12 ++- src/bin/pg_dump/dumputils.c | 18 +++- src/include/common/guc-common.h | 31 +++++++ src/include/nodes/parsenodes.h | 1 + src/include/utils/guc.h | 4 +- 11 files changed, 198 insertions(+), 42 deletions(-) create mode 100644 src/include/common/guc-common.h diff --git a/doc/src/sgml/ref/alter_database.sgml b/doc/src/sgml/ref/alter_database.sgml index 89ed261b4c2..697db7b720f 100644 --- a/doc/src/sgml/ref/alter_database.sgml +++ b/doc/src/sgml/ref/alter_database.sgml @@ -37,7 +37,7 @@ ALTER DATABASE name SET TABLESPACE ALTER DATABASE name REFRESH COLLATION VERSION -ALTER DATABASE name SET configuration_parameter { TO | = } { value | DEFAULT } +ALTER DATABASE name SET configuration_parameter { TO | = } { value | value USER SET | DEFAULT } ALTER DATABASE name SET configuration_parameter FROM CURRENT ALTER DATABASE name RESET configuration_parameter ALTER DATABASE name RESET ALL @@ -206,6 +206,19 @@ ALTER DATABASE name RESET ALL + + + USER SET + + + Specifies that variable should be set on behalf of ordinal role. + That lets ordinal role set placeholder variables, which permission + requirements is not known yet; see . + The value set wouldn't be used if variable finally appear to require + superuser privileges. + + + diff --git a/doc/src/sgml/ref/alter_role.sgml b/doc/src/sgml/ref/alter_role.sgml index 5aa5648ae7b..0c51d1d0561 100644 --- a/doc/src/sgml/ref/alter_role.sgml +++ b/doc/src/sgml/ref/alter_role.sgml @@ -38,7 +38,7 @@ ALTER ROLE role_specification [ WIT ALTER ROLE name RENAME TO new_name -ALTER ROLE { role_specification | ALL } [ IN DATABASE database_name ] SET configuration_parameter { TO | = } { value | DEFAULT } +ALTER ROLE { role_specification | ALL } [ IN DATABASE database_name ] SET configuration_parameter { TO | = } { value | value USER SET | DEFAULT } ALTER ROLE { role_specification | ALL } [ IN DATABASE database_name ] SET configuration_parameter FROM CURRENT ALTER ROLE { role_specification | ALL } [ IN DATABASE database_name ] RESET configuration_parameter ALTER ROLE { role_specification | ALL } [ IN DATABASE database_name ] RESET ALL @@ -234,6 +234,19 @@ ALTER ROLE { role_specification | A + + + USER SET + + + Specifies that variable should be set on behalf of ordinal role. + That lets ordinal role set placeholder variables, which permission + requirements is not known yet; see . + The value set wouldn't be used if variable finally appear to require + superuser privileges. + + + @@ -329,6 +342,13 @@ ALTER ROLE worker_bee SET maintenance_work_mem = 100000; ALTER ROLE fred IN DATABASE devel SET client_min_messages = DEBUG; + + + + Give a role a non-default placeholder setting on behalf of ordinal user. + + +ALTER ROLE fred SET my.param = 'value' USER SET; diff --git a/src/backend/catalog/pg_db_role_setting.c b/src/backend/catalog/pg_db_role_setting.c index 42387f4e304..4b9a39a953d 100644 --- a/src/backend/catalog/pg_db_role_setting.c +++ b/src/backend/catalog/pg_db_role_setting.c @@ -115,7 +115,7 @@ AlterSetting(Oid databaseid, Oid roleid, VariableSetStmt *setstmt) /* Update (valuestr is NULL in RESET cases) */ if (valuestr) - a = GUCArrayAdd(a, setstmt->name, valuestr); + a = GUCArrayAdd(a, setstmt->name, valuestr, setstmt->user_set); else a = GUCArrayDelete(a, setstmt->name); @@ -141,7 +141,7 @@ AlterSetting(Oid databaseid, Oid roleid, VariableSetStmt *setstmt) memset(nulls, false, sizeof(nulls)); - a = GUCArrayAdd(NULL, setstmt->name, valuestr); + a = GUCArrayAdd(NULL, setstmt->name, valuestr, setstmt->user_set); values[Anum_pg_db_role_setting_setdatabase - 1] = ObjectIdGetDatum(databaseid); diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c index 1f820c93e96..950fd28badc 100644 --- a/src/backend/commands/functioncmds.c +++ b/src/backend/commands/functioncmds.c @@ -662,7 +662,7 @@ update_proconfig_value(ArrayType *a, List *set_items) char *valuestr = ExtractSetVariableArgs(sstmt); if (valuestr) - a = GUCArrayAdd(a, sstmt->name, valuestr); + a = GUCArrayAdd(a, sstmt->name, valuestr, sstmt->user_set); else /* RESET */ a = GUCArrayDelete(a, sstmt->name); } diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 737bd2d06d5..c38398d8528 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -1622,6 +1622,26 @@ generic_set: n->args = $3; $$ = n; } + | var_name TO var_list USER SET + { + VariableSetStmt *n = makeNode(VariableSetStmt); + + n->kind = VAR_SET_VALUE; + n->name = $1; + n->args = $3; + n->user_set = true; + $$ = n; + } + | var_name '=' var_list USER SET + { + VariableSetStmt *n = makeNode(VariableSetStmt); + + n->kind = VAR_SET_VALUE; + n->name = $1; + n->args = $3; + n->user_set = true; + $$ = n; + } | var_name TO DEFAULT { VariableSetStmt *n = makeNode(VariableSetStmt); diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index 117a2d26a0e..b311142ff2f 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -224,7 +224,6 @@ static bool reporting_enabled; /* true to enable GUC_REPORT */ static int GUCNestLevel = 0; /* 1 when in main transaction */ - static int guc_var_compare(const void *a, const void *b); static uint32 guc_name_hash(const void *key, Size keysize); static int guc_name_match(const void *key1, const void *key2, Size keysize); @@ -244,7 +243,7 @@ static void reapply_stacked_values(struct config_generic *variable, GucContext curscontext, GucSource cursource, Oid cursrole); static bool validate_option_array_item(const char *name, const char *value, - bool skipIfNoPermissions); + bool user_set, bool skipIfNoPermissions); static void write_auto_conf_file(int fd, const char *filename, ConfigVariable *head); static void replace_auto_config_value(ConfigVariable **head_p, ConfigVariable **tail_p, const char *name, const char *value); @@ -6159,13 +6158,16 @@ RestoreGUCState(void *gucstate) /* * A little "long argument" simulation, although not quite GNU - * compliant. Takes a string of the form "some-option=some value" and - * returns name = "some_option" and value = "some value" in palloc'ed - * storage. Note that '-' is converted to '_' in the option name. If - * there is no '=' in the input string then value will be NULL. + * compliant. Takes a string of the form "some-option=some value" or + * "some-option(u)=some value" and returns name = "some_option" and + * value = "some value" in palloc'ed storage. If user_set is not null then + * the presence of "(u)" flag is stored there. Note that '-' is converted + * to '_' in the option name. If there is no '=' in the input string then + * value will be NULL. */ -void -ParseLongOption(const char *string, char **name, char **value) +static void +ParseLongOptionInternal(const char *string, char **name, char **value, + bool *user_set) { size_t equal_pos; char *cp; @@ -6176,25 +6178,41 @@ ParseLongOption(const char *string, char **name, char **value) equal_pos = strcspn(string, "="); - if (string[equal_pos] == '=') + if (GUC_ARRAY_IS_USERSET_SIGN_BEFORE(string, string + equal_pos)) + { + *name = palloc(equal_pos - GUC_ARRAY_USERSET_SIGN_LEN + 1); + strlcpy(*name, string, equal_pos - GUC_ARRAY_USERSET_SIGN_LEN + 1); + if (user_set) + *user_set = true; + } + else { *name = palloc(equal_pos + 1); strlcpy(*name, string, equal_pos + 1); + if (user_set) + *user_set = false; + } + if (string[equal_pos] == '=') *value = pstrdup(&string[equal_pos + 1]); - } else - { - /* no equal sign in string */ - *name = pstrdup(string); *value = NULL; - } for (cp = *name; *cp; cp++) if (*cp == '-') *cp = '_'; } +/* + * The exported version of ParseLongOptionInternal(). Doesn't need user_set + * argument since no external users need it. + */ +void +ParseLongOption(const char *string, char **name, char **value) +{ + ParseLongOptionInternal(string, name, value, NULL); +} + /* * Handle options fetched from pg_db_role_setting.setconfig, @@ -6220,6 +6238,7 @@ ProcessGUCArray(ArrayType *array, char *s; char *name; char *value; + bool user_set; d = array_ref(array, 1, &i, -1 /* varlenarray */ , @@ -6233,7 +6252,7 @@ ProcessGUCArray(ArrayType *array, s = TextDatumGetCString(d); - ParseLongOption(s, &name, &value); + ParseLongOptionInternal(s, &name, &value, &user_set); if (!value) { ereport(WARNING, @@ -6245,7 +6264,7 @@ ProcessGUCArray(ArrayType *array, } (void) set_config_option(name, value, - context, source, + user_set ? PGC_USERSET : context, source, action, true, 0, false); pfree(name); @@ -6260,7 +6279,8 @@ ProcessGUCArray(ArrayType *array, * to indicate the current table entry is NULL. */ ArrayType * -GUCArrayAdd(ArrayType *array, const char *name, const char *value) +GUCArrayAdd(ArrayType *array, const char *name, const char *value, + bool user_set) { struct config_generic *record; Datum datum; @@ -6271,7 +6291,7 @@ GUCArrayAdd(ArrayType *array, const char *name, const char *value) Assert(value); /* test if the option is valid and we're allowed to set it */ - (void) validate_option_array_item(name, value, false); + (void) validate_option_array_item(name, value, user_set, false); /* normalize name (converts obsolete GUC names to modern spellings) */ record = find_option(name, false, true, WARNING); @@ -6279,7 +6299,11 @@ GUCArrayAdd(ArrayType *array, const char *name, const char *value) name = record->name; /* build new item for array */ - newval = psprintf("%s=%s", name, value); + if (user_set) + newval = psprintf("%s" GUC_ARRAY_USERSET_SIGN "=%s", + name, value); + else + newval = psprintf("%s=%s", name, value); datum = CStringGetTextDatum(newval); if (array) @@ -6309,9 +6333,17 @@ GUCArrayAdd(ArrayType *array, const char *name, const char *value) continue; current = TextDatumGetCString(d); - /* check for match up through and including '=' */ - if (strncmp(current, newval, strlen(name) + 1) == 0) + /* check for the name match */ + if (strncmp(current, newval, strlen(name)) == 0 && + GUC_ARRAY_IS_NAME_BORDER(current + strlen(name))) { + /* + * Recheck permissons if we found an option without USER SET + * flag while we're setting an optionn with USER SET flag. + */ + if (current[strlen(name)] == '=' && user_set) + (void) validate_option_array_item(name, value, + false, false); index = i; break; } @@ -6347,9 +6379,6 @@ GUCArrayDelete(ArrayType *array, const char *name) Assert(name); - /* test if the option is valid and we're allowed to set it */ - (void) validate_option_array_item(name, NULL, false); - /* normalize name (converts obsolete GUC names to modern spellings) */ record = find_option(name, false, true, WARNING); if (record) @@ -6379,9 +6408,15 @@ GUCArrayDelete(ArrayType *array, const char *name) val = TextDatumGetCString(d); /* ignore entry if it's what we want to delete */ - if (strncmp(val, name, strlen(name)) == 0 - && val[strlen(name)] == '=') + if (strncmp(val, name, strlen(name)) == 0 && + GUC_ARRAY_IS_NAME_BORDER(val + strlen(name))) + { + /* test if the option is valid and we're allowed to set it */ + (void) validate_option_array_item(name, NULL, + GUC_ARRAY_IS_USERSET_SIGN(val + strlen(name)), + false); continue; + } /* else add it to the output array */ if (newarray) @@ -6431,6 +6466,7 @@ GUCArrayReset(ArrayType *array) char *val; char *eqsgn; bool isnull; + bool user_set = false; d = array_ref(array, 1, &i, -1 /* varlenarray */ , @@ -6443,10 +6479,18 @@ GUCArrayReset(ArrayType *array) val = TextDatumGetCString(d); eqsgn = strchr(val, '='); - *eqsgn = '\0'; + if (GUC_ARRAY_IS_USERSET_SIGN_BEFORE(val, eqsgn)) + { + *(eqsgn - GUC_ARRAY_USERSET_SIGN_LEN) = '\0'; + user_set = true; + } + else + { + *eqsgn = '\0'; + } /* skip if we have permission to delete it */ - if (validate_option_array_item(val, NULL, true)) + if (validate_option_array_item(val, NULL, user_set, true)) continue; /* else add it to the output array */ @@ -6472,15 +6516,16 @@ GUCArrayReset(ArrayType *array) * Validate a proposed option setting for GUCArrayAdd/Delete/Reset. * * name is the option name. value is the proposed value for the Add case, - * or NULL for the Delete/Reset cases. If skipIfNoPermissions is true, it's - * not an error to have no permissions to set the option. + * or NULL for the Delete/Reset cases. user_set indicates this is the USER SET + * option. If skipIfNoPermissions is true, it's not an error to have no + * permissions to set the option. * * Returns true if OK, false if skipIfNoPermissions is true and user does not * have permission to change this option (all other error cases result in an * error being thrown). */ static bool -validate_option_array_item(const char *name, const char *value, +validate_option_array_item(const char *name, const char *value, bool user_set, bool skipIfNoPermissions) { @@ -6516,8 +6561,10 @@ validate_option_array_item(const char *name, const char *value, { /* * We cannot do any meaningful check on the value, so only permissions - * are useful to check. + * are useful to check. USER SET options are always allowed. */ + if (user_set) + return true; if (superuser() || pg_parameter_aclcheck(name, GetUserId(), ACL_SET) == ACLCHECK_OK) return true; diff --git a/src/backend/utils/misc/guc_funcs.c b/src/backend/utils/misc/guc_funcs.c index 108b3bd1290..963921710cd 100644 --- a/src/backend/utils/misc/guc_funcs.c +++ b/src/backend/utils/misc/guc_funcs.c @@ -166,12 +166,22 @@ ExecSetVariableStmt(VariableSetStmt *stmt, bool isTopLevel) 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); + { + 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; } diff --git a/src/bin/pg_dump/dumputils.c b/src/bin/pg_dump/dumputils.c index 6e501a54138..4316232d1d1 100644 --- a/src/bin/pg_dump/dumputils.c +++ b/src/bin/pg_dump/dumputils.c @@ -16,6 +16,7 @@ #include +#include "common/guc-common.h" #include "dumputils.h" #include "fe_utils/string_utils.h" @@ -804,8 +805,8 @@ SplitGUCList(char *rawstring, char separator, /* * Helper function for dumping "ALTER DATABASE/ROLE SET ..." commands. * - * Parse the contents of configitem (a "name=value" string), wrap it in - * a complete ALTER command, and append it to buf. + * Parse the contents of configitem (a "name=value" or "name(u)=value" string), + * wrap it in a complete ALTER command, and append it to buf. * * type is DATABASE or ROLE, and name is the name of the database or role. * If we need an "IN" clause, type2 and name2 similarly define what to put @@ -820,6 +821,7 @@ makeAlterConfigCommand(PGconn *conn, const char *configitem, { char *mine; char *pos; + bool user_set = false; /* Parse the configitem. If we can't find an "=", silently do nothing. */ mine = pg_strdup(configitem); @@ -829,7 +831,13 @@ makeAlterConfigCommand(PGconn *conn, const char *configitem, pg_free(mine); return; } - *pos++ = '\0'; + if (GUC_ARRAY_IS_USERSET_SIGN_BEFORE(mine, pos)) + { + user_set = true; + *(pos - GUC_ARRAY_USERSET_SIGN_LEN) = '\0'; + } + else + *pos++ = '\0'; /* Build the command, with suitable quoting for everything. */ appendPQExpBuffer(buf, "ALTER %s %s ", type, fmtId(name)); @@ -872,6 +880,10 @@ makeAlterConfigCommand(PGconn *conn, const char *configitem, else appendStringLiteralConn(buf, pos, conn); + /* Add USER SET flag if specified in the string */ + if (user_set) + appendPQExpBufferStr(buf, "USER SET;\n"); + appendPQExpBufferStr(buf, ";\n"); pg_free(mine); diff --git a/src/include/common/guc-common.h b/src/include/common/guc-common.h new file mode 100644 index 00000000000..8b31953789e --- /dev/null +++ b/src/include/common/guc-common.h @@ -0,0 +1,31 @@ +/*------------------------------------------------------------------------- + * + * guc-common.h + * Common declarations for Grand Unified Configuration. + * + * + * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/common/guc-common.h + * + *------------------------------------------------------------------------- + */ +#ifndef GUC_COMMON_H +#define GUC_COMMON_H + +/* + * The designator of USER SET value in GUC array. + */ +#define GUC_ARRAY_USERSET_SIGN "(u)" +#define GUC_ARRAY_USERSET_SIGN_LEN \ + (sizeof(GUC_ARRAY_USERSET_SIGN) - 1) +#define GUC_ARRAY_IS_USERSET_SIGN(s) \ + (strncmp((s), GUC_ARRAY_USERSET_SIGN, GUC_ARRAY_USERSET_SIGN_LEN) == 0) +#define GUC_ARRAY_IS_USERSET_SIGN_BEFORE(start, eqsign) \ + ((eqsign) - (start) >= GUC_ARRAY_USERSET_SIGN_LEN && \ + GUC_ARRAY_IS_USERSET_SIGN((eqsign) - GUC_ARRAY_USERSET_SIGN_LEN)) +#define GUC_ARRAY_IS_NAME_BORDER(s) \ + ((*(s)) == '=' || GUC_ARRAY_IS_USERSET_SIGN(s)) + +#endif /* GUC_COMMON_H */ diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 7e7ad3f7e47..d48acda7c7b 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -2228,6 +2228,7 @@ typedef struct VariableSetStmt char *name; /* variable to be set */ List *args; /* List of A_Const nodes */ bool is_local; /* SET LOCAL? */ + bool user_set; } VariableSetStmt; /* ---------------------- diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h index b3aaff9665b..9802973f086 100644 --- a/src/include/utils/guc.h +++ b/src/include/utils/guc.h @@ -12,6 +12,7 @@ #ifndef GUC_H #define GUC_H +#include "common/guc-common.h" #include "nodes/parsenodes.h" #include "tcop/dest.h" #include "utils/array.h" @@ -393,7 +394,8 @@ extern char *GetConfigOptionByName(const char *name, const char **varname, extern void ProcessGUCArray(ArrayType *array, GucContext context, GucSource source, GucAction action); -extern ArrayType *GUCArrayAdd(ArrayType *array, const char *name, const char *value); +extern ArrayType *GUCArrayAdd(ArrayType *array, const char *name, + const char *value, bool user_set); extern ArrayType *GUCArrayDelete(ArrayType *array, const char *name); extern ArrayType *GUCArrayReset(ArrayType *array); -- 2.24.3 (Apple Git-128)