From 8d4b066723db8c7aa6a5125a4df7d3dc51b47dfc Mon Sep 17 00:00:00 2001
From: Mark Dilger <mark.dilger@enterprisedb.com>
Date: Wed, 20 Oct 2021 07:51:42 -0700
Subject: [PATCH v1 09/12] Adding role pg_manage_query_tuning_settings

This new role can ALTER SYSTEM SET any GUC variable which controls
query planning and execution tuning.
---
 doc/src/sgml/user-manag.sgml            |  13 ++-
 src/backend/utils/misc/guc.c            | 104 ++++++++++++------------
 src/include/catalog/pg_authid.dat       |   5 ++
 src/include/utils/guc.h                 |   1 +
 src/test/regress/expected/guc_privs.out |  81 ++++++++++++++++++
 src/test/regress/sql/guc_privs.sql      |  51 ++++++++++++
 6 files changed, 201 insertions(+), 54 deletions(-)

diff --git a/doc/src/sgml/user-manag.sgml b/doc/src/sgml/user-manag.sgml
index 4c577f9ca8..3bd08d6aef 100644
--- a/doc/src/sgml/user-manag.sgml
+++ b/doc/src/sgml/user-manag.sgml
@@ -610,6 +610,10 @@ DROP ROLE doomed_role;
        <entry>pg_manage_wal_settings</entry>
        <entry>Allow changing settings which control write ahead log behavior.</entry>
       </row>
+      <row>
+       <entry>pg_manage_query_tuning_settings</entry>
+       <entry>Allow changing settings which control tuning of query planning and execution.</entry>
+      </row>
      </tbody>
     </tgroup>
    </table>
@@ -660,10 +664,11 @@ DROP ROLE doomed_role;
   <literal>pg_manage_autovacuum_settings</literal>,
   <literal>pg_manage_logging_settings</literal>,
   <literal>pg_manage_replication_settings</literal>,
-  <literal>pg_manage_connection_settings</literal> and
-  <literal>pg_manage_wal_settings</literal> roles are intended to allow
-  administrators to have trusted, but non-superuser, roles to configure the
-  server.
+  <literal>pg_manage_connection_settings</literal>,
+  <literal>pg_manage_wal_settings</literal> and
+  <literal>pg_manage_query_tuning_settings</literal> roles are intended to
+  allow administrators to have trusted, but non-superuser, roles to configure
+  the server.
   </para>
 
   <para>
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index f5ad9f37d5..65f371138f 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -974,7 +974,7 @@ static struct config_bool ConfigureNamesBool[] =
 		{"enable_seqscan", PGC_USERSET, QUERY_TUNING_METHOD,
 			gettext_noop("Enables the planner's use of sequential-scan plans."),
 			NULL,
-			GUC_SUPERUSER_ONLY,
+			GUC_MANAGE_QUERY_TUNING_SETTINGS,
 			GUC_EXPLAIN
 		},
 		&enable_seqscan,
@@ -985,7 +985,7 @@ static struct config_bool ConfigureNamesBool[] =
 		{"enable_indexscan", PGC_USERSET, QUERY_TUNING_METHOD,
 			gettext_noop("Enables the planner's use of index-scan plans."),
 			NULL,
-			GUC_SUPERUSER_ONLY,
+			GUC_MANAGE_QUERY_TUNING_SETTINGS,
 			GUC_EXPLAIN
 		},
 		&enable_indexscan,
@@ -996,7 +996,7 @@ static struct config_bool ConfigureNamesBool[] =
 		{"enable_indexonlyscan", PGC_USERSET, QUERY_TUNING_METHOD,
 			gettext_noop("Enables the planner's use of index-only-scan plans."),
 			NULL,
-			GUC_SUPERUSER_ONLY,
+			GUC_MANAGE_QUERY_TUNING_SETTINGS,
 			GUC_EXPLAIN
 		},
 		&enable_indexonlyscan,
@@ -1007,7 +1007,7 @@ static struct config_bool ConfigureNamesBool[] =
 		{"enable_bitmapscan", PGC_USERSET, QUERY_TUNING_METHOD,
 			gettext_noop("Enables the planner's use of bitmap-scan plans."),
 			NULL,
-			GUC_SUPERUSER_ONLY,
+			GUC_MANAGE_QUERY_TUNING_SETTINGS,
 			GUC_EXPLAIN
 		},
 		&enable_bitmapscan,
