Subject: Re: Granting SET and ALTER SYSTE privileges for GUCs

From: Mark Dilger <[email-masked]>
Date: Mon, 31 Jan 2022 11:17:12 -0800

> On Jan 26, 2022, at 7:24 AM, Mark Dilger <[email-masked]> wrote:
> 
> Rebased:
> 
> <v7-0001-Allow-GRANT-of-SET-and-ALTER-SYSTEM-SET-for-gucs.patch>

Hackers,

Joshua and I had some off-list back-and-forth on this patch and I think the attached v8 patch meets his needs.  Specifically, v8 adds support for object access hooks in the case of SET and ALTER SYSTEM SET of a guc variable.

The way InvokeObjectPostAlterHookArg() is written, it expects some things like a Oid that we don't always have.  I abused the parameters of that function to pass as much information as possible.  Anyone wanting to review this might consider how that is coded.

Joshua, this is byte-for-byte the same patch that you already reviewed off-list (or I attached the wrong patch!)  If you are satisfied, could you set the status to ready-for-committer?

—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

---

From: Tom Lane <[email-masked]>
Date: Sun, 06 Mar 2022 17:13:44 -0500

Mark Dilger <[email-masked]> writes:
> [ v8-0001-Allow-GRANT-of-SET-and-ALTER-SYSTEM-SET-for-gucs.patch ]

I noticed that this is failing in the cfbot as a side-effect of my
cc50080a8, so here's a rebase (basically it just removes the no-
longer-necessary sanity_check.out hunk).

I haven't reviewed this particularly, or read the thread, but I'm
pretty disturbed by the contrib/ changes that it proposes.

1. If we need to change these two contrib modules, doesn't that imply
a lot of changes forced on external modules as well?  What are the
security implications if somebody doesn't make such a change?

2. It looks to me like if someone installs the updated postgres_fdw.so,
but doesn't run ALTER EXTENSION UPDATE, they are going to be left with a
rather broken extension.  This seems not good either, especially if it's
multiplied by a boatload of third-party extensions requiring updates.

So I think some more thought is needed to see if we can't avoid
the need to touch extension modules in this patch.  Or at least
avoid the need for synchronized C-level and SQL-level updates,
because that is going to create a lot of pain for end users.

I'm also fairly distressed by the number of changes in guc.c,
mainly because I fear that that means that pending patches that
add GUC variables will be subtly incorrect/insecure if they're not
updated to account for this.  Frankly, I also reject the apparent
position that we need to support forbidding users from touching
many of these GUCs.  Or, if that's our position, why are there
per-GUC changes at all, rather than just redefining what the
context values mean?  (That is, why not redefine USERSET and
SUSET as simply indicating the default ACL to be applied if there's
no entry in the catalog.)

			regards, tom lane

diff --git a/contrib/pg_trgm/Makefile b/contrib/pg_trgm/Makefile
index 1fbdc9ec1e..8fac4f6289 100644
--- a/contrib/pg_trgm/Makefile
+++ b/contrib/pg_trgm/Makefile
@@ -9,9 +9,9 @@ OBJS = \
 	trgm_regexp.o
 
 EXTENSION = pg_trgm
-DATA = pg_trgm--1.5--1.6.sql pg_trgm--1.4--1.5.sql pg_trgm--1.3--1.4.sql \
-	pg_trgm--1.3.sql pg_trgm--1.2--1.3.sql pg_trgm--1.1--1.2.sql \
-	pg_trgm--1.0--1.1.sql
+DATA = pg_trgm--1.6--1.7.sql pg_trgm--1.5--1.6.sql pg_trgm--1.4--1.5.sql \
+	pg_trgm--1.3--1.4.sql pg_trgm--1.3.sql pg_trgm--1.2--1.3.sql \
+	pg_trgm--1.1--1.2.sql pg_trgm--1.0--1.1.sql
 PGFILEDESC = "pg_trgm - trigram matching"
 
 REGRESS = pg_trgm pg_word_trgm pg_strict_word_trgm
diff --git a/contrib/pg_trgm/pg_trgm--1.6--1.7.sql b/contrib/pg_trgm/pg_trgm--1.6--1.7.sql
new file mode 100644
index 0000000000..06b783acbd
--- /dev/null
+++ b/contrib/pg_trgm/pg_trgm--1.6--1.7.sql
@@ -0,0 +1,8 @@
+/* contrib/pg_trgm/pg_trgm--1.6--1.7.sql */
+
+-- complain if script is sourced in psql, rather than via ALTER EXTENSION
+\echo Use "ALTER EXTENSION pg_trgm UPDATE TO '1.7'" to load this file. \quit
+
+GRANT SET VALUE ON "pg_trgm.similarity_threshold" TO public;
+GRANT SET VALUE ON "pg_trgm.word_similarity_threshold" TO public;
+GRANT SET VALUE ON "pg_trgm.strict_word_similarity_threshold" TO public;
diff --git a/contrib/pg_trgm/pg_trgm.control b/contrib/pg_trgm/pg_trgm.control
index 1d6a9ddf25..6e3ee43c51 100644
--- a/contrib/pg_trgm/pg_trgm.control
+++ b/contrib/pg_trgm/pg_trgm.control
@@ -1,6 +1,6 @@
 # pg_trgm extension
 comment = 'text similarity measurement and index searching based on trigrams'
-default_version = '1.6'
+default_version = '1.7'
 module_pathname = '$libdir/pg_trgm'
 relocatable = true
 trusted = true
diff --git a/contrib/postgres_fdw/Makefile b/contrib/postgres_fdw/Makefile
index c1b0cad453..3564015ee8 100644
--- a/contrib/postgres_fdw/Makefile
+++ b/contrib/postgres_fdw/Makefile
@@ -14,7 +14,8 @@ PG_CPPFLAGS = -I$(libpq_srcdir)
 SHLIB_LINK_INTERNAL = $(libpq)
 
 EXTENSION = postgres_fdw
-DATA = postgres_fdw--1.0.sql postgres_fdw--1.0--1.1.sql
+DATA = postgres_fdw--1.0.sql postgres_fdw--1.0--1.1.sql \
+	postgres_fdw--1.1--1.2.sql
 
 REGRESS = postgres_fdw
 
diff --git a/contrib/postgres_fdw/option.c b/contrib/postgres_fdw/option.c
index 572591a558..df58b5f339 100644
--- a/contrib/postgres_fdw/option.c
+++ b/contrib/postgres_fdw/option.c
@@ -534,7 +534,7 @@ _PG_init(void)
 							   NULL,
 							   &pgfdw_application_name,
 							   NULL,
-							   PGC_USERSET,
+							   PGC_SUSET,
 							   0,
 							   NULL,
 							   NULL,
diff --git a/contrib/postgres_fdw/postgres_fdw--1.1--1.2.sql b/contrib/postgres_fdw/postgres_fdw--1.1--1.2.sql
new file mode 100644
index 0000000000..b174e04283
--- /dev/null
+++ b/contrib/postgres_fdw/postgres_fdw--1.1--1.2.sql
@@ -0,0 +1,6 @@
+/* contrib/postgres_fdw/postgres_fdw--1.1--1.2.sql */
+
+-- complain if script is sourced in psql, rather than via ALTER EXTENSION
+\echo Use "ALTER EXTENSION postgres_fdw UPDATE TO '1.2'" to load this file. \quit
+
+GRANT SET VALUE ON "postgres_fdw.application_name" TO public;
diff --git a/contrib/postgres_fdw/postgres_fdw.control b/contrib/postgres_fdw/postgres_fdw.control
index d489382064..a4b800be4f 100644
--- a/contrib/postgres_fdw/postgres_fdw.control
+++ b/contrib/postgres_fdw/postgres_fdw.control
@@ -1,5 +1,5 @@
 # postgres_fdw extension
 comment = 'foreign-data wrapper for remote PostgreSQL servers'
-default_version = '1.1'
+default_version = '1.2'
 module_pathname = '$libdir/postgres_fdw'
 relocatable = true
diff --git a/contrib/sepgsql/hooks.c b/contrib/sepgsql/hooks.c
index 97e61b8043..7f899fa8bf 100644
--- a/contrib/sepgsql/hooks.c
+++ b/contrib/sepgsql/hooks.c
@@ -449,7 +449,7 @@ _PG_init(void)
 							 NULL,
 							 &sepgsql_debug_audit,
 							 false,
-							 PGC_USERSET,
+							 PGC_SUSET,
 							 GUC_NOT_IN_SAMPLE,
 							 NULL,
 							 NULL,
diff --git a/contrib/sepgsql/sepgsql.sql.in b/contrib/sepgsql/sepgsql.sql.in
index 917d12dbbe..4dce769306 100644
--- a/contrib/sepgsql/sepgsql.sql.in
+++ b/contrib/sepgsql/sepgsql.sql.in
@@ -34,4 +34,5 @@ CREATE OR REPLACE FUNCTION pg_catalog.sepgsql_setcon(text) RETURNS bool AS 'MODU
 CREATE OR REPLACE FUNCTION pg_catalog.sepgsql_mcstrans_in(text) RETURNS text AS 'MODULE_PATHNAME', 'sepgsql_mcstrans_in' LANGUAGE C STRICT;
 CREATE OR REPLACE FUNCTION pg_catalog.sepgsql_mcstrans_out(text) RETURNS text AS 'MODULE_PATHNAME', 'sepgsql_mcstrans_out' LANGUAGE C STRICT;
 CREATE OR REPLACE FUNCTION pg_catalog.sepgsql_restorecon(text) RETURNS bool AS 'MODULE_PATHNAME', 'sepgsql_restorecon' LANGUAGE C;
+GRANT SET VALUE ON "sepgsql.debug_audit" TO public;
 SELECT sepgsql_restorecon(NULL);
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 83987a9904..7b288cce30 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -105,6 +105,11 @@
       <entry>collations (locale information)</entry>
      </row>
 
+     <row>
+      <entry><link linkend="catalog-pg-setting-acl"><structname>pg_setting_acl</structname></link></entry>
+      <entry>configuration parameters which have privileges granted to roles</entry>
+     </row>
+
      <row>
       <entry><link linkend="catalog-pg-constraint"><structname>pg_constraint</structname></link></entry>
       <entry>check constraints, unique constraints, primary key constraints, foreign key constraints</entry>
@@ -2423,6 +2428,64 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
   </para>
  </sect1>
 
+ <sect1 id="catalog-pg-setting-acl">
+  <title><structname>pg_setting_acl</structname></title>
+
+  <indexterm zone="catalog-pg-setting-acl">
+   <primary>pg_setting_acl</primary>
+  </indexterm>
+
+  <para>
+   The catalog <structname>pg_setting_acl</structname> records configuration
+   parameters which have had privileges to <literal>SET</literal> or
+   <literal>ALTER SYSTEM</literal> granted to one or more roles.
+  </para>
+
+  <para>
+   Unlike most system catalogs, <structname>pg_setting_acl</structname>
+   is shared across all databases of a cluster: there is only one
+   copy of <structname>pg_setting_acl</structname> per cluster, not
+   one per database.
+  </para>
+
+  <table>
+   <title><structname>pg_setting_acl</structname> Columns</title>
+   <tgroup cols="1">
+    <thead>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       Column Type
+      </para>
+      <para>
+       Description
+      </para></entry>
+     </row>
+    </thead>
+
+    <tbody>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>setting</structfield> <type>text</type>
+      </para>
+      <para>
+       The name of the configuration parameter for which privileges are granted.
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>setacl</structfield> <type>aclitem[]</type>
+      </para>
+      <para>
+       Access privileges; see <xref linkend="ddl-priv"/> for details
+      </para></entry>
+     </row>
+
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
+
  <sect1 id="catalog-pg-constraint">
   <title><structname>pg_constraint</structname></title>
 
@@ -12540,11 +12603,13 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
     <term><literal>superuser</literal></term>
     <listitem>
      <para>
-      These settings can be set from <filename>postgresql.conf</filename>,
-      or within a session via the <command>SET</command> command; but only superusers
-      can change them via <command>SET</command>.  Changes in
-      <filename>postgresql.conf</filename> will affect existing sessions
-      only if no session-local value has been established with <command>SET</command>.
+      These settings can be set from <filename>postgresql.conf</filename>, or
+      within a session via the <command>SET</command> command; but only
+      superusers or users with <literal>SET VALUE</literal> privilege granted
+      on the setting can change them via <command>SET</command>.  Changes in
+      <filename>postgresql.conf</filename> will affect existing sessions only
+      if no session-local value has been established with
+      <command>SET</command>.
      </para>
     </listitem>
    </varlistentry>
diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index 166b7a352d..78d97a9689 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -1691,7 +1691,8 @@ ALTER TABLE products RENAME TO items;
    <literal>INSERT</literal>, <literal>UPDATE</literal>, <literal>DELETE</literal>,
    <literal>TRUNCATE</literal>, <literal>REFERENCES</literal>, <literal>TRIGGER</literal>,
    <literal>CREATE</literal>, <literal>CONNECT</literal>, <literal>TEMPORARY</literal>,
-   <literal>EXECUTE</literal>, and <literal>USAGE</literal>.
+   <literal>EXECUTE</literal>, <literal>USAGE</literal>, <literal>SET VALUE</literal>
+   and <literal>ALTER SYSTEM</literal>.
    The privileges applicable to a particular
    object vary depending on the object's type (table, function, etc).
    More detail about the meanings of these privileges appears below.
@@ -1959,6 +1960,28 @@ REVOKE ALL ON accounts FROM PUBLIC;
       </para>
      </listitem>
     </varlistentry>
+
+    <varlistentry>
+     <term><literal>SET VALUE</literal></term>
+     <listitem>
+      <para>
+       Allows non-superuser roles to use the <command>SET</command> command to
+       change <literal>superuser</literal> run-time configuration parameters.
+       (Any user can set <literal>user</literal> run-time configuration
+       parameters.)
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><literal>ALTER SYSTEM</literal></term>
+     <listitem>
+      <para>
+       Allows non-superuser roles to use the <command>ALTER SYSTEM
+       SET</command> command to change server configuration parameters.
+      </para>
+     </listitem>
+    </varlistentry>
    </variablelist>
 
    The privileges required by other commands are listed on the
@@ -2097,6 +2120,16 @@ REVOKE ALL ON accounts FROM PUBLIC;
        <literal>TYPE</literal>
       </entry>
      </row>
+     <row>
+      <entry><literal>SET VALUE</literal></entry>
+      <entry><literal>s</literal></entry>
+      <entry>configuration parameter</entry>
+     </row>
+     <row>
+      <entry><literal>ALTER SYSTEM</literal></entry>
+      <entry><literal>A</literal></entry>
+      <entry>configuration parameter</entry>
+     </row>
      </tbody>
    </tgroup>
   </table>
@@ -2203,6 +2236,12 @@ REVOKE ALL ON accounts FROM PUBLIC;
       <entry><literal>U</literal></entry>
       <entry><literal>\dT+</literal></entry>
      </row>
+     <row>
+      <entry><literal>Configuration parameter</literal></entry>
+      <entry><literal>sA</literal></entry>
+      <entry>none</entry>
+      <entry><literal></literal></entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 8a802fb225..36b33d8639 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -22691,8 +22691,7 @@ SELECT * FROM pg_ls_dir('.') WITH ORDINALITY AS t(ls,n);
    privilege is held with grant option. Also, multiple privilege types can be
    listed separated by commas, in which case the result will be true if any of
    the listed privileges is held. (Case of the privilege string is not
-   significant, and extra whitespace is allowed between but not within
-   privilege names.)
+   significant, and extra whitespace is allowed between privilege names.)
    Some examples:
 <programlisting>
 SELECT has_table_privilege('myschema.mytable', 'select');
@@ -22957,6 +22956,23 @@ SELECT has_function_privilege('joeuser', 'myfunc(int, text)', 'execute');
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>has_setting_privilege</primary>
+        </indexterm>
+        <function>has_setting_privilege</function> (
+          <optional> <parameter>user</parameter> <type>name</type> or <type>oid</type>, </optional>
+          <parameter>setting</parameter> <type>text</type> or <type>oid</type>,
+          <parameter>privilege</parameter> <type>text</type> )
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does user have privilege for setting?
+        Allowable privilege types are <literal>SET VALUE</literal> and <literal>ALTER SYSTEM</literal>.
+       </para></entry>
+      </row>
+
       <row>
        <entry role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/doc/src/sgml/ref/grant.sgml b/doc/src/sgml/ref/grant.sgml
index a897712de2..21ec320a8d 100644
--- a/doc/src/sgml/ref/grant.sgml
+++ b/doc/src/sgml/ref/grant.sgml
@@ -92,6 +92,11 @@ GRANT { USAGE | ALL [ PRIVILEGES ] }
     TO <replaceable class="parameter">role_specification</replaceable> [, ...] [ WITH GRANT OPTION ]
     [ GRANTED BY <replaceable class="parameter">role_specification</replaceable> ]
 
+GRANT { SET VALUE | ALTER SYSTEM }
+    ON <replaceable class="parameter">configuration_parameter</replaceable> [, ...]
+    TO <replaceable class="parameter">role_specification</replaceable> [, ...] [ WITH GRANT OPTION ]
+    [ GRANTED BY <replaceable class="parameter">role_specification</replaceable> ]
+
 GRANT <replaceable class="parameter">role_name</replaceable> [, ...] TO <replaceable class="parameter">role_specification</replaceable> [, ...]
     [ WITH ADMIN OPTION ]
     [ GRANTED BY <replaceable class="parameter">role_specification</replaceable> ]
@@ -185,6 +190,8 @@ GRANT <replaceable class="parameter">role_name</replaceable> [, ...] TO <replace
      <term><literal>TEMPORARY</literal></term>
      <term><literal>EXECUTE</literal></term>
      <term><literal>USAGE</literal></term>
+     <term><literal>SET VALUE</literal></term>
+     <term><literal>ALTER SYSTEM</literal></term>
      <listitem>
       <para>
        Specific types of privileges, as defined in <xref linkend="ddl-priv"/>.
diff --git a/doc/src/sgml/ref/set.sgml b/doc/src/sgml/ref/set.sgml
index 339ee9eec9..a08057d1d1 100644
--- a/doc/src/sgml/ref/set.sgml
+++ b/doc/src/sgml/ref/set.sgml
@@ -34,8 +34,9 @@ SET [ SESSION | LOCAL ] TIME ZONE { <replaceable class="parameter">timezone</rep
    parameters.  Many of the run-time parameters listed in
    <xref linkend="runtime-config"/> can be changed on-the-fly with
    <command>SET</command>.
-   (But some require superuser privileges to change, and others cannot
-   be changed after server or session start.)
+   (But some require either superuser privileges or granted <literal>SET
+   VALUE</literal> privileges to change, and others cannot be changed after
+   server or session start.)
    <command>SET</command> only affects the value used by the current
    session.
   </para>
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index eefebb7bb8..fefbfc343d 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -28,6 +28,7 @@ OBJS = \
 	pg_cast.o \
 	pg_class.o \
 	pg_collation.o \
+	pg_setting_acl.o \
 	pg_constraint.o \
 	pg_conversion.o \
 	pg_db_role_setting.o \
@@ -54,7 +55,7 @@ include $(top_srcdir)/src/backend/common.mk
 # there are reputedly other, undocumented ordering dependencies.
 CATALOG_HEADERS := \
 	pg_proc.h pg_type.h pg_attribute.h pg_class.h \
-	pg_attrdef.h pg_constraint.h pg_inherits.h pg_index.h pg_operator.h \
+	pg_attrdef.h pg_setting_acl.h pg_constraint.h pg_inherits.h pg_index.h pg_operator.h \
 	pg_opfamily.h pg_opclass.h pg_am.h pg_amop.h pg_amproc.h \
 	pg_language.h pg_largeobject_metadata.h pg_largeobject.h pg_aggregate.h \
 	pg_statistic.h pg_statistic_ext.h pg_statistic_ext_data.h \
@@ -126,6 +127,7 @@ install-data: bki-stamp installdirs
 	$(INSTALL_DATA) $(srcdir)/system_functions.sql '$(DESTDIR)$(datadir)/system_functions.sql'
 	$(INSTALL_DATA) $(srcdir)/system_views.sql '$(DESTDIR)$(datadir)/system_views.sql'
 	$(INSTALL_DATA) $(srcdir)/information_schema.sql '$(DESTDIR)$(datadir)/information_schema.sql'
+	$(INSTALL_DATA) $(srcdir)/setting_privileges.sql '$(DESTDIR)$(datadir)/setting_privileges.sql'
 	$(INSTALL_DATA) $(srcdir)/sql_features.txt '$(DESTDIR)$(datadir)/sql_features.txt'
 
 installdirs:
@@ -133,7 +135,7 @@ installdirs:
 
 .PHONY: uninstall-data
 uninstall-data:
-	rm -f $(addprefix '$(DESTDIR)$(datadir)'/, postgres.bki system_constraints.sql system_functions.sql system_views.sql information_schema.sql sql_features.txt)
+	rm -f $(addprefix '$(DESTDIR)$(datadir)'/, postgres.bki system_constraints.sql system_functions.sql system_views.sql information_schema.sql setting_privileges.sql sql_features.txt)
 
 # postgres.bki, system_constraints.sql, and the generated headers are
 # in the distribution tarball, so they are not cleaned here.
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 1dd03a8e51..3f8699b867 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -49,6 +49,7 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_opfamily.h"
 #include "catalog/pg_proc.h"
+#include "catalog/pg_setting_acl.h"
 #include "catalog/pg_statistic_ext.h"
 #include "catalog/pg_subscription.h"
 #include "catalog/pg_tablespace.h"
@@ -112,6 +113,7 @@ static void ExecGrant_Largeobject(InternalGrant *grantStmt);
 static void ExecGrant_Namespace(InternalGrant *grantStmt);
 static void ExecGrant_Tablespace(InternalGrant *grantStmt);
 static void ExecGrant_Type(InternalGrant *grantStmt);
+static void ExecGrant_Setting(InternalGrant *grantStmt);
 
 static void SetDefaultACLsInSchemas(InternalDefaultACL *iacls, List *nspnames);
 static void SetDefaultACL(InternalDefaultACL *iacls);
@@ -259,6 +261,9 @@ restrict_and_check_grant(bool is_grant, AclMode avail_goptions, bool all_privs,
 		case OBJECT_TYPE:
 			whole_mask = ACL_ALL_RIGHTS_TYPE;
 			break;
+		case OBJECT_SETTING:
+			whole_mask = ACL_ALL_RIGHTS_SETTING;
+			break;
 		default:
 			elog(ERROR, "unrecognized object type: %d", objtype);
 			/* not reached, but keep compiler quiet */
@@ -498,6 +503,10 @@ ExecuteGrantStmt(GrantStmt *stmt)
 			all_privileges = ACL_ALL_RIGHTS_FOREIGN_SERVER;
 			errormsg = gettext_noop("invalid privilege type %s for foreign server");
 			break;
+		case OBJECT_SETTING:
+			all_privileges = ACL_ALL_RIGHTS_SETTING;
+			errormsg = gettext_noop("invalid privilege type %s for setting");
+			break;
 		default:
 			elog(ERROR, "unrecognized GrantStmt.objtype: %d",
 				 (int) stmt->objtype);
@@ -600,6 +609,9 @@ ExecGrantStmt_oids(InternalGrant *istmt)
 		case OBJECT_TABLESPACE:
 			ExecGrant_Tablespace(istmt);
 			break;
+		case OBJECT_SETTING:
+			ExecGrant_Setting(istmt);
+			break;
 		default:
 			elog(ERROR, "unrecognized GrantStmt.objtype: %d",
 				 (int) istmt->objtype);
@@ -759,6 +771,38 @@ objectNamesToOids(ObjectType objtype, List *objnames)
 				objects = lappend_oid(objects, srvid);
 			}
 			break;
+		case OBJECT_SETTING:
+			foreach(cell, objnames)
+			{
+				char	   *setting = strVal(lfirst(cell));
+				Oid			settingid = get_setting_oid(setting, true);
+
+				if (!OidIsValid(settingid))
+				{
+					/*
+					 * Lookup the existing entry, or if necessary, add a new
+					 * entry for this parameter.  Entries only exist for
+					 * parameters which currently have, or previously have had,
+					 * privileges assigned.
+					 *
+					 * It is tempting to sanity-check the given configuration
+					 * parameter name against known guc names in the guc
+					 * tables, but for handling upgrades we need to accept
+					 * setting names that do not yet exist.
+					 */
+					settingid = SettingAclCreate(setting, true);
+
+					/*
+					 * Prevent error when processing duplicate objects, and
+					 * make this new entry visible to our later selves which
+					 * will need to update the Acl.
+					 */
+					CommandCounterIncrement();
+				}
+
+				objects = lappend_oid(objects, settingid);
+			}
+			break;
 		default:
 			elog(ERROR, "unrecognized GrantStmt.objtype: %d",
 				 (int) objtype);
@@ -1494,6 +1538,9 @@ RemoveRoleFromObjectACL(Oid roleid, Oid classid, Oid objid)
 			case ForeignDataWrapperRelationId:
 				istmt.objtype = OBJECT_FDW;
 				break;
+			case SettingAclRelationId:
+				istmt.objtype = OBJECT_SETTING;
+				break;
 			default:
 				elog(ERROR, "unexpected object class %u", classid);
 				break;
@@ -3225,6 +3272,131 @@ ExecGrant_Type(InternalGrant *istmt)
 	table_close(relation, RowExclusiveLock);
 }
 
+static void
+ExecGrant_Setting(InternalGrant *istmt)
+{
+	Relation	relation;
+	ListCell   *cell;
+
+	if (istmt->all_privs && istmt->privileges == ACL_NO_RIGHTS)
+		istmt->privileges = ACL_ALL_RIGHTS_SETTING;
+
+	relation = table_open(SettingAclRelationId, RowExclusiveLock);
+
+	foreach(cell, istmt->objects)
+	{
+		Oid			settingId = lfirst_oid(cell);
+		Form_pg_setting_acl pg_setting_acl_tuple;
+		Datum		aclDatum;
+		bool		isNull;
+		AclMode		avail_goptions;
+		AclMode		this_privileges;
+		Acl		   *old_acl;
+		Acl		   *new_acl;
+		Oid			grantorId;
+		HeapTuple	tuple;
+		HeapTuple	newtuple;
+		Datum		values[Natts_pg_setting_acl];
+		bool		nulls[Natts_pg_setting_acl];
+		bool		replaces[Natts_pg_setting_acl];
+		int			noldmembers;
+		int			nnewmembers;
+		Oid		   *oldmembers;
+		Oid		   *newmembers;
+
+		tuple = SearchSysCache1(SETTINGOID, ObjectIdGetDatum(settingId));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "cache lookup failed for setting %u", settingId);
+
+		pg_setting_acl_tuple = (Form_pg_setting_acl) GETSTRUCT(tuple);
+
+		/*
+		 * Get owner ID and working copy of existing ACL. If there's no ACL,
+		 * substitute the proper default.
+		 */
+		aclDatum = SysCacheGetAttr(SETTINGNAME, tuple, Anum_pg_setting_acl_setacl,
+								   &isNull);
+		if (isNull)
+		{
+			old_acl = acldefault(OBJECT_SETTING, InvalidOid);
+			/* There are no old member roles according to the catalogs */
+			noldmembers = 0;
+			oldmembers = NULL;
+		}
+		else
+		{
+			old_acl = DatumGetAclPCopy(aclDatum);
+			/* Get the roles mentioned in the existing ACL */
+			noldmembers = aclmembers(old_acl, &oldmembers);
+		}
+
+		/* Determine ID to do the grant as, and available grant options */
+		select_best_grantor(GetUserId(), istmt->privileges,
+							old_acl, GetUserId(),
+							&grantorId, &avail_goptions);
+
+		/*
+		 * Restrict the privileges to what we can actually grant, and emit the
+		 * standards-mandated warning and error messages.
+		 */
+		this_privileges =
+			restrict_and_check_grant(istmt->is_grant, avail_goptions,
+									 istmt->all_privs, istmt->privileges,
+									 settingId, grantorId, OBJECT_SETTING,
+									 text_to_cstring(&pg_setting_acl_tuple->setting),
+									 0, NULL);
+
+		/*
+		 * Generate new ACL.
+		 */
+		new_acl = merge_acl_with_grant(old_acl, istmt->is_grant,
+									   istmt->grant_option, istmt->behavior,
+									   istmt->grantees, this_privileges,
+									   grantorId, InvalidOid);
+
+		/*
+		 * We need the members of both old and new ACLs so we can correct the
+		 * shared dependency information.
+		 */
+		nnewmembers = aclmembers(new_acl, &newmembers);
+
+		/* finished building new ACL value, now insert it */
+		MemSet(values, 0, sizeof(values));
+		MemSet(nulls, false, sizeof(nulls));
+		MemSet(replaces, false, sizeof(replaces));
+
+		replaces[Anum_pg_setting_acl_setacl - 1] = true;
+		values[Anum_pg_setting_acl_setacl - 1] = PointerGetDatum(new_acl);
+
+		newtuple = heap_modify_tuple(tuple, RelationGetDescr(relation), values,
+									 nulls, replaces);
+
+		CatalogTupleUpdate(relation, &newtuple->t_self, newtuple);
+
+		/* Update initial privileges for extensions */
+		recordExtensionInitPriv(settingId, SettingAclRelationId, 0, new_acl);
+
+		/* Update the shared dependency ACL info */
+		updateAclDependencies(SettingAclRelationId,
+							  pg_setting_acl_tuple->oid, 0,
+							  InvalidOid,
+							  noldmembers, oldmembers,
+							  nnewmembers, newmembers);
+
+		ReleaseSysCache(tuple);
+
+		pfree(new_acl);
+
+		/* Post alter hook called for grant and revoke */
+		InvokeObjectPostAlterHook(SettingAclRelationId, settingId, 0);
+
+		/* prevent error when processing duplicate objects */
+		CommandCounterIncrement();
+	}
+
+	table_close(relation, RowExclusiveLock);
+}
+
 
 static AclMode
 string_to_privilege(const char *privname)
