Defaults for run-time configuration variables
</para></entry>
</row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>setuser</structfield> <type>bool[]</type>
+ </para>
+ <para>
+ Values of <link linkend="sql-alterrole-user-set"><literal>USER SET</literal></link>
+ flag for every setting in <structfield>setconfig</structfield>
+ </para></entry>
+ </row>
</tbody>
</tgroup>
</table>
ALTER DATABASE <replaceable class="parameter">name</replaceable> REFRESH COLLATION VERSION
-ALTER DATABASE <replaceable class="parameter">name</replaceable> SET <replaceable>configuration_parameter</replaceable> { TO | = } { <replaceable>value</replaceable> | DEFAULT }
+ALTER DATABASE <replaceable class="parameter">name</replaceable> SET <replaceable>configuration_parameter</replaceable> { TO | = } { <replaceable>value</replaceable> | <replaceable>value</replaceable> USER SET | DEFAULT }
ALTER DATABASE <replaceable class="parameter">name</replaceable> SET <replaceable>configuration_parameter</replaceable> FROM CURRENT
ALTER DATABASE <replaceable class="parameter">name</replaceable> RESET <replaceable>configuration_parameter</replaceable>
ALTER DATABASE <replaceable class="parameter">name</replaceable> RESET ALL
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><literal>USER SET</literal></term>
+ <listitem>
+ <para>
+ 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 <xref linkend="runtime-config-custom"/>. The variable won't
+ be set if it appears to require superuser privileges.
+ </para>
+ </listitem>
+ </varlistentry>
</variablelist>
</refsect1>
ALTER ROLE <replaceable class="parameter">name</replaceable> RENAME TO <replaceable>new_name</replaceable>
-ALTER ROLE { <replaceable class="parameter">role_specification</replaceable> | ALL } [ IN DATABASE <replaceable class="parameter">database_name</replaceable> ] SET <replaceable>configuration_parameter</replaceable> { TO | = } { <replaceable>value</replaceable> | DEFAULT }
+ALTER ROLE { <replaceable class="parameter">role_specification</replaceable> | ALL } [ IN DATABASE <replaceable class="parameter">database_name</replaceable> ] SET <replaceable>configuration_parameter</replaceable> { TO | = } { <replaceable>value</replaceable> | <replaceable>value</replaceable> USER SET | DEFAULT }
ALTER ROLE { <replaceable class="parameter">role_specification</replaceable> | ALL } [ IN DATABASE <replaceable class="parameter">database_name</replaceable> ] SET <replaceable>configuration_parameter</replaceable> FROM CURRENT
ALTER ROLE { <replaceable class="parameter">role_specification</replaceable> | ALL } [ IN DATABASE <replaceable class="parameter">database_name</replaceable> ] RESET <replaceable>configuration_parameter</replaceable>
ALTER ROLE { <replaceable class="parameter">role_specification</replaceable> | ALL } [ IN DATABASE <replaceable class="parameter">database_name</replaceable> ] RESET ALL
</para>
</listitem>
</varlistentry>
+
+ <varlistentry id="sql-alterrole-user-set">
+ <term><literal>USER SET</literal></term>
+ <listitem>
+ <para>
+ 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 <xref linkend="runtime-config-custom"/>. The variable won't
+ be set if it appears to require superuser privileges.
+ </para>
+ </listitem>
+ </varlistentry>
</variablelist>
</refsect1>
<programlisting>
ALTER ROLE fred IN DATABASE devel SET client_min_messages = DEBUG;
+</programlisting></para>
+
+ <para>
+ Give a role a non-default placeholder setting on behalf of ordinary user:
+
+<programlisting>
+ALTER ROLE fred SET my.param = 'value' USER SET;
</programlisting></para>
</refsect1>
ALTER USER <replaceable class="parameter">name</replaceable> RENAME TO <replaceable>new_name</replaceable>
-ALTER USER { <replaceable class="parameter">role_specification</replaceable> | ALL } [ IN DATABASE <replaceable class="parameter">database_name</replaceable> ] SET <replaceable>configuration_parameter</replaceable> { TO | = } { <replaceable>value</replaceable> | DEFAULT }
+ALTER USER { <replaceable class="parameter">role_specification</replaceable> | ALL } [ IN DATABASE <replaceable class="parameter">database_name</replaceable> ] SET <replaceable>configuration_parameter</replaceable> { TO | = } { <replaceable>value</replaceable> | <replaceable>value</replaceable> USER SET | DEFAULT }
ALTER USER { <replaceable class="parameter">role_specification</replaceable> | ALL } [ IN DATABASE <replaceable class="parameter">database_name</replaceable> ] SET <replaceable>configuration_parameter</replaceable> FROM CURRENT
ALTER USER { <replaceable class="parameter">role_specification</replaceable> | ALL } [ IN DATABASE <replaceable class="parameter">database_name</replaceable> ] RESET <replaceable>configuration_parameter</replaceable>
ALTER USER { <replaceable class="parameter">role_specification</replaceable> | ALL } [ IN DATABASE <replaceable class="parameter">database_name</replaceable> ] RESET ALL
commands are used to define per-role and per-database configuration
settings.
</para>
+
+ <para>
+ Since <productname>PostgreSQL</productname> 16 the output includes
+ column with the values of
+ <link linkend="sql-alterrole-user-set"><literal>USER SET</literal></link>
+ flag for each setting.
+ </para>
</listitem>
</varlistentry>
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)
{
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);
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);
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);
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);
}
}
{
save_nestlevel = NewGUCNestLevel();
ProcessGUCArray(set_items,
+ NULL,
(superuser() ? PGC_SUSET : PGC_USERSET),
PGC_S_SESSION,
GUC_ACTION_SAVE);
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);
}
}
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);
switch (elmtype)
{
case CHAROID:
+ case BOOLOID:
elmlen = 1;
elmbyval = true;
elmalign = TYPALIGN_CHAR;
if (fcache->proconfig)
{
ProcessGUCArray(fcache->proconfig,
+ NULL,
(superuser() ? PGC_SUSET : PGC_USERSET),
PGC_S_SESSION,
GUC_ACTION_SAVE);
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);
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);
{
*name = palloc(equal_pos + 1);
strlcpy(*name, string, equal_pos + 1);
-
*value = pstrdup(&string[equal_pos + 1]);
}
else
* 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;
for (i = 1; i <= ARR_DIMS(array)[0]; i++)
{
Datum d;
+ Datum userSetDatum = BoolGetDatum(false);
bool isnull;
char *s;
char *name;
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);
* 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;
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);
/* 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;
}
-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;
}
* 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)
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;
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,
-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;
}
* 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;
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;
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,
-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;
}
* 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)
{
{
/*
* 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;
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;
}
*/
void
makeAlterConfigCommand(PGconn *conn, const char *configitem,
+ const char *userset,
const char *type, const char *name,
const char *type2, const char *name2,
PQExpBuffer buf)
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);
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);
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);
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);
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);
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;
}
}
}
+ /* 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"))
*/
/* yyyymmddN */
-#define CATALOG_VERSION_NO 202212061
+#define CATALOG_VERSION_NO 202212091
#endif
#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;
char *name; /* variable to be set */
List *args; /* List of A_Const nodes */
bool is_local; /* SET LOCAL? */
+ bool user_set;
} VariableSetStmt;
/* ----------------------
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);
test_misc \
test_oat_hooks \
test_parser \
+ test_pg_db_role_setting \
test_pg_dump \
test_predtest \
test_rbtree \
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')
--- /dev/null
+# Generated subdirectories
+/log/
+/results/
+/tmp_check/
--- /dev/null
+# 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
--- /dev/null
+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)
+
--- /dev/null
+# 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'],
+ },
+}
--- /dev/null
+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;
--- /dev/null
+/* 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;
--- /dev/null
+/*--------------------------------------------------------------------------
+ *
+ * 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();
+}
--- /dev/null
+# 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
(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"