@@ -1018,7 +1018,7 @@ static struct config_bool ConfigureNamesBool[] =
 		{"enable_tidscan", PGC_USERSET, QUERY_TUNING_METHOD,
 			gettext_noop("Enables the planner's use of TID scan plans."),
 			NULL,
-			GUC_SUPERUSER_ONLY,
+			GUC_MANAGE_QUERY_TUNING_SETTINGS,
 			GUC_EXPLAIN
 		},
 		&enable_tidscan,
@@ -1029,7 +1029,7 @@ static struct config_bool ConfigureNamesBool[] =
 		{"enable_sort", PGC_USERSET, QUERY_TUNING_METHOD,
 			gettext_noop("Enables the planner's use of explicit sort steps."),
 			NULL,
-			GUC_SUPERUSER_ONLY,
+			GUC_MANAGE_QUERY_TUNING_SETTINGS,
 			GUC_EXPLAIN
 		},
 		&enable_sort,
@@ -1040,7 +1040,7 @@ static struct config_bool ConfigureNamesBool[] =
 		{"enable_incremental_sort", PGC_USERSET, QUERY_TUNING_METHOD,
 			gettext_noop("Enables the planner's use of incremental sort steps."),
 			NULL,
-			GUC_SUPERUSER_ONLY
+			GUC_MANAGE_QUERY_TUNING_SETTINGS
 		},
 		&enable_incremental_sort,
 		true,
@@ -1050,7 +1050,7 @@ static struct config_bool ConfigureNamesBool[] =
 		{"enable_hashagg", PGC_USERSET, QUERY_TUNING_METHOD,
 			gettext_noop("Enables the planner's use of hashed aggregation plans."),
 			NULL,
-			GUC_SUPERUSER_ONLY,
+			GUC_MANAGE_QUERY_TUNING_SETTINGS,
 			GUC_EXPLAIN
 		},
 		&enable_hashagg,
@@ -1061,7 +1061,7 @@ static struct config_bool ConfigureNamesBool[] =
 		{"enable_material", PGC_USERSET, QUERY_TUNING_METHOD,
 			gettext_noop("Enables the planner's use of materialization."),
 			NULL,
-			GUC_SUPERUSER_ONLY,
+			GUC_MANAGE_QUERY_TUNING_SETTINGS,
 			GUC_EXPLAIN
 		},
 		&enable_material,
@@ -1072,7 +1072,7 @@ static struct config_bool ConfigureNamesBool[] =
 		{"enable_memoize", PGC_USERSET, QUERY_TUNING_METHOD,
 			gettext_noop("Enables the planner's use of memoization."),
 			NULL,
-			GUC_SUPERUSER_ONLY,
+			GUC_MANAGE_QUERY_TUNING_SETTINGS,
 			GUC_EXPLAIN
 		},
 		&enable_memoize,
@@ -1083,7 +1083,7 @@ static struct config_bool ConfigureNamesBool[] =
 		{"enable_nestloop", PGC_USERSET, QUERY_TUNING_METHOD,
 			gettext_noop("Enables the planner's use of nested-loop join plans."),
 			NULL,
-			GUC_SUPERUSER_ONLY,
+			GUC_MANAGE_QUERY_TUNING_SETTINGS,
 			GUC_EXPLAIN
 		},
 		&enable_nestloop,
@@ -1094,7 +1094,7 @@ static struct config_bool ConfigureNamesBool[] =
 		{"enable_mergejoin", PGC_USERSET, QUERY_TUNING_METHOD,
 			gettext_noop("Enables the planner's use of merge join plans."),
 			NULL,
-			GUC_SUPERUSER_ONLY,
+			GUC_MANAGE_QUERY_TUNING_SETTINGS,
 			GUC_EXPLAIN
 		},
 		&enable_mergejoin,
@@ -1105,7 +1105,7 @@ static struct config_bool ConfigureNamesBool[] =
 		{"enable_hashjoin", PGC_USERSET, QUERY_TUNING_METHOD,
 			gettext_noop("Enables the planner's use of hash join plans."),
 			NULL,
-			GUC_SUPERUSER_ONLY,
+			GUC_MANAGE_QUERY_TUNING_SETTINGS,
 			GUC_EXPLAIN
 		},
 		&enable_hashjoin,