@@ -3255,6 +3427,10 @@ string_to_privilege(const char *privname)
 		return ACL_CREATE_TEMP;
 	if (strcmp(privname, "connect") == 0)
 		return ACL_CONNECT;
+	if (strcmp(privname, "set value") == 0)
+		return ACL_SET_VALUE;
+	if (strcmp(privname, "alter system") == 0)
+		return ACL_ALTER_SYSTEM;
 	if (strcmp(privname, "rule") == 0)
 		return 0;				/* ignore old RULE privileges */
 	ereport(ERROR,
@@ -3292,6 +3468,10 @@ privilege_to_string(AclMode privilege)
 			return "TEMP";
 		case ACL_CONNECT:
 			return "CONNECT";
+		case ACL_SET_VALUE:
+			return "SET VALUE";
+		case ACL_ALTER_SYSTEM:
+			return "ALTER SYSTEM";
 		default:
 			elog(ERROR, "unrecognized privilege: %d", (int) privilege);
 	}
@@ -3328,6 +3508,13 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
 					case OBJECT_COLUMN:
 						msg = gettext_noop("permission denied for column %s");
 						break;
+					case OBJECT_SETTING:
+						/*
+						 * Quote the object name for backward compatibility
+						 * with behavior before SET was handled here.
+						 */
+						msg = gettext_noop("permission denied to set parameter \"%s\"");
+						break;
 					case OBJECT_CONVERSION:
 						msg = gettext_noop("permission denied for conversion %s");
 						break;
@@ -3564,6 +3751,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
 					case OBJECT_AMPROC:
 					case OBJECT_ATTRIBUTE:
 					case OBJECT_CAST:
+					case OBJECT_SETTING:
 					case OBJECT_DEFAULT:
 					case OBJECT_DEFACL:
 					case OBJECT_DOMCONSTRAINT:
@@ -4000,6 +4188,59 @@ pg_database_aclmask(Oid db_oid, Oid roleid,
 	return result;
 }
 
+/*
+ * Exported routine for examining a user's privileges for a configuration
+ * parameter (GUC)
+ */
+AclMode
+pg_setting_acl_aclmask(Oid config_oid, Oid roleid,
+						AclMode mask, AclMaskHow how)
+{
+	AclMode		result;
+	HeapTuple	tuple;
+	Datum		aclDatum;
+	bool		isNull;
+	Acl		   *acl;
+
+	/* Superusers bypass all permission checking. */
+	if (superuser_arg(roleid))
+		return mask;
+
+	/*
+	 * Get the setting's ACL from pg_setting_acl
+	 */
+	tuple = SearchSysCache1(SETTINGOID, ObjectIdGetDatum(config_oid));
+	if (!HeapTupleIsValid(tuple))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_DATABASE),
+				 errmsg("setting with OID %u does not exist",
+						config_oid)));
+
+	aclDatum = SysCacheGetAttr(SETTINGOID, tuple, Anum_pg_setting_acl_setacl,
+							   &isNull);
+	if (isNull)
+	{
+		/* No ACL, so build default ACL */
+		acl = acldefault(OBJECT_SETTING, InvalidOid);
+		aclDatum = (Datum) 0;
+	}
+	else
+	{
+		/* detoast ACL if necessary */
+		acl = DatumGetAclP(aclDatum);
+	}
+
+	result = aclmask(acl, roleid, InvalidOid, mask, how);
+
+	/* if we have a detoasted copy, free it */
+	if (acl && (Pointer) acl != DatumGetPointer(aclDatum))
+		pfree(acl);
+
+	ReleaseSysCache(tuple);
+
+	return result;
+}
+
 /*
  * Exported routine for examining a user's privileges for a function
  */
@@ -4713,6 +4954,19 @@ pg_database_aclcheck(Oid db_oid, Oid roleid, AclMode mode)
 		return ACLCHECK_NO_PRIV;
 }
 
+/*
+ * Exported routine for checking a user's access privileges to a configuration
+ * parameter
+ */
+AclResult
+pg_setting_acl_aclcheck(Oid config_oid, Oid roleid, AclMode mode)
+{
+	if (pg_setting_acl_aclmask(config_oid, roleid, mode, ACLMASK_ANY) != 0)
+		return ACLCHECK_OK;
+	else
+		return ACLCHECK_NO_PRIV;
+}
+
 /*
  * Exported routine for checking a user's access privileges to a function
  */
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index dfd5fb669e..78868e7052 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_largeobject.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_replication_origin.h"
+#include "catalog/pg_setting_acl.h"
 #include "catalog/pg_shdepend.h"
 #include "catalog/pg_shdescription.h"
 #include "catalog/pg_shseclabel.h"
@@ -246,6 +247,7 @@ IsSharedRelation(Oid relationId)
 	/* These are the shared catalogs (look for BKI_SHARED_RELATION) */
 	if (relationId == AuthIdRelationId ||
 		relationId == AuthMemRelationId ||
+		relationId == SettingAclRelationId ||
 		relationId == DatabaseRelationId ||
 		relationId == SharedDescriptionRelationId ||
 		relationId == SharedDependRelationId ||
@@ -260,6 +262,8 @@ IsSharedRelation(Oid relationId)
 		relationId == AuthIdOidIndexId ||
 		relationId == AuthMemRoleMemIndexId ||
 		relationId == AuthMemMemRoleIndexId ||
+		relationId == SettingAclSettingIndexId ||
+		relationId == SettingAclOidIndexId ||
 		relationId == DatabaseNameIndexId ||
 		relationId == DatabaseOidIndexId ||
 		relationId == SharedDescriptionObjIndexId ||
@@ -277,6 +281,8 @@ IsSharedRelation(Oid relationId)
 	/* These are their toast tables and toast indexes */
 	if (relationId == PgAuthidToastTable ||
 		relationId == PgAuthidToastIndex ||
+		relationId == PgSettingAclToastTable ||
+		relationId == PgSettingAclToastIndex ||
 		relationId == PgDatabaseToastTable ||
 		relationId == PgDatabaseToastIndex ||
 		relationId == PgDbRoleSettingToastTable ||
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index ab9e42d7d1..5d2504463d 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -52,6 +52,7 @@
 #include "catalog/pg_publication_namespace.h"
 #include "catalog/pg_publication_rel.h"
 #include "catalog/pg_rewrite.h"
+#include "catalog/pg_setting_acl.h"
 #include "catalog/pg_statistic_ext.h"
 #include "catalog/pg_subscription.h"
 #include "catalog/pg_tablespace.h"
@@ -150,6 +151,7 @@ static const Oid object_classes[] = {
 	TypeRelationId,				/* OCLASS_TYPE */
 	CastRelationId,				/* OCLASS_CAST */
 	CollationRelationId,		/* OCLASS_COLLATION */
+	SettingAclRelationId,		/* OCLASS_SETTING */
 	ConstraintRelationId,		/* OCLASS_CONSTRAINT */
 	ConversionRelationId,		/* OCLASS_CONVERSION */
 	AttrDefaultRelationId,		/* OCLASS_DEFAULT */
@@ -1504,6 +1506,7 @@ doDeletion(const ObjectAddress *object, int flags)
 			/*
 			 * These global object types are not supported here.
 			 */
+		case OCLASS_SETTING:
 		case OCLASS_ROLE:
 		case OCLASS_DATABASE:
 		case OCLASS_TBLSPACE:
@@ -2778,6 +2781,9 @@ getObjectClass(const ObjectAddress *object)
 		case CollationRelationId:
 			return OCLASS_COLLATION;
 
+		case SettingAclRelationId:
+			return OCLASS_SETTING;
+
 		case ConstraintRelationId:
 			return OCLASS_CONSTRAINT;
 
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index f30c742d48..1ef833a0e3 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -51,6 +51,7 @@
 #include "catalog/pg_publication_namespace.h"
 #include "catalog/pg_publication_rel.h"
 #include "catalog/pg_rewrite.h"
+#include "catalog/pg_setting_acl.h"
 #include "catalog/pg_statistic_ext.h"
 #include "catalog/pg_subscription.h"
 #include "catalog/pg_tablespace.h"
@@ -2309,6 +2310,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
 		case OBJECT_COLUMN:
 		case OBJECT_ATTRIBUTE:
 		case OBJECT_COLLATION:
+		case OBJECT_SETTING:
 		case OBJECT_CONVERSION:
 		case OBJECT_STATISTIC_EXT:
 		case OBJECT_TSPARSER:
@@ -3510,6 +3512,22 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
 				break;
 			}
 
+		case OCLASS_SETTING:
+			{
+				char	   *setting;
+
+				setting = get_setting_name(object->objectId);
+				if (!setting)
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for setting %u",
+							 object->objectId);
+					break;
+				}
+				appendStringInfo(&buffer, _("setting %s"), setting);
+				break;
+			}
+
 		case OCLASS_SCHEMA:
 			{
 				char	   *nspname;
@@ -4473,6 +4491,10 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
 			appendStringInfoString(&buffer, "collation");
 			break;
 
+		case OCLASS_SETTING:
+			appendStringInfoString(&buffer, "setting");
+			break;
+
 		case OCLASS_CONSTRAINT:
 			getConstraintTypeDescription(&buffer, object->objectId,
 										 missing_ok);
@@ -4977,6 +4999,30 @@ getObjectIdentityParts(const ObjectAddress *object,
 				break;
 			}
 
+		case OCLASS_SETTING:
+			{
+				HeapTuple	configTup;
+				Form_pg_setting_acl configForm;
+				char	   *namestr;
+
+				configTup = SearchSysCache1(SETTINGOID,
+											ObjectIdGetDatum(object->objectId));
+				if (!HeapTupleIsValid(configTup))
+				{
+					if (!missing_ok)
+						elog(ERROR, "cache lookup failed for setting %u",
+							 object->objectId);
+					break;
+				}
+				configForm = (Form_pg_setting_acl) GETSTRUCT(configTup);
+				namestr = text_to_cstring(&configForm->setting);
+				appendStringInfoString(&buffer, namestr);
+				if (objname)
+					*objname = list_make1(namestr);
+				ReleaseSysCache(configTup);
+				break;
+			}
+
 		case OCLASS_CONVERSION:
 			{
 				HeapTuple	conTup;
diff --git a/src/backend/catalog/pg_setting_acl.c b/src/backend/catalog/pg_setting_acl.c
new file mode 100644
index 0000000000..3c6594da03
--- /dev/null
+++ b/src/backend/catalog/pg_setting_acl.c
@@ -0,0 +1,122 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_setting_acl.c
+ *	  routines to support manipulation of the pg_setting_acl relation
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/catalog/pg_setting_acl.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/genam.h"
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "access/sysattr.h"
+#include "access/table.h"
+#include "access/tableam.h"
+#include "catalog/catalog.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/objectaccess.h"
+#include "catalog/pg_namespace.h"
+#include "catalog/pg_setting_acl.h"
+#include "mb/pg_wchar.h"
+#include "utils/builtins.h"
+#include "utils/fmgroids.h"
+#include "utils/guc.h"
+#include "utils/pg_locale.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+
+
+/*
+ * SettingAclCreate
+ *
+ * Add a new tuple to pg_setting_acl.
+ *
+ * setting: the setting name to create.
+ * if_not_exists: if true, don't fail on duplicate name, but rather return
+ * the existing entry's Oid.
+ */
+Oid
+SettingAclCreate(const char *setting, bool if_not_exists)
+{
+	Relation	rel;
+	TupleDesc	tupDesc;
+	HeapTuple	tuple;
+	Datum		values[Natts_pg_setting_acl];
+	bool		nulls[Natts_pg_setting_acl];
+	Oid			settingId;
+	const char *canonical;
+
+	/*
+	 * Check whether the setting (by the given name or alias)
+	 * already exists.
+	 */
+	settingId = get_setting_oid(setting, true);
+	if (OidIsValid(settingId))
+	{
+		if (if_not_exists)
+			return settingId;
+		ereport(ERROR,
+				(errcode(ERRCODE_DUPLICATE_OBJECT),
+				 errmsg("setting \"%s\" already exists",
+						setting)));
+	}
+
+	/*
+	 * We must not require the setting to be in the list of existent GUCs,
+	 * as we may be called at different points during upgrades or the
+	 * installation or removal of extensions.  Instead, perform a basic sanity
+	 * check of the setting, and translate old forms of known names to their
+	 * canonical forms.
+	 *
+	 * If you deprecate a configuration name in favor of a new spelling, be
+	 * sure to consider whether to also upgrade pg_setting_acl entries.
+	 */
+	if (!valid_variable_name(setting, NULL))
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_NAME),
+				 errmsg("invalid setting name \"%s\"",
+						setting)));
+	canonical = GetConfigOptionCanonicalName(setting);
+	if (!canonical)
+		canonical = setting;
+
+	/*
+	 * Create and insert a new record, starting with a blank Acl.
+	 *
+	 * We don't take a strong enough lock to prevent concurrent insertions,
+	 * relying instead on the unique index.
+	 */
+	rel = table_open(SettingAclRelationId, RowExclusiveLock);
+	tupDesc = RelationGetDescr(rel);
+	MemSet(values, 0, sizeof(values));
+	MemSet(nulls, false, sizeof(nulls));
+	values[Anum_pg_setting_acl_setting - 1] =
+		DirectFunctionCall1(textin, CStringGetDatum(canonical));
+	settingId = GetNewOidWithIndex(rel,
+									   SettingAclOidIndexId,
+									   Anum_pg_setting_acl_oid);
+	values[Anum_pg_setting_acl_oid - 1] = ObjectIdGetDatum(settingId);
+	nulls[Anum_pg_setting_acl_setacl - 1] = true;
+	tuple = heap_form_tuple(tupDesc, values, nulls);
+	CatalogTupleInsert(rel, tuple);
+
+	/* Post creation hook for new setting */
+	InvokeObjectPostCreateHook(SettingAclRelationId, settingId, 0);
+
+	/*
+	 * Close pg_setting_acl, but keep lock till commit.
+	 */
+	heap_freetuple(tuple);
+	table_close(rel, NoLock);
+
+	return settingId;
+}
diff --git a/src/backend/catalog/setting_privileges.sql b/src/backend/catalog/setting_privileges.sql
new file mode 100644
index 0000000000..b788ebee7f
--- /dev/null
+++ b/src/backend/catalog/setting_privileges.sql
@@ -0,0 +1,62 @@
+/*
+ * PostgreSQL User SET variables
+ *
+ * Copyright (c) 2021, PostgreSQL Global Development Group
+ *
+ * src/backend/catalog/setting_privileges.sql
+ *
+ * Note: this file is read in single-user -j mode, which means that the
+ * command terminator is semicolon-newline-newline; whenever the backend
+ * sees that, it stops and executes what it's got.  If you write a lot of
+ * statements without empty lines between, they'll all get quoted to you
+ * in any error message about one of them, so don't do that.  Also, you
+ * cannot write a semicolon immediately followed by an empty line in a
+ * string literal (including a function body!) or a multiline comment.
+ */
+
+GRANT SET VALUE ON
+	enable_seqscan, enable_indexscan, enable_indexonlyscan, enable_bitmapscan,
+	enable_tidscan, enable_sort, enable_incremental_sort, enable_hashagg,
+	enable_material, enable_memoize, enable_nestloop, enable_mergejoin,
+	enable_hashjoin, enable_gathermerge, enable_partitionwise_join,
+	enable_partitionwise_aggregate, enable_parallel_append,
+	enable_parallel_hash, enable_partition_pruning, enable_async_append, geqo,
+	exit_on_error, debug_print_parse, debug_print_rewritten, debug_print_plan,
+	debug_pretty_print, trace_notify, transform_null_equals,
+	default_transaction_read_only, transaction_read_only,
+	default_transaction_deferrable, transaction_deferrable, row_security,
+	check_function_bodies, array_nulls, default_with_oids, trace_sort,
+	trace_syncscan, optimize_bounded_sort, escape_string_warning,
+	standard_conforming_strings, synchronize_seqscans, quote_all_identifiers,
+	parallel_leader_participation, jit, jit_expressions, jit_tuple_deforming,
+	default_statistics_target, from_collapse_limit, join_collapse_limit,
+	geqo_threshold, geqo_effort, geqo_pool_size, geqo_generations,
+	temp_buffers, work_mem, maintenance_work_mem, logical_decoding_work_mem,
+	vacuum_cost_page_hit, vacuum_cost_page_miss, vacuum_cost_page_dirty,
+	vacuum_cost_limit, statement_timeout, lock_timeout,
+	idle_in_transaction_session_timeout, idle_session_timeout,
+	vacuum_freeze_min_age, vacuum_freeze_table_age,
+	vacuum_multixact_freeze_min_age, vacuum_multixact_freeze_table_age,
+	vacuum_failsafe_age, vacuum_multixact_failsafe_age, wal_skip_threshold,
+	wal_sender_timeout, commit_siblings, extra_float_digits,
+	log_parameter_max_length_on_error, effective_io_concurrency,
+	maintenance_io_concurrency, backend_flush_after,
+	max_parallel_maintenance_workers, max_parallel_workers_per_gather,
+	max_parallel_workers, tcp_keepalives_idle, tcp_keepalives_interval,
+	ssl_renegotiation_limit, tcp_keepalives_count, gin_fuzzy_search_limit,
+	effective_cache_size, min_parallel_table_scan_size,
+	min_parallel_index_scan_size, gin_pending_list_limit, tcp_user_timeout,
+	client_connection_check_interval, seq_page_cost, random_page_cost,
+	cpu_tuple_cost, cpu_index_tuple_cost, cpu_operator_cost,
+	parallel_tuple_cost, parallel_setup_cost, jit_above_cost,
+	jit_optimize_above_cost, jit_inline_above_cost, cursor_tuple_fraction,
+	geqo_selection_bias, geqo_seed, hash_mem_multiplier, seed,
+	vacuum_cost_delay, DateStyle, default_table_access_method,
+	default_tablespace, temp_tablespaces, lc_monetary, lc_numeric, lc_time,
+	local_preload_libraries, search_path, role, TimeZone,
+	timezone_abbreviations, default_text_search_config, application_name,
+	backslash_quote, bytea_output, client_min_messages, constraint_exclusion,
+	default_toast_compression, default_transaction_isolation,
+	transaction_isolation, IntervalStyle, synchronous_commit, xmlbinary,
+	xmloption, force_parallel_mode, password_encryption, plan_cache_mode
+	TO public;
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 40b7bca5a9..92b226b8d2 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -595,6 +595,19 @@ CREATE RULE pg_settings_n AS
 
 GRANT SELECT, UPDATE ON pg_settings TO PUBLIC;
 
+CREATE VIEW pg_setting_privileges AS
+	SELECT grantor.rolname AS grantor,
+		   grantee.rolname AS grantee,
+		   set_acl.setting AS setting,
+		   acl.privilege_type AS privilege_type,
+		   acl.is_grantable
+		FROM pg_catalog.pg_setting_acl set_acl,
+		LATERAL (SELECT * FROM aclexplode(set_acl.setacl)) acl
+		LEFT JOIN pg_catalog.pg_authid grantee ON acl.grantee = grantee.oid
+		LEFT JOIN pg_catalog.pg_authid grantor ON acl.grantor = grantor.oid;
+
+GRANT SELECT ON pg_setting_privileges TO PUBLIC;
+
 CREATE VIEW pg_file_settings AS
    SELECT * FROM pg_show_all_file_settings() AS A;
 
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index 1f64c8aa51..3dd0c5cadb 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -639,6 +639,7 @@ AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
 			break;
 
 		case OCLASS_CAST:
+		case OCLASS_SETTING:
 		case OCLASS_CONSTRAINT:
 		case OCLASS_DEFAULT:
 		case OCLASS_LANGUAGE:
diff --git a/src/backend/commands/dropcmds.c b/src/backend/commands/dropcmds.c
index c9b5732448..00398727ed 100644
--- a/src/backend/commands/dropcmds.c
+++ b/src/backend/commands/dropcmds.c
@@ -276,6 +276,13 @@ does_not_exist_skipping(ObjectType objtype, Node *object)
 				name = NameListToString(castNode(List, object));
 			}
 			break;
