From c6f7f87019bf3b95c0139a71f357a78981e23516 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Dagfinn=20Ilmari=20Manns=C3=A5ker?= <ilmari@ilmari.org>
Date: Sat, 9 Aug 2025 00:29:39 +0100
Subject: [PATCH v6 5/5] Improve tab completion for ALTER SYSTEM (SET|RESET)
 for non-superusers

Non-superusers can only use ALTER SYSTEM on variables they've
explicitly been GRANTed access to, and can't read the data_directory
setting or pg_settings.sourceline column, so the existing tab
completion for ALTER SYSTEM isn't very useful for them.

Instead, on servers > v15, query pg_parameter_acl to show the variables
they do have permission to set via ALTER SYSTEM.
---
 src/bin/psql/tab-complete.in.c | 46 +++++++++++++++++++++++++++++++---
 1 file changed, 43 insertions(+), 3 deletions(-)

diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index 156fef50085..08a1df85492 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -304,6 +304,23 @@ do { \
 	COMPLETE_WITH_VERSIONED_QUERY_LIST(query, list); \
 } while (0)
 
+#define COMPLETE_WITH_VERSIONED_QUERY_VERBATIM(query) \
+	COMPLETE_WITH_VERSIONED_QUERY_VERBATIM_LIST(query, NULL)
+
+#define COMPLETE_WITH_VERSIONED_QUERY_VERBATIM_LIST(query, list) \
+do { \
+	completion_vquery = query; \
+	completion_charpp = list; \
+	completion_verbatim = true; \
+	matches = rl_completion_matches(text, complete_from_versioned_query); \
+} while (0)
+
+#define COMPLETE_WITH_VERSIONED_QUERY_VERBATIM_PLUS(query, ...) \
+do { \
+	static const char *const list[] = { __VA_ARGS__, NULL }; \
+	COMPLETE_WITH_VERSIONED_QUERY_VERBATIM_LIST(query, list); \
+} while (0)
+
 #define COMPLETE_WITH_SCHEMA_QUERY(query) \
 	COMPLETE_WITH_SCHEMA_QUERY_LIST(query, NULL)
 
@@ -1067,6 +1084,19 @@ static const SchemaQuery Query_for_trigger_of_table = {
 " WHERE sourcefile ~ '[\\\\/]postgresql\\.auto\\.conf$' " \
 "   AND pg_catalog.lower(name) LIKE pg_catalog.lower('%s')"
 
+static const VersionedQuery Query_for_list_of_alter_system_granted_vars[] = {
+	{150000,
+		"SELECT pg_catalog.lower(parname) FROM pg_catalog.pg_parameter_acl "
+		" WHERE EXISTS (SELECT FROM pg_catalog.aclexplode(paracl) "
+		"                WHERE pg_has_role(current_role, grantee, 'usage') "
+		"                  AND privilege_type = 'ALTER SYSTEM') "
+		"   AND pg_catalog.lower(parname) LIKE pg_catalog.lower('%s')",
+	},
+	/* this is only used for non-superusers, who can't ALTER SYSTEM before 15,
+	 * so no point in any fallback*/
+	{0, NULL},
+};
+
 #define Query_for_list_of_set_vars \
 "SELECT pg_catalog.lower(name) FROM pg_catalog.pg_settings "\
 " WHERE context IN ('user', 'superuser') "\
@@ -2653,10 +2683,20 @@ match_previous_words(int pattern_id,
 	else if (Matches("ALTER", "SYSTEM"))
 		COMPLETE_WITH("SET", "RESET");
 	else if (Matches("ALTER", "SYSTEM", "SET"))
-		COMPLETE_WITH_QUERY_VERBATIM(Query_for_list_of_alter_system_set_vars);
+	{
+		if (is_superuser())
+			COMPLETE_WITH_QUERY_VERBATIM(Query_for_list_of_alter_system_set_vars);
+		else
+			COMPLETE_WITH_VERSIONED_QUERY_VERBATIM(Query_for_list_of_alter_system_granted_vars);
+	}
 	else if (Matches("ALTER", "SYSTEM", "RESET"))
-		COMPLETE_WITH_QUERY_VERBATIM_PLUS(Query_for_list_of_alter_system_reset_vars,
-										  "ALL");
+	{
+		if (is_superuser())
+			COMPLETE_WITH_QUERY_VERBATIM_PLUS(Query_for_list_of_alter_system_reset_vars,
+											  "ALL");
+		else
+			COMPLETE_WITH_VERSIONED_QUERY_VERBATIM(Query_for_list_of_alter_system_granted_vars);
+	}
 	else if (Matches("ALTER", "SYSTEM", "SET", MatchAny))
 		COMPLETE_WITH("TO");
 	/* ALTER VIEW <name> */
-- 
2.51.2