@@ -1116,7 +1116,7 @@ static struct config_bool ConfigureNamesBool[] =
 		{"enable_gathermerge", PGC_USERSET, QUERY_TUNING_METHOD,
 			gettext_noop("Enables the planner's use of gather merge plans."),
 			NULL,
-			GUC_SUPERUSER_ONLY,
+			GUC_MANAGE_QUERY_TUNING_SETTINGS,
 			GUC_EXPLAIN
 		},
 		&enable_gathermerge,
@@ -1127,7 +1127,7 @@ static struct config_bool ConfigureNamesBool[] =
 		{"enable_partitionwise_join", PGC_USERSET, QUERY_TUNING_METHOD,
 			gettext_noop("Enables partitionwise join."),
 			NULL,
-			GUC_SUPERUSER_ONLY,
+			GUC_MANAGE_QUERY_TUNING_SETTINGS,
 			GUC_EXPLAIN
 		},
 		&enable_partitionwise_join,
@@ -1138,7 +1138,7 @@ static struct config_bool ConfigureNamesBool[] =
 		{"enable_partitionwise_aggregate", PGC_USERSET, QUERY_TUNING_METHOD,
 			gettext_noop("Enables partitionwise aggregation and grouping."),
 			NULL,
-			GUC_SUPERUSER_ONLY,
+			GUC_MANAGE_QUERY_TUNING_SETTINGS,
 			GUC_EXPLAIN
 		},
 		&enable_partitionwise_aggregate,
@@ -1149,7 +1149,7 @@ static struct config_bool ConfigureNamesBool[] =
 		{"enable_parallel_append", PGC_USERSET, QUERY_TUNING_METHOD,
 			gettext_noop("Enables the planner's use of parallel append plans."),
 			NULL,
-			GUC_SUPERUSER_ONLY,
+			GUC_MANAGE_QUERY_TUNING_SETTINGS,
 			GUC_EXPLAIN
 		},
 		&enable_parallel_append,
@@ -1160,7 +1160,7 @@ static struct config_bool ConfigureNamesBool[] =
 		{"enable_parallel_hash", PGC_USERSET, QUERY_TUNING_METHOD,
 			gettext_noop("Enables the planner's use of parallel hash plans."),
 			NULL,
-			GUC_SUPERUSER_ONLY,
+			GUC_MANAGE_QUERY_TUNING_SETTINGS,
 			GUC_EXPLAIN
 		},
 		&enable_parallel_hash,
@@ -1173,7 +1173,7 @@ static struct config_bool ConfigureNamesBool[] =
 			gettext_noop("Allows the query planner and executor to compare partition "
 						 "bounds to conditions in the query to determine which "
 						 "partitions must be scanned."),
-			GUC_SUPERUSER_ONLY,
+			GUC_MANAGE_QUERY_TUNING_SETTINGS,
 			GUC_EXPLAIN
 		},
 		&enable_partition_pruning,
@@ -1184,7 +1184,7 @@ static struct config_bool ConfigureNamesBool[] =
 		{"enable_async_append", PGC_USERSET, QUERY_TUNING_METHOD,
 			gettext_noop("Enables the planner's use of async append plans."),
 			NULL,
-			GUC_SUPERUSER_ONLY,
+			GUC_MANAGE_QUERY_TUNING_SETTINGS,
 			GUC_EXPLAIN
 		},
 		&enable_async_append,
@@ -1196,7 +1196,7 @@ static struct config_bool ConfigureNamesBool[] =
 			gettext_noop("Enables genetic query optimization."),
 			gettext_noop("This algorithm attempts to do planning without "
 						 "exhaustive searching."),
-			GUC_SUPERUSER_ONLY,
+			GUC_MANAGE_QUERY_TUNING_SETTINGS,
 			GUC_EXPLAIN
 		},
 		&enable_geqo,
@@ -1903,7 +1903,7 @@ static struct config_bool ConfigureNamesBool[] =
 			"optimize_bounded_sort", PGC_USERSET, QUERY_TUNING_METHOD,
 			gettext_noop("Enable bounded sorting using heap sort."),
 			NULL,
-			GUC_SUPERUSER_ONLY,
+			GUC_MANAGE_QUERY_TUNING_SETTINGS,
 			GUC_NOT_IN_SAMPLE | GUC_EXPLAIN
 		},
 		&optimize_bounded_sort,