+		case OBJECT_SETTING:
+			if (!schema_does_not_exist_skipping(castNode(List, object), &msg, &name))
+			{
+				msg = gettext_noop("setting \"%s\" does not exist, skipping");
+				name = NameListToString(castNode(List, object));
+			}
+			break;
 		case OBJECT_CONVERSION:
 			if (!schema_does_not_exist_skipping(castNode(List, object), &msg, &name))
 			{
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 1e8587502e..038486a622 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -937,6 +937,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
 {
 	switch (obtype)
 	{
+		case OBJECT_SETTING:
 		case OBJECT_DATABASE:
 		case OBJECT_TABLESPACE:
 		case OBJECT_ROLE:
@@ -1012,6 +1013,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
 {
 	switch (objclass)
 	{
+		case OCLASS_SETTING:
 		case OCLASS_DATABASE:
 		case OCLASS_TBLSPACE:
 		case OCLASS_ROLE:
@@ -2072,6 +2074,8 @@ stringify_grant_objtype(ObjectType objtype)
 	{
 		case OBJECT_COLUMN:
 			return "COLUMN";
+		case OBJECT_SETTING:
+			return "SETTING";
 		case OBJECT_TABLE:
 			return "TABLE";
 		case OBJECT_SEQUENCE:
@@ -2155,6 +2159,8 @@ stringify_adefprivs_objtype(ObjectType objtype)
 	{
 		case OBJECT_COLUMN:
 			return "COLUMNS";
+		case OBJECT_SETTING:
+			return "SETTINGS";
 		case OBJECT_TABLE:
 			return "TABLES";
 		case OBJECT_SEQUENCE:
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 7a62d547e2..7b05eb0110 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -67,6 +67,7 @@ SecLabelSupportsObjectType(ObjectType objtype)
 		case OBJECT_ATTRIBUTE:
 		case OBJECT_CAST:
 		case OBJECT_COLLATION:
+		case OBJECT_SETTING:
 		case OBJECT_CONVERSION:
 		case OBJECT_DEFAULT:
 		case OBJECT_DEFACL:
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index dc5872f988..11671390a5 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -12620,6 +12620,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 			case OCLASS_TYPE:
 			case OCLASS_CAST:
 			case OCLASS_COLLATION:
+			case OCLASS_SETTING:
 			case OCLASS_CONVERSION:
 			case OCLASS_LANGUAGE:
 			case OCLASS_LARGEOBJECT:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index a03b33b53b..45a9f078f4 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -363,8 +363,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <str>		foreign_server_version opt_foreign_server_version
 %type <str>		opt_in_database
 
-%type <str>		OptSchemaName
-%type <list>	OptSchemaEltList
+%type <str>		OptSchemaName setting_name
+%type <list>	OptSchemaEltList setting_target
 
 %type <chr>		am_type
 
@@ -402,8 +402,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <str>		iso_level opt_encoding
 %type <rolespec> grantee
 %type <list>	grantee_list
-%type <accesspriv> privilege
-%type <list>	privileges privilege_list
+%type <accesspriv> privilege setting_priv
+%type <list>	privileges privilege_list setting_priv_list
 %type <privtarget> privilege_target
 %type <objwithargs> function_with_argtypes aggregate_with_argtypes operator_with_argtypes
 %type <list>	function_with_argtypes_list aggregate_with_argtypes_list operator_with_argtypes_list
@@ -716,7 +716,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	ORDER ORDINALITY OTHERS OUT_P OUTER_P
 	OVER OVERLAPS OVERLAY OVERRIDING OWNED OWNER
 
-	PARALLEL PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLANS POLICY
+	PARALLEL PARAMETER PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLANS POLICY
 	POSITION PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
 	PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROCEDURES PROGRAM PUBLICATION
 
@@ -6981,6 +6981,20 @@ GrantStmt:	GRANT privileges ON privilege_target TO grantee_list
 					n->grantor = $8;
 					$$ = (Node*)n;
 				}
+			| GRANT setting_priv_list ON setting_target TO grantee_list
+			opt_grant_grant_option opt_granted_by
+				{
+					GrantStmt *n = makeNode(GrantStmt);
+					n->is_grant = true;
+					n->privileges = $2;
+					n->targtype = ACL_TARGET_OBJECT;
+					n->objtype = OBJECT_SETTING;
+					n->objects = $4;
+					n->grantees = $6;
+					n->grant_option = $7;
+					n->grantor = $8;
+					$$ = (Node*)n;
+				}
 		;
 
 RevokeStmt:
@@ -7014,6 +7028,36 @@ RevokeStmt:
 					n->behavior = $11;
 					$$ = (Node *)n;
 				}
+			| REVOKE setting_priv_list ON setting_target FROM grantee_list
+			opt_granted_by opt_drop_behavior
+				{
+					GrantStmt *n = makeNode(GrantStmt);
+					n->is_grant = false;
+					n->grant_option = false;
+					n->privileges = $2;
+					n->targtype = ACL_TARGET_OBJECT;
+					n->objtype = OBJECT_SETTING;
+					n->objects = $4;
+					n->grantees = $6;
+					n->grantor = $7;
+					n->behavior = $8;
+					$$ = (Node *)n;
+				}
+			| REVOKE GRANT OPTION FOR setting_priv_list ON setting_target
+			FROM grantee_list opt_granted_by opt_drop_behavior
+				{
+					GrantStmt *n = makeNode(GrantStmt);
+					n->is_grant = false;
+					n->grant_option = true;
+					n->privileges = $5;
+					n->targtype = ACL_TARGET_OBJECT;
+					n->objtype = OBJECT_SETTING;
+					n->objects = $7;
+					n->grantees = $9;
+					n->grantor = $10;
+					n->behavior = $11;
+					$$ = (Node *)n;
+				}
 		;

@@ -7082,6 +7126,49 @@ privilege:	SELECT opt_column_list
 			}
 		;
 
+setting_priv_list: setting_priv						{ $$ = list_make1($1); }
+			| setting_priv_list ',' setting_priv	{ $$ = lappend($1, $3); }
+		;
+
+setting_priv:
+		ALTER SYSTEM_P
+			{
+				AccessPriv *n = makeNode(AccessPriv);
+				n->priv_name = pstrdup("alter system");
+				n->cols = NIL;
+				$$ = n;
+			}
+		| SET VALUE_P
+			{
+				AccessPriv *n = makeNode(AccessPriv);
+				n->priv_name = pstrdup("set value");
+				n->cols = NIL;
+				$$ = n;
+			}
+		;
+
+
+setting_target:
+			setting_name
+				{
+					$$ = list_make1(makeString($1));
+				}
+			| setting_target ',' setting_name
+				{
+					$$ = lappend($1, makeString($3));
+				}
+		;
+
+setting_name:
+			ColId
+				{
+					$$ = $1;
+				}
+			| setting_name '.' ColId
+				{
+					$$ = psprintf("%s.%s", $1, $3);
+				}
+		;
 
 /* Don't bother trying to fold the first two rules into one using
  * opt_table.  You're going to get conflicts.
@@ -15851,6 +15938,7 @@ unreserved_keyword:
 			| OWNED
 			| OWNER
 			| PARALLEL
+			| PARAMETER
 			| PARSER
 			| PARTIAL
 			| PARTITION
@@ -16429,6 +16517,7 @@ bare_label_keyword:
 			| OWNED
 			| OWNER
 			| PARALLEL
+			| PARAMETER
 			| PARSER
 			| PARTIAL
 			| PARTITION
diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c
index 0a16f8156c..b0e43118bf 100644
--- a/src/backend/utils/adt/acl.c
+++ b/src/backend/utils/adt/acl.c
@@ -23,6 +23,7 @@
 #include "catalog/pg_authid.h"
 #include "catalog/pg_class.h"
 #include "catalog/pg_database.h"
+#include "catalog/pg_setting_acl.h"
 #include "catalog/pg_type.h"
 #include "commands/dbcommands.h"
 #include "commands/proclang.h"
@@ -36,6 +37,7 @@
 #include "utils/array.h"
 #include "utils/builtins.h"
 #include "utils/catcache.h"
+#include "utils/guc.h"
 #include "utils/inval.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -109,6 +111,8 @@ static Oid	convert_tablespace_name(text *tablespacename);
 static AclMode convert_tablespace_priv_string(text *priv_type_text);
 static Oid	convert_type_name(text *typename);
 static AclMode convert_type_priv_string(text *priv_type_text);
+static Oid	convert_setting_name(text *setting);
+static AclMode convert_setting_priv_string(text *priv_setting_text);
 static AclMode convert_role_priv_string(text *priv_type_text);
 static AclResult pg_role_aclcheck(Oid role_oid, Oid roleid, AclMode mode);
 
@@ -306,6 +310,12 @@ aclparse(const char *s, AclItem *aip)
 			case ACL_CONNECT_CHR:
 				read = ACL_CONNECT;
 				break;
+			case ACL_SET_VALUE_CHR:
+				read = ACL_SET_VALUE;
+				break;
+			case ACL_ALTER_SYSTEM_CHR:
+				read = ACL_ALTER_SYSTEM;
+				break;
 			case 'R':			/* ignore old RULE privileges */
 				read = 0;
 				break;
@@ -794,6 +804,10 @@ acldefault(ObjectType objtype, Oid ownerId)
 			world_default = ACL_USAGE;
 			owner_default = ACL_ALL_RIGHTS_TYPE;
 			break;
+		case OBJECT_SETTING:
+			world_default = ACL_NO_RIGHTS;
+			owner_default = ACL_NO_RIGHTS;
+			break;
 		default:
 			elog(ERROR, "unrecognized objtype: %d", (int) objtype);
 			world_default = ACL_NO_RIGHTS;	/* keep compiler quiet */
@@ -1602,6 +1616,10 @@ convert_priv_string(text *priv_type_text)
 		return ACL_CREATE_TEMP;
 	if (pg_strcasecmp(priv_type, "CONNECT") == 0)
 		return ACL_CONNECT;
+	if (pg_strcasecmp(priv_type, "SET VALUE") == 0)
+		return ACL_SET_VALUE;
+	if (pg_strcasecmp(priv_type, "ALTER SYSTEM") == 0)
+		return ACL_ALTER_SYSTEM;
 	if (pg_strcasecmp(priv_type, "RULE") == 0)
 		return 0;				/* ignore old RULE privileges */
 
@@ -1698,6 +1716,10 @@ convert_aclright_to_string(int aclright)
 			return "TEMPORARY";
 		case ACL_CONNECT:
 			return "CONNECT";
+		case ACL_SET_VALUE:
+			return "SET VALUE";
+		case ACL_ALTER_SYSTEM:
+			return "ALTER SYSTEM";
 		default:
 			elog(ERROR, "unrecognized aclright: %d", aclright);
 			return NULL;
@@ -4429,6 +4451,205 @@ convert_type_priv_string(text *priv_type_text)
 	return convert_any_priv_string(priv_type_text, type_priv_map);
 }
 
+/*
+ * has_setting_privilege variants
+ *		These are all named "has_setting_privilege" at the SQL level.
+ *		They take various combinations of setting name, setting OID,
+ *		user name, user OID, or implicit user = current_user.
+ *
+ *		The result is a boolean value: true if user has the indicated
+ *		privilege, false if not, or NULL if object doesn't exist.
+ */
+
+/*
+ * has_setting_privilege_name_name
+ *		Check user privileges on a setting given
+ *		name username, text setting, and text priv name.
+ */
+Datum
+has_setting_privilege_name_name(PG_FUNCTION_ARGS)
+{
+	Name		username = PG_GETARG_NAME(0);
+	text	   *setting = PG_GETARG_TEXT_PP(1);
+	text	   *priv_setting_text = PG_GETARG_TEXT_PP(2);
+	Oid			roleid;
+	Oid			settingoid;
+	AclMode		mode;
+	AclResult	aclresult;
+
+	roleid = get_role_oid_or_public(NameStr(*username));
+	settingoid = convert_setting_name(setting);
+	mode = convert_setting_priv_string(priv_setting_text);
+
+	aclresult = pg_setting_acl_aclcheck(settingoid, roleid, mode);
+
+	PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
+}
+
+/*
+ * has_setting_privilege_name
+ *		Check user privileges on a setting given
+ *		text setting and text priv name.
+ *		current_user is assumed
+ */
+Datum
+has_setting_privilege_name(PG_FUNCTION_ARGS)
+{
+	text	   *setting = PG_GETARG_TEXT_PP(0);
+	text	   *priv_setting_text = PG_GETARG_TEXT_PP(1);
+	Oid			roleid;
+	Oid			settingoid;
+	AclMode		mode;
+	AclResult	aclresult;
+
+	roleid = GetUserId();
+	settingoid = convert_setting_name(setting);
+	mode = convert_setting_priv_string(priv_setting_text);
+
+	aclresult = pg_setting_acl_aclcheck(settingoid, roleid, mode);
+
+	PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
+}
+
+/*
+ * has_setting_privilege_name_id
+ *		Check user privileges on a setting given
+ *		name usename, setting oid, and text priv name.
+ */
+Datum
+has_setting_privilege_name_id(PG_FUNCTION_ARGS)
+{
+	Name		username = PG_GETARG_NAME(0);
+	Oid			settingoid = PG_GETARG_OID(1);
+	text	   *priv_setting_text = PG_GETARG_TEXT_PP(2);
+	Oid			roleid;
+	AclMode		mode;
+	AclResult	aclresult;
+
+	roleid = get_role_oid_or_public(NameStr(*username));
+	mode = convert_setting_priv_string(priv_setting_text);
+
+	if (!SearchSysCacheExists1(SETTINGOID, ObjectIdGetDatum(settingoid)))
+		PG_RETURN_NULL();
+
+	aclresult = pg_setting_acl_aclcheck(settingoid, roleid, mode);
+
+	PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
+}
+
+/*
+ * has_setting_privilege_id
+ *		Check user privileges on a setting given
+ *		setting oid, and text priv name.
+ *		current_user is assumed
+ */
+Datum
+has_setting_privilege_id(PG_FUNCTION_ARGS)
+{
+	Oid			settingoid = PG_GETARG_OID(0);
+	text	   *priv_setting_text = PG_GETARG_TEXT_PP(1);
+	Oid			roleid;
+	AclMode		mode;
+	AclResult	aclresult;
+
+	roleid = GetUserId();
+	mode = convert_setting_priv_string(priv_setting_text);
+
+	if (!SearchSysCacheExists1(SETTINGOID, ObjectIdGetDatum(settingoid)))
+		PG_RETURN_NULL();
+
+	aclresult = pg_setting_acl_aclcheck(settingoid, roleid, mode);
+
+	PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
+}
+
+/*
+ * has_setting_privilege_id_name
+ *		Check user privileges on a setting given
+ *		roleid, text setting, and text priv name.
+ */
+Datum
+has_setting_privilege_id_name(PG_FUNCTION_ARGS)
+{
+	Oid			roleid = PG_GETARG_OID(0);
+	text	   *setting = PG_GETARG_TEXT_PP(1);
+	text	   *priv_setting_text = PG_GETARG_TEXT_PP(2);
+	Oid			settingoid;
+	AclMode		mode;
+	AclResult	aclresult;
+
+	settingoid = convert_setting_name(setting);
+	mode = convert_setting_priv_string(priv_setting_text);
+
+	aclresult = pg_setting_acl_aclcheck(settingoid, roleid, mode);
+
+	PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
+}
+
+/*
+ * has_setting_privilege_id_id
+ *		Check user privileges on a setting given
+ *		roleid, setting oid, and text priv name.
+ */
+Datum
+has_setting_privilege_id_id(PG_FUNCTION_ARGS)
+{
+	Oid			roleid = PG_GETARG_OID(0);
+	Oid			settingoid = PG_GETARG_OID(1);
+	text	   *priv_setting_text = PG_GETARG_TEXT_PP(2);
+	AclMode		mode;
+	AclResult	aclresult;
+
+	mode = convert_setting_priv_string(priv_setting_text);
+
+	if (!SearchSysCacheExists1(SETTINGOID, ObjectIdGetDatum(settingoid)))
+		PG_RETURN_NULL();
+
+	aclresult = pg_setting_acl_aclcheck(settingoid, roleid, mode);
+
+	PG_RETURN_BOOL(aclresult == ACLCHECK_OK);
+}
+
+/*
+ *		Support routines for has_setting_privilege family.
+ */
+
+/*
+ * Given a setting name expressed as a string, look it up and return Oid
+ */
+static Oid
+convert_setting_name(text *settingname)
+{
+	Oid			oid;
+	char	   *setting = text_to_cstring(settingname);
+
+	oid = get_setting_oid(setting, true);
+
+	if (!OidIsValid(oid))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("setting \"%s\" does not exist", setting)));
+
+	return oid;
+}
+
+/*
+ * convert_setting_priv_string
+ *		Convert text string to AclMode value.
+ */
+static AclMode
+convert_setting_priv_string(text *priv_setting_text)
+{
+	static const priv_map setting_priv_map[] = {
+		{"SET VALUE", ACL_SET_VALUE},
+		{"SET VALUE WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_SET_VALUE)},
+		{"ALTER SYSTEM", ACL_ALTER_SYSTEM},
+		{"ALTER SYSTEM WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_ALTER_SYSTEM)},
+		{NULL, 0}
+	};
+
+	return convert_any_priv_string(priv_setting_text, setting_priv_map);
+}
 
 /*
  * pg_has_role variants
@@ -4670,6 +4891,43 @@ initialize_acl(void)
 	}
 }
 
+/*
+ * get_setting_oid - Given a configuration parameter name, look up the
+ * configuration parameter's OID.  Note that names which are aliases for
+ * a canonical name will be translated automatically and the OID found.
+ *
+ * If missing_ok is false, throw an error if the configuration parameter name
+ * is not found.
+ *
+ * Returns the Oid of the configuration parameter.
+ */
+Oid
+get_setting_oid(const char *setting, bool missing_ok)
+{
+	Oid			oid;
+
+	/* Check for the variable by the name we were given */
+	oid = GetSysCacheOid1(SETTINGNAME, Anum_pg_setting_acl_oid,
+						  PointerGetDatum(cstring_to_text(setting)));
+	if (!OidIsValid(oid))
+	{
+		const char *canonical;
+
+		/* Check if the variable has a different canonical spelling */
+		canonical = GetConfigOptionCanonicalName(setting);
+		if (canonical != NULL)
+			oid = GetSysCacheOid1(SETTINGNAME, Anum_pg_setting_acl_oid,
+								  PointerGetDatum(cstring_to_text(canonical)));
+
+		if (!OidIsValid(oid) && !missing_ok)
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_OBJECT),
+					 errmsg("setting \"%s\" does not exist", setting)));
+	}
+
+	return oid;
+}
+
 /*
  * RoleMembershipCacheCallback
  *		Syscache inval callback function
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index feef999863..f4e806fe8e 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -32,6 +32,7 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_range.h"
+#include "catalog/pg_setting_acl.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_transform.h"
 #include "catalog/pg_type.h"
@@ -3304,6 +3305,25 @@ free_attstatsslot(AttStatsSlot *sslot)
 		pfree(sslot->numbers_arr);
 }
 
+char *
+get_setting_name(Oid configid)
+{
+	HeapTuple	tp;
+
+	tp = SearchSysCache1(SETTINGOID, ObjectIdGetDatum(configid));
+	if (HeapTupleIsValid(tp))
+	{
+		Form_pg_setting_acl configtup = (Form_pg_setting_acl) GETSTRUCT(tp);
+		char	   *result;
+
+		result = pstrdup(text_to_cstring(&configtup->setting));
+		ReleaseSysCache(tp);
+		return result;
+	}
+	else
+		return NULL;
+}
+
 /*				---------- PG_NAMESPACE CACHE ----------				 */
 
 /*
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index f4e7819f1e..cbcbf02839 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -57,6 +57,7 @@
 #include "catalog/pg_rewrite.h"
 #include "catalog/pg_seclabel.h"
 #include "catalog/pg_sequence.h"
+#include "catalog/pg_setting_acl.h"
 #include "catalog/pg_shdepend.h"
 #include "catalog/pg_shdescription.h"
 #include "catalog/pg_shseclabel.h"
@@ -762,6 +763,28 @@ static const struct cachedesc cacheinfo[] = {
 		},
 		32
 	},
+	{SettingAclRelationId,		/* SETTINGNAME */
+		SettingAclSettingIndexId,
+		1,
+		{
+			Anum_pg_setting_acl_setting,
+			0,
+			0,
+			0
+		},
+		4
+	},
+	{SettingAclRelationId,		/* SETTINGOID */
+		SettingAclOidIndexId,
+		1,
+		{
+			Anum_pg_setting_acl_oid,
+			0,
+			0,
+			0
+		},
+		4
+	},
 	{StatisticExtDataRelationId,	/* STATEXTDATASTXOID */
 		StatisticExtDataStxoidInhIndexId,
 		2,
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 1e3650184b..2fa38b14af 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -43,7 +43,9 @@
 #include "access/xlog_internal.h"
 #include "access/xlogrecovery.h"
 #include "catalog/namespace.h"
+#include "catalog/objectaccess.h"
 #include "catalog/pg_authid.h"
+#include "catalog/pg_setting_acl.h"
 #include "catalog/storage.h"
 #include "commands/async.h"
 #include "commands/prepare.h"
@@ -977,7 +979,7 @@ static const unit_conversion time_unit_conversion_table[] =
 static struct config_bool ConfigureNamesBool[] =
 {
 	{
-		{"enable_seqscan", PGC_USERSET, QUERY_TUNING_METHOD,
+		{"enable_seqscan", PGC_SUSET, QUERY_TUNING_METHOD,
 			gettext_noop("Enables the planner's use of sequential-scan plans."),
 			NULL,
 			GUC_EXPLAIN
@@ -987,7 +989,7 @@ static struct config_bool ConfigureNamesBool[] =
 		NULL, NULL, NULL
 	},
 	{
-		{"enable_indexscan", PGC_USERSET, QUERY_TUNING_METHOD,
+		{"enable_indexscan", PGC_SUSET, QUERY_TUNING_METHOD,
 			gettext_noop("Enables the planner's use of index-scan plans."),
 			NULL,
 			GUC_EXPLAIN
@@ -997,7 +999,7 @@ static struct config_bool ConfigureNamesBool[] =
 		NULL, NULL, NULL
 	},
 	{
-		{"enable_indexonlyscan", PGC_USERSET, QUERY_TUNING_METHOD,
+		{"enable_indexonlyscan", PGC_SUSET, QUERY_TUNING_METHOD,
 			gettext_noop("Enables the planner's use of index-only-scan plans."),
 			NULL,
 			GUC_EXPLAIN
@@ -1007,7 +1009,7 @@ static struct config_bool ConfigureNamesBool[] =
 		NULL, NULL, NULL
 	},
 	{
-		{"enable_bitmapscan", PGC_USERSET, QUERY_TUNING_METHOD,
+		{"enable_bitmapscan", PGC_SUSET, QUERY_TUNING_METHOD,
 			gettext_noop("Enables the planner's use of bitmap-scan plans."),
 			NULL,
 			GUC_EXPLAIN
@@ -1017,7 +1019,7 @@ static struct config_bool ConfigureNamesBool[] =
 		NULL, NULL, NULL
 	},
 	{
-		{"enable_tidscan", PGC_USERSET, QUERY_TUNING_METHOD,
+		{"enable_tidscan", PGC_SUSET, QUERY_TUNING_METHOD,
 			gettext_noop("Enables the planner's use of TID scan plans."),
 			NULL,
 			GUC_EXPLAIN
@@ -1027,7 +1029,7 @@ static struct config_bool ConfigureNamesBool[] =
 		NULL, NULL, NULL
 	},
 	{
-		{"enable_sort", PGC_USERSET, QUERY_TUNING_METHOD,
+		{"enable_sort", PGC_SUSET, QUERY_TUNING_METHOD,
 			gettext_noop("Enables the planner's use of explicit sort steps."),
 			NULL,
 			GUC_EXPLAIN
@@ -1037,7 +1039,7 @@ static struct config_bool ConfigureNamesBool[] =
 		NULL, NULL, NULL
 	},
 	{
-		{"enable_incremental_sort", PGC_USERSET, QUERY_TUNING_METHOD,
+		{"enable_incremental_sort", PGC_SUSET, QUERY_TUNING_METHOD,
 			gettext_noop("Enables the planner's use of incremental sort steps."),
 			NULL,
 			GUC_EXPLAIN
@@ -1047,7 +1049,7 @@ static struct config_bool ConfigureNamesBool[] =
 		NULL, NULL, NULL
 	},
 	{
-		{"enable_hashagg", PGC_USERSET, QUERY_TUNING_METHOD,
+		{"enable_hashagg", PGC_SUSET, QUERY_TUNING_METHOD,
 			gettext_noop("Enables the planner's use of hashed aggregation plans."),
 			NULL,
 			GUC_EXPLAIN
@@ -1057,7 +1059,7 @@ static struct config_bool ConfigureNamesBool[] =
 		NULL, NULL, NULL
 	},
 	{
-		{"enable_material", PGC_USERSET, QUERY_TUNING_METHOD,
+		{"enable_material", PGC_SUSET, QUERY_TUNING_METHOD,
 			gettext_noop("Enables the planner's use of materialization."),
 			NULL,
 			GUC_EXPLAIN
@@ -1067,7 +1069,7 @@ static struct config_bool ConfigureNamesBool[] =
 		NULL, NULL, NULL
 	},
 	{
-		{"enable_memoize", PGC_USERSET, QUERY_TUNING_METHOD,
+		{"enable_memoize", PGC_SUSET, QUERY_TUNING_METHOD,
 			gettext_noop("Enables the planner's use of memoization."),
 			NULL,
 			GUC_EXPLAIN
@@ -1077,7 +1079,7 @@ static struct config_bool ConfigureNamesBool[] =
 		NULL, NULL, NULL
 	},
 	{
-		{"enable_nestloop", PGC_USERSET, QUERY_TUNING_METHOD,
+		{"enable_nestloop", PGC_SUSET, QUERY_TUNING_METHOD,
 			gettext_noop("Enables the planner's use of nested-loop join plans."),
 			NULL,
 			GUC_EXPLAIN
@@ -1087,7 +1089,7 @@ static struct config_bool ConfigureNamesBool[] =
 		NULL, NULL, NULL
 	},
 	{
-		{"enable_mergejoin", PGC_USERSET, QUERY_TUNING_METHOD,
+		{"enable_mergejoin", PGC_SUSET, QUERY_TUNING_METHOD,
 			gettext_noop("Enables the planner's use of merge join plans."),
 			NULL,
 			GUC_EXPLAIN
@@ -1097,7 +1099,7 @@ static struct config_bool ConfigureNamesBool[] =
 		NULL, NULL, NULL
 	},
 	{
-		{"enable_hashjoin", PGC_USERSET, QUERY_TUNING_METHOD,
+		{"enable_hashjoin", PGC_SUSET, QUERY_TUNING_METHOD,
 			gettext_noop("Enables the planner's use of hash join plans."),
 			NULL,
 			GUC_EXPLAIN
@@ -1107,7 +1109,7 @@ static struct config_bool ConfigureNamesBool[] =
 		NULL, NULL, NULL
 	},
 	{
-		{"enable_gathermerge", PGC_USERSET, QUERY_TUNING_METHOD,
+		{"enable_gathermerge", PGC_SUSET, QUERY_TUNING_METHOD,
 			gettext_noop("Enables the planner's use of gather merge plans."),
 			NULL,
 			GUC_EXPLAIN
@@ -1117,7 +1119,7 @@ static struct config_bool ConfigureNamesBool[] =
 		NULL, NULL, NULL
 	},
 	{
-		{"enable_partitionwise_join", PGC_USERSET, QUERY_TUNING_METHOD,
+		{"enable_partitionwise_join", PGC_SUSET, QUERY_TUNING_METHOD,
 			gettext_noop("Enables partitionwise join."),
 			NULL,
 			GUC_EXPLAIN
@@ -1127,7 +1129,7 @@ static struct config_bool ConfigureNamesBool[] =
 		NULL, NULL, NULL
 	},
 	{
-		{"enable_partitionwise_aggregate", PGC_USERSET, QUERY_TUNING_METHOD,
+		{"enable_partitionwise_aggregate", PGC_SUSET, QUERY_TUNING_METHOD,
 			gettext_noop("Enables partitionwise aggregation and grouping."),
 			NULL,
 			GUC_EXPLAIN
@@ -1137,7 +1139,7 @@ static struct config_bool ConfigureNamesBool[] =
 		NULL, NULL, NULL
 	},
 	{
-		{"enable_parallel_append", PGC_USERSET, QUERY_TUNING_METHOD,
+		{"enable_parallel_append", PGC_SUSET, QUERY_TUNING_METHOD,
 			gettext_noop("Enables the planner's use of parallel append plans."),
 			NULL,
 			GUC_EXPLAIN
@@ -1147,7 +1149,7 @@ static struct config_bool ConfigureNamesBool[] =
 		NULL, NULL, NULL
 	},
 	{
-		{"enable_parallel_hash", PGC_USERSET, QUERY_TUNING_METHOD,
+		{"enable_parallel_hash", PGC_SUSET, QUERY_TUNING_METHOD,
 			gettext_noop("Enables the planner's use of parallel hash plans."),
 			NULL,
 			GUC_EXPLAIN
@@ -1157,7 +1159,7 @@ static struct config_bool ConfigureNamesBool[] =
 		NULL, NULL, NULL
 	},
 	{
-		{"enable_partition_pruning", PGC_USERSET, QUERY_TUNING_METHOD,
+		{"enable_partition_pruning", PGC_SUSET, QUERY_TUNING_METHOD,
 			gettext_noop("Enables plan-time and execution-time partition pruning."),
 			gettext_noop("Allows the query planner and executor to compare partition "
 						 "bounds to conditions in the query to determine which "
@@ -1169,7 +1171,7 @@ static struct config_bool ConfigureNamesBool[] =
 		NULL, NULL, NULL
 	},
 	{
-		{"enable_async_append", PGC_USERSET, QUERY_TUNING_METHOD,
+		{"enable_async_append", PGC_SUSET, QUERY_TUNING_METHOD,
 			gettext_noop("Enables the planner's use of async append plans."),
 			NULL,
 			GUC_EXPLAIN
@@ -1179,7 +1181,7 @@ static struct config_bool ConfigureNamesBool[] =
 		NULL, NULL, NULL
 	},
 	{
-		{"geqo", PGC_USERSET, QUERY_TUNING_GEQO,
+		{"geqo", PGC_SUSET, QUERY_TUNING_GEQO,
 			gettext_noop("Enables genetic query optimization."),
 			gettext_noop("This algorithm attempts to do planning without "
 						 "exhaustive searching."),
@@ -1401,7 +1403,7 @@ static struct config_bool ConfigureNamesBool[] =
 	},
 
 	{
-		{"exit_on_error", PGC_USERSET, ERROR_HANDLING_OPTIONS,
+		{"exit_on_error", PGC_SUSET, ERROR_HANDLING_OPTIONS,
 			gettext_noop("Terminate session on any error."),
 			NULL
 		},
@@ -1439,7 +1441,7 @@ static struct config_bool ConfigureNamesBool[] =
 		NULL, NULL, NULL
 	},
 	{
-		{"debug_print_parse", PGC_USERSET, LOGGING_WHAT,
+		{"debug_print_parse", PGC_SUSET, LOGGING_WHAT,
 			gettext_noop("Logs each query's parse tree."),
 			NULL
 		},
@@ -1448,7 +1450,7 @@ static struct config_bool ConfigureNamesBool[] =
 		NULL, NULL, NULL
 	},
 	{
-		{"debug_print_rewritten", PGC_USERSET, LOGGING_WHAT,
+		{"debug_print_rewritten", PGC_SUSET, LOGGING_WHAT,
 			gettext_noop("Logs each query's rewritten parse tree."),
 			NULL
 		},
@@ -1457,7 +1459,7 @@ static struct config_bool ConfigureNamesBool[] =
 		NULL, NULL, NULL
 	},
 	{
-		{"debug_print_plan", PGC_USERSET, LOGGING_WHAT,
+		{"debug_print_plan", PGC_SUSET, LOGGING_WHAT,
 			gettext_noop("Logs each query's execution plan."),
 			NULL
 		},
@@ -1466,7 +1468,7 @@ static struct config_bool ConfigureNamesBool[] =
 		NULL, NULL, NULL
 	},
 	{
-		{"debug_pretty_print", PGC_USERSET, LOGGING_WHAT,
+		{"debug_pretty_print", PGC_SUSET, LOGGING_WHAT,
 			gettext_noop("Indents parse and plan tree displays."),
 			NULL
 		},
@@ -1587,7 +1589,7 @@ static struct config_bool ConfigureNamesBool[] =
 	},
 
 	{
-		{"trace_notify", PGC_USERSET, DEVELOPER_OPTIONS,
+		{"trace_notify", PGC_SUSET, DEVELOPER_OPTIONS,
 			gettext_noop("Generates debugging output for LISTEN and NOTIFY."),
 			NULL,
 			GUC_NOT_IN_SAMPLE
@@ -1671,7 +1673,7 @@ static struct config_bool ConfigureNamesBool[] =
 		NULL, NULL, NULL
 	},
 	{
-		{"transform_null_equals", PGC_USERSET, COMPAT_OPTIONS_CLIENT,
+		{"transform_null_equals", PGC_SUSET, COMPAT_OPTIONS_CLIENT,
 			gettext_noop("Treats \"expr=NULL\" as \"expr IS NULL\"."),
 			gettext_noop("When turned on, expressions of the form expr = NULL "
 						 "(or NULL = expr) are treated as expr IS NULL, that is, they "
@@ -1693,7 +1695,7 @@ static struct config_bool ConfigureNamesBool[] =
 		NULL, NULL, NULL
 	},
 	{
-		{"default_transaction_read_only", PGC_USERSET, CLIENT_CONN_STATEMENT,
+		{"default_transaction_read_only", PGC_SUSET, CLIENT_CONN_STATEMENT,
 			gettext_noop("Sets the default read-only status of new transactions."),
 			NULL,
 			GUC_REPORT
@@ -1703,7 +1705,7 @@ static struct config_bool ConfigureNamesBool[] =
 		NULL, NULL, NULL
 	},
 	{
-		{"transaction_read_only", PGC_USERSET, CLIENT_CONN_STATEMENT,
+		{"transaction_read_only", PGC_SUSET, CLIENT_CONN_STATEMENT,
 			gettext_noop("Sets the current transaction's read-only status."),
 			NULL,
 			GUC_NO_RESET_ALL | GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE
@@ -1713,7 +1715,7 @@ static struct config_bool ConfigureNamesBool[] =
 		check_transaction_read_only, NULL, NULL
 	},
 	{
-		{"default_transaction_deferrable", PGC_USERSET, CLIENT_CONN_STATEMENT,
+		{"default_transaction_deferrable", PGC_SUSET, CLIENT_CONN_STATEMENT,
 			gettext_noop("Sets the default deferrable status of new transactions."),
 			NULL
 		},
@@ -1722,7 +1724,7 @@ static struct config_bool ConfigureNamesBool[] =
 		NULL, NULL, NULL
 	},
 	{
-		{"transaction_deferrable", PGC_USERSET, CLIENT_CONN_STATEMENT,
+		{"transaction_deferrable", PGC_SUSET, CLIENT_CONN_STATEMENT,
 			gettext_noop("Whether to defer a read-only serializable transaction until it can be executed with no possible serialization failures."),
 			NULL,
 			GUC_NO_RESET_ALL | GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE
@@ -1732,7 +1734,7 @@ static struct config_bool ConfigureNamesBool[] =
 		check_transaction_deferrable, NULL, NULL
 	},
 	{
-		{"row_security", PGC_USERSET, CLIENT_CONN_STATEMENT,
+		{"row_security", PGC_SUSET, CLIENT_CONN_STATEMENT,
 			gettext_noop("Enable row security."),
 			gettext_noop("When enabled, row security will be applied to all users.")
 		},
@@ -1741,7 +1743,7 @@ static struct config_bool ConfigureNamesBool[] =
 		NULL, NULL, NULL
 	},
 	{
-		{"check_function_bodies", PGC_USERSET, CLIENT_CONN_STATEMENT,
+		{"check_function_bodies", PGC_SUSET, CLIENT_CONN_STATEMENT,
 			gettext_noop("Check routine bodies during CREATE FUNCTION and CREATE PROCEDURE."),
 			NULL
 		},
@@ -1750,7 +1752,7 @@ static struct config_bool ConfigureNamesBool[] =
 		NULL, NULL, NULL
 	},
 	{
-		{"array_nulls", PGC_USERSET, COMPAT_OPTIONS_PREVIOUS,
+		{"array_nulls", PGC_SUSET, COMPAT_OPTIONS_PREVIOUS,
 			gettext_noop("Enable input of NULL elements in arrays."),
 			gettext_noop("When turned on, unquoted NULL in an array input "
 						 "value means a null value; "
@@ -1767,7 +1769,7 @@ static struct config_bool ConfigureNamesBool[] =
 	 * avoid unnecessarily breaking older dump files.
 	 */
 	{
-		{"default_with_oids", PGC_USERSET, COMPAT_OPTIONS_PREVIOUS,
+		{"default_with_oids", PGC_SUSET, COMPAT_OPTIONS_PREVIOUS,
 			gettext_noop("WITH OIDS is no longer supported; this can only be false."),
 			NULL,
 			GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE
@@ -1797,7 +1799,7 @@ static struct config_bool ConfigureNamesBool[] =
 
 #ifdef TRACE_SORT
 	{
-		{"trace_sort", PGC_USERSET, DEVELOPER_OPTIONS,
+		{"trace_sort", PGC_SUSET, DEVELOPER_OPTIONS,
 			gettext_noop("Emit information about resource usage in sorting."),
 			NULL,
 			GUC_NOT_IN_SAMPLE
@@ -1811,7 +1813,7 @@ static struct config_bool ConfigureNamesBool[] =
 #ifdef TRACE_SYNCSCAN
 	/* this is undocumented because not exposed in a standard build */
 	{
-		{"trace_syncscan", PGC_USERSET, DEVELOPER_OPTIONS,
+		{"trace_syncscan", PGC_SUSET, DEVELOPER_OPTIONS,
 			gettext_noop("Generate debugging output for synchronized scanning."),
 			NULL,
 			GUC_NOT_IN_SAMPLE
@@ -1826,7 +1828,7 @@ static struct config_bool ConfigureNamesBool[] =
 	/* this is undocumented because not exposed in a standard build */
 	{
 		{
-			"optimize_bounded_sort", PGC_USERSET, QUERY_TUNING_METHOD,
+			"optimize_bounded_sort", PGC_SUSET, QUERY_TUNING_METHOD,
 			gettext_noop("Enable bounded sorting using heap sort."),
 			NULL,
 			GUC_NOT_IN_SAMPLE | GUC_EXPLAIN
@@ -1872,7 +1874,7 @@ static struct config_bool ConfigureNamesBool[] =
 	},
 
 	{
-		{"escape_string_warning", PGC_USERSET, COMPAT_OPTIONS_PREVIOUS,
+		{"escape_string_warning", PGC_SUSET, COMPAT_OPTIONS_PREVIOUS,
 			gettext_noop("Warn about backslash escapes in ordinary string literals."),
 			NULL
 		},
@@ -1882,7 +1884,7 @@ static struct config_bool ConfigureNamesBool[] =
 	},
 
 	{
-		{"standard_conforming_strings", PGC_USERSET, COMPAT_OPTIONS_PREVIOUS,
+		{"standard_conforming_strings", PGC_SUSET, COMPAT_OPTIONS_PREVIOUS,
 			gettext_noop("Causes '...' strings to treat backslashes literally."),
 			NULL,
 			GUC_REPORT
@@ -1893,7 +1895,7 @@ static struct config_bool ConfigureNamesBool[] =
 	},
 
 	{
-		{"synchronize_seqscans", PGC_USERSET, COMPAT_OPTIONS_PREVIOUS,
+		{"synchronize_seqscans", PGC_SUSET, COMPAT_OPTIONS_PREVIOUS,
 			gettext_noop("Enable synchronized sequential scans."),
 			NULL
 		},
@@ -1989,7 +1991,7 @@ static struct config_bool ConfigureNamesBool[] =
 	},
 
 	{
-		{"quote_all_identifiers", PGC_USERSET, COMPAT_OPTIONS_PREVIOUS,
+		{"quote_all_identifiers", PGC_SUSET, COMPAT_OPTIONS_PREVIOUS,
 			gettext_noop("When generating SQL fragments, quote all identifiers."),
 			NULL,
 		},
@@ -2030,7 +2032,7 @@ static struct config_bool ConfigureNamesBool[] =
 	},
 
 	{
-		{"parallel_leader_participation", PGC_USERSET, RESOURCES_ASYNCHRONOUS,
+		{"parallel_leader_participation", PGC_SUSET, RESOURCES_ASYNCHRONOUS,
 			gettext_noop("Controls whether Gather and Gather Merge also run subplans."),
 			gettext_noop("Should gather nodes also run subplans or just gather tuples?"),
 			GUC_EXPLAIN
@@ -2041,7 +2043,7 @@ static struct config_bool ConfigureNamesBool[] =
 	},
 
 	{
-		{"jit", PGC_USERSET, QUERY_TUNING_OTHER,
+		{"jit", PGC_SUSET, QUERY_TUNING_OTHER,
 			gettext_noop("Allow JIT compilation."),
 			NULL,
 			GUC_EXPLAIN
@@ -2080,7 +2082,7 @@ static struct config_bool ConfigureNamesBool[] =
 	},
 
 	{
-		{"jit_expressions", PGC_USERSET, DEVELOPER_OPTIONS,
+		{"jit_expressions", PGC_SUSET, DEVELOPER_OPTIONS,
 			gettext_noop("Allow JIT compilation of expressions."),
 			NULL,
 			GUC_NOT_IN_SAMPLE
@@ -2108,7 +2110,7 @@ static struct config_bool ConfigureNamesBool[] =
 	},
 
 	{
-		{"jit_tuple_deforming", PGC_USERSET, DEVELOPER_OPTIONS,
+		{"jit_tuple_deforming", PGC_SUSET, DEVELOPER_OPTIONS,
 			gettext_noop("Allow JIT compilation of tuple deforming."),
 			NULL,
 			GUC_NOT_IN_SAMPLE
@@ -2168,7 +2170,7 @@ static struct config_int ConfigureNamesInt[] =
 		NULL, NULL, NULL
 	},
 	{
-		{"default_statistics_target", PGC_USERSET, QUERY_TUNING_OTHER,
+		{"default_statistics_target", PGC_SUSET, QUERY_TUNING_OTHER,
 			gettext_noop("Sets the default statistics target."),
 			gettext_noop("This applies to table columns that have not had a "
 						 "column-specific target set via ALTER TABLE SET STATISTICS.")
@@ -2178,7 +2180,7 @@ static struct config_int ConfigureNamesInt[] =
 		NULL, NULL, NULL
 	},
 	{
-		{"from_collapse_limit", PGC_USERSET, QUERY_TUNING_OTHER,
+		{"from_collapse_limit", PGC_SUSET, QUERY_TUNING_OTHER,
 			gettext_noop("Sets the FROM-list size beyond which subqueries "
 						 "are not collapsed."),
 			gettext_noop("The planner will merge subqueries into upper "
@@ -2191,7 +2193,7 @@ static struct config_int ConfigureNamesInt[] =
 		NULL, NULL, NULL
 	},
 	{
-		{"join_collapse_limit", PGC_USERSET, QUERY_TUNING_OTHER,
+		{"join_collapse_limit", PGC_SUSET, QUERY_TUNING_OTHER,
 			gettext_noop("Sets the FROM-list size beyond which JOIN "
 						 "constructs are not flattened."),
 			gettext_noop("The planner will flatten explicit JOIN "
@@ -2204,7 +2206,7 @@ static struct config_int ConfigureNamesInt[] =
 		NULL, NULL, NULL
 	},
 	{
-		{"geqo_threshold", PGC_USERSET, QUERY_TUNING_GEQO,
+		{"geqo_threshold", PGC_SUSET, QUERY_TUNING_GEQO,
 			gettext_noop("Sets the threshold of FROM items beyond which GEQO is used."),
 			NULL,
 			GUC_EXPLAIN
@@ -2214,7 +2216,7 @@ static struct config_int ConfigureNamesInt[] =
 		NULL, NULL, NULL
 	},
 	{
-		{"geqo_effort", PGC_USERSET, QUERY_TUNING_GEQO,
+		{"geqo_effort", PGC_SUSET, QUERY_TUNING_GEQO,
 			gettext_noop("GEQO: effort is used to set the default for other GEQO parameters."),
 			NULL,
 			GUC_EXPLAIN
@@ -2224,7 +2226,7 @@ static struct config_int ConfigureNamesInt[] =
 		NULL, NULL, NULL
 	},
 	{
-		{"geqo_pool_size", PGC_USERSET, QUERY_TUNING_GEQO,
+		{"geqo_pool_size", PGC_SUSET, QUERY_TUNING_GEQO,
 			gettext_noop("GEQO: number of individuals in the population."),
 			gettext_noop("Zero selects a suitable default value."),
 			GUC_EXPLAIN
@@ -2234,7 +2236,7 @@ static struct config_int ConfigureNamesInt[] =
 		NULL, NULL, NULL
 	},
 	{
-		{"geqo_generations", PGC_USERSET, QUERY_TUNING_GEQO,
+		{"geqo_generations", PGC_SUSET, QUERY_TUNING_GEQO,
 			gettext_noop("GEQO: number of iterations of the algorithm."),
 			gettext_noop("Zero selects a suitable default value."),
 			GUC_EXPLAIN
@@ -2381,7 +2383,7 @@ static struct config_int ConfigureNamesInt[] =
 	},
 
 	{
-		{"temp_buffers", PGC_USERSET, RESOURCES_MEM,
+		{"temp_buffers", PGC_SUSET, RESOURCES_MEM,
 			gettext_noop("Sets the maximum number of temporary buffers used by each session."),
 			NULL,
 			GUC_UNIT_BLOCKS | GUC_EXPLAIN
@@ -2446,7 +2448,7 @@ static struct config_int ConfigureNamesInt[] =
 	},
 
 	{
-		{"work_mem", PGC_USERSET, RESOURCES_MEM,
+		{"work_mem", PGC_SUSET, RESOURCES_MEM,
 			gettext_noop("Sets the maximum memory to be used for query workspaces."),
 			gettext_noop("This much memory can be used by each internal "
 						 "sort operation and hash table before switching to "
@@ -2459,7 +2461,7 @@ static struct config_int ConfigureNamesInt[] =
 	},
 
 	{
-		{"maintenance_work_mem", PGC_USERSET, RESOURCES_MEM,
+		{"maintenance_work_mem", PGC_SUSET, RESOURCES_MEM,
 			gettext_noop("Sets the maximum memory to be used for maintenance operations."),
 			gettext_noop("This includes operations such as VACUUM and CREATE INDEX."),
 			GUC_UNIT_KB
@@ -2470,7 +2472,7 @@ static struct config_int ConfigureNamesInt[] =
 	},
 
 	{
-		{"logical_decoding_work_mem", PGC_USERSET, RESOURCES_MEM,
+		{"logical_decoding_work_mem", PGC_SUSET, RESOURCES_MEM,
 			gettext_noop("Sets the maximum memory to be used for logical decoding."),
 			gettext_noop("This much memory can be used by each internal "
 						 "reorder buffer before spilling to disk."),
@@ -2509,7 +2511,7 @@ static struct config_int ConfigureNamesInt[] =
 	},
 
 	{
-		{"vacuum_cost_page_hit", PGC_USERSET, RESOURCES_VACUUM_DELAY,
+		{"vacuum_cost_page_hit", PGC_SUSET, RESOURCES_VACUUM_DELAY,
 			gettext_noop("Vacuum cost for a page found in the buffer cache."),
 			NULL
 		},
@@ -2519,7 +2521,7 @@ static struct config_int ConfigureNamesInt[] =
 	},
 
 	{
-		{"vacuum_cost_page_miss", PGC_USERSET, RESOURCES_VACUUM_DELAY,
+		{"vacuum_cost_page_miss", PGC_SUSET, RESOURCES_VACUUM_DELAY,
 			gettext_noop("Vacuum cost for a page not found in the buffer cache."),
 			NULL
 		},
@@ -2529,7 +2531,7 @@ static struct config_int ConfigureNamesInt[] =
 	},
 
 	{
-		{"vacuum_cost_page_dirty", PGC_USERSET, RESOURCES_VACUUM_DELAY,
+		{"vacuum_cost_page_dirty", PGC_SUSET, RESOURCES_VACUUM_DELAY,
 			gettext_noop("Vacuum cost for a page dirtied by vacuum."),
 			NULL
 		},
@@ -2539,7 +2541,7 @@ static struct config_int ConfigureNamesInt[] =
 	},
 
 	{
-		{"vacuum_cost_limit", PGC_USERSET, RESOURCES_VACUUM_DELAY,
+		{"vacuum_cost_limit", PGC_SUSET, RESOURCES_VACUUM_DELAY,
 			gettext_noop("Vacuum cost amount available before napping."),
 			NULL
 		},
@@ -2605,7 +2607,7 @@ static struct config_int ConfigureNamesInt[] =
 #endif
 
 	{
-		{"statement_timeout", PGC_USERSET, CLIENT_CONN_STATEMENT,
+		{"statement_timeout", PGC_SUSET, CLIENT_CONN_STATEMENT,
 			gettext_noop("Sets the maximum allowed duration of any statement."),
 			gettext_noop("A value of 0 turns off the timeout."),
 			GUC_UNIT_MS
@@ -2616,7 +2618,7 @@ static struct config_int ConfigureNamesInt[] =
 	},
 
 	{
-		{"lock_timeout", PGC_USERSET, CLIENT_CONN_STATEMENT,
+		{"lock_timeout", PGC_SUSET, CLIENT_CONN_STATEMENT,
 			gettext_noop("Sets the maximum allowed duration of any wait for a lock."),
 			gettext_noop("A value of 0 turns off the timeout."),
 			GUC_UNIT_MS
@@ -2627,7 +2629,7 @@ static struct config_int ConfigureNamesInt[] =
 	},
 
 	{
-		{"idle_in_transaction_session_timeout", PGC_USERSET, CLIENT_CONN_STATEMENT,
+		{"idle_in_transaction_session_timeout", PGC_SUSET, CLIENT_CONN_STATEMENT,
 			gettext_noop("Sets the maximum allowed idle time between queries, when in a transaction."),
 			gettext_noop("A value of 0 turns off the timeout."),
 			GUC_UNIT_MS
@@ -2638,7 +2640,7 @@ static struct config_int ConfigureNamesInt[] =
 	},
 
 	{
-		{"idle_session_timeout", PGC_USERSET, CLIENT_CONN_STATEMENT,
+		{"idle_session_timeout", PGC_SUSET, CLIENT_CONN_STATEMENT,
 			gettext_noop("Sets the maximum allowed idle time between queries, when not in a transaction."),
 			gettext_noop("A value of 0 turns off the timeout."),
 			GUC_UNIT_MS
@@ -2649,7 +2651,7 @@ static struct config_int ConfigureNamesInt[] =
 	},
 
 	{
-		{"vacuum_freeze_min_age", PGC_USERSET, CLIENT_CONN_STATEMENT,
+		{"vacuum_freeze_min_age", PGC_SUSET, CLIENT_CONN_STATEMENT,
 			gettext_noop("Minimum age at which VACUUM should freeze a table row."),
 			NULL
 		},
@@ -2659,7 +2661,7 @@ static struct config_int ConfigureNamesInt[] =
 	},
 
 	{
-		{"vacuum_freeze_table_age", PGC_USERSET, CLIENT_CONN_STATEMENT,
+		{"vacuum_freeze_table_age", PGC_SUSET, CLIENT_CONN_STATEMENT,
 			gettext_noop("Age at which VACUUM should scan whole table to freeze tuples."),
 			NULL
 		},
@@ -2669,7 +2671,7 @@ static struct config_int ConfigureNamesInt[] =
 	},
 
 	{
-		{"vacuum_multixact_freeze_min_age", PGC_USERSET, CLIENT_CONN_STATEMENT,
+		{"vacuum_multixact_freeze_min_age", PGC_SUSET, CLIENT_CONN_STATEMENT,
 			gettext_noop("Minimum age at which VACUUM should freeze a MultiXactId in a table row."),
 			NULL
 		},
@@ -2679,7 +2681,7 @@ static struct config_int ConfigureNamesInt[] =
 	},
 
 	{
-		{"vacuum_multixact_freeze_table_age", PGC_USERSET, CLIENT_CONN_STATEMENT,
+		{"vacuum_multixact_freeze_table_age", PGC_SUSET, CLIENT_CONN_STATEMENT,
 			gettext_noop("Multixact age at which VACUUM should scan whole table to freeze tuples."),
 			NULL
 		},
@@ -2698,7 +2700,7 @@ static struct config_int ConfigureNamesInt[] =
 		NULL, NULL, NULL
 	},
 	{
-		{"vacuum_failsafe_age", PGC_USERSET, CLIENT_CONN_STATEMENT,
+		{"vacuum_failsafe_age", PGC_SUSET, CLIENT_CONN_STATEMENT,
 			gettext_noop("Age at which VACUUM should trigger failsafe to avoid a wraparound outage."),
 			NULL
 		},
@@ -2707,7 +2709,7 @@ static struct config_int ConfigureNamesInt[] =
 		NULL, NULL, NULL
 	},
 	{
-		{"vacuum_multixact_failsafe_age", PGC_USERSET, CLIENT_CONN_STATEMENT,
+		{"vacuum_multixact_failsafe_age", PGC_SUSET, CLIENT_CONN_STATEMENT,
 			gettext_noop("Multixact age at which VACUUM should trigger failsafe to avoid a wraparound outage."),
 			NULL
 		},
@@ -2895,7 +2897,7 @@ static struct config_int ConfigureNamesInt[] =
 	},
 
 	{
-		{"wal_skip_threshold", PGC_USERSET, WAL_SETTINGS,
+		{"wal_skip_threshold", PGC_SUSET, WAL_SETTINGS,
 			gettext_noop("Minimum size of new file to fsync instead of writing WAL."),
 			NULL,
 			GUC_UNIT_KB
@@ -2940,7 +2942,7 @@ static struct config_int ConfigureNamesInt[] =
 	},
 
 	{
-		{"wal_sender_timeout", PGC_USERSET, REPLICATION_SENDING,
+		{"wal_sender_timeout", PGC_SUSET, REPLICATION_SENDING,
 			gettext_noop("Sets the maximum time to wait for WAL replication."),
 			NULL,
 			GUC_UNIT_MS
@@ -2963,7 +2965,7 @@ static struct config_int ConfigureNamesInt[] =
 	},
 
 	{
-		{"commit_siblings", PGC_USERSET, WAL_SETTINGS,
+		{"commit_siblings", PGC_SUSET, WAL_SETTINGS,
 			gettext_noop("Sets the minimum number of concurrent open transactions "
 						 "required before performing commit_delay."),
 			NULL
@@ -2974,7 +2976,7 @@ static struct config_int ConfigureNamesInt[] =
 	},
 
 	{
-		{"extra_float_digits", PGC_USERSET, CLIENT_CONN_LOCALE,
+		{"extra_float_digits", PGC_SUSET, CLIENT_CONN_LOCALE,
 			gettext_noop("Sets the number of digits displayed for floating-point values."),
 			gettext_noop("This affects real, double precision, and geometric data types. "
 						 "A zero or negative parameter value is added to the standard "
@@ -3036,7 +3038,7 @@ static struct config_int ConfigureNamesInt[] =
 	},
 
 	{
-		{"log_parameter_max_length_on_error", PGC_USERSET, LOGGING_WHAT,
+		{"log_parameter_max_length_on_error", PGC_SUSET, LOGGING_WHAT,
 			gettext_noop("Sets the maximum length in bytes of data logged for bind "
 						 "parameter values when logging statements, on error."),
 			gettext_noop("-1 to print values in full."),
@@ -3081,7 +3083,7 @@ static struct config_int ConfigureNamesInt[] =
 
 	{
 		{"effective_io_concurrency",
-			PGC_USERSET,
+			PGC_SUSET,
 			RESOURCES_ASYNCHRONOUS,
 			gettext_noop("Number of simultaneous requests that can be handled efficiently by the disk subsystem."),
 			NULL,
@@ -3099,7 +3101,7 @@ static struct config_int ConfigureNamesInt[] =
 
 	{
 		{"maintenance_io_concurrency",
-			PGC_USERSET,
+			PGC_SUSET,
 			RESOURCES_ASYNCHRONOUS,
 			gettext_noop("A variant of effective_io_concurrency that is used for maintenance work."),
 			NULL,
@@ -3116,7 +3118,7 @@ static struct config_int ConfigureNamesInt[] =
 	},
 
 	{
-		{"backend_flush_after", PGC_USERSET, RESOURCES_ASYNCHRONOUS,
+		{"backend_flush_after", PGC_SUSET, RESOURCES_ASYNCHRONOUS,
 			gettext_noop("Number of pages after which previously performed writes are flushed to disk."),
 			NULL,
 			GUC_UNIT_BLOCKS
@@ -3348,7 +3350,7 @@ static struct config_int ConfigureNamesInt[] =
 	},
 
 	{
-		{"max_parallel_maintenance_workers", PGC_USERSET, RESOURCES_ASYNCHRONOUS,
+		{"max_parallel_maintenance_workers", PGC_SUSET, RESOURCES_ASYNCHRONOUS,
 			gettext_noop("Sets the maximum number of parallel processes per maintenance operation."),
 			NULL
 		},
@@ -3358,7 +3360,7 @@ static struct config_int ConfigureNamesInt[] =
 	},
 
 	{
-		{"max_parallel_workers_per_gather", PGC_USERSET, RESOURCES_ASYNCHRONOUS,
+		{"max_parallel_workers_per_gather", PGC_SUSET, RESOURCES_ASYNCHRONOUS,
 			gettext_noop("Sets the maximum number of parallel processes per executor node."),
 			NULL,
 			GUC_EXPLAIN
@@ -3369,7 +3371,7 @@ static struct config_int ConfigureNamesInt[] =
 	},
 
 	{
-		{"max_parallel_workers", PGC_USERSET, RESOURCES_ASYNCHRONOUS,
+		{"max_parallel_workers", PGC_SUSET, RESOURCES_ASYNCHRONOUS,
 			gettext_noop("Sets the maximum number of parallel workers that can be active at one time."),
 			NULL,
 			GUC_EXPLAIN
@@ -3402,7 +3404,7 @@ static struct config_int ConfigureNamesInt[] =
 	},
 
 	{
-		{"tcp_keepalives_idle", PGC_USERSET, CONN_AUTH_SETTINGS,
+		{"tcp_keepalives_idle", PGC_SUSET, CONN_AUTH_SETTINGS,
 			gettext_noop("Time between issuing TCP keepalives."),
 			gettext_noop("A value of 0 uses the system default."),
 			GUC_UNIT_S
@@ -3413,7 +3415,7 @@ static struct config_int ConfigureNamesInt[] =
 	},
 
 	{
-		{"tcp_keepalives_interval", PGC_USERSET, CONN_AUTH_SETTINGS,
+		{"tcp_keepalives_interval", PGC_SUSET, CONN_AUTH_SETTINGS,
 			gettext_noop("Time between TCP keepalive retransmits."),
 			gettext_noop("A value of 0 uses the system default."),
 			GUC_UNIT_S
@@ -3424,7 +3426,7 @@ static struct config_int ConfigureNamesInt[] =
 	},
 
 	{
-		{"ssl_renegotiation_limit", PGC_USERSET, COMPAT_OPTIONS_PREVIOUS,
+		{"ssl_renegotiation_limit", PGC_SUSET, COMPAT_OPTIONS_PREVIOUS,
 			gettext_noop("SSL renegotiation is no longer supported; this can only be 0."),
 			NULL,
 			GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE,
@@ -3435,7 +3437,7 @@ static struct config_int ConfigureNamesInt[] =
 	},
 
 	{
-		{"tcp_keepalives_count", PGC_USERSET, CONN_AUTH_SETTINGS,
+		{"tcp_keepalives_count", PGC_SUSET, CONN_AUTH_SETTINGS,
 			gettext_noop("Maximum number of TCP keepalive retransmits."),
 			gettext_noop("This controls the number of consecutive keepalive retransmits that can be "
 						 "lost before a connection is considered dead. A value of 0 uses the "
@@ -3447,7 +3449,7 @@ static struct config_int ConfigureNamesInt[] =
 	},
 
 	{
-		{"gin_fuzzy_search_limit", PGC_USERSET, CLIENT_CONN_OTHER,
+		{"gin_fuzzy_search_limit", PGC_SUSET, CLIENT_CONN_OTHER,
 			gettext_noop("Sets the maximum allowed result for exact search by GIN."),
 			NULL,
 			0
@@ -3458,7 +3460,7 @@ static struct config_int ConfigureNamesInt[] =
 	},
 
 	{
-		{"effective_cache_size", PGC_USERSET, QUERY_TUNING_COST,
+		{"effective_cache_size", PGC_SUSET, QUERY_TUNING_COST,
 			gettext_noop("Sets the planner's assumption about the total size of the data caches."),
 			gettext_noop("That is, the total size of the caches (kernel cache and shared buffers) used for PostgreSQL data files. "
 						 "This is measured in disk pages, which are normally 8 kB each."),
@@ -3470,7 +3472,7 @@ static struct config_int ConfigureNamesInt[] =
 	},
 
 	{
-		{"min_parallel_table_scan_size", PGC_USERSET, QUERY_TUNING_COST,
+		{"min_parallel_table_scan_size", PGC_SUSET, QUERY_TUNING_COST,
 			gettext_noop("Sets the minimum amount of table data for a parallel scan."),
 			gettext_noop("If the planner estimates that it will read a number of table pages too small to reach this limit, a parallel scan will not be considered."),
 			GUC_UNIT_BLOCKS | GUC_EXPLAIN,
@@ -3481,7 +3483,7 @@ static struct config_int ConfigureNamesInt[] =
 	},
 
 	{
-		{"min_parallel_index_scan_size", PGC_USERSET, QUERY_TUNING_COST,
+		{"min_parallel_index_scan_size", PGC_SUSET, QUERY_TUNING_COST,
 			gettext_noop("Sets the minimum amount of index data for a parallel scan."),
 			gettext_noop("If the planner estimates that it will read a number of index pages too small to reach this limit, a parallel scan will not be considered."),
 			GUC_UNIT_BLOCKS | GUC_EXPLAIN,
@@ -3526,7 +3528,7 @@ static struct config_int ConfigureNamesInt[] =
 	},
 
 	{
-		{"gin_pending_list_limit", PGC_USERSET, CLIENT_CONN_STATEMENT,
+		{"gin_pending_list_limit", PGC_SUSET, CLIENT_CONN_STATEMENT,
 			gettext_noop("Sets the maximum size of the pending list for GIN index."),
 			NULL,
 			GUC_UNIT_KB
@@ -3537,7 +3539,7 @@ static struct config_int ConfigureNamesInt[] =
 	},
 
 	{
-		{"tcp_user_timeout", PGC_USERSET, CONN_AUTH_SETTINGS,
+		{"tcp_user_timeout", PGC_SUSET, CONN_AUTH_SETTINGS,
 			gettext_noop("TCP user timeout."),
 			gettext_noop("A value of 0 uses the system default."),
 			GUC_UNIT_MS
@@ -3582,7 +3584,7 @@ static struct config_int ConfigureNamesInt[] =
 	},
 
 	{
-		{"client_connection_check_interval", PGC_USERSET, CONN_AUTH_SETTINGS,
+		{"client_connection_check_interval", PGC_SUSET, CONN_AUTH_SETTINGS,
 			gettext_noop("Sets the time interval between checks for disconnection while running queries."),
 			NULL,
 			GUC_UNIT_MS
@@ -3614,7 +3616,7 @@ static struct config_int ConfigureNamesInt[] =
 static struct config_real ConfigureNamesReal[] =
 {
 	{
-		{"seq_page_cost", PGC_USERSET, QUERY_TUNING_COST,
+		{"seq_page_cost", PGC_SUSET, QUERY_TUNING_COST,
 			gettext_noop("Sets the planner's estimate of the cost of a "
 						 "sequentially fetched disk page."),
 			NULL,
@@ -3625,7 +3627,7 @@ static struct config_real ConfigureNamesReal[] =
 		NULL, NULL, NULL
 	},
 	{
-		{"random_page_cost", PGC_USERSET, QUERY_TUNING_COST,
+		{"random_page_cost", PGC_SUSET, QUERY_TUNING_COST,
 			gettext_noop("Sets the planner's estimate of the cost of a "
 						 "nonsequentially fetched disk page."),
 			NULL,
@@ -3636,7 +3638,7 @@ static struct config_real ConfigureNamesReal[] =
 		NULL, NULL, NULL
 	},
 	{
-		{"cpu_tuple_cost", PGC_USERSET, QUERY_TUNING_COST,
+		{"cpu_tuple_cost", PGC_SUSET, QUERY_TUNING_COST,
 			gettext_noop("Sets the planner's estimate of the cost of "
 						 "processing each tuple (row)."),
 			NULL,
@@ -3647,7 +3649,7 @@ static struct config_real ConfigureNamesReal[] =
 		NULL, NULL, NULL
 	},
 	{
-		{"cpu_index_tuple_cost", PGC_USERSET, QUERY_TUNING_COST,
+		{"cpu_index_tuple_cost", PGC_SUSET, QUERY_TUNING_COST,
 			gettext_noop("Sets the planner's estimate of the cost of "
 						 "processing each index entry during an index scan."),
 			NULL,
@@ -3658,7 +3660,7 @@ static struct config_real ConfigureNamesReal[] =
 		NULL, NULL, NULL
 	},
 	{
-		{"cpu_operator_cost", PGC_USERSET, QUERY_TUNING_COST,
+		{"cpu_operator_cost", PGC_SUSET, QUERY_TUNING_COST,
 			gettext_noop("Sets the planner's estimate of the cost of "
 						 "processing each operator or function call."),
 			NULL,
@@ -3669,7 +3671,7 @@ static struct config_real ConfigureNamesReal[] =
 		NULL, NULL, NULL
 	},
 	{
-		{"parallel_tuple_cost", PGC_USERSET, QUERY_TUNING_COST,
+		{"parallel_tuple_cost", PGC_SUSET, QUERY_TUNING_COST,
 			gettext_noop("Sets the planner's estimate of the cost of "
 						 "passing each tuple (row) from worker to leader backend."),
 			NULL,
@@ -3680,7 +3682,7 @@ static struct config_real ConfigureNamesReal[] =
 		NULL, NULL, NULL
 	},
 	{
-		{"parallel_setup_cost", PGC_USERSET, QUERY_TUNING_COST,
+		{"parallel_setup_cost", PGC_SUSET, QUERY_TUNING_COST,
 			gettext_noop("Sets the planner's estimate of the cost of "
 						 "starting up worker processes for parallel query."),
 			NULL,
@@ -3692,7 +3694,7 @@ static struct config_real ConfigureNamesReal[] =
 	},
 
 	{
-		{"jit_above_cost", PGC_USERSET, QUERY_TUNING_COST,
+		{"jit_above_cost", PGC_SUSET, QUERY_TUNING_COST,
 			gettext_noop("Perform JIT compilation if query is more expensive."),
 			gettext_noop("-1 disables JIT compilation."),
 			GUC_EXPLAIN
@@ -3703,7 +3705,7 @@ static struct config_real ConfigureNamesReal[] =
 	},
 
 	{
-		{"jit_optimize_above_cost", PGC_USERSET, QUERY_TUNING_COST,
+		{"jit_optimize_above_cost", PGC_SUSET, QUERY_TUNING_COST,
 			gettext_noop("Optimize JIT-compiled functions if query is more expensive."),
 			gettext_noop("-1 disables optimization."),
 			GUC_EXPLAIN
@@ -3714,7 +3716,7 @@ static struct config_real ConfigureNamesReal[] =
 	},
 
 	{
-		{"jit_inline_above_cost", PGC_USERSET, QUERY_TUNING_COST,
+		{"jit_inline_above_cost", PGC_SUSET, QUERY_TUNING_COST,
 			gettext_noop("Perform JIT inlining if query is more expensive."),
 			gettext_noop("-1 disables inlining."),
 			GUC_EXPLAIN
@@ -3725,7 +3727,7 @@ static struct config_real ConfigureNamesReal[] =
 	},
 
 	{
-		{"cursor_tuple_fraction", PGC_USERSET, QUERY_TUNING_OTHER,
+		{"cursor_tuple_fraction", PGC_SUSET, QUERY_TUNING_OTHER,
 			gettext_noop("Sets the planner's estimate of the fraction of "
 						 "a cursor's rows that will be retrieved."),
 			NULL,
@@ -3737,7 +3739,7 @@ static struct config_real ConfigureNamesReal[] =
 	},
 
 	{
-		{"geqo_selection_bias", PGC_USERSET, QUERY_TUNING_GEQO,
+		{"geqo_selection_bias", PGC_SUSET, QUERY_TUNING_GEQO,
 			gettext_noop("GEQO: selective pressure within the population."),
 			NULL,
 			GUC_EXPLAIN
@@ -3748,7 +3750,7 @@ static struct config_real ConfigureNamesReal[] =
 		NULL, NULL, NULL
 	},
 	{
-		{"geqo_seed", PGC_USERSET, QUERY_TUNING_GEQO,
+		{"geqo_seed", PGC_SUSET, QUERY_TUNING_GEQO,
 			gettext_noop("GEQO: seed for random path selection."),
 			NULL,
 			GUC_EXPLAIN
@@ -3759,7 +3761,7 @@ static struct config_real ConfigureNamesReal[] =
 	},
 
 	{
-		{"hash_mem_multiplier", PGC_USERSET, RESOURCES_MEM,
+		{"hash_mem_multiplier", PGC_SUSET, RESOURCES_MEM,
 			gettext_noop("Multiple of work_mem to use for hash tables."),
 			NULL,
 			GUC_EXPLAIN
@@ -3780,7 +3782,7 @@ static struct config_real ConfigureNamesReal[] =
 	},
 
 	{
-		{"seed", PGC_USERSET, UNGROUPED,
+		{"seed", PGC_SUSET, UNGROUPED,
 			gettext_noop("Sets the seed for random-number generation."),
 			NULL,
 			GUC_NO_SHOW_ALL | GUC_NO_RESET_ALL | GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE
@@ -3791,7 +3793,7 @@ static struct config_real ConfigureNamesReal[] =
 	},
 
 	{
-		{"vacuum_cost_delay", PGC_USERSET, RESOURCES_VACUUM_DELAY,
+		{"vacuum_cost_delay", PGC_SUSET, RESOURCES_VACUUM_DELAY,
 			gettext_noop("Vacuum cost delay in milliseconds."),
 			NULL,
 			GUC_UNIT_MS
@@ -4051,7 +4053,7 @@ static struct config_string ConfigureNamesString[] =
 	},
 
 	{
-		{"DateStyle", PGC_USERSET, CLIENT_CONN_LOCALE,
+		{"DateStyle", PGC_SUSET, CLIENT_CONN_LOCALE,
 			gettext_noop("Sets the display format for date and time values."),
 			gettext_noop("Also controls interpretation of ambiguous "
 						 "date inputs."),
@@ -4063,7 +4065,7 @@ static struct config_string ConfigureNamesString[] =
 	},
 
 	{
-		{"default_table_access_method", PGC_USERSET, CLIENT_CONN_STATEMENT,
+		{"default_table_access_method", PGC_SUSET, CLIENT_CONN_STATEMENT,
 			gettext_noop("Sets the default table access method for new tables."),
 			NULL,
 			GUC_IS_NAME
@@ -4074,7 +4076,7 @@ static struct config_string ConfigureNamesString[] =
 	},
 
 	{
-		{"default_tablespace", PGC_USERSET, CLIENT_CONN_STATEMENT,
+		{"default_tablespace", PGC_SUSET, CLIENT_CONN_STATEMENT,
 			gettext_noop("Sets the default tablespace to create tables and indexes in."),
 			gettext_noop("An empty string selects the database's default tablespace."),
 			GUC_IS_NAME
@@ -4085,7 +4087,7 @@ static struct config_string ConfigureNamesString[] =
 	},
 
 	{
-		{"temp_tablespaces", PGC_USERSET, CLIENT_CONN_STATEMENT,
+		{"temp_tablespaces", PGC_SUSET, CLIENT_CONN_STATEMENT,
 			gettext_noop("Sets the tablespace(s) to use for temporary tables and sort files."),
 			NULL,
 			GUC_LIST_INPUT | GUC_LIST_QUOTE
@@ -4165,7 +4167,7 @@ static struct config_string ConfigureNamesString[] =
 	},
 
 	{
-		{"lc_monetary", PGC_USERSET, CLIENT_CONN_LOCALE,
+		{"lc_monetary", PGC_SUSET, CLIENT_CONN_LOCALE,
 			gettext_noop("Sets the locale for formatting monetary amounts."),
 			NULL
 		},
@@ -4175,7 +4177,7 @@ static struct config_string ConfigureNamesString[] =
 	},
 
 	{
-		{"lc_numeric", PGC_USERSET, CLIENT_CONN_LOCALE,
+		{"lc_numeric", PGC_SUSET, CLIENT_CONN_LOCALE,
 			gettext_noop("Sets the locale for formatting numbers."),
 			NULL
 		},
@@ -4185,7 +4187,7 @@ static struct config_string ConfigureNamesString[] =
 	},
 
 	{
-		{"lc_time", PGC_USERSET, CLIENT_CONN_LOCALE,
+		{"lc_time", PGC_SUSET, CLIENT_CONN_LOCALE,
 			gettext_noop("Sets the locale for formatting date and time values."),
 			NULL
 		},
@@ -4217,7 +4219,7 @@ static struct config_string ConfigureNamesString[] =
 	},
 
 	{
-		{"local_preload_libraries", PGC_USERSET, CLIENT_CONN_PRELOAD,
+		{"local_preload_libraries", PGC_SUSET, CLIENT_CONN_PRELOAD,
 			gettext_noop("Lists unprivileged shared libraries to preload into each backend."),
 			NULL,
 			GUC_LIST_INPUT | GUC_LIST_QUOTE
@@ -4228,7 +4230,7 @@ static struct config_string ConfigureNamesString[] =
 	},
 
 	{
-		{"search_path", PGC_USERSET, CLIENT_CONN_STATEMENT,
+		{"search_path", PGC_SUSET, CLIENT_CONN_STATEMENT,
 			gettext_noop("Sets the schema search order for names that are not schema-qualified."),
 			NULL,
 			GUC_LIST_INPUT | GUC_LIST_QUOTE | GUC_EXPLAIN
@@ -4264,7 +4266,7 @@ static struct config_string ConfigureNamesString[] =
 
 	{
 		/* Not for general use --- used by SET ROLE */
-		{"role", PGC_USERSET, UNGROUPED,
+		{"role", PGC_SUSET, UNGROUPED,
 			gettext_noop("Sets the current role."),
 			NULL,
 			GUC_IS_NAME | GUC_NO_SHOW_ALL | GUC_NO_RESET_ALL | GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE | GUC_NOT_WHILE_SEC_REST
@@ -4343,7 +4345,7 @@ static struct config_string ConfigureNamesString[] =
 	},
 
 	{
-		{"TimeZone", PGC_USERSET, CLIENT_CONN_LOCALE,
+		{"TimeZone", PGC_SUSET, CLIENT_CONN_LOCALE,
 			gettext_noop("Sets the time zone for displaying and interpreting time stamps."),
 			NULL,
 			GUC_REPORT
@@ -4353,7 +4355,7 @@ static struct config_string ConfigureNamesString[] =
 		check_timezone, assign_timezone, show_timezone
 	},
 	{
-		{"timezone_abbreviations", PGC_USERSET, CLIENT_CONN_LOCALE,
+		{"timezone_abbreviations", PGC_SUSET, CLIENT_CONN_LOCALE,
 			gettext_noop("Selects a file of time zone abbreviations."),
 			NULL
 		},
@@ -4546,7 +4548,7 @@ static struct config_string ConfigureNamesString[] =
 	},
 
 	{
-		{"default_text_search_config", PGC_USERSET, CLIENT_CONN_LOCALE,
+		{"default_text_search_config", PGC_SUSET, CLIENT_CONN_LOCALE,
 			gettext_noop("Sets default text search configuration."),
 			NULL
 		},
@@ -4608,7 +4610,7 @@ static struct config_string ConfigureNamesString[] =
 	},
 
 	{
-		{"application_name", PGC_USERSET, LOGGING_WHAT,
+		{"application_name", PGC_SUSET, LOGGING_WHAT,
 			gettext_noop("Sets the application name to be reported in statistics and logs."),
 			NULL,
 			GUC_IS_NAME | GUC_REPORT | GUC_NOT_IN_SAMPLE
@@ -4672,7 +4674,7 @@ static struct config_string ConfigureNamesString[] =
 static struct config_enum ConfigureNamesEnum[] =
 {
 	{
-		{"backslash_quote", PGC_USERSET, COMPAT_OPTIONS_PREVIOUS,
+		{"backslash_quote", PGC_SUSET, COMPAT_OPTIONS_PREVIOUS,
 			gettext_noop("Sets whether \"\\'\" is allowed in string literals."),
 			NULL
 		},
@@ -4682,7 +4684,7 @@ static struct config_enum ConfigureNamesEnum[] =
 	},
 
 	{
-		{"bytea_output", PGC_USERSET, CLIENT_CONN_STATEMENT,
+		{"bytea_output", PGC_SUSET, CLIENT_CONN_STATEMENT,
 			gettext_noop("Sets the output format for bytea."),
 			NULL
 		},
@@ -4692,7 +4694,7 @@ static struct config_enum ConfigureNamesEnum[] =
 	},
 
 	{
-		{"client_min_messages", PGC_USERSET, CLIENT_CONN_STATEMENT,
+		{"client_min_messages", PGC_SUSET, CLIENT_CONN_STATEMENT,
 			gettext_noop("Sets the message levels that are sent to the client."),
 			gettext_noop("Each level includes all the levels that follow it. The later"
 						 " the level, the fewer messages are sent.")
@@ -4713,7 +4715,7 @@ static struct config_enum ConfigureNamesEnum[] =
 	},
 
 	{
-		{"constraint_exclusion", PGC_USERSET, QUERY_TUNING_OTHER,
+		{"constraint_exclusion", PGC_SUSET, QUERY_TUNING_OTHER,
 			gettext_noop("Enables the planner to use constraints to optimize queries."),
 			gettext_noop("Table scans will be skipped if their constraints"
 						 " guarantee that no rows match the query."),
@@ -4725,7 +4727,7 @@ static struct config_enum ConfigureNamesEnum[] =
 	},
 
 	{
-		{"default_toast_compression", PGC_USERSET, CLIENT_CONN_STATEMENT,
+		{"default_toast_compression", PGC_SUSET, CLIENT_CONN_STATEMENT,
 			gettext_noop("Sets the default compression method for compressible values."),
 			NULL
 		},
@@ -4736,7 +4738,7 @@ static struct config_enum ConfigureNamesEnum[] =
 	},
 
 	{
-		{"default_transaction_isolation", PGC_USERSET, CLIENT_CONN_STATEMENT,
+		{"default_transaction_isolation", PGC_SUSET, CLIENT_CONN_STATEMENT,
 			gettext_noop("Sets the transaction isolation level of each new transaction."),
 			NULL
 		},
@@ -4746,7 +4748,7 @@ static struct config_enum ConfigureNamesEnum[] =
 	},
 
 	{
-		{"transaction_isolation", PGC_USERSET, CLIENT_CONN_STATEMENT,
+		{"transaction_isolation", PGC_SUSET, CLIENT_CONN_STATEMENT,
 			gettext_noop("Sets the current transaction's isolation level."),
 			NULL,
 			GUC_NO_RESET_ALL | GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE
@@ -4757,7 +4759,7 @@ static struct config_enum ConfigureNamesEnum[] =
 	},
 
 	{
-		{"IntervalStyle", PGC_USERSET, CLIENT_CONN_LOCALE,
+		{"IntervalStyle", PGC_SUSET, CLIENT_CONN_LOCALE,
 			gettext_noop("Sets the display format for interval values."),
 			NULL,
 			GUC_REPORT
@@ -4835,7 +4837,7 @@ static struct config_enum ConfigureNamesEnum[] =
 	},
 
 	{
-		{"synchronous_commit", PGC_USERSET, WAL_SETTINGS,
+		{"synchronous_commit", PGC_SUSET, WAL_SETTINGS,
 			gettext_noop("Sets the current transaction's synchronization level."),
 			NULL
 		},
@@ -4942,7 +4944,7 @@ static struct config_enum ConfigureNamesEnum[] =
 	},
 
 	{
-		{"xmlbinary", PGC_USERSET, CLIENT_CONN_STATEMENT,
+		{"xmlbinary", PGC_SUSET, CLIENT_CONN_STATEMENT,
 			gettext_noop("Sets how binary values are to be encoded in XML."),
 			NULL
 		},
@@ -4952,7 +4954,7 @@ static struct config_enum ConfigureNamesEnum[] =
 	},
 
 	{
-		{"xmloption", PGC_USERSET, CLIENT_CONN_STATEMENT,
+		{"xmloption", PGC_SUSET, CLIENT_CONN_STATEMENT,
 			gettext_noop("Sets whether XML data in implicit parsing and serialization "
 						 "operations is to be considered as documents or content fragments."),
 			NULL
@@ -4973,7 +4975,7 @@ static struct config_enum ConfigureNamesEnum[] =
 	},
 
 	{
-		{"force_parallel_mode", PGC_USERSET, DEVELOPER_OPTIONS,
+		{"force_parallel_mode", PGC_SUSET, DEVELOPER_OPTIONS,
 			gettext_noop("Forces use of parallel query facilities."),
 			gettext_noop("If possible, run query using a parallel worker and with parallel restrictions."),
 			GUC_NOT_IN_SAMPLE | GUC_EXPLAIN
@@ -4984,7 +4986,7 @@ static struct config_enum ConfigureNamesEnum[] =
 	},
 
 	{
-		{"password_encryption", PGC_USERSET, CONN_AUTH_AUTH,
+		{"password_encryption", PGC_SUSET, CONN_AUTH_AUTH,
 			gettext_noop("Chooses the algorithm for encrypting passwords."),
 			NULL
 		},
@@ -4994,7 +4996,7 @@ static struct config_enum ConfigureNamesEnum[] =
 	},
 
 	{
-		{"plan_cache_mode", PGC_USERSET, QUERY_TUNING_OTHER,
+		{"plan_cache_mode", PGC_SUSET, QUERY_TUNING_OTHER,
 			gettext_noop("Controls the planner's selection of custom or generic plan."),
 			gettext_noop("Prepared statements can have custom and generic plans, and the planner "
 						 "will attempt to choose which is better.  This can be set to override "
@@ -5053,6 +5055,10 @@ static struct config_enum ConfigureNamesEnum[] =
  * the following mappings to any unrecognized name.  Note that an old name
  * should be mapped to a new one only if the new variable has very similar
  * semantics to the old.
+ *
+ * If you deprecate a name in favor of a new spelling, be sure to consider what
+ * upgrade support will be needed, if any, for existing pg_setting_acl
+ * entries.
  */
 static const char *const map_old_guc_names[] = {
 	"sort_mem", "work_mem",
@@ -5452,25 +5458,29 @@ add_guc_variable(struct config_generic *var, int elevel)
 }
 
 /*
- * Decide whether a proposed custom variable name is allowed.
+ * Decide whether a proposed variable name is allowed.
  *
- * It must be two or more identifiers separated by dots, where the rules
- * for what is an identifier agree with scan.l.  (If you change this rule,
- * adjust the errdetail in find_option().)
+ * It must be one or more identifiers separated by zero or more dots, where the
+ * rules for what is an identifier agree with scan.l.  (If you change this
+ * rule, adjust the errdetail in find_option().)
+ *
+ * partcnt: returns by reference the number of dot separated identifiers.
  */
-static bool
-valid_custom_variable_name(const char *name)
+bool
+valid_variable_name(const char *name, int *partcnt)
 {
-	bool		saw_sep = false;
+	int			parts = 1;
 	bool		name_start = true;
 
+	if (partcnt)
+		*partcnt = -1;
 	for (const char *p = name; *p; p++)
 	{
 		if (*p == GUC_QUALIFIER_SEPARATOR)
 		{
 			if (name_start)
 				return false;	/* empty name component */
-			saw_sep = true;
+			parts++;
 			name_start = true;
 		}
 		else if (strchr("ABCDEFGHIJKLMNOPQRSTUVWXYZ"
@@ -5487,8 +5497,24 @@ valid_custom_variable_name(const char *name)
 	}
 	if (name_start)
 		return false;			/* empty name component */
-	/* OK if we found at least one separator */
-	return saw_sep;
+	if (partcnt)
+		*partcnt = parts;
+	return true;
+}
+
+/*
+ * Decide whether a proposed custom variable name is allowed.
+ *
+ * It must be two or more identifiers separated by dots, where the rules
+ * for what is an identifier agree with scan.l.  (If you change this rule,
+ * adjust the errdetail in find_option().)
+ */
+static bool
+valid_custom_variable_name(const char *name)
+{
+	int			partcnt;
+
+	return (valid_variable_name(name, &partcnt) && partcnt > 1);
 }
 
 /*
@@ -7542,6 +7568,24 @@ set_config_option(const char *name, const char *value,
 		case PGC_SUSET:
 			if (context == PGC_USERSET || context == PGC_BACKEND)
 			{
+				/*
+				 * Check whether the current user has granted privilege to set
+				 * this GUC.
+				 */
+				Oid			settingid = get_setting_oid(name, true);
+
+				if (OidIsValid(settingid))
+				{
+					AclResult	aclresult;
+
+					aclresult = pg_setting_acl_aclcheck(settingid, GetUserId(),
+														ACL_SET_VALUE);
+
+					if (aclresult == ACLCHECK_OK)
+						break;		/* okay */
+				}
+
+				/* No granted privilege */
 				ereport(elevel,
 						(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
 						 errmsg("permission denied to set parameter \"%s\"",
@@ -8584,6 +8628,7 @@ AlterSystemSetConfigFile(AlterSystemStmt *altersysstmt)
 {
 	char	   *name;
 	char	   *value;
+	Oid			settingId = InvalidOid;
 	bool		resetall = false;
 	ConfigVariable *head = NULL;
 	ConfigVariable *tail = NULL;
@@ -8591,16 +8636,31 @@ AlterSystemSetConfigFile(AlterSystemStmt *altersysstmt)
 	char		AutoConfFileName[MAXPGPATH];
 	char		AutoConfTmpFileName[MAXPGPATH];
 
-	if (!superuser())
-		ereport(ERROR,
-				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-				 errmsg("must be superuser to execute ALTER SYSTEM command")));
-
 	/*
 	 * Extract statement arguments
 	 */
 	name = altersysstmt->setstmt->name;
 
+	/*
+	 * Check permission to run ALTER SYSTEM on the target variable registered.
+	 */
+	if (!superuser())
+	{
+		AclResult	aclresult;
+
+		settingId = get_setting_oid(name, true);
+		if (!OidIsValid(settingId))
+			ereport(ERROR,
+					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+					 errmsg("permission denied to set parameter \"%s\"",
+							name)));
+
+		aclresult = pg_setting_acl_aclcheck(settingId, GetUserId(),
+											 ACL_ALTER_SYSTEM);
+		if (aclresult != ACLCHECK_OK)
+			aclcheck_error(aclresult, OBJECT_SETTING, name);
+	}
+
 	switch (altersysstmt->setstmt->kind)
 	{
 		case VAR_SET_VALUE:
@@ -8733,6 +8793,21 @@ AlterSystemSetConfigFile(AlterSystemStmt *altersysstmt)
 		replace_auto_config_value(&head, &tail, name, value);
 	}
 
+	/*
+	 * Invoke the post-alter hook for setting this GUC variable.  Guc variables
+	 * do not always have corresponding entries in pg_setting_acl; the hooks
+	 * must therefore be prepared to receive settingId = InvalidOid.  We also
+	 * abuse the notion of subId to pass the kind of alteration (set vs. alter
+	 * system), and auxiliaryId to pass the VariableSetKind.
+	 *
+	 * We do this here rather than at the end, because ALTER SYSTEM is not
+	 * transactional.  If the hook aborts our transaction, it will be cleaner
+	 * to do so before we touch any files.
+	 */
+	InvokeObjectPostAlterHookArg(SettingAclRelationId, settingId,
+								 ACL_ALTER_SYSTEM, altersysstmt->setstmt->kind,
+								 false);
+
 	/*
 	 * To ensure crash safety, first write the new file data to a temp file,
 	 * then atomically rename it into place.
@@ -8793,6 +8868,9 @@ void
 ExecSetVariableStmt(VariableSetStmt *stmt, bool isTopLevel)
 {
 	GucAction	action = stmt->is_local ? GUC_ACTION_LOCAL : GUC_ACTION_SET;
+	GucContext	context;
+	AclResult	aclresult;
+	Oid			settingId;
 
 	/*
 	 * Workers synchronize these parameters at the start of the parallel
@@ -8803,6 +8881,24 @@ ExecSetVariableStmt(VariableSetStmt *stmt, bool isTopLevel)
 				(errcode(ERRCODE_INVALID_TRANSACTION_STATE),
 				 errmsg("cannot set parameters during a parallel operation")));
 
+	/* Get the Oid of this setting, or InvalidOid if none. */
+	settingId = get_setting_oid(stmt->name, true);
+
+	/*
+	 * Superusers and users who have been granted SET privilege can set with
+	 * PGC_SUSET context.  All others have only PGC_USERSET.
+	 */
+	context = PGC_USERSET;
+	if (superuser())
+		context = PGC_SUSET;
+	else if (OidIsValid(settingId))
+	{
+		aclresult = pg_setting_acl_aclcheck(settingId, GetUserId(),
+											ACL_SET_VALUE);
+		if (aclresult == ACLCHECK_OK)
+			context = PGC_SUSET;
+	}
+
 	switch (stmt->kind)
 	{
 		case VAR_SET_VALUE:
@@ -8811,7 +8907,7 @@ ExecSetVariableStmt(VariableSetStmt *stmt, bool isTopLevel)
 				WarnNoTransactionBlock(isTopLevel, "SET LOCAL");
 			(void) set_config_option(stmt->name,
 									 ExtractSetVariableArgs(stmt),
-									 (superuser() ? PGC_SUSET : PGC_USERSET),
+									 context,
 									 PGC_S_SESSION,
 									 action, true, 0, false);
 			break;
@@ -8896,7 +8992,7 @@ ExecSetVariableStmt(VariableSetStmt *stmt, bool isTopLevel)
 
 			(void) set_config_option(stmt->name,
 									 NULL,
-									 (superuser() ? PGC_SUSET : PGC_USERSET),
+									 context,
 									 PGC_S_SESSION,
 									 action, true, 0, false);
 			break;
@@ -8904,6 +9000,16 @@ ExecSetVariableStmt(VariableSetStmt *stmt, bool isTopLevel)
 			ResetAllOptions();
 			break;
 	}
+
+	/*
+	 * Invoke the post-alter hook for setting this GUC variable.  Guc variables
+	 * do not always have corresponding entries in pg_setting_acl; the hooks
+	 * must therefore be prepared to receive settingId = InvalidOid.  We also
+	 * abuse the notion of subId to pass the kind of alteration (set vs. alter
+	 * system), and auxiliaryId to pass the VariableSetKind.
+	 */
+	InvokeObjectPostAlterHookArg(SettingAclRelationId, settingId,
+								 ACL_SET_VALUE, stmt->kind, false);
 }
 
 /*
@@ -9664,6 +9770,22 @@ get_explain_guc_options(int *num)
 	return result;
 }
 
+/*
+ * Return GUC variable canonical name, or NULL if no variable by the given
+ * name or alias exists.
+ */
+const char *
+GetConfigOptionCanonicalName(const char *alias)
+{
+	struct config_generic *record;
+
+	record = find_option(alias, false, true, LOG);
+	if (record == NULL)
+		return NULL;
+
+	return record->name;
+}
+
 /*
  * Return GUC variable value by name; optionally return canonical form of
  * name.  If the GUC is unset, then throw an error unless missing_ok is true,
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index 97f15971e2..3138722c19 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -164,6 +164,7 @@ static char *features_file;
 static char *system_constraints_file;
 static char *system_functions_file;
 static char *system_views_file;
+static char *setting_privileges_file;
 static bool success = false;
 static bool made_new_pgdata = false;
 static bool found_existing_pgdata = false;
@@ -2464,6 +2465,7 @@ setup_data_file_paths(void)
 	set_input(&system_constraints_file, "system_constraints.sql");
 	set_input(&system_functions_file, "system_functions.sql");
 	set_input(&system_views_file, "system_views.sql");
+	set_input(&setting_privileges_file, "setting_privileges.sql");
 
 	if (show_setting || debug)
 	{
@@ -2492,6 +2494,7 @@ setup_data_file_paths(void)
 	check_input(system_constraints_file);
 	check_input(system_functions_file);
 	check_input(system_views_file);
+	check_input(setting_privileges_file);
 }

@@ -2839,6 +2842,8 @@ initialize_data_directory(void)
 
 	setup_run_file(cmdfd, system_views_file);
 
+	setup_run_file(cmdfd, setting_privileges_file);
+
 	setup_description(cmdfd);
 
 	setup_collation(cmdfd);
diff --git a/src/bin/pg_dump/dumputils.c b/src/bin/pg_dump/dumputils.c
index 6086d57cf3..a84efd79e5 100644
--- a/src/bin/pg_dump/dumputils.c
+++ b/src/bin/pg_dump/dumputils.c
@@ -37,7 +37,7 @@ static void AddAcl(PQExpBuffer aclbuf, const char *keyword,
  *	nspname: the namespace the object is in (NULL if none); not pre-quoted
  *	type: the object type (as seen in GRANT command: must be one of
  *		TABLE, SEQUENCE, FUNCTION, PROCEDURE, LANGUAGE, SCHEMA, DATABASE, TABLESPACE,
- *		FOREIGN DATA WRAPPER, SERVER, or LARGE OBJECT)
+ *		FOREIGN DATA WRAPPER, SERVER, SETTING or LARGE OBJECT)
  *	acls: the ACL string fetched from the database
  *	baseacls: the initial ACL string for this object
  *	owner: username of object owner (will be passed through fmtId); can be
@@ -501,6 +501,11 @@ do { \
 		CONVERT_PRIV('U', "USAGE");
 	else if (strcmp(type, "FOREIGN TABLE") == 0)
 		CONVERT_PRIV('r', "SELECT");
+	else if (strcmp(type, "SETTING") == 0)
+	{
+		CONVERT_PRIV('s', "SET VALUE");
+		CONVERT_PRIV('A', "ALTER SYSTEM");
+	}
 	else if (strcmp(type, "LARGE OBJECT") == 0)
 	{
 		CONVERT_PRIV('r', "SELECT");
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index d41a99d6ea..bb0badca65 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -3435,6 +3435,7 @@ _getObjectDescription(PQExpBuffer buf, TocEntry *te)
 		strcmp(type, "SCHEMA") == 0 ||
 		strcmp(type, "EVENT TRIGGER") == 0 ||
 		strcmp(type, "FOREIGN DATA WRAPPER") == 0 ||
+		strcmp(type, "SETTING") == 0 ||
 		strcmp(type, "SERVER") == 0 ||
 		strcmp(type, "PUBLICATION") == 0 ||
 		strcmp(type, "SUBSCRIPTION") == 0 ||
@@ -3618,6 +3619,7 @@ _printTocEntry(ArchiveHandle *AH, TocEntry *te, bool isData)
 			strcmp(te->desc, "TEXT SEARCH DICTIONARY") == 0 ||
 			strcmp(te->desc, "TEXT SEARCH CONFIGURATION") == 0 ||
 			strcmp(te->desc, "FOREIGN DATA WRAPPER") == 0 ||
+			strcmp(te->desc, "SETTING") == 0 ||
 			strcmp(te->desc, "SERVER") == 0 ||
 			strcmp(te->desc, "STATISTICS") == 0 ||
 			strcmp(te->desc, "PUBLICATION") == 0 ||
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index e69dcf8a48..dea1b89ff8 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -14166,6 +14166,9 @@ dumpDefaultACL(Archive *fout, const DefaultACLInfo *daclinfo)
 		case DEFACLOBJ_NAMESPACE:
 			type = "SCHEMAS";
 			break;
+		case DEFACLOBJ_SETTING:
+			type = "SETTINGS";
+			break;
 		default:
 			/* shouldn't get here */
 			fatal("unrecognized object type in default privileges: %d",
@@ -14209,7 +14212,7 @@ dumpDefaultACL(Archive *fout, const DefaultACLInfo *daclinfo)
  *		or InvalidDumpId if there is no need for a second dependency.
  * 'type' must be one of
  *		TABLE, SEQUENCE, FUNCTION, LANGUAGE, SCHEMA, DATABASE, TABLESPACE,
- *		FOREIGN DATA WRAPPER, SERVER, or LARGE OBJECT.
+ *		FOREIGN DATA WRAPPER, SERVER, SETTING or LARGE OBJECT.
  * 'name' is the formatted name of the object.  Must be quoted etc. already.
  * 'subname' is the formatted name of the sub-object, if any.  Must be quoted.
  *		(Currently we assume that subname is only provided for table columns.)
diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c
index 9c9f7c6d63..b2cf8cbe76 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -36,6 +36,7 @@ static void help(void);
 static void dropRoles(PGconn *conn);
 static void dumpRoles(PGconn *conn);
 static void dumpRoleMembership(PGconn *conn);
+static void dumpRoleGUCPrivs(PGconn *conn);
 static void dropTablespaces(PGconn *conn);
 static void dumpTablespaces(PGconn *conn);
 static void dropDBs(PGconn *conn);
@@ -585,6 +586,10 @@ main(int argc, char *argv[])
 
 			/* Dump role memberships */
 			dumpRoleMembership(conn);
+
+			/* Dump role guc privileges */
+			if (server_version >= 150000)
+				dumpRoleGUCPrivs(conn);
 		}
 
 		/* Dump tablespaces */
@@ -1024,6 +1029,72 @@ dropTablespaces(PGconn *conn)
 	fprintf(OPF, "\n\n");
 }
 
+/*
+ * Dump role configuration parameter privileges.  This code is used for 15.0
+ * and later servers.
+ *
+ * Note: we expect dumpRoles already created all the roles, but there are
+ * no per-role configuration parameter privileges yet..
+ */
+static void
+dumpRoleGUCPrivs(PGconn *conn)
+{
+	PQExpBuffer buf = createPQExpBuffer();
+	PGresult   *res;
+	int			i;
+
+	printfPQExpBuffer(buf, "SELECT string_agg(acl.privilege_type, ', ' ORDER BY acl.privilege_type), "
+					  "set_acl.setting, "
+					  "grantee.rolname AS grantee, "
+					  "acl.is_grantable, "
+					  "grantor.rolname AS grantor "
+					  "FROM pg_catalog.pg_setting_acl set_acl, "
+					  "LATERAL (SELECT * FROM aclexplode(set_acl.setacl)) acl "
+					  "JOIN pg_catalog.pg_authid grantee "
+					  "ON acl.grantee = grantee.oid "
+					  "LEFT JOIN pg_catalog.pg_authid grantor ON "
+					  "acl.grantor = grantor.oid "
+					  "WHERE acl.grantee > 0 "
+					  "GROUP BY setting, grantee.rolname, is_grantable, grantor.rolname"
+					  );
+
+	res = executeQuery(conn, buf->data);
+
+	if (PQntuples(res) > 0)
+		fprintf(OPF, "--\n-- Role privileges on configuration parameters\n--\n\n");
+
+	for (i = 0; i < PQntuples(res); i++)
+	{
+		char	   *privilege = PQgetvalue(res, i, 0);
+		char	   *setting = PQgetvalue(res, i, 1);
+		char	   *grantee = PQgetvalue(res, i, 2);
+		char	   *grantable = PQgetvalue(res, i, 3);
+
+		fprintf(OPF, "GRANT %s", privilege);
+		fprintf(OPF, " ON %s", setting);
+		fprintf(OPF, " TO %s", fmtId(grantee));
+		if (*grantable == 't')
+			fprintf(OPF, " WITH GRANT OPTION");
+
+		/*
+		 * We don't track the grantor very carefully in the backend, so cope
+		 * with the possibility that it has been dropped.
+		 */
+		if (!PQgetisnull(res, i, 4))
+		{
+			char	   *grantor = PQgetvalue(res, i, 4);
+
+			fprintf(OPF, " GRANTED BY %s", fmtId(grantor));
+		}
+		fprintf(OPF, ";\n");
+	}
+
+	PQclear(res);
+	destroyPQExpBuffer(buf);
+
+	fprintf(OPF, "\n\n");
+}
+
 /*
  * Dump tablespaces.
  */
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 344482ec87..a0c3966da9 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -92,6 +92,7 @@ typedef enum ObjectClass
 	OCLASS_TYPE,				/* pg_type */
 	OCLASS_CAST,				/* pg_cast */
 	OCLASS_COLLATION,			/* pg_collation */
+	OCLASS_SETTING,				/* pg_setting_acl */
 	OCLASS_CONSTRAINT,			/* pg_constraint */
 	OCLASS_CONVERSION,			/* pg_conversion */
 	OCLASS_DEFAULT,				/* pg_attrdef */
diff --git a/src/include/catalog/pg_default_acl.h b/src/include/catalog/pg_default_acl.h
index 2a79155636..12ec9b0a31 100644
--- a/src/include/catalog/pg_default_acl.h
+++ b/src/include/catalog/pg_default_acl.h
@@ -66,6 +66,7 @@ DECLARE_UNIQUE_INDEX_PKEY(pg_default_acl_oid_index, 828, DefaultAclOidIndexId, o
 #define DEFACLOBJ_FUNCTION		'f' /* function */
 #define DEFACLOBJ_TYPE			'T' /* type */
 #define DEFACLOBJ_NAMESPACE		'n' /* namespace */
+#define DEFACLOBJ_SETTING	'c' /* configuration parameter */
 
 #endif							/* EXPOSE_TO_CLIENT_CODE */
 
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index d8e8715ed1..ebae1f4f65 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -7207,6 +7207,25 @@
   proname => 'has_type_privilege', provolatile => 's', prorettype => 'bool',
   proargtypes => 'oid text', prosrc => 'has_type_privilege_id' },
 
+{ oid => '8050', descr => 'user privilege on setting by username, setting name',
+  proname => 'has_setting_privilege', provolatile => 's', prorettype => 'bool',
+  proargtypes => 'name text text', prosrc => 'has_setting_privilege_name_name' },
+{ oid => '8051', descr => 'user privilege on setting by username, setting oid',
+  proname => 'has_setting_privilege', provolatile => 's', prorettype => 'bool',
+  proargtypes => 'name oid text', prosrc => 'has_setting_privilege_name_id' },
+{ oid => '8052', descr => 'user privilege on setting by user oid, setting name',
+  proname => 'has_setting_privilege', provolatile => 's', prorettype => 'bool',
+  proargtypes => 'oid text text', prosrc => 'has_setting_privilege_id_name' },
+{ oid => '8053', descr => 'user privilege on setting by user oid, setting oid',
+  proname => 'has_setting_privilege', provolatile => 's', prorettype => 'bool',
+  proargtypes => 'oid oid text', prosrc => 'has_setting_privilege_id_id' },
+{ oid => '8054', descr => 'current user privilege on setting by setting name',
+  proname => 'has_setting_privilege', provolatile => 's', prorettype => 'bool',
+  proargtypes => 'text text', prosrc => 'has_setting_privilege_name' },
+{ oid => '8055', descr => 'current user privilege on setting by setting oid',
+  proname => 'has_setting_privilege', provolatile => 's', prorettype => 'bool',
+  proargtypes => 'oid text', prosrc => 'has_setting_privilege_id' },
+
 { oid => '2705', descr => 'user privilege on role by username, role name',
   proname => 'pg_has_role', provolatile => 's', prorettype => 'bool',
   proargtypes => 'name name text', prosrc => 'pg_has_role_name_name' },
diff --git a/src/include/catalog/pg_setting_acl.h b/src/include/catalog/pg_setting_acl.h
new file mode 100644
index 0000000000..b7dee55e5c
--- /dev/null
+++ b/src/include/catalog/pg_setting_acl.h
@@ -0,0 +1,63 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_setting_acl.h
+ *	  definition of the "configuration parameter" system catalog
+ *	  (pg_setting_acl).
+ *
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_setting_acl.h
+ *
+ * NOTES
+ *	  The Catalog.pm module reads this file and derives schema
+ *	  information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_SETTING_ACL_H
+#define PG_SETTING_ACL_H
+
+#include "catalog/genbki.h"
+#include "catalog/pg_setting_acl_d.h"
+
+/* ----------------
+ *		pg_setting_acl definition.  cpp turns this into
+ *		typedef struct FormData_pg_setting_acl
+ * ----------------
+ */
+CATALOG(pg_setting_acl,8924,SettingAclRelationId) BKI_SHARED_RELATION
+{
+	Oid			oid;			/* oid */
+	/*
+
+	 * Variable-length fields start here, but we allow direct access to
+	 * setting.
+	 */
+	text		setting BKI_FORCE_NOT_NULL;
+
+#ifdef CATALOG_VARLEN
+	/* Access privileges */
+	aclitem		setacl[1] BKI_DEFAULT(_null_);
+#endif
+} FormData_pg_setting_acl;
+
+
+/* ----------------
+ *		Form_pg_setting_acl corresponds to a pointer to a tuple with
+ *		the format of pg_setting_acl relation.
+ * ----------------
+ */
+typedef FormData_pg_setting_acl *Form_pg_setting_acl;
+
+DECLARE_TOAST(pg_setting_acl, 8925, 8926);
+#define PgSettingAclToastTable 8925
+#define PgSettingAclToastIndex 8926
+
+DECLARE_UNIQUE_INDEX(pg_setting_acl_setting_index, 8927, SettingAclSettingIndexId, on pg_setting_acl using btree(setting text_ops));
+DECLARE_UNIQUE_INDEX_PKEY(pg_setting_acl_oid_index, 8928, SettingAclOidIndexId, on pg_setting_acl using btree(oid oid_ops));
+
+extern Oid	SettingAclCreate(const char *setting, bool if_not_exists);
+
+#endif							/* PG_SETTING_ACL_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 1617702d9d..5f5e4ae8e1 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -92,7 +92,9 @@ typedef uint32 AclMode;			/* a bitmask of privilege bits */
 #define ACL_CREATE		(1<<9)	/* for namespaces and databases */
 #define ACL_CREATE_TEMP (1<<10) /* for databases */
 #define ACL_CONNECT		(1<<11) /* for databases */
-#define N_ACL_RIGHTS	12		/* 1 plus the last 1<<x */
+#define ACL_SET_VALUE	(1<<12) /* for configuration parameters */
+#define ACL_ALTER_SYSTEM (1<<13) /* for configuration parameters */
+#define N_ACL_RIGHTS	14		/* 1 plus the last 1<<x */
 #define ACL_NO_RIGHTS	0
 /* Currently, SELECT ... FOR [KEY] UPDATE/SHARE requires UPDATE privileges */
 #define ACL_SELECT_FOR_UPDATE	ACL_UPDATE
@@ -1795,6 +1797,7 @@ typedef enum ObjectType
 	OBJECT_CAST,
 	OBJECT_COLUMN,
 	OBJECT_COLLATION,
+	OBJECT_SETTING,
 	OBJECT_CONVERSION,
 	OBJECT_DATABASE,
 	OBJECT_DEFAULT,
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index bcef7eed2f..f5ed8c2082 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -307,6 +307,7 @@ PG_KEYWORD("overriding", OVERRIDING, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("owned", OWNED, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("owner", OWNER, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("parallel", PARALLEL, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("parameter", PARAMETER, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("parser", PARSER, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("partial", PARTIAL, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("partition", PARTITION, UNRESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/include/utils/acl.h b/src/include/utils/acl.h
index 1ce4c5556e..e6d7761643 100644
--- a/src/include/utils/acl.h
+++ b/src/include/utils/acl.h
@@ -146,9 +146,11 @@ typedef struct ArrayType Acl;
 #define ACL_CREATE_CHR			'C'
 #define ACL_CREATE_TEMP_CHR		'T'
 #define ACL_CONNECT_CHR			'c'
+#define ACL_SET_VALUE_CHR		's'
+#define ACL_ALTER_SYSTEM_CHR	'A'
 
 /* string holding all privilege code chars, in order by bitmask position */
-#define ACL_ALL_RIGHTS_STR	"arwdDxtXUCTc"
+#define ACL_ALL_RIGHTS_STR	"arwdDxtXUCTcsA"
 
 /*
  * Bitmasks defining "all rights" for each supported object type
@@ -165,6 +167,7 @@ typedef struct ArrayType Acl;
 #define ACL_ALL_RIGHTS_SCHEMA		(ACL_USAGE|ACL_CREATE)
 #define ACL_ALL_RIGHTS_TABLESPACE	(ACL_CREATE)
 #define ACL_ALL_RIGHTS_TYPE			(ACL_USAGE)
+#define ACL_ALL_RIGHTS_SETTING	(ACL_SET_VALUE|ACL_ALTER_SYSTEM)
 
 /* operation codes for pg_*_aclmask */
 typedef enum
@@ -223,6 +226,8 @@ extern void select_best_grantor(Oid roleId, AclMode privileges,
 
 extern void initialize_acl(void);
 
+extern Oid	get_setting_oid(const char *setting, bool missing_ok);
+
 /*
  * prototypes for functions in aclchk.c
  */
@@ -243,6 +248,8 @@ extern AclMode pg_class_aclmask_ext(Oid table_oid, Oid roleid,
 									bool *is_missing);
 extern AclMode pg_database_aclmask(Oid db_oid, Oid roleid,
 								   AclMode mask, AclMaskHow how);
+extern AclMode pg_setting_acl_aclmask(Oid config_oid, Oid roleid,
+								   AclMode mask, AclMaskHow how);
 extern AclMode pg_proc_aclmask(Oid proc_oid, Oid roleid,
 							   AclMode mask, AclMaskHow how);
 extern AclMode pg_language_aclmask(Oid lang_oid, Oid roleid,
@@ -271,6 +278,7 @@ extern AclResult pg_class_aclcheck(Oid table_oid, Oid roleid, AclMode mode);
 extern AclResult pg_class_aclcheck_ext(Oid table_oid, Oid roleid,
 									   AclMode mode, bool *is_missing);
 extern AclResult pg_database_aclcheck(Oid db_oid, Oid roleid, AclMode mode);
+extern AclResult pg_setting_acl_aclcheck(Oid config_oid, Oid roleid, AclMode mode);
 extern AclResult pg_proc_aclcheck(Oid proc_oid, Oid roleid, AclMode mode);
 extern AclResult pg_language_aclcheck(Oid lang_oid, Oid roleid, AclMode mode);
 extern AclResult pg_largeobject_aclcheck_snapshot(Oid lang_oid, Oid roleid,
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index ea774968f0..2d0a1a53f7 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -382,6 +382,8 @@ extern int	set_config_option(const char *name, const char *value,
 							  GucAction action, bool changeVal, int elevel,
 							  bool is_reload);
 extern void AlterSystemSetConfigFile(AlterSystemStmt *altersysstmt);
+extern bool valid_variable_name(const char *name, int *partcnt);
+extern const char *GetConfigOptionCanonicalName(const char *alias);
 extern char *GetConfigOptionByName(const char *name, const char **varname,
 								   bool missing_ok);
 extern void GetConfigOptionByNum(int varnum, const char **values, bool *noshow);
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index b8dd27d4a9..7a8ba82770 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -188,6 +188,7 @@ extern int32 get_attavgwidth(Oid relid, AttrNumber attnum);
 extern bool get_attstatsslot(AttStatsSlot *sslot, HeapTuple statstuple,
 							 int reqkind, Oid reqop, int flags);
 extern void free_attstatsslot(AttStatsSlot *sslot);
+extern char *get_setting_name(Oid configid);
 extern char *get_namespace_name(Oid nspid);
 extern char *get_namespace_name_or_temp(Oid nspid);
 extern Oid	get_range_subtype(Oid rangeOid);
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index 9c1a76e8bb..5961921462 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -89,6 +89,8 @@ enum SysCacheIdentifier
 	REPLORIGNAME,
 	RULERELNAME,
 	SEQRELID,
+	SETTINGNAME,
+	SETTINGOID,
 	STATEXTDATASTXOID,
 	STATEXTNAMENSP,
 	STATEXTOID,
diff --git a/src/test/modules/test_pg_dump/t/001_base.pl b/src/test/modules/test_pg_dump/t/001_base.pl
index c73bd37835..7f8984bc45 100644
--- a/src/test/modules/test_pg_dump/t/001_base.pl
+++ b/src/test/modules/test_pg_dump/t/001_base.pl
@@ -317,6 +317,53 @@ my %tests = (
 		like         => { pg_dumpall_globals => 1, },
 	},
 
+	'GRANT ALTER SYSTEM ON ignore_checksum_failure' => {
+		create_order => 2,
+		create_sql   =>
+			'GRANT ALTER SYSTEM ON ignore_checksum_failure TO regress_dump_test_role;',
+		regexp       =>
+			qr/^GRANT ALTER SYSTEM ON ignore_checksum_failure TO regress_dump_test_role GRANTED BY /m,
+		like         => { pg_dumpall_globals => 1, },
+	},
+
+	'GRANT SET VALUE ON my.missing.guc' => {
+		create_order => 2,
+		create_sql   =>
+			'GRANT SET VALUE ON my.missing.guc TO regress_dump_test_role;',
+		regexp       =>
+			qr/^GRANT SET VALUE ON my\.missing\.guc TO regress_dump_test_role GRANTED BY /m,
+		like         => { pg_dumpall_globals => 1, },
+	},
+
+	'GRANT SET VALUE, ALTER SYSTEM ON something WITH GRANT OPTION' => {
+		create_order => 2,
+		create_sql =>
+			'GRANT SET VALUE, ALTER SYSTEM ON something TO regress_dump_test_role WITH GRANT OPTION;',
+		regexp =>
+			qr/^GRANT ALTER SYSTEM, SET VALUE ON something TO regress_dump_test_role WITH GRANT OPTION GRANTED BY /m,
+		like => { pg_dumpall_globals => 1, },
+	},
+
+	'GRANT ALTER SYSTEM ON MyReallyLong.ButValidCustom.GucNameThatCannotFit.InNamedata64Byte.Format' => {
+		create_order => 2,
+		create_sql =>
+			# configuration parameters get cased folded
+			'GRANT ALTER SYSTEM ON MyReallyLong.ButValidCustom.GucNameThatCannotFit.InNamedata64Byte.Format TO regress_dump_test_role;',
+		regexp =>
+			qr/^GRANT ALTER SYSTEM ON myreallylong\.butvalidcustom\.gucnamethatcannotfit\.innamedata64byte\.format TO regress_dump_test_role GRANTED BY /m,
+		like => { pg_dumpall_globals => 1, },
+	},
+
+	'GRANT ALTER SYSTEM, SET VALUE ON my.guc TO regress_dump_test_role GRANTED BY CURRENT_ROLE' => {
+		create_order => 2,
+		create_sql =>
+			# GRANTED BY CURRENT_ROLE is allowed for SQL compatibility, but is ignored
+			'GRANT ALTER SYSTEM, SET VALUE ON my.guc TO regress_dump_test_role GRANTED BY CURRENT_ROLE;',
+		regexp =>
+			qr/^GRANT ALTER SYSTEM, SET VALUE ON my\.guc TO regress_dump_test_role GRANTED BY /m,
+		like => { pg_dumpall_globals => 1, },
+	},
+
 	'CREATE SCHEMA public' => {
 		regexp => qr/^CREATE SCHEMA public;/m,
 		like   => {
diff --git a/src/test/regress/expected/guc_privs.out b/src/test/regress/expected/guc_privs.out
new file mode 100644
index 0000000000..c2033e77d1
--- /dev/null
+++ b/src/test/regress/expected/guc_privs.out
@@ -0,0 +1,780 @@
+-- Test superuser
+-- Superuser DBA
+CREATE ROLE regress_admin SUPERUSER;
+-- Perform all operations as user 'regress_admin' --
+SET SESSION AUTHORIZATION regress_admin;
+-- PGC_BACKEND
+SET ignore_system_indexes = OFF;  -- fail, cannot be set after connection start
+ERROR:  parameter "ignore_system_indexes" cannot be set after connection start
+RESET ignore_system_indexes;  -- fail, cannot be set after connection start
+ERROR:  parameter "ignore_system_indexes" cannot be set after connection start
+ALTER SYSTEM SET ignore_system_indexes = OFF;  -- ok
+ALTER SYSTEM RESET ignore_system_indexes;  -- ok
+-- PGC_INTERNAL
+SET block_size = 50;  -- fail, cannot be changed
+ERROR:  parameter "block_size" cannot be changed
+RESET block_size;  -- fail, cannot be changed
+ERROR:  parameter "block_size" cannot be changed
+ALTER SYSTEM SET block_size = 50;  -- fail, cannot be changed
+ERROR:  parameter "block_size" cannot be changed
+ALTER SYSTEM RESET block_size;  -- fail, cannot be changed
+ERROR:  parameter "block_size" cannot be changed
+-- PGC_POSTMASTER
+SET autovacuum_freeze_max_age = 1000050000;  -- fail, requires restart
+ERROR:  parameter "autovacuum_freeze_max_age" cannot be changed without restarting the server
+RESET autovacuum_freeze_max_age;  -- fail, requires restart
+ERROR:  parameter "autovacuum_freeze_max_age" cannot be changed without restarting the server
+ALTER SYSTEM SET autovacuum_freeze_max_age = 1000050000;  -- ok
+ALTER SYSTEM RESET autovacuum_freeze_max_age;  -- ok
+ALTER SYSTEM SET config_file = '/usr/local/data/postgresql.conf';  -- fail, cannot be changed
+ERROR:  parameter "config_file" cannot be changed
+ALTER SYSTEM RESET config_file;  -- fail, cannot be changed
+ERROR:  parameter "config_file" cannot be changed
+-- PGC_SIGHUP
+SET autovacuum = OFF;  -- fail, requires reload
+ERROR:  parameter "autovacuum" cannot be changed now
+RESET autovacuum;  -- fail, requires reload
+ERROR:  parameter "autovacuum" cannot be changed now
+ALTER SYSTEM SET autovacuum = OFF;  -- ok
+ALTER SYSTEM RESET autovacuum;  -- ok
+-- PGC_SUSET
+SET lc_messages = 'C';  -- ok
+RESET lc_messages;  -- ok
+ALTER SYSTEM SET lc_messages = 'C';  -- ok
+ALTER SYSTEM RESET lc_messages;  -- ok
+-- PGC_SU_BACKEND
+SET jit_debugging_support = OFF;  -- fail, cannot be set after connection start
+ERROR:  parameter "jit_debugging_support" cannot be set after connection start
+RESET jit_debugging_support;  -- fail, cannot be set after connection start
+ERROR:  parameter "jit_debugging_support" cannot be set after connection start
+ALTER SYSTEM SET jit_debugging_support = OFF;  -- ok
+ALTER SYSTEM RESET jit_debugging_support;  -- ok
+-- PGC_USERSET
+SET DateStyle = 'ISO, MDY';  -- ok
+RESET DateStyle;  -- ok
+ALTER SYSTEM SET DateStyle = 'ISO, MDY';  -- ok
+ALTER SYSTEM RESET DateStyle;  -- ok
+ALTER SYSTEM SET ssl_renegotiation_limit = 0;  -- fail, cannot be changed
+ERROR:  parameter "ssl_renegotiation_limit" cannot be changed
+ALTER SYSTEM RESET ssl_renegotiation_limit;  -- fail, cannot be changed
+ERROR:  parameter "ssl_renegotiation_limit" cannot be changed
+-- Finished testing superuser
+RESET statement_timeout;
+-- Check setting privileges prior to creating any
+SELECT grantee, setting, privilege_type, is_grantable
+	FROM pg_catalog.pg_setting_privileges
+	ORDER BY grantee, setting, privilege_type;
+ grantee |               setting               | privilege_type | is_grantable 
+---------+-------------------------------------+----------------+--------------
+         | DateStyle                           | SET VALUE      | f
+         | IntervalStyle                       | SET VALUE      | f
+         | TimeZone                            | SET VALUE      | f
+         | application_name                    | SET VALUE      | f
+         | array_nulls                         | SET VALUE      | f
+         | backend_flush_after                 | SET VALUE      | f
+         | backslash_quote                     | SET VALUE      | f
+         | bytea_output                        | SET VALUE      | f
+         | check_function_bodies               | SET VALUE      | f
+         | client_connection_check_interval    | SET VALUE      | f
+         | client_min_messages                 | SET VALUE      | f
+         | commit_siblings                     | SET VALUE      | f
+         | constraint_exclusion                | SET VALUE      | f
+         | cpu_index_tuple_cost                | SET VALUE      | f
+         | cpu_operator_cost                   | SET VALUE      | f
+         | cpu_tuple_cost                      | SET VALUE      | f
+         | cursor_tuple_fraction               | SET VALUE      | f
+         | debug_pretty_print                  | SET VALUE      | f
+         | debug_print_parse                   | SET VALUE      | f
+         | debug_print_plan                    | SET VALUE      | f
+         | debug_print_rewritten               | SET VALUE      | f
+         | default_statistics_target           | SET VALUE      | f
+         | default_table_access_method         | SET VALUE      | f
+         | default_tablespace                  | SET VALUE      | f
+         | default_text_search_config          | SET VALUE      | f
+         | default_toast_compression           | SET VALUE      | f
+         | default_transaction_deferrable      | SET VALUE      | f
+         | default_transaction_isolation       | SET VALUE      | f
+         | default_transaction_read_only       | SET VALUE      | f
+         | default_with_oids                   | SET VALUE      | f
+         | effective_cache_size                | SET VALUE      | f
+         | effective_io_concurrency            | SET VALUE      | f
+         | enable_async_append                 | SET VALUE      | f
+         | enable_bitmapscan                   | SET VALUE      | f
+         | enable_gathermerge                  | SET VALUE      | f
+         | enable_hashagg                      | SET VALUE      | f
+         | enable_hashjoin                     | SET VALUE      | f
+         | enable_incremental_sort             | SET VALUE      | f
+         | enable_indexonlyscan                | SET VALUE      | f
+         | enable_indexscan                    | SET VALUE      | f
+         | enable_material                     | SET VALUE      | f
+         | enable_memoize                      | SET VALUE      | f
+         | enable_mergejoin                    | SET VALUE      | f
+         | enable_nestloop                     | SET VALUE      | f
+         | enable_parallel_append              | SET VALUE      | f
+         | enable_parallel_hash                | SET VALUE      | f
+         | enable_partition_pruning            | SET VALUE      | f
+         | enable_partitionwise_aggregate      | SET VALUE      | f
+         | enable_partitionwise_join           | SET VALUE      | f
+         | enable_seqscan                      | SET VALUE      | f
+         | enable_sort                         | SET VALUE      | f
+         | enable_tidscan                      | SET VALUE      | f
+         | escape_string_warning               | SET VALUE      | f
+         | exit_on_error                       | SET VALUE      | f
+         | extra_float_digits                  | SET VALUE      | f
+         | force_parallel_mode                 | SET VALUE      | f
+         | from_collapse_limit                 | SET VALUE      | f
+         | geqo                                | SET VALUE      | f
+         | geqo_effort                         | SET VALUE      | f
+         | geqo_generations                    | SET VALUE      | f
+         | geqo_pool_size                      | SET VALUE      | f
+         | geqo_seed                           | SET VALUE      | f
+         | geqo_selection_bias                 | SET VALUE      | f
+         | geqo_threshold                      | SET VALUE      | f
+         | gin_fuzzy_search_limit              | SET VALUE      | f
+         | gin_pending_list_limit              | SET VALUE      | f
+         | hash_mem_multiplier                 | SET VALUE      | f
+         | idle_in_transaction_session_timeout | SET VALUE      | f
+         | idle_session_timeout                | SET VALUE      | f
+         | jit                                 | SET VALUE      | f
+         | jit_above_cost                      | SET VALUE      | f
+         | jit_expressions                     | SET VALUE      | f
+         | jit_inline_above_cost               | SET VALUE      | f
+         | jit_optimize_above_cost             | SET VALUE      | f
+         | jit_tuple_deforming                 | SET VALUE      | f
+         | join_collapse_limit                 | SET VALUE      | f
+         | lc_monetary                         | SET VALUE      | f
+         | lc_numeric                          | SET VALUE      | f
+         | lc_time                             | SET VALUE      | f
+         | local_preload_libraries             | SET VALUE      | f
+         | lock_timeout                        | SET VALUE      | f
+         | log_parameter_max_length_on_error   | SET VALUE      | f
+         | logical_decoding_work_mem           | SET VALUE      | f
+         | maintenance_io_concurrency          | SET VALUE      | f
+         | maintenance_work_mem                | SET VALUE      | f
+         | max_parallel_maintenance_workers    | SET VALUE      | f
+         | max_parallel_workers                | SET VALUE      | f
+         | max_parallel_workers_per_gather     | SET VALUE      | f
+         | min_parallel_index_scan_size        | SET VALUE      | f
+         | min_parallel_table_scan_size        | SET VALUE      | f
+         | optimize_bounded_sort               | SET VALUE      | f
+         | parallel_leader_participation       | SET VALUE      | f
+         | parallel_setup_cost                 | SET VALUE      | f
+         | parallel_tuple_cost                 | SET VALUE      | f
+         | password_encryption                 | SET VALUE      | f
+         | plan_cache_mode                     | SET VALUE      | f
+         | quote_all_identifiers               | SET VALUE      | f
+         | random_page_cost                    | SET VALUE      | f
+         | role                                | SET VALUE      | f
+         | row_security                        | SET VALUE      | f
+         | search_path                         | SET VALUE      | f
+         | seed                                | SET VALUE      | f
+         | seq_page_cost                       | SET VALUE      | f
+         | ssl_renegotiation_limit             | SET VALUE      | f
+         | standard_conforming_strings         | SET VALUE      | f
+         | statement_timeout                   | SET VALUE      | f
+         | synchronize_seqscans                | SET VALUE      | f
+         | synchronous_commit                  | SET VALUE      | f
+         | tcp_keepalives_count                | SET VALUE      | f
+         | tcp_keepalives_idle                 | SET VALUE      | f
+         | tcp_keepalives_interval             | SET VALUE      | f
+         | tcp_user_timeout                    | SET VALUE      | f
+         | temp_buffers                        | SET VALUE      | f
+         | temp_tablespaces                    | SET VALUE      | f
+         | timezone_abbreviations              | SET VALUE      | f
+         | trace_notify                        | SET VALUE      | f
+         | trace_sort                          | SET VALUE      | f
+         | trace_syncscan                      | SET VALUE      | f
+         | transaction_deferrable              | SET VALUE      | f
+         | transaction_isolation               | SET VALUE      | f
+         | transaction_read_only               | SET VALUE      | f
+         | transform_null_equals               | SET VALUE      | f
+         | vacuum_cost_delay                   | SET VALUE      | f
+         | vacuum_cost_limit                   | SET VALUE      | f
+         | vacuum_cost_page_dirty              | SET VALUE      | f
+         | vacuum_cost_page_hit                | SET VALUE      | f
+         | vacuum_cost_page_miss               | SET VALUE      | f
+         | vacuum_failsafe_age                 | SET VALUE      | f
+         | vacuum_freeze_min_age               | SET VALUE      | f
+         | vacuum_freeze_table_age             | SET VALUE      | f
+         | vacuum_multixact_failsafe_age       | SET VALUE      | f
+         | vacuum_multixact_freeze_min_age     | SET VALUE      | f
+         | vacuum_multixact_freeze_table_age   | SET VALUE      | f
+         | wal_sender_timeout                  | SET VALUE      | f
+         | wal_skip_threshold                  | SET VALUE      | f
+         | work_mem                            | SET VALUE      | f
+         | xmlbinary                           | SET VALUE      | f
+         | xmloption                           | SET VALUE      | f
+(138 rows)
+
+-- Create non-superuser with privileges to configure host resource usage
+CREATE ROLE regress_host_resource_admin NOSUPERUSER;
+-- Check the new role does not yet have privileges on settings
+SELECT has_setting_privilege('regress_host_resource_admin', 'work_mem', 'SET VALUE, ALTER SYSTEM');
+ has_setting_privilege 
+-----------------------
+ t
+(1 row)
+
+SELECT has_setting_privilege('regress_host_resource_admin', 'work_mem', 'SET VALUE');
+ has_setting_privilege 
+-----------------------
+ t
+(1 row)
+
+SELECT has_setting_privilege('regress_host_resource_admin', 'work_mem', 'ALTER SYSTEM');
+ has_setting_privilege 
+-----------------------
+ f
+(1 row)
+
+-- Check inappropriate and nonsense privilege types
+SELECT has_setting_privilege('regress_host_resource_admin', 'work_mem', 'SELECT, UPDATE, CREATE');
+ERROR:  unrecognized privilege type: "SELECT"
+SELECT has_setting_privilege('regress_host_resource_admin', 'work_mem', 'USAGE');
+ERROR:  unrecognized privilege type: "USAGE"
+SELECT has_setting_privilege('regress_host_resource_admin', 'work_mem', 'WHATEVER');
+ERROR:  unrecognized privilege type: "WHATEVER"
+-- Grant privileges on settings to the new non-superuser role
+GRANT SET VALUE, ALTER SYSTEM ON
+	autovacuum_work_mem, hash_mem_multiplier, logical_decoding_work_mem,
+	maintenance_work_mem, max_stack_depth, min_dynamic_shared_memory,
+	shared_buffers, temp_buffers, temp_file_limit, work_mem
+TO regress_host_resource_admin;
+-- Check setting privileges after creating some
+SELECT grantee, setting, privilege_type, is_grantable
+	FROM pg_catalog.pg_setting_privileges
+	ORDER BY grantee, setting, privilege_type;
+           grantee           |               setting               | privilege_type | is_grantable 
+-----------------------------+-------------------------------------+----------------+--------------
+ regress_host_resource_admin | autovacuum_work_mem                 | ALTER SYSTEM   | f
+ regress_host_resource_admin | autovacuum_work_mem                 | SET VALUE      | f
+ regress_host_resource_admin | hash_mem_multiplier                 | ALTER SYSTEM   | f
+ regress_host_resource_admin | hash_mem_multiplier                 | SET VALUE      | f
+ regress_host_resource_admin | logical_decoding_work_mem           | ALTER SYSTEM   | f
+ regress_host_resource_admin | logical_decoding_work_mem           | SET VALUE      | f
+ regress_host_resource_admin | maintenance_work_mem                | ALTER SYSTEM   | f
+ regress_host_resource_admin | maintenance_work_mem                | SET VALUE      | f
+ regress_host_resource_admin | max_stack_depth                     | ALTER SYSTEM   | f
+ regress_host_resource_admin | max_stack_depth                     | SET VALUE      | f
+ regress_host_resource_admin | min_dynamic_shared_memory           | ALTER SYSTEM   | f
+ regress_host_resource_admin | min_dynamic_shared_memory           | SET VALUE      | f
+ regress_host_resource_admin | shared_buffers                      | ALTER SYSTEM   | f
+ regress_host_resource_admin | shared_buffers                      | SET VALUE      | f
+ regress_host_resource_admin | temp_buffers                        | ALTER SYSTEM   | f
+ regress_host_resource_admin | temp_buffers                        | SET VALUE      | f
+ regress_host_resource_admin | temp_file_limit                     | ALTER SYSTEM   | f
+ regress_host_resource_admin | temp_file_limit                     | SET VALUE      | f
+ regress_host_resource_admin | work_mem                            | ALTER SYSTEM   | f
+ regress_host_resource_admin | work_mem                            | SET VALUE      | f
+                             | DateStyle                           | SET VALUE      | f
+                             | IntervalStyle                       | SET VALUE      | f
+                             | TimeZone                            | SET VALUE      | f
+                             | application_name                    | SET VALUE      | f
+                             | array_nulls                         | SET VALUE      | f
+                             | backend_flush_after                 | SET VALUE      | f
+                             | backslash_quote                     | SET VALUE      | f
+                             | bytea_output                        | SET VALUE      | f
+                             | check_function_bodies               | SET VALUE      | f
+                             | client_connection_check_interval    | SET VALUE      | f
+                             | client_min_messages                 | SET VALUE      | f
+                             | commit_siblings                     | SET VALUE      | f
+                             | constraint_exclusion                | SET VALUE      | f
+                             | cpu_index_tuple_cost                | SET VALUE      | f
+                             | cpu_operator_cost                   | SET VALUE      | f
+                             | cpu_tuple_cost                      | SET VALUE      | f
+                             | cursor_tuple_fraction               | SET VALUE      | f
+                             | debug_pretty_print                  | SET VALUE      | f
+                             | debug_print_parse                   | SET VALUE      | f
+                             | debug_print_plan                    | SET VALUE      | f
+                             | debug_print_rewritten               | SET VALUE      | f
+                             | default_statistics_target           | SET VALUE      | f
+                             | default_table_access_method         | SET VALUE      | f
+                             | default_tablespace                  | SET VALUE      | f
+                             | default_text_search_config          | SET VALUE      | f
+                             | default_toast_compression           | SET VALUE      | f
+                             | default_transaction_deferrable      | SET VALUE      | f
+                             | default_transaction_isolation       | SET VALUE      | f
+                             | default_transaction_read_only       | SET VALUE      | f
+                             | default_with_oids                   | SET VALUE      | f
+                             | effective_cache_size                | SET VALUE      | f
+                             | effective_io_concurrency            | SET VALUE      | f
+                             | enable_async_append                 | SET VALUE      | f
+                             | enable_bitmapscan                   | SET VALUE      | f
+                             | enable_gathermerge                  | SET VALUE      | f
+                             | enable_hashagg                      | SET VALUE      | f
+                             | enable_hashjoin                     | SET VALUE      | f
+                             | enable_incremental_sort             | SET VALUE      | f
+                             | enable_indexonlyscan                | SET VALUE      | f
+                             | enable_indexscan                    | SET VALUE      | f
+                             | enable_material                     | SET VALUE      | f
+                             | enable_memoize                      | SET VALUE      | f
+                             | enable_mergejoin                    | SET VALUE      | f
+                             | enable_nestloop                     | SET VALUE      | f
+                             | enable_parallel_append              | SET VALUE      | f
+                             | enable_parallel_hash                | SET VALUE      | f
+                             | enable_partition_pruning            | SET VALUE      | f
+                             | enable_partitionwise_aggregate      | SET VALUE      | f
+                             | enable_partitionwise_join           | SET VALUE      | f
+                             | enable_seqscan                      | SET VALUE      | f
+                             | enable_sort                         | SET VALUE      | f
+                             | enable_tidscan                      | SET VALUE      | f
+                             | escape_string_warning               | SET VALUE      | f
+                             | exit_on_error                       | SET VALUE      | f
+                             | extra_float_digits                  | SET VALUE      | f
+                             | force_parallel_mode                 | SET VALUE      | f
+                             | from_collapse_limit                 | SET VALUE      | f
+                             | geqo                                | SET VALUE      | f
+                             | geqo_effort                         | SET VALUE      | f
+                             | geqo_generations                    | SET VALUE      | f
+                             | geqo_pool_size                      | SET VALUE      | f
+                             | geqo_seed                           | SET VALUE      | f
+                             | geqo_selection_bias                 | SET VALUE      | f
+                             | geqo_threshold                      | SET VALUE      | f
+                             | gin_fuzzy_search_limit              | SET VALUE      | f
+                             | gin_pending_list_limit              | SET VALUE      | f
+                             | hash_mem_multiplier                 | SET VALUE      | f
+                             | idle_in_transaction_session_timeout | SET VALUE      | f
+                             | idle_session_timeout                | SET VALUE      | f
+                             | jit                                 | SET VALUE      | f
+                             | jit_above_cost                      | SET VALUE      | f
+                             | jit_expressions                     | SET VALUE      | f
+                             | jit_inline_above_cost               | SET VALUE      | f
+                             | jit_optimize_above_cost             | SET VALUE      | f
+                             | jit_tuple_deforming                 | SET VALUE      | f
+                             | join_collapse_limit                 | SET VALUE      | f
+                             | lc_monetary                         | SET VALUE      | f
+                             | lc_numeric                          | SET VALUE      | f
+                             | lc_time                             | SET VALUE      | f
+                             | local_preload_libraries             | SET VALUE      | f
+                             | lock_timeout                        | SET VALUE      | f
+                             | log_parameter_max_length_on_error   | SET VALUE      | f
+                             | logical_decoding_work_mem           | SET VALUE      | f
+                             | maintenance_io_concurrency          | SET VALUE      | f
+                             | maintenance_work_mem                | SET VALUE      | f
+                             | max_parallel_maintenance_workers    | SET VALUE      | f
+                             | max_parallel_workers                | SET VALUE      | f
+                             | max_parallel_workers_per_gather     | SET VALUE      | f
+                             | min_parallel_index_scan_size        | SET VALUE      | f
+                             | min_parallel_table_scan_size        | SET VALUE      | f
+                             | optimize_bounded_sort               | SET VALUE      | f
+                             | parallel_leader_participation       | SET VALUE      | f
+                             | parallel_setup_cost                 | SET VALUE      | f
+                             | parallel_tuple_cost                 | SET VALUE      | f
+                             | password_encryption                 | SET VALUE      | f
+                             | plan_cache_mode                     | SET VALUE      | f
+                             | quote_all_identifiers               | SET VALUE      | f
+                             | random_page_cost                    | SET VALUE      | f
+                             | role                                | SET VALUE      | f
+                             | row_security                        | SET VALUE      | f
+                             | search_path                         | SET VALUE      | f
+                             | seed                                | SET VALUE      | f
+                             | seq_page_cost                       | SET VALUE      | f
+                             | ssl_renegotiation_limit             | SET VALUE      | f
+                             | standard_conforming_strings         | SET VALUE      | f
+                             | statement_timeout                   | SET VALUE      | f
+                             | synchronize_seqscans                | SET VALUE      | f
+                             | synchronous_commit                  | SET VALUE      | f
+                             | tcp_keepalives_count                | SET VALUE      | f
+                             | tcp_keepalives_idle                 | SET VALUE      | f
+                             | tcp_keepalives_interval             | SET VALUE      | f
+                             | tcp_user_timeout                    | SET VALUE      | f
+                             | temp_buffers                        | SET VALUE      | f
+                             | temp_tablespaces                    | SET VALUE      | f
+                             | timezone_abbreviations              | SET VALUE      | f
+                             | trace_notify                        | SET VALUE      | f
+                             | trace_sort                          | SET VALUE      | f
+                             | trace_syncscan                      | SET VALUE      | f
+                             | transaction_deferrable              | SET VALUE      | f
+                             | transaction_isolation               | SET VALUE      | f
+                             | transaction_read_only               | SET VALUE      | f
+                             | transform_null_equals               | SET VALUE      | f
+                             | vacuum_cost_delay                   | SET VALUE      | f
+                             | vacuum_cost_limit                   | SET VALUE      | f
+                             | vacuum_cost_page_dirty              | SET VALUE      | f
+                             | vacuum_cost_page_hit                | SET VALUE      | f
+                             | vacuum_cost_page_miss               | SET VALUE      | f
+                             | vacuum_failsafe_age                 | SET VALUE      | f
+                             | vacuum_freeze_min_age               | SET VALUE      | f
+                             | vacuum_freeze_table_age             | SET VALUE      | f
+                             | vacuum_multixact_failsafe_age       | SET VALUE      | f
+                             | vacuum_multixact_freeze_min_age     | SET VALUE      | f
+                             | vacuum_multixact_freeze_table_age   | SET VALUE      | f
+                             | wal_sender_timeout                  | SET VALUE      | f
+                             | wal_skip_threshold                  | SET VALUE      | f
+                             | work_mem                            | SET VALUE      | f
+                             | xmlbinary                           | SET VALUE      | f
+                             | xmloption                           | SET VALUE      | f
+(158 rows)
+
+-- Check the new role now has privilges on settings
+SELECT has_setting_privilege('regress_host_resource_admin', 'work_mem', 'SET VALUE, ALTER SYSTEM');
+ has_setting_privilege 
+-----------------------
+ t
+(1 row)
+
+SELECT has_setting_privilege('regress_host_resource_admin', 'work_mem', 'SET VALUE');
+ has_setting_privilege 
+-----------------------
+ t
+(1 row)
+
+SELECT has_setting_privilege('regress_host_resource_admin', 'work_mem', 'ALTER SYSTEM');
+ has_setting_privilege 
+-----------------------
+ t
+(1 row)
+
+SELECT has_setting_privilege('regress_host_resource_admin', 'work_mem', 'SET VALUE WITH GRANT OPTION, ALTER SYSTEM WITH GRANT OPTION');
+ has_setting_privilege 
+-----------------------
+ f
+(1 row)
+
+-- Check again the inappropriate and nonsense privilege types.  The prior similar check
+-- was performed before any entry for work_mem existed.
+SELECT has_setting_privilege('regress_host_resource_admin', 'work_mem', 'SELECT, UPDATE, CREATE');
+ERROR:  unrecognized privilege type: "SELECT"
+SELECT has_setting_privilege('regress_host_resource_admin', 'work_mem', 'USAGE');
+ERROR:  unrecognized privilege type: "USAGE"
+SELECT has_setting_privilege('regress_host_resource_admin', 'work_mem', 'WHATEVER');
+ERROR:  unrecognized privilege type: "WHATEVER"
+SELECT has_setting_privilege('regress_host_resource_admin', 'work_mem', 'WHATEVER WITH GRANT OPTION');
+ERROR:  unrecognized privilege type: "WHATEVER WITH GRANT OPTION"
+-- Check other function signatures
+SELECT has_setting_privilege('regress_host_resource_admin',
+							 (SELECT oid FROM pg_catalog.pg_setting_acl WHERE setting = 'max_stack_depth'),
+							 'SET VALUE');
+ has_setting_privilege 
+-----------------------
+ t
+(1 row)
+
+SELECT has_setting_privilege((SELECT oid FROM pg_catalog.pg_authid WHERE rolname = 'regress_host_resource_admin'),
+							 'max_stack_depth',
+							 'SET VALUE');
+ has_setting_privilege 
+-----------------------
+ t
+(1 row)
+
+SELECT has_setting_privilege((SELECT oid FROM pg_catalog.pg_authid WHERE rolname = 'regress_host_resource_admin'),
+							 (SELECT oid FROM pg_catalog.pg_setting_acl WHERE setting = 'max_stack_depth'),
+							 'SET VALUE');
+ has_setting_privilege 
+-----------------------
+ t
+(1 row)
+
+SELECT has_setting_privilege('hash_mem_multiplier', 'set value');
+ has_setting_privilege 
+-----------------------
+ t
+(1 row)
+
+SELECT has_setting_privilege((SELECT oid FROM pg_catalog.pg_setting_acl WHERE setting = 'work_mem'),
+							 'alter system with grant option');
+ has_setting_privilege 
+-----------------------
+ t
+(1 row)
+
+-- Check setting privileges show up in view
+SELECT grantee, setting, privilege_type, is_grantable
+	FROM pg_catalog.pg_setting_privileges
+	ORDER BY grantee, setting, privilege_type;
+           grantee           |               setting               | privilege_type | is_grantable 
+-----------------------------+-------------------------------------+----------------+--------------
+ regress_host_resource_admin | autovacuum_work_mem                 | ALTER SYSTEM   | f
+ regress_host_resource_admin | autovacuum_work_mem                 | SET VALUE      | f
+ regress_host_resource_admin | hash_mem_multiplier                 | ALTER SYSTEM   | f
+ regress_host_resource_admin | hash_mem_multiplier                 | SET VALUE      | f
+ regress_host_resource_admin | logical_decoding_work_mem           | ALTER SYSTEM   | f
+ regress_host_resource_admin | logical_decoding_work_mem           | SET VALUE      | f
+ regress_host_resource_admin | maintenance_work_mem                | ALTER SYSTEM   | f
+ regress_host_resource_admin | maintenance_work_mem                | SET VALUE      | f
+ regress_host_resource_admin | max_stack_depth                     | ALTER SYSTEM   | f
+ regress_host_resource_admin | max_stack_depth                     | SET VALUE      | f
+ regress_host_resource_admin | min_dynamic_shared_memory           | ALTER SYSTEM   | f
+ regress_host_resource_admin | min_dynamic_shared_memory           | SET VALUE      | f
+ regress_host_resource_admin | shared_buffers                      | ALTER SYSTEM   | f
+ regress_host_resource_admin | shared_buffers                      | SET VALUE      | f
+ regress_host_resource_admin | temp_buffers                        | ALTER SYSTEM   | f
+ regress_host_resource_admin | temp_buffers                        | SET VALUE      | f
+ regress_host_resource_admin | temp_file_limit                     | ALTER SYSTEM   | f
+ regress_host_resource_admin | temp_file_limit                     | SET VALUE      | f
+ regress_host_resource_admin | work_mem                            | ALTER SYSTEM   | f
+ regress_host_resource_admin | work_mem                            | SET VALUE      | f
+                             | DateStyle                           | SET VALUE      | f
+                             | IntervalStyle                       | SET VALUE      | f
+                             | TimeZone                            | SET VALUE      | f
+                             | application_name                    | SET VALUE      | f
+                             | array_nulls                         | SET VALUE      | f
+                             | backend_flush_after                 | SET VALUE      | f
+                             | backslash_quote                     | SET VALUE      | f
+                             | bytea_output                        | SET VALUE      | f
+                             | check_function_bodies               | SET VALUE      | f
+                             | client_connection_check_interval    | SET VALUE      | f
+                             | client_min_messages                 | SET VALUE      | f
+                             | commit_siblings                     | SET VALUE      | f
+                             | constraint_exclusion                | SET VALUE      | f
+                             | cpu_index_tuple_cost                | SET VALUE      | f
+                             | cpu_operator_cost                   | SET VALUE      | f
+                             | cpu_tuple_cost                      | SET VALUE      | f
+                             | cursor_tuple_fraction               | SET VALUE      | f
+                             | debug_pretty_print                  | SET VALUE      | f
+                             | debug_print_parse                   | SET VALUE      | f
+                             | debug_print_plan                    | SET VALUE      | f
+                             | debug_print_rewritten               | SET VALUE      | f
+                             | default_statistics_target           | SET VALUE      | f
+                             | default_table_access_method         | SET VALUE      | f
+                             | default_tablespace                  | SET VALUE      | f
+                             | default_text_search_config          | SET VALUE      | f
+                             | default_toast_compression           | SET VALUE      | f
+                             | default_transaction_deferrable      | SET VALUE      | f
+                             | default_transaction_isolation       | SET VALUE      | f
+                             | default_transaction_read_only       | SET VALUE      | f
+                             | default_with_oids                   | SET VALUE      | f
+                             | effective_cache_size                | SET VALUE      | f
+                             | effective_io_concurrency            | SET VALUE      | f
+                             | enable_async_append                 | SET VALUE      | f
+                             | enable_bitmapscan                   | SET VALUE      | f
+                             | enable_gathermerge                  | SET VALUE      | f
+                             | enable_hashagg                      | SET VALUE      | f
+                             | enable_hashjoin                     | SET VALUE      | f
+                             | enable_incremental_sort             | SET VALUE      | f
+                             | enable_indexonlyscan                | SET VALUE      | f
+                             | enable_indexscan                    | SET VALUE      | f
+                             | enable_material                     | SET VALUE      | f
+                             | enable_memoize                      | SET VALUE      | f
+                             | enable_mergejoin                    | SET VALUE      | f
+                             | enable_nestloop                     | SET VALUE      | f
+                             | enable_parallel_append              | SET VALUE      | f
+                             | enable_parallel_hash                | SET VALUE      | f
+                             | enable_partition_pruning            | SET VALUE      | f
+                             | enable_partitionwise_aggregate      | SET VALUE      | f
+                             | enable_partitionwise_join           | SET VALUE      | f
+                             | enable_seqscan                      | SET VALUE      | f
+                             | enable_sort                         | SET VALUE      | f
+                             | enable_tidscan                      | SET VALUE      | f
+                             | escape_string_warning               | SET VALUE      | f
+                             | exit_on_error                       | SET VALUE      | f
+                             | extra_float_digits                  | SET VALUE      | f
+                             | force_parallel_mode                 | SET VALUE      | f
+                             | from_collapse_limit                 | SET VALUE      | f
+                             | geqo                                | SET VALUE      | f
+                             | geqo_effort                         | SET VALUE      | f
+                             | geqo_generations                    | SET VALUE      | f
+                             | geqo_pool_size                      | SET VALUE      | f
+                             | geqo_seed                           | SET VALUE      | f
+                             | geqo_selection_bias                 | SET VALUE      | f
+                             | geqo_threshold                      | SET VALUE      | f
+                             | gin_fuzzy_search_limit              | SET VALUE      | f
+                             | gin_pending_list_limit              | SET VALUE      | f
+                             | hash_mem_multiplier                 | SET VALUE      | f
+                             | idle_in_transaction_session_timeout | SET VALUE      | f
+                             | idle_session_timeout                | SET VALUE      | f
+                             | jit                                 | SET VALUE      | f
+                             | jit_above_cost                      | SET VALUE      | f
+                             | jit_expressions                     | SET VALUE      | f
+                             | jit_inline_above_cost               | SET VALUE      | f
+                             | jit_optimize_above_cost             | SET VALUE      | f
+                             | jit_tuple_deforming                 | SET VALUE      | f
+                             | join_collapse_limit                 | SET VALUE      | f
+                             | lc_monetary                         | SET VALUE      | f
+                             | lc_numeric                          | SET VALUE      | f
+                             | lc_time                             | SET VALUE      | f
+                             | local_preload_libraries             | SET VALUE      | f
+                             | lock_timeout                        | SET VALUE      | f
+                             | log_parameter_max_length_on_error   | SET VALUE      | f
+                             | logical_decoding_work_mem           | SET VALUE      | f
+                             | maintenance_io_concurrency          | SET VALUE      | f
+                             | maintenance_work_mem                | SET VALUE      | f
+                             | max_parallel_maintenance_workers    | SET VALUE      | f
+                             | max_parallel_workers                | SET VALUE      | f
+                             | max_parallel_workers_per_gather     | SET VALUE      | f
+                             | min_parallel_index_scan_size        | SET VALUE      | f
+                             | min_parallel_table_scan_size        | SET VALUE      | f
+                             | optimize_bounded_sort               | SET VALUE      | f
+                             | parallel_leader_participation       | SET VALUE      | f
+                             | parallel_setup_cost                 | SET VALUE      | f
+                             | parallel_tuple_cost                 | SET VALUE      | f
+                             | password_encryption                 | SET VALUE      | f
+                             | plan_cache_mode                     | SET VALUE      | f
+                             | quote_all_identifiers               | SET VALUE      | f
+                             | random_page_cost                    | SET VALUE      | f
+                             | role                                | SET VALUE      | f
+                             | row_security                        | SET VALUE      | f
+                             | search_path                         | SET VALUE      | f
+                             | seed                                | SET VALUE      | f
+                             | seq_page_cost                       | SET VALUE      | f
+                             | ssl_renegotiation_limit             | SET VALUE      | f
+                             | standard_conforming_strings         | SET VALUE      | f
+                             | statement_timeout                   | SET VALUE      | f
+                             | synchronize_seqscans                | SET VALUE      | f
+                             | synchronous_commit                  | SET VALUE      | f
+                             | tcp_keepalives_count                | SET VALUE      | f
+                             | tcp_keepalives_idle                 | SET VALUE      | f
+                             | tcp_keepalives_interval             | SET VALUE      | f
+                             | tcp_user_timeout                    | SET VALUE      | f
+                             | temp_buffers                        | SET VALUE      | f
+                             | temp_tablespaces                    | SET VALUE      | f
+                             | timezone_abbreviations              | SET VALUE      | f
+                             | trace_notify                        | SET VALUE      | f
+                             | trace_sort                          | SET VALUE      | f
+                             | trace_syncscan                      | SET VALUE      | f
+                             | transaction_deferrable              | SET VALUE      | f
+                             | transaction_isolation               | SET VALUE      | f
+                             | transaction_read_only               | SET VALUE      | f
+                             | transform_null_equals               | SET VALUE      | f
+                             | vacuum_cost_delay                   | SET VALUE      | f
+                             | vacuum_cost_limit                   | SET VALUE      | f
+                             | vacuum_cost_page_dirty              | SET VALUE      | f
+                             | vacuum_cost_page_hit                | SET VALUE      | f
+                             | vacuum_cost_page_miss               | SET VALUE      | f
+                             | vacuum_failsafe_age                 | SET VALUE      | f
+                             | vacuum_freeze_min_age               | SET VALUE      | f
+                             | vacuum_freeze_table_age             | SET VALUE      | f
+                             | vacuum_multixact_failsafe_age       | SET VALUE      | f
+                             | vacuum_multixact_freeze_min_age     | SET VALUE      | f
+                             | vacuum_multixact_freeze_table_age   | SET VALUE      | f
+                             | wal_sender_timeout                  | SET VALUE      | f
+                             | wal_skip_threshold                  | SET VALUE      | f
+                             | work_mem                            | SET VALUE      | f
+                             | xmlbinary                           | SET VALUE      | f
+                             | xmloption                           | SET VALUE      | f
+(158 rows)
+
+-- Perform all operations as user 'regress_host_resource_admin' --
+SET SESSION AUTHORIZATION regress_host_resource_admin;
+ALTER SYSTEM SET autovacuum_work_mem = 32;  -- ok, privileges have been granted
+ALTER SYSTEM SET ignore_system_indexes = OFF;  -- fail, insufficient privileges
+ERROR:  permission denied to set parameter "ignore_system_indexes"
+ALTER SYSTEM RESET autovacuum_multixact_freeze_max_age;  -- fail, insufficient privileges
+ERROR:  permission denied to set parameter "autovacuum_multixact_freeze_max_age"
+SET jit_provider = 'llvmjit';  -- fail, insufficient privileges
+ERROR:  parameter "jit_provider" cannot be changed without restarting the server
+ALTER SYSTEM SET shared_buffers = 50;  -- ok
+ALTER SYSTEM RESET shared_buffers;  -- ok
+SET autovacuum_work_mem = 50;  -- cannot be changed now
+ERROR:  parameter "autovacuum_work_mem" cannot be changed now
+ALTER SYSTEM RESET temp_file_limit;  -- ok
+SET TimeZone = 'Europe/Helsinki';  -- ok
+ALTER SYSTEM RESET autovacuum_work_mem;  -- ok, privileges have been granted
+RESET TimeZone;  -- ok
+SET SESSION AUTHORIZATION regress_admin;
+DROP ROLE regress_host_resource_admin;  -- fail, privileges remain
+ERROR:  role "regress_host_resource_admin" cannot be dropped because some objects depend on it
+DETAIL:  privileges for setting temp_buffers
+privileges for setting work_mem
+privileges for setting maintenance_work_mem
+privileges for setting logical_decoding_work_mem
+privileges for setting hash_mem_multiplier
+privileges for setting autovacuum_work_mem
+privileges for setting max_stack_depth
+privileges for setting min_dynamic_shared_memory
+privileges for setting shared_buffers
+privileges for setting temp_file_limit
+-- Use "revoke" to remove the privileges and allow the role to be dropped
+REVOKE SET VALUE, ALTER SYSTEM ON
+	autovacuum_work_mem, hash_mem_multiplier, logical_decoding_work_mem,
+	maintenance_work_mem, max_stack_depth, min_dynamic_shared_memory,
+	shared_buffers, temp_buffers, temp_file_limit, work_mem
+FROM regress_host_resource_admin;
+DROP ROLE regress_host_resource_admin;  -- ok
+-- Try that again, but use "drop owned by" instead of "revoke"
+CREATE ROLE regress_host_resource_admin NOSUPERUSER;
+SET SESSION AUTHORIZATION regress_host_resource_admin;
+ALTER SYSTEM SET autovacuum_work_mem = 32;  -- fail, privileges not yet granted
+ERROR:  permission denied to set parameter "autovacuum_work_mem"
+SET SESSION AUTHORIZATION regress_admin;
+GRANT SET VALUE, ALTER SYSTEM ON
+	autovacuum_work_mem, hash_mem_multiplier, logical_decoding_work_mem,
+	maintenance_work_mem, max_stack_depth, min_dynamic_shared_memory,
+	shared_buffers, temp_buffers, temp_file_limit, work_mem
+TO regress_host_resource_admin;
+DROP ROLE regress_host_resource_admin;  -- fail, privileges remain
+ERROR:  role "regress_host_resource_admin" cannot be dropped because some objects depend on it
+DETAIL:  privileges for setting temp_buffers
+privileges for setting work_mem
+privileges for setting maintenance_work_mem
+privileges for setting logical_decoding_work_mem
+privileges for setting hash_mem_multiplier
+privileges for setting autovacuum_work_mem
+privileges for setting max_stack_depth
+privileges for setting min_dynamic_shared_memory
+privileges for setting shared_buffers
+privileges for setting temp_file_limit
+DROP OWNED BY regress_host_resource_admin RESTRICT; -- cascade should not be needed
+SET SESSION AUTHORIZATION regress_host_resource_admin;
+ALTER SYSTEM SET autovacuum_work_mem = 32;  -- fail, "drop owned" has dropped privileges
+ERROR:  permission denied to set parameter "autovacuum_work_mem"
+SET SESSION AUTHORIZATION regress_admin;
+DROP ROLE regress_host_resource_admin;  -- ok
+-- Try that again, but use "reassign owned by" this time
+CREATE ROLE regress_host_resource_admin NOSUPERUSER;
+CREATE ROLE regress_host_resource_newadmin NOSUPERUSER;
+GRANT SET VALUE, ALTER SYSTEM ON
+	autovacuum_work_mem, hash_mem_multiplier, logical_decoding_work_mem,
+	maintenance_work_mem, max_stack_depth, min_dynamic_shared_memory,
+	shared_buffers, temp_buffers, temp_file_limit, work_mem
+TO regress_host_resource_admin;
+REASSIGN OWNED BY regress_host_resource_admin TO regress_host_resource_newadmin;
+SET SESSION AUTHORIZATION regress_host_resource_admin;
+ALTER SYSTEM SET autovacuum_work_mem = 32;  -- ok, "reassign owned" did not change privileges
+SET SESSION AUTHORIZATION regress_admin;
+DROP ROLE regress_host_resource_admin;  -- fail, privileges remain
+ERROR:  role "regress_host_resource_admin" cannot be dropped because some objects depend on it
+DETAIL:  privileges for setting temp_buffers
+privileges for setting work_mem
+privileges for setting maintenance_work_mem
+privileges for setting logical_decoding_work_mem
+privileges for setting hash_mem_multiplier
+privileges for setting autovacuum_work_mem
+privileges for setting max_stack_depth
+privileges for setting min_dynamic_shared_memory
+privileges for setting shared_buffers
+privileges for setting temp_file_limit
+DROP ROLE regress_host_resource_newadmin;  -- ok, nothing was transferred
+-- Use "drop owned by" so we can drop the role
+DROP OWNED BY regress_host_resource_admin;  -- ok
+DROP ROLE regress_host_resource_admin;  -- ok
+-- Create non-superuser with privileges to configure plgsql custom variables
+CREATE ROLE regress_plpgsql_admin NOSUPERUSER;
+-- Perform all operations as user 'regress_plpgsql_admin' --
+SET SESSION AUTHORIZATION regress_plpgsql_admin;
+SET plpgsql.extra_errors TO 'all';
+SET plpgsql.extra_warnings TO 'all';
+RESET plpgsql.extra_errors;
+RESET plpgsql.extra_warnings;
+ALTER SYSTEM SET plpgsql.extra_errors TO 'all';
+ERROR:  permission denied to set parameter "plpgsql.extra_errors"
+ALTER SYSTEM SET plpgsql.extra_warnings TO 'all';
+ERROR:  permission denied to set parameter "plpgsql.extra_warnings"
+ALTER SYSTEM RESET plpgsql.extra_errors;
+ERROR:  permission denied to set parameter "plpgsql.extra_errors"
+ALTER SYSTEM RESET plpgsql.extra_warnings;
+ERROR:  permission denied to set parameter "plpgsql.extra_warnings"
+SET SESSION AUTHORIZATION regress_admin;
+GRANT SET VALUE, ALTER SYSTEM ON
+	plpgsql.extra_warnings, plpgsql.extra_errors
+TO regress_plpgsql_admin;
+-- Perform all operations as user 'regress_plpgsql_admin' --
+SET SESSION AUTHORIZATION regress_plpgsql_admin;  -- ok
+SET plpgsql.extra_errors TO 'all';  -- ok
+SET plpgsql.extra_warnings TO 'all';  -- ok
+RESET plpgsql.extra_errors;  -- ok
+RESET plpgsql.extra_warnings;  -- ok
+ALTER SYSTEM SET plpgsql.extra_errors TO 'all';  -- ok
+ALTER SYSTEM SET plpgsql.extra_warnings TO 'all';  -- ok
+ALTER SYSTEM RESET plpgsql.extra_errors;  -- ok
+ALTER SYSTEM RESET plpgsql.extra_warnings;  -- ok
+SET SESSION AUTHORIZATION regress_admin;
+DROP ROLE regress_plpgsql_admin;  -- fail, privileges remain
+ERROR:  role "regress_plpgsql_admin" cannot be dropped because some objects depend on it
+DETAIL:  privileges for setting plpgsql.extra_warnings
+privileges for setting plpgsql.extra_errors
+REVOKE SET VALUE, ALTER SYSTEM ON
+	plpgsql.extra_warnings, plpgsql.extra_errors
+FROM regress_plpgsql_admin;
+DROP ROLE regress_plpgsql_admin;  -- ok
diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out
index 291e21d7a6..1c762cea54 100644
--- a/src/test/regress/expected/privileges.out
+++ b/src/test/regress/expected/privileges.out
@@ -54,6 +54,33 @@ REVOKE pg_read_all_settings FROM regress_priv_user8;
 DROP USER regress_priv_user10;
 DROP USER regress_priv_user9;
 DROP USER regress_priv_user8;
+GRANT SET VALUE ON enable_memoize TO regress_priv_user6;
+GRANT SET VALUE ON enable_nestloop TO regress_priv_user6;
+SET ROLE regress_priv_user6;
+SET enable_memoize TO false;
+SET enable_nestloop TO false;
+RESET enable_memoize;
+RESET enable_nestloop;
+RESET ROLE;
+GRANT ALTER SYSTEM ON enable_seqscan TO regress_priv_user7; -- ok
+GRANT ALTER SYSTEM ON sort_mem TO regress_priv_user7; -- old name for "work_mem"
+GRANT ALTER SYSTEM ON maintenance_work_mem TO regress_priv_user7; -- ok
+GRANT ALTER SYSTEM ON no_such_param TO regress_priv_user7; -- ok
+GRANT ALTER SYSTEM ON no_such_extension.no_such_param TO regress_priv_user7; -- ok
+GRANT ALTER SYSTEM ON no_such_extension.no_such_param.longer.than.maximum.namedata.length TO regress_priv_user7; -- ok
+GRANT ALTER SYSTEM ON "" TO regress_priv_user7; -- bad name
+ERROR:  zero-length delimited identifier at or near """"
+LINE 1: GRANT ALTER SYSTEM ON "" TO regress_priv_user7;
+                              ^
+GRANT ALTER SYSTEM ON " " TO regress_priv_user7; -- bad name
+ERROR:  invalid setting name " "
+GRANT ALTER SYSTEM ON " foo " TO regress_priv_user7; -- bad name
+ERROR:  invalid setting name " foo "
+GRANT SELECT ON public.persons2 TO regress_priv_user7;
+SET ROLE regress_priv_user7;
+ALTER SYSTEM SET enable_seqscan = OFF;
+ALTER SYSTEM RESET enable_seqscan;
+RESET ROLE;
 CREATE GROUP regress_priv_group1;
 CREATE GROUP regress_priv_group2 WITH USER regress_priv_user1, regress_priv_user2;
 ALTER GROUP regress_priv_group1 ADD USER regress_priv_user4;
@@ -2355,10 +2382,32 @@ DROP USER regress_priv_user2;
 DROP USER regress_priv_user3;
 DROP USER regress_priv_user4;
 DROP USER regress_priv_user5;
-DROP USER regress_priv_user6;
-DROP USER regress_priv_user7;
+DROP USER regress_priv_user6; -- privileges remain
+ERROR:  role "regress_priv_user6" cannot be dropped because some objects depend on it
+DETAIL:  privileges for setting enable_memoize
+privileges for setting enable_nestloop
+DROP USER regress_priv_user7; -- privileges remain
+ERROR:  role "regress_priv_user7" cannot be dropped because some objects depend on it
+DETAIL:  privileges for setting enable_seqscan
+privileges for setting work_mem
+privileges for setting maintenance_work_mem
+privileges for table persons2
+privileges for setting no_such_param
+privileges for setting no_such_extension.no_such_param
+privileges for setting no_such_extension.no_such_param.longer.than.maximum.namedata.length
 DROP USER regress_priv_user8; -- does not exist
 ERROR:  role "regress_priv_user8" does not exist
+REVOKE SELECT ON public.persons2 FROM regress_priv_user7;
+REVOKE ALTER SYSTEM ON enable_seqscan FROM regress_priv_user7; -- ok
+REVOKE ALTER SYSTEM ON work_mem FROM regress_priv_user7; -- ok, use new name
+REVOKE ALTER SYSTEM ON vacuum_mem FROM regress_priv_user7; -- ok, use old name
+REVOKE ALTER SYSTEM ON no_such_param FROM regress_priv_user7; -- ok
+REVOKE ALTER SYSTEM ON no_such_extension.no_such_param FROM regress_priv_user7; -- ok
+REVOKE ALTER SYSTEM ON no_such_extension.no_such_param.longer.than.maximum.namedata.length FROM regress_priv_user7; -- ok
+DROP USER regress_priv_user7; -- ok
+REVOKE SET VALUE ON enable_memoize FROM regress_priv_user6;
+REVOKE SET VALUE ON enable_nestloop FROM regress_priv_user6;
+DROP USER regress_priv_user6; -- ok
 -- permissions with LOCK TABLE
 CREATE USER regress_locktable_user;
 CREATE TABLE lock_table (a int);
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index ac468568a1..0d0e3ff410 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1686,6 +1686,19 @@ pg_sequences| SELECT n.nspname AS schemaname,
      JOIN pg_class c ON ((c.oid = s.seqrelid)))
      LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
   WHERE ((NOT pg_is_other_temp_schema(n.oid)) AND (c.relkind = 'S'::"char"));
+pg_setting_privileges| SELECT grantor.rolname AS grantor,
+    grantee.rolname AS grantee,
+    set_acl.setting,
+    acl.privilege_type,
+    acl.is_grantable
+   FROM pg_setting_acl set_acl,
+    ((LATERAL ( SELECT aclexplode.grantor,
+            aclexplode.grantee,
+            aclexplode.privilege_type,
+            aclexplode.is_grantable
+           FROM aclexplode(set_acl.setacl) aclexplode(grantor, grantee, privilege_type, is_grantable)) acl
+     LEFT JOIN pg_authid grantee ON ((acl.grantee = grantee.oid)))
+     LEFT JOIN pg_authid grantor ON ((acl.grantor = grantor.oid)));
 pg_settings| SELECT a.name,
     a.setting,
     a.unit,
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 6d8f524ae9..4dfa0642c0 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -86,7 +86,7 @@ test: brin_bloom brin_multi
 # psql depends on create_am
 # amutils depends on geometry, create_index_spgist, hash_index, brin
 # ----------
-test: create_table_like alter_generic alter_operator misc async dbsize misc_functions sysviews tsrf tid tidscan tidrangescan collate.icu.utf8 incremental_sort create_role
+test: create_table_like alter_generic alter_operator misc async dbsize misc_functions sysviews tsrf tid tidscan tidrangescan collate.icu.utf8 incremental_sort create_role guc_privs
 
 # collate.*.utf8 tests cannot be run in parallel with each other
 test: rules psql psql_crosstab amutils stats_ext collate.linux.utf8
diff --git a/src/test/regress/sql/guc_privs.sql b/src/test/regress/sql/guc_privs.sql
new file mode 100644
index 0000000000..076c136563
--- /dev/null
+++ b/src/test/regress/sql/guc_privs.sql
@@ -0,0 +1,185 @@
+-- Test superuser
+-- Superuser DBA
+CREATE ROLE regress_admin SUPERUSER;
+-- Perform all operations as user 'regress_admin' --
+SET SESSION AUTHORIZATION regress_admin;
+-- PGC_BACKEND
+SET ignore_system_indexes = OFF;  -- fail, cannot be set after connection start
+RESET ignore_system_indexes;  -- fail, cannot be set after connection start
+ALTER SYSTEM SET ignore_system_indexes = OFF;  -- ok
+ALTER SYSTEM RESET ignore_system_indexes;  -- ok
+-- PGC_INTERNAL
+SET block_size = 50;  -- fail, cannot be changed
+RESET block_size;  -- fail, cannot be changed
+ALTER SYSTEM SET block_size = 50;  -- fail, cannot be changed
+ALTER SYSTEM RESET block_size;  -- fail, cannot be changed
+-- PGC_POSTMASTER
+SET autovacuum_freeze_max_age = 1000050000;  -- fail, requires restart
+RESET autovacuum_freeze_max_age;  -- fail, requires restart
+ALTER SYSTEM SET autovacuum_freeze_max_age = 1000050000;  -- ok
+ALTER SYSTEM RESET autovacuum_freeze_max_age;  -- ok
+ALTER SYSTEM SET config_file = '/usr/local/data/postgresql.conf';  -- fail, cannot be changed
+ALTER SYSTEM RESET config_file;  -- fail, cannot be changed
+-- PGC_SIGHUP
+SET autovacuum = OFF;  -- fail, requires reload
+RESET autovacuum;  -- fail, requires reload
+ALTER SYSTEM SET autovacuum = OFF;  -- ok
+ALTER SYSTEM RESET autovacuum;  -- ok
+-- PGC_SUSET
+SET lc_messages = 'C';  -- ok
+RESET lc_messages;  -- ok
+ALTER SYSTEM SET lc_messages = 'C';  -- ok
+ALTER SYSTEM RESET lc_messages;  -- ok
+-- PGC_SU_BACKEND
+SET jit_debugging_support = OFF;  -- fail, cannot be set after connection start
+RESET jit_debugging_support;  -- fail, cannot be set after connection start
+ALTER SYSTEM SET jit_debugging_support = OFF;  -- ok
+ALTER SYSTEM RESET jit_debugging_support;  -- ok
+-- PGC_USERSET
+SET DateStyle = 'ISO, MDY';  -- ok
+RESET DateStyle;  -- ok
+ALTER SYSTEM SET DateStyle = 'ISO, MDY';  -- ok
+ALTER SYSTEM RESET DateStyle;  -- ok
+ALTER SYSTEM SET ssl_renegotiation_limit = 0;  -- fail, cannot be changed
+ALTER SYSTEM RESET ssl_renegotiation_limit;  -- fail, cannot be changed
+-- Finished testing superuser
+RESET statement_timeout;
+-- Check setting privileges prior to creating any
+SELECT grantee, setting, privilege_type, is_grantable
+	FROM pg_catalog.pg_setting_privileges
+	ORDER BY grantee, setting, privilege_type;
+-- Create non-superuser with privileges to configure host resource usage
+CREATE ROLE regress_host_resource_admin NOSUPERUSER;
+-- Check the new role does not yet have privileges on settings
+SELECT has_setting_privilege('regress_host_resource_admin', 'work_mem', 'SET VALUE, ALTER SYSTEM');
+SELECT has_setting_privilege('regress_host_resource_admin', 'work_mem', 'SET VALUE');
+SELECT has_setting_privilege('regress_host_resource_admin', 'work_mem', 'ALTER SYSTEM');
+-- Check inappropriate and nonsense privilege types
+SELECT has_setting_privilege('regress_host_resource_admin', 'work_mem', 'SELECT, UPDATE, CREATE');
+SELECT has_setting_privilege('regress_host_resource_admin', 'work_mem', 'USAGE');
+SELECT has_setting_privilege('regress_host_resource_admin', 'work_mem', 'WHATEVER');
+-- Grant privileges on settings to the new non-superuser role
+GRANT SET VALUE, ALTER SYSTEM ON
+	autovacuum_work_mem, hash_mem_multiplier, logical_decoding_work_mem,
+	maintenance_work_mem, max_stack_depth, min_dynamic_shared_memory,
+	shared_buffers, temp_buffers, temp_file_limit, work_mem
+TO regress_host_resource_admin;
+-- Check setting privileges after creating some
+SELECT grantee, setting, privilege_type, is_grantable
+	FROM pg_catalog.pg_setting_privileges
+	ORDER BY grantee, setting, privilege_type;
+-- Check the new role now has privilges on settings
+SELECT has_setting_privilege('regress_host_resource_admin', 'work_mem', 'SET VALUE, ALTER SYSTEM');
+SELECT has_setting_privilege('regress_host_resource_admin', 'work_mem', 'SET VALUE');
+SELECT has_setting_privilege('regress_host_resource_admin', 'work_mem', 'ALTER SYSTEM');
+SELECT has_setting_privilege('regress_host_resource_admin', 'work_mem', 'SET VALUE WITH GRANT OPTION, ALTER SYSTEM WITH GRANT OPTION');
+-- Check again the inappropriate and nonsense privilege types.  The prior similar check
+-- was performed before any entry for work_mem existed.
+SELECT has_setting_privilege('regress_host_resource_admin', 'work_mem', 'SELECT, UPDATE, CREATE');
+SELECT has_setting_privilege('regress_host_resource_admin', 'work_mem', 'USAGE');
+SELECT has_setting_privilege('regress_host_resource_admin', 'work_mem', 'WHATEVER');
+SELECT has_setting_privilege('regress_host_resource_admin', 'work_mem', 'WHATEVER WITH GRANT OPTION');
+-- Check other function signatures
+SELECT has_setting_privilege('regress_host_resource_admin',
+							 (SELECT oid FROM pg_catalog.pg_setting_acl WHERE setting = 'max_stack_depth'),
+							 'SET VALUE');
+SELECT has_setting_privilege((SELECT oid FROM pg_catalog.pg_authid WHERE rolname = 'regress_host_resource_admin'),
+							 'max_stack_depth',
+							 'SET VALUE');
+SELECT has_setting_privilege((SELECT oid FROM pg_catalog.pg_authid WHERE rolname = 'regress_host_resource_admin'),
+							 (SELECT oid FROM pg_catalog.pg_setting_acl WHERE setting = 'max_stack_depth'),
+							 'SET VALUE');
+SELECT has_setting_privilege('hash_mem_multiplier', 'set value');
+SELECT has_setting_privilege((SELECT oid FROM pg_catalog.pg_setting_acl WHERE setting = 'work_mem'),
+							 'alter system with grant option');
+-- Check setting privileges show up in view
+SELECT grantee, setting, privilege_type, is_grantable
+	FROM pg_catalog.pg_setting_privileges
+	ORDER BY grantee, setting, privilege_type;
+-- Perform all operations as user 'regress_host_resource_admin' --
+SET SESSION AUTHORIZATION regress_host_resource_admin;
+ALTER SYSTEM SET autovacuum_work_mem = 32;  -- ok, privileges have been granted
+ALTER SYSTEM SET ignore_system_indexes = OFF;  -- fail, insufficient privileges
+ALTER SYSTEM RESET autovacuum_multixact_freeze_max_age;  -- fail, insufficient privileges
+SET jit_provider = 'llvmjit';  -- fail, insufficient privileges
+ALTER SYSTEM SET shared_buffers = 50;  -- ok
+ALTER SYSTEM RESET shared_buffers;  -- ok
+SET autovacuum_work_mem = 50;  -- cannot be changed now
+ALTER SYSTEM RESET temp_file_limit;  -- ok
+SET TimeZone = 'Europe/Helsinki';  -- ok
+ALTER SYSTEM RESET autovacuum_work_mem;  -- ok, privileges have been granted
+RESET TimeZone;  -- ok
+SET SESSION AUTHORIZATION regress_admin;
+DROP ROLE regress_host_resource_admin;  -- fail, privileges remain
+-- Use "revoke" to remove the privileges and allow the role to be dropped
+REVOKE SET VALUE, ALTER SYSTEM ON
+	autovacuum_work_mem, hash_mem_multiplier, logical_decoding_work_mem,
+	maintenance_work_mem, max_stack_depth, min_dynamic_shared_memory,
+	shared_buffers, temp_buffers, temp_file_limit, work_mem
+FROM regress_host_resource_admin;
+DROP ROLE regress_host_resource_admin;  -- ok
+-- Try that again, but use "drop owned by" instead of "revoke"
+CREATE ROLE regress_host_resource_admin NOSUPERUSER;
+SET SESSION AUTHORIZATION regress_host_resource_admin;
+ALTER SYSTEM SET autovacuum_work_mem = 32;  -- fail, privileges not yet granted
+SET SESSION AUTHORIZATION regress_admin;
+GRANT SET VALUE, ALTER SYSTEM ON
+	autovacuum_work_mem, hash_mem_multiplier, logical_decoding_work_mem,
+	maintenance_work_mem, max_stack_depth, min_dynamic_shared_memory,
+	shared_buffers, temp_buffers, temp_file_limit, work_mem
+TO regress_host_resource_admin;
+DROP ROLE regress_host_resource_admin;  -- fail, privileges remain
+DROP OWNED BY regress_host_resource_admin RESTRICT; -- cascade should not be needed
+SET SESSION AUTHORIZATION regress_host_resource_admin;
+ALTER SYSTEM SET autovacuum_work_mem = 32;  -- fail, "drop owned" has dropped privileges
+SET SESSION AUTHORIZATION regress_admin;
+DROP ROLE regress_host_resource_admin;  -- ok
+-- Try that again, but use "reassign owned by" this time
+CREATE ROLE regress_host_resource_admin NOSUPERUSER;
+CREATE ROLE regress_host_resource_newadmin NOSUPERUSER;
+GRANT SET VALUE, ALTER SYSTEM ON
+	autovacuum_work_mem, hash_mem_multiplier, logical_decoding_work_mem,
+	maintenance_work_mem, max_stack_depth, min_dynamic_shared_memory,
+	shared_buffers, temp_buffers, temp_file_limit, work_mem
+TO regress_host_resource_admin;
+REASSIGN OWNED BY regress_host_resource_admin TO regress_host_resource_newadmin;
+SET SESSION AUTHORIZATION regress_host_resource_admin;
+ALTER SYSTEM SET autovacuum_work_mem = 32;  -- ok, "reassign owned" did not change privileges
+SET SESSION AUTHORIZATION regress_admin;
+DROP ROLE regress_host_resource_admin;  -- fail, privileges remain
+DROP ROLE regress_host_resource_newadmin;  -- ok, nothing was transferred
+-- Use "drop owned by" so we can drop the role
+DROP OWNED BY regress_host_resource_admin;  -- ok
+DROP ROLE regress_host_resource_admin;  -- ok
+-- Create non-superuser with privileges to configure plgsql custom variables
+CREATE ROLE regress_plpgsql_admin NOSUPERUSER;
+-- Perform all operations as user 'regress_plpgsql_admin' --
+SET SESSION AUTHORIZATION regress_plpgsql_admin;
+SET plpgsql.extra_errors TO 'all';
+SET plpgsql.extra_warnings TO 'all';
+RESET plpgsql.extra_errors;
+RESET plpgsql.extra_warnings;
+ALTER SYSTEM SET plpgsql.extra_errors TO 'all';
+ALTER SYSTEM SET plpgsql.extra_warnings TO 'all';
+ALTER SYSTEM RESET plpgsql.extra_errors;
+ALTER SYSTEM RESET plpgsql.extra_warnings;
+SET SESSION AUTHORIZATION regress_admin;
+GRANT SET VALUE, ALTER SYSTEM ON
+	plpgsql.extra_warnings, plpgsql.extra_errors
+TO regress_plpgsql_admin;
+-- Perform all operations as user 'regress_plpgsql_admin' --
+SET SESSION AUTHORIZATION regress_plpgsql_admin;  -- ok
+SET plpgsql.extra_errors TO 'all';  -- ok
+SET plpgsql.extra_warnings TO 'all';  -- ok
+RESET plpgsql.extra_errors;  -- ok
+RESET plpgsql.extra_warnings;  -- ok
+ALTER SYSTEM SET plpgsql.extra_errors TO 'all';  -- ok
+ALTER SYSTEM SET plpgsql.extra_warnings TO 'all';  -- ok
+ALTER SYSTEM RESET plpgsql.extra_errors;  -- ok
+ALTER SYSTEM RESET plpgsql.extra_warnings;  -- ok
+SET SESSION AUTHORIZATION regress_admin;
+DROP ROLE regress_plpgsql_admin;  -- fail, privileges remain
+REVOKE SET VALUE, ALTER SYSTEM ON
+	plpgsql.extra_warnings, plpgsql.extra_errors
+FROM regress_plpgsql_admin;
+DROP ROLE regress_plpgsql_admin;  -- ok
diff --git a/src/test/regress/sql/privileges.sql b/src/test/regress/sql/privileges.sql
index c8c545b64c..ddbf6afa44 100644
--- a/src/test/regress/sql/privileges.sql
+++ b/src/test/regress/sql/privileges.sql
@@ -66,6 +66,33 @@ DROP USER regress_priv_user10;
 DROP USER regress_priv_user9;
 DROP USER regress_priv_user8;
 
+GRANT SET VALUE ON enable_memoize TO regress_priv_user6;
+GRANT SET VALUE ON enable_nestloop TO regress_priv_user6;
+
+SET ROLE regress_priv_user6;
+SET enable_memoize TO false;
+SET enable_nestloop TO false;
+RESET enable_memoize;
+RESET enable_nestloop;
+RESET ROLE;
+
+GRANT ALTER SYSTEM ON enable_seqscan TO regress_priv_user7; -- ok
+GRANT ALTER SYSTEM ON sort_mem TO regress_priv_user7; -- old name for "work_mem"
+GRANT ALTER SYSTEM ON maintenance_work_mem TO regress_priv_user7; -- ok
+GRANT ALTER SYSTEM ON no_such_param TO regress_priv_user7; -- ok
+GRANT ALTER SYSTEM ON no_such_extension.no_such_param TO regress_priv_user7; -- ok
+GRANT ALTER SYSTEM ON no_such_extension.no_such_param.longer.than.maximum.namedata.length TO regress_priv_user7; -- ok
+GRANT ALTER SYSTEM ON "" TO regress_priv_user7; -- bad name
+GRANT ALTER SYSTEM ON " " TO regress_priv_user7; -- bad name
+GRANT ALTER SYSTEM ON " foo " TO regress_priv_user7; -- bad name
+
+GRANT SELECT ON public.persons2 TO regress_priv_user7;
+
+SET ROLE regress_priv_user7;
+ALTER SYSTEM SET enable_seqscan = OFF;
+ALTER SYSTEM RESET enable_seqscan;
+RESET ROLE;
+
 CREATE GROUP regress_priv_group1;
 CREATE GROUP regress_priv_group2 WITH USER regress_priv_user1, regress_priv_user2;
 
@@ -1426,10 +1453,23 @@ DROP USER regress_priv_user2;
 DROP USER regress_priv_user3;
 DROP USER regress_priv_user4;
 DROP USER regress_priv_user5;
-DROP USER regress_priv_user6;
-DROP USER regress_priv_user7;
+DROP USER regress_priv_user6; -- privileges remain
+DROP USER regress_priv_user7; -- privileges remain
 DROP USER regress_priv_user8; -- does not exist
 
+REVOKE SELECT ON public.persons2 FROM regress_priv_user7;
+REVOKE ALTER SYSTEM ON enable_seqscan FROM regress_priv_user7; -- ok
+REVOKE ALTER SYSTEM ON work_mem FROM regress_priv_user7; -- ok, use new name
+REVOKE ALTER SYSTEM ON vacuum_mem FROM regress_priv_user7; -- ok, use old name
+REVOKE ALTER SYSTEM ON no_such_param FROM regress_priv_user7; -- ok
+REVOKE ALTER SYSTEM ON no_such_extension.no_such_param FROM regress_priv_user7; -- ok
+REVOKE ALTER SYSTEM ON no_such_extension.no_such_param.longer.than.maximum.namedata.length FROM regress_priv_user7; -- ok
+DROP USER regress_priv_user7; -- ok
+
+REVOKE SET VALUE ON enable_memoize FROM regress_priv_user6;
+REVOKE SET VALUE ON enable_nestloop FROM regress_priv_user6;
+
+DROP USER regress_priv_user6; -- ok
 
 -- permissions with LOCK TABLE
 CREATE USER regress_locktable_user;

---

From: Mark Dilger <[email-masked]>
Date: Sun, 6 Mar 2022 14:40:50 -0800

> On Mar 6, 2022, at 2:13 PM, Tom Lane <[email-masked]> wrote:
> 

To my knowledge, there is no mechanism to revoke an implicit privilege.  You can revoke a privilege explicitly listed in an aclitem[], but only if the privilege is being tracked that way.

Userset variables are implicitly settable by any user.  There was a request, off-list as I recall, to make it possible to revoke the privilege to set variables such as "work_mem".  To make that possible, but not change the default behavior vis-a-vis prior releases, I upgraded most userset variables to suset with a corresponding grant to public on the variable.  Sites which wish to have a more restrictive policy on such variables can revoke that privilege from public and instead issue more restrictive grants.  There were a few variables where such treatment didn't seem sensible, such as ones to do with client connections, and I left them alone.  I didn't insist on a defense for why any particular setting needed to be revocable in order to apply this treatment.  My assumption was that sites should be allowed to determine their own security policies per setting unless there is a technical difficulty for a given setting that would make it overly burdensome to implement.

It seemed more complete to do the same thing for contrib modules, so I did.

If a third-party module doesn't do the equivalent operation, they would simply continue with their userset variables working as before.  I don't see a specific security concern with that, though I'd be happy to consider arguments to the contrary.

I believe there are also some issues with this patch relating to pg_dump/pg_restore and to pg_upgrade, which EDB is working to address.  We intend to repost something soon.

—
Mark Dilger