Allow placeholders in ALTER ROLE w/o superuser
Hello hackers,
Using placeholders for application variables allows the use of RLS for
application users as shown in this blog post
https://www.2ndquadrant.com/en/blog/application-users-vs-row-level-security/
.
SET my.username = 'tomas'
CREATE POLICY chat_policy ON chat
USING (current_setting('my.username') IN (message_from, message_to))
WITH CHECK (message_from = current_setting('my.username'))
This technique has enabled postgres sidecar services(PostgREST,
PostGraphQL, etc) to keep the application security at the database level,
which has worked great.
However, defining placeholders at the role level require superuser:
alter role myrole set my.username to 'tomas';
ERROR: permission denied to set parameter "my.username"
Which is inconsistent and surprising behavior. I think it doesn't make
sense since you can already set them at the session or transaction
level(SET LOCAL my.username = 'tomas'). Enabling this would allow sidecar
services to store metadata scoped to its pertaining role.
I've attached a patch that removes this restriction. From my testing, this
doesn't affect permission checking when an extension defines its custom GUC
variables.
DefineCustomStringVariable("my.custom", NULL, NULL, &my_custom, NULL,
PGC_SUSET, ..);
Using PGC_SUSET or PGC_SIGHUP will fail accordingly. Also no tests fail
when doing "make installcheck".
---
Steve Chavez
Engineering at https://supabase.com/
Attachments:
0001-Allow-placeholders-in-ALTER-ROLE-w-o-superuser.patchtext/x-patch; charset=US-ASCII; name=0001-Allow-placeholders-in-ALTER-ROLE-w-o-superuser.patchDownload
From c3493373a8cddfff56d10bddce1dcdabc0722a34 Mon Sep 17 00:00:00 2001
From: Steve Chavez <steve@supabase.io>
Date: Sun, 5 Jun 2022 19:10:52 -0500
Subject: [PATCH] Allow placeholders in ALTER ROLE w/o superuser
Removes inconsistent superuser check for placeholders on
validate_option_array_item.
---
src/backend/utils/misc/guc.c | 24 +-----------------------
1 file changed, 1 insertion(+), 23 deletions(-)
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 8e9b71375c..7c83bc3004 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -11658,19 +11658,12 @@ validate_option_array_item(const char *name, const char *value,
struct config_generic *gconf;
/*
- * There are three cases to consider:
+ * There are two cases to consider:
*
* name is a known GUC variable. Check the value normally, check
* permissions normally (i.e., allow if variable is USERSET, or if it's
* SUSET and user is superuser).
*
- * name is not known, but exists or can be created as a placeholder (i.e.,
- * it has a valid custom name). We allow this case if you're a superuser,
- * otherwise not. Superusers are assumed to know what they're doing. We
- * can't allow it for other users, because when the placeholder is
- * resolved it might turn out to be a SUSET variable;
- * define_custom_variable assumes we checked that.
- *
* name is not known and can't be created as a placeholder. Throw error,
* unless skipIfNoPermissions is true, in which case return false.
*/
@@ -11681,21 +11674,6 @@ validate_option_array_item(const char *name, const char *value,
return false;
}
- if (gconf->flags & GUC_CUSTOM_PLACEHOLDER)
- {
- /*
- * We cannot do any meaningful check on the value, so only permissions
- * are useful to check.
- */
- if (superuser())
- return true;
- if (skipIfNoPermissions)
- return false;
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied to set parameter \"%s\"", name)));
- }
-
/* manual permissions check so we can avoid an error being thrown */
if (gconf->context == PGC_USERSET)
/* ok */ ;
--
2.32.0
On Sun, Jun 05, 2022 at 11:20:38PM -0500, Steve Chavez wrote:
However, defining placeholders at the role level require superuser:
alter role myrole set my.username to 'tomas';
ERROR: permission denied to set parameter "my.username"Which is inconsistent and surprising behavior. I think it doesn't make
sense since you can already set them at the session or transaction
level(SET LOCAL my.username = 'tomas'). Enabling this would allow sidecar
services to store metadata scoped to its pertaining role.I've attached a patch that removes this restriction. From my testing, this
doesn't affect permission checking when an extension defines its custom GUC
variables.DefineCustomStringVariable("my.custom", NULL, NULL, &my_custom, NULL,
PGC_SUSET, ..);Using PGC_SUSET or PGC_SIGHUP will fail accordingly. Also no tests fail
when doing "make installcheck".
IIUC you are basically proposing to revert a6dcd19 [0]/messages/by-id/4090.1258042387@sss.pgh.pa.us, but it is not clear
to me why that is safe. Am I missing something?
[0]: /messages/by-id/4090.1258042387@sss.pgh.pa.us
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
On Fri, Jul 01, 2022 at 04:40:27PM -0700, Nathan Bossart wrote:
On Sun, Jun 05, 2022 at 11:20:38PM -0500, Steve Chavez wrote:
However, defining placeholders at the role level require superuser:
alter role myrole set my.username to 'tomas';
ERROR: permission denied to set parameter "my.username"Which is inconsistent and surprising behavior. I think it doesn't make
sense since you can already set them at the session or transaction
level(SET LOCAL my.username = 'tomas'). Enabling this would allow sidecar
services to store metadata scoped to its pertaining role.I've attached a patch that removes this restriction. From my testing, this
doesn't affect permission checking when an extension defines its custom GUC
variables.DefineCustomStringVariable("my.custom", NULL, NULL, &my_custom, NULL,
PGC_SUSET, ..);Using PGC_SUSET or PGC_SIGHUP will fail accordingly. Also no tests fail
when doing "make installcheck".IIUC you are basically proposing to revert a6dcd19 [0], but it is not clear
to me why that is safe. Am I missing something?
I spent some more time looking into this, and I think I've constructed a
simple example that demonstrates the problem with removing this
restriction.
postgres=# CREATE ROLE test CREATEROLE;
CREATE ROLE
postgres=# CREATE ROLE other LOGIN;
CREATE ROLE
postgres=# GRANT CREATE ON DATABASE postgres TO other;
GRANT
postgres=# SET ROLE test;
SET
postgres=> ALTER ROLE other SET plperl.on_plperl_init = 'test';
ALTER ROLE
postgres=> \c postgres other
You are now connected to database "postgres" as user "other".
postgres=> CREATE EXTENSION plperl;
CREATE EXTENSION
postgres=> SHOW plperl.on_plperl_init;
plperl.on_plperl_init
-----------------------
test
(1 row)
In this example, the non-superuser role sets a placeholder GUC for another
role. This GUC becomes a PGC_SUSET GUC when plperl is loaded, so a
non-superuser role will have successfully set a PGC_SUSET GUC. If we had a
record of who ran ALTER ROLE, we might be able to apply appropriate
permissions checking when the module is loaded, but this information
doesn't exist in pg_db_role_setting. IIUC we have the following options:
1. Store information about who ran ALTER ROLE. I think there are a
couple of problems with this. For example, what happens if the
grantor was dropped or its privileges were altered? Should we
instead store the context of the user (i.e., PGC_USERSET or
PGC_SUSET)? Do we need to add entries to pg_depend?
2. Ignore or ERROR for any ALTER ROLE settings for custom GUCs. Since
we don't know who ran ALTER ROLE, we can't trust the value.
3. Require superuser to use ALTER ROLE for a placeholder. This is what
we do today. Since we know a superuser set the value, we can always
apply it when the custom GUC is finally defined.
If this is an accurate representation of the options, it seems clear why
the superuser restriction is in place.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
Thanks a lot for the feedback Nathan.
Taking your options into consideration, for me the correct behaviour should
be:
- The ALTER ROLE placeholder should always be stored with a PGC_USERSET
GucContext. It's a placeholder anyway, so it should be the less restrictive
one. If the user wants to define it as PGC_SUSET or other this should be
done through a custom extension.
- When an extension claims the placeholder, we should check the
DefineCustomXXXVariable GucContext with PGC_USERSET. If there's a match,
then the value gets applied, otherwise WARN or ERR.
The role GUCs get applied at login time right? So at this point we can
WARN or ERR about the defined role GUCs.
What do you think?
On Mon, 18 Jul 2022 at 19:03, Nathan Bossart <nathandbossart@gmail.com>
wrote:
Show quoted text
On Fri, Jul 01, 2022 at 04:40:27PM -0700, Nathan Bossart wrote:
On Sun, Jun 05, 2022 at 11:20:38PM -0500, Steve Chavez wrote:
However, defining placeholders at the role level require superuser:
alter role myrole set my.username to 'tomas';
ERROR: permission denied to set parameter "my.username"Which is inconsistent and surprising behavior. I think it doesn't make
sense since you can already set them at the session or transaction
level(SET LOCAL my.username = 'tomas'). Enabling this would allowsidecar
services to store metadata scoped to its pertaining role.
I've attached a patch that removes this restriction. From my testing,
this
doesn't affect permission checking when an extension defines its custom
GUC
variables.
DefineCustomStringVariable("my.custom", NULL, NULL, &my_custom,
NULL,
PGC_SUSET, ..);
Using PGC_SUSET or PGC_SIGHUP will fail accordingly. Also no tests fail
when doing "make installcheck".IIUC you are basically proposing to revert a6dcd19 [0], but it is not
clear
to me why that is safe. Am I missing something?
I spent some more time looking into this, and I think I've constructed a
simple example that demonstrates the problem with removing this
restriction.postgres=# CREATE ROLE test CREATEROLE;
CREATE ROLE
postgres=# CREATE ROLE other LOGIN;
CREATE ROLE
postgres=# GRANT CREATE ON DATABASE postgres TO other;
GRANT
postgres=# SET ROLE test;
SET
postgres=> ALTER ROLE other SET plperl.on_plperl_init = 'test';
ALTER ROLE
postgres=> \c postgres other
You are now connected to database "postgres" as user "other".
postgres=> CREATE EXTENSION plperl;
CREATE EXTENSION
postgres=> SHOW plperl.on_plperl_init;
plperl.on_plperl_init
-----------------------
test
(1 row)In this example, the non-superuser role sets a placeholder GUC for another
role. This GUC becomes a PGC_SUSET GUC when plperl is loaded, so a
non-superuser role will have successfully set a PGC_SUSET GUC. If we had a
record of who ran ALTER ROLE, we might be able to apply appropriate
permissions checking when the module is loaded, but this information
doesn't exist in pg_db_role_setting. IIUC we have the following options:1. Store information about who ran ALTER ROLE. I think there are a
couple of problems with this. For example, what happens if the
grantor was dropped or its privileges were altered? Should we
instead store the context of the user (i.e., PGC_USERSET or
PGC_SUSET)? Do we need to add entries to pg_depend?
2. Ignore or ERROR for any ALTER ROLE settings for custom GUCs.
Since
we don't know who ran ALTER ROLE, we can't trust the value.
3. Require superuser to use ALTER ROLE for a placeholder. This is
what
we do today. Since we know a superuser set the value, we can
always
apply it when the custom GUC is finally defined.If this is an accurate representation of the options, it seems clear why
the superuser restriction is in place.--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
On Tue, Jul 19, 2022 at 12:55:14AM -0500, Steve Chavez wrote:
Taking your options into consideration, for me the correct behaviour should
be:- The ALTER ROLE placeholder should always be stored with a PGC_USERSET
GucContext. It's a placeholder anyway, so it should be the less restrictive
one. If the user wants to define it as PGC_SUSET or other this should be
done through a custom extension.
- When an extension claims the placeholder, we should check the
DefineCustomXXXVariable GucContext with PGC_USERSET. If there's a match,
then the value gets applied, otherwise WARN or ERR.
The role GUCs get applied at login time right? So at this point we can
WARN or ERR about the defined role GUCs.What do you think?
Hm. I would expect ALTER ROLE to store the PGC_SUSET context when executed
by a superuser or a role with privileges via pg_parameter_acl. Storing all
placeholder GUC settings as PGC_USERSET would make things more restrictive
than they are today. For example, it would no longer be possible to apply
any ALTER ROLE settings from superusers for placeholders that later become
custom GUCS.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
At Tue, 19 Jul 2022 09:53:39 -0700, Nathan Bossart <nathandbossart@gmail.com> wrote in
On Tue, Jul 19, 2022 at 12:55:14AM -0500, Steve Chavez wrote:
Taking your options into consideration, for me the correct behaviour should
be:- The ALTER ROLE placeholder should always be stored with a PGC_USERSET
GucContext. It's a placeholder anyway, so it should be the less restrictive
one. If the user wants to define it as PGC_SUSET or other this should be
done through a custom extension.
- When an extension claims the placeholder, we should check the
DefineCustomXXXVariable GucContext with PGC_USERSET. If there's a match,
then the value gets applied, otherwise WARN or ERR.
The role GUCs get applied at login time right? So at this point we can
WARN or ERR about the defined role GUCs.What do you think?
Hm. I would expect ALTER ROLE to store the PGC_SUSET context when executed
by a superuser or a role with privileges via pg_parameter_acl. Storing all
placeholder GUC settings as PGC_USERSET would make things more restrictive
than they are today. For example, it would no longer be possible to apply
any ALTER ROLE settings from superusers for placeholders that later become
custom GUCS.
Currently placehoders are always created PGC_USERSET, thus
non-superuser can set it. But if loaded module defines the custom
variable as PGC_SUSET, the value set by the user is refused then the
value from ALTER-ROLE-SET or otherwise the default value from
DefineCustom*Variable is used. If the module defines it as
PGC_USERSET, the last value is accepted.
If a placehoders were created PGC_SUSET, non-superusers cannot set it
on-session. But that behavior is not needed since loadable modules
reject PGC_USERSET values as above.
Returning to the topic, that operation can be allowed in PG15, having
being granted by superuser using the GRANT SET ON PARMETER command.
=# GRANT SET ON PARAMETER my.username TO r1;
r1=> ALTER ROLE r1 SET my.username = 'hoge_user_x';
<success>
r1=> \c
r1=> => show my.username;
my.username
-------------
hoge_user_x
(1 row)
regards.
--
Kyotaro Horiguchi
NTT Open Source Software Center
Kyotaro Horiguchi <horikyota.ntt@gmail.com> writes:
At Tue, 19 Jul 2022 09:53:39 -0700, Nathan Bossart <nathandbossart@gmail.com> wrote in
Hm. I would expect ALTER ROLE to store the PGC_SUSET context when executed
by a superuser or a role with privileges via pg_parameter_acl. Storing all
placeholder GUC settings as PGC_USERSET would make things more restrictive
than they are today. For example, it would no longer be possible to apply
any ALTER ROLE settings from superusers for placeholders that later become
custom GUCS.
Returning to the topic, that operation can be allowed in PG15, having
being granted by superuser using the GRANT SET ON PARMETER command.
I think that 13d838815 has completely changed the terms that this
discussion needs to be conducted under. It seems clear to me now
that if you want to relax this only-superusers restriction, what
you have to do is store the OID of the role that issued ALTER ROLE/DB SET,
and then apply the same checks that would be used in the ordinary case
where a placeholder is being filled in after being set intra-session.
That is, we'd no longer assume that a value coming from pg_db_role_setting
was set with superuser privileges, but we'd know exactly who did set it.
This might also tie into Nathan's question in another thread about
exactly what permissions should be required to issue ALTER ROLE/DB SET.
In particular I'm wondering if different permissions should be needed to
override an existing entry than if there is no existing entry. If not,
we could find ourselves downgrading a superuser-set entry to a
non-superuser-set entry, which might have bad consequences later
(eg, by rendering the entry nonfunctional because when we actually
load the extension we find out the GUC is SUSET).
Possibly related to this: I felt while working on 13d838815 that
PGC_SUSET and PGC_SU_BACKEND should be usable as GucContext
values for GUC variables, indicating that the GUC requires special
privileges to be set, but we should no longer use them as passed-in
GucContext values. That is, we should remove privilege tests from
the call sites, like this:
(void) set_config_option(stmt->name,
ExtractSetVariableArgs(stmt),
- (superuser() ? PGC_SUSET : PGC_USERSET),
+ PGC_USERSET,
PGC_S_SESSION,
action, true, 0, false);
and instead put that behavior inside set_config_option_ext, which
would want to look at superuser_arg(srole) instead, and indeed might
not need to do anything because pg_parameter_aclcheck would subsume
the test. I didn't pursue this further because it wasn't essential
to fixing the bug. But it seems relevant here, because that line of
thought leads to the conclusion that storing PGC_SUSET vs PGC_USERSET
is entirely the wrong approach.
There is a bunch of infrastructure work that has to be done if anyone
wants to make this happen:
* redesign physical representation of pg_db_role_setting
* be sure to clean up if a role mentioned in pg_db_role_setting is dropped
* pg_dump would need to be taught to dump the state of affairs correctly.
regards, tom lane
On Wed, Jul 20, 2022 at 11:50:10AM -0400, Tom Lane wrote:
I think that 13d838815 has completely changed the terms that this
discussion needs to be conducted under. It seems clear to me now
that if you want to relax this only-superusers restriction, what
you have to do is store the OID of the role that issued ALTER ROLE/DB SET,
and then apply the same checks that would be used in the ordinary case
where a placeholder is being filled in after being set intra-session.
That is, we'd no longer assume that a value coming from pg_db_role_setting
was set with superuser privileges, but we'd know exactly who did set it.
I was imagining that the permissions checks would apply at ALTER ROLE/DB
SET time, not at login time. Otherwise, changing a role's privileges might
impact other roles' parameters, and it's not clear (at least to me) what
should happen when the role is dropped. Another reason I imagined it this
way is because that's basically how it works today. We assume that the
pg_db_role_setting entry was added by a superuser, but we don't check that
the user that ran ALTER ROLE/DB SET is still superuser every time you log
in.
This might also tie into Nathan's question in another thread about
exactly what permissions should be required to issue ALTER ROLE/DB SET.
In particular I'm wondering if different permissions should be needed to
override an existing entry than if there is no existing entry. If not,
we could find ourselves downgrading a superuser-set entry to a
non-superuser-set entry, which might have bad consequences later
(eg, by rendering the entry nonfunctional because when we actually
load the extension we find out the GUC is SUSET).
Yeah, this is why I suggested storing something that equates to PGC_SUSET
any time a role is superuser or has grantable GUC permissions.
Possibly related to this: I felt while working on 13d838815 that
PGC_SUSET and PGC_SU_BACKEND should be usable as GucContext
values for GUC variables, indicating that the GUC requires special
privileges to be set, but we should no longer use them as passed-in
GucContext values. That is, we should remove privilege tests from
the call sites, like this:(void) set_config_option(stmt->name, ExtractSetVariableArgs(stmt), - (superuser() ? PGC_SUSET : PGC_USERSET), + PGC_USERSET, PGC_S_SESSION, action, true, 0, false);and instead put that behavior inside set_config_option_ext, which
would want to look at superuser_arg(srole) instead, and indeed might
not need to do anything because pg_parameter_aclcheck would subsume
the test. I didn't pursue this further because it wasn't essential
to fixing the bug. But it seems relevant here, because that line of
thought leads to the conclusion that storing PGC_SUSET vs PGC_USERSET
is entirely the wrong approach.
Couldn't ProcessGUCArray() use set_config_option_ext() with the context
indicated by pg_db_role_setting? Also, instead of using PGC_USERSET in all
the set_config_option() call sites, shouldn't we remove the "context"
argument altogether? I am likely misunderstanding your proposal, but while
I think simplifying set_config_option() is worthwhile, I don't see why it
would preclude storing the context in pg_db_role_setting.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
On Thu, Jul 21, 2022 at 10:56:41AM -0700, Nathan Bossart wrote:
Couldn't ProcessGUCArray() use set_config_option_ext() with the context
indicated by pg_db_role_setting? Also, instead of using PGC_USERSET in all
the set_config_option() call sites, shouldn't we remove the "context"
argument altogether? I am likely misunderstanding your proposal, but while
I think simplifying set_config_option() is worthwhile, I don't see why it
would preclude storing the context in pg_db_role_setting.
This thread has remained idle for a bit more than two months, so I
have marked its CF entry as returned with feedback.
--
Michael
Hi!
I'd like to resume this discussion.
On Wed, Jul 20, 2022 at 6:50 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
Kyotaro Horiguchi <horikyota.ntt@gmail.com> writes:
At Tue, 19 Jul 2022 09:53:39 -0700, Nathan Bossart <nathandbossart@gmail.com> wrote in
Hm. I would expect ALTER ROLE to store the PGC_SUSET context when executed
by a superuser or a role with privileges via pg_parameter_acl. Storing all
placeholder GUC settings as PGC_USERSET would make things more restrictive
than they are today. For example, it would no longer be possible to apply
any ALTER ROLE settings from superusers for placeholders that later become
custom GUCS.Returning to the topic, that operation can be allowed in PG15, having
being granted by superuser using the GRANT SET ON PARMETER command.I think that 13d838815 has completely changed the terms that this
discussion needs to be conducted under. It seems clear to me now
that if you want to relax this only-superusers restriction, what
you have to do is store the OID of the role that issued ALTER ROLE/DB SET,
and then apply the same checks that would be used in the ordinary case
where a placeholder is being filled in after being set intra-session.
That is, we'd no longer assume that a value coming from pg_db_role_setting
was set with superuser privileges, but we'd know exactly who did set it.
This makes sense. But do we really need to store the OID of the role?
validate_option_array_item() already checks if the placeholder option
passes validation for PGC_SUSET. So, we can just save a flag
indicating that this check was not successful. If so, then the value
stored can be only used for PGC_USERSET. Do you think this would be
correct?
------
Regards,
Alexander Korotkov
Alexander Korotkov <aekorotkov@gmail.com> writes:
This makes sense. But do we really need to store the OID of the role?
validate_option_array_item() already checks if the placeholder option
passes validation for PGC_SUSET. So, we can just save a flag
indicating that this check was not successful. If so, then the value
stored can be only used for PGC_USERSET. Do you think this would be
correct?
Meh ... doesn't seem like much of an improvement. You still need
to store something that's not there now. This also seems to require
some shaky assumptions about decisions having been made when storing
still being valid later on. Given the possibility of granting or
revoking permissions for SET, I think we don't really want it to act
that way.
regards, tom lane
... BTW, re-reading the commit message for a0ffa885e:
One caveat is that PGC_USERSET GUCs are unaffected by the SET privilege
--- one could wish that those were handled by a revocable grant to
PUBLIC, but they are not, because we couldn't make it robust enough
for GUCs defined by extensions.
it suddenly struck me to wonder if the later 13d838815 changed the
situation enough to allow revisiting that problem, and/or if storing
the source role's OID in pg_db_role_setting would help.
I don't immediately recall all the problems that led us to leave USERSET
GUCs out of the feature, so maybe this is nuts; but maybe it isn't.
It'd be worth considering if we're trying to improve matters here.
regards, tom lane
On Sat, Nov 19, 2022 at 12:33 AM Tom Lane <tgl@sss.pgh.pa.us> wrote:
Alexander Korotkov <aekorotkov@gmail.com> writes:
This makes sense. But do we really need to store the OID of the role?
validate_option_array_item() already checks if the placeholder option
passes validation for PGC_SUSET. So, we can just save a flag
indicating that this check was not successful. If so, then the value
stored can be only used for PGC_USERSET. Do you think this would be
correct?Meh ... doesn't seem like much of an improvement. You still need
to store something that's not there now.
Yes, but it wouldn't be needed to track dependencies of pg_role
mentions in pg_db_role_setting. That seems to be a significant
simplification.
This also seems to require
some shaky assumptions about decisions having been made when storing
still being valid later on. Given the possibility of granting or
revoking permissions for SET, I think we don't really want it to act
that way.
Yes, it might be shaky. Consider user sets parameter
pg_db_role_setting, and that appears to be capable only for
PGC_USERSET. Next this user gets the SET permissions. Then this
parameter needs to be set again in order for the new permission to
take effect.
But consider the other side. How should we handle stored OID of a
role? Should the privilege checking be moved from "set time" to "run
time"? Therefore, revoking SET permission from role may affect
existing parameters in pg_db_role_setting. It feels like revoke of
SET permission also aborts changes previously made with that
permission. This is not how we normally do, and that seems confusing.
I think if we implement the flag and make it user-visible, e.g.
implement something like "ALTER ROLE ... SET ... USERSET;", then it
might be the lesser confusing option.
Thoughts?
------
Regards,
Alexander Korotkov
On Sat, Nov 19, 2022 at 12:41 AM Tom Lane <tgl@sss.pgh.pa.us> wrote:
... BTW, re-reading the commit message for a0ffa885e:
One caveat is that PGC_USERSET GUCs are unaffected by the SET privilege --- one could wish that those were handled by a revocable grant to PUBLIC, but they are not, because we couldn't make it robust enough for GUCs defined by extensions.it suddenly struck me to wonder if the later 13d838815 changed the
situation enough to allow revisiting that problem, and/or if storing
the source role's OID in pg_db_role_setting would help.I don't immediately recall all the problems that led us to leave USERSET
GUCs out of the feature, so maybe this is nuts; but maybe it isn't.
It'd be worth considering if we're trying to improve matters here.
I think if we implement the user-visible USERSET flag for ALTER ROLE,
then we might just check permissions for such parameters from the
target role.
------
Regards,
Alexander Korotkov
On Sat, Nov 19, 2022 at 4:02 AM Alexander Korotkov <aekorotkov@gmail.com> wrote:
On Sat, Nov 19, 2022 at 12:41 AM Tom Lane <tgl@sss.pgh.pa.us> wrote:
... BTW, re-reading the commit message for a0ffa885e:
One caveat is that PGC_USERSET GUCs are unaffected by the SET privilege --- one could wish that those were handled by a revocable grant to PUBLIC, but they are not, because we couldn't make it robust enough for GUCs defined by extensions.it suddenly struck me to wonder if the later 13d838815 changed the
situation enough to allow revisiting that problem, and/or if storing
the source role's OID in pg_db_role_setting would help.I don't immediately recall all the problems that led us to leave USERSET
GUCs out of the feature, so maybe this is nuts; but maybe it isn't.
It'd be worth considering if we're trying to improve matters here.I think if we implement the user-visible USERSET flag for ALTER ROLE,
then we might just check permissions for such parameters from the
target role.
I've drafted a patch implementing ALTER ROLE ... SET ... TO ... USER SET syntax.
These options are working only for USERSET GUC variables, but require
less privileges to set. I think there is no problem to implement
Also it seems that this approach doesn't conflict with future
privileges for USERSET GUCs [1]. I expect that USERSET GUCs should be
available unless explicitly REVOKEd. That mean we should be able to
check those privileges during ALTER ROLE.
Opinions on the patch draft?
------
Regards,
Alexander Korotkov
Attachments:
0001-ALTER-ROLE-.-SET-.-TO-.-USER-SET-v1.patchapplication/octet-stream; name=0001-ALTER-ROLE-.-SET-.-TO-.-USER-SET-v1.patchDownload
From 37838f16be7549936457ce265ab290a76928fd72 Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <akorotkov@postgresql.org>
Date: Sun, 20 Nov 2022 03:29:09 +0300
Subject: [PATCH] ALTER ROLE ... SET ... TO ... USER SET
---
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 | 90 +++++++++++++++++++-----
src/bin/pg_dump/dumputils.c | 11 ++-
src/include/nodes/parsenodes.h | 1 +
src/include/utils/guc.h | 3 +-
7 files changed, 108 insertions(+), 23 deletions(-)
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..c7af6305306 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -244,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 skipIfNoPermissions, bool user_set);
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);
@@ -6164,8 +6164,9 @@ RestoreGUCState(void *gucstate)
* storage. 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;
@@ -6178,8 +6179,20 @@ ParseLongOption(const char *string, char **name, char **value)
if (string[equal_pos] == '=')
{
- *name = palloc(equal_pos + 1);
- strlcpy(*name, string, equal_pos + 1);
+ if (equal_pos >= 3 && string[equal_pos - 1] == ')')
+ {
+ *name = palloc(equal_pos - 2);
+ strlcpy(*name, string, equal_pos - 2);
+ if (user_set)
+ *user_set = true;
+ }
+ else
+ {
+ *name = palloc(equal_pos + 1);
+ strlcpy(*name, string, equal_pos + 1);
+ if (user_set)
+ *user_set = false;
+ }
*value = pstrdup(&string[equal_pos + 1]);
}
@@ -6188,6 +6201,9 @@ ParseLongOption(const char *string, char **name, char **value)
/* no equal sign in string */
*name = pstrdup(string);
*value = NULL;
+
+ if (user_set)
+ *user_set = false;
}
for (cp = *name; *cp; cp++)
@@ -6195,6 +6211,19 @@ ParseLongOption(const char *string, char **name, char **value)
*cp = '_';
}
+/*
+ * 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.
+ */
+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 +6249,7 @@ ProcessGUCArray(ArrayType *array,
char *s;
char *name;
char *value;
+ bool user_set;
d = array_ref(array, 1, &i,
-1 /* varlenarray */ ,
@@ -6233,7 +6263,7 @@ ProcessGUCArray(ArrayType *array,
s = TextDatumGetCString(d);
- ParseLongOption(s, &name, &value);
+ ParseLongOptionInternal(s, &name, &value, &user_set);
if (!value)
{
ereport(WARNING,
@@ -6245,7 +6275,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 +6290,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 +6302,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, false, user_set);
/* normalize name (converts obsolete GUC names to modern spellings) */
record = find_option(name, false, true, WARNING);
@@ -6279,7 +6310,10 @@ 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(u)=%s", name, value);
+ else
+ newval = psprintf("%s=%s", name, value);
datum = CStringGetTextDatum(newval);
if (array)
@@ -6310,8 +6344,15 @@ GUCArrayAdd(ArrayType *array, const char *name, const char *value)
current = TextDatumGetCString(d);
/* check for match up through and including '=' */
- if (strncmp(current, newval, strlen(name) + 1) == 0)
+ if (strncmp(current, newval, strlen(name)) == 0 &&
+ (current[strlen(name)] == '=' || current[strlen(name)] == '('))
{
+ /*
+ * recheck permissons if there is not USER SET option, while
+ * we were looking for USER SET
+ */
+ if (current[strlen(name)] == '=' && user_set)
+ (void) validate_option_array_item(name, value, false, false);
index = i;
break;
}
@@ -6347,9 +6388,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)
@@ -6380,8 +6418,13 @@ GUCArrayDelete(ArrayType *array, const char *name)
/* ignore entry if it's what we want to delete */
if (strncmp(val, name, strlen(name)) == 0
- && val[strlen(name)] == '=')
+ && (val[strlen(name)] == '=' || val[strlen(name)] == '('))
+ {
+ /* test if the option is valid and we're allowed to set it */
+ (void) validate_option_array_item(name, NULL, false,
+ val[strlen(name)] == '(');
continue;
+ }
/* else add it to the output array */
if (newarray)
@@ -6431,6 +6474,7 @@ GUCArrayReset(ArrayType *array)
char *val;
char *eqsgn;
bool isnull;
+ bool user_set = false;
d = array_ref(array, 1, &i,
-1 /* varlenarray */ ,
@@ -6443,10 +6487,18 @@ GUCArrayReset(ArrayType *array)
val = TextDatumGetCString(d);
eqsgn = strchr(val, '=');
- *eqsgn = '\0';
+ if (eqsgn - val >= 3 && eqsgn[-1] == ')')
+ {
+ eqsgn[-3] = '\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, true, user_set))
continue;
/* else add it to the output array */
@@ -6481,7 +6533,7 @@ GUCArrayReset(ArrayType *array)
*/
static bool
validate_option_array_item(const char *name, const char *value,
- bool skipIfNoPermissions)
+ bool skipIfNoPermissions, bool user_set)
{
struct config_generic *gconf;
@@ -6518,6 +6570,8 @@ 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.
*/
+ if (user_set)
+ return true;
if (superuser() ||
pg_parameter_aclcheck(name, GetUserId(), ACL_SET) == ACLCHECK_OK)
return true;
diff --git a/src/bin/pg_dump/dumputils.c b/src/bin/pg_dump/dumputils.c
index 6e501a54138..3aea75296dc 100644
--- a/src/bin/pg_dump/dumputils.c
+++ b/src/bin/pg_dump/dumputils.c
@@ -820,6 +820,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 +830,13 @@ makeAlterConfigCommand(PGconn *conn, const char *configitem,
pg_free(mine);
return;
}
- *pos++ = '\0';
+ if (pos - mine >= 3 && pos[-1] == ')')
+ {
+ user_set = true;
+ pos[-3] = '\0';
+ }
+ else
+ *pos++ = '\0';
/* Build the command, with suitable quoting for everything. */
appendPQExpBuffer(buf, "ALTER %s %s ", type, fmtId(name));
@@ -872,6 +879,8 @@ makeAlterConfigCommand(PGconn *conn, const char *configitem,
else
appendStringLiteralConn(buf, pos, conn);
+ if (user_set)
+ appendPQExpBufferStr(buf, "USER SET;\n");
appendPQExpBufferStr(buf, ";\n");
pg_free(mine);
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..95b605f9b1e 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -393,7 +393,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)
.On Sun, Nov 20, 2022 at 8:48 PM Alexander Korotkov
<aekorotkov@gmail.com> wrote:
I've drafted a patch implementing ALTER ROLE ... SET ... TO ... USER SET syntax.
These options are working only for USERSET GUC variables, but require
less privileges to set. I think there is no problem to implementAlso it seems that this approach doesn't conflict with future
privileges for USERSET GUCs [1]. I expect that USERSET GUCs should be
available unless explicitly REVOKEd. That mean we should be able to
check those privileges during ALTER ROLE.Opinions on the patch draft?
Uh, sorry for the wrong link. I meant
/messages/by-id/2271988.1668807706@sss.pgh.pa.us
------
Regards,
Alexander Korotkov
Hey Alexander,
Looks like your latest patch addresses the original issue I posted!
So now I can create a placeholder with the USERSET modifier without a
superuser, while non-USERSET placeholders still require superuser:
```sql
create role foo noinherit;
set role to foo;
alter role foo set prefix.bar to true user set;
ALTER ROLE
alter role foo set prefix.baz to true;
ERROR: permission denied to set parameter "prefix.baz"
set role to postgres;
alter role foo set prefix.baz to true;
ALTER ROLE
```
Also USERSET gucs are marked(`(u)`) on `pg_db_role_setting`:
```sql
select * from pg_db_role_setting ;
setdatabase | setrole | setconfig
-------------+---------+--------------------------------------
0 | 16384 | {prefix.bar(u)=true,prefix.baz=true}
```
Which I guess avoids the need for adding columns to `pg_catalog` and makes
the "fix" simpler.
So from my side this all looks good!
Best regards,
Steve
On Sun, 20 Nov 2022 at 12:50, Alexander Korotkov <aekorotkov@gmail.com>
wrote:
Show quoted text
.On Sun, Nov 20, 2022 at 8:48 PM Alexander Korotkov
<aekorotkov@gmail.com> wrote:I've drafted a patch implementing ALTER ROLE ... SET ... TO ... USER SET
syntax.
These options are working only for USERSET GUC variables, but require
less privileges to set. I think there is no problem to implementAlso it seems that this approach doesn't conflict with future
privileges for USERSET GUCs [1]. I expect that USERSET GUCs should be
available unless explicitly REVOKEd. That mean we should be able to
check those privileges during ALTER ROLE.Opinions on the patch draft?
Links
1.Uh, sorry for the wrong link. I meant
/messages/by-id/2271988.1668807706@sss.pgh.pa.us------
Regards,
Alexander Korotkov
On Wed, Nov 23, 2022 at 1:53 AM Steve Chavez <steve@supabase.io> wrote:
So from my side this all looks good!
Thank you for your feedback.
The next revision of the patch is attached. It contains code
improvements, comments and documentation. I'm going to also write
sode tests. pg_db_role_setting doesn't seem to be well-covered with
tests. I will probably need to write a new module into
src/tests/modules to check now placeholders interacts with dynamically
defined GUCs.
------
Regards,
Alexander Korotkov
Attachments:
0001-ALTER-ROLE-.-SET-.-TO-.-USER-SET-v2.patchapplication/octet-stream; name=0001-ALTER-ROLE-.-SET-.-TO-.-USER-SET-v2.patchDownload
From 662abcc9c3c312955577e02f521576ac8949152f Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <akorotkov@postgresql.org>
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 <replaceable class="parameter">name</replaceable> SET TABLESPACE
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
@@ -206,6 +206,19 @@ 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 ordinal role.
+ That lets ordinal role set placeholder variables, which permission
+ requirements is not known yet; see <xref linkend="runtime-config-custom"/>.
+ The value set wouldn't be used if variable finally appear to require
+ superuser privileges.
+ </para>
+ </listitem>
+ </varlistentry>
</variablelist>
</refsect1>
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 <replaceable class="parameter">role_specification</replaceable> [ WIT
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
@@ -234,6 +234,19 @@ ALTER ROLE { <replaceable class="parameter">role_specification</replaceable> | A
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><literal>USER SET</literal></term>
+ <listitem>
+ <para>
+ 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 <xref linkend="runtime-config-custom"/>.
+ The value set wouldn't be used if variable finally appear to require
+ superuser privileges.
+ </para>
+ </listitem>
+ </varlistentry>
</variablelist>
</refsect1>
@@ -329,6 +342,13 @@ ALTER ROLE worker_bee SET maintenance_work_mem = 100000;
<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 ordinal user.
+
+<programlisting>
+ALTER ROLE fred SET my.param = 'value' USER SET;
</programlisting></para>
</refsect1>
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 <ctype.h>
+#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)
On Thu, Dec 1, 2022 at 6:14 AM Alexander Korotkov <aekorotkov@gmail.com> wrote:
On Wed, Nov 23, 2022 at 1:53 AM Steve Chavez <steve@supabase.io> wrote:
So from my side this all looks good!
Thank you for your feedback.
The next revision of the patch is attached. It contains code
improvements, comments and documentation. I'm going to also write
sode tests. pg_db_role_setting doesn't seem to be well-covered with
tests. I will probably need to write a new module into
src/tests/modules to check now placeholders interacts with dynamically
defined GUCs.
Another revision of patch is attached. It's fixed now that USER SET
values can't be used for PGC_SUSET parameters. Tests are added. That
require new module test_pg_db_role_setting to check dynamically
defined GUCs.
------
Regards,
Alexander Korotkov
Attachments:
0001-USER-SET-parameters-for-pg_db_role_setting-v3.patchapplication/octet-stream; name=0001-USER-SET-parameters-for-pg_db_role_setting-v3.patchDownload
From 194f07aa7f6abdd4ade3759907a9c84b9bc82814 Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <akorotkov@postgresql.org>
Date: Sun, 20 Nov 2022 03:29:09 +0300
Subject: [PATCH] USER SET parameters for pg_db_role_setting
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.
The value set wouldn't be used if variable finally appear to require
superuser privileges.
---
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 | 125 +++++++++++++-----
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 +-
src/test/modules/Makefile | 1 +
src/test/modules/meson.build | 1 +
.../test_pg_db_role_setting/.gitignore | 4 +
.../modules/test_pg_db_role_setting/Makefile | 29 ++++
.../expected/test_pg_db_role_setting.out | 120 +++++++++++++++++
.../test_pg_db_role_setting/meson.build | 35 +++++
.../sql/test_pg_db_role_setting.sql | 55 ++++++++
.../test_pg_db_role_setting--1.0.sql | 7 +
.../test_pg_db_role_setting.c | 57 ++++++++
.../test_pg_db_role_setting.control | 7 +
21 files changed, 526 insertions(+), 44 deletions(-)
create mode 100644 src/include/common/guc-common.h
create mode 100644 src/test/modules/test_pg_db_role_setting/.gitignore
create mode 100644 src/test/modules/test_pg_db_role_setting/Makefile
create mode 100644 src/test/modules/test_pg_db_role_setting/expected/test_pg_db_role_setting.out
create mode 100644 src/test/modules/test_pg_db_role_setting/meson.build
create mode 100644 src/test/modules/test_pg_db_role_setting/sql/test_pg_db_role_setting.sql
create mode 100644 src/test/modules/test_pg_db_role_setting/test_pg_db_role_setting--1.0.sql
create mode 100644 src/test/modules/test_pg_db_role_setting/test_pg_db_role_setting.c
create mode 100644 src/test/modules/test_pg_db_role_setting/test_pg_db_role_setting.control
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 <replaceable class="parameter">name</replaceable> SET TABLESPACE
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
@@ -206,6 +206,19 @@ 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 ordinal role.
+ That lets ordinal role set placeholder variables, which permission
+ requirements is not known yet; see <xref linkend="runtime-config-custom"/>.
+ The value set wouldn't be used if variable finally appear to require
+ superuser privileges.
+ </para>
+ </listitem>
+ </varlistentry>
</variablelist>
</refsect1>
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 <replaceable class="parameter">role_specification</replaceable> [ WIT
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
@@ -234,6 +234,19 @@ ALTER ROLE { <replaceable class="parameter">role_specification</replaceable> | A
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><literal>USER SET</literal></term>
+ <listitem>
+ <para>
+ 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 <xref linkend="runtime-config-custom"/>.
+ The value set wouldn't be used if variable finally appear to require
+ superuser privileges.
+ </para>
+ </listitem>
+ </varlistentry>
</variablelist>
</refsect1>
@@ -329,6 +342,13 @@ ALTER ROLE worker_bee SET maintenance_work_mem = 100000;
<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 ordinal user.
+
+<programlisting>
+ALTER ROLE fred SET my.param = 'value' USER SET;
</programlisting></para>
</refsect1>
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..d1564c7284e 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,
@@ -6244,9 +6263,19 @@ ProcessGUCArray(ArrayType *array,
continue;
}
- (void) set_config_option(name, value,
- context, source,
- action, true, 0, 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 (!user_set)
+ (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);
@@ -6260,7 +6289,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 +6301,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 +6309,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 +6343,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 +6389,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 +6418,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 +6476,7 @@ GUCArrayReset(ArrayType *array)
char *val;
char *eqsgn;
bool isnull;
+ bool user_set = false;
d = array_ref(array, 1, &i,
-1 /* varlenarray */ ,
@@ -6443,10 +6489,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 +6526,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 +6571,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 <ctype.h>
+#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);
diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile
index 7b3f2929652..ef724703d46 100644
--- a/src/test/modules/Makefile
+++ b/src/test/modules/Makefile
@@ -24,6 +24,7 @@ SUBDIRS = \
test_misc \
test_oat_hooks \
test_parser \
+ test_pg_db_role_settings \
test_pg_dump \
test_predtest \
test_rbtree \
diff --git a/src/test/modules/meson.build b/src/test/modules/meson.build
index c2e5f5ffd5a..c152723a277 100644
--- a/src/test/modules/meson.build
+++ b/src/test/modules/meson.build
@@ -18,6 +18,7 @@ subdir('test_lfind')
subdir('test_misc')
subdir('test_oat_hooks')
subdir('test_parser')
+subdir('test_pg_db_role_settings')
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 00000000000..5dcb3ff9723
--- /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 00000000000..aacd78f74c5
--- /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 00000000000..b67b20d8357
--- /dev/null
+++ b/src/test/modules/test_pg_db_role_setting/expected/test_pg_db_role_setting.out
@@ -0,0 +1,120 @@
+CREATE EXTENSION test_pg_db_role_settings;
+ERROR: extension "test_pg_db_role_settings" is not available
+DETAIL: Could not open extension control file "/Users/smagen/projects/postgresql/env/master/src/tmp_install/Users/smagen/projects/postgresql/env/master/pgsql/share/extension/test_pg_db_role_settings.control": No such file or directory.
+HINT: The extension must first be installed on the system where PostgreSQL is running.
+CREATE USER super_user SUPERUSER;
+CREATE USER regular_user;
+\c - regular_user
+-- successfully set a placeholder value
+SET test_pg_db_role_settings.superuser_param = 'aaa';
+-- module is loaded, the placeholder value is thrown away
+SELECT load_test_pg_db_role_settings();
+ERROR: function load_test_pg_db_role_settings() does not exist
+LINE 1: SELECT load_test_pg_db_role_settings();
+ ^
+HINT: No function matches the given name and argument types. You might need to add explicit type casts.
+SHOW test_pg_db_role_settings.superuser_param;
+ test_pg_db_role_settings.superuser_param
+------------------------------------------
+ aaa
+(1 row)
+
+SHOW test_pg_db_role_settings.user_param;
+ERROR: unrecognized configuration parameter "test_pg_db_role_settings.user_param"
+\c - regular_user
+-- fail, not privileges
+ALTER ROLE regular_user SET test_pg_db_role_settings.superuser_param = 'aaa';
+ERROR: permission denied to set parameter "test_pg_db_role_settings.superuser_param"
+ALTER ROLE regular_user SET test_pg_db_role_settings.user_param = 'bbb';
+ERROR: permission denied to set parameter "test_pg_db_role_settings.user_param"
+-- success for USER SET parameters
+ALTER ROLE regular_user SET test_pg_db_role_settings.superuser_param = 'aaa' USER SET;
+ALTER ROLE regular_user SET test_pg_db_role_settings.user_param = 'bbb' USER SET;
+SELECT * FROM pg_db_role_setting;
+ setdatabase | setrole | setconfig
+-------------+---------+------------------------------------------------------------------------------------------------------
+ 16384 | 0 | {lc_messages=C,lc_monetary=C,lc_numeric=C,lc_time=C,bytea_output=hex,timezone_abbreviations=Default}
+ 0 | 16386 | {test_pg_db_role_settings.superuser_param(u)=aaa,test_pg_db_role_settings.user_param(u)=bbb}
+(2 rows)
+
+\c - regular_user
+-- successfully set placeholders
+SHOW test_pg_db_role_settings.superuser_param;
+ test_pg_db_role_settings.superuser_param
+------------------------------------------
+ aaa
+(1 row)
+
+SHOW test_pg_db_role_settings.user_param;
+ test_pg_db_role_settings.user_param
+-------------------------------------
+ bbb
+(1 row)
+
+-- module is loaded, the placeholder value of superuser param is thrown away
+SELECT load_test_pg_db_role_settings();
+ERROR: function load_test_pg_db_role_settings() does not exist
+LINE 1: SELECT load_test_pg_db_role_settings();
+ ^
+HINT: No function matches the given name and argument types. You might need to add explicit type casts.
+SHOW test_pg_db_role_settings.superuser_param;
+ test_pg_db_role_settings.superuser_param
+------------------------------------------
+ aaa
+(1 row)
+
+SHOW test_pg_db_role_settings.user_param;
+ test_pg_db_role_settings.user_param
+-------------------------------------
+ bbb
+(1 row)
+
+\c - super_user
+SELECT load_test_pg_db_role_settings();
+ERROR: function load_test_pg_db_role_settings() does not exist
+LINE 1: SELECT load_test_pg_db_role_settings();
+ ^
+HINT: No function matches the given name and argument types. You might need to add explicit type casts.
+-- give the privilege to set SUSET param to the regular user
+GRANT SET ON PARAMETER test_pg_db_role_settings.superuser_param TO regular_user;
+\c - regular_user
+ALTER ROLE regular_user SET test_pg_db_role_settings.superuser_param = 'aaa';
+SELECT * FROM pg_db_role_setting;
+ setdatabase | setrole | setconfig
+-------------+---------+------------------------------------------------------------------------------------------------------
+ 16384 | 0 | {lc_messages=C,lc_monetary=C,lc_numeric=C,lc_time=C,bytea_output=hex,timezone_abbreviations=Default}
+ 0 | 16386 | {test_pg_db_role_settings.superuser_param=aaa,test_pg_db_role_settings.user_param(u)=bbb}
+(2 rows)
+
+\c - regular_user
+-- successfully set placeholders
+SHOW test_pg_db_role_settings.superuser_param;
+ test_pg_db_role_settings.superuser_param
+------------------------------------------
+ aaa
+(1 row)
+
+SHOW test_pg_db_role_settings.user_param;
+ test_pg_db_role_settings.user_param
+-------------------------------------
+ bbb
+(1 row)
+
+-- module is loaded, and placeholder values are succesfully set
+SELECT load_test_pg_db_role_settings();
+ERROR: function load_test_pg_db_role_settings() does not exist
+LINE 1: SELECT load_test_pg_db_role_settings();
+ ^
+HINT: No function matches the given name and argument types. You might need to add explicit type casts.
+SHOW test_pg_db_role_settings.superuser_param;
+ test_pg_db_role_settings.superuser_param
+------------------------------------------
+ aaa
+(1 row)
+
+SHOW test_pg_db_role_settings.user_param;
+ test_pg_db_role_settings.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 00000000000..ffb50d830e0
--- /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_oat_hooks_sources = files(
+ 'test_pg_db_role_setting.c',
+)
+
+if host_system == 'windows'
+ test_oat_hooks_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 00000000000..4c2576cb9be
--- /dev/null
+++ b/src/test/modules/test_pg_db_role_setting/sql/test_pg_db_role_setting.sql
@@ -0,0 +1,55 @@
+CREATE EXTENSION test_pg_db_role_settings;
+CREATE USER super_user SUPERUSER;
+CREATE USER regular_user;
+
+\c - regular_user
+-- successfully set a placeholder value
+SET test_pg_db_role_settings.superuser_param = 'aaa';
+
+-- module is loaded, the placeholder value is thrown away
+SELECT load_test_pg_db_role_settings();
+
+SHOW test_pg_db_role_settings.superuser_param;
+SHOW test_pg_db_role_settings.user_param;
+
+\c - regular_user
+-- fail, not privileges
+ALTER ROLE regular_user SET test_pg_db_role_settings.superuser_param = 'aaa';
+ALTER ROLE regular_user SET test_pg_db_role_settings.user_param = 'bbb';
+-- success for USER SET parameters
+ALTER ROLE regular_user SET test_pg_db_role_settings.superuser_param = 'aaa' USER SET;
+ALTER ROLE regular_user SET test_pg_db_role_settings.user_param = 'bbb' USER SET;
+
+SELECT * FROM pg_db_role_setting;
+
+\c - regular_user
+-- successfully set placeholders
+SHOW test_pg_db_role_settings.superuser_param;
+SHOW test_pg_db_role_settings.user_param;
+
+-- module is loaded, the placeholder value of superuser param is thrown away
+SELECT load_test_pg_db_role_settings();
+
+SHOW test_pg_db_role_settings.superuser_param;
+SHOW test_pg_db_role_settings.user_param;
+
+\c - super_user
+SELECT load_test_pg_db_role_settings();
+-- give the privilege to set SUSET param to the regular user
+GRANT SET ON PARAMETER test_pg_db_role_settings.superuser_param TO regular_user;
+
+\c - regular_user
+ALTER ROLE regular_user SET test_pg_db_role_settings.superuser_param = 'aaa';
+
+SELECT * FROM pg_db_role_setting;
+
+\c - regular_user
+-- successfully set placeholders
+SHOW test_pg_db_role_settings.superuser_param;
+SHOW test_pg_db_role_settings.user_param;
+
+-- module is loaded, and placeholder values are succesfully set
+SELECT load_test_pg_db_role_settings();
+
+SHOW test_pg_db_role_settings.superuser_param;
+SHOW test_pg_db_role_settings.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 00000000000..1ed3d285c7e
--- /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 00000000000..7d71cf305d7
--- /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_settings.c
+ * Code for testing mandatory access control (MAC) using object access hooks.
+ *
+ * Copyright (c) 2015-2022, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/test/modules/test_pg_db_role_settings/test_pg_db_role_settings.c
+ *
+ * -------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "utils/guc.h"
+
+PG_MODULE_MAGIC;
+
+PG_FUNCTION_INFO_V1(load_test_pg_db_role_settings);
+
+static char *superuser_param;
+static char *user_param;
+
+/*
+ * Module load callback
+ */
+void
+_PG_init(void)
+{
+ DefineCustomStringVariable("test_pg_db_role_settings.superuser_param",
+ "Sample superuser parameter.",
+ NULL,
+ &superuser_param,
+ "superuser_param_value",
+ PGC_SUSET,
+ 0,
+ NULL, NULL, NULL);
+
+ DefineCustomStringVariable("test_pg_db_role_settings.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_settings(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 00000000000..9678cff376d
--- /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
--
2.24.3 (Apple Git-128)
On Thu, Dec 1, 2022 at 6:14 AM Alexander Korotkov <aekorotkov@gmail.com> wrote:
On Wed, Nov 23, 2022 at 1:53 AM Steve Chavez <steve@supabase.io> wrote:
So from my side this all looks good!
Thank you for your feedback.
The next revision of the patch is attached. It contains code
improvements, comments and documentation. I'm going to also write
sode tests. pg_db_role_setting doesn't seem to be well-covered with
tests. I will probably need to write a new module into
src/tests/modules to check now placeholders interacts with dynamically
defined GUCs.Another revision of patch is attached. It's fixed now that USER SET
values can't be used for PGC_SUSET parameters. Tests are added. That
require new module test_pg_db_role_setting to check dynamically
defined GUCs.
I've looked through the last version of a patch. The tests in v3
failed due to naming mismatches. I fixed this in v4 (PFA).
The other thing that may seem unexpected: is whether the value should
apply to the ordinary user only, encoded in the parameter name. The
pro of this is that it doesn't break catalog compatibility by a
separate field for GUC permissions a concept that doesn't exist today
(and maybe not needed at all). Also, it makes the patch more
minimalistic in the code. This is also fully compatible with the
previous parameters naming due to parentheses being an unsupported
symbol for the parameter name.
I've also tried to revise the comments and docs a little bit to
reflect the changes.
The CI-enabled build of patch v4 for reference is at
https://github.com/pashkinelfe/postgres/tree/placeholders-in-alter-role-v4
Overall the patch looks useful and good enough to be committed.
Kind regards,
Pavel Borisov,
Supabase
Attachments:
v4-0001-USER-SET-parameters-for-pg_db_role_setting.patchapplication/octet-stream; name=v4-0001-USER-SET-parameters-for-pg_db_role_setting.patchDownload
From 17abdf7de6766da7c441ca68651b8d2b41be80f1 Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <akorotkov@postgresql.org>
Date: Sun, 20 Nov 2022 03:29:09 +0300
Subject: [PATCH v4] USER SET parameters for pg_db_role_setting
Specifies that variable should be set on behalf of ordinary role.
That lets ordinary role set placeholder variables, which permission
requirements is not known yet.
The value set wouldn't be used if variable finally appear to require
superuser privileges.
---
doc/src/sgml/config.sgml | 15 ++-
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 | 125 ++++++++++++-----
src/backend/utils/misc/guc_funcs.c | 12 +-
src/bin/pg_dump/dumputils.c | 18 ++-
src/include/common/guc-common.h | 32 +++++
src/include/nodes/parsenodes.h | 1 +
src/include/utils/guc.h | 4 +-
src/test/modules/Makefile | 1 +
src/test/modules/meson.build | 1 +
.../test_pg_db_role_setting/.gitignore | 4 +
.../modules/test_pg_db_role_setting/Makefile | 29 ++++
.../expected/test_pg_db_role_setting.out | 127 ++++++++++++++++++
.../test_pg_db_role_setting/meson.build | 35 +++++
.../sql/test_pg_db_role_setting.sql | 55 ++++++++
.../test_pg_db_role_setting--1.0.sql | 7 +
.../test_pg_db_role_setting.c | 57 ++++++++
.../test_pg_db_role_setting.control | 7 +
22 files changed, 545 insertions(+), 48 deletions(-)
create mode 100644 src/include/common/guc-common.h
create mode 100644 src/test/modules/test_pg_db_role_setting/.gitignore
create mode 100644 src/test/modules/test_pg_db_role_setting/Makefile
create mode 100644 src/test/modules/test_pg_db_role_setting/expected/test_pg_db_role_setting.out
create mode 100644 src/test/modules/test_pg_db_role_setting/meson.build
create mode 100644 src/test/modules/test_pg_db_role_setting/sql/test_pg_db_role_setting.sql
create mode 100644 src/test/modules/test_pg_db_role_setting/test_pg_db_role_setting--1.0.sql
create mode 100644 src/test/modules/test_pg_db_role_setting/test_pg_db_role_setting.c
create mode 100644 src/test/modules/test_pg_db_role_setting/test_pg_db_role_setting.control
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index ff6fcd902a8..90c4fd41c55 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -22,10 +22,17 @@
<title>Parameter Names and Values</title>
<para>
- All parameter names are case-insensitive. Every parameter takes a
- value of one of five types: boolean, string, integer, floating point,
- or enumerated (enum). The type determines the syntax for setting the
- parameter:
+ Parameter names can contain only alphanumeric characters and underscore and
+ are case-insensitive. If dash is provided is is converted to underscore.
+ Brackets are allowed only to specify by <literal>(u)</literal> postfix
+ that a value should be applied only if ordinary user priviledges are
+ sufficient for setting it. Otherwise it is to be skipped.
+ </para>
+
+ <para>
+ Every parameter takes a value of one of five types: boolean, string,
+ integer, floating point, or enumerated (enum). The type determines the
+ syntax for setting the parameter:
</para>
<itemizedlist>
diff --git a/doc/src/sgml/ref/alter_database.sgml b/doc/src/sgml/ref/alter_database.sgml
index 89ed261b4c2..181e9d36205 100644
--- a/doc/src/sgml/ref/alter_database.sgml
+++ b/doc/src/sgml/ref/alter_database.sgml
@@ -37,7 +37,7 @@ ALTER DATABASE <replaceable class="parameter">name</replaceable> SET TABLESPACE
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
@@ -206,6 +206,19 @@ 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>
diff --git a/doc/src/sgml/ref/alter_role.sgml b/doc/src/sgml/ref/alter_role.sgml
index 5aa5648ae7b..06d4cea00bc 100644
--- a/doc/src/sgml/ref/alter_role.sgml
+++ b/doc/src/sgml/ref/alter_role.sgml
@@ -38,7 +38,7 @@ ALTER ROLE <replaceable class="parameter">role_specification</replaceable> [ WIT
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
@@ -234,6 +234,19 @@ ALTER ROLE { <replaceable class="parameter">role_specification</replaceable> | A
</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>
@@ -329,6 +342,13 @@ ALTER ROLE worker_bee SET maintenance_work_mem = 100000;
<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>
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 57489f65f2e..dd882576d70 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 b1ae5f834cd..adc3f8ced3b 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/misc/guc.c b/src/backend/utils/misc/guc.c
index 28313b3a94a..eca72d62b6c 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);
@@ -6161,13 +6160,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;
@@ -6178,25 +6180,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,
@@ -6222,6 +6240,7 @@ ProcessGUCArray(ArrayType *array,
char *s;
char *name;
char *value;
+ bool user_set;
d = array_ref(array, 1, &i,
-1 /* varlenarray */ ,
@@ -6235,7 +6254,7 @@ ProcessGUCArray(ArrayType *array,
s = TextDatumGetCString(d);
- ParseLongOption(s, &name, &value);
+ ParseLongOptionInternal(s, &name, &value, &user_set);
if (!value)
{
ereport(WARNING,
@@ -6246,9 +6265,19 @@ ProcessGUCArray(ArrayType *array,
continue;
}
- (void) set_config_option(name, value,
- context, source,
- action, true, 0, 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 (!user_set)
+ (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 +6291,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;
@@ -6273,7 +6303,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);
@@ -6281,7 +6311,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)
@@ -6311,9 +6345,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;
}
@@ -6349,9 +6391,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)
@@ -6381,9 +6420,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)
@@ -6433,6 +6478,7 @@ GUCArrayReset(ArrayType *array)
char *val;
char *eqsgn;
bool isnull;
+ bool user_set = false;
d = array_ref(array, 1, &i,
-1 /* varlenarray */ ,
@@ -6445,10 +6491,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 */
@@ -6474,15 +6528,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 +6573,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 9311417f18c..dd0cf4e3a2e 100644
--- a/src/bin/pg_dump/dumputils.c
+++ b/src/bin/pg_dump/dumputils.c
@@ -16,6 +16,7 @@
#include <ctype.h>
+#include "common/guc-common.h"
#include "dumputils.h"
#include "fe_utils/string_utils.h"
@@ -806,8 +807,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
@@ -822,6 +823,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);
@@ -831,7 +833,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));
@@ -874,6 +882,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..d2a84c3a574
--- /dev/null
+++ b/src/include/common/guc-common.h
@@ -0,0 +1,32 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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. GUC name is not allowed
+ * to contain parentheses, so no conflict is possible.
+ */
+#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 f17846e30e2..e8c9e0e8db0 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2231,6 +2231,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);
diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile
index 96addded814..c629cbe3830 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 1d265448549..911a768a294 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 00000000000..5dcb3ff9723
--- /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 00000000000..aacd78f74c5
--- /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 00000000000..090c001c899
--- /dev/null
+++ b/src/test/modules/test_pg_db_role_setting/expected/test_pg_db_role_setting.out
@@ -0,0 +1,127 @@
+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;
+SELECT * FROM pg_db_role_setting;
+ setdatabase | setrole | setconfig
+-------------+---------+------------------------------------------------------------------------------------------------------
+ 16384 | 0 | {lc_messages=C,lc_monetary=C,lc_numeric=C,lc_time=C,bytea_output=hex,timezone_abbreviations=Default}
+ 0 | 16388 | {test_pg_db_role_setting.superuser_param(u)=aaa,test_pg_db_role_setting.user_param(u)=bbb}
+(2 rows)
+
+\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
+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 = 'aaa';
+SELECT * FROM pg_db_role_setting;
+ setdatabase | setrole | setconfig
+-------------+---------+------------------------------------------------------------------------------------------------------
+ 16384 | 0 | {lc_messages=C,lc_monetary=C,lc_numeric=C,lc_time=C,bytea_output=hex,timezone_abbreviations=Default}
+ 0 | 16388 | {test_pg_db_role_setting.superuser_param=aaa,test_pg_db_role_setting.user_param(u)=bbb}
+(2 rows)
+
+\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, 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
+-----------------------------------------
+ aaa
+(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 00000000000..3a6410cca21
--- /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 00000000000..1c7b46c0ee7
--- /dev/null
+++ b/src/test/modules/test_pg_db_role_setting/sql/test_pg_db_role_setting.sql
@@ -0,0 +1,55 @@
+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;
+
+SELECT * FROM pg_db_role_setting;
+
+\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
+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 = 'aaa';
+
+SELECT * FROM pg_db_role_setting;
+
+\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 00000000000..1ed3d285c7e
--- /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 00000000000..01b41b9c9a6
--- /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) 2015-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 00000000000..9678cff376d
--- /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
--
2.24.3 (Apple Git-128)
After posting the patch I've found my own typo in docs. So corrected
it in v5 (PFA).
Regards,
Pavel.
Attachments:
v5-0001-USER-SET-parameters-for-pg_db_role_setting.patchapplication/octet-stream; name=v5-0001-USER-SET-parameters-for-pg_db_role_setting.patchDownload
From e304ab6f264304a2773f8a2ad4aeba9aec564c8c Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <akorotkov@postgresql.org>
Date: Sun, 20 Nov 2022 03:29:09 +0300
Subject: [PATCH v5] USER SET parameters for pg_db_role_setting
Specifies that variable should be set on behalf of ordinary role.
That lets ordinary role set placeholder variables, which permission
requirements is not known yet.
The value set wouldn't be used if variable finally appear to require
superuser privileges.
---
doc/src/sgml/config.sgml | 15 ++-
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 | 125 ++++++++++++-----
src/backend/utils/misc/guc_funcs.c | 12 +-
src/bin/pg_dump/dumputils.c | 18 ++-
src/include/common/guc-common.h | 32 +++++
src/include/nodes/parsenodes.h | 1 +
src/include/utils/guc.h | 4 +-
src/test/modules/Makefile | 1 +
src/test/modules/meson.build | 1 +
.../test_pg_db_role_setting/.gitignore | 4 +
.../modules/test_pg_db_role_setting/Makefile | 29 ++++
.../expected/test_pg_db_role_setting.out | 127 ++++++++++++++++++
.../test_pg_db_role_setting/meson.build | 35 +++++
.../sql/test_pg_db_role_setting.sql | 55 ++++++++
.../test_pg_db_role_setting--1.0.sql | 7 +
.../test_pg_db_role_setting.c | 57 ++++++++
.../test_pg_db_role_setting.control | 7 +
22 files changed, 545 insertions(+), 48 deletions(-)
create mode 100644 src/include/common/guc-common.h
create mode 100644 src/test/modules/test_pg_db_role_setting/.gitignore
create mode 100644 src/test/modules/test_pg_db_role_setting/Makefile
create mode 100644 src/test/modules/test_pg_db_role_setting/expected/test_pg_db_role_setting.out
create mode 100644 src/test/modules/test_pg_db_role_setting/meson.build
create mode 100644 src/test/modules/test_pg_db_role_setting/sql/test_pg_db_role_setting.sql
create mode 100644 src/test/modules/test_pg_db_role_setting/test_pg_db_role_setting--1.0.sql
create mode 100644 src/test/modules/test_pg_db_role_setting/test_pg_db_role_setting.c
create mode 100644 src/test/modules/test_pg_db_role_setting/test_pg_db_role_setting.control
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index ff6fcd902a8..0dba6169ae5 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -22,10 +22,17 @@
<title>Parameter Names and Values</title>
<para>
- All parameter names are case-insensitive. Every parameter takes a
- value of one of five types: boolean, string, integer, floating point,
- or enumerated (enum). The type determines the syntax for setting the
- parameter:
+ Parameter names can contain only alphanumeric characters and underscore and
+ are case-insensitive. If dash is provided it is converted to underscore.
+ Parentheses are allowed only to specify by <literal>(u)</literal> postfix
+ that a value should be applied only if ordinary user priviledges are
+ sufficient for setting it. Otherwise it is to be skipped.
+ </para>
+
+ <para>
+ Every parameter takes a value of one of five types: boolean, string,
+ integer, floating point, or enumerated (enum). The type determines the
+ syntax for setting the parameter:
</para>
<itemizedlist>
diff --git a/doc/src/sgml/ref/alter_database.sgml b/doc/src/sgml/ref/alter_database.sgml
index 89ed261b4c2..181e9d36205 100644
--- a/doc/src/sgml/ref/alter_database.sgml
+++ b/doc/src/sgml/ref/alter_database.sgml
@@ -37,7 +37,7 @@ ALTER DATABASE <replaceable class="parameter">name</replaceable> SET TABLESPACE
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
@@ -206,6 +206,19 @@ 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>
diff --git a/doc/src/sgml/ref/alter_role.sgml b/doc/src/sgml/ref/alter_role.sgml
index 5aa5648ae7b..06d4cea00bc 100644
--- a/doc/src/sgml/ref/alter_role.sgml
+++ b/doc/src/sgml/ref/alter_role.sgml
@@ -38,7 +38,7 @@ ALTER ROLE <replaceable class="parameter">role_specification</replaceable> [ WIT
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
@@ -234,6 +234,19 @@ ALTER ROLE { <replaceable class="parameter">role_specification</replaceable> | A
</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>
@@ -329,6 +342,13 @@ ALTER ROLE worker_bee SET maintenance_work_mem = 100000;
<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>
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 57489f65f2e..dd882576d70 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 b1ae5f834cd..adc3f8ced3b 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/misc/guc.c b/src/backend/utils/misc/guc.c
index 28313b3a94a..eca72d62b6c 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);
@@ -6161,13 +6160,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;
@@ -6178,25 +6180,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,
@@ -6222,6 +6240,7 @@ ProcessGUCArray(ArrayType *array,
char *s;
char *name;
char *value;
+ bool user_set;
d = array_ref(array, 1, &i,
-1 /* varlenarray */ ,
@@ -6235,7 +6254,7 @@ ProcessGUCArray(ArrayType *array,
s = TextDatumGetCString(d);
- ParseLongOption(s, &name, &value);
+ ParseLongOptionInternal(s, &name, &value, &user_set);
if (!value)
{
ereport(WARNING,
@@ -6246,9 +6265,19 @@ ProcessGUCArray(ArrayType *array,
continue;
}
- (void) set_config_option(name, value,
- context, source,
- action, true, 0, 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 (!user_set)
+ (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 +6291,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;
@@ -6273,7 +6303,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);
@@ -6281,7 +6311,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)
@@ -6311,9 +6345,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;
}
@@ -6349,9 +6391,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)
@@ -6381,9 +6420,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)
@@ -6433,6 +6478,7 @@ GUCArrayReset(ArrayType *array)
char *val;
char *eqsgn;
bool isnull;
+ bool user_set = false;
d = array_ref(array, 1, &i,
-1 /* varlenarray */ ,
@@ -6445,10 +6491,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 */
@@ -6474,15 +6528,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 +6573,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 9311417f18c..dd0cf4e3a2e 100644
--- a/src/bin/pg_dump/dumputils.c
+++ b/src/bin/pg_dump/dumputils.c
@@ -16,6 +16,7 @@
#include <ctype.h>
+#include "common/guc-common.h"
#include "dumputils.h"
#include "fe_utils/string_utils.h"
@@ -806,8 +807,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
@@ -822,6 +823,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);
@@ -831,7 +833,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));
@@ -874,6 +882,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..d2a84c3a574
--- /dev/null
+++ b/src/include/common/guc-common.h
@@ -0,0 +1,32 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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. GUC name is not allowed
+ * to contain parentheses, so no conflict is possible.
+ */
+#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 f17846e30e2..e8c9e0e8db0 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2231,6 +2231,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);
diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile
index 96addded814..c629cbe3830 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 1d265448549..911a768a294 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 00000000000..5dcb3ff9723
--- /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 00000000000..aacd78f74c5
--- /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 00000000000..090c001c899
--- /dev/null
+++ b/src/test/modules/test_pg_db_role_setting/expected/test_pg_db_role_setting.out
@@ -0,0 +1,127 @@
+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;
+SELECT * FROM pg_db_role_setting;
+ setdatabase | setrole | setconfig
+-------------+---------+------------------------------------------------------------------------------------------------------
+ 16384 | 0 | {lc_messages=C,lc_monetary=C,lc_numeric=C,lc_time=C,bytea_output=hex,timezone_abbreviations=Default}
+ 0 | 16388 | {test_pg_db_role_setting.superuser_param(u)=aaa,test_pg_db_role_setting.user_param(u)=bbb}
+(2 rows)
+
+\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
+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 = 'aaa';
+SELECT * FROM pg_db_role_setting;
+ setdatabase | setrole | setconfig
+-------------+---------+------------------------------------------------------------------------------------------------------
+ 16384 | 0 | {lc_messages=C,lc_monetary=C,lc_numeric=C,lc_time=C,bytea_output=hex,timezone_abbreviations=Default}
+ 0 | 16388 | {test_pg_db_role_setting.superuser_param=aaa,test_pg_db_role_setting.user_param(u)=bbb}
+(2 rows)
+
+\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, 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
+-----------------------------------------
+ aaa
+(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 00000000000..3a6410cca21
--- /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 00000000000..1c7b46c0ee7
--- /dev/null
+++ b/src/test/modules/test_pg_db_role_setting/sql/test_pg_db_role_setting.sql
@@ -0,0 +1,55 @@
+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;
+
+SELECT * FROM pg_db_role_setting;
+
+\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
+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 = 'aaa';
+
+SELECT * FROM pg_db_role_setting;
+
+\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 00000000000..1ed3d285c7e
--- /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 00000000000..01b41b9c9a6
--- /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) 2015-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 00000000000..9678cff376d
--- /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
--
2.24.3 (Apple Git-128)
On Mon, Dec 5, 2022 at 2:27 PM Pavel Borisov <pashkin.elfe@gmail.com> wrote:
After posting the patch I've found my own typo in docs. So corrected
it in v5 (PFA).
The new revision of the patch is attached.
I've removed the mention of "(s)" suffix from the "Server
Configuration" docs section. I think it might be confusing since this
suffix isn't a part of the variable name. It is only used for storage.
Instead, I've added the description of this suffix to the catalog
structure description and psql documentation.
Also, I've added psql tab completion for the USER SET flag, and made
some enhancements to comments, tests, and commit message.
------
Regards,
Alexander Korotkov
Attachments:
0001-Add-USER-SET-parameter-values-for-pg_db_role_sett-v6.patchapplication/octet-stream; name=0001-Add-USER-SET-parameter-values-for-pg_db_role_sett-v6.patchDownload
From b920f430be6224397c0d0701d92982aa2705e3ab Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <akorotkov@postgresql.org>
Date: Mon, 5 Dec 2022 16:00:33 +0300
Subject: [PATCH] Add USER SET parameter values for pg_db_role_setting
The USER SET flag specifies that the variable should be set on behalf of an
ordinary role. That lets ordinary roles set placeholder variables, which
permission requirements are not known yet. Such a value wouldn't be used if
the variable finally appear to require superuser privileges.
The catalog schema isn't changed. Instead, the new flag "(s)" is appended to
the variable name when values are stored in pg_db_role_setting. We don't allow
braces in variable names, thus there is no conflict. However, we still bump
the catversion because the previous code could have an error interpreting the
new flag.
This commit is inspired by the previous work by Steve Chavez.
Discussion: https://postgr.es/m/CAPpHfdsLd6E--epnGqXENqLP6dLwuNZrPMcNYb3wJ87WR7UBOQ%40mail.gmail.com
Author: Alexander Korotkov, Steve Chavez
Reviewed-by: Pavel Borisov, Steve Chavez
---
doc/src/sgml/catalogs.sgml | 6 +-
doc/src/sgml/ref/alter_database.sgml | 15 ++-
doc/src/sgml/ref/alter_role.sgml | 22 ++-
doc/src/sgml/ref/alter_user.sgml | 2 +-
doc/src/sgml/ref/psql-ref.sgml | 8 ++
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 | 125 ++++++++++++-----
src/backend/utils/misc/guc_funcs.c | 12 +-
src/bin/pg_dump/dumputils.c | 18 ++-
src/bin/psql/tab-complete.c | 4 +
src/include/common/guc-common.h | 32 +++++
src/include/nodes/parsenodes.h | 1 +
src/include/utils/guc.h | 4 +-
src/test/modules/Makefile | 1 +
src/test/modules/meson.build | 1 +
.../test_pg_db_role_setting/.gitignore | 4 +
.../modules/test_pg_db_role_setting/Makefile | 29 ++++
.../expected/test_pg_db_role_setting.out | 127 ++++++++++++++++++
.../test_pg_db_role_setting/meson.build | 35 +++++
.../sql/test_pg_db_role_setting.sql | 63 +++++++++
.../test_pg_db_role_setting--1.0.sql | 7 +
.../test_pg_db_role_setting.c | 57 ++++++++
.../test_pg_db_role_setting.control | 7 +
25 files changed, 560 insertions(+), 46 deletions(-)
create mode 100644 src/include/common/guc-common.h
create mode 100644 src/test/modules/test_pg_db_role_setting/.gitignore
create mode 100644 src/test/modules/test_pg_db_role_setting/Makefile
create mode 100644 src/test/modules/test_pg_db_role_setting/expected/test_pg_db_role_setting.out
create mode 100644 src/test/modules/test_pg_db_role_setting/meson.build
create mode 100644 src/test/modules/test_pg_db_role_setting/sql/test_pg_db_role_setting.sql
create mode 100644 src/test/modules/test_pg_db_role_setting/test_pg_db_role_setting--1.0.sql
create mode 100644 src/test/modules/test_pg_db_role_setting/test_pg_db_role_setting.c
create mode 100644 src/test/modules/test_pg_db_role_setting/test_pg_db_role_setting.control
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 9ed2b020b7d..ca9a0f3e1af 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -3191,7 +3191,11 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
<structfield>setconfig</structfield> <type>text[]</type>
</para>
<para>
- Defaults for run-time configuration variables
+ Defaults for run-time configuration variables. Contains a set of strings
+ in the form of <literal>varname=value</literal> or
+ <literal>varname(u)=value</literal>. The <literal>(s)</literal>
+ name suffix signs the <link linkend="sql-alterrole-user-set"><literal>USER SET</literal></link>
+ value.
</para></entry>
</row>
</tbody>
diff --git a/doc/src/sgml/ref/alter_database.sgml b/doc/src/sgml/ref/alter_database.sgml
index 89ed261b4c2..181e9d36205 100644
--- a/doc/src/sgml/ref/alter_database.sgml
+++ b/doc/src/sgml/ref/alter_database.sgml
@@ -37,7 +37,7 @@ ALTER DATABASE <replaceable class="parameter">name</replaceable> SET TABLESPACE
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
@@ -206,6 +206,19 @@ 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>
diff --git a/doc/src/sgml/ref/alter_role.sgml b/doc/src/sgml/ref/alter_role.sgml
index 5aa5648ae7b..1e9c93d6d35 100644
--- a/doc/src/sgml/ref/alter_role.sgml
+++ b/doc/src/sgml/ref/alter_role.sgml
@@ -38,7 +38,7 @@ ALTER ROLE <replaceable class="parameter">role_specification</replaceable> [ WIT
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
@@ -234,6 +234,19 @@ ALTER ROLE { <replaceable class="parameter">role_specification</replaceable> | A
</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>
@@ -329,6 +342,13 @@ ALTER ROLE worker_bee SET maintenance_work_mem = 100000;
<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>
diff --git a/doc/src/sgml/ref/alter_user.sgml b/doc/src/sgml/ref/alter_user.sgml
index 0ee89f54c5c..24f737d5870 100644
--- a/doc/src/sgml/ref/alter_user.sgml
+++ b/doc/src/sgml/ref/alter_user.sgml
@@ -38,7 +38,7 @@ ALTER USER <replaceable class="parameter">role_specification</replaceable> [ WIT
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
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index d3dd638b148..b518a4bd284 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -1891,6 +1891,14 @@ INSERT INTO tbl1 VALUES ($1, $2) \bind 'first value' 'second value' \g
not role-specific or database-specific, respectively.
</para>
+ <para>
+ The settings comprises a set of strings in the form of
+ <literal>varname=value</literal> or <literal>varname(u)=value</literal>.
+ The <literal>(s)</literal> name suffix signs the
+ <link linkend="sql-alterrole-user-set"><literal>USER SET</literal></link>
+ value.
+ </para>
+
<para>
The <link linkend="sql-alterrole"><command>ALTER ROLE</command></link> and
<link linkend="sql-alterdatabase"><command>ALTER DATABASE</command></link>
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 57489f65f2e..dd882576d70 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 b1ae5f834cd..adc3f8ced3b 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/misc/guc.c b/src/backend/utils/misc/guc.c
index 28313b3a94a..eca72d62b6c 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);
@@ -6161,13 +6160,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;
@@ -6178,25 +6180,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,
@@ -6222,6 +6240,7 @@ ProcessGUCArray(ArrayType *array,
char *s;
char *name;
char *value;
+ bool user_set;
d = array_ref(array, 1, &i,
-1 /* varlenarray */ ,
@@ -6235,7 +6254,7 @@ ProcessGUCArray(ArrayType *array,
s = TextDatumGetCString(d);
- ParseLongOption(s, &name, &value);
+ ParseLongOptionInternal(s, &name, &value, &user_set);
if (!value)
{
ereport(WARNING,
@@ -6246,9 +6265,19 @@ ProcessGUCArray(ArrayType *array,
continue;
}
- (void) set_config_option(name, value,
- context, source,
- action, true, 0, 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 (!user_set)
+ (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 +6291,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;
@@ -6273,7 +6303,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);
@@ -6281,7 +6311,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)
@@ -6311,9 +6345,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;
}
@@ -6349,9 +6391,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)
@@ -6381,9 +6420,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)
@@ -6433,6 +6478,7 @@ GUCArrayReset(ArrayType *array)
char *val;
char *eqsgn;
bool isnull;
+ bool user_set = false;
d = array_ref(array, 1, &i,
-1 /* varlenarray */ ,
@@ -6445,10 +6491,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 */
@@ -6474,15 +6528,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 +6573,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 9311417f18c..dd0cf4e3a2e 100644
--- a/src/bin/pg_dump/dumputils.c
+++ b/src/bin/pg_dump/dumputils.c
@@ -16,6 +16,7 @@
#include <ctype.h>
+#include "common/guc-common.h"
#include "dumputils.h"
#include "fe_utils/string_utils.h"
@@ -806,8 +807,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
@@ -822,6 +823,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);
@@ -831,7 +833,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));
@@ -874,6 +882,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/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 89e7317c233..7d222680f53 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/common/guc-common.h b/src/include/common/guc-common.h
new file mode 100644
index 00000000000..d2a84c3a574
--- /dev/null
+++ b/src/include/common/guc-common.h
@@ -0,0 +1,32 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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. GUC name is not allowed
+ * to contain parentheses, so no conflict is possible.
+ */
+#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 6112cd85c84..78437b0bfbb 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2231,6 +2231,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);
diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile
index 96addded814..c629cbe3830 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 1d265448549..911a768a294 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 00000000000..5dcb3ff9723
--- /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 00000000000..aacd78f74c5
--- /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 00000000000..090c001c899
--- /dev/null
+++ b/src/test/modules/test_pg_db_role_setting/expected/test_pg_db_role_setting.out
@@ -0,0 +1,127 @@
+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;
+SELECT * FROM pg_db_role_setting;
+ setdatabase | setrole | setconfig
+-------------+---------+------------------------------------------------------------------------------------------------------
+ 16384 | 0 | {lc_messages=C,lc_monetary=C,lc_numeric=C,lc_time=C,bytea_output=hex,timezone_abbreviations=Default}
+ 0 | 16388 | {test_pg_db_role_setting.superuser_param(u)=aaa,test_pg_db_role_setting.user_param(u)=bbb}
+(2 rows)
+
+\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
+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 = 'aaa';
+SELECT * FROM pg_db_role_setting;
+ setdatabase | setrole | setconfig
+-------------+---------+------------------------------------------------------------------------------------------------------
+ 16384 | 0 | {lc_messages=C,lc_monetary=C,lc_numeric=C,lc_time=C,bytea_output=hex,timezone_abbreviations=Default}
+ 0 | 16388 | {test_pg_db_role_setting.superuser_param=aaa,test_pg_db_role_setting.user_param(u)=bbb}
+(2 rows)
+
+\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, 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
+-----------------------------------------
+ aaa
+(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 00000000000..3a6410cca21
--- /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 00000000000..451781d014e
--- /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;
+
+SELECT setconfig FROM pg_db_role_setting WHERE setrole = 'regular_user'::regrole;
+
+\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';
+SELECT setconfig FROM pg_db_role_setting WHERE setrole = 'regular_user'::regrole;
+
+\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';
+
+SELECT setconfig FROM pg_db_role_setting WHERE setrole = 'regular_user'::regrole;
+
+\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 00000000000..1ed3d285c7e
--- /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 00000000000..01b41b9c9a6
--- /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) 2015-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 00000000000..9678cff376d
--- /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
--
2.24.3 (Apple Git-128)
Hi, Alexander!
On Mon, 5 Dec 2022 at 17:51, Alexander Korotkov <aekorotkov@gmail.com> wrote:
On Mon, Dec 5, 2022 at 2:27 PM Pavel Borisov <pashkin.elfe@gmail.com> wrote:
After posting the patch I've found my own typo in docs. So corrected
it in v5 (PFA).The new revision of the patch is attached.
I've removed the mention of "(s)" suffix from the "Server
Configuration" docs section. I think it might be confusing since this
suffix isn't a part of the variable name. It is only used for storage.
Instead, I've added the description of this suffix to the catalog
structure description and psql documentation.Also, I've added psql tab completion for the USER SET flag, and made
some enhancements to comments, tests, and commit message.
The changes in expected test results are somehow lost in v6, I've
corrected them in v7.
Otherwise, I've looked through the updated patch and it is good.
Regards,
Pavel.
Attachments:
v7-0001-Add-USER-SET-parameter-values-for-pg_db_role_sett.patchapplication/octet-stream; name=v7-0001-Add-USER-SET-parameter-values-for-pg_db_role_sett.patchDownload
From 62eff25c15f634682568314ff049642c16ccfc00 Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <akorotkov@postgresql.org>
Date: Mon, 5 Dec 2022 16:00:33 +0300
Subject: [PATCH v7] Add USER SET parameter values for pg_db_role_setting
The USER SET flag specifies that the variable should be set on behalf of an
ordinary role. That lets ordinary roles set placeholder variables, which
permission requirements are not known yet. Such a value wouldn't be used if
the variable finally appear to require superuser privileges.
The catalog schema isn't changed. Instead, the new flag "(s)" is appended to
the variable name when values are stored in pg_db_role_setting. We don't allow
braces in variable names, thus there is no conflict. However, we still bump
the catversion because the previous code could have an error interpreting the
new flag.
This commit is inspired by the previous work by Steve Chavez.
Discussion: https://postgr.es/m/CAPpHfdsLd6E--epnGqXENqLP6dLwuNZrPMcNYb3wJ87WR7UBOQ%40mail.gmail.com
Author: Alexander Korotkov, Steve Chavez
Reviewed-by: Pavel Borisov, Steve Chavez
---
doc/src/sgml/catalogs.sgml | 6 +-
doc/src/sgml/ref/alter_database.sgml | 15 +-
doc/src/sgml/ref/alter_role.sgml | 22 ++-
doc/src/sgml/ref/alter_user.sgml | 2 +-
doc/src/sgml/ref/psql-ref.sgml | 8 +
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 | 125 +++++++++++-----
src/backend/utils/misc/guc_funcs.c | 12 +-
src/bin/pg_dump/dumputils.c | 18 ++-
src/bin/psql/tab-complete.c | 4 +
src/include/common/guc-common.h | 32 ++++
src/include/nodes/parsenodes.h | 1 +
src/include/utils/guc.h | 4 +-
src/test/modules/Makefile | 1 +
src/test/modules/meson.build | 1 +
.../test_pg_db_role_setting/.gitignore | 4 +
.../modules/test_pg_db_role_setting/Makefile | 29 ++++
.../expected/test_pg_db_role_setting.out | 137 ++++++++++++++++++
.../test_pg_db_role_setting/meson.build | 35 +++++
.../sql/test_pg_db_role_setting.sql | 63 ++++++++
.../test_pg_db_role_setting--1.0.sql | 7 +
.../test_pg_db_role_setting.c | 57 ++++++++
.../test_pg_db_role_setting.control | 7 +
25 files changed, 570 insertions(+), 46 deletions(-)
create mode 100644 src/include/common/guc-common.h
create mode 100644 src/test/modules/test_pg_db_role_setting/.gitignore
create mode 100644 src/test/modules/test_pg_db_role_setting/Makefile
create mode 100644 src/test/modules/test_pg_db_role_setting/expected/test_pg_db_role_setting.out
create mode 100644 src/test/modules/test_pg_db_role_setting/meson.build
create mode 100644 src/test/modules/test_pg_db_role_setting/sql/test_pg_db_role_setting.sql
create mode 100644 src/test/modules/test_pg_db_role_setting/test_pg_db_role_setting--1.0.sql
create mode 100644 src/test/modules/test_pg_db_role_setting/test_pg_db_role_setting.c
create mode 100644 src/test/modules/test_pg_db_role_setting/test_pg_db_role_setting.control
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 9ed2b020b7d..ca9a0f3e1af 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -3191,7 +3191,11 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
<structfield>setconfig</structfield> <type>text[]</type>
</para>
<para>
- Defaults for run-time configuration variables
+ Defaults for run-time configuration variables. Contains a set of strings
+ in the form of <literal>varname=value</literal> or
+ <literal>varname(u)=value</literal>. The <literal>(s)</literal>
+ name suffix signs the <link linkend="sql-alterrole-user-set"><literal>USER SET</literal></link>
+ value.
</para></entry>
</row>
</tbody>
diff --git a/doc/src/sgml/ref/alter_database.sgml b/doc/src/sgml/ref/alter_database.sgml
index 89ed261b4c2..181e9d36205 100644
--- a/doc/src/sgml/ref/alter_database.sgml
+++ b/doc/src/sgml/ref/alter_database.sgml
@@ -37,7 +37,7 @@ ALTER DATABASE <replaceable class="parameter">name</replaceable> SET TABLESPACE
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
@@ -206,6 +206,19 @@ 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>
diff --git a/doc/src/sgml/ref/alter_role.sgml b/doc/src/sgml/ref/alter_role.sgml
index 5aa5648ae7b..1e9c93d6d35 100644
--- a/doc/src/sgml/ref/alter_role.sgml
+++ b/doc/src/sgml/ref/alter_role.sgml
@@ -38,7 +38,7 @@ ALTER ROLE <replaceable class="parameter">role_specification</replaceable> [ WIT
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
@@ -234,6 +234,19 @@ ALTER ROLE { <replaceable class="parameter">role_specification</replaceable> | A
</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>
@@ -329,6 +342,13 @@ ALTER ROLE worker_bee SET maintenance_work_mem = 100000;
<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>
diff --git a/doc/src/sgml/ref/alter_user.sgml b/doc/src/sgml/ref/alter_user.sgml
index 0ee89f54c5c..24f737d5870 100644
--- a/doc/src/sgml/ref/alter_user.sgml
+++ b/doc/src/sgml/ref/alter_user.sgml
@@ -38,7 +38,7 @@ ALTER USER <replaceable class="parameter">role_specification</replaceable> [ WIT
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
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index d3dd638b148..b518a4bd284 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -1891,6 +1891,14 @@ INSERT INTO tbl1 VALUES ($1, $2) \bind 'first value' 'second value' \g
not role-specific or database-specific, respectively.
</para>
+ <para>
+ The settings comprises a set of strings in the form of
+ <literal>varname=value</literal> or <literal>varname(u)=value</literal>.
+ The <literal>(s)</literal> name suffix signs the
+ <link linkend="sql-alterrole-user-set"><literal>USER SET</literal></link>
+ value.
+ </para>
+
<para>
The <link linkend="sql-alterrole"><command>ALTER ROLE</command></link> and
<link linkend="sql-alterdatabase"><command>ALTER DATABASE</command></link>
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 57489f65f2e..dd882576d70 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 b1ae5f834cd..adc3f8ced3b 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/misc/guc.c b/src/backend/utils/misc/guc.c
index 28313b3a94a..eca72d62b6c 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);
@@ -6161,13 +6160,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;
@@ -6178,25 +6180,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,
@@ -6222,6 +6240,7 @@ ProcessGUCArray(ArrayType *array,
char *s;
char *name;
char *value;
+ bool user_set;
d = array_ref(array, 1, &i,
-1 /* varlenarray */ ,
@@ -6235,7 +6254,7 @@ ProcessGUCArray(ArrayType *array,
s = TextDatumGetCString(d);
- ParseLongOption(s, &name, &value);
+ ParseLongOptionInternal(s, &name, &value, &user_set);
if (!value)
{
ereport(WARNING,
@@ -6246,9 +6265,19 @@ ProcessGUCArray(ArrayType *array,
continue;
}
- (void) set_config_option(name, value,
- context, source,
- action, true, 0, 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 (!user_set)
+ (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 +6291,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;
@@ -6273,7 +6303,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);
@@ -6281,7 +6311,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)
@@ -6311,9 +6345,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;
}
@@ -6349,9 +6391,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)
@@ -6381,9 +6420,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)
@@ -6433,6 +6478,7 @@ GUCArrayReset(ArrayType *array)
char *val;
char *eqsgn;
bool isnull;
+ bool user_set = false;
d = array_ref(array, 1, &i,
-1 /* varlenarray */ ,
@@ -6445,10 +6491,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 */
@@ -6474,15 +6528,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 +6573,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 9311417f18c..dd0cf4e3a2e 100644
--- a/src/bin/pg_dump/dumputils.c
+++ b/src/bin/pg_dump/dumputils.c
@@ -16,6 +16,7 @@
#include <ctype.h>
+#include "common/guc-common.h"
#include "dumputils.h"
#include "fe_utils/string_utils.h"
@@ -806,8 +807,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
@@ -822,6 +823,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);
@@ -831,7 +833,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));
@@ -874,6 +882,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/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 89e7317c233..7d222680f53 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/common/guc-common.h b/src/include/common/guc-common.h
new file mode 100644
index 00000000000..d2a84c3a574
--- /dev/null
+++ b/src/include/common/guc-common.h
@@ -0,0 +1,32 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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. GUC name is not allowed
+ * to contain parentheses, so no conflict is possible.
+ */
+#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 f17846e30e2..e8c9e0e8db0 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2231,6 +2231,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);
diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile
index 96addded814..c629cbe3830 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 1d265448549..911a768a294 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 00000000000..5dcb3ff9723
--- /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 00000000000..aacd78f74c5
--- /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 00000000000..7544e454b4c
--- /dev/null
+++ b/src/test/modules/test_pg_db_role_setting/expected/test_pg_db_role_setting.out
@@ -0,0 +1,137 @@
+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;
+SELECT setconfig FROM pg_db_role_setting WHERE setrole = 'regular_user'::regrole;
+ setconfig
+--------------------------------------------------------------------------------------------
+ {test_pg_db_role_setting.superuser_param(u)=aaa,test_pg_db_role_setting.user_param(u)=bbb}
+(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';
+SELECT setconfig FROM pg_db_role_setting WHERE setrole = 'regular_user'::regrole;
+ setconfig
+-----------------------------------------------------------------------------------------
+ {test_pg_db_role_setting.superuser_param=aaa,test_pg_db_role_setting.user_param(u)=bbb}
+(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';
+SELECT setconfig FROM pg_db_role_setting WHERE setrole = 'regular_user'::regrole;
+ setconfig
+-----------------------------------------------------------------------------------------
+ {test_pg_db_role_setting.superuser_param=ccc,test_pg_db_role_setting.user_param(u)=bbb}
+(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 00000000000..3a6410cca21
--- /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 00000000000..451781d014e
--- /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;
+
+SELECT setconfig FROM pg_db_role_setting WHERE setrole = 'regular_user'::regrole;
+
+\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';
+SELECT setconfig FROM pg_db_role_setting WHERE setrole = 'regular_user'::regrole;
+
+\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';
+
+SELECT setconfig FROM pg_db_role_setting WHERE setrole = 'regular_user'::regrole;
+
+\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 00000000000..1ed3d285c7e
--- /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 00000000000..01b41b9c9a6
--- /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) 2015-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 00000000000..9678cff376d
--- /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
--
2.24.3 (Apple Git-128)
I couldn't find any discussion of the idea of adding "(s)" to the
variable name in order to mark the variable userset in the catalog, and
I have to admit I find it a bit strange. Are we really agreed that
that's the way to proceed?
--
Álvaro Herrera PostgreSQL Developer — https://www.EnterpriseDB.com/
"Having your biases confirmed independently is how scientific progress is
made, and hence made our great society what it is today" (Mary Gardiner)
Alvaro Herrera <alvherre@alvh.no-ip.org> writes:
I couldn't find any discussion of the idea of adding "(s)" to the
variable name in order to mark the variable userset in the catalog, and
I have to admit I find it a bit strange. Are we really agreed that
that's the way to proceed?
I hadn't been paying close attention to this thread, sorry.
I agree that that seems like a very regrettable choice,
especially if you anticipate having to bump catversion anyway.
Better to add a bool column to the catalog.
regards, tom lane
On Mon, Dec 5, 2022 at 8:18 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
Alvaro Herrera <alvherre@alvh.no-ip.org> writes:
I couldn't find any discussion of the idea of adding "(s)" to the
variable name in order to mark the variable userset in the catalog, and
I have to admit I find it a bit strange. Are we really agreed that
that's the way to proceed?I hadn't been paying close attention to this thread, sorry.
I agree that that seems like a very regrettable choice,
especially if you anticipate having to bump catversion anyway.
I totally understand that this change requires a catversion bump.
I've reflected this in the commit message.
Better to add a bool column to the catalog.
What about adding a boolean array to the pg_db_role_setting? So,
pg_db_role_setting would have the following columns.
* setdatabase oid
* setrole oid
* setconfig text[]
* setuser bool[]
------
Regards,
Alexander Korotkov
On Mon, Dec 5, 2022 at 10:32 PM Alexander Korotkov <aekorotkov@gmail.com> wrote:
On Mon, Dec 5, 2022 at 8:18 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
Alvaro Herrera <alvherre@alvh.no-ip.org> writes:
I couldn't find any discussion of the idea of adding "(s)" to the
variable name in order to mark the variable userset in the catalog, and
I have to admit I find it a bit strange. Are we really agreed that
that's the way to proceed?I hadn't been paying close attention to this thread, sorry.
I agree that that seems like a very regrettable choice,
especially if you anticipate having to bump catversion anyway.I totally understand that this change requires a catversion bump.
I've reflected this in the commit message.Better to add a bool column to the catalog.
What about adding a boolean array to the pg_db_role_setting? So,
pg_db_role_setting would have the following columns.
* setdatabase oid
* setrole oid
* setconfig text[]
* setuser bool[]
The revised patch implements this way for storage USER SET flag. I
think it really became more structured and less cumbersome.
------
Regards,
Alexander Korotkov
Attachments:
0001-Add-USER-SET-parameter-values-for-pg_db_role_sett-v8.patchapplication/octet-stream; name=0001-Add-USER-SET-parameter-values-for-pg_db_role_sett-v8.patchDownload
From 946be1809eb46941cbcb885c474cb101453f2336 Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <akorotkov@postgresql.org>
Date: Mon, 5 Dec 2022 16:00:33 +0300
Subject: [PATCH] Add USER SET parameter values for pg_db_role_setting
The USER SET flag specifies that the variable should be set on behalf of an
ordinary role. That lets ordinary roles set placeholder variables, which
permission requirements are not known yet. Such a value wouldn't be used if
the variable finally appear to require superuser privileges.
The new flags are stored in the pg_db_role_setting.setuser array. Catversion
is bumped.
This commit is inspired by the previous work by Steve Chavez.
Discussion: https://postgr.es/m/CAPpHfdsLd6E--epnGqXENqLP6dLwuNZrPMcNYb3wJ87WR7UBOQ%40mail.gmail.com
Author: Alexander Korotkov, Steve Chavez
Reviewed-by: Pavel Borisov, Steve Chavez
---
doc/src/sgml/catalogs.sgml | 10 ++
doc/src/sgml/ref/alter_database.sgml | 15 +-
doc/src/sgml/ref/alter_role.sgml | 22 ++-
doc/src/sgml/ref/alter_user.sgml | 2 +-
doc/src/sgml/ref/psql-ref.sgml | 7 +
src/backend/catalog/pg_db_role_setting.c | 46 ++++-
src/backend/catalog/pg_proc.c | 1 +
src/backend/commands/functioncmds.c | 4 +-
src/backend/parser/gram.y | 20 +++
src/backend/utils/adt/arrayfuncs.c | 1 +
src/backend/utils/fmgr/fmgr.c | 1 +
src/backend/utils/misc/guc.c | 160 +++++++++++++++---
src/backend/utils/misc/guc_funcs.c | 12 +-
src/bin/pg_dump/dumputils.c | 5 +
src/bin/pg_dump/dumputils.h | 1 +
src/bin/pg_dump/pg_dump.c | 27 ++-
src/bin/pg_dump/pg_dumpall.c | 12 +-
src/bin/psql/describe.c | 11 +-
src/bin/psql/tab-complete.c | 4 +
src/include/catalog/pg_db_role_setting.h | 2 +
src/include/nodes/parsenodes.h | 1 +
src/include/utils/guc.h | 11 +-
src/test/modules/Makefile | 1 +
src/test/modules/meson.build | 1 +
.../test_pg_db_role_setting/.gitignore | 4 +
.../modules/test_pg_db_role_setting/Makefile | 29 ++++
.../expected/test_pg_db_role_setting.out | 143 ++++++++++++++++
.../test_pg_db_role_setting/meson.build | 35 ++++
.../sql/test_pg_db_role_setting.sql | 63 +++++++
.../test_pg_db_role_setting--1.0.sql | 7 +
.../test_pg_db_role_setting.c | 57 +++++++
.../test_pg_db_role_setting.control | 7 +
src/test/regress/expected/psql.out | 6 +-
33 files changed, 678 insertions(+), 50 deletions(-)
create mode 100644 src/test/modules/test_pg_db_role_setting/.gitignore
create mode 100644 src/test/modules/test_pg_db_role_setting/Makefile
create mode 100644 src/test/modules/test_pg_db_role_setting/expected/test_pg_db_role_setting.out
create mode 100644 src/test/modules/test_pg_db_role_setting/meson.build
create mode 100644 src/test/modules/test_pg_db_role_setting/sql/test_pg_db_role_setting.sql
create mode 100644 src/test/modules/test_pg_db_role_setting/test_pg_db_role_setting--1.0.sql
create mode 100644 src/test/modules/test_pg_db_role_setting/test_pg_db_role_setting.c
create mode 100644 src/test/modules/test_pg_db_role_setting/test_pg_db_role_setting.control
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 9ed2b020b7d..4ecd18e321d 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -3194,6 +3194,16 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
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>
diff --git a/doc/src/sgml/ref/alter_database.sgml b/doc/src/sgml/ref/alter_database.sgml
index 89ed261b4c2..181e9d36205 100644
--- a/doc/src/sgml/ref/alter_database.sgml
+++ b/doc/src/sgml/ref/alter_database.sgml
@@ -37,7 +37,7 @@ ALTER DATABASE <replaceable class="parameter">name</replaceable> SET TABLESPACE
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
@@ -206,6 +206,19 @@ 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>
diff --git a/doc/src/sgml/ref/alter_role.sgml b/doc/src/sgml/ref/alter_role.sgml
index 5aa5648ae7b..1e9c93d6d35 100644
--- a/doc/src/sgml/ref/alter_role.sgml
+++ b/doc/src/sgml/ref/alter_role.sgml
@@ -38,7 +38,7 @@ ALTER ROLE <replaceable class="parameter">role_specification</replaceable> [ WIT
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
@@ -234,6 +234,19 @@ ALTER ROLE { <replaceable class="parameter">role_specification</replaceable> | A
</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>
@@ -329,6 +342,13 @@ ALTER ROLE worker_bee SET maintenance_work_mem = 100000;
<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>
diff --git a/doc/src/sgml/ref/alter_user.sgml b/doc/src/sgml/ref/alter_user.sgml
index 0ee89f54c5c..24f737d5870 100644
--- a/doc/src/sgml/ref/alter_user.sgml
+++ b/doc/src/sgml/ref/alter_user.sgml
@@ -38,7 +38,7 @@ ALTER USER <replaceable class="parameter">role_specification</replaceable> [ WIT
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
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index d3dd638b148..8a5285da9aa 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.
</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>
diff --git a/src/backend/catalog/pg_db_role_setting.c b/src/backend/catalog/pg_db_role_setting.c
index 42387f4e304..6572fcd965b 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 69f43aa0ecb..e3f9d0b5cfe 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 57489f65f2e..f020fe5ec85 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 b1ae5f834cd..adc3f8ced3b 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 495e449a9e9..59a0852d07c 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 3c210297aa1..cd0daa7e166 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 28313b3a94a..7097c7ed096 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;
@@ -6430,6 +6520,7 @@ GUCArrayReset(ArrayType *array)
for (i = 1; i <= ARR_DIMS(array)[0]; i++)
{
Datum d;
+ Datum userSetDatum = BoolGetDatum(false);
char *val;
char *eqsgn;
bool isnull;
@@ -6444,15 +6535,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 +6563,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(&d, 1, BOOLOID);
+ }
index++;
pfree(val);
}
+ if (usersetArray)
+ *usersetArray = newUsersetArray;
+
return newarray;
}
@@ -6474,15 +6593,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 +6638,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..23da603fe76 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 9311417f18c..c0985fae5ad 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 c67c3b5b842..6e7f50c6b86 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 da427f4d4a1..91924cc0d23 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -3235,32 +3235,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 a87262e3335..7b40081678b 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 2eae519b1dd..df166365e81 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 89e7317c233..7d222680f53 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/pg_db_role_setting.h b/src/include/catalog/pg_db_role_setting.h
index f92e867df45..c52bbf665f1 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 6112cd85c84..78437b0bfbb 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2231,6 +2231,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..91cc3418547 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 96addded814..c629cbe3830 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 1d265448549..911a768a294 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 00000000000..5dcb3ff9723
--- /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 00000000000..aacd78f74c5
--- /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 00000000000..4da17dca28c
--- /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 00000000000..3a6410cca21
--- /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 00000000000..cb6eb0448e3
--- /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 00000000000..1ed3d285c7e
--- /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 00000000000..01b41b9c9a6
--- /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) 2015-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 00000000000..9678cff376d
--- /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 1047399ef88..1e78ba73e14 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -6126,9 +6126,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"
--
2.24.3 (Apple Git-128)
Hi, Alexander!
On Tue, 6 Dec 2022 at 19:01, Alexander Korotkov <aekorotkov@gmail.com> wrote:
On Mon, Dec 5, 2022 at 10:32 PM Alexander Korotkov <aekorotkov@gmail.com> wrote:
On Mon, Dec 5, 2022 at 8:18 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
Alvaro Herrera <alvherre@alvh.no-ip.org> writes:
I couldn't find any discussion of the idea of adding "(s)" to the
variable name in order to mark the variable userset in the catalog, and
I have to admit I find it a bit strange. Are we really agreed that
that's the way to proceed?I hadn't been paying close attention to this thread, sorry.
I agree that that seems like a very regrettable choice,
especially if you anticipate having to bump catversion anyway.I totally understand that this change requires a catversion bump.
I've reflected this in the commit message.Better to add a bool column to the catalog.
What about adding a boolean array to the pg_db_role_setting? So,
pg_db_role_setting would have the following columns.
* setdatabase oid
* setrole oid
* setconfig text[]
* setuser bool[]The revised patch implements this way for storage USER SET flag.
think it really became more structured and less cumbersome.
I agree that the patch became more structured and the complications
for string parameter suffixing have gone away. I've looked it through
and don't see problems with it. The only two-lines fix regarding
variable initializing may be relevant (see v9). Tests pass and CI is
also happy with it. I'd like to set it ready for committer if no
objections.
Regards,
Pavel Borisov,
Supabase.
Attachments:
v9-0001-Add-USER-SET-parameter-values-for-pg_db_role_sett.patchapplication/octet-stream; name=v9-0001-Add-USER-SET-parameter-values-for-pg_db_role_sett.patchDownload
From d03e62c114549f24fda9b70abd3b56691ee73c86 Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <akorotkov@postgresql.org>
Date: Mon, 5 Dec 2022 16:00:33 +0300
Subject: [PATCH v9] Add USER SET parameter values for pg_db_role_setting
The USER SET flag specifies that the variable should be set on behalf of an
ordinary role. That lets ordinary roles set placeholder variables, which
permission requirements are not known yet. Such a value wouldn't be used if
the variable finally appear to require superuser privileges.
The new flags are stored in the pg_db_role_setting.setuser array. Catversion
is bumped.
This commit is inspired by the previous work by Steve Chavez.
Discussion: https://postgr.es/m/CAPpHfdsLd6E--epnGqXENqLP6dLwuNZrPMcNYb3wJ87WR7UBOQ%40mail.gmail.com
Author: Alexander Korotkov, Steve Chavez
Reviewed-by: Pavel Borisov, Steve Chavez
---
doc/src/sgml/catalogs.sgml | 10 ++
doc/src/sgml/ref/alter_database.sgml | 15 +-
doc/src/sgml/ref/alter_role.sgml | 22 ++-
doc/src/sgml/ref/alter_user.sgml | 2 +-
doc/src/sgml/ref/psql-ref.sgml | 7 +
src/backend/catalog/pg_db_role_setting.c | 46 ++++-
src/backend/catalog/pg_proc.c | 1 +
src/backend/commands/functioncmds.c | 4 +-
src/backend/parser/gram.y | 20 +++
src/backend/utils/adt/arrayfuncs.c | 1 +
src/backend/utils/fmgr/fmgr.c | 1 +
src/backend/utils/misc/guc.c | 161 +++++++++++++++---
src/backend/utils/misc/guc_funcs.c | 12 +-
src/bin/pg_dump/dumputils.c | 5 +
src/bin/pg_dump/dumputils.h | 1 +
src/bin/pg_dump/pg_dump.c | 27 ++-
src/bin/pg_dump/pg_dumpall.c | 12 +-
src/bin/psql/describe.c | 11 +-
src/bin/psql/tab-complete.c | 4 +
src/include/catalog/pg_db_role_setting.h | 2 +
src/include/nodes/parsenodes.h | 1 +
src/include/utils/guc.h | 11 +-
src/test/modules/Makefile | 1 +
src/test/modules/meson.build | 1 +
.../test_pg_db_role_setting/.gitignore | 4 +
.../modules/test_pg_db_role_setting/Makefile | 29 ++++
.../expected/test_pg_db_role_setting.out | 143 ++++++++++++++++
.../test_pg_db_role_setting/meson.build | 35 ++++
.../sql/test_pg_db_role_setting.sql | 63 +++++++
.../test_pg_db_role_setting--1.0.sql | 7 +
.../test_pg_db_role_setting.c | 57 +++++++
.../test_pg_db_role_setting.control | 7 +
src/test/regress/expected/psql.out | 6 +-
33 files changed, 679 insertions(+), 50 deletions(-)
create mode 100644 src/test/modules/test_pg_db_role_setting/.gitignore
create mode 100644 src/test/modules/test_pg_db_role_setting/Makefile
create mode 100644 src/test/modules/test_pg_db_role_setting/expected/test_pg_db_role_setting.out
create mode 100644 src/test/modules/test_pg_db_role_setting/meson.build
create mode 100644 src/test/modules/test_pg_db_role_setting/sql/test_pg_db_role_setting.sql
create mode 100644 src/test/modules/test_pg_db_role_setting/test_pg_db_role_setting--1.0.sql
create mode 100644 src/test/modules/test_pg_db_role_setting/test_pg_db_role_setting.c
create mode 100644 src/test/modules/test_pg_db_role_setting/test_pg_db_role_setting.control
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 9ed2b020b7d..4ecd18e321d 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -3194,6 +3194,16 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
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>
diff --git a/doc/src/sgml/ref/alter_database.sgml b/doc/src/sgml/ref/alter_database.sgml
index 89ed261b4c2..181e9d36205 100644
--- a/doc/src/sgml/ref/alter_database.sgml
+++ b/doc/src/sgml/ref/alter_database.sgml
@@ -37,7 +37,7 @@ ALTER DATABASE <replaceable class="parameter">name</replaceable> SET TABLESPACE
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
@@ -206,6 +206,19 @@ 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>
diff --git a/doc/src/sgml/ref/alter_role.sgml b/doc/src/sgml/ref/alter_role.sgml
index 5aa5648ae7b..1e9c93d6d35 100644
--- a/doc/src/sgml/ref/alter_role.sgml
+++ b/doc/src/sgml/ref/alter_role.sgml
@@ -38,7 +38,7 @@ ALTER ROLE <replaceable class="parameter">role_specification</replaceable> [ WIT
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
@@ -234,6 +234,19 @@ ALTER ROLE { <replaceable class="parameter">role_specification</replaceable> | A
</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>
@@ -329,6 +342,13 @@ ALTER ROLE worker_bee SET maintenance_work_mem = 100000;
<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>
diff --git a/doc/src/sgml/ref/alter_user.sgml b/doc/src/sgml/ref/alter_user.sgml
index 0ee89f54c5c..24f737d5870 100644
--- a/doc/src/sgml/ref/alter_user.sgml
+++ b/doc/src/sgml/ref/alter_user.sgml
@@ -38,7 +38,7 @@ ALTER USER <replaceable class="parameter">role_specification</replaceable> [ WIT
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
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index d3dd638b148..8a5285da9aa 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.
</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>
diff --git a/src/backend/catalog/pg_db_role_setting.c b/src/backend/catalog/pg_db_role_setting.c
index 42387f4e304..6572fcd965b 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 69f43aa0ecb..e3f9d0b5cfe 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 57489f65f2e..f020fe5ec85 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 b1ae5f834cd..adc3f8ced3b 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 495e449a9e9..59a0852d07c 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 3c210297aa1..cd0daa7e166 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 28313b3a94a..c6326d50532 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 108b3bd1290..23da603fe76 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 9311417f18c..c0985fae5ad 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 c67c3b5b842..6e7f50c6b86 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 ad6693c358a..44d957c0388 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 a87262e3335..7b40081678b 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 2eae519b1dd..df166365e81 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 89e7317c233..7d222680f53 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/pg_db_role_setting.h b/src/include/catalog/pg_db_role_setting.h
index f92e867df45..c52bbf665f1 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 6a6d3293e41..8fe9b2fcfe9 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 b3aaff9665b..91cc3418547 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 96addded814..c629cbe3830 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 1d265448549..911a768a294 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 00000000000..5dcb3ff9723
--- /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 00000000000..aacd78f74c5
--- /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 00000000000..4da17dca28c
--- /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 00000000000..3a6410cca21
--- /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 00000000000..cb6eb0448e3
--- /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 00000000000..1ed3d285c7e
--- /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 00000000000..01b41b9c9a6
--- /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) 2015-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 00000000000..9678cff376d
--- /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 b4cb6ffb5b4..8fc62cebd2d 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"
--
2.24.3 (Apple Git-128)
On Wed, Dec 7, 2022 at 1:28 AM Pavel Borisov <pashkin.elfe@gmail.com> wrote:
On Tue, 6 Dec 2022 at 19:01, Alexander Korotkov <aekorotkov@gmail.com> wrote:
On Mon, Dec 5, 2022 at 10:32 PM Alexander Korotkov <aekorotkov@gmail.com> wrote:
On Mon, Dec 5, 2022 at 8:18 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
Alvaro Herrera <alvherre@alvh.no-ip.org> writes:
I couldn't find any discussion of the idea of adding "(s)" to the
variable name in order to mark the variable userset in the catalog, and
I have to admit I find it a bit strange. Are we really agreed that
that's the way to proceed?I hadn't been paying close attention to this thread, sorry.
I agree that that seems like a very regrettable choice,
especially if you anticipate having to bump catversion anyway.I totally understand that this change requires a catversion bump.
I've reflected this in the commit message.Better to add a bool column to the catalog.
What about adding a boolean array to the pg_db_role_setting? So,
pg_db_role_setting would have the following columns.
* setdatabase oid
* setrole oid
* setconfig text[]
* setuser bool[]The revised patch implements this way for storage USER SET flag.
think it really became more structured and less cumbersome.I agree that the patch became more structured and the complications
for string parameter suffixing have gone away. I've looked it through
and don't see problems with it. The only two-lines fix regarding
variable initializing may be relevant (see v9). Tests pass and CI is
also happy with it. I'd like to set it ready for committer if no
objections.
Thank you, Pavel.
I've made few minor improvements in the docs and comments.
I'm going to push this if no objections.
------
Regards,
Alexander Korotkov
Attachments:
0001-Add-USER-SET-parameter-values-for-pg_db_role_set-v10.patchapplication/octet-stream; name=0001-Add-USER-SET-parameter-values-for-pg_db_role_set-v10.patchDownload
From a26a20dc4102733df0fd18652430bfbdcd82e6bb Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <akorotkov@postgresql.org>
Date: Mon, 5 Dec 2022 16:00:33 +0300
Subject: [PATCH] Add USER SET parameter values for pg_db_role_setting
The USER SET flag specifies that the variable should be set on behalf of an
ordinary role. That lets ordinary roles set placeholder variables, which
permission requirements are not known yet. Such a value wouldn't be used if
the variable finally appear to require superuser privileges.
The new flags are stored in the pg_db_role_setting.setuser array. Catversion
is bumped.
This commit is inspired by the previous work by Steve Chavez.
Discussion: https://postgr.es/m/CAPpHfdsLd6E--epnGqXENqLP6dLwuNZrPMcNYb3wJ87WR7UBOQ%40mail.gmail.com
Author: Alexander Korotkov, Steve Chavez
Reviewed-by: Pavel Borisov, Steve Chavez
---
doc/src/sgml/catalogs.sgml | 10 ++
doc/src/sgml/ref/alter_database.sgml | 15 +-
doc/src/sgml/ref/alter_role.sgml | 22 ++-
doc/src/sgml/ref/alter_user.sgml | 2 +-
doc/src/sgml/ref/psql-ref.sgml | 7 +
src/backend/catalog/pg_db_role_setting.c | 46 ++++-
src/backend/catalog/pg_proc.c | 1 +
src/backend/commands/functioncmds.c | 4 +-
src/backend/parser/gram.y | 20 +++
src/backend/utils/adt/arrayfuncs.c | 1 +
src/backend/utils/fmgr/fmgr.c | 1 +
src/backend/utils/misc/guc.c | 161 +++++++++++++++---
src/backend/utils/misc/guc_funcs.c | 12 +-
src/bin/pg_dump/dumputils.c | 5 +
src/bin/pg_dump/dumputils.h | 1 +
src/bin/pg_dump/pg_dump.c | 27 ++-
src/bin/pg_dump/pg_dumpall.c | 12 +-
src/bin/psql/describe.c | 11 +-
src/bin/psql/tab-complete.c | 4 +
src/include/catalog/pg_db_role_setting.h | 2 +
src/include/nodes/parsenodes.h | 1 +
src/include/utils/guc.h | 11 +-
src/test/modules/Makefile | 1 +
src/test/modules/meson.build | 1 +
.../test_pg_db_role_setting/.gitignore | 4 +
.../modules/test_pg_db_role_setting/Makefile | 29 ++++
.../expected/test_pg_db_role_setting.out | 143 ++++++++++++++++
.../test_pg_db_role_setting/meson.build | 35 ++++
.../sql/test_pg_db_role_setting.sql | 63 +++++++
.../test_pg_db_role_setting--1.0.sql | 7 +
.../test_pg_db_role_setting.c | 57 +++++++
.../test_pg_db_role_setting.control | 7 +
src/test/regress/expected/psql.out | 6 +-
33 files changed, 679 insertions(+), 50 deletions(-)
create mode 100644 src/test/modules/test_pg_db_role_setting/.gitignore
create mode 100644 src/test/modules/test_pg_db_role_setting/Makefile
create mode 100644 src/test/modules/test_pg_db_role_setting/expected/test_pg_db_role_setting.out
create mode 100644 src/test/modules/test_pg_db_role_setting/meson.build
create mode 100644 src/test/modules/test_pg_db_role_setting/sql/test_pg_db_role_setting.sql
create mode 100644 src/test/modules/test_pg_db_role_setting/test_pg_db_role_setting--1.0.sql
create mode 100644 src/test/modules/test_pg_db_role_setting/test_pg_db_role_setting.c
create mode 100644 src/test/modules/test_pg_db_role_setting/test_pg_db_role_setting.control
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 9ed2b020b7d..9316b811ac3 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -3194,6 +3194,16 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
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>
diff --git a/doc/src/sgml/ref/alter_database.sgml b/doc/src/sgml/ref/alter_database.sgml
index 89ed261b4c2..181e9d36205 100644
--- a/doc/src/sgml/ref/alter_database.sgml
+++ b/doc/src/sgml/ref/alter_database.sgml
@@ -37,7 +37,7 @@ ALTER DATABASE <replaceable class="parameter">name</replaceable> SET TABLESPACE
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
@@ -206,6 +206,19 @@ 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>
diff --git a/doc/src/sgml/ref/alter_role.sgml b/doc/src/sgml/ref/alter_role.sgml
index 5aa5648ae7b..33ac7327070 100644
--- a/doc/src/sgml/ref/alter_role.sgml
+++ b/doc/src/sgml/ref/alter_role.sgml
@@ -38,7 +38,7 @@ ALTER ROLE <replaceable class="parameter">role_specification</replaceable> [ WIT
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
@@ -234,6 +234,19 @@ ALTER ROLE { <replaceable class="parameter">role_specification</replaceable> | A
</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>
@@ -329,6 +342,13 @@ ALTER ROLE worker_bee SET maintenance_work_mem = 100000;
<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>
diff --git a/doc/src/sgml/ref/alter_user.sgml b/doc/src/sgml/ref/alter_user.sgml
index 0ee89f54c5c..24f737d5870 100644
--- a/doc/src/sgml/ref/alter_user.sgml
+++ b/doc/src/sgml/ref/alter_user.sgml
@@ -38,7 +38,7 @@ ALTER USER <replaceable class="parameter">role_specification</replaceable> [ WIT
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
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index d3dd638b148..8a5285da9aa 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.
</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>
diff --git a/src/backend/catalog/pg_db_role_setting.c b/src/backend/catalog/pg_db_role_setting.c
index 42387f4e304..6572fcd965b 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 69f43aa0ecb..e3f9d0b5cfe 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 57489f65f2e..f020fe5ec85 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 b1ae5f834cd..adc3f8ced3b 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 495e449a9e9..59a0852d07c 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 3c210297aa1..cd0daa7e166 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 28313b3a94a..c6326d50532 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 108b3bd1290..23da603fe76 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 9311417f18c..c0985fae5ad 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 c67c3b5b842..6e7f50c6b86 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 ad6693c358a..44d957c0388 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 a87262e3335..7b40081678b 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 2eae519b1dd..df166365e81 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 89e7317c233..7d222680f53 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/pg_db_role_setting.h b/src/include/catalog/pg_db_role_setting.h
index f92e867df45..c52bbf665f1 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 6a6d3293e41..8fe9b2fcfe9 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 b3aaff9665b..91cc3418547 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 96addded814..c629cbe3830 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 1d265448549..911a768a294 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 00000000000..5dcb3ff9723
--- /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 00000000000..aacd78f74c5
--- /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 00000000000..4da17dca28c
--- /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 00000000000..3a6410cca21
--- /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 00000000000..cb6eb0448e3
--- /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 00000000000..1ed3d285c7e
--- /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 00000000000..3982ae5629f
--- /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 00000000000..9678cff376d
--- /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 b4cb6ffb5b4..8fc62cebd2d 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"
--
2.24.3 (Apple Git-128)
On Wed, Dec 7, 2022 at 4:36 PM Alexander Korotkov <aekorotkov@gmail.com> wrote:
On Wed, Dec 7, 2022 at 1:28 AM Pavel Borisov <pashkin.elfe@gmail.com> wrote:
On Tue, 6 Dec 2022 at 19:01, Alexander Korotkov <aekorotkov@gmail.com> wrote:
On Mon, Dec 5, 2022 at 10:32 PM Alexander Korotkov <aekorotkov@gmail.com> wrote:
On Mon, Dec 5, 2022 at 8:18 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
Alvaro Herrera <alvherre@alvh.no-ip.org> writes:
I couldn't find any discussion of the idea of adding "(s)" to the
variable name in order to mark the variable userset in the catalog, and
I have to admit I find it a bit strange. Are we really agreed that
that's the way to proceed?I hadn't been paying close attention to this thread, sorry.
I agree that that seems like a very regrettable choice,
especially if you anticipate having to bump catversion anyway.I totally understand that this change requires a catversion bump.
I've reflected this in the commit message.Better to add a bool column to the catalog.
What about adding a boolean array to the pg_db_role_setting? So,
pg_db_role_setting would have the following columns.
* setdatabase oid
* setrole oid
* setconfig text[]
* setuser bool[]The revised patch implements this way for storage USER SET flag.
think it really became more structured and less cumbersome.I agree that the patch became more structured and the complications
for string parameter suffixing have gone away. I've looked it through
and don't see problems with it. The only two-lines fix regarding
variable initializing may be relevant (see v9). Tests pass and CI is
also happy with it. I'd like to set it ready for committer if no
objections.Thank you, Pavel.
I've made few minor improvements in the docs and comments.
I'm going to push this if no objections.
Pushed, thanks to everyone!
------
Regards,
Alexander Korotkov
Hi, Alexander!
Pushed, thanks to everyone!
Thank you!
I've found a minor thing that makes the new test fail on sifaka and
longfin build farm animals. If role names in regression don't start
with "regress_" this invokes a warning. I've consulted in other
modules regression tests e.g. in test_rls_hooks and changed the role
naming accordingly. In essence, a fix is just a batch replace in test
SQL and expected results.
Regards,
Pavel.
Attachments:
0001-Fix-role-names-in-regression-test-in-module-test_pg_.patchapplication/octet-stream; name=0001-Fix-role-names-in-regression-test-in-module-test_pg_.patchDownload
From 43f184b5c308436bbdce29eaf8e5e8c232fa6d89 Mon Sep 17 00:00:00 2001
From: Pavel Borisov <pashkin.elfe@gmail.com>
Date: Fri, 9 Dec 2022 15:02:41 +0400
Subject: [PATCH] Fix role names in regression test in module
test_pg_db_role_setting
Buildfarm members sifaka and longfin were unhappy that role names
in regression tests didn't start with "regress_". The fix is just
regexp replace in test sql and results.
---
.../expected/test_pg_db_role_setting.out | 76 +++++++++----------
.../sql/test_pg_db_role_setting.sql | 42 +++++-----
2 files changed, 59 insertions(+), 59 deletions(-)
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
index 4da17dca28c..bee237cf40b 100644
--- 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
@@ -1,7 +1,7 @@
CREATE EXTENSION test_pg_db_role_setting;
-CREATE USER super_user SUPERUSER;
-CREATE USER regular_user;
-\c - regular_user
+CREATE USER regress_super_user SUPERUSER;
+CREATE USER regress_regular_user;
+\c - regress_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
@@ -24,24 +24,24 @@ SHOW test_pg_db_role_setting.user_param;
user_param_value
(1 row)
-\c - regular_user
+\c - regress_regular_user
-- fail, not privileges
-ALTER ROLE regular_user SET test_pg_db_role_setting.superuser_param = 'aaa';
+ALTER ROLE regress_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';
+ALTER ROLE regress_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
+ALTER ROLE regress_regular_user SET test_pg_db_role_setting.superuser_param = 'aaa' USER SET;
+ALTER ROLE regress_regular_user SET test_pg_db_role_setting.user_param = 'bbb' USER SET;
+\drds regress_regular_user
+ List of settings
+ Role | Database | Settings | User set
+----------------------+----------+---------------------------------------------+----------
+ regress_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
+\c - regress_regular_user
-- successfully set placeholders
SHOW test_pg_db_role_setting.superuser_param;
test_pg_db_role_setting.superuser_param
@@ -75,21 +75,21 @@ SHOW 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
+\c - regress_super_user
+ALTER ROLE regress_regular_user SET test_pg_db_role_setting.superuser_param = 'aaa';
+\drds regress_regular_user
+ List of settings
+ Role | Database | Settings | User set
+----------------------+----------+---------------------------------------------+----------
+ regress_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
+\c - regress_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;
+ALTER ROLE regress_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
+\c - regress_super_user
SELECT load_test_pg_db_role_setting();
load_test_pg_db_role_setting
------------------------------
@@ -97,18 +97,18 @@ SELECT 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
+GRANT SET ON PARAMETER test_pg_db_role_setting.superuser_param TO regress_regular_user;
+\c - regress_regular_user
+ALTER ROLE regress_regular_user SET test_pg_db_role_setting.superuser_param = 'ccc';
+\drds regress_regular_user
+ List of settings
+ Role | Database | Settings | User set
+----------------------+----------+---------------------------------------------+----------
+ regress_regular_user | | test_pg_db_role_setting.superuser_param=ccc+| f +
+ | | test_pg_db_role_setting.user_param=bbb | t
+(1 row)
+
+\c - regress_regular_user
-- successfully set placeholders
SHOW test_pg_db_role_setting.superuser_param;
test_pg_db_role_setting.superuser_param
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
index cb6eb0448e3..c6095dfa98c 100644
--- 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
@@ -1,8 +1,8 @@
CREATE EXTENSION test_pg_db_role_setting;
-CREATE USER super_user SUPERUSER;
-CREATE USER regular_user;
+CREATE USER regress_super_user SUPERUSER;
+CREATE USER regress_regular_user;
-\c - regular_user
+\c - regress_regular_user
-- successfully set a placeholder value
SET test_pg_db_role_setting.superuser_param = 'aaa';
@@ -12,17 +12,17 @@ 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
+\c - regress_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';
+ALTER ROLE regress_regular_user SET test_pg_db_role_setting.superuser_param = 'aaa';
+ALTER ROLE regress_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;
+ALTER ROLE regress_regular_user SET test_pg_db_role_setting.superuser_param = 'aaa' USER SET;
+ALTER ROLE regress_regular_user SET test_pg_db_role_setting.user_param = 'bbb' USER SET;
-\drds regular_user
+\drds regress_regular_user
-\c - regular_user
+\c - regress_regular_user
-- successfully set placeholders
SHOW test_pg_db_role_setting.superuser_param;
SHOW test_pg_db_role_setting.user_param;
@@ -33,25 +33,25 @@ 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 - regress_super_user
+ALTER ROLE regress_regular_user SET test_pg_db_role_setting.superuser_param = 'aaa';
+\drds regress_regular_user
-\c - regular_user
+\c - regress_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;
+ALTER ROLE regress_regular_user SET test_pg_db_role_setting.superuser_param = 'ccc' USER SET;
-\c - super_user
+\c - regress_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;
+GRANT SET ON PARAMETER test_pg_db_role_setting.superuser_param TO regress_regular_user;
-\c - regular_user
-ALTER ROLE regular_user SET test_pg_db_role_setting.superuser_param = 'ccc';
+\c - regress_regular_user
+ALTER ROLE regress_regular_user SET test_pg_db_role_setting.superuser_param = 'ccc';
-\drds regular_user
+\drds regress_regular_user
-\c - regular_user
+\c - regress_regular_user
-- successfully set placeholders
SHOW test_pg_db_role_setting.superuser_param;
SHOW test_pg_db_role_setting.user_param;
--
2.24.3 (Apple Git-128)
Hi, Alexander!
Hi, Alexander!
Pushed, thanks to everyone!
Thank you!
I've found a minor thing that makes the new test fail on sifaka and
longfin build farm animals. If role names in regression don't start
with "regress_" this invokes a warning. I've consulted in other
modules regression tests e.g. in test_rls_hooks and changed the role
naming accordingly. In essence, a fix is just a batch replace in test
SQL and expected results.
I see you already pushed the fix for this in beecbe8e5001. So no
worries, it it not needed anymore.
Regards,
Pavel.
On Fri, Dec 9, 2022 at 2:57 PM Pavel Borisov <pashkin.elfe@gmail.com> wrote:
Pushed, thanks to everyone!
Thank you!
I've found a minor thing that makes the new test fail on sifaka and
longfin build farm animals. If role names in regression don't start
with "regress_" this invokes a warning. I've consulted in other
modules regression tests e.g. in test_rls_hooks and changed the role
naming accordingly. In essence, a fix is just a batch replace in test
SQL and expected results.I see you already pushed the fix for this in beecbe8e5001. So no
worries, it it not needed anymore.
OK. Thank you for keeping eyes on buildfarm.
------
Regards,
Alexander Korotkov
On Fri, Dec 09, 2022 at 01:23:56PM +0300, Alexander Korotkov wrote:
Pushed, thanks to everyone!
FYI: this causes meson test running ("installcheck") fail when run
twice. I guess that's expected to work, per:
b62303794efd97f2afb55f1e1b82fffae2cf8a2d
f31111bbe81db0e84fb486c6423a234c47091b30
6928484bda454f9ab2456d385b2d317f18b6bf1a
072710dff3eef4540f1c64d07890eb128535e212
b22b770683806db0a1c0a52a4601a3b6755891e0
--
Justin
Justin Pryzby <pryzby@telsasoft.com> writes:
FYI: this causes meson test running ("installcheck") fail when run
twice. I guess that's expected to work, per:
We do indeed expect that to work ... but I don't see any problem
with repeat "make installcheck" on HEAD. Can you provide more
detail about what you're seeing?
regards, tom lane
On Tue, Dec 27, 2022 at 01:58:14AM -0500, Tom Lane wrote:
Justin Pryzby <pryzby@telsasoft.com> writes:
FYI: this causes meson test running ("installcheck") fail when run
twice. I guess that's expected to work, per:We do indeed expect that to work ... but I don't see any problem
with repeat "make installcheck" on HEAD. Can you provide more
detail about what you're seeing?
This fails when run more than once:
time meson test --setup running --print test_pg_db_role_setting-running/regress
@@ -1,12 +1,13 @@
CREATE EXTENSION test_pg_db_role_setting;
CREATE USER regress_super_user SUPERUSER;
+ERROR: role "regress_super_user" already exists
CREATE USER regress_regular_user;
+ERROR: role "regress_regular_user" already exists
...
It didn't fail for you because it says:
./src/test/modules/test_pg_db_role_setting/Makefile
+# disable installcheck for now
+NO_INSTALLCHECK = 1
It also says:
+# and also for now force NO_LOCALE and UTF8
+ENCODING = UTF8
+NO_LOCALE = 1
which was evidently copied from the "oat" tests, which have said that
since March (5b29a9f77, 7c51b7f7c).
It fails the same way with "make" if you change it to not disable
installcheck:
EXTRA_REGRESS_OPTS="--bindir=`pwd`/tmp_install/usr/local/pgsql/bin" PGHOST=/tmp make installcheck -C src/test/modules/test_pg_db_role_setting
--
Justin
Justin Pryzby <pryzby@telsasoft.com> writes:
This fails when run more than once:
time meson test --setup running --print test_pg_db_role_setting-running/regress
Ah.
It didn't fail for you because it says: ./src/test/modules/test_pg_db_role_setting/Makefile +# disable installcheck for now +NO_INSTALLCHECK = 1
So ... exactly why is the meson infrastructure failing to honor that?
This test looks sufficiently careless about its side-effects that
I completely agree with the decision to not run it in pre-existing
installations. Failing to drop a created superuser is just one of
its risk factors --- it also leaves around pg_db_role_setting entries.
regards, tom lane
On Tue, Dec 27, 2022 at 11:29:40PM -0500, Tom Lane wrote:
Justin Pryzby <pryzby@telsasoft.com> writes:
This fails when run more than once:
time meson test --setup running --print test_pg_db_role_setting-running/regressAh.
It didn't fail for you because it says: ./src/test/modules/test_pg_db_role_setting/Makefile +# disable installcheck for now +NO_INSTALLCHECK = 1So ... exactly why is the meson infrastructure failing to honor that?
This test looks sufficiently careless about its side-effects that
I completely agree with the decision to not run it in pre-existing
installations. Failing to drop a created superuser is just one of
its risk factors --- it also leaves around pg_db_role_setting entries.
Meson doesn't try to parse the Makefiles (like the MSVC scripts) but
(since 3f0e786ccb) has its own implementation, which involves setting
'runningcheck': false.
096dd80f3c seems to have copied the NO_INSTALLCHECK from oat's makefile,
but didn't copy "runningcheck" from oat's meson.build (but did copy its
regress_args).
--
Justin
Hi, Justin!
On Wed, 28 Dec 2022 at 21:28, Justin Pryzby <pryzby@telsasoft.com> wrote:
On Tue, Dec 27, 2022 at 11:29:40PM -0500, Tom Lane wrote:
Justin Pryzby <pryzby@telsasoft.com> writes:
This fails when run more than once:
time meson test --setup running --print test_pg_db_role_setting-running/regressAh.
It didn't fail for you because it says: ./src/test/modules/test_pg_db_role_setting/Makefile +# disable installcheck for now +NO_INSTALLCHECK = 1So ... exactly why is the meson infrastructure failing to honor that?
This test looks sufficiently careless about its side-effects that
I completely agree with the decision to not run it in pre-existing
installations. Failing to drop a created superuser is just one of
its risk factors --- it also leaves around pg_db_role_setting entries.Meson doesn't try to parse the Makefiles (like the MSVC scripts) but
(since 3f0e786ccb) has its own implementation, which involves setting
'runningcheck': false.096dd80f3c seems to have copied the NO_INSTALLCHECK from oat's makefile,
but didn't copy "runningcheck" from oat's meson.build (but did copy its
regress_args).
I completely agree with your analysis. Fixes by 3f0e786ccbf5 to oat
and the other modules tests came just a couple of days before
committing the main pg_db_role_setting commit 096dd80f3c so they were
forgotten to be included into it.
I support committing the same fix to pg_db_role_setting test and added
this minor fix as a patch to this thread.
Kind regards, and happy New year!
Pavel Borisov,
Supabase.
Attachments:
0001-meson-Add-running-test-setup-as-a-replacement-for-in.patchapplication/octet-stream; name=0001-meson-Add-running-test-setup-as-a-replacement-for-in.patchDownload
From 8edd6638820a2b407367af57c5db22c379dfa607 Mon Sep 17 00:00:00 2001
From: Pavel Borisov <pashkin.elfe@gmail.com>
Date: Mon, 2 Jan 2023 12:30:16 +0400
Subject: [PATCH] meson: Add 'running' test setup, as a replacement for
installcheck
Does the same as 3f0e786ccbf5 for test_pg_db_role_setting
---
src/test/modules/test_pg_db_role_setting/meson.build | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/test/modules/test_pg_db_role_setting/meson.build b/src/test/modules/test_pg_db_role_setting/meson.build
index 9a2b688049c..48d261ad8b8 100644
--- a/src/test/modules/test_pg_db_role_setting/meson.build
+++ b/src/test/modules/test_pg_db_role_setting/meson.build
@@ -33,5 +33,6 @@ tests += {
'test_pg_db_role_setting',
],
'regress_args': ['--no-locale', '--encoding=UTF8'],
+ 'runningcheck': false,
},
}
--
2.24.3 (Apple Git-128)
Justin, Tom, Pavel, thank you for catching this.
On Mon, Jan 2, 2023 at 11:54 AM Pavel Borisov <pashkin.elfe@gmail.com> wrote:
I completely agree with your analysis. Fixes by 3f0e786ccbf5 to oat
and the other modules tests came just a couple of days before
committing the main pg_db_role_setting commit 096dd80f3c so they were
forgotten to be included into it.I support committing the same fix to pg_db_role_setting test and added
this minor fix as a patch to this thread.
I'm going to push this if no objections.
Kind regards, and happy New year!
Thanks, and happy New Year to you!
------
Regards,
Alexander Korotkov
On Mon, Jan 02, 2023 at 06:14:48PM +0300, Alexander Korotkov wrote:
I'm going to push this if no objections.
I also suggest that meson.build should not copy regress_args.
--
Justin
On Mon, Jan 2, 2023 at 6:42 PM Justin Pryzby <pryzby@telsasoft.com> wrote:
On Mon, Jan 02, 2023 at 06:14:48PM +0300, Alexander Korotkov wrote:
I'm going to push this if no objections.
I also suggest that meson.build should not copy regress_args.
Good point, thanks.
------
Regards,
Alexander Korotkov
Attachments:
0001-meson-Add-running-test-setup-as-a-replacement-for-v2.patchapplication/octet-stream; name=0001-meson-Add-running-test-setup-as-a-replacement-for-v2.patchDownload
From 15cb25844f929b4f8eae073640c65e0377d0f827 Mon Sep 17 00:00:00 2001
From: Pavel Borisov <pashkin.elfe@gmail.com>
Date: Mon, 2 Jan 2023 12:30:16 +0400
Subject: [PATCH] meson: Add 'running' test setup, as a replacement for
installcheck
Do the same as 3f0e786ccbf5 for test_pg_db_role_setting. Also, remove extra
regress_args parameter from meson.build.
---
src/test/modules/test_pg_db_role_setting/meson.build | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/test/modules/test_pg_db_role_setting/meson.build b/src/test/modules/test_pg_db_role_setting/meson.build
index 3a6410cca21..ecafb62ab4f 100644
--- a/src/test/modules/test_pg_db_role_setting/meson.build
+++ b/src/test/modules/test_pg_db_role_setting/meson.build
@@ -30,6 +30,6 @@ tests += {
'sql': [
'test_pg_db_role_setting',
],
- 'regress_args': ['--no-locale', '--encoding=UTF8'],
+ 'runningcheck': false,
},
}
--
2.24.3 (Apple Git-128)
On Tue, 3 Jan 2023 at 09:29, Alexander Korotkov <aekorotkov@gmail.com> wrote:
On Mon, Jan 2, 2023 at 6:42 PM Justin Pryzby <pryzby@telsasoft.com> wrote:
On Mon, Jan 02, 2023 at 06:14:48PM +0300, Alexander Korotkov wrote:
I'm going to push this if no objections.
I also suggest that meson.build should not copy regress_args.
Good point, thanks.
Nice, thanks!
Isn't there the same reason to remove regress_args from meson.build in
oat's test and possibly from other modules with runningcheck=false?
Regards,
Pavel Borisov
On Tue, Jan 3, 2023 at 11:51 AM Pavel Borisov <pashkin.elfe@gmail.com> wrote:
On Tue, 3 Jan 2023 at 09:29, Alexander Korotkov <aekorotkov@gmail.com> wrote:
On Mon, Jan 2, 2023 at 6:42 PM Justin Pryzby <pryzby@telsasoft.com> wrote:
On Mon, Jan 02, 2023 at 06:14:48PM +0300, Alexander Korotkov wrote:
I'm going to push this if no objections.
I also suggest that meson.build should not copy regress_args.
Good point, thanks.
Nice, thanks!
Isn't there the same reason to remove regress_args from meson.build in
oat's test and possibly from other modules with runningcheck=false?
This makes sense to me too. I don't see anything specific in oat's
regression test that requires setting regress_args.
------
Regards,
Alexander Korotkov
Hi, Alexander!
On Tue, 3 Jan 2023 at 13:48, Alexander Korotkov <aekorotkov@gmail.com> wrote:
On Tue, Jan 3, 2023 at 11:51 AM Pavel Borisov <pashkin.elfe@gmail.com> wrote:
On Tue, 3 Jan 2023 at 09:29, Alexander Korotkov <aekorotkov@gmail.com> wrote:
On Mon, Jan 2, 2023 at 6:42 PM Justin Pryzby <pryzby@telsasoft.com> wrote:
On Mon, Jan 02, 2023 at 06:14:48PM +0300, Alexander Korotkov wrote:
I'm going to push this if no objections.
I also suggest that meson.build should not copy regress_args.
Good point, thanks.
Nice, thanks!
Isn't there the same reason to remove regress_args from meson.build in
oat's test and possibly from other modules with runningcheck=false?This makes sense to me too. I don't see anything specific in oat's
regression test that requires setting regress_args.
Yes, it seems so.
Regress args in oat's Makefile are added as a response to a buildfarm
issues by 7c51b7f7cc08. They seem unneeded to be copied into
meson.build with runningcheck=false. I may mistake but it seems to me
that removing regress_args from meson.build with runningcheck=false is
just to make it neat, not for functionality. So I consider fixing it
in pg_db_role_setting, oat, or both of them optional.
Regards,
Pavel Borisov.
On Tue, Jan 03, 2023 at 02:20:38PM +0300, Pavel Borisov wrote:
Hi, Alexander!
On Tue, 3 Jan 2023 at 13:48, Alexander Korotkov <aekorotkov@gmail.com> wrote:
On Tue, Jan 3, 2023 at 11:51 AM Pavel Borisov <pashkin.elfe@gmail.com> wrote:
On Tue, 3 Jan 2023 at 09:29, Alexander Korotkov <aekorotkov@gmail.com> wrote:
On Mon, Jan 2, 2023 at 6:42 PM Justin Pryzby <pryzby@telsasoft.com> wrote:
On Mon, Jan 02, 2023 at 06:14:48PM +0300, Alexander Korotkov wrote:
I'm going to push this if no objections.
I also suggest that meson.build should not copy regress_args.
Good point, thanks.
Nice, thanks!
Isn't there the same reason to remove regress_args from meson.build in
oat's test and possibly from other modules with runningcheck=false?This makes sense to me too. I don't see anything specific in oat's
regression test that requires setting regress_args.Yes, it seems so.
Regress args in oat's Makefile are added as a response to a buildfarm
issues by 7c51b7f7cc08. They seem unneeded to be copied into
meson.build with runningcheck=false. I may mistake but it seems to me
that removing regress_args from meson.build with runningcheck=false is
just to make it neat, not for functionality. So I consider fixing it
in pg_db_role_setting, oat, or both of them optional.
Right - my suggestion is to "uncopy" them from pg_db_role_setting, where
they serve no purpose, since they shouldn't have been copied originally.
On Tue, Jan 03, 2023 at 09:29:00AM +0300, Alexander Korotkov wrote:
On Mon, Jan 2, 2023 at 6:42 PM Justin Pryzby <pryzby@telsasoft.com> wrote:
On Mon, Jan 02, 2023 at 06:14:48PM +0300, Alexander Korotkov wrote:
I'm going to push this if no objections.
I also suggest that meson.build should not copy regress_args.
Good point, thanks.
I should've mentioned that the same things should be removed from
Makefile, too...
--
Justin
On Tue, 3 Jan 2023 at 17:28, Justin Pryzby <pryzby@telsasoft.com> wrote:
On Tue, Jan 03, 2023 at 02:20:38PM +0300, Pavel Borisov wrote:
Hi, Alexander!
On Tue, 3 Jan 2023 at 13:48, Alexander Korotkov <aekorotkov@gmail.com> wrote:
On Tue, Jan 3, 2023 at 11:51 AM Pavel Borisov <pashkin.elfe@gmail.com> wrote:
On Tue, 3 Jan 2023 at 09:29, Alexander Korotkov <aekorotkov@gmail.com> wrote:
On Mon, Jan 2, 2023 at 6:42 PM Justin Pryzby <pryzby@telsasoft.com> wrote:
On Mon, Jan 02, 2023 at 06:14:48PM +0300, Alexander Korotkov wrote:
I'm going to push this if no objections.
I also suggest that meson.build should not copy regress_args.
Good point, thanks.
Nice, thanks!
Isn't there the same reason to remove regress_args from meson.build in
oat's test and possibly from other modules with runningcheck=false?This makes sense to me too. I don't see anything specific in oat's
regression test that requires setting regress_args.Yes, it seems so.
Regress args in oat's Makefile are added as a response to a buildfarm
issues by 7c51b7f7cc08. They seem unneeded to be copied into
meson.build with runningcheck=false. I may mistake but it seems to me
that removing regress_args from meson.build with runningcheck=false is
just to make it neat, not for functionality. So I consider fixing it
in pg_db_role_setting, oat, or both of them optional.Right - my suggestion is to "uncopy" them from pg_db_role_setting, where
they serve no purpose, since they shouldn't have been copied originally.On Tue, Jan 03, 2023 at 09:29:00AM +0300, Alexander Korotkov wrote:
On Mon, Jan 2, 2023 at 6:42 PM Justin Pryzby <pryzby@telsasoft.com> wrote:
On Mon, Jan 02, 2023 at 06:14:48PM +0300, Alexander Korotkov wrote:
I'm going to push this if no objections.
I also suggest that meson.build should not copy regress_args.
Good point, thanks.
I should've mentioned that the same things should be removed from
Makefile, too...--
Thanks, Justin!
Attached is a new patch accordingly.
Regards,
Pavel Borisov!
Attachments:
v3-0001-meson-Add-running-test-setup-as-a-replacement-for.patchapplication/octet-stream; name=v3-0001-meson-Add-running-test-setup-as-a-replacement-for.patchDownload
From 9448c0dec9367282e452614308cb405e2d1dee10 Mon Sep 17 00:00:00 2001
From: Pavel Borisov <pashkin.elfe@gmail.com>
Date: Mon, 2 Jan 2023 12:30:16 +0400
Subject: [PATCH v3] meson: Add 'running' test setup, as a replacement for
installcheck
Does the same as 3f0e786ccbf5 for test_pg_db_role_setting
Also remove unneeded regress_args.
---
src/test/modules/test_pg_db_role_setting/Makefile | 3 ---
src/test/modules/test_pg_db_role_setting/meson.build | 2 +-
2 files changed, 1 insertion(+), 4 deletions(-)
diff --git a/src/test/modules/test_pg_db_role_setting/Makefile b/src/test/modules/test_pg_db_role_setting/Makefile
index aacd78f74c5..50ac02db449 100644
--- a/src/test/modules/test_pg_db_role_setting/Makefile
+++ b/src/test/modules/test_pg_db_role_setting/Makefile
@@ -13,9 +13,6 @@ 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
diff --git a/src/test/modules/test_pg_db_role_setting/meson.build b/src/test/modules/test_pg_db_role_setting/meson.build
index 9a2b688049c..cc949c7d747 100644
--- a/src/test/modules/test_pg_db_role_setting/meson.build
+++ b/src/test/modules/test_pg_db_role_setting/meson.build
@@ -32,6 +32,6 @@ tests += {
'sql': [
'test_pg_db_role_setting',
],
- 'regress_args': ['--no-locale', '--encoding=UTF8'],
+ 'runningcheck': false,
},
}
--
2.24.3 (Apple Git-128)
On Tue, Jan 3, 2023 at 5:28 PM Justin Pryzby <pryzby@telsasoft.com> wrote:
On Tue, Jan 03, 2023 at 09:29:00AM +0300, Alexander Korotkov wrote:
On Mon, Jan 2, 2023 at 6:42 PM Justin Pryzby <pryzby@telsasoft.com> wrote:
I also suggest that meson.build should not copy regress_args.
Good point, thanks.
I should've mentioned that the same things should be removed from
Makefile, too...
This makes sense too. See the attached patchset.
------
Regards,
Alexander Korotkov
Attachments:
0001-meson-Add-running-test-setup-as-a-replacement-for-v3.patchapplication/octet-stream; name=0001-meson-Add-running-test-setup-as-a-replacement-for-v3.patchDownload
From f51f56605294a0b8f23624d6a1901fcb6a8321e0 Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <akorotkov@postgresql.org>
Date: Tue, 3 Jan 2023 17:18:36 +0300
Subject: [PATCH 1/2] meson: Add 'running' test setup, as a replacement for
installcheck
Do the same as 3f0e786ccbf5 for test_pg_db_role_setting.
Discussion: https://postgr.es/m/20221227065456.GU1153@telsasoft.com
Author: Pavel Borisov
Reviewed-by: Justin Pryzby, Tom Lane
---
src/test/modules/test_pg_db_role_setting/meson.build | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/test/modules/test_pg_db_role_setting/meson.build b/src/test/modules/test_pg_db_role_setting/meson.build
index 6db1333f2ef..9ad525aeb86 100644
--- a/src/test/modules/test_pg_db_role_setting/meson.build
+++ b/src/test/modules/test_pg_db_role_setting/meson.build
@@ -33,5 +33,6 @@ tests += {
'test_pg_db_role_setting',
],
'regress_args': ['--no-locale', '--encoding=UTF8'],
+ 'runningcheck': false,
},
}
--
2.24.3 (Apple Git-128)
0002-Remove-extra-regress-check-arguments-from-test_pg-v3.patchapplication/octet-stream; name=0002-Remove-extra-regress-check-arguments-from-test_pg-v3.patchDownload
From 6ce32991067c0a52645187dc6aa70a4693702dc5 Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <akorotkov@postgresql.org>
Date: Tue, 3 Jan 2023 17:33:22 +0300
Subject: [PATCH 2/2] Remove extra regress check arguments from
test_pg_db_role_setting
They were accidentally copied from test_oat_hooks.
Reported-by: Justin Pryzby
Discussion: https://postgr.es/m/20230102154240.GL1153%40telsasoft.com
Reviewed-by: Pavel Borisov
---
src/test/modules/test_pg_db_role_setting/Makefile | 3 ---
src/test/modules/test_pg_db_role_setting/meson.build | 1 -
2 files changed, 4 deletions(-)
diff --git a/src/test/modules/test_pg_db_role_setting/Makefile b/src/test/modules/test_pg_db_role_setting/Makefile
index aacd78f74c5..50ac02db449 100644
--- a/src/test/modules/test_pg_db_role_setting/Makefile
+++ b/src/test/modules/test_pg_db_role_setting/Makefile
@@ -13,9 +13,6 @@ 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
diff --git a/src/test/modules/test_pg_db_role_setting/meson.build b/src/test/modules/test_pg_db_role_setting/meson.build
index 9ad525aeb86..fa0e691d796 100644
--- a/src/test/modules/test_pg_db_role_setting/meson.build
+++ b/src/test/modules/test_pg_db_role_setting/meson.build
@@ -32,7 +32,6 @@ tests += {
'sql': [
'test_pg_db_role_setting',
],
- 'regress_args': ['--no-locale', '--encoding=UTF8'],
'runningcheck': false,
},
}
--
2.24.3 (Apple Git-128)
On Tue, Jan 3, 2023 at 5:41 PM Pavel Borisov <pashkin.elfe@gmail.com> wrote:
On Tue, 3 Jan 2023 at 17:28, Justin Pryzby <pryzby@telsasoft.com> wrote:
I should've mentioned that the same things should be removed from
Makefile, too...Thanks, Justin!
Attached is a new patch accordingly.
Thank you. I've pushed my version, which is split into two commits.
------
Regards,
Alexander Korotkov