@@ -2126,7 +2126,7 @@ static struct config_bool ConfigureNamesBool[] =
 		{"jit", PGC_USERSET, QUERY_TUNING_OTHER,
 			gettext_noop("Allow JIT compilation."),
 			NULL,
-			GUC_SUPERUSER_ONLY,
+			GUC_MANAGE_QUERY_TUNING_SETTINGS,
 			GUC_EXPLAIN
 		},
 		&jit_enabled,
@@ -2265,7 +2265,7 @@ static struct config_int ConfigureNamesInt[] =
 			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."),
-			GUC_SUPERUSER_ONLY
+			GUC_MANAGE_QUERY_TUNING_SETTINGS
 		},
 		&default_statistics_target,
 		100, 1, 10000,
@@ -2278,7 +2278,7 @@ static struct config_int ConfigureNamesInt[] =
 			gettext_noop("The planner will merge subqueries into upper "
 						 "queries if the resulting FROM list would have no more than "
 						 "this many items."),
-			GUC_SUPERUSER_ONLY,
+			GUC_MANAGE_QUERY_TUNING_SETTINGS,
 			GUC_EXPLAIN
 		},
 		&from_collapse_limit,
@@ -2292,7 +2292,7 @@ static struct config_int ConfigureNamesInt[] =
 			gettext_noop("The planner will flatten explicit JOIN "
 						 "constructs into lists of FROM items whenever a "
 						 "list of no more than this many items would result."),
-			GUC_SUPERUSER_ONLY,
+			GUC_MANAGE_QUERY_TUNING_SETTINGS,
 			GUC_EXPLAIN
 		},
 		&join_collapse_limit,
@@ -2303,7 +2303,7 @@ static struct config_int ConfigureNamesInt[] =
 		{"geqo_threshold", PGC_USERSET, QUERY_TUNING_GEQO,
 			gettext_noop("Sets the threshold of FROM items beyond which GEQO is used."),
 			NULL,
-			GUC_SUPERUSER_ONLY,
+			GUC_MANAGE_QUERY_TUNING_SETTINGS,
 			GUC_EXPLAIN
 		},
 		&geqo_threshold,
@@ -2314,7 +2314,7 @@ static struct config_int ConfigureNamesInt[] =
 		{"geqo_effort", PGC_USERSET, QUERY_TUNING_GEQO,
 			gettext_noop("GEQO: effort is used to set the default for other GEQO parameters."),
 			NULL,
-			GUC_SUPERUSER_ONLY,
+			GUC_MANAGE_QUERY_TUNING_SETTINGS,
 			GUC_EXPLAIN
 		},
 		&Geqo_effort,
@@ -2325,7 +2325,7 @@ static struct config_int ConfigureNamesInt[] =
 		{"geqo_pool_size", PGC_USERSET, QUERY_TUNING_GEQO,
 			gettext_noop("GEQO: number of individuals in the population."),
 			gettext_noop("Zero selects a suitable default value."),
-			GUC_SUPERUSER_ONLY,
+			GUC_MANAGE_QUERY_TUNING_SETTINGS,
 			GUC_EXPLAIN
 		},
 		&Geqo_pool_size,
@@ -2336,7 +2336,7 @@ static struct config_int ConfigureNamesInt[] =
 		{"geqo_generations", PGC_USERSET, QUERY_TUNING_GEQO,
 			gettext_noop("GEQO: number of iterations of the algorithm."),
 			gettext_noop("Zero selects a suitable default value."),
-			GUC_SUPERUSER_ONLY,
+			GUC_MANAGE_QUERY_TUNING_SETTINGS,
 			GUC_EXPLAIN
 		},
 		&Geqo_generations,
@@ -3254,7 +3254,7 @@ static struct config_int ConfigureNamesInt[] =
 			RESOURCES_ASYNCHRONOUS,
 			gettext_noop("Number of simultaneous requests that can be handled efficiently by the disk subsystem."),
 			NULL,
-			GUC_SUPERUSER_ONLY,
+			GUC_MANAGE_QUERY_TUNING_SETTINGS,
 			GUC_EXPLAIN
 		},
 		&effective_io_concurrency,
