diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index 9ed2b020b7..9316b811ac 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -3194,6 +3194,16 @@ SCRAM-SHA-256$<iteration count>:&l Defaults for run-time configuration variables + + + + setuser bool[] + + + Values of USER SET + flag for every setting in setconfig + + diff --git a/doc/src/sgml/ref/alter_database.sgml b/doc/src/sgml/ref/alter_database.sgml index 89ed261b4c..181e9d3620 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 ordinary role. + That lets non-superuser and non-replication role to set placeholder + variables, with permission requirements is not known yet; + see . The variable won't + be set if it appears to require superuser privileges. + + + diff --git a/doc/src/sgml/ref/alter_role.sgml b/doc/src/sgml/ref/alter_role.sgml index 5aa5648ae7..33ac732707 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 ordinary role. + That lets non-superuser and non-replication role to set placeholder + variables, with permission requirements is not known yet; + see . The variable won't + be set if it appears 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 ordinary user: + + +ALTER ROLE fred SET my.param = 'value' USER SET; diff --git a/doc/src/sgml/ref/alter_user.sgml b/doc/src/sgml/ref/alter_user.sgml index 0ee89f54c5..24f737d587 100644 --- a/doc/src/sgml/ref/alter_user.sgml +++ b/doc/src/sgml/ref/alter_user.sgml @@ -38,7 +38,7 @@ ALTER USER role_specification [ WIT ALTER USER name RENAME TO new_name -ALTER USER { role_specification | ALL } [ IN DATABASE database_name ] SET configuration_parameter { TO | = } { value | DEFAULT } +ALTER USER { role_specification | ALL } [ IN DATABASE database_name ] SET configuration_parameter { TO | = } { value | value USER SET | DEFAULT } ALTER USER { role_specification | ALL } [ IN DATABASE database_name ] SET configuration_parameter FROM CURRENT ALTER USER { role_specification | ALL } [ IN DATABASE database_name ] RESET configuration_parameter ALTER USER { role_specification | ALL } [ IN DATABASE database_name ] RESET ALL diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index d3dd638b14..8a5285da9a 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -1897,6 +1897,13 @@ INSERT INTO tbl1 VALUES ($1, $2) \bind 'first value' 'second value' \g commands are used to define per-role and per-database configuration settings. + + + Since PostgreSQL 16 the output includes + column with the values of + USER SET + flag for each setting. + diff --git a/src/backend/catalog/pg_db_role_setting.c b/src/backend/catalog/pg_db_role_setting.c index 42387f4e30..6572fcd965 100644 --- a/src/backend/catalog/pg_db_role_setting.c +++ b/src/backend/catalog/pg_db_role_setting.c @@ -63,14 +63,23 @@ AlterSetting(Oid databaseid, Oid roleid, VariableSetStmt *setstmt) if (HeapTupleIsValid(tuple)) { ArrayType *new = NULL; + ArrayType *usersetArray; Datum datum; + Datum usersetDatum; bool isnull; + bool usersetIsnull; datum = heap_getattr(tuple, Anum_pg_db_role_setting_setconfig, RelationGetDescr(rel), &isnull); + usersetDatum = heap_getattr(tuple, Anum_pg_db_role_setting_setuser, + RelationGetDescr(rel), &usersetIsnull); if (!isnull) - new = GUCArrayReset(DatumGetArrayTypeP(datum)); + { + Assert(!usersetIsnull); + usersetArray = DatumGetArrayTypeP(usersetDatum); + new = GUCArrayReset(DatumGetArrayTypeP(datum), &usersetArray); + } if (new) { @@ -86,6 +95,11 @@ AlterSetting(Oid databaseid, Oid roleid, VariableSetStmt *setstmt) repl_repl[Anum_pg_db_role_setting_setconfig - 1] = true; repl_null[Anum_pg_db_role_setting_setconfig - 1] = false; + repl_val[Anum_pg_db_role_setting_setuser - 1] = + PointerGetDatum(usersetArray); + repl_repl[Anum_pg_db_role_setting_setuser - 1] = true; + repl_null[Anum_pg_db_role_setting_setuser - 1] = false; + newtuple = heap_modify_tuple(tuple, RelationGetDescr(rel), repl_val, repl_null, repl_repl); CatalogTupleUpdate(rel, &tuple->t_self, newtuple); @@ -101,28 +115,39 @@ AlterSetting(Oid databaseid, Oid roleid, VariableSetStmt *setstmt) bool repl_repl[Natts_pg_db_role_setting]; HeapTuple newtuple; Datum datum; + Datum usersetDatum; bool isnull; + bool usersetIsnull; ArrayType *a; + ArrayType *usersetArray; memset(repl_repl, false, sizeof(repl_repl)); repl_repl[Anum_pg_db_role_setting_setconfig - 1] = true; repl_null[Anum_pg_db_role_setting_setconfig - 1] = false; + repl_repl[Anum_pg_db_role_setting_setuser - 1] = true; + repl_null[Anum_pg_db_role_setting_setuser - 1] = false; - /* Extract old value of setconfig */ + /* Extract old values of setconfig and setuser */ datum = heap_getattr(tuple, Anum_pg_db_role_setting_setconfig, RelationGetDescr(rel), &isnull); a = isnull ? NULL : DatumGetArrayTypeP(datum); + usersetDatum = heap_getattr(tuple, Anum_pg_db_role_setting_setuser, + RelationGetDescr(rel), &usersetIsnull); + usersetArray = usersetIsnull ? NULL : DatumGetArrayTypeP(usersetDatum); + /* Update (valuestr is NULL in RESET cases) */ if (valuestr) - a = GUCArrayAdd(a, setstmt->name, valuestr); + a = GUCArrayAdd(a, &usersetArray, setstmt->name, valuestr, setstmt->user_set); else - a = GUCArrayDelete(a, setstmt->name); + a = GUCArrayDelete(a, &usersetArray, setstmt->name); if (a) { repl_val[Anum_pg_db_role_setting_setconfig - 1] = PointerGetDatum(a); + repl_val[Anum_pg_db_role_setting_setuser - 1] = + PointerGetDatum(usersetArray); newtuple = heap_modify_tuple(tuple, RelationGetDescr(rel), repl_val, repl_null, repl_repl); @@ -137,16 +162,18 @@ AlterSetting(Oid databaseid, Oid roleid, VariableSetStmt *setstmt) HeapTuple newtuple; Datum values[Natts_pg_db_role_setting]; bool nulls[Natts_pg_db_role_setting]; - ArrayType *a; + ArrayType *a, + *usersetArray; memset(nulls, false, sizeof(nulls)); - a = GUCArrayAdd(NULL, setstmt->name, valuestr); + a = GUCArrayAdd(NULL, &usersetArray, setstmt->name, valuestr, setstmt->user_set); values[Anum_pg_db_role_setting_setdatabase - 1] = ObjectIdGetDatum(databaseid); values[Anum_pg_db_role_setting_setrole - 1] = ObjectIdGetDatum(roleid); values[Anum_pg_db_role_setting_setconfig - 1] = PointerGetDatum(a); + values[Anum_pg_db_role_setting_setuser - 1] = PointerGetDatum(usersetArray); newtuple = heap_form_tuple(RelationGetDescr(rel), values, nulls); CatalogTupleInsert(rel, newtuple); @@ -240,20 +267,25 @@ ApplySetting(Snapshot snapshot, Oid databaseid, Oid roleid, while (HeapTupleIsValid(tup = systable_getnext(scan))) { bool isnull; + bool usersetIsnull; Datum datum; + Datum usersetDatum; datum = heap_getattr(tup, Anum_pg_db_role_setting_setconfig, RelationGetDescr(relsetting), &isnull); + usersetDatum = heap_getattr(tup, Anum_pg_db_role_setting_setuser, + RelationGetDescr(relsetting), &usersetIsnull); if (!isnull) { ArrayType *a = DatumGetArrayTypeP(datum); + ArrayType *usersetArray = DatumGetArrayTypeP(usersetDatum); /* * We process all the options at SUSET level. We assume that the * right to insert an option into pg_db_role_setting was checked * when it was inserted. */ - ProcessGUCArray(a, PGC_SUSET, source, GUC_ACTION_SET); + ProcessGUCArray(a, usersetArray, PGC_SUSET, source, GUC_ACTION_SET); } } diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c index 69f43aa0ec..e3f9d0b5cf 100644 --- a/src/backend/catalog/pg_proc.c +++ b/src/backend/catalog/pg_proc.c @@ -698,6 +698,7 @@ ProcedureCreate(const char *procedureName, { save_nestlevel = NewGUCNestLevel(); ProcessGUCArray(set_items, + NULL, (superuser() ? PGC_SUSET : PGC_USERSET), PGC_S_SESSION, GUC_ACTION_SAVE); diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c index 57489f65f2..f020fe5ec8 100644 --- a/src/backend/commands/functioncmds.c +++ b/src/backend/commands/functioncmds.c @@ -662,9 +662,9 @@ update_proconfig_value(ArrayType *a, List *set_items) char *valuestr = ExtractSetVariableArgs(sstmt); if (valuestr) - a = GUCArrayAdd(a, sstmt->name, valuestr); + a = GUCArrayAdd(a, NULL, sstmt->name, valuestr, sstmt->user_set); else /* RESET */ - a = GUCArrayDelete(a, sstmt->name); + a = GUCArrayDelete(a, NULL, sstmt->name); } } diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index b1ae5f834c..adc3f8ced3 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -1621,6 +1621,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/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c index 495e449a9e..59a0852d07 100644 --- a/src/backend/utils/adt/arrayfuncs.c +++ b/src/backend/utils/adt/arrayfuncs.c @@ -3344,6 +3344,7 @@ construct_array_builtin(Datum *elems, int nelems, Oid elmtype) switch (elmtype) { case CHAROID: + case BOOLOID: elmlen = 1; elmbyval = true; elmalign = TYPALIGN_CHAR; diff --git a/src/backend/utils/fmgr/fmgr.c b/src/backend/utils/fmgr/fmgr.c index 3c210297aa..cd0daa7e16 100644 --- a/src/backend/utils/fmgr/fmgr.c +++ b/src/backend/utils/fmgr/fmgr.c @@ -706,6 +706,7 @@ fmgr_security_definer(PG_FUNCTION_ARGS) if (fcache->proconfig) { ProcessGUCArray(fcache->proconfig, + NULL, (superuser() ? PGC_SUSET : PGC_USERSET), PGC_S_SESSION, GUC_ACTION_SAVE); diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index 28313b3a94..c6326d5053 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -225,7 +225,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); @@ -245,7 +244,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); @@ -6182,7 +6181,6 @@ ParseLongOption(const char *string, char **name, char **value) { *name = palloc(equal_pos + 1); strlcpy(*name, string, equal_pos + 1); - *value = pstrdup(&string[equal_pos + 1]); } else @@ -6205,7 +6203,7 @@ ParseLongOption(const char *string, char **name, char **value) * The array parameter must be an array of TEXT (it must not be NULL). */ void -ProcessGUCArray(ArrayType *array, +ProcessGUCArray(ArrayType *array, ArrayType *usersetArray, GucContext context, GucSource source, GucAction action) { int i; @@ -6218,6 +6216,7 @@ ProcessGUCArray(ArrayType *array, for (i = 1; i <= ARR_DIMS(array)[0]; i++) { Datum d; + Datum userSetDatum = BoolGetDatum(false); bool isnull; char *s; char *name; @@ -6246,9 +6245,29 @@ ProcessGUCArray(ArrayType *array, continue; } - (void) set_config_option(name, value, - context, source, - action, true, 0, false); + if (usersetArray) + userSetDatum = array_ref(usersetArray, 1, &i, + -1 /* varlenarray */ , + sizeof(bool) /* BOOL's typlen */ , + true /* BOOL's typbyval */ , + TYPALIGN_CHAR /* BOOL's typalign */ , + &isnull); + if (isnull) + userSetDatum = BoolGetDatum(false); + + /* + * USER SET values are appliciable only for PGC_USERSET parameters. We + * use InvalidOid as role in order to evade possible privileges of the + * current user. + */ + if (!DatumGetBool(userSetDatum)) + (void) set_config_option(name, value, + context, source, + action, true, 0, false); + else + (void) set_config_option_ext(name, value, + PGC_USERSET, source, InvalidOid, + action, true, 0, false); pfree(name); pfree(value); @@ -6262,7 +6281,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, ArrayType **usersetArray, + const char *name, const char *value, bool user_set) { struct config_generic *record; Datum datum; @@ -6273,7 +6293,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); @@ -6314,6 +6334,27 @@ GUCArrayAdd(ArrayType *array, const char *name, const char *value) /* check for match up through and including '=' */ if (strncmp(current, newval, strlen(name) + 1) == 0) { + bool currentUserSet = false; + + if (usersetArray) + { + currentUserSet = DatumGetBool(array_ref(*usersetArray, 1, &i, + -1 /* varlenarray */ , + sizeof(bool) /* BOOL's typlen */ , + true /* BOOL's typbyval */ , + TYPALIGN_CHAR /* BOOL's typalign */ , + &isnull)); + if (isnull) + currentUserSet = false; + } + + /* + * Recheck permissons if we found an option without USER SET + * flag while we're setting an optionn with USER SET flag. + */ + if (!currentUserSet && user_set) + (void) validate_option_array_item(name, value, + false, false); index = i; break; } @@ -6326,9 +6367,25 @@ GUCArrayAdd(ArrayType *array, const char *name, const char *value) -1 /* TEXT's typlen */ , false /* TEXT's typbyval */ , TYPALIGN_INT /* TEXT's typalign */ ); + + if (usersetArray) + *usersetArray = array_set(*usersetArray, 1, &index, + BoolGetDatum(user_set), + false, + -1 /* varlena array */ , + sizeof(bool) /* BOOL's typlen */ , + true /* BOOL's typbyval */ , + TYPALIGN_CHAR /* BOOL's typalign */ ); } else + { a = construct_array_builtin(&datum, 1, TEXTOID); + if (usersetArray) + { + datum = BoolGetDatum(user_set); + *usersetArray = construct_array_builtin(&datum, 1, BOOLOID); + } + } return a; } @@ -6340,18 +6397,16 @@ GUCArrayAdd(ArrayType *array, const char *name, const char *value) * is NULL then a null should be stored. */ ArrayType * -GUCArrayDelete(ArrayType *array, const char *name) +GUCArrayDelete(ArrayType *array, ArrayType **usersetArray, const char *name) { struct config_generic *record; ArrayType *newarray; + ArrayType *newUsersetArray; int i; int index; 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) @@ -6362,11 +6417,13 @@ GUCArrayDelete(ArrayType *array, const char *name) return NULL; newarray = NULL; + newUsersetArray = NULL; index = 1; for (i = 1; i <= ARR_DIMS(array)[0]; i++) { Datum d; + Datum userSetDatum = BoolGetDatum(false); char *val; bool isnull; @@ -6380,13 +6437,29 @@ GUCArrayDelete(ArrayType *array, const char *name) continue; val = TextDatumGetCString(d); + if (usersetArray) + userSetDatum = array_ref(*usersetArray, 1, &i, + -1 /* varlenarray */ , + sizeof(bool) /* BOOL's typlen */ , + true /* BOOL's typbyval */ , + TYPALIGN_CHAR /* BOOL's typalign */ , + &isnull); + if (isnull) + userSetDatum = BoolGetDatum(false); + /* ignore entry if it's what we want to delete */ if (strncmp(val, name, strlen(name)) == 0 && val[strlen(name)] == '=') + { + /* test if the option is valid and we're allowed to set it */ + (void) validate_option_array_item(name, NULL, + DatumGetBool(userSetDatum), false); continue; + } /* else add it to the output array */ if (newarray) + { newarray = array_set(newarray, 1, &index, d, false, @@ -6394,12 +6467,28 @@ GUCArrayDelete(ArrayType *array, const char *name) -1 /* TEXT's typlen */ , false /* TEXT's typbyval */ , TYPALIGN_INT /* TEXT's typalign */ ); + if (usersetArray) + newUsersetArray = array_set(newUsersetArray, 1, &index, + userSetDatum, + false, + -1 /* varlena array */ , + sizeof(bool) /* BOOL's typlen */ , + true /* BOOL's typbyval */ , + TYPALIGN_CHAR /* BOOL's typalign */ ); + } else + { newarray = construct_array_builtin(&d, 1, TEXTOID); + if (usersetArray) + newUsersetArray = construct_array_builtin(&d, 1, BOOLOID); + } index++; } + if (usersetArray) + *usersetArray = newUsersetArray; + return newarray; } @@ -6410,9 +6499,10 @@ GUCArrayDelete(ArrayType *array, const char *name) * those that are PGC_USERSET or we have permission to set */ ArrayType * -GUCArrayReset(ArrayType *array) +GUCArrayReset(ArrayType *array, ArrayType **usersetArray) { ArrayType *newarray; + ArrayType *newUsersetArray; int i; int index; @@ -6425,11 +6515,13 @@ GUCArrayReset(ArrayType *array) return NULL; newarray = NULL; + newUsersetArray = NULL; index = 1; for (i = 1; i <= ARR_DIMS(array)[0]; i++) { Datum d; + Datum userSetDatum = BoolGetDatum(false); char *val; char *eqsgn; bool isnull; @@ -6444,15 +6536,27 @@ GUCArrayReset(ArrayType *array) continue; val = TextDatumGetCString(d); + if (usersetArray) + userSetDatum = array_ref(*usersetArray, 1, &i, + -1 /* varlenarray */ , + sizeof(bool) /* BOOL's typlen */ , + true /* BOOL's typbyval */ , + TYPALIGN_CHAR /* BOOL's typalign */ , + &isnull); + if (isnull) + userSetDatum = BoolGetDatum(false); + eqsgn = strchr(val, '='); *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, + DatumGetBool(userSetDatum), true)) continue; /* else add it to the output array */ if (newarray) + { newarray = array_set(newarray, 1, &index, d, false, @@ -6460,13 +6564,29 @@ GUCArrayReset(ArrayType *array) -1 /* TEXT's typlen */ , false /* TEXT's typbyval */ , TYPALIGN_INT /* TEXT's typalign */ ); + if (usersetArray) + newUsersetArray = array_set(newUsersetArray, 1, &index, + userSetDatum, + false, + -1 /* varlena array */ , + sizeof(bool) /* BOOL's typlen */ , + true /* BOOL's typbyval */ , + TYPALIGN_CHAR /* BOOL's typalign */ ); + } else + { newarray = construct_array_builtin(&d, 1, TEXTOID); + if (usersetArray) + newUsersetArray = construct_array_builtin(&userSetDatum, 1, BOOLOID); + } index++; pfree(val); } + if (usersetArray) + *usersetArray = newUsersetArray; + return newarray; } @@ -6474,15 +6594,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) { @@ -6518,8 +6639,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 108b3bd129..23da603fe7 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 9311417f18..c0985fae5a 100644 --- a/src/bin/pg_dump/dumputils.c +++ b/src/bin/pg_dump/dumputils.c @@ -816,6 +816,7 @@ SplitGUCList(char *rawstring, char separator, */ void makeAlterConfigCommand(PGconn *conn, const char *configitem, + const char *userset, const char *type, const char *name, const char *type2, const char *name2, PQExpBuffer buf) @@ -874,6 +875,10 @@ makeAlterConfigCommand(PGconn *conn, const char *configitem, else appendStringLiteralConn(buf, pos, conn); + /* Add USER SET flag if specified in the string */ + if (userset && !strcmp(userset, "t")) + appendPQExpBufferStr(buf, " USER SET"); + appendPQExpBufferStr(buf, ";\n"); pg_free(mine); diff --git a/src/bin/pg_dump/dumputils.h b/src/bin/pg_dump/dumputils.h index c67c3b5b84..6e7f50c6b8 100644 --- a/src/bin/pg_dump/dumputils.h +++ b/src/bin/pg_dump/dumputils.h @@ -59,6 +59,7 @@ extern bool SplitGUCList(char *rawstring, char separator, char ***namelist); extern void makeAlterConfigCommand(PGconn *conn, const char *configitem, + const char *userset, const char *type, const char *name, const char *type2, const char *name2, PQExpBuffer buf); diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index ad6693c358..44d957c038 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -3270,32 +3270,49 @@ dumpDatabaseConfig(Archive *AH, PQExpBuffer outbuf, PGresult *res; /* First collect database-specific options */ - printfPQExpBuffer(buf, "SELECT unnest(setconfig) FROM pg_db_role_setting " + printfPQExpBuffer(buf, "SELECT unnest(setconfig)"); + if (AH->remoteVersion >= 160000) + appendPQExpBufferStr(buf, ", unnest(setuser)"); + appendPQExpBuffer(buf, " FROM pg_db_role_setting " "WHERE setrole = 0 AND setdatabase = '%u'::oid", dboid); res = ExecuteSqlQuery(AH, buf->data, PGRES_TUPLES_OK); for (int i = 0; i < PQntuples(res); i++) - makeAlterConfigCommand(conn, PQgetvalue(res, i, 0), + { + char *userset = NULL; + + if (AH->remoteVersion >= 160000) + userset = PQgetvalue(res, i, 1); + makeAlterConfigCommand(conn, PQgetvalue(res, i, 0), userset, "DATABASE", dbname, NULL, NULL, outbuf); + } PQclear(res); /* Now look for role-and-database-specific options */ - printfPQExpBuffer(buf, "SELECT rolname, unnest(setconfig) " - "FROM pg_db_role_setting s, pg_roles r " + printfPQExpBuffer(buf, "SELECT rolname, unnest(setconfig)"); + if (AH->remoteVersion >= 160000) + appendPQExpBufferStr(buf, ", unnest(setuser)"); + appendPQExpBuffer(buf, " FROM pg_db_role_setting s, pg_roles r " "WHERE setrole = r.oid AND setdatabase = '%u'::oid", dboid); res = ExecuteSqlQuery(AH, buf->data, PGRES_TUPLES_OK); for (int i = 0; i < PQntuples(res); i++) - makeAlterConfigCommand(conn, PQgetvalue(res, i, 1), + { + char *userset = NULL; + + if (AH->remoteVersion >= 160000) + userset = PQgetvalue(res, i, 2); + makeAlterConfigCommand(conn, PQgetvalue(res, i, 1), userset, "ROLE", PQgetvalue(res, i, 0), "DATABASE", dbname, outbuf); + } PQclear(res); diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c index a87262e333..7b40081678 100644 --- a/src/bin/pg_dump/pg_dumpall.c +++ b/src/bin/pg_dump/pg_dumpall.c @@ -1384,7 +1384,10 @@ dumpUserConfig(PGconn *conn, const char *username) PQExpBuffer buf = createPQExpBuffer(); PGresult *res; - printfPQExpBuffer(buf, "SELECT unnest(setconfig) FROM pg_db_role_setting " + printfPQExpBuffer(buf, "SELECT unnest(setconfig)"); + if (server_version >= 160000) + appendPQExpBufferStr(buf, ", unnest(setuser)"); + appendPQExpBuffer(buf, " FROM pg_db_role_setting " "WHERE setdatabase = 0 AND setrole = " "(SELECT oid FROM %s WHERE rolname = ", role_catalog); @@ -1398,8 +1401,13 @@ dumpUserConfig(PGconn *conn, const char *username) for (int i = 0; i < PQntuples(res); i++) { + char *userset = NULL; + + if (server_version >= 160000) + userset = PQgetvalue(res, i, 1); + resetPQExpBuffer(buf); - makeAlterConfigCommand(conn, PQgetvalue(res, i, 0), + makeAlterConfigCommand(conn, PQgetvalue(res, i, 0), userset, "ROLE", username, NULL, NULL, buf); fprintf(OPF, "%s", buf->data); diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c index 2eae519b1d..df166365e8 100644 --- a/src/bin/psql/describe.c +++ b/src/bin/psql/describe.c @@ -3769,13 +3769,16 @@ listDbRoleSettings(const char *pattern, const char *pattern2) initPQExpBuffer(&buf); printfPQExpBuffer(&buf, "SELECT rolname AS \"%s\", datname AS \"%s\",\n" - "pg_catalog.array_to_string(setconfig, E'\\n') AS \"%s\"\n" - "FROM pg_catalog.pg_db_role_setting s\n" - "LEFT JOIN pg_catalog.pg_database d ON d.oid = setdatabase\n" - "LEFT JOIN pg_catalog.pg_roles r ON r.oid = setrole\n", + "pg_catalog.array_to_string(setconfig, E'\\n') AS \"%s\"", gettext_noop("Role"), gettext_noop("Database"), gettext_noop("Settings")); + if (pset.sversion >= 160000) + appendPQExpBuffer(&buf, ",\npg_catalog.array_to_string(setuser, E'\\n') AS \"%s\"", + gettext_noop("User set")); + appendPQExpBuffer(&buf, "\nFROM pg_catalog.pg_db_role_setting s\n" + "LEFT JOIN pg_catalog.pg_database d ON d.oid = setdatabase\n" + "LEFT JOIN pg_catalog.pg_roles r ON r.oid = setrole\n"); if (!validateSQLNamePattern(&buf, pattern, false, false, NULL, "r.rolname", NULL, NULL, &havewhere, 1)) goto error_return; diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c index 89e7317c23..7d222680f5 100644 --- a/src/bin/psql/tab-complete.c +++ b/src/bin/psql/tab-complete.c @@ -4442,6 +4442,10 @@ psql_completion(const char *text, int start, int end) } } } + /* Complete ALTER DATABASE|ROLE|USER ... SET ... TO ... USER SET */ + else if (HeadMatches("ALTER", "DATABASE|ROLE|USER") && + TailMatches("SET", MatchAny, "TO|=", MatchAny)) + COMPLETE_WITH("USER SET"); /* START TRANSACTION */ else if (Matches("START")) diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index 51e5acfe4f..17958aebf4 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -57,6 +57,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 202212061 +#define CATALOG_VERSION_NO 202212091 #endif diff --git a/src/include/catalog/pg_db_role_setting.h b/src/include/catalog/pg_db_role_setting.h index f92e867df4..c52bbf665f 100644 --- a/src/include/catalog/pg_db_role_setting.h +++ b/src/include/catalog/pg_db_role_setting.h @@ -41,6 +41,8 @@ CATALOG(pg_db_role_setting,2964,DbRoleSettingRelationId) BKI_SHARED_RELATION #ifdef CATALOG_VARLEN /* variable-length fields start here */ text setconfig[1]; /* GUC settings to apply at login */ + + bool setuser[1]; /* USER SET flags for GUC settings */ #endif } FormData_pg_db_role_setting; diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 6a6d3293e4..8fe9b2fcfe 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -2257,6 +2257,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 b3aaff9665..91cc341854 100644 --- a/src/include/utils/guc.h +++ b/src/include/utils/guc.h @@ -391,11 +391,14 @@ extern void AlterSystemSetConfigFile(AlterSystemStmt *altersysstmt); extern char *GetConfigOptionByName(const char *name, const char **varname, bool missing_ok); -extern void ProcessGUCArray(ArrayType *array, +extern void ProcessGUCArray(ArrayType *array, ArrayType *usersetArray, GucContext context, GucSource source, GucAction action); -extern ArrayType *GUCArrayAdd(ArrayType *array, const char *name, const char *value); -extern ArrayType *GUCArrayDelete(ArrayType *array, const char *name); -extern ArrayType *GUCArrayReset(ArrayType *array); +extern ArrayType *GUCArrayAdd(ArrayType *array, ArrayType **usersetArray, + const char *name, const char *value, + bool user_set); +extern ArrayType *GUCArrayDelete(ArrayType *array, ArrayType **usersetArray, + const char *name); +extern ArrayType *GUCArrayReset(ArrayType *array, ArrayType **usersetArray); extern void *guc_malloc(int elevel, size_t size); extern pg_nodiscard void *guc_realloc(int elevel, void *old, size_t size); diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile index 96addded81..c629cbe383 100644 --- a/src/test/modules/Makefile +++ b/src/test/modules/Makefile @@ -25,6 +25,7 @@ SUBDIRS = \ test_misc \ test_oat_hooks \ test_parser \ + test_pg_db_role_setting \ test_pg_dump \ test_predtest \ test_rbtree \ diff --git a/src/test/modules/meson.build b/src/test/modules/meson.build index 1d26544854..911a768a29 100644 --- a/src/test/modules/meson.build +++ b/src/test/modules/meson.build @@ -19,6 +19,7 @@ subdir('test_lfind') subdir('test_misc') subdir('test_oat_hooks') subdir('test_parser') +subdir('test_pg_db_role_setting') subdir('test_pg_dump') subdir('test_predtest') subdir('test_rbtree') diff --git a/src/test/modules/test_pg_db_role_setting/.gitignore b/src/test/modules/test_pg_db_role_setting/.gitignore new file mode 100644 index 0000000000..5dcb3ff972 --- /dev/null +++ b/src/test/modules/test_pg_db_role_setting/.gitignore @@ -0,0 +1,4 @@ +# Generated subdirectories +/log/ +/results/ +/tmp_check/ diff --git a/src/test/modules/test_pg_db_role_setting/Makefile b/src/test/modules/test_pg_db_role_setting/Makefile new file mode 100644 index 0000000000..aacd78f74c --- /dev/null +++ b/src/test/modules/test_pg_db_role_setting/Makefile @@ -0,0 +1,29 @@ +# src/test/modules/test_pg_db_role_setting/Makefile + +MODULE_big = test_pg_db_role_setting +OBJS = \ + $(WIN32RES) \ + test_pg_db_role_setting.o +EXTENSION = test_pg_db_role_setting +DATA = test_pg_db_role_setting--1.0.sql + +PGFILEDESC = "test_pg_db_role_setting - tests for default GUC values stored in pg_db_role_settings" + +REGRESS = test_pg_db_role_setting + +# disable installcheck for now +NO_INSTALLCHECK = 1 +# and also for now force NO_LOCALE and UTF8 +ENCODING = UTF8 +NO_LOCALE = 1 + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = src/test/modules/test_pg_db_role_setting +top_builddir = ../../../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/src/test/modules/test_pg_db_role_setting/expected/test_pg_db_role_setting.out b/src/test/modules/test_pg_db_role_setting/expected/test_pg_db_role_setting.out new file mode 100644 index 0000000000..4da17dca28 --- /dev/null +++ b/src/test/modules/test_pg_db_role_setting/expected/test_pg_db_role_setting.out @@ -0,0 +1,143 @@ +CREATE EXTENSION test_pg_db_role_setting; +CREATE USER super_user SUPERUSER; +CREATE USER regular_user; +\c - regular_user +-- successfully set a placeholder value +SET test_pg_db_role_setting.superuser_param = 'aaa'; +-- module is loaded, the placeholder value is thrown away +SELECT load_test_pg_db_role_setting(); +WARNING: permission denied to set parameter "test_pg_db_role_setting.superuser_param" + load_test_pg_db_role_setting +------------------------------ + +(1 row) + +SHOW test_pg_db_role_setting.superuser_param; + test_pg_db_role_setting.superuser_param +----------------------------------------- + superuser_param_value +(1 row) + +SHOW test_pg_db_role_setting.user_param; + test_pg_db_role_setting.user_param +------------------------------------ + user_param_value +(1 row) + +\c - regular_user +-- fail, not privileges +ALTER ROLE regular_user SET test_pg_db_role_setting.superuser_param = 'aaa'; +ERROR: permission denied to set parameter "test_pg_db_role_setting.superuser_param" +ALTER ROLE regular_user SET test_pg_db_role_setting.user_param = 'bbb'; +ERROR: permission denied to set parameter "test_pg_db_role_setting.user_param" +-- success for USER SET parameters +ALTER ROLE regular_user SET test_pg_db_role_setting.superuser_param = 'aaa' USER SET; +ALTER ROLE regular_user SET test_pg_db_role_setting.user_param = 'bbb' USER SET; +\drds regular_user + List of settings + Role | Database | Settings | User set +--------------+----------+---------------------------------------------+---------- + regular_user | | test_pg_db_role_setting.superuser_param=aaa+| t + + | | test_pg_db_role_setting.user_param=bbb | t +(1 row) + +\c - regular_user +-- successfully set placeholders +SHOW test_pg_db_role_setting.superuser_param; + test_pg_db_role_setting.superuser_param +----------------------------------------- + aaa +(1 row) + +SHOW test_pg_db_role_setting.user_param; + test_pg_db_role_setting.user_param +------------------------------------ + bbb +(1 row) + +-- module is loaded, the placeholder value of superuser param is thrown away +SELECT load_test_pg_db_role_setting(); +WARNING: permission denied to set parameter "test_pg_db_role_setting.superuser_param" + load_test_pg_db_role_setting +------------------------------ + +(1 row) + +SHOW test_pg_db_role_setting.superuser_param; + test_pg_db_role_setting.superuser_param +----------------------------------------- + superuser_param_value +(1 row) + +SHOW test_pg_db_role_setting.user_param; + test_pg_db_role_setting.user_param +------------------------------------ + bbb +(1 row) + +\c - super_user +ALTER ROLE regular_user SET test_pg_db_role_setting.superuser_param = 'aaa'; +\drds regular_user + List of settings + Role | Database | Settings | User set +--------------+----------+---------------------------------------------+---------- + regular_user | | test_pg_db_role_setting.superuser_param=aaa+| f + + | | test_pg_db_role_setting.user_param=bbb | t +(1 row) + +\c - regular_user +-- don't have a priviledge to change superuser value to user set one +ALTER ROLE regular_user SET test_pg_db_role_setting.superuser_param = 'ccc' USER SET; +ERROR: permission denied to set parameter "test_pg_db_role_setting.superuser_param" +\c - super_user +SELECT load_test_pg_db_role_setting(); + load_test_pg_db_role_setting +------------------------------ + +(1 row) + +-- give the privilege to set SUSET param to the regular user +GRANT SET ON PARAMETER test_pg_db_role_setting.superuser_param TO regular_user; +\c - regular_user +ALTER ROLE regular_user SET test_pg_db_role_setting.superuser_param = 'ccc'; +\drds regular_user + List of settings + Role | Database | Settings | User set +--------------+----------+---------------------------------------------+---------- + regular_user | | test_pg_db_role_setting.superuser_param=ccc+| f + + | | test_pg_db_role_setting.user_param=bbb | t +(1 row) + +\c - regular_user +-- successfully set placeholders +SHOW test_pg_db_role_setting.superuser_param; + test_pg_db_role_setting.superuser_param +----------------------------------------- + ccc +(1 row) + +SHOW test_pg_db_role_setting.user_param; + test_pg_db_role_setting.user_param +------------------------------------ + bbb +(1 row) + +-- module is loaded, and placeholder values are succesfully set +SELECT load_test_pg_db_role_setting(); + load_test_pg_db_role_setting +------------------------------ + +(1 row) + +SHOW test_pg_db_role_setting.superuser_param; + test_pg_db_role_setting.superuser_param +----------------------------------------- + ccc +(1 row) + +SHOW test_pg_db_role_setting.user_param; + test_pg_db_role_setting.user_param +------------------------------------ + bbb +(1 row) + diff --git a/src/test/modules/test_pg_db_role_setting/meson.build b/src/test/modules/test_pg_db_role_setting/meson.build new file mode 100644 index 0000000000..3a6410cca2 --- /dev/null +++ b/src/test/modules/test_pg_db_role_setting/meson.build @@ -0,0 +1,35 @@ +# FIXME: prevent install during main install, but not during test :/ + +test_pg_db_role_setting_sources = files( + 'test_pg_db_role_setting.c', +) + +if host_system == 'windows' + test_pg_db_role_setting_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'test_pg_db_role_setting', + '--FILEDESC', 'test_pg_db_role_setting - tests for default GUC values stored in pg_db_role_settings',]) +endif + +test_pg_db_role_setting = shared_module('test_pg_db_role_setting', + test_pg_db_role_setting_sources, + kwargs: pg_mod_args, +) +testprep_targets += test_pg_db_role_setting + +install_data( + 'test_pg_db_role_setting.control', + 'test_pg_db_role_setting--1.0.sql', + kwargs: contrib_data_args, +) + +tests += { + 'name': 'test_pg_db_role_setting', + 'sd': meson.current_source_dir(), + 'bd': meson.current_build_dir(), + 'regress': { + 'sql': [ + 'test_pg_db_role_setting', + ], + 'regress_args': ['--no-locale', '--encoding=UTF8'], + }, +} diff --git a/src/test/modules/test_pg_db_role_setting/sql/test_pg_db_role_setting.sql b/src/test/modules/test_pg_db_role_setting/sql/test_pg_db_role_setting.sql new file mode 100644 index 0000000000..cb6eb0448e --- /dev/null +++ b/src/test/modules/test_pg_db_role_setting/sql/test_pg_db_role_setting.sql @@ -0,0 +1,63 @@ +CREATE EXTENSION test_pg_db_role_setting; +CREATE USER super_user SUPERUSER; +CREATE USER regular_user; + +\c - regular_user +-- successfully set a placeholder value +SET test_pg_db_role_setting.superuser_param = 'aaa'; + +-- module is loaded, the placeholder value is thrown away +SELECT load_test_pg_db_role_setting(); + +SHOW test_pg_db_role_setting.superuser_param; +SHOW test_pg_db_role_setting.user_param; + +\c - regular_user +-- fail, not privileges +ALTER ROLE regular_user SET test_pg_db_role_setting.superuser_param = 'aaa'; +ALTER ROLE regular_user SET test_pg_db_role_setting.user_param = 'bbb'; +-- success for USER SET parameters +ALTER ROLE regular_user SET test_pg_db_role_setting.superuser_param = 'aaa' USER SET; +ALTER ROLE regular_user SET test_pg_db_role_setting.user_param = 'bbb' USER SET; + +\drds regular_user + +\c - regular_user +-- successfully set placeholders +SHOW test_pg_db_role_setting.superuser_param; +SHOW test_pg_db_role_setting.user_param; + +-- module is loaded, the placeholder value of superuser param is thrown away +SELECT load_test_pg_db_role_setting(); + +SHOW test_pg_db_role_setting.superuser_param; +SHOW test_pg_db_role_setting.user_param; + +\c - super_user +ALTER ROLE regular_user SET test_pg_db_role_setting.superuser_param = 'aaa'; +\drds regular_user + +\c - regular_user +-- don't have a priviledge to change superuser value to user set one +ALTER ROLE regular_user SET test_pg_db_role_setting.superuser_param = 'ccc' USER SET; + +\c - super_user +SELECT load_test_pg_db_role_setting(); +-- give the privilege to set SUSET param to the regular user +GRANT SET ON PARAMETER test_pg_db_role_setting.superuser_param TO regular_user; + +\c - regular_user +ALTER ROLE regular_user SET test_pg_db_role_setting.superuser_param = 'ccc'; + +\drds regular_user + +\c - regular_user +-- successfully set placeholders +SHOW test_pg_db_role_setting.superuser_param; +SHOW test_pg_db_role_setting.user_param; + +-- module is loaded, and placeholder values are succesfully set +SELECT load_test_pg_db_role_setting(); + +SHOW test_pg_db_role_setting.superuser_param; +SHOW test_pg_db_role_setting.user_param; diff --git a/src/test/modules/test_pg_db_role_setting/test_pg_db_role_setting--1.0.sql b/src/test/modules/test_pg_db_role_setting/test_pg_db_role_setting--1.0.sql new file mode 100644 index 0000000000..1ed3d285c7 --- /dev/null +++ b/src/test/modules/test_pg_db_role_setting/test_pg_db_role_setting--1.0.sql @@ -0,0 +1,7 @@ +/* src/test/modules/test_pg_db_role_setting/test_pg_db_role_setting--1.0.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION test_pg_db_role_setting" to load this file. \quit + +CREATE FUNCTION load_test_pg_db_role_setting() RETURNS void + AS 'MODULE_PATHNAME' LANGUAGE C; diff --git a/src/test/modules/test_pg_db_role_setting/test_pg_db_role_setting.c b/src/test/modules/test_pg_db_role_setting/test_pg_db_role_setting.c new file mode 100644 index 0000000000..3982ae5629 --- /dev/null +++ b/src/test/modules/test_pg_db_role_setting/test_pg_db_role_setting.c @@ -0,0 +1,57 @@ +/*-------------------------------------------------------------------------- + * + * test_pg_db_role_setting.c + * Code for testing mandatory access control (MAC) using object access hooks. + * + * Copyright (c) 2022, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/test/modules/test_pg_db_role_setting/test_pg_db_role_setting.c + * + * ------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "utils/guc.h" + +PG_MODULE_MAGIC; + +PG_FUNCTION_INFO_V1(load_test_pg_db_role_setting); + +static char *superuser_param; +static char *user_param; + +/* + * Module load callback + */ +void +_PG_init(void) +{ + DefineCustomStringVariable("test_pg_db_role_setting.superuser_param", + "Sample superuser parameter.", + NULL, + &superuser_param, + "superuser_param_value", + PGC_SUSET, + 0, + NULL, NULL, NULL); + + DefineCustomStringVariable("test_pg_db_role_setting.user_param", + "Sample user parameter.", + NULL, + &user_param, + "user_param_value", + PGC_USERSET, + 0, + NULL, NULL, NULL); +} + +/* + * Empty function, which is used just to trigger load of this module. + */ +Datum +load_test_pg_db_role_setting(PG_FUNCTION_ARGS) +{ + PG_RETURN_VOID(); +} diff --git a/src/test/modules/test_pg_db_role_setting/test_pg_db_role_setting.control b/src/test/modules/test_pg_db_role_setting/test_pg_db_role_setting.control new file mode 100644 index 0000000000..9678cff376 --- /dev/null +++ b/src/test/modules/test_pg_db_role_setting/test_pg_db_role_setting.control @@ -0,0 +1,7 @@ +# test_pg_db_role_setting extension +comment = 'test_pg_db_role_setting - tests for default GUC values stored in pg_db_role_setting' +default_version = '1.0' +module_pathname = '$libdir/test_pg_db_role_setting' +relocatable = true +superuser = false +trusted = true diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index b4cb6ffb5b..8fc62cebd2 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -6188,9 +6188,9 @@ List of schemas (0 rows) \drds "no.such.setting" - List of settings - Role | Database | Settings -------+----------+---------- + List of settings + Role | Database | Settings | User set +------+----------+----------+---------- (0 rows) \dRp "no.such.publication"