@@ -3273,7 +3273,7 @@ static struct config_int ConfigureNamesInt[] =
 			RESOURCES_ASYNCHRONOUS,
 			gettext_noop("A variant of effective_io_concurrency that is used for maintenance work."),
 			NULL,
-			GUC_SUPERUSER_ONLY,
+			GUC_MANAGE_QUERY_TUNING_SETTINGS,
 			GUC_EXPLAIN
 		},
 		&maintenance_io_concurrency,
@@ -3665,7 +3665,7 @@ static struct config_int ConfigureNamesInt[] =
 			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."),
-			GUC_SUPERUSER_ONLY,
+			GUC_MANAGE_QUERY_TUNING_SETTINGS,
 			GUC_UNIT_BLOCKS | GUC_EXPLAIN,
 		},
 		&effective_cache_size,
@@ -3677,7 +3677,7 @@ static struct config_int ConfigureNamesInt[] =
 		{"min_parallel_table_scan_size", PGC_USERSET, 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_SUPERUSER_ONLY,
+			GUC_MANAGE_QUERY_TUNING_SETTINGS,
 			GUC_UNIT_BLOCKS | GUC_EXPLAIN,
 		},
 		&min_parallel_table_scan_size,
@@ -3689,7 +3689,7 @@ static struct config_int ConfigureNamesInt[] =
 		{"min_parallel_index_scan_size", PGC_USERSET, 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_SUPERUSER_ONLY,
+			GUC_MANAGE_QUERY_TUNING_SETTINGS,
 			GUC_UNIT_BLOCKS | GUC_EXPLAIN,
 		},
 		&min_parallel_index_scan_size,
@@ -3820,7 +3820,7 @@ static struct config_real ConfigureNamesReal[] =
 			gettext_noop("Sets the planner's estimate of the cost of a "
 						 "sequentially fetched disk page."),
 			NULL,
-			GUC_SUPERUSER_ONLY,
+			GUC_MANAGE_QUERY_TUNING_SETTINGS,
 			GUC_EXPLAIN
 		},
 		&seq_page_cost,
@@ -3832,7 +3832,7 @@ static struct config_real ConfigureNamesReal[] =
 			gettext_noop("Sets the planner's estimate of the cost of a "
 						 "nonsequentially fetched disk page."),
 			NULL,
-			GUC_SUPERUSER_ONLY,
+			GUC_MANAGE_QUERY_TUNING_SETTINGS,
 			GUC_EXPLAIN
 		},
 		&random_page_cost,
@@ -3844,7 +3844,7 @@ static struct config_real ConfigureNamesReal[] =
 			gettext_noop("Sets the planner's estimate of the cost of "
 						 "processing each tuple (row)."),
 			NULL,
-			GUC_SUPERUSER_ONLY,
+			GUC_MANAGE_QUERY_TUNING_SETTINGS,
 			GUC_EXPLAIN
 		},
 		&cpu_tuple_cost,
@@ -3856,7 +3856,7 @@ static struct config_real ConfigureNamesReal[] =
 			gettext_noop("Sets the planner's estimate of the cost of "
 						 "processing each index entry during an index scan."),
 			NULL,
-			GUC_SUPERUSER_ONLY,
+			GUC_MANAGE_QUERY_TUNING_SETTINGS,
 			GUC_EXPLAIN
 		},
 		&cpu_index_tuple_cost,
@@ -3868,7 +3868,7 @@ static struct config_real ConfigureNamesReal[] =
 			gettext_noop("Sets the planner's estimate of the cost of "
 						 "processing each operator or function call."),
 			NULL,
-			GUC_SUPERUSER_ONLY,
+			GUC_MANAGE_QUERY_TUNING_SETTINGS,
 			GUC_EXPLAIN
 		},
 		&cpu_operator_cost,
@@ -3880,7 +3880,7 @@ static struct config_real ConfigureNamesReal[] =
 			gettext_noop("Sets the planner's estimate of the cost of "
 						 "passing each tuple (row) from worker to leader backend."),
 			NULL,
-			GUC_SUPERUSER_ONLY,
+			GUC_MANAGE_QUERY_TUNING_SETTINGS,
 			GUC_EXPLAIN
 		},
 		&parallel_tuple_cost,
@@ -3892,7 +3892,7 @@ static struct config_real ConfigureNamesReal[] =
 			gettext_noop("Sets the planner's estimate of the cost of "
 						 "starting up worker processes for parallel query."),
 			NULL,
-			GUC_SUPERUSER_ONLY,
+			GUC_MANAGE_QUERY_TUNING_SETTINGS,
 			GUC_EXPLAIN
 		},
 		&parallel_setup_cost,
@@ -3904,7 +3904,7 @@ static struct config_real ConfigureNamesReal[] =
 		{"jit_above_cost", PGC_USERSET, QUERY_TUNING_COST,
 			gettext_noop("Perform JIT compilation if query is more expensive."),
 			gettext_noop("-1 disables JIT compilation."),
-			GUC_SUPERUSER_ONLY,
+			GUC_MANAGE_QUERY_TUNING_SETTINGS,
 			GUC_EXPLAIN
 		},
 		&jit_above_cost,
@@ -3916,7 +3916,7 @@ static struct config_real ConfigureNamesReal[] =
 		{"jit_optimize_above_cost", PGC_USERSET, QUERY_TUNING_COST,
 			gettext_noop("Optimize JIT-compiled functions if query is more expensive."),
 			gettext_noop("-1 disables optimization."),
-			GUC_SUPERUSER_ONLY,
+			GUC_MANAGE_QUERY_TUNING_SETTINGS,
 			GUC_EXPLAIN
 		},
 		&jit_optimize_above_cost,
@@ -3928,7 +3928,7 @@ static struct config_real ConfigureNamesReal[] =
 		{"jit_inline_above_cost", PGC_USERSET, QUERY_TUNING_COST,
 			gettext_noop("Perform JIT inlining if query is more expensive."),
 			gettext_noop("-1 disables inlining."),
-			GUC_SUPERUSER_ONLY,
+			GUC_MANAGE_QUERY_TUNING_SETTINGS,
 			GUC_EXPLAIN
 		},
 		&jit_inline_above_cost,
@@ -3941,7 +3941,7 @@ static struct config_real ConfigureNamesReal[] =
 			gettext_noop("Sets the planner's estimate of the fraction of "
 						 "a cursor's rows that will be retrieved."),
 			NULL,
-			GUC_SUPERUSER_ONLY,
+			GUC_MANAGE_QUERY_TUNING_SETTINGS,
 			GUC_EXPLAIN
 		},
 		&cursor_tuple_fraction,
@@ -3953,7 +3953,7 @@ static struct config_real ConfigureNamesReal[] =
 		{"geqo_selection_bias", PGC_USERSET, QUERY_TUNING_GEQO,
 			gettext_noop("GEQO: selective pressure within the population."),
 			NULL,
-			GUC_SUPERUSER_ONLY,
+			GUC_MANAGE_QUERY_TUNING_SETTINGS,
 			GUC_EXPLAIN
 		},
 		&Geqo_selection_bias,
@@ -3965,7 +3965,7 @@ static struct config_real ConfigureNamesReal[] =
 		{"geqo_seed", PGC_USERSET, QUERY_TUNING_GEQO,
 			gettext_noop("GEQO: seed for random path selection."),
 			NULL,
-			GUC_SUPERUSER_ONLY,
+			GUC_MANAGE_QUERY_TUNING_SETTINGS,
 			GUC_EXPLAIN
 		},
 		&Geqo_seed,
@@ -4993,7 +4993,7 @@ static struct config_enum ConfigureNamesEnum[] =
 			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."),
-			GUC_SUPERUSER_ONLY,
+			GUC_MANAGE_QUERY_TUNING_SETTINGS,
 			GUC_EXPLAIN
 		},
 		&constraint_exclusion,
@@ -5300,7 +5300,7 @@ static struct config_enum ConfigureNamesEnum[] =
 			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 "
 						 "the default behavior."),
-			GUC_SUPERUSER_ONLY,
+			GUC_MANAGE_QUERY_TUNING_SETTINGS,
 			GUC_EXPLAIN
 		},
 		&plan_cache_mode,
@@ -7659,6 +7659,10 @@ role_has_privileges(Oid roleid, int privileges)
 		! has_privs_of_role(roleid, ROLE_PG_MANAGE_WAL_SETTINGS))
 		return false;
 
+	if ((privileges & GUC_MANAGE_QUERY_TUNING_SETTINGS) &&
+		! has_privs_of_role(roleid, ROLE_PG_MANAGE_QUERY_TUNING_SETTINGS))
+		return false;
+
 	return true;
 }
 
diff --git a/src/include/catalog/pg_authid.dat b/src/include/catalog/pg_authid.dat
index cde919bee8..68e305c34f 100644
--- a/src/include/catalog/pg_authid.dat
+++ b/src/include/catalog/pg_authid.dat
@@ -114,5 +114,10 @@
   rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
   rolreplication => 'f', rolbypassrls => 't', rolconnlimit => '-1',
   rolpassword => '_null_', rolvaliduntil => '_null_' },
+{ oid => '9286', oid_symbol => 'ROLE_PG_MANAGE_QUERY_TUNING_SETTINGS',
+  rolname => 'pg_manage_query_tuning_settings', rolsuper => 'f', rolinherit => 't',
+  rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
+  rolreplication => 'f', rolbypassrls => 't', rolconnlimit => '-1',
+  rolpassword => '_null_', rolvaliduntil => '_null_' },
 
 ]
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index a79c617bbe..bca2c113de 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -209,6 +209,7 @@ typedef enum
 #define GUC_MANAGE_REPLICATION_SETTINGS		0x0020
 #define GUC_MANAGE_CONNECTION_SETTINGS		0x0040
 #define GUC_MANAGE_WAL_SETTINGS				0x0080
+#define GUC_MANAGE_QUERY_TUNING_SETTINGS	0x0200	/* 0x0100 = superuser */
 
 /*
  * bit values in "flags" of a GUC variable
diff --git a/src/test/regress/expected/guc_privs.out b/src/test/regress/expected/guc_privs.out
index aadeb5b26c..e748b6531b 100644
--- a/src/test/regress/expected/guc_privs.out
+++ b/src/test/regress/expected/guc_privs.out
@@ -755,3 +755,84 @@ ALTER SYSTEM RESET commit_siblings;  -- ok
 RESET statement_timeout;
 RESET SESSION AUTHORIZATION;
 DROP ROLE regress_wal_admin;
+-- Test role pg_manage_query_tuning_settings
+-- Non-superuser with privileges to tune query planning and execution
+CREATE ROLE regress_query_tuning_admin NOSUPERUSER;
+GRANT pg_manage_query_tuning_settings TO regress_query_tuning_admin;
+-- Perform all operations as user 'regress_query_tuning_admin' --
+SET SESSION AUTHORIZATION regress_query_tuning_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;  -- fail, regress_query_tuning_admin has insufficient privileges
+ERROR:  permission denied to set parameter "ignore_system_indexes"
+ALTER SYSTEM RESET ignore_system_indexes;  -- fail, regress_query_tuning_admin has insufficient privileges
+ERROR:  permission denied to set parameter "ignore_system_indexes"
+-- 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;  -- fail, regress_query_tuning_admin has insufficient privileges
+ERROR:  permission denied to set parameter "autovacuum_freeze_max_age"
+ALTER SYSTEM RESET autovacuum_freeze_max_age;  -- fail, regress_query_tuning_admin has insufficient privileges
+ERROR:  permission denied to set parameter "autovacuum_freeze_max_age"
+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;  -- fail, regress_query_tuning_admin has insufficient privileges
+ERROR:  permission denied to set parameter "autovacuum"
+ALTER SYSTEM RESET autovacuum;  -- fail, regress_query_tuning_admin has insufficient privileges
+ERROR:  permission denied to set parameter "autovacuum"
+-- PGC_SUSET
+SET lc_messages = 'en_US.UTF-8';  -- fail, regress_query_tuning_admin has insufficient privileges
+ERROR:  permission denied to set parameter "lc_messages"
+RESET lc_messages;  -- fail, regress_query_tuning_admin has insufficient privileges
+ERROR:  permission denied to set parameter "lc_messages"
+ALTER SYSTEM SET lc_messages = 'en_US.UTF-8';  -- fail, regress_query_tuning_admin has insufficient privileges
+ERROR:  permission denied to set parameter "lc_messages"
+ALTER SYSTEM RESET lc_messages;  -- fail, regress_query_tuning_admin has insufficient privileges
+ERROR:  permission denied to set parameter "lc_messages"
+-- 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;  -- fail, regress_query_tuning_admin has insufficient privileges
+ERROR:  permission denied to set parameter "jit_debugging_support"
+ALTER SYSTEM RESET jit_debugging_support;  -- fail, regress_query_tuning_admin has insufficient privileges
+ERROR:  permission denied to set parameter "jit_debugging_support"
+-- PGC_USERSET
+SET DateStyle = 'ISO, MDY';  -- ok
+RESET DateStyle;  -- ok
+ALTER SYSTEM SET DateStyle = 'ISO, MDY';  -- fail, regress_query_tuning_admin has insufficient privileges
+ERROR:  permission denied to set parameter "datestyle"
+ALTER SYSTEM RESET DateStyle;  -- fail, regress_query_tuning_admin has insufficient privileges
+ERROR:  permission denied to set parameter "datestyle"
+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
+ALTER SYSTEM SET cpu_index_tuple_cost = 50;  -- ok
+ALTER SYSTEM RESET cpu_index_tuple_cost;  -- ok
+-- Finished testing role pg_manage_query_tuning_settings
+RESET statement_timeout;
+RESET SESSION AUTHORIZATION;
+DROP ROLE regress_query_tuning_admin;
diff --git a/src/test/regress/sql/guc_privs.sql b/src/test/regress/sql/guc_privs.sql
index 3932abc28f..d30284f4c9 100644
--- a/src/test/regress/sql/guc_privs.sql
+++ b/src/test/regress/sql/guc_privs.sql
@@ -499,3 +499,54 @@ ALTER SYSTEM RESET commit_siblings;  -- ok
 RESET statement_timeout;
 RESET SESSION AUTHORIZATION;
 DROP ROLE regress_wal_admin;
+-- Test role pg_manage_query_tuning_settings
+-- Non-superuser with privileges to tune query planning and execution
+CREATE ROLE regress_query_tuning_admin NOSUPERUSER;
+GRANT pg_manage_query_tuning_settings TO regress_query_tuning_admin;
+-- Perform all operations as user 'regress_query_tuning_admin' --
+SET SESSION AUTHORIZATION regress_query_tuning_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;  -- fail, regress_query_tuning_admin has insufficient privileges
+ALTER SYSTEM RESET ignore_system_indexes;  -- fail, regress_query_tuning_admin has insufficient privileges
+-- 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;  -- fail, regress_query_tuning_admin has insufficient privileges
+ALTER SYSTEM RESET autovacuum_freeze_max_age;  -- fail, regress_query_tuning_admin has insufficient privileges
+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;  -- fail, regress_query_tuning_admin has insufficient privileges
+ALTER SYSTEM RESET autovacuum;  -- fail, regress_query_tuning_admin has insufficient privileges
+-- PGC_SUSET
+SET lc_messages = 'en_US.UTF-8';  -- fail, regress_query_tuning_admin has insufficient privileges
+RESET lc_messages;  -- fail, regress_query_tuning_admin has insufficient privileges
+ALTER SYSTEM SET lc_messages = 'en_US.UTF-8';  -- fail, regress_query_tuning_admin has insufficient privileges
+ALTER SYSTEM RESET lc_messages;  -- fail, regress_query_tuning_admin has insufficient privileges
+-- 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;  -- fail, regress_query_tuning_admin has insufficient privileges
+ALTER SYSTEM RESET jit_debugging_support;  -- fail, regress_query_tuning_admin has insufficient privileges
+-- PGC_USERSET
+SET DateStyle = 'ISO, MDY';  -- ok
+RESET DateStyle;  -- ok
+ALTER SYSTEM SET DateStyle = 'ISO, MDY';  -- fail, regress_query_tuning_admin has insufficient privileges
+ALTER SYSTEM RESET DateStyle;  -- fail, regress_query_tuning_admin has insufficient privileges
+ALTER SYSTEM SET ssl_renegotiation_limit = 0;  -- fail, cannot be changed
+ALTER SYSTEM RESET ssl_renegotiation_limit;  -- fail, cannot be changed
+ALTER SYSTEM SET cpu_index_tuple_cost = 50;  -- ok
+ALTER SYSTEM RESET cpu_index_tuple_cost;  -- ok
+-- Finished testing role pg_manage_query_tuning_settings
+RESET statement_timeout;
+RESET SESSION AUTHORIZATION;
+DROP ROLE regress_query_tuning_admin;
-- 
2.21.1 (Apple Git-122.3)

