diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 502b5c5751..27a0ecfaf4 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -47,6 +47,7 @@
 #include "catalog/pg_class_d.h"
 #include "common.h"
 #include "libpq-fe.h"
+#include "mb/pg_wchar.h"
 #include "pqexpbuffer.h"
 #include "settings.h"
 #include "stringutils.h"
@@ -148,16 +149,17 @@ typedef struct SchemaQuery
 	const char *namespace;
 
 	/*
-	 * Result --- the appropriately-quoted name to return, in the case of an
-	 * unqualified name.  For example, "pg_catalog.quote_ident(c.relname)".
+	 * Result --- the (unquoted) name to return, in the case of an unqualified
+	 * name.  For example, "c.relname".
 	 */
 	const char *result;
 
 	/*
-	 * In some cases a different result must be used for qualified names.
-	 * Enter that here, or write NULL if result can be used.
+	 * Additional literal strings (usually keywords) to be offered along with
+	 * the query results.  Provide a NULL-terminated array of constant
+	 * strings, or NULL if none.
 	 */
-	const char *qualresult;
+	const char *const *keywords;
 } SchemaQuery;
 
 
@@ -179,6 +181,7 @@ static const char *completion_info_charp2;	/* to pass a third string */
 static const VersionedQuery *completion_vquery; /* to pass a VersionedQuery */
 static const SchemaQuery *completion_squery;	/* to pass a SchemaQuery */
 static bool completion_case_sensitive;	/* completion is case sensitive */
+static bool completion_verbatim;	/* completion is verbatim */
 static bool completion_force_quote; /* true to force-quote filenames */
 
 /*
@@ -190,36 +193,85 @@ static bool completion_force_quote; /* true to force-quote filenames */
  *	  We support both simple and versioned schema queries.
  * 3) The items from a null-pointer-terminated list (with or without
  *	  case-sensitive comparison); if the list is constant you can build it
- *	  with COMPLETE_WITH() or COMPLETE_WITH_CS().
+ *	  with COMPLETE_WITH() or COMPLETE_WITH_CS().  The QUERY_LIST and
+ *	  QUERY_PLUS forms combine such literal lists with a query result.
  * 4) The list of attributes of the given table (possibly schema-qualified).
  * 5) The list of arguments to the given function (possibly schema-qualified).
  */
 #define COMPLETE_WITH_QUERY(query) \
+	COMPLETE_WITH_QUERY_LIST(query, NULL)
+
+#define COMPLETE_WITH_QUERY_LIST(query, list) \
 do { \
 	completion_charp = query; \
+	completion_charpp = list; \
+	completion_verbatim = false; \
+	matches = rl_completion_matches(text, complete_from_query); \
+} while (0)
+
+#define COMPLETE_WITH_QUERY_PLUS(query, ...) \
+do { \
+	static const char *const list[] = { __VA_ARGS__, NULL }; \
+	COMPLETE_WITH_QUERY_LIST(query, list); \
+} while (0)
+
+#define COMPLETE_WITH_QUERY_VERBATIM(query) \
+do { \
+	completion_charp = query; \
+	completion_charpp = NULL; \
+	completion_verbatim = true; \
 	matches = rl_completion_matches(text, complete_from_query); \
 } while (0)
 
 #define COMPLETE_WITH_VERSIONED_QUERY(query) \
+	COMPLETE_WITH_VERSIONED_QUERY_LIST(query, NULL)
+
+#define COMPLETE_WITH_VERSIONED_QUERY_LIST(query, list) \
 do { \
 	completion_vquery = query; \
+	completion_charpp = list; \
+	completion_verbatim = false; \
 	matches = rl_completion_matches(text, complete_from_versioned_query); \
 } while (0)
 
-#define COMPLETE_WITH_SCHEMA_QUERY(query, addon) \
+#define COMPLETE_WITH_VERSIONED_QUERY_PLUS(query, ...) \
+do { \
+	static const char *const list[] = { __VA_ARGS__, NULL }; \
+	COMPLETE_WITH_VERSIONED_QUERY_LIST(query, list); \
+} while (0)
+
+#define COMPLETE_WITH_SCHEMA_QUERY(query) \
+	COMPLETE_WITH_SCHEMA_QUERY_LIST(query, NULL)
+
+#define COMPLETE_WITH_SCHEMA_QUERY_LIST(query, list) \
 do { \
 	completion_squery = &(query); \
-	completion_charp = addon; \
+	completion_charpp = list; \
 	matches = rl_completion_matches(text, complete_from_schema_query); \
 } while (0)
 
-#define COMPLETE_WITH_VERSIONED_SCHEMA_QUERY(query, addon) \
+#define COMPLETE_WITH_SCHEMA_QUERY_PLUS(query, ...) \
+do { \
+	static const char *const list[] = { __VA_ARGS__, NULL }; \
+	COMPLETE_WITH_SCHEMA_QUERY_LIST(query, list); \
+} while (0)
+
+#define COMPLETE_WITH_VERSIONED_SCHEMA_QUERY(query) \
+	COMPLETE_WITH_VERSIONED_SCHEMA_QUERY_LIST(query, NULL)
+
+#define COMPLETE_WITH_VERSIONED_SCHEMA_QUERY_LIST(query, list) \
 do { \
 	completion_squery = query; \
-	completion_vquery = addon; \
+	completion_charpp = list; \
 	matches = rl_completion_matches(text, complete_from_versioned_schema_query); \
 } while (0)
 
+#define COMPLETE_WITH_VERSIONED_SCHEMA_QUERY_PLUS(query, ...) \
+do { \
+	static const char *const list[] = { __VA_ARGS__, NULL }; \
+	COMPLETE_WITH_VERSIONED_SCHEMA_QUERY_LIST(query, list); \
+} while (0)
+
 /*
  * Caution: COMPLETE_WITH_CONST is not for general-purpose use; you probably
  * want COMPLETE_WITH() with one element, instead.
@@ -253,7 +305,10 @@ do { \
 	COMPLETE_WITH_LIST_CS(list); \
 } while (0)
 
-#define COMPLETE_WITH_ATTR(relation, addon) \
+#define COMPLETE_WITH_ATTR(relation) \
+	COMPLETE_WITH_ATTR_LIST(relation, NULL)
+
+#define COMPLETE_WITH_ATTR_LIST(relation, list) \
 do { \
 	char   *_completion_schema; \
 	char   *_completion_table; \
@@ -266,18 +321,26 @@ do { \
 								false, false, pset.encoding); \
 	if (_completion_table == NULL) \
 	{ \
-		completion_charp = Query_for_list_of_attributes  addon; \
+		completion_charp = Query_for_list_of_attributes; \
 		completion_info_charp = relation; \
 	} \
 	else \
 	{ \
-		completion_charp = Query_for_list_of_attributes_with_schema  addon; \
+		completion_charp = Query_for_list_of_attributes_with_schema; \
 		completion_info_charp = _completion_table; \
 		completion_info_charp2 = _completion_schema; \
 	} \
+	completion_charpp = list; \
+	completion_verbatim = false; \
 	matches = rl_completion_matches(text, complete_from_query); \
 } while (0)
 
+#define COMPLETE_WITH_ATTR_PLUS(relation, ...) \
+do { \
+	static const char *const list[] = { __VA_ARGS__, NULL }; \
+	COMPLETE_WITH_ATTR_LIST(relation, list); \
+} while (0)
+
 /*
  * libedit will typically include the literal's leading single quote in
  * "text", while readline will not.  Adapt our offered strings to fit.
@@ -315,6 +378,8 @@ do { \
 		completion_info_charp = _completion_type; \
 		completion_info_charp2 = _completion_schema; \
 	} \
+	completion_charpp = NULL; \
+	completion_verbatim = true; \
 	matches = rl_completion_matches(text, complete_from_query); \
 } while (0)
 
@@ -340,6 +405,8 @@ do { \
 		completion_info_charp = _completion_function; \
 		completion_info_charp2 = _completion_schema; \
 	} \
+	completion_charpp = NULL; \
+	completion_verbatim = true; \
 	matches = rl_completion_matches(text, complete_from_query); \
 } while (0)
 
@@ -357,17 +424,45 @@ static const SchemaQuery Query_for_list_of_aggregates[] = {
 		.selcondition = "p.prokind = 'a'",
 		.viscondition = "pg_catalog.pg_function_is_visible(p.oid)",
 		.namespace = "p.pronamespace",
-		.result = "pg_catalog.quote_ident(p.proname)",
+		.result = "p.proname",
 	},
 	{
 		.catname = "pg_catalog.pg_proc p",
 		.selcondition = "p.proisagg",
 		.viscondition = "pg_catalog.pg_function_is_visible(p.oid)",
 		.namespace = "p.pronamespace",
-		.result = "pg_catalog.quote_ident(p.proname)",
+		.result = "p.proname",
 	}
 };
 
+static const char *const Keywords_for_list_of_datatypes[] = {
+	"bigint",
+	"boolean",
+	"character",
+	"double precision",
+	"integer",
+	"real",
+	"smallint",
+
+	/*
+	 * Note: currently there's no value in offering the following multiword
+	 * type names, because tab completion cannot succeed for them: we can't
+	 * disambiguate until somewhere in the second word, at which point we
+	 * won't have the first word as context.  ("double precision" does work,
+	 * as long as no other type name begins with "double".)  Leave them out to
+	 * encourage users to use the PG-specific aliases, which we can complete.
+	 */
+#ifdef NOT_USED
+	"bit varying",
+	"character varying",
+	"time with time zone",
+	"time without time zone",
+	"timestamp with time zone",
+	"timestamp without time zone",
+#endif
+	NULL
+};
+
 static const SchemaQuery Query_for_list_of_datatypes = {
 	.catname = "pg_catalog.pg_type t",
 	/* selcondition --- ignore table rowtypes and array types */
@@ -377,8 +472,8 @@ static const SchemaQuery Query_for_list_of_datatypes = {
 	"AND t.typname !~ '^_'",
 	.viscondition = "pg_catalog.pg_type_is_visible(t.oid)",
 	.namespace = "t.typnamespace",
-	.result = "pg_catalog.format_type(t.oid, NULL)",
-	.qualresult = "pg_catalog.quote_ident(t.typname)",
+	.result = "t.typname",
+	.keywords = Keywords_for_list_of_datatypes,
 };
 
 static const SchemaQuery Query_for_list_of_composite_datatypes = {
@@ -389,8 +484,7 @@ static const SchemaQuery Query_for_list_of_composite_datatypes = {
 	"AND t.typname !~ '^_'",
 	.viscondition = "pg_catalog.pg_type_is_visible(t.oid)",
 	.namespace = "t.typnamespace",
-	.result = "pg_catalog.format_type(t.oid, NULL)",
-	.qualresult = "pg_catalog.quote_ident(t.typname)",
+	.result = "t.typname",
 };
 
 static const SchemaQuery Query_for_list_of_domains = {
@@ -398,7 +492,7 @@ static const SchemaQuery Query_for_list_of_domains = {
 	.selcondition = "t.typtype = 'd'",
 	.viscondition = "pg_catalog.pg_type_is_visible(t.oid)",
 	.namespace = "t.typnamespace",
-	.result = "pg_catalog.quote_ident(t.typname)",
+	.result = "t.typname",
 };
 
 /* Note: this intentionally accepts aggregates as well as plain functions */
@@ -409,13 +503,13 @@ static const SchemaQuery Query_for_list_of_functions[] = {
 		.selcondition = "p.prokind != 'p'",
 		.viscondition = "pg_catalog.pg_function_is_visible(p.oid)",
 		.namespace = "p.pronamespace",
-		.result = "pg_catalog.quote_ident(p.proname)",
+		.result = "p.proname",
 	},
 	{
 		.catname = "pg_catalog.pg_proc p",
 		.viscondition = "pg_catalog.pg_function_is_visible(p.oid)",
 		.namespace = "p.pronamespace",
-		.result = "pg_catalog.quote_ident(p.proname)",
+		.result = "p.proname",
 	}
 };
 
@@ -426,7 +520,7 @@ static const SchemaQuery Query_for_list_of_procedures[] = {
 		.selcondition = "p.prokind = 'p'",
 		.viscondition = "pg_catalog.pg_function_is_visible(p.oid)",
 		.namespace = "p.pronamespace",
-		.result = "pg_catalog.quote_ident(p.proname)",
+		.result = "p.proname",
 	},
 	{
 		/* not supported in older versions */
@@ -438,7 +532,7 @@ static const SchemaQuery Query_for_list_of_routines = {
 	.catname = "pg_catalog.pg_proc p",
 	.viscondition = "pg_catalog.pg_function_is_visible(p.oid)",
 	.namespace = "p.pronamespace",
-	.result = "pg_catalog.quote_ident(p.proname)",
+	.result = "p.proname",
 };
 
 static const SchemaQuery Query_for_list_of_sequences = {
@@ -446,7 +540,7 @@ static const SchemaQuery Query_for_list_of_sequences = {
 	.selcondition = "c.relkind IN (" CppAsString2(RELKIND_SEQUENCE) ")",
 	.viscondition = "pg_catalog.pg_table_is_visible(c.oid)",
 	.namespace = "c.relnamespace",
-	.result = "pg_catalog.quote_ident(c.relname)",
+	.result = "c.relname",
 };
 
 static const SchemaQuery Query_for_list_of_foreign_tables = {
@@ -454,7 +548,7 @@ static const SchemaQuery Query_for_list_of_foreign_tables = {
 	.selcondition = "c.relkind IN (" CppAsString2(RELKIND_FOREIGN_TABLE) ")",
 	.viscondition = "pg_catalog.pg_table_is_visible(c.oid)",
 	.namespace = "c.relnamespace",
-	.result = "pg_catalog.quote_ident(c.relname)",
+	.result = "c.relname",
 };
 
 static const SchemaQuery Query_for_list_of_tables = {
@@ -464,7 +558,7 @@ static const SchemaQuery Query_for_list_of_tables = {
 	CppAsString2(RELKIND_PARTITIONED_TABLE) ")",
 	.viscondition = "pg_catalog.pg_table_is_visible(c.oid)",
 	.namespace = "c.relnamespace",
-	.result = "pg_catalog.quote_ident(c.relname)",
+	.result = "c.relname",
 };
 
 static const SchemaQuery Query_for_list_of_partitioned_tables = {
@@ -472,7 +566,7 @@ static const SchemaQuery Query_for_list_of_partitioned_tables = {
 	.selcondition = "c.relkind IN (" CppAsString2(RELKIND_PARTITIONED_TABLE) ")",
 	.viscondition = "pg_catalog.pg_table_is_visible(c.oid)",
 	.namespace = "c.relnamespace",
-	.result = "pg_catalog.quote_ident(c.relname)",
+	.result = "c.relname",
 };
 
 static const SchemaQuery Query_for_list_of_views = {
@@ -480,7 +574,7 @@ static const SchemaQuery Query_for_list_of_views = {
 	.selcondition = "c.relkind IN (" CppAsString2(RELKIND_VIEW) ")",
 	.viscondition = "pg_catalog.pg_table_is_visible(c.oid)",
 	.namespace = "c.relnamespace",
-	.result = "pg_catalog.quote_ident(c.relname)",
+	.result = "c.relname",
 };
 
 static const SchemaQuery Query_for_list_of_matviews = {
@@ -488,7 +582,7 @@ static const SchemaQuery Query_for_list_of_matviews = {
 	.selcondition = "c.relkind IN (" CppAsString2(RELKIND_MATVIEW) ")",
 	.viscondition = "pg_catalog.pg_table_is_visible(c.oid)",
 	.namespace = "c.relnamespace",
-	.result = "pg_catalog.quote_ident(c.relname)",
+	.result = "c.relname",
 };
 
 static const SchemaQuery Query_for_list_of_indexes = {
@@ -498,7 +592,7 @@ static const SchemaQuery Query_for_list_of_indexes = {
 	CppAsString2(RELKIND_PARTITIONED_INDEX) ")",
 	.viscondition = "pg_catalog.pg_table_is_visible(c.oid)",
 	.namespace = "c.relnamespace",
-	.result = "pg_catalog.quote_ident(c.relname)",
+	.result = "c.relname",
 };
 
 static const SchemaQuery Query_for_list_of_partitioned_indexes = {
@@ -506,7 +600,7 @@ static const SchemaQuery Query_for_list_of_partitioned_indexes = {
 	.selcondition = "c.relkind = " CppAsString2(RELKIND_PARTITIONED_INDEX),
 	.viscondition = "pg_catalog.pg_table_is_visible(c.oid)",
 	.namespace = "c.relnamespace",
-	.result = "pg_catalog.quote_ident(c.relname)",
+	.result = "c.relname",
 };
 
 
@@ -515,7 +609,7 @@ static const SchemaQuery Query_for_list_of_relations = {
 	.catname = "pg_catalog.pg_class c",
 	.viscondition = "pg_catalog.pg_table_is_visible(c.oid)",
 	.namespace = "c.relnamespace",
-	.result = "pg_catalog.quote_ident(c.relname)",
+	.result = "c.relname",
 };
 
 /* partitioned relations */
@@ -525,14 +619,14 @@ static const SchemaQuery Query_for_list_of_partitioned_relations = {
 	", " CppAsString2(RELKIND_PARTITIONED_INDEX) ")",
 	.viscondition = "pg_catalog.pg_table_is_visible(c.oid)",
 	.namespace = "c.relnamespace",
-	.result = "pg_catalog.quote_ident(c.relname)",
+	.result = "c.relname",
 };
 
 static const SchemaQuery Query_for_list_of_operator_families = {
 	.catname = "pg_catalog.pg_opfamily c",
 	.viscondition = "pg_catalog.pg_opfamily_is_visible(c.oid)",
 	.namespace = "c.opfnamespace",
-	.result = "pg_catalog.quote_ident(c.opfname)",
+	.result = "c.opfname",
 };
 
 /* Relations supporting INSERT, UPDATE or DELETE */
@@ -545,7 +639,7 @@ static const SchemaQuery Query_for_list_of_updatables = {
 	CppAsString2(RELKIND_PARTITIONED_TABLE) ")",
 	.viscondition = "pg_catalog.pg_table_is_visible(c.oid)",
 	.namespace = "c.relnamespace",
-	.result = "pg_catalog.quote_ident(c.relname)",
+	.result = "c.relname",
 };
 
 /* Relations supporting SELECT */
@@ -560,7 +654,7 @@ static const SchemaQuery Query_for_list_of_selectables = {
 	CppAsString2(RELKIND_PARTITIONED_TABLE) ")",
 	.viscondition = "pg_catalog.pg_table_is_visible(c.oid)",
 	.namespace = "c.relnamespace",
-	.result = "pg_catalog.quote_ident(c.relname)",
+	.result = "c.relname",
 };
 
 /* Relations supporting TRUNCATE */
@@ -572,7 +666,7 @@ static const SchemaQuery Query_for_list_of_truncatables = {
 	CppAsString2(RELKIND_PARTITIONED_TABLE) ")",
 	.viscondition = "pg_catalog.pg_table_is_visible(c.oid)",
 	.namespace = "c.relnamespace",
-	.result = "pg_catalog.quote_ident(c.relname)",
+	.result = "c.relname",
 };
 
 /* Relations supporting GRANT are currently same as those supporting SELECT */
@@ -588,7 +682,7 @@ static const SchemaQuery Query_for_list_of_analyzables = {
 	CppAsString2(RELKIND_FOREIGN_TABLE) ")",
 	.viscondition = "pg_catalog.pg_table_is_visible(c.oid)",
 	.namespace = "c.relnamespace",
-	.result = "pg_catalog.quote_ident(c.relname)",
+	.result = "c.relname",
 };
 
 /* Relations supporting index creation */
@@ -600,7 +694,7 @@ static const SchemaQuery Query_for_list_of_indexables = {
 	CppAsString2(RELKIND_MATVIEW) ")",
 	.viscondition = "pg_catalog.pg_table_is_visible(c.oid)",
 	.namespace = "c.relnamespace",
-	.result = "pg_catalog.quote_ident(c.relname)",
+	.result = "c.relname",
 };
 
 /*
@@ -617,7 +711,7 @@ static const SchemaQuery Query_for_list_of_clusterables = {
 	CppAsString2(RELKIND_MATVIEW) ")",
 	.viscondition = "pg_catalog.pg_table_is_visible(c.oid)",
 	.namespace = "c.relnamespace",
-	.result = "pg_catalog.quote_ident(c.relname)",
+	.result = "c.relname",
 };
 
 static const SchemaQuery Query_for_list_of_constraints_with_schema = {
@@ -625,14 +719,14 @@ static const SchemaQuery Query_for_list_of_constraints_with_schema = {
 	.selcondition = "c.conrelid <> 0",
 	.viscondition = "true",		/* there is no pg_constraint_is_visible */
 	.namespace = "c.connamespace",
-	.result = "pg_catalog.quote_ident(c.conname)",
+	.result = "c.conname",
 };
 
 static const SchemaQuery Query_for_list_of_statistics = {
 	.catname = "pg_catalog.pg_statistic_ext s",
 	.viscondition = "pg_catalog.pg_statistics_obj_is_visible(s.oid)",
 	.namespace = "s.stxnamespace",
-	.result = "pg_catalog.quote_ident(s.stxname)",
+	.result = "s.stxname",
 };
 
 static const SchemaQuery Query_for_list_of_collations = {
@@ -640,7 +734,7 @@ static const SchemaQuery Query_for_list_of_collations = {
 	.selcondition = "c.collencoding IN (-1, pg_catalog.pg_char_to_encoding(pg_catalog.getdatabaseencoding()))",
 	.viscondition = "pg_catalog.pg_collation_is_visible(c.oid)",
 	.namespace = "c.collnamespace",
-	.result = "pg_catalog.quote_ident(c.collname)",
+	.result = "c.collname",
 };
 
 
@@ -659,13 +753,13 @@ static const SchemaQuery Query_for_list_of_collations = {
  */
 
 #define Query_for_list_of_attributes \
-"SELECT pg_catalog.quote_ident(attname) "\
+"SELECT attname "\
 "  FROM pg_catalog.pg_attribute a, pg_catalog.pg_class c "\
 " WHERE c.oid = a.attrelid "\
 "   AND a.attnum > 0 "\
 "   AND NOT a.attisdropped "\
-"   AND substring(pg_catalog.quote_ident(attname),1,%d)='%s' "\
-"   AND (pg_catalog.quote_ident(relname)='%s' "\
+"   AND substring(attname,1,%d)='%s' "\
+"   AND (relname='%s' "\
 "        OR '\"' || relname || '\"'='%s') "\
 "   AND pg_catalog.pg_table_is_visible(c.oid)"
 
@@ -676,21 +770,21 @@ static const SchemaQuery Query_for_list_of_collations = {
 "   AND a.attnum > 0 "\
 "   AND NOT a.attisdropped "\
 "   AND substring(attnum::pg_catalog.text,1,%d)='%s' "\
-"   AND (pg_catalog.quote_ident(relname)='%s' "\
+"   AND (relname='%s' "\
 "        OR '\"' || relname || '\"'='%s') "\
 "   AND pg_catalog.pg_table_is_visible(c.oid)"
 
 #define Query_for_list_of_attributes_with_schema \
-"SELECT pg_catalog.quote_ident(attname) "\
+"SELECT attname "\
 "  FROM pg_catalog.pg_attribute a, pg_catalog.pg_class c, pg_catalog.pg_namespace n "\
 " WHERE c.oid = a.attrelid "\
 "   AND n.oid = c.relnamespace "\
 "   AND a.attnum > 0 "\
 "   AND NOT a.attisdropped "\
-"   AND substring(pg_catalog.quote_ident(attname),1,%d)='%s' "\
-"   AND (pg_catalog.quote_ident(relname)='%s' "\
+"   AND substring(attname,1,%d)='%s' "\
+"   AND (relname='%s' "\
 "        OR '\"' || relname || '\"' ='%s') "\
-"   AND (pg_catalog.quote_ident(nspname)='%s' "\
+"   AND (nspname='%s' "\
 "        OR '\"' || nspname || '\"' ='%s') "
 
 #define Query_for_list_of_enum_values_quoted \
@@ -698,7 +792,7 @@ static const SchemaQuery Query_for_list_of_collations = {
 "  FROM pg_catalog.pg_enum e, pg_catalog.pg_type t "\
 " WHERE t.oid = e.enumtypid "\
 "   AND substring(pg_catalog.quote_literal(enumlabel),1,%d)='%s' "\
-"   AND (pg_catalog.quote_ident(typname)='%s' "\
+"   AND (typname='%s' "\
 "        OR '\"' || typname || '\"'='%s') "\
 "   AND pg_catalog.pg_type_is_visible(t.oid)"
 
@@ -707,7 +801,7 @@ static const SchemaQuery Query_for_list_of_collations = {
 "  FROM pg_catalog.pg_enum e, pg_catalog.pg_type t "\
 " WHERE t.oid = e.enumtypid "\
 "   AND substring(enumlabel,1,%d)='%s' "\
-"   AND (pg_catalog.quote_ident(typname)='%s' "\
+"   AND (typname='%s' "\
 "        OR '\"' || typname || '\"'='%s') "\
 "   AND pg_catalog.pg_type_is_visible(t.oid)"
 
@@ -717,9 +811,9 @@ static const SchemaQuery Query_for_list_of_collations = {
 " WHERE t.oid = e.enumtypid "\
 "   AND n.oid = t.typnamespace "\
 "   AND substring(pg_catalog.quote_literal(enumlabel),1,%d)='%s' "\
-"   AND (pg_catalog.quote_ident(typname)='%s' "\
+"   AND (typname='%s' "\
 "        OR '\"' || typname || '\"'='%s') "\
-"   AND (pg_catalog.quote_ident(nspname)='%s' "\
+"   AND (nspname='%s' "\
 "        OR '\"' || nspname || '\"' ='%s') "
 
 #define Query_for_list_of_enum_values_with_schema_unquoted \
@@ -728,24 +822,24 @@ static const SchemaQuery Query_for_list_of_collations = {
 " WHERE t.oid = e.enumtypid "\
 "   AND n.oid = t.typnamespace "\
 "   AND substring(enumlabel,1,%d)='%s' "\
-"   AND (pg_catalog.quote_ident(typname)='%s' "\
+"   AND (typname='%s' "\
 "        OR '\"' || typname || '\"'='%s') "\
-"   AND (pg_catalog.quote_ident(nspname)='%s' "\
+"   AND (nspname='%s' "\
 "        OR '\"' || nspname || '\"' ='%s') "
 
 #define Query_for_list_of_template_databases \
-"SELECT pg_catalog.quote_ident(d.datname) "\
+"SELECT d.datname "\
 "  FROM pg_catalog.pg_database d "\
-" WHERE substring(pg_catalog.quote_ident(d.datname),1,%d)='%s' "\
+" WHERE substring(d.datname,1,%d)='%s' "\
 "   AND (d.datistemplate OR pg_catalog.pg_has_role(d.datdba, 'USAGE'))"
 
 #define Query_for_list_of_databases \
-"SELECT pg_catalog.quote_ident(datname) FROM pg_catalog.pg_database "\
-" WHERE substring(pg_catalog.quote_ident(datname),1,%d)='%s'"
+"SELECT datname FROM pg_catalog.pg_database "\
+" WHERE substring(datname,1,%d)='%s'"
 
 #define Query_for_list_of_tablespaces \
-"SELECT pg_catalog.quote_ident(spcname) FROM pg_catalog.pg_tablespace "\
-" WHERE substring(pg_catalog.quote_ident(spcname),1,%d)='%s'"
+"SELECT spcname FROM pg_catalog.pg_tablespace "\
+" WHERE substring(spcname,1,%d)='%s'"
 
 #define Query_for_list_of_encodings \
 " SELECT DISTINCT pg_catalog.pg_encoding_to_char(conforencoding) "\
@@ -753,287 +847,260 @@ static const SchemaQuery Query_for_list_of_collations = {
 "  WHERE substring(pg_catalog.pg_encoding_to_char(conforencoding),1,%d)=UPPER('%s')"
 
 #define Query_for_list_of_languages \
-"SELECT pg_catalog.quote_ident(lanname) "\
+"SELECT lanname "\
 "  FROM pg_catalog.pg_language "\
 " WHERE lanname != 'internal' "\
-"   AND substring(pg_catalog.quote_ident(lanname),1,%d)='%s'"
+"   AND substring(lanname,1,%d)='%s'"
 
 #define Query_for_list_of_schemas \
-"SELECT pg_catalog.quote_ident(nspname) FROM pg_catalog.pg_namespace "\
-" WHERE substring(pg_catalog.quote_ident(nspname),1,%d)='%s'"
+"SELECT nspname FROM pg_catalog.pg_namespace "\
+" WHERE substring(nspname,1,%d)='%s'"
 
 #define Query_for_list_of_alter_system_set_vars \
 "SELECT name FROM "\
 " (SELECT pg_catalog.lower(name) AS name FROM pg_catalog.pg_settings "\
 "  WHERE context != 'internal' "\
-"  UNION ALL SELECT 'all') ss "\
+" ) ss "\
 " WHERE substring(name,1,%d)='%s'"
 
 #define Query_for_list_of_set_vars \
 "SELECT name FROM "\
 " (SELECT pg_catalog.lower(name) AS name FROM pg_catalog.pg_settings "\
 "  WHERE context IN ('user', 'superuser') "\
-"  UNION ALL SELECT 'constraints' "\
-"  UNION ALL SELECT 'transaction' "\
-"  UNION ALL SELECT 'session' "\
-"  UNION ALL SELECT 'role' "\
-"  UNION ALL SELECT 'tablespace' "\
-"  UNION ALL SELECT 'all') ss "\
+" ) ss "\
 " WHERE substring(name,1,%d)='%s'"
 
 #define Query_for_list_of_show_vars \
 "SELECT name FROM "\
 " (SELECT pg_catalog.lower(name) AS name FROM pg_catalog.pg_settings "\
-"  UNION ALL SELECT 'session authorization' "\
-"  UNION ALL SELECT 'all') ss "\
+" ) ss "\
 " WHERE substring(name,1,%d)='%s'"
 
 #define Query_for_list_of_roles \
-" SELECT pg_catalog.quote_ident(rolname) "\
+" SELECT rolname "\
 "   FROM pg_catalog.pg_roles "\
-"  WHERE substring(pg_catalog.quote_ident(rolname),1,%d)='%s'"
+"  WHERE substring(rolname,1,%d)='%s'"
 
-#define Query_for_list_of_grant_roles \
-" SELECT pg_catalog.quote_ident(rolname) "\
-"   FROM pg_catalog.pg_roles "\
-"  WHERE substring(pg_catalog.quote_ident(rolname),1,%d)='%s'"\
-" UNION ALL SELECT 'PUBLIC'"\
-" UNION ALL SELECT 'CURRENT_ROLE'"\
-" UNION ALL SELECT 'CURRENT_USER'"\
-" UNION ALL SELECT 'SESSION_USER'"
+/* add these to Query_for_list_of_roles in GRANT contexts */
+#define Keywords_for_list_of_grant_roles \
+"PUBLIC", "CURRENT_ROLE", "CURRENT_USER", "SESSION_USER"
 
-/* the silly-looking length condition is just to eat up the current word */
 #define Query_for_index_of_table \
-"SELECT pg_catalog.quote_ident(c2.relname) "\
+"SELECT c2.relname "\
 "  FROM pg_catalog.pg_class c1, pg_catalog.pg_class c2, pg_catalog.pg_index i"\
 " WHERE c1.oid=i.indrelid and i.indexrelid=c2.oid"\
-"       and (%d = pg_catalog.length('%s'))"\
-"       and pg_catalog.quote_ident(c1.relname)='%s'"\
+"       and substring(c2.relname,1,%d)='%s'"\
+"       and c1.relname='%s'"\
 "       and pg_catalog.pg_table_is_visible(c2.oid)"
 
 #define Query_for_unique_index_of_table \
 Query_for_index_of_table \
 "       and i.indisunique"
 
-/* the silly-looking length condition is just to eat up the current word */
 #define Query_for_constraint_of_table \
-"SELECT pg_catalog.quote_ident(conname) "\
+"SELECT conname "\
 "  FROM pg_catalog.pg_class c1, pg_catalog.pg_constraint con "\
-" WHERE c1.oid=conrelid and (%d = pg_catalog.length('%s'))"\
-"       and pg_catalog.quote_ident(c1.relname)='%s'"\
+" WHERE c1.oid=conrelid and substring(conname,1,%d)='%s'"\
+"       and c1.relname='%s'"\
 "       and pg_catalog.pg_table_is_visible(c1.oid)"
 
-/* the silly-looking length condition is just to eat up the current word */
 #define Query_for_constraint_of_table_not_validated \
-"SELECT pg_catalog.quote_ident(conname) "\
-"  FROM pg_catalog.pg_class c1, pg_catalog.pg_constraint con "\
-" WHERE c1.oid=conrelid and (%d = pg_catalog.length('%s'))"\
-"       and pg_catalog.quote_ident(c1.relname)='%s'"\
-"       and pg_catalog.pg_table_is_visible(c1.oid)" \
+Query_for_constraint_of_table \
 "       and not con.convalidated"
 
 #define Query_for_all_table_constraints \
-"SELECT pg_catalog.quote_ident(conname) "\
+"SELECT conname "\
 "  FROM pg_catalog.pg_constraint c "\
-" WHERE c.conrelid <> 0 "
+" WHERE c.conrelid <> 0 "\
+"       and substring(conname,1,%d)='%s'"
 
-/* the silly-looking length condition is just to eat up the current word */
 #define Query_for_constraint_of_type \
-"SELECT pg_catalog.quote_ident(conname) "\
+"SELECT conname "\
 "  FROM pg_catalog.pg_type t, pg_catalog.pg_constraint con "\
-" WHERE t.oid=contypid and (%d = pg_catalog.length('%s'))"\
-"       and pg_catalog.quote_ident(t.typname)='%s'"\
+" WHERE t.oid=contypid and substring(conname,1,%d)='%s'"\
+"       and t.typname='%s'"\
 "       and pg_catalog.pg_type_is_visible(t.oid)"
 
-/* the silly-looking length condition is just to eat up the current word */
 #define Query_for_list_of_tables_for_constraint \
-"SELECT pg_catalog.quote_ident(relname) "\
+"SELECT relname "\
 "  FROM pg_catalog.pg_class"\
-" WHERE (%d = pg_catalog.length('%s'))"\
+" WHERE substring(relname,1,%d)='%s'"\
 "   AND oid IN "\
 "       (SELECT conrelid FROM pg_catalog.pg_constraint "\
-"         WHERE pg_catalog.quote_ident(conname)='%s')"
+"         WHERE conname='%s')"
 
-/* the silly-looking length condition is just to eat up the current word */
 #define Query_for_rule_of_table \
-"SELECT pg_catalog.quote_ident(rulename) "\
+"SELECT rulename "\
 "  FROM pg_catalog.pg_class c1, pg_catalog.pg_rewrite "\
-" WHERE c1.oid=ev_class and (%d = pg_catalog.length('%s'))"\
-"       and pg_catalog.quote_ident(c1.relname)='%s'"\
+" WHERE c1.oid=ev_class and substring(rulename,1,%d)='%s'"\
+"       and c1.relname='%s'"\
 "       and pg_catalog.pg_table_is_visible(c1.oid)"
 
-/* the silly-looking length condition is just to eat up the current word */
 #define Query_for_list_of_tables_for_rule \
-"SELECT pg_catalog.quote_ident(relname) "\
+"SELECT relname "\
 "  FROM pg_catalog.pg_class"\
-" WHERE (%d = pg_catalog.length('%s'))"\
+" WHERE substring(relname,1,%d)='%s'"\
 "   AND oid IN "\
 "       (SELECT ev_class FROM pg_catalog.pg_rewrite "\
-"         WHERE pg_catalog.quote_ident(rulename)='%s')"
+"         WHERE rulename='%s')"
 
-/* the silly-looking length condition is just to eat up the current word */
 #define Query_for_trigger_of_table \
-"SELECT pg_catalog.quote_ident(tgname) "\
+"SELECT tgname "\
 "  FROM pg_catalog.pg_class c1, pg_catalog.pg_trigger "\
-" WHERE c1.oid=tgrelid and (%d = pg_catalog.length('%s'))"\
-"       and pg_catalog.quote_ident(c1.relname)='%s'"\
+" WHERE c1.oid=tgrelid and substring(tgname,1,%d)='%s'"\
+"       and c1.relname='%s'"\
 "       and pg_catalog.pg_table_is_visible(c1.oid)"\
 "       and not tgisinternal"
 
-/* the silly-looking length condition is just to eat up the current word */
 #define Query_for_list_of_tables_for_trigger \
-"SELECT pg_catalog.quote_ident(relname) "\
+"SELECT relname "\
 "  FROM pg_catalog.pg_class"\
-" WHERE (%d = pg_catalog.length('%s'))"\
+" WHERE substring(relname,1,%d)='%s'"\
 "   AND oid IN "\
 "       (SELECT tgrelid FROM pg_catalog.pg_trigger "\
-"         WHERE pg_catalog.quote_ident(tgname)='%s')"
+"         WHERE tgname='%s')"
 
 #define Query_for_list_of_ts_configurations \
-"SELECT pg_catalog.quote_ident(cfgname) FROM pg_catalog.pg_ts_config "\
-" WHERE substring(pg_catalog.quote_ident(cfgname),1,%d)='%s'"
+"SELECT cfgname FROM pg_catalog.pg_ts_config "\
+" WHERE substring(cfgname,1,%d)='%s'"
 
 #define Query_for_list_of_ts_dictionaries \
-"SELECT pg_catalog.quote_ident(dictname) FROM pg_catalog.pg_ts_dict "\
-" WHERE substring(pg_catalog.quote_ident(dictname),1,%d)='%s'"
+"SELECT dictname FROM pg_catalog.pg_ts_dict "\
+" WHERE substring(dictname,1,%d)='%s'"
 
 #define Query_for_list_of_ts_parsers \
-"SELECT pg_catalog.quote_ident(prsname) FROM pg_catalog.pg_ts_parser "\
-" WHERE substring(pg_catalog.quote_ident(prsname),1,%d)='%s'"
+"SELECT prsname FROM pg_catalog.pg_ts_parser "\
+" WHERE substring(prsname,1,%d)='%s'"
 
 #define Query_for_list_of_ts_templates \
-"SELECT pg_catalog.quote_ident(tmplname) FROM pg_catalog.pg_ts_template "\
-" WHERE substring(pg_catalog.quote_ident(tmplname),1,%d)='%s'"
+"SELECT tmplname FROM pg_catalog.pg_ts_template "\
+" WHERE substring(tmplname,1,%d)='%s'"
 
 #define Query_for_list_of_fdws \
-" SELECT pg_catalog.quote_ident(fdwname) "\
+" SELECT fdwname "\
 "   FROM pg_catalog.pg_foreign_data_wrapper "\
-"  WHERE substring(pg_catalog.quote_ident(fdwname),1,%d)='%s'"
+"  WHERE substring(fdwname,1,%d)='%s'"
 
 #define Query_for_list_of_servers \
-" SELECT pg_catalog.quote_ident(srvname) "\
+" SELECT srvname "\
 "   FROM pg_catalog.pg_foreign_server "\
-"  WHERE substring(pg_catalog.quote_ident(srvname),1,%d)='%s'"
+"  WHERE substring(srvname,1,%d)='%s'"
 
 #define Query_for_list_of_user_mappings \
-" SELECT pg_catalog.quote_ident(usename) "\
+" SELECT usename "\
 "   FROM pg_catalog.pg_user_mappings "\
-"  WHERE substring(pg_catalog.quote_ident(usename),1,%d)='%s'"
+"  WHERE substring(usename,1,%d)='%s'"
 
 #define Query_for_list_of_access_methods \
-" SELECT pg_catalog.quote_ident(amname) "\
+" SELECT amname "\
 "   FROM pg_catalog.pg_am "\
-"  WHERE substring(pg_catalog.quote_ident(amname),1,%d)='%s'"
+"  WHERE substring(amname,1,%d)='%s'"
 
 #define Query_for_list_of_index_access_methods \
-" SELECT pg_catalog.quote_ident(amname) "\
+" SELECT amname "\
 "   FROM pg_catalog.pg_am "\
-"  WHERE substring(pg_catalog.quote_ident(amname),1,%d)='%s' AND "\
+"  WHERE substring(amname,1,%d)='%s' AND "\
 "   amtype=" CppAsString2(AMTYPE_INDEX)
 
 #define Query_for_list_of_table_access_methods \
-" SELECT pg_catalog.quote_ident(amname) "\
+" SELECT amname "\
 "   FROM pg_catalog.pg_am "\
-"  WHERE substring(pg_catalog.quote_ident(amname),1,%d)='%s' AND "\
+"  WHERE substring(amname,1,%d)='%s' AND "\
 "   amtype=" CppAsString2(AMTYPE_TABLE)
 
-/* the silly-looking length condition is just to eat up the current word */
 #define Query_for_list_of_arguments \
 "SELECT pg_catalog.oidvectortypes(proargtypes)||')' "\
 "  FROM pg_catalog.pg_proc "\
-" WHERE (%d = pg_catalog.length('%s'))"\
-"   AND (pg_catalog.quote_ident(proname)='%s'"\
+" WHERE substring(pg_catalog.oidvectortypes(proargtypes)||')',1,%d)='%s'"\
+"   AND (proname='%s'"\
 "        OR '\"' || proname || '\"'='%s') "\
 "   AND (pg_catalog.pg_function_is_visible(pg_proc.oid))"
 
-/* the silly-looking length condition is just to eat up the current word */
 #define Query_for_list_of_arguments_with_schema \
 "SELECT pg_catalog.oidvectortypes(proargtypes)||')' "\
 "  FROM pg_catalog.pg_proc p, pg_catalog.pg_namespace n "\
-" WHERE (%d = pg_catalog.length('%s'))"\
+" WHERE substring(pg_catalog.oidvectortypes(proargtypes)||')',1,%d)='%s'"\
 "   AND n.oid = p.pronamespace "\
-"   AND (pg_catalog.quote_ident(proname)='%s' "\
+"   AND (proname='%s' "\
 "        OR '\"' || proname || '\"' ='%s') "\
-"   AND (pg_catalog.quote_ident(nspname)='%s' "\
+"   AND (nspname='%s' "\
 "        OR '\"' || nspname || '\"' ='%s') "
 
 #define Query_for_list_of_extensions \
-" SELECT pg_catalog.quote_ident(extname) "\
+" SELECT extname "\
 "   FROM pg_catalog.pg_extension "\
-"  WHERE substring(pg_catalog.quote_ident(extname),1,%d)='%s'"
+"  WHERE substring(extname,1,%d)='%s'"
 
 #define Query_for_list_of_available_extensions \
-" SELECT pg_catalog.quote_ident(name) "\
+" SELECT name "\
 "   FROM pg_catalog.pg_available_extensions "\
-"  WHERE substring(pg_catalog.quote_ident(name),1,%d)='%s' AND installed_version IS NULL"
+"  WHERE substring(name,1,%d)='%s' AND installed_version IS NULL"
 
-/* the silly-looking length condition is just to eat up the current word */
+/* the result of this query is not an identifier, so use VERBATIM */
 #define Query_for_list_of_available_extension_versions \
-" SELECT pg_catalog.quote_ident(version) "\
+" SELECT version "\
 "   FROM pg_catalog.pg_available_extension_versions "\
-"  WHERE (%d = pg_catalog.length('%s'))"\
-"    AND pg_catalog.quote_ident(name)='%s'"
+"  WHERE substring(version,1,%d)='%s'"\
+"    AND name='%s'"
 
-/* the silly-looking length condition is just to eat up the current word */
+/* the result of this query is not an identifier, so use VERBATIM */
 #define Query_for_list_of_available_extension_versions_with_TO \
-" SELECT 'TO ' || pg_catalog.quote_ident(version) "\
+" SELECT 'TO ' || version "\
 "   FROM pg_catalog.pg_available_extension_versions "\
-"  WHERE (%d = pg_catalog.length('%s'))"\
-"    AND pg_catalog.quote_ident(name)='%s'"
+"  WHERE substring('TO ' || version,1,%d)='%s'"\
+"    AND name='%s'"
 
 #define Query_for_list_of_prepared_statements \
-" SELECT pg_catalog.quote_ident(name) "\
+" SELECT name "\
 "   FROM pg_catalog.pg_prepared_statements "\
-"  WHERE substring(pg_catalog.quote_ident(name),1,%d)='%s'"
+"  WHERE substring(name,1,%d)='%s'"
 
 #define Query_for_list_of_event_triggers \
-" SELECT pg_catalog.quote_ident(evtname) "\
+" SELECT evtname "\
 "   FROM pg_catalog.pg_event_trigger "\
-"  WHERE substring(pg_catalog.quote_ident(evtname),1,%d)='%s'"
+"  WHERE substring(evtname,1,%d)='%s'"
 
 #define Query_for_list_of_tablesample_methods \
-" SELECT pg_catalog.quote_ident(proname) "\
+" SELECT proname "\
 "   FROM pg_catalog.pg_proc "\
 "  WHERE prorettype = 'pg_catalog.tsm_handler'::pg_catalog.regtype AND "\
 "        proargtypes[0] = 'pg_catalog.internal'::pg_catalog.regtype AND "\
-"        substring(pg_catalog.quote_ident(proname),1,%d)='%s'"
+"        substring(proname,1,%d)='%s'"
 
 #define Query_for_list_of_policies \
-" SELECT pg_catalog.quote_ident(polname) "\
+" SELECT polname "\
 "   FROM pg_catalog.pg_policy "\
-"  WHERE substring(pg_catalog.quote_ident(polname),1,%d)='%s'"
+"  WHERE substring(polname,1,%d)='%s'"
 
 #define Query_for_list_of_tables_for_policy \
-"SELECT pg_catalog.quote_ident(relname) "\
+"SELECT relname "\
 "  FROM pg_catalog.pg_class"\
-" WHERE (%d = pg_catalog.length('%s'))"\
+" WHERE substring(relname,1,%d)='%s'"\
 "   AND oid IN "\
 "       (SELECT polrelid FROM pg_catalog.pg_policy "\
-"         WHERE pg_catalog.quote_ident(polname)='%s')"
+"         WHERE polname='%s')"
 
 #define Query_for_enum \
 " SELECT name FROM ( "\
-"   SELECT pg_catalog.quote_ident(pg_catalog.unnest(enumvals)) AS name "\
+"   SELECT pg_catalog.unnest(enumvals) AS name "\
 "     FROM pg_catalog.pg_settings "\
 "    WHERE pg_catalog.lower(name)=pg_catalog.lower('%s') "\
-"    UNION ALL " \
-"   SELECT 'DEFAULT' ) ss "\
+"    ) ss "\
 "  WHERE pg_catalog.substring(name,1,%%d)='%%s'"
 
-/* the silly-looking length condition is just to eat up the current word */
 #define Query_for_partition_of_table \
-"SELECT pg_catalog.quote_ident(c2.relname) "\
+"SELECT c2.relname "\
 "  FROM pg_catalog.pg_class c1, pg_catalog.pg_class c2, pg_catalog.pg_inherits i"\
 " WHERE c1.oid=i.inhparent and i.inhrelid=c2.oid"\
-"       and (%d = pg_catalog.length('%s'))"\
-"       and pg_catalog.quote_ident(c1.relname)='%s'"\
+"       and substring(c2.relname,1,%d)='%s'"\
+"       and c1.relname='%s'"\
 "       and pg_catalog.pg_table_is_visible(c2.oid)"\
 "       and c2.relispartition = 'true'"
 
 #define Query_for_list_of_cursors \
-" SELECT pg_catalog.quote_ident(name) "\
+" SELECT name "\
 "   FROM pg_catalog.pg_cursors "\
-"  WHERE substring(pg_catalog.quote_ident(name),1,%d)='%s'"
+"  WHERE substring(name,1,%d)='%s'"
 
 /*
  * These object types were introduced later than our support cutoff of
@@ -1043,18 +1110,18 @@ Query_for_index_of_table \
 
 static const VersionedQuery Query_for_list_of_publications[] = {
 	{100000,
-		" SELECT pg_catalog.quote_ident(pubname) "
+		" SELECT pubname "
 		"   FROM pg_catalog.pg_publication "
-		"  WHERE substring(pg_catalog.quote_ident(pubname),1,%d)='%s'"
+		"  WHERE substring(pubname,1,%d)='%s'"
 	},
 	{0, NULL}
 };
 
 static const VersionedQuery Query_for_list_of_subscriptions[] = {
 	{100000,
-		" SELECT pg_catalog.quote_ident(s.subname) "
+		" SELECT s.subname "
 		"   FROM pg_catalog.pg_subscription s, pg_catalog.pg_database d "
-		"  WHERE substring(pg_catalog.quote_ident(s.subname),1,%d)='%s' "
+		"  WHERE substring(s.subname,1,%d)='%s' "
 		"    AND d.datname = pg_catalog.current_database() "
 		"    AND s.subdbid = d.oid"
 	},
@@ -1069,9 +1136,11 @@ static const VersionedQuery Query_for_list_of_subscriptions[] = {
 typedef struct
 {
 	const char *name;
+	/* Provide at most one of these three types of query: */
 	const char *query;			/* simple query, or NULL */
 	const VersionedQuery *vquery;	/* versioned query, or NULL */
 	const SchemaQuery *squery;	/* schema query, or NULL */
+	const char *const *keywords;	/* keywords to be offered as well */
 	const bits32 flags;			/* visibility flags, see below */
 } pgsql_thing_t;
 
@@ -1080,8 +1149,14 @@ typedef struct
 #define THING_NO_ALTER		(1 << 2)	/* should not show up after ALTER */
 #define THING_NO_SHOW		(THING_NO_CREATE | THING_NO_DROP | THING_NO_ALTER)
 
+/* When we have DROP USER etc, also offer MAPPING FOR */
+static const char *const Keywords_for_user_thing[] = {
+	"MAPPING FOR",
+	NULL
+};
+
 static const pgsql_thing_t words_after_create[] = {
-	{"ACCESS METHOD", NULL, NULL, NULL, THING_NO_ALTER},
+	{"ACCESS METHOD", NULL, NULL, NULL, NULL, THING_NO_ALTER},
 	{"AGGREGATE", NULL, NULL, Query_for_list_of_aggregates},
 	{"CAST", NULL, NULL, NULL}, /* Casts have complex structures for names, so
 								 * skip it */
@@ -1091,11 +1166,11 @@ static const pgsql_thing_t words_after_create[] = {
 	 * CREATE CONSTRAINT TRIGGER is not supported here because it is designed
 	 * to be used only by pg_dump.
 	 */
-	{"CONFIGURATION", Query_for_list_of_ts_configurations, NULL, NULL, THING_NO_SHOW},
-	{"CONVERSION", "SELECT pg_catalog.quote_ident(conname) FROM pg_catalog.pg_conversion WHERE substring(pg_catalog.quote_ident(conname),1,%d)='%s'"},
+	{"CONFIGURATION", Query_for_list_of_ts_configurations, NULL, NULL, NULL, THING_NO_SHOW},
+	{"CONVERSION", "SELECT conname FROM pg_catalog.pg_conversion WHERE substring(conname,1,%d)='%s'"},
 	{"DATABASE", Query_for_list_of_databases},
-	{"DEFAULT PRIVILEGES", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
-	{"DICTIONARY", Query_for_list_of_ts_dictionaries, NULL, NULL, THING_NO_SHOW},
+	{"DEFAULT PRIVILEGES", NULL, NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
+	{"DICTIONARY", Query_for_list_of_ts_dictionaries, NULL, NULL, NULL, THING_NO_SHOW},
 	{"DOMAIN", NULL, NULL, &Query_for_list_of_domains},
 	{"EVENT TRIGGER", NULL, NULL, NULL},
 	{"EXTENSION", Query_for_list_of_extensions},
@@ -1105,41 +1180,41 @@ static const pgsql_thing_t words_after_create[] = {
 	{"GROUP", Query_for_list_of_roles},
 	{"INDEX", NULL, NULL, &Query_for_list_of_indexes},
 	{"LANGUAGE", Query_for_list_of_languages},
-	{"LARGE OBJECT", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
+	{"LARGE OBJECT", NULL, NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
 	{"MATERIALIZED VIEW", NULL, NULL, &Query_for_list_of_matviews},
 	{"OPERATOR", NULL, NULL, NULL}, /* Querying for this is probably not such
 									 * a good idea. */
-	{"OR REPLACE", NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER},
-	{"OWNED", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_ALTER},	/* for DROP OWNED BY ... */
-	{"PARSER", Query_for_list_of_ts_parsers, NULL, NULL, THING_NO_SHOW},
+	{"OR REPLACE", NULL, NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER},
+	{"OWNED", NULL, NULL, NULL, NULL, THING_NO_CREATE | THING_NO_ALTER},	/* for DROP OWNED BY ... */
+	{"PARSER", Query_for_list_of_ts_parsers, NULL, NULL, NULL, THING_NO_SHOW},
 	{"POLICY", NULL, NULL, NULL},
 	{"PROCEDURE", NULL, NULL, Query_for_list_of_procedures},
 	{"PUBLICATION", NULL, Query_for_list_of_publications},
 	{"ROLE", Query_for_list_of_roles},
-	{"ROUTINE", NULL, NULL, &Query_for_list_of_routines, THING_NO_CREATE},
-	{"RULE", "SELECT pg_catalog.quote_ident(rulename) FROM pg_catalog.pg_rules WHERE substring(pg_catalog.quote_ident(rulename),1,%d)='%s'"},
+	{"ROUTINE", NULL, NULL, &Query_for_list_of_routines, NULL, THING_NO_CREATE},
+	{"RULE", "SELECT rulename FROM pg_catalog.pg_rules WHERE substring(rulename,1,%d)='%s'"},
 	{"SCHEMA", Query_for_list_of_schemas},
 	{"SEQUENCE", NULL, NULL, &Query_for_list_of_sequences},
 	{"SERVER", Query_for_list_of_servers},
 	{"STATISTICS", NULL, NULL, &Query_for_list_of_statistics},
 	{"SUBSCRIPTION", NULL, Query_for_list_of_subscriptions},
-	{"SYSTEM", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
+	{"SYSTEM", NULL, NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
 	{"TABLE", NULL, NULL, &Query_for_list_of_tables},
 	{"TABLESPACE", Query_for_list_of_tablespaces},
-	{"TEMP", NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE TEMP TABLE
-																 * ... */
-	{"TEMPLATE", Query_for_list_of_ts_templates, NULL, NULL, THING_NO_SHOW},
-	{"TEMPORARY", NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER},	/* for CREATE TEMPORARY
-																		 * TABLE ... */
+	{"TEMP", NULL, NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER},	/* for CREATE TEMP TABLE
+																		 * ... */
+	{"TEMPLATE", Query_for_list_of_ts_templates, NULL, NULL, NULL, THING_NO_SHOW},
+	{"TEMPORARY", NULL, NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER},	/* for CREATE TEMPORARY
+																			 * TABLE ... */
 	{"TEXT SEARCH", NULL, NULL, NULL},
-	{"TRANSFORM", NULL, NULL, NULL, THING_NO_ALTER},
-	{"TRIGGER", "SELECT pg_catalog.quote_ident(tgname) FROM pg_catalog.pg_trigger WHERE substring(pg_catalog.quote_ident(tgname),1,%d)='%s' AND NOT tgisinternal"},
+	{"TRANSFORM", NULL, NULL, NULL, NULL, THING_NO_ALTER},
+	{"TRIGGER", "SELECT tgname FROM pg_catalog.pg_trigger WHERE substring(tgname,1,%d)='%s' AND NOT tgisinternal"},
 	{"TYPE", NULL, NULL, &Query_for_list_of_datatypes},
-	{"UNIQUE", NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER},	/* for CREATE UNIQUE
-																	 * INDEX ... */
-	{"UNLOGGED", NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE UNLOGGED
-																	 * TABLE ... */
-	{"USER", Query_for_list_of_roles " UNION SELECT 'MAPPING FOR'"},
+	{"UNIQUE", NULL, NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE UNIQUE
+																		 * INDEX ... */
+	{"UNLOGGED", NULL, NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER},	/* for CREATE UNLOGGED
+																			 * TABLE ... */
+	{"USER", Query_for_list_of_roles, NULL, NULL, Keywords_for_user_thing},
 	{"USER MAPPING FOR", NULL, NULL, NULL},
 	{"VIEW", NULL, NULL, &Query_for_list_of_views},
 	{NULL}						/* end of list */
@@ -1200,6 +1275,8 @@ static char *complete_from_schema_query(const char *text, int state);
 static char *complete_from_versioned_schema_query(const char *text, int state);
 static char *_complete_from_query(const char *simple_query,
 								  const SchemaQuery *schema_query,
+								  const char *const *keywords,
+								  bool verbatim,
 								  const char *text, int state);
 static char *complete_from_list(const char *text, int state);
 static char *complete_from_const(const char *text, int state);
@@ -1212,6 +1289,12 @@ static char *complete_from_files(const char *text, int state);
 
 static char *pg_strdup_keyword_case(const char *s, const char *ref);
 static char *escape_string(const char *text);
+static void parse_identifier(const char *ident,
+							 char **schemaname, char **objectname,
+							 bool *schemaquoted, bool *objectquoted);
+static char *requote_identifier(const char *schemaname, const char *objectname,
+								bool quote_schema, bool quote_object);
+static bool identifier_needs_quotes(const char *ident);
 static PGresult *exec_query(const char *query);
 
 static char **get_previous_words(int point, char **buffer, int *nwords);
@@ -1650,8 +1733,8 @@ psql_completion(const char *text, int start, int end)
 
 	/* ALTER TABLE */
 	else if (Matches("ALTER", "TABLE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
-								   "UNION SELECT 'ALL IN TABLESPACE'");
+		COMPLETE_WITH_SCHEMA_QUERY_PLUS(Query_for_list_of_tables,
+										"ALL IN TABLESPACE");
 
 	/* ALTER something */
 	else if (Matches("ALTER"))
@@ -1699,11 +1782,11 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET"))
 		COMPLETE_WITH("(", "ALL TABLES IN SCHEMA", "TABLE");
 	else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD|DROP|SET", "ALL", "TABLES", "IN", "SCHEMA"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_schemas
-							" AND nspname != 'pg_catalog' "
-							" AND nspname not like 'pg\\_toast%%' "
-							" AND nspname not like 'pg\\_temp%%' "
-							" UNION SELECT 'CURRENT_SCHEMA'");
+		COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_schemas
+								 " AND nspname != 'pg_catalog' "
+								 " AND nspname not like 'pg\\_toast%%' "
+								 " AND nspname not like 'pg\\_temp%%' ",
+								 "CURRENT_SCHEMA");
 	/* ALTER PUBLICATION <name> SET ( */
 	else if (HeadMatches("ALTER", "PUBLICATION", MatchAny) && TailMatches("SET", "("))
 		COMPLETE_WITH("publish", "publish_via_partition_root");
@@ -1782,14 +1865,14 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches("ALTER", "EXTENSION", MatchAny, "UPDATE"))
 	{
 		completion_info_charp = prev2_wd;
-		COMPLETE_WITH_QUERY(Query_for_list_of_available_extension_versions_with_TO);
+		COMPLETE_WITH_QUERY_VERBATIM(Query_for_list_of_available_extension_versions_with_TO);
 	}
 
 	/* ALTER EXTENSION <name> UPDATE TO */
 	else if (Matches("ALTER", "EXTENSION", MatchAny, "UPDATE", "TO"))
 	{
 		completion_info_charp = prev3_wd;
-		COMPLETE_WITH_QUERY(Query_for_list_of_available_extension_versions);
+		COMPLETE_WITH_QUERY_VERBATIM(Query_for_list_of_available_extension_versions);
 	}
 
 	/* ALTER FOREIGN */
@@ -1811,8 +1894,8 @@ psql_completion(const char *text, int start, int end)
 
 	/* ALTER INDEX */
 	else if (Matches("ALTER", "INDEX"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
-								   "UNION SELECT 'ALL IN TABLESPACE'");
+		COMPLETE_WITH_SCHEMA_QUERY_PLUS(Query_for_list_of_indexes,
+										"ALL IN TABLESPACE");
 	/* ALTER INDEX <name> */
 	else if (Matches("ALTER", "INDEX", MatchAny))
 		COMPLETE_WITH("ALTER COLUMN", "OWNER TO", "RENAME TO", "SET",
@@ -1821,7 +1904,7 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches("ALTER", "INDEX", MatchAny, "ATTACH"))
 		COMPLETE_WITH("PARTITION");
 	else if (Matches("ALTER", "INDEX", MatchAny, "ATTACH", "PARTITION"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes);
 	/* ALTER INDEX <name> ALTER */
 	else if (Matches("ALTER", "INDEX", MatchAny, "ALTER"))
 		COMPLETE_WITH("COLUMN");
@@ -1878,8 +1961,8 @@ psql_completion(const char *text, int start, int end)
 
 	/* ALTER MATERIALIZED VIEW */
 	else if (Matches("ALTER", "MATERIALIZED", "VIEW"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
-								   "UNION SELECT 'ALL IN TABLESPACE'");
+		COMPLETE_WITH_SCHEMA_QUERY_PLUS(Query_for_list_of_matviews,
+										"ALL IN TABLESPACE");
 
 	/* ALTER USER,ROLE <name> */
 	else if (Matches("ALTER", "USER|ROLE", MatchAny) &&
@@ -1973,7 +2056,8 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches("ALTER", "SYSTEM"))
 		COMPLETE_WITH("SET", "RESET");
 	else if (Matches("ALTER", "SYSTEM", "SET|RESET"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_alter_system_set_vars);
+		COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_alter_system_set_vars,
+								 "all");
 	else if (Matches("ALTER", "SYSTEM", "SET", MatchAny))
 		COMPLETE_WITH("TO");
 	/* ALTER VIEW <name> */
@@ -1982,9 +2066,9 @@ psql_completion(const char *text, int start, int end)
 					  "SET SCHEMA");
 	/* ALTER VIEW xxx RENAME */
 	else if (Matches("ALTER", "VIEW", MatchAny, "RENAME"))
-		COMPLETE_WITH_ATTR(prev2_wd, " UNION SELECT 'COLUMN' UNION SELECT 'TO'");
+		COMPLETE_WITH_ATTR_PLUS(prev2_wd, "COLUMN", "TO");
 	else if (Matches("ALTER", "VIEW", MatchAny, "ALTER|RENAME", "COLUMN"))
-		COMPLETE_WITH_ATTR(prev3_wd, "");
+		COMPLETE_WITH_ATTR(prev3_wd);
 	/* ALTER VIEW xxx ALTER [ COLUMN ] yyy */
 	else if (Matches("ALTER", "VIEW", MatchAny, "ALTER", MatchAny) ||
 			 Matches("ALTER", "VIEW", MatchAny, "ALTER", "COLUMN", MatchAny))
@@ -2003,9 +2087,9 @@ psql_completion(const char *text, int start, int end)
 					  "RESET (", "SET");
 	/* ALTER MATERIALIZED VIEW xxx RENAME */
 	else if (Matches("ALTER", "MATERIALIZED", "VIEW", MatchAny, "RENAME"))
-		COMPLETE_WITH_ATTR(prev2_wd, " UNION SELECT 'COLUMN' UNION SELECT 'TO'");
+		COMPLETE_WITH_ATTR_PLUS(prev2_wd, "COLUMN", "TO");
 	else if (Matches("ALTER", "MATERIALIZED", "VIEW", MatchAny, "ALTER|RENAME", "COLUMN"))
-		COMPLETE_WITH_ATTR(prev3_wd, "");
+		COMPLETE_WITH_ATTR(prev3_wd);
 	/* ALTER MATERIALIZED VIEW xxx RENAME yyy */
 	else if (Matches("ALTER", "MATERIALIZED", "VIEW", MatchAny, "RENAME", MatchAnyExcept("TO")))
 		COMPLETE_WITH("TO");
@@ -2032,7 +2116,8 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH("RENAME TO", "TO", "USING (", "WITH CHECK (");
 	/* ALTER POLICY <name> ON <table> TO <role> */
 	else if (Matches("ALTER", "POLICY", MatchAny, "ON", MatchAny, "TO"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
+		COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_roles,
+								 Keywords_for_list_of_grant_roles);
 	/* ALTER POLICY <name> ON <table> USING ( */
 	else if (Matches("ALTER", "POLICY", MatchAny, "ON", MatchAny, "USING"))
 		COMPLETE_WITH("(");
@@ -2073,7 +2158,7 @@ psql_completion(const char *text, int start, int end)
 	 * If we have ALTER TRIGGER <sth> ON, then add the correct tablename
 	 */
 	else if (Matches("ALTER", "TRIGGER", MatchAny, "ON"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
 
 	/* ALTER TRIGGER <name> ON <name> */
 	else if (Matches("ALTER", "TRIGGER", MatchAny, "ON", MatchAny))
@@ -2100,7 +2185,7 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches("ALTER", "TABLE", MatchAny, "ADD", "COLUMN", MatchAny) ||
 			 (Matches("ALTER", "TABLE", MatchAny, "ADD", MatchAny) &&
 			  !Matches("ALTER", "TABLE", MatchAny, "ADD", "COLUMN|CONSTRAINT|CHECK|UNIQUE|PRIMARY|EXCLUDE|FOREIGN")))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes);
 	/* ALTER TABLE xxx ADD CONSTRAINT yyy */
 	else if (Matches("ALTER", "TABLE", MatchAny, "ADD", "CONSTRAINT", MatchAny))
 		COMPLETE_WITH("CHECK", "UNIQUE", "PRIMARY KEY", "EXCLUDE", "FOREIGN KEY");
@@ -2164,13 +2249,13 @@ psql_completion(const char *text, int start, int end)
 	}
 	/* ALTER TABLE xxx INHERIT */
 	else if (Matches("ALTER", "TABLE", MatchAny, "INHERIT"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
 	/* ALTER TABLE xxx NO */
 	else if (Matches("ALTER", "TABLE", MatchAny, "NO"))
 		COMPLETE_WITH("FORCE ROW LEVEL SECURITY", "INHERIT");
 	/* ALTER TABLE xxx NO INHERIT */
 	else if (Matches("ALTER", "TABLE", MatchAny, "NO", "INHERIT"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
 	/* ALTER TABLE xxx DISABLE */
 	else if (Matches("ALTER", "TABLE", MatchAny, "DISABLE"))
 		COMPLETE_WITH("ROW LEVEL SECURITY", "RULE", "TRIGGER");
@@ -2187,13 +2272,13 @@ psql_completion(const char *text, int start, int end)
 
 	/* ALTER TABLE xxx ALTER */
 	else if (Matches("ALTER", "TABLE", MatchAny, "ALTER"))
-		COMPLETE_WITH_ATTR(prev2_wd, " UNION SELECT 'COLUMN' UNION SELECT 'CONSTRAINT'");
+		COMPLETE_WITH_ATTR_PLUS(prev2_wd, "COLUMN", "CONSTRAINT");
 
 	/* ALTER TABLE xxx RENAME */
 	else if (Matches("ALTER", "TABLE", MatchAny, "RENAME"))
-		COMPLETE_WITH_ATTR(prev2_wd, " UNION SELECT 'COLUMN' UNION SELECT 'CONSTRAINT' UNION SELECT 'TO'");
+		COMPLETE_WITH_ATTR_PLUS(prev2_wd, "COLUMN", "CONSTRAINT", "TO");
 	else if (Matches("ALTER", "TABLE", MatchAny, "ALTER|RENAME", "COLUMN"))
-		COMPLETE_WITH_ATTR(prev3_wd, "");
+		COMPLETE_WITH_ATTR(prev3_wd);
 
 	/* ALTER TABLE xxx RENAME yyy */
 	else if (Matches("ALTER", "TABLE", MatchAny, "RENAME", MatchAnyExcept("CONSTRAINT|TO")))
@@ -2208,7 +2293,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH("COLUMN", "CONSTRAINT");
 	/* If we have ALTER TABLE <sth> DROP COLUMN, provide list of columns */
 	else if (Matches("ALTER", "TABLE", MatchAny, "DROP", "COLUMN"))
-		COMPLETE_WITH_ATTR(prev3_wd, "");
+		COMPLETE_WITH_ATTR(prev3_wd);
 	/* ALTER TABLE <sth> ALTER|DROP|RENAME CONSTRAINT <constraint> */
 	else if (Matches("ALTER", "TABLE", MatchAny, "ALTER|DROP|RENAME", "CONSTRAINT"))
 	{
@@ -2298,7 +2383,7 @@ psql_completion(const char *text, int start, int end)
 	 * tables.
 	 */
 	else if (Matches("ALTER", "TABLE", MatchAny, "ATTACH", "PARTITION"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
 	/* Limited completion support for partition bound specification */
 	else if (TailMatches("ATTACH", "PARTITION", MatchAny))
 		COMPLETE_WITH("FOR VALUES", "DEFAULT");
@@ -2360,7 +2445,7 @@ psql_completion(const char *text, int start, int end)
 	 * of attributes
 	 */
 	else if (Matches("ALTER", "TYPE", MatchAny, "ALTER|DROP|RENAME", "ATTRIBUTE"))
-		COMPLETE_WITH_ATTR(prev3_wd, "");
+		COMPLETE_WITH_ATTR(prev3_wd);
 	/* ALTER TYPE ALTER ATTRIBUTE <foo> */
 	else if (Matches("ALTER", "TYPE", MatchAny, "ALTER", "ATTRIBUTE", MatchAny))
 		COMPLETE_WITH("TYPE");
@@ -2385,8 +2470,8 @@ psql_completion(const char *text, int start, int end)
  * ANALYZE [ VERBOSE ] [ table_and_columns [, ...] ]
  */
 	else if (Matches("ANALYZE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_analyzables,
-								   " UNION SELECT 'VERBOSE'");
+		COMPLETE_WITH_SCHEMA_QUERY_PLUS(Query_for_list_of_analyzables,
+										"VERBOSE");
 	else if (HeadMatches("ANALYZE", "(*") &&
 			 !HeadMatches("ANALYZE", "(*)"))
 	{
@@ -2402,9 +2487,9 @@ psql_completion(const char *text, int start, int end)
 	}
 	else if (HeadMatches("ANALYZE") && TailMatches("("))
 		/* "ANALYZE (" should be caught above, so assume we want columns */
-		COMPLETE_WITH_ATTR(prev2_wd, "");
+		COMPLETE_WITH_ATTR(prev2_wd);
 	else if (HeadMatches("ANALYZE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_analyzables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_analyzables);
 
 /* BEGIN */
 	else if (Matches("BEGIN"))
@@ -2425,19 +2510,20 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH("CHAIN");
 /* CALL */
 	else if (Matches("CALL"))
-		COMPLETE_WITH_VERSIONED_SCHEMA_QUERY(Query_for_list_of_procedures, NULL);
+		COMPLETE_WITH_VERSIONED_SCHEMA_QUERY(Query_for_list_of_procedures);
 	else if (Matches("CALL", MatchAny))
 		COMPLETE_WITH("(");
 /* CLOSE */
 	else if (Matches("CLOSE"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_cursors
-							" UNION SELECT 'ALL'");
+		COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_cursors,
+								 "ALL");
 /* CLUSTER */
 	else if (Matches("CLUSTER"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_clusterables, "UNION SELECT 'VERBOSE'");
+		COMPLETE_WITH_SCHEMA_QUERY_PLUS(Query_for_list_of_clusterables,
+										"VERBOSE");
 	else if (Matches("CLUSTER", "VERBOSE") ||
 			 Matches("CLUSTER", "(*)"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_clusterables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_clusterables);
 	/* If we have CLUSTER <sth>, then add "USING" */
 	else if (Matches("CLUSTER", MatchAnyExcept("VERBOSE|ON|(|(*)")))
 		COMPLETE_WITH("USING");
@@ -2487,19 +2573,19 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches("COMMENT", "ON", "CONSTRAINT", MatchAny, "ON"))
 	{
 		completion_info_charp = prev2_wd;
-		COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_constraint
-							" UNION SELECT 'DOMAIN'");
+		COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_tables_for_constraint,
+								 "DOMAIN");
 	}
 	else if (Matches("COMMENT", "ON", "CONSTRAINT", MatchAny, "ON", "DOMAIN"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains);
 	else if (Matches("COMMENT", "ON", "EVENT", "TRIGGER"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
 	else if (Matches("COMMENT", "ON", "FOREIGN"))
 		COMPLETE_WITH("DATA WRAPPER", "TABLE");
 	else if (Matches("COMMENT", "ON", "FOREIGN", "TABLE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables);
 	else if (Matches("COMMENT", "ON", "MATERIALIZED", "VIEW"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews);
 	else if (Matches("COMMENT", "ON", "POLICY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_policies);
 	else if (Matches("COMMENT", "ON", "POLICY", MatchAny))
@@ -2529,7 +2615,7 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches("COMMENT", "ON", "TEXT", "SEARCH", "TEMPLATE"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_ts_templates);
 	else if (Matches("COMMENT", "ON", "TRANSFORM", "FOR"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes);
 	else if (Matches("COMMENT", "ON", "TRANSFORM", "FOR", MatchAny))
 		COMPLETE_WITH("LANGUAGE");
 	else if (Matches("COMMENT", "ON", "TRANSFORM", "FOR", MatchAny, "LANGUAGE"))
@@ -2557,8 +2643,7 @@ psql_completion(const char *text, int start, int end)
 	 * backslash command).
 	 */
 	else if (Matches("COPY|\\copy"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
-								   " UNION ALL SELECT '('");
+		COMPLETE_WITH_SCHEMA_QUERY_PLUS(Query_for_list_of_tables, "(");
 	/* Complete COPY ( with legal query commands */
 	else if (Matches("COPY|\\copy", "("))
 		COMPLETE_WITH("SELECT", "TABLE", "VALUES", "INSERT INTO", "UPDATE", "DELETE FROM", "WITH");
@@ -2616,7 +2701,7 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches("CREATE", "COLLATION", MatchAny))
 		COMPLETE_WITH("(", "FROM");
 	else if (Matches("CREATE", "COLLATION", MatchAny, "FROM"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_collations, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_collations);
 	else if (HeadMatches("CREATE", "COLLATION", MatchAny, "(*"))
 	{
 		if (TailMatches("(|*,"))
@@ -2642,12 +2727,12 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches("CREATE", "DOMAIN", MatchAny))
 		COMPLETE_WITH("AS");
 	else if (Matches("CREATE", "DOMAIN", MatchAny, "AS"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes);
 	else if (Matches("CREATE", "DOMAIN", MatchAny, "AS", MatchAny))
 		COMPLETE_WITH("COLLATE", "DEFAULT", "CONSTRAINT",
 					  "NOT NULL", "NULL", "CHECK (");
 	else if (Matches("CREATE", "DOMAIN", MatchAny, "COLLATE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_collations, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_collations);
 
 	/* CREATE EXTENSION */
 	/* Complete with available extensions rather than installed ones. */
@@ -2660,7 +2745,7 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches("CREATE", "EXTENSION", MatchAny, "VERSION"))
 	{
 		completion_info_charp = prev2_wd;
-		COMPLETE_WITH_QUERY(Query_for_list_of_available_extension_versions);
+		COMPLETE_WITH_QUERY_VERBATIM(Query_for_list_of_available_extension_versions);
 	}
 
 	/* CREATE FOREIGN */
@@ -2685,9 +2770,8 @@ psql_completion(const char *text, int start, int end)
 	 * existing indexes
 	 */
 	else if (TailMatches("CREATE|UNIQUE", "INDEX"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
-								   " UNION SELECT 'ON'"
-								   " UNION SELECT 'CONCURRENTLY'");
+		COMPLETE_WITH_SCHEMA_QUERY_PLUS(Query_for_list_of_indexes,
+										"ON", "CONCURRENTLY");
 
 	/*
 	 * Complete ... INDEX|CONCURRENTLY [<name>] ON with a list of relations
@@ -2695,15 +2779,15 @@ psql_completion(const char *text, int start, int end)
 	 */
 	else if (TailMatches("INDEX|CONCURRENTLY", MatchAny, "ON") ||
 			 TailMatches("INDEX|CONCURRENTLY", "ON"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexables);
 
 	/*
 	 * Complete CREATE|UNIQUE INDEX CONCURRENTLY with "ON" and existing
 	 * indexes
 	 */
 	else if (TailMatches("CREATE|UNIQUE", "INDEX", "CONCURRENTLY"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
-								   " UNION SELECT 'ON'");
+		COMPLETE_WITH_SCHEMA_QUERY_PLUS(Query_for_list_of_indexes,
+										"ON");
 	/* Complete CREATE|UNIQUE INDEX [CONCURRENTLY] <sth> with "ON" */
 	else if (TailMatches("CREATE|UNIQUE", "INDEX", MatchAny) ||
 			 TailMatches("CREATE|UNIQUE", "INDEX", "CONCURRENTLY", MatchAny))
@@ -2718,10 +2802,10 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH("(", "USING");
 	else if (TailMatches("INDEX", MatchAny, "ON", MatchAny, "(") ||
 			 TailMatches("INDEX|CONCURRENTLY", "ON", MatchAny, "("))
-		COMPLETE_WITH_ATTR(prev2_wd, "");
+		COMPLETE_WITH_ATTR(prev2_wd);
 	/* same if you put in USING */
 	else if (TailMatches("ON", MatchAny, "USING", MatchAny, "("))
-		COMPLETE_WITH_ATTR(prev4_wd, "");
+		COMPLETE_WITH_ATTR(prev4_wd);
 	/* Complete USING with an index method */
 	else if (TailMatches("INDEX", MatchAny, MatchAny, "ON", MatchAny, "USING") ||
 			 TailMatches("INDEX", MatchAny, "ON", MatchAny, "USING") ||
@@ -2742,7 +2826,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH("ON");
 	/* Complete "CREATE POLICY <name> ON <table>" */
 	else if (Matches("CREATE", "POLICY", MatchAny, "ON"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
 	/* Complete "CREATE POLICY <name> ON <table> AS|FOR|TO|USING|WITH CHECK" */
 	else if (Matches("CREATE", "POLICY", MatchAny, "ON", MatchAny))
 		COMPLETE_WITH("AS", "FOR", "TO", "USING (", "WITH CHECK (");
@@ -2770,7 +2854,8 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH("TO", "USING (", "WITH CHECK (");
 	/* Complete "CREATE POLICY <name> ON <table> TO <role>" */
 	else if (Matches("CREATE", "POLICY", MatchAny, "ON", MatchAny, "TO"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
+		COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_roles,
+								 Keywords_for_list_of_grant_roles);
 	/* Complete "CREATE POLICY <name> ON <table> USING (" */
 	else if (Matches("CREATE", "POLICY", MatchAny, "ON", MatchAny, "USING"))
 		COMPLETE_WITH("(");
@@ -2808,7 +2893,8 @@ psql_completion(const char *text, int start, int end)
 	 * <role>"
 	 */
 	else if (Matches("CREATE", "POLICY", MatchAny, "ON", MatchAny, "AS", MatchAny, "TO"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
+		COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_roles,
+								 Keywords_for_list_of_grant_roles);
 
 	/*
 	 * Complete "CREATE POLICY <name> ON <table> AS PERMISSIVE|RESTRICTIVE
@@ -2831,18 +2917,18 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH("WITH (");
 	/* Complete "CREATE PUBLICATION <name> FOR TABLE" with "<table>, ..." */
 	else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
 
 	/*
 	 * Complete "CREATE PUBLICATION <name> FOR ALL TABLES IN SCHEMA <schema>,
 	 * ..."
 	 */
 	else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES", "IN", "SCHEMA"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_schemas
-							" AND nspname != 'pg_catalog' "
-							" AND nspname not like 'pg\\_toast%%' "
-							" AND nspname not like 'pg\\_temp%%' "
-							" UNION SELECT 'CURRENT_SCHEMA' ");
+		COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_schemas
+								 " AND nspname != 'pg_catalog' "
+								 " AND nspname not like 'pg\\_toast%%' "
+								 " AND nspname not like 'pg\\_temp%%' ",
+								 "CURRENT_SCHEMA");
 	else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES", "IN", "SCHEMA", MatchAny) && (!ends_with(prev_wd, ',')))
 		COMPLETE_WITH("WITH (");
 	/* Complete "CREATE PUBLICATION <name> [...] WITH" */
@@ -2871,7 +2957,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH("TO");
 	/* Complete "AS ON <sth> TO" with a table name */
 	else if (TailMatches("AS", "ON", "SELECT|UPDATE|INSERT|DELETE", "TO"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
 
 /* CREATE SEQUENCE --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	else if (TailMatches("CREATE", "SEQUENCE", MatchAny) ||
@@ -2898,7 +2984,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH("ON");
 	else if (HeadMatches("CREATE", "STATISTICS", MatchAny) &&
 			 TailMatches("FROM"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
 
 /* CREATE TABLE --- is allowed inside CREATE SCHEMA, so use TailMatches */
 	/* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
@@ -2912,7 +2998,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH("RANGE (", "LIST (", "HASH (");
 	/* If we have xxx PARTITION OF, provide a list of partitioned tables */
 	else if (TailMatches("PARTITION", "OF"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_partitioned_tables, "");
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_partitioned_tables);
 	/* Limited completion support for partition bound specification */
 	else if (TailMatches("PARTITION", "OF", MatchAny))
 		COMPLETE_WITH("FOR VALUES", "DEFAULT");
@@ -2923,7 +3009,7 @@ psql_completion(const char *text, int start, int end)
 	/* Complete CREATE TABLE <name> OF with list of composite types */
 	else if (TailMatches("CREATE", "TABLE", MatchAny, "OF") ||
 			 TailMatches("CREATE", "TEMP|TEMPORARY|UNLOGGED", "TABLE", MatchAny, "OF"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_composite_datatypes, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_composite_datatypes);
 	/* Complete CREATE TABLE name (...) with supported options */
 	else if (TailMatches("CREATE", "TABLE", MatchAny, "(*)") ||
 			 TailMatches("CREATE", "UNLOGGED", "TABLE", MatchAny, "(*)"))
@@ -2962,7 +3048,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH("FOR");
 	else if (Matches("CREATE", "TRANSFORM", "FOR") ||
 			 Matches("CREATE", "OR", "REPLACE", "TRANSFORM", "FOR"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes);
 	else if (Matches("CREATE", "TRANSFORM", "FOR", MatchAny) ||
 			 Matches("CREATE", "OR", "REPLACE", "TRANSFORM", "FOR", MatchAny))
 		COMPLETE_WITH("LANGUAGE");
@@ -3030,7 +3116,7 @@ psql_completion(const char *text, int start, int end)
 	 */
 	else if (TailMatches("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny, "ON") ||
 			 TailMatches("CREATE", "OR", "REPLACE", "TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny, "ON"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
 
 	/*
 	 * Complete CREATE [ OR REPLACE ] TRIGGER ... INSTEAD OF event ON with a
@@ -3038,7 +3124,7 @@ psql_completion(const char *text, int start, int end)
 	 */
 	else if (TailMatches("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF", MatchAny, "ON") ||
 			 TailMatches("CREATE", "OR", "REPLACE", "TRIGGER", MatchAny, "INSTEAD", "OF", MatchAny, "ON"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views);
 	else if ((HeadMatches("CREATE", "TRIGGER") ||
 			  HeadMatches("CREATE", "OR", "REPLACE", "TRIGGER")) &&
 			 TailMatches("ON", MatchAny))
@@ -3143,7 +3229,7 @@ psql_completion(const char *text, int start, int end)
 	else if ((HeadMatches("CREATE", "TRIGGER") ||
 			  HeadMatches("CREATE", "OR", "REPLACE", "TRIGGER")) &&
 			 TailMatches("EXECUTE", "FUNCTION|PROCEDURE"))
-		COMPLETE_WITH_VERSIONED_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
+		COMPLETE_WITH_VERSIONED_SCHEMA_QUERY(Query_for_list_of_functions);
 
 /* CREATE ROLE,USER,GROUP <name> */
 	else if (Matches("CREATE", "ROLE|GROUP|USER", MatchAny) &&
@@ -3179,7 +3265,7 @@ psql_completion(const char *text, int start, int end)
 	else if (HeadMatches("CREATE", "TYPE", MatchAny, "AS", "("))
 	{
 		if (TailMatches("(|*,", MatchAny))
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL);
+			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes);
 		else if (TailMatches("(|*,", MatchAny, MatchAnyExcept("*)")))
 			COMPLETE_WITH("COLLATE", ",", ")");
 	}
@@ -3263,12 +3349,12 @@ psql_completion(const char *text, int start, int end)
 	}
 	else if (HeadMatches("CREATE", "EVENT", "TRIGGER") &&
 			 TailMatches("EXECUTE", "FUNCTION|PROCEDURE"))
-		COMPLETE_WITH_VERSIONED_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
+		COMPLETE_WITH_VERSIONED_SCHEMA_QUERY(Query_for_list_of_functions);
 
 /* DEALLOCATE */
 	else if (Matches("DEALLOCATE"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_prepared_statements
-							" UNION SELECT 'ALL'");
+		COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_prepared_statements,
+								 "ALL");
 
 /* DECLARE */
 
@@ -3316,7 +3402,7 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH("FROM");
 	/* Complete DELETE FROM with a list of tables */
 	else if (TailMatches("DELETE", "FROM"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables);
 	/* Complete DELETE FROM <table> */
 	else if (TailMatches("DELETE", "FROM", MatchAny))
 		COMPLETE_WITH("USING", "WHERE");
@@ -3358,10 +3444,10 @@ psql_completion(const char *text, int start, int end)
 
 	/* DROP INDEX */
 	else if (Matches("DROP", "INDEX"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
-								   " UNION SELECT 'CONCURRENTLY'");
+		COMPLETE_WITH_SCHEMA_QUERY_PLUS(Query_for_list_of_indexes,
+										"CONCURRENTLY");
 	else if (Matches("DROP", "INDEX", "CONCURRENTLY"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes);
 	else if (Matches("DROP", "INDEX", MatchAny))
 		COMPLETE_WITH("CASCADE", "RESTRICT");
 	else if (Matches("DROP", "INDEX", "CONCURRENTLY", MatchAny))
@@ -3371,7 +3457,7 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches("DROP", "MATERIALIZED"))
 		COMPLETE_WITH("VIEW");
 	else if (Matches("DROP", "MATERIALIZED", "VIEW"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews);
 	else if (Matches("DROP", "MATERIALIZED", "VIEW", MatchAny))
 		COMPLETE_WITH("CASCADE", "RESTRICT");
 
@@ -3440,7 +3526,7 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches("DROP", "TRANSFORM"))
 		COMPLETE_WITH("FOR");
 	else if (Matches("DROP", "TRANSFORM", "FOR"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes);
 	else if (Matches("DROP", "TRANSFORM", "FOR", MatchAny))
 		COMPLETE_WITH("LANGUAGE");
 	else if (Matches("DROP", "TRANSFORM", "FOR", MatchAny, "LANGUAGE"))
@@ -3494,28 +3580,28 @@ psql_completion(const char *text, int start, int end)
 	 * NEXT, PRIOR, FIRST, LAST, FROM, IN, and a list of cursors
 	 */
 	else if (Matches("FETCH|MOVE"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_cursors
-							" UNION SELECT 'ABSOLUTE'"
-							" UNION SELECT 'BACKWARD'"
-							" UNION SELECT 'FORWARD'"
-							" UNION SELECT 'RELATIVE'"
-							" UNION SELECT 'ALL'"
-							" UNION SELECT 'NEXT'"
-							" UNION SELECT 'PRIOR'"
-							" UNION SELECT 'FIRST'"
-							" UNION SELECT 'LAST'"
-							" UNION SELECT 'FROM'"
-							" UNION SELECT 'IN'");
+		COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_cursors,
+								 "ABSOLUTE",
+								 "BACKWARD",
+								 "FORWARD",
+								 "RELATIVE",
+								 "ALL",
+								 "NEXT",
+								 "PRIOR",
+								 "FIRST",
+								 "LAST",
+								 "FROM",
+								 "IN");
 
 	/*
 	 * Complete FETCH BACKWARD or FORWARD with one of ALL, FROM, IN, and a
 	 * list of cursors
 	 */
 	else if (Matches("FETCH|MOVE", "BACKWARD|FORWARD"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_cursors
-							" UNION SELECT 'ALL'"
-							" UNION SELECT 'FROM'"
-							" UNION SELECT 'IN'");
+		COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_cursors,
+								 "ALL",
+								 "FROM",
+								 "IN");
 
 	/*
 	 * Complete FETCH <direction> with "FROM" or "IN". These are equivalent,
@@ -3525,9 +3611,9 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches("FETCH|MOVE", "ABSOLUTE|BACKWARD|FORWARD|RELATIVE",
 					 MatchAnyExcept("FROM|IN")) ||
 			 Matches("FETCH|MOVE", "ALL|NEXT|PRIOR|FIRST|LAST"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_cursors
-							" UNION SELECT 'FROM'"
-							" UNION SELECT 'IN'");
+		COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_cursors,
+								 "FROM",
+								 "IN");
 	/* Complete FETCH <direction> "FROM" or "IN" with a list of cursors */
 	else if (HeadMatches("FETCH|MOVE") &&
 			 TailMatches("FROM|IN"))
@@ -3546,7 +3632,7 @@ psql_completion(const char *text, int start, int end)
 /* FOREIGN TABLE */
 	else if (TailMatches("FOREIGN", "TABLE") &&
 			 !TailMatches("CREATE", MatchAny, MatchAny))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables);
 
 /* FOREIGN SERVER */
 	else if (TailMatches("FOREIGN", "SERVER"))
@@ -3568,20 +3654,20 @@ psql_completion(const char *text, int start, int end)
 						  "DELETE", "TRUNCATE", "REFERENCES", "TRIGGER",
 						  "EXECUTE", "USAGE", "ALL");
 		else
-			COMPLETE_WITH_QUERY(Query_for_list_of_roles
-								" UNION SELECT 'SELECT'"
-								" UNION SELECT 'INSERT'"
-								" UNION SELECT 'UPDATE'"
-								" UNION SELECT 'DELETE'"
-								" UNION SELECT 'TRUNCATE'"
-								" UNION SELECT 'REFERENCES'"
-								" UNION SELECT 'TRIGGER'"
-								" UNION SELECT 'CREATE'"
-								" UNION SELECT 'CONNECT'"
-								" UNION SELECT 'TEMPORARY'"
-								" UNION SELECT 'EXECUTE'"
-								" UNION SELECT 'USAGE'"
-								" UNION SELECT 'ALL'");
+			COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_roles,
+									 "SELECT",
+									 "INSERT",
+									 "UPDATE",
+									 "DELETE",
+									 "TRUNCATE",
+									 "REFERENCES",
+									 "TRIGGER",
+									 "CREATE",
+									 "CONNECT",
+									 "TEMPORARY",
+									 "EXECUTE",
+									 "USAGE",
+									 "ALL");
 	}
 
 	/*
@@ -3601,9 +3687,6 @@ psql_completion(const char *text, int start, int end)
 	/*
 	 * Complete GRANT/REVOKE <sth> ON with a list of appropriate relations.
 	 *
-	 * Keywords like DATABASE, FUNCTION, LANGUAGE and SCHEMA added to query
-	 * result via UNION; seems to work intuitively.
-	 *
 	 * Note: GRANT/REVOKE can get quite complex; tab-completion as implemented
 	 * here will only work if the privilege list contains exactly one
 	 * privilege.
@@ -3617,26 +3700,26 @@ psql_completion(const char *text, int start, int end)
 		if (HeadMatches("ALTER", "DEFAULT", "PRIVILEGES"))
 			COMPLETE_WITH("TABLES", "SEQUENCES", "FUNCTIONS", "PROCEDURES", "ROUTINES", "TYPES", "SCHEMAS");
 		else
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_grantables,
-									   " UNION SELECT 'ALL FUNCTIONS IN SCHEMA'"
-									   " UNION SELECT 'ALL PROCEDURES IN SCHEMA'"
-									   " UNION SELECT 'ALL ROUTINES IN SCHEMA'"
-									   " UNION SELECT 'ALL SEQUENCES IN SCHEMA'"
-									   " UNION SELECT 'ALL TABLES IN SCHEMA'"
-									   " UNION SELECT 'DATABASE'"
-									   " UNION SELECT 'DOMAIN'"
-									   " UNION SELECT 'FOREIGN DATA WRAPPER'"
-									   " UNION SELECT 'FOREIGN SERVER'"
-									   " UNION SELECT 'FUNCTION'"
-									   " UNION SELECT 'LANGUAGE'"
-									   " UNION SELECT 'LARGE OBJECT'"
-									   " UNION SELECT 'PROCEDURE'"
-									   " UNION SELECT 'ROUTINE'"
-									   " UNION SELECT 'SCHEMA'"
-									   " UNION SELECT 'SEQUENCE'"
-									   " UNION SELECT 'TABLE'"
-									   " UNION SELECT 'TABLESPACE'"
-									   " UNION SELECT 'TYPE'");
+			COMPLETE_WITH_SCHEMA_QUERY_PLUS(Query_for_list_of_grantables,
+											"ALL FUNCTIONS IN SCHEMA",
+											"ALL PROCEDURES IN SCHEMA",
+											"ALL ROUTINES IN SCHEMA",
+											"ALL SEQUENCES IN SCHEMA",
+											"ALL TABLES IN SCHEMA",
+											"DATABASE",
+											"DOMAIN",
+											"FOREIGN DATA WRAPPER",
+											"FOREIGN SERVER",
+											"FUNCTION",
+											"LANGUAGE",
+											"LARGE OBJECT",
+											"PROCEDURE",
+											"ROUTINE",
+											"SCHEMA",
+											"SEQUENCE",
+											"TABLE",
+											"TABLESPACE",
+											"TYPE");
 	}
 	else if (TailMatches("GRANT|REVOKE", MatchAny, "ON", "ALL"))
 		COMPLETE_WITH("FUNCTIONS IN SCHEMA",
@@ -3658,25 +3741,25 @@ psql_completion(const char *text, int start, int end)
 		if (TailMatches("DATABASE"))
 			COMPLETE_WITH_QUERY(Query_for_list_of_databases);
 		else if (TailMatches("DOMAIN"))
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains, NULL);
+			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains);
 		else if (TailMatches("FUNCTION"))
-			COMPLETE_WITH_VERSIONED_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
+			COMPLETE_WITH_VERSIONED_SCHEMA_QUERY(Query_for_list_of_functions);
 		else if (TailMatches("LANGUAGE"))
 			COMPLETE_WITH_QUERY(Query_for_list_of_languages);
 		else if (TailMatches("PROCEDURE"))
-			COMPLETE_WITH_VERSIONED_SCHEMA_QUERY(Query_for_list_of_procedures, NULL);
+			COMPLETE_WITH_VERSIONED_SCHEMA_QUERY(Query_for_list_of_procedures);
 		else if (TailMatches("ROUTINE"))
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_routines, NULL);
+			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_routines);
 		else if (TailMatches("SCHEMA"))
 			COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
 		else if (TailMatches("SEQUENCE"))
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences, NULL);
+			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences);
 		else if (TailMatches("TABLE"))
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_grantables, NULL);
+			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_grantables);
 		else if (TailMatches("TABLESPACE"))
 			COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
 		else if (TailMatches("TYPE"))
-			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL);
+			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes);
 		else if (TailMatches("GRANT", MatchAny, MatchAny, MatchAny))
 			COMPLETE_WITH("TO");
 		else
@@ -3689,10 +3772,12 @@ psql_completion(const char *text, int start, int end)
 	 */
 	else if ((HeadMatches("GRANT") && TailMatches("TO")) ||
 			 (HeadMatches("REVOKE") && TailMatches("FROM")))
-		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
+		COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_roles,
+								 Keywords_for_list_of_grant_roles);
 	/* Complete "ALTER DEFAULT PRIVILEGES ... GRANT/REVOKE ... TO/FROM */
 	else if (HeadMatches("ALTER", "DEFAULT", "PRIVILEGES") && TailMatches("TO|FROM"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
+		COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_roles,
+								 Keywords_for_list_of_grant_roles);
 	/* Complete "GRANT/REVOKE ... ON * *" with TO/FROM */
 	else if (HeadMatches("GRANT") && TailMatches("ON", MatchAny, MatchAny))
 		COMPLETE_WITH("TO");
@@ -3753,10 +3838,10 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH("INTO");
 	/* Complete INSERT INTO with table names */
 	else if (TailMatches("INSERT", "INTO"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables);
 	/* Complete "INSERT INTO <table> (" with attribute names */
 	else if (TailMatches("INSERT", "INTO", MatchAny, "("))
-		COMPLETE_WITH_ATTR(prev2_wd, "");
+		COMPLETE_WITH_ATTR(prev2_wd);
 
 	/*
 	 * Complete INSERT INTO <table> with "(" or "VALUES" or "SELECT" or
@@ -3788,14 +3873,13 @@ psql_completion(const char *text, int start, int end)
 /* LOCK */
 	/* Complete LOCK [TABLE] [ONLY] with a list of tables */
 	else if (Matches("LOCK"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
-								   " UNION SELECT 'TABLE'"
-								   " UNION SELECT 'ONLY'");
+		COMPLETE_WITH_SCHEMA_QUERY_PLUS(Query_for_list_of_tables,
+										"TABLE", "ONLY");
 	else if (Matches("LOCK", "TABLE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
-								   " UNION SELECT 'ONLY'");
+		COMPLETE_WITH_SCHEMA_QUERY_PLUS(Query_for_list_of_tables,
+										"ONLY");
 	else if (Matches("LOCK", "TABLE", "ONLY") || Matches("LOCK", "ONLY"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
 	/* For the following, handle the case of a single table only for now */
 
 	/* Complete LOCK [TABLE] [ONLY] <table> with IN or NOWAIT */
@@ -3831,7 +3915,7 @@ psql_completion(const char *text, int start, int end)
 
 /* NOTIFY --- can be inside EXPLAIN, RULE, etc */
 	else if (TailMatches("NOTIFY"))
-		COMPLETE_WITH_QUERY("SELECT pg_catalog.quote_ident(channel) FROM pg_catalog.pg_listening_channels() AS channel WHERE substring(pg_catalog.quote_ident(channel),1,%d)='%s'");
+		COMPLETE_WITH_QUERY("SELECT channel FROM pg_catalog.pg_listening_channels() AS channel WHERE substring(channel,1,%d)='%s'");
 
 /* OPTIONS */
 	else if (TailMatches("OPTIONS"))
@@ -3845,7 +3929,7 @@ psql_completion(const char *text, int start, int end)
 	else if (TailMatches("FROM", MatchAny, "ORDER"))
 		COMPLETE_WITH("BY");
 	else if (TailMatches("FROM", MatchAny, "ORDER", "BY"))
-		COMPLETE_WITH_ATTR(prev3_wd, "");
+		COMPLETE_WITH_ATTR(prev3_wd);
 
 /* PREPARE xx AS */
 	else if (Matches("PREPARE", MatchAny, "AS"))
@@ -3874,10 +3958,10 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches("REFRESH", "MATERIALIZED"))
 		COMPLETE_WITH("VIEW");
 	else if (Matches("REFRESH", "MATERIALIZED", "VIEW"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
-								   " UNION SELECT 'CONCURRENTLY'");
+		COMPLETE_WITH_SCHEMA_QUERY_PLUS(Query_for_list_of_matviews,
+										"CONCURRENTLY");
 	else if (Matches("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews);
 	else if (Matches("REFRESH", "MATERIALIZED", "VIEW", MatchAny))
 		COMPLETE_WITH("WITH");
 	else if (Matches("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY", MatchAny))
@@ -3897,26 +3981,26 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH("TABLE", "INDEX", "SYSTEM", "SCHEMA", "DATABASE");
 	else if (Matches("REINDEX", "TABLE") ||
 			 Matches("REINDEX", "(*)", "TABLE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexables,
-								   " UNION SELECT 'CONCURRENTLY'");
+		COMPLETE_WITH_SCHEMA_QUERY_PLUS(Query_for_list_of_indexables,
+										"CONCURRENTLY");
 	else if (Matches("REINDEX", "INDEX") ||
 			 Matches("REINDEX", "(*)", "INDEX"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
-								   " UNION SELECT 'CONCURRENTLY'");
+		COMPLETE_WITH_SCHEMA_QUERY_PLUS(Query_for_list_of_indexes,
+										"CONCURRENTLY");
 	else if (Matches("REINDEX", "SCHEMA") ||
 			 Matches("REINDEX", "(*)", "SCHEMA"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_schemas
-							" UNION SELECT 'CONCURRENTLY'");
+		COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_schemas,
+								 "CONCURRENTLY");
 	else if (Matches("REINDEX", "SYSTEM|DATABASE") ||
 			 Matches("REINDEX", "(*)", "SYSTEM|DATABASE"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_databases
-							" UNION SELECT 'CONCURRENTLY'");
+		COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_databases,
+								 "CONCURRENTLY");
 	else if (Matches("REINDEX", "TABLE", "CONCURRENTLY") ||
 			 Matches("REINDEX", "(*)", "TABLE", "CONCURRENTLY"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexables);
 	else if (Matches("REINDEX", "INDEX", "CONCURRENTLY") ||
 			 Matches("REINDEX", "(*)", "INDEX", "CONCURRENTLY"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes);
 	else if (Matches("REINDEX", "SCHEMA", "CONCURRENTLY") ||
 			 Matches("REINDEX", "(*)", "SCHEMA", "CONCURRENTLY"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
@@ -3960,9 +4044,17 @@ psql_completion(const char *text, int start, int end)
 /* SET, RESET, SHOW */
 	/* Complete with a variable name */
 	else if (TailMatches("SET|RESET") && !TailMatches("UPDATE", MatchAny, "SET"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_set_vars);
+		COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_set_vars,
+								 "constraints",
+								 "transaction",
+								 "session",
+								 "role",
+								 "tablespace",
+								 "all");
 	else if (Matches("SHOW"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_show_vars);
+		COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_show_vars,
+								 "session authorization",
+								 "all");
 	/* Complete "SET TRANSACTION" */
 	else if (Matches("SET", "TRANSACTION"))
 		COMPLETE_WITH("SNAPSHOT", "ISOLATION LEVEL", "READ", "DEFERRABLE", "NOT DEFERRABLE");
@@ -3997,7 +4089,8 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH("ONLY", "WRITE");
 	/* SET CONSTRAINTS */
 	else if (Matches("SET", "CONSTRAINTS"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_constraints_with_schema, "UNION SELECT 'ALL'");
+		COMPLETE_WITH_SCHEMA_QUERY_PLUS(Query_for_list_of_constraints_with_schema,
+										"ALL");
 	/* Complete SET CONSTRAINTS <foo> with DEFERRED|IMMEDIATE */
 	else if (Matches("SET", "CONSTRAINTS", MatchAny))
 		COMPLETE_WITH("DEFERRED", "IMMEDIATE");
@@ -4009,7 +4102,8 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH("AUTHORIZATION", "CHARACTERISTICS AS TRANSACTION");
 	/* Complete SET SESSION AUTHORIZATION with username */
 	else if (Matches("SET", "SESSION", "AUTHORIZATION"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles " UNION SELECT 'DEFAULT'");
+		COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_roles,
+								 "DEFAULT");
 	/* Complete RESET SESSION with AUTHORIZATION */
 	else if (Matches("RESET", "SESSION"))
 		COMPLETE_WITH("AUTHORIZATION");
@@ -4039,10 +4133,10 @@ psql_completion(const char *text, int start, int end)
 						  "US", "European", "NonEuropean",
 						  "DEFAULT");
 		else if (TailMatches("search_path", "TO|="))
-			COMPLETE_WITH_QUERY(Query_for_list_of_schemas
-								" AND nspname not like 'pg\\_toast%%' "
-								" AND nspname not like 'pg\\_temp%%' "
-								" UNION SELECT 'DEFAULT' ");
+			COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_schemas
+									 " AND nspname not like 'pg\\_toast%%' "
+									 " AND nspname not like 'pg\\_temp%%' ",
+									 "DEFAULT");
 		else
 		{
 			/* generic, type based, GUC support */
@@ -4061,7 +4155,7 @@ psql_completion(const char *text, int start, int end)
 
 					snprintf(querybuf, sizeof(querybuf),
 							 Query_for_enum, prev2_wd);
-					COMPLETE_WITH_QUERY(querybuf);
+					COMPLETE_WITH_QUERY_PLUS(querybuf, "DEFAULT");
 				}
 				else if (strcmp(guctype, "bool") == 0)
 					COMPLETE_WITH("on", "off", "true", "false", "yes", "no",
@@ -4080,7 +4174,7 @@ psql_completion(const char *text, int start, int end)
 
 /* TABLE, but not TABLE embedded in other commands */
 	else if (Matches("TABLE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_selectables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_selectables);
 
 /* TABLESAMPLE */
 	else if (TailMatches("TABLESAMPLE"))
@@ -4090,14 +4184,13 @@ psql_completion(const char *text, int start, int end)
 
 /* TRUNCATE */
 	else if (Matches("TRUNCATE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_truncatables,
-								   " UNION SELECT 'TABLE'"
-								   " UNION SELECT 'ONLY'");
+		COMPLETE_WITH_SCHEMA_QUERY_PLUS(Query_for_list_of_truncatables,
+										"TABLE", "ONLY");
 	else if (Matches("TRUNCATE", "TABLE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_truncatables,
-								   " UNION SELECT 'ONLY'");
+		COMPLETE_WITH_SCHEMA_QUERY_PLUS(Query_for_list_of_truncatables,
+										"ONLY");
 	else if (HeadMatches("TRUNCATE") && TailMatches("ONLY"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_truncatables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_truncatables);
 	else if (Matches("TRUNCATE", MatchAny) ||
 			 Matches("TRUNCATE", "TABLE|ONLY", MatchAny) ||
 			 Matches("TRUNCATE", "TABLE", "ONLY", MatchAny))
@@ -4107,18 +4200,20 @@ psql_completion(const char *text, int start, int end)
 
 /* UNLISTEN */
 	else if (Matches("UNLISTEN"))
-		COMPLETE_WITH_QUERY("SELECT pg_catalog.quote_ident(channel) FROM pg_catalog.pg_listening_channels() AS channel WHERE substring(pg_catalog.quote_ident(channel),1,%d)='%s' UNION SELECT '*'");
+		COMPLETE_WITH_QUERY_PLUS("SELECT channel FROM pg_catalog.pg_listening_channels() AS channel"
+								 " WHERE substring(channel,1,%d)='%s'",
+								 "*");
 
 /* UPDATE --- can be inside EXPLAIN, RULE, etc */
 	/* If prev. word is UPDATE suggest a list of tables */
 	else if (TailMatches("UPDATE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables);
 	/* Complete UPDATE <table> with "SET" */
 	else if (TailMatches("UPDATE", MatchAny))
 		COMPLETE_WITH("SET");
 	/* Complete UPDATE <table> SET with list of attributes */
 	else if (TailMatches("UPDATE", MatchAny, "SET"))
-		COMPLETE_WITH_ATTR(prev2_wd, "");
+		COMPLETE_WITH_ATTR(prev2_wd);
 	/* UPDATE <table> SET <attr> = */
 	else if (TailMatches("UPDATE", MatchAny, "SET", MatchAnyExcept("*=")))
 		COMPLETE_WITH("=");
@@ -4127,11 +4222,11 @@ psql_completion(const char *text, int start, int end)
 	else if (Matches("ALTER|CREATE|DROP", "USER", "MAPPING"))
 		COMPLETE_WITH("FOR");
 	else if (Matches("CREATE", "USER", "MAPPING", "FOR"))
-		COMPLETE_WITH_QUERY(Query_for_list_of_roles
-							" UNION SELECT 'CURRENT_ROLE'"
-							" UNION SELECT 'CURRENT_USER'"
-							" UNION SELECT 'PUBLIC'"
-							" UNION SELECT 'USER'");
+		COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_roles,
+								 "CURRENT_ROLE",
+								 "CURRENT_USER",
+								 "PUBLIC",
+								 "USER");
 	else if (Matches("ALTER|DROP", "USER", "MAPPING", "FOR"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_user_mappings);
 	else if (Matches("CREATE|ALTER|DROP", "USER", "MAPPING", "FOR", MatchAny))
@@ -4144,26 +4239,26 @@ psql_completion(const char *text, int start, int end)
  * VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ table_and_columns [, ...] ]
  */
 	else if (Matches("VACUUM"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_vacuumables,
-								   " UNION SELECT 'FULL'"
-								   " UNION SELECT 'FREEZE'"
-								   " UNION SELECT 'ANALYZE'"
-								   " UNION SELECT 'VERBOSE'");
+		COMPLETE_WITH_SCHEMA_QUERY_PLUS(Query_for_list_of_vacuumables,
+										"FULL",
+										"FREEZE",
+										"ANALYZE",
+										"VERBOSE");
 	else if (Matches("VACUUM", "FULL"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_vacuumables,
-								   " UNION SELECT 'FREEZE'"
-								   " UNION SELECT 'ANALYZE'"
-								   " UNION SELECT 'VERBOSE'");
+		COMPLETE_WITH_SCHEMA_QUERY_PLUS(Query_for_list_of_vacuumables,
+										"FREEZE",
+										"ANALYZE",
+										"VERBOSE");
 	else if (Matches("VACUUM", "FREEZE") ||
 			 Matches("VACUUM", "FULL", "FREEZE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_vacuumables,
-								   " UNION SELECT 'VERBOSE'"
-								   " UNION SELECT 'ANALYZE'");
+		COMPLETE_WITH_SCHEMA_QUERY_PLUS(Query_for_list_of_vacuumables,
+										"VERBOSE",
+										"ANALYZE");
 	else if (Matches("VACUUM", "VERBOSE") ||
 			 Matches("VACUUM", "FULL|FREEZE", "VERBOSE") ||
 			 Matches("VACUUM", "FULL", "FREEZE", "VERBOSE"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_vacuumables,
-								   " UNION SELECT 'ANALYZE'");
+		COMPLETE_WITH_SCHEMA_QUERY_PLUS(Query_for_list_of_vacuumables,
+										"ANALYZE");
 	else if (HeadMatches("VACUUM", "(*") &&
 			 !HeadMatches("VACUUM", "(*)"))
 	{
@@ -4184,9 +4279,9 @@ psql_completion(const char *text, int start, int end)
 	}
 	else if (HeadMatches("VACUUM") && TailMatches("("))
 		/* "VACUUM (" should be caught above, so assume we want columns */
-		COMPLETE_WITH_ATTR(prev2_wd, "");
+		COMPLETE_WITH_ATTR(prev2_wd);
 	else if (HeadMatches("VACUUM"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_vacuumables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_vacuumables);
 
 /* WITH [RECURSIVE] */
 
@@ -4200,16 +4295,16 @@ psql_completion(const char *text, int start, int end)
 /* WHERE */
 	/* Simple case of the word before the where being the table name */
 	else if (TailMatches(MatchAny, "WHERE"))
-		COMPLETE_WITH_ATTR(prev2_wd, "");
+		COMPLETE_WITH_ATTR(prev2_wd);
 
 /* ... FROM ... */
 /* TODO: also include SRF ? */
 	else if (TailMatches("FROM") && !Matches("COPY|\\copy", MatchAny, "FROM"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_selectables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_selectables);
 
 /* ... JOIN ... */
 	else if (TailMatches("JOIN"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_selectables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_selectables);
 
 /* Backslash commands */
 /* TODO:  \dc \dd \dl */
@@ -4226,19 +4321,19 @@ psql_completion(const char *text, int start, int end)
 			COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 	}
 	else if (TailMatchesCS("\\da*"))
-		COMPLETE_WITH_VERSIONED_SCHEMA_QUERY(Query_for_list_of_aggregates, NULL);
+		COMPLETE_WITH_VERSIONED_SCHEMA_QUERY(Query_for_list_of_aggregates);
 	else if (TailMatchesCS("\\dAc*", MatchAny) ||
 			 TailMatchesCS("\\dAf*", MatchAny))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes);
 	else if (TailMatchesCS("\\dAo*", MatchAny) ||
 			 TailMatchesCS("\\dAp*", MatchAny))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_operator_families, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_operator_families);
 	else if (TailMatchesCS("\\dA*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_access_methods);
 	else if (TailMatchesCS("\\db*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
 	else if (TailMatchesCS("\\dD*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains);
 	else if (TailMatchesCS("\\des*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_servers);
 	else if (TailMatchesCS("\\deu*"))
@@ -4246,9 +4341,9 @@ psql_completion(const char *text, int start, int end)
 	else if (TailMatchesCS("\\dew*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_fdws);
 	else if (TailMatchesCS("\\df*"))
-		COMPLETE_WITH_VERSIONED_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
+		COMPLETE_WITH_VERSIONED_SCHEMA_QUERY(Query_for_list_of_functions);
 	else if (HeadMatchesCS("\\df*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes);
 
 	else if (TailMatchesCS("\\dFd*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_ts_dictionaries);
@@ -4261,51 +4356,51 @@ psql_completion(const char *text, int start, int end)
 		COMPLETE_WITH_QUERY(Query_for_list_of_ts_configurations);
 
 	else if (TailMatchesCS("\\di*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes);
 	else if (TailMatchesCS("\\dL*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_languages);
 	else if (TailMatchesCS("\\dn*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
 	/* no support for completing operators, but we can complete types: */
 	else if (HeadMatchesCS("\\do*", MatchAny))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes);
 	else if (TailMatchesCS("\\dp") || TailMatchesCS("\\z"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_grantables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_grantables);
 	else if (TailMatchesCS("\\dPi*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_partitioned_indexes, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_partitioned_indexes);
 	else if (TailMatchesCS("\\dPt*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_partitioned_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_partitioned_tables);
 	else if (TailMatchesCS("\\dP*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_partitioned_relations, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_partitioned_relations);
 	else if (TailMatchesCS("\\ds*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences);
 	else if (TailMatchesCS("\\dt*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
 	else if (TailMatchesCS("\\dT*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes);
 	else if (TailMatchesCS("\\du*") || TailMatchesCS("\\dg*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 	else if (TailMatchesCS("\\dv*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views);
 	else if (TailMatchesCS("\\dx*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_extensions);
 	else if (TailMatchesCS("\\dX*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_statistics, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_statistics);
 	else if (TailMatchesCS("\\dm*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews);
 	else if (TailMatchesCS("\\dE*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables);
 	else if (TailMatchesCS("\\dy*"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers);
 
 	/* must be at end of \d alternatives: */
 	else if (TailMatchesCS("\\d*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_relations, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_relations);
 
 	else if (TailMatchesCS("\\ef"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_routines, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_routines);
 	else if (TailMatchesCS("\\ev"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views);
 
 	else if (TailMatchesCS("\\encoding"))
 		COMPLETE_WITH_QUERY(Query_for_list_of_encodings);
@@ -4407,9 +4502,9 @@ psql_completion(const char *text, int start, int end)
 			COMPLETE_WITH_CS("default", "verbose", "terse", "sqlstate");
 	}
 	else if (TailMatchesCS("\\sf*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_routines, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_routines);
 	else if (TailMatchesCS("\\sv*"))
-		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views);
 	else if (TailMatchesCS("\\cd|\\e|\\edit|\\g|\\gx|\\i|\\include|"
 						   "\\ir|\\include_relative|\\o|\\out|"
 						   "\\s|\\w|\\write|\\lo_import"))
@@ -4426,19 +4521,21 @@ psql_completion(const char *text, int start, int end)
 	 */
 	else
 	{
-		int			i;
+		const pgsql_thing_t *wac;
 
-		for (i = 0; words_after_create[i].name; i++)
+		for (wac = words_after_create; wac->name != NULL; wac++)
 		{
-			if (pg_strcasecmp(prev_wd, words_after_create[i].name) == 0)
+			if (pg_strcasecmp(prev_wd, wac->name) == 0)
 			{
-				if (words_after_create[i].query)
-					COMPLETE_WITH_QUERY(words_after_create[i].query);
-				else if (words_after_create[i].vquery)
-					COMPLETE_WITH_VERSIONED_QUERY(words_after_create[i].vquery);
-				else if (words_after_create[i].squery)
-					COMPLETE_WITH_VERSIONED_SCHEMA_QUERY(words_after_create[i].squery,
-														 NULL);
+				if (wac->query)
+					COMPLETE_WITH_QUERY_LIST(wac->query,
+											 wac->keywords);
+				else if (wac->vquery)
+					COMPLETE_WITH_VERSIONED_QUERY_LIST(wac->vquery,
+													   wac->keywords);
+				else if (wac->squery)
+					COMPLETE_WITH_VERSIONED_SCHEMA_QUERY_LIST(wac->squery,
+															  wac->keywords);
 				break;
 			}
 		}
@@ -4551,7 +4648,8 @@ static char *
 complete_from_query(const char *text, int state)
 {
 	/* query is assumed to work for any server version */
-	return _complete_from_query(completion_charp, NULL, text, state);
+	return _complete_from_query(completion_charp, NULL, completion_charpp,
+								completion_verbatim, text, state);
 }
 
 static char *
@@ -4566,22 +4664,22 @@ complete_from_versioned_query(const char *text, int state)
 	if (vquery->query == NULL)
 		return NULL;
 
-	return _complete_from_query(vquery->query, NULL, text, state);
+	return _complete_from_query(vquery->query, NULL, completion_charpp,
+								completion_verbatim, text, state);
 }
 
 static char *
 complete_from_schema_query(const char *text, int state)
 {
 	/* query is assumed to work for any server version */
-	return _complete_from_query(completion_charp, completion_squery,
-								text, state);
+	return _complete_from_query(NULL, completion_squery, completion_charpp,
+								false, text, state);
 }
 
 static char *
 complete_from_versioned_schema_query(const char *text, int state)
 {
 	const SchemaQuery *squery = completion_squery;
-	const VersionedQuery *vquery = completion_vquery;
 
 	/* Find appropriate array element */
 	while (pset.sversion < squery->min_server_version)
@@ -4590,17 +4688,8 @@ complete_from_versioned_schema_query(const char *text, int state)
 	if (squery->catname == NULL)
 		return NULL;
 
-	/* Likewise for the add-on text, if any */
-	if (vquery)
-	{
-		while (pset.sversion < vquery->min_server_version)
-			vquery++;
-		if (vquery->query == NULL)
-			return NULL;
-	}
-
-	return _complete_from_query(vquery ? vquery->query : NULL,
-								squery, text, state);
+	return _complete_from_query(NULL, squery, completion_charpp,
+								false, text, state);
 }
 
 
@@ -4611,35 +4700,51 @@ complete_from_versioned_schema_query(const char *text, int state)
  *
  * The query can be one of two kinds:
  *
- * 1. A simple query which must contain a %d and a %s, which will be replaced
- * by the string length of the text and the text itself. The query may also
- * have up to four more %s in it; the first two such will be replaced by the
- * value of completion_info_charp, the next two by the value of
- * completion_info_charp2.
+ * 1. A simple query, which must contain a restriction clause of the form
+ *		substring(OUTPUT,1,%d)='%s'
+ * where "OUTPUT" is the same string that the query returns.  The %d and %s
+ * will be replaced by the string length of the text and the text itself,
+ * causing the results to be limited to those matching the already-typed text.
+ * The query may also have up to four more %s in it; the first two such will
+ * be replaced by the value of completion_info_charp, the next two by the
+ * value of completion_info_charp2.  (These strings will be escaped to
+ * become SQL literals, so what is actually in the query should be '%s'.)
+ * Simple queries should return a single column of matches.  If "verbatim"
+ * is true, the matches are returned as-is; otherwise, they are taken to
+ * be SQL identifiers and quoted if necessary.
  *
  * 2. A schema query used for completion of both schema and relation names.
- * These are more complex and must contain in the following order:
- * %d %s %d %s %d %s %s %d %s
- * where %d is the string length of the text and %s the text itself.
+ * This is represented by a SchemaQuery object; see that typedef for details.
+ *
+ * See top of file for examples of both kinds of query.
  *
- * If both simple_query and schema_query are non-NULL, then we construct
- * a schema query and append the (uninterpreted) string simple_query to it.
+ * In addition to the query itself, we accept a null-terminated array of
+ * literal keywords, which will be returned if they match the input-so-far
+ * (case insensitively).  (These are in addition to keywords specified
+ * within the schema_query, if any.)
  *
- * It is assumed that strings should be escaped to become SQL literals
- * (that is, what is in the query is actually ... '%s' ...)
+ * If "verbatim" is true, then we use the given text as-is to match the
+ * query results; otherwise we parse it as a possibly-qualified identifier,
+ * and reconstruct suitable quoting afterward.
  *
- * See top of file for examples of both kinds of query.
+ * "text" and "state" are supplied by Readline.  "text" is the word we are
+ * trying to complete.  "state" is zero on first call, nonzero later.
  *
- * "text" and "state" are supplied by readline.
+ * readline will call this repeatedly with the same text and varying
+ * state.  On each call, we are supposed to return a malloc'd string
+ * that is a candidate completion.  Return NULL when done.
  */
 static char *
 _complete_from_query(const char *simple_query,
 					 const SchemaQuery *schema_query,
+					 const char *const *keywords,
+					 bool verbatim,
 					 const char *text, int state)
 {
-	static int	list_index,
-				byte_length;
+	static int	list_index;
 	static PGresult *result = NULL;
+	static bool schemaquoted;
+	static bool objectquoted;
 
 	/*
 	 * If this is the first time for this completion, we fetch a list of our
@@ -4648,31 +4753,56 @@ _complete_from_query(const char *simple_query,
 	if (state == 0)
 	{
 		PQExpBufferData query_buffer;
-		char	   *e_text;
+		char	   *schemaname;
+		char	   *objectname;
+		int			schema_length = 0;
+		int			object_length = 0;
+		char	   *e_schemaname;
+		char	   *e_objectname;
 		char	   *e_info_charp;
 		char	   *e_info_charp2;
-		const char *pstr = text;
-		int			char_length = 0;
 
+		/* Reset static state, ensuring no memory leaks */
 		list_index = 0;
-		byte_length = strlen(text);
+		PQclear(result);
+		result = NULL;
+
+		/* Parse text, splitting into schema and object name if needed */
+		if (verbatim)
+		{
+			objectname = pg_strdup(text);
+			schemaname = NULL;
+		}
+		else
+		{
+			parse_identifier(text,
+							 &schemaname, &objectname,
+							 &schemaquoted, &objectquoted);
+		}
 
 		/*
-		 * Count length as number of characters (not bytes), for passing to
+		 * Count lengths as number of characters (not bytes), for passing to
 		 * substring
 		 */
-		while (*pstr)
+		if (schemaname)
 		{
-			char_length++;
-			pstr += PQmblenBounded(pstr, pset.encoding);
+			for (const char *p = schemaname;
+				 *p;
+				 p += PQmblenBounded(p, pset.encoding))
+				schema_length++;
 		}
-
-		/* Free any prior result */
-		PQclear(result);
-		result = NULL;
+		for (const char *p = objectname;
+			 *p;
+			 p += PQmblenBounded(p, pset.encoding))
+			object_length++;
 
 		/* Set up suitably-escaped copies of textual inputs */
-		e_text = escape_string(text);
+		if (schemaname)
+			e_schemaname = escape_string(schemaname);
+		else
+			e_schemaname = NULL;
+
+		e_objectname = escape_string(objectname);
 
 		if (completion_info_charp)
 			e_info_charp = escape_string(completion_info_charp);
@@ -4688,14 +4818,17 @@ _complete_from_query(const char *simple_query,
 
 		if (schema_query)
 		{
-			/* schema_query gives us the pieces to assemble */
-			const char *qualresult = schema_query->qualresult;
-
-			if (qualresult == NULL)
-				qualresult = schema_query->result;
+			Assert(simple_query == NULL);
 
+			/*
+			 * We issue different queries depending on whether the input is
+			 * already qualified or not.  schema_query gives us the pieces to
+			 * assemble.
+			 */
+			if (schemaname == NULL)
+			{
 			/* Get unqualified names matching the input-so-far */
-			appendPQExpBuffer(&query_buffer, "SELECT %s FROM %s WHERE ",
+			appendPQExpBuffer(&query_buffer, "SELECT %s, NULL::pg_catalog.text FROM %s WHERE ",
 							  schema_query->result,
 							  schema_query->catname);
 			if (schema_query->selcondition)
@@ -4703,7 +4836,7 @@ _complete_from_query(const char *simple_query,
 								  schema_query->selcondition);
 			appendPQExpBuffer(&query_buffer, "substring(%s,1,%d)='%s'",
 							  schema_query->result,
-							  char_length, e_text);
+							  object_length, e_objectname);
 			appendPQExpBuffer(&query_buffer, " AND %s",
 							  schema_query->viscondition);
 
@@ -4715,72 +4848,59 @@ _complete_from_query(const char *simple_query,
 			 */
 			if (strcmp(schema_query->catname,
 					   "pg_catalog.pg_class c") == 0 &&
-				strncmp(text, "pg_", 3) != 0)
+				strncmp(objectname, "pg_", 3) != 0)
 			{
 				appendPQExpBufferStr(&query_buffer,
 									 " AND c.relnamespace <> (SELECT oid FROM"
 									 " pg_catalog.pg_namespace WHERE nspname = 'pg_catalog')");
 			}
 
-			/*
-			 * Add in matching schema names, but only if there is more than
-			 * one potential match among schema names.
-			 */
+			/* Add in schema names matching the input-so-far */
 			appendPQExpBuffer(&query_buffer, "\nUNION\n"
-							  "SELECT pg_catalog.quote_ident(n.nspname) || '.' "
+							  "SELECT NULL::pg_catalog.text, n.nspname "
 							  "FROM pg_catalog.pg_namespace n "
-							  "WHERE substring(pg_catalog.quote_ident(n.nspname) || '.',1,%d)='%s'",
-							  char_length, e_text);
-			appendPQExpBuffer(&query_buffer,
-							  " AND (SELECT pg_catalog.count(*)"
-							  " FROM pg_catalog.pg_namespace"
-							  " WHERE substring(pg_catalog.quote_ident(nspname) || '.',1,%d) ="
-							  " substring('%s',1,pg_catalog.length(pg_catalog.quote_ident(nspname))+1)) > 1",
-							  char_length, e_text);
+							  "WHERE substring(n.nspname,1,%d)='%s'",
+							  object_length, e_objectname);
 
 			/*
-			 * Add in matching qualified names, but only if there is exactly
-			 * one schema matching the input-so-far.
+			 * Likewise, suppress system schemas unless the input-so-far
+			 * begins with "pg_".
 			 */
-			appendPQExpBuffer(&query_buffer, "\nUNION\n"
-							  "SELECT pg_catalog.quote_ident(n.nspname) || '.' || %s "
+			if (strncmp(objectname, "pg_", 3) != 0)
+				appendPQExpBufferStr(&query_buffer,
+									 " AND n.nspname NOT LIKE 'pg\\_%'");
+
+			/*
+			 * If the input-so-far had quotes, force any added schema names
+			 * to be quoted, too.
+			 */
+			schemaquoted = objectquoted;
+			}
+			else
+			{
+			/* Input is qualified, so produce only qualified names */
+			appendPQExpBuffer(&query_buffer, "SELECT %s, n.nspname "
 							  "FROM %s, pg_catalog.pg_namespace n "
 							  "WHERE %s = n.oid AND ",
-							  qualresult,
+							  schema_query->result,
 							  schema_query->catname,
 							  schema_query->namespace);
 			if (schema_query->selcondition)
 				appendPQExpBuffer(&query_buffer, "%s AND ",
 								  schema_query->selcondition);
-			appendPQExpBuffer(&query_buffer, "substring(pg_catalog.quote_ident(n.nspname) || '.' || %s,1,%d)='%s'",
-							  qualresult,
-							  char_length, e_text);
-
-			/*
-			 * This condition exploits the single-matching-schema rule to
-			 * speed up the query
-			 */
-			appendPQExpBuffer(&query_buffer,
-							  " AND substring(pg_catalog.quote_ident(n.nspname) || '.',1,%d) ="
-							  " substring('%s',1,pg_catalog.length(pg_catalog.quote_ident(n.nspname))+1)",
-							  char_length, e_text);
-			appendPQExpBuffer(&query_buffer,
-							  " AND (SELECT pg_catalog.count(*)"
-							  " FROM pg_catalog.pg_namespace"
-							  " WHERE substring(pg_catalog.quote_ident(nspname) || '.',1,%d) ="
-							  " substring('%s',1,pg_catalog.length(pg_catalog.quote_ident(nspname))+1)) = 1",
-							  char_length, e_text);
-
-			/* If an addon query was provided, use it */
-			if (simple_query)
-				appendPQExpBuffer(&query_buffer, "\n%s", simple_query);
+			appendPQExpBuffer(&query_buffer, "substring(%s,1,%d)='%s' AND ",
+							  schema_query->result,
+							  object_length, e_objectname);
+			appendPQExpBuffer(&query_buffer, "n.nspname = '%s'",
+							  e_schemaname);
+			}
 		}
 		else
 		{
 			Assert(simple_query);
 			/* simple_query is an sprintf-style format string */
 			appendPQExpBuffer(&query_buffer, simple_query,
-							  char_length, e_text,
+							  object_length, e_objectname,
 							  e_info_charp, e_info_charp,
 							  e_info_charp2, e_info_charp2);
 		}
@@ -4789,28 +4909,118 @@ _complete_from_query(const char *simple_query,
 		appendPQExpBuffer(&query_buffer, "\nLIMIT %d",
 						  completion_max_records);
 
+		/* Finally, we can issue the query */
 		result = exec_query(query_buffer.data);
 
+		/*
+		 * If we have a schema name, and any of the resulting object names
+		 * require quoting, we have to force them all to be quoted.  This
+		 * copes with a Readline heuristic that causes it to actually remove
+		 * user-typed characters that are beyond the common prefix of what we
+		 * return, that is if the user types foo.bar<TAB> and we return both
+		 * foo.barbaz and foo."barXYZ", Readline actually discards "bar",
+		 * which is not helpful at all.  That heuristic doesn't seem to be
+		 * applied if the common prefix is empty, so we don't need to do this
+		 * if dealing with unqualified names.
+		 */
+		if (result && PQresultStatus(result) == PGRES_TUPLES_OK &&
+			schemaname != NULL && !verbatim && !objectquoted)
+		{
+			for (int i = 0; i < PQntuples(result); i++)
+			{
+				const char *item = PQgetvalue(result, i, 0);
+
+				if (identifier_needs_quotes(item))
+				{
+					objectquoted = true;
+					break;
+				}
+			}
+		}
+
+		/* Clean up */
 		termPQExpBuffer(&query_buffer);
-		free(e_text);
+		if (e_schemaname)
+			free(e_schemaname);
+		free(e_objectname);
 		if (e_info_charp)
 			free(e_info_charp);
 		if (e_info_charp2)
 			free(e_info_charp2);
 	}
 
-	/* Find something that matches */
+	/* Return the next result, if any, but not if the query failed */
 	if (result && PQresultStatus(result) == PGRES_TUPLES_OK)
 	{
-		const char *item;
+		int			nskip;
+
+		if (list_index < PQntuples(result))
+		{
+			const char *item = NULL;
+			const char *nsp = NULL;
+
+			if (!PQgetisnull(result, list_index, 0))
+				item = PQgetvalue(result, list_index, 0);
+			if (PQnfields(result) > 1 &&
+				!PQgetisnull(result, list_index, 1))
+				nsp = PQgetvalue(result, list_index, 1);
+			list_index++;
 
-		while (list_index < PQntuples(result) &&
-			   (item = PQgetvalue(result, list_index++, 0)))
-			if (pg_strncasecmp(text, item, byte_length) == 0)
+			if (verbatim)
 				return pg_strdup(item);
+
+			/*
+			 * Hack: if we're returning one single schema name, don't let
+			 * Readline add a space after it.  Otherwise it'll stop being part
+			 * of the completion subject text, which is not what we want.
+			 */
+#ifdef HAVE_RL_COMPLETION_APPEND_CHARACTER
+			if (item == NULL && nsp != NULL && PQntuples(result) == 1)
+				rl_completion_append_character = '\0';
+#endif
+
+			return requote_identifier(nsp, item, schemaquoted, objectquoted);
+		}
+
+		/*
+		 * When the query result is exhausted, check for hard-wired keywords.
+		 * These will only be returned if they match the input-so-far,
+		 * ignoring case.
+		 */
+		nskip = list_index - PQntuples(result);
+		if (schema_query && schema_query->keywords)
+		{
+			const char *const *itemp = schema_query->keywords;
+
+			while (*itemp)
+			{
+				const char *item = *itemp++;
+
+				if (nskip-- > 0)
+					continue;
+				list_index++;
+				if (pg_strncasecmp(text, item, strlen(text)) == 0)
+					return pg_strdup(item);
+			}
+		}
+		if (keywords)
+		{
+			const char *const *itemp = keywords;
+
+			while (*itemp)
+			{
+				const char *item = *itemp++;
+
+				if (nskip-- > 0)
+					continue;
+				list_index++;
+				if (pg_strncasecmp(text, item, strlen(text)) == 0)
+					return pg_strdup(item);
+			}
+		}
 	}
 
-	/* If nothing matches, free the db structure and return null */
+	/* When nothing (else) matches, free the db structure and return null */
 	PQclear(result);
 	result = NULL;
 	return NULL;
@@ -5144,6 +5354,199 @@ escape_string(const char *text)
 }
 
 
+/*
+ * parse_identifier - Parse a possibly-schema-qualified SQL identifier.
+ *
+ * This involves splitting off the schema name if present, de-quoting,
+ * and downcasing any unquoted text.  We are a bit laxer than the backend
+ * in that we allow just portions of a name to be quoted --- that's because
+ * psql metacommands have traditionally behaved that way.
+ *
+ * Outputs are a malloc'd schema name (NULL if none), malloc'd object name,
+ * and booleans telling whether any part of the schema and object name was
+ * double-quoted.
+ */
+static void
+parse_identifier(const char *ident,
+				 char **schemaname, char **objectname,
+				 bool *schemaquoted, bool *objectquoted)
+{
+	size_t		buflen = strlen(ident) + 1;
+	bool		enc_is_single_byte = (pg_encoding_max_length(pset.encoding) == 1);
+	char	   *sname;
+	char	   *oname;
+	char	   *optr;
+	bool		inquotes;
+
+	/* Initialize, making a certainly-large-enough output buffer */
+	sname = NULL;
+	oname = pg_malloc(buflen);
+	*schemaquoted = *objectquoted = false;
+	/* Scan */
+	optr = oname;
+	inquotes = false;
+	while (*ident)
+	{
+		unsigned char ch = (unsigned char) *ident++;
+
+		if (ch == '"')
+		{
+			if (inquotes && *ident == '"')
+			{
+				/* two quote marks within a quoted identifier = emit quote */
+				*optr++ = '"';
+				ident++;
+			}
+			else
+			{
+				inquotes = !inquotes;
+				*objectquoted = true;
+			}
+		}
+		else if (ch == '.' && !inquotes)
+		{
+			/* Found a schema name, transfer it to sname / *schemaquoted */
+			*optr = '\0';
+			free(sname);		/* drop any catalog name */
+			sname = oname;
+			oname = pg_malloc(buflen);
+			optr = oname;
+			*schemaquoted = *objectquoted;
+			*objectquoted = false;
+		}
+		else
+		{
+			if (!inquotes)
+			{
+				/*
+				 * This downcasing transformation should match the backend's
+				 * downcase_identifier() as best we can.  We do not know the
+				 * backend's locale, though, so it's necessarily approximate.
+				 * We assume that psql is operating in the same locale and
+				 * encoding as the backend.
+				 */
+				if (ch >= 'A' && ch <= 'Z')
+					ch += 'a' - 'A';
+				else if (enc_is_single_byte && IS_HIGHBIT_SET(ch) && isupper(ch))
+					ch = tolower(ch);
+			}
+			*optr++ = (char) ch;
+		}
+	}
+
+	*optr = '\0';
+	*schemaname = sname;
+	*objectname = oname;
+}
+
+
+/*
+ * requote_identifier - Reconstruct a possibly-schema-qualified SQL identifier.
+ *
+ * Build a malloc'd string containing the identifier, with quoting applied
+ * as necessary.  This is more or less the inverse of parse_identifier;
+ * in particular, if an input component was quoted, we'll quote the output
+ * even when that isn't strictly required.
+ *
+ * Unlike parse_identifier, we handle the case where a schema and no
+ * object name is provided, producing just "schema.".
+ */
+static char *
+requote_identifier(const char *schemaname, const char *objectname,
+				   bool quote_schema, bool quote_object)
+{
+	char	   *result;
+	size_t		buflen = 1;		/* count the trailing \0 */
+	char	   *ptr;
+
+	/*
+	 * We could use PQescapeIdentifier for some of this, but not all, and it
+	 * adds more notational cruft than it seems worth.
+	 */
+	if (schemaname)
+	{
+		buflen += strlen(schemaname) + 1;	/* +1 for the dot */
+		if (!quote_schema)
+			quote_schema = identifier_needs_quotes(schemaname);
+		if (quote_schema)
+		{
+			buflen += 2;		/* account for quote marks */
+			for (const char *p = schemaname; *p; p++)
+			{
+				if (*p == '"')
+					buflen++;
+			}
+		}
+	}
+	if (objectname)
+	{
+		buflen += strlen(objectname);
+		if (!quote_object)
+			quote_object = identifier_needs_quotes(objectname);
+		if (quote_object)
+		{
+			buflen += 2;		/* account for quote marks */
+			for (const char *p = objectname; *p; p++)
+			{
+				if (*p == '"')
+					buflen++;
+			}
+		}
+	}
+	result = pg_malloc(buflen);
+	ptr = result;
+	if (schemaname)
+	{
+		if (quote_schema)
+			*ptr++ = '"';
+		for (const char *p = schemaname; *p; p++)
+		{
+			*ptr++ = *p;
+			if (*p == '"')
+				*ptr++ = '"';
+		}
+		if (quote_schema)
+			*ptr++ = '"';
+		*ptr++ = '.';
+	}
+	if (objectname)
+	{
+		if (quote_object)
+			*ptr++ = '"';
+		for (const char *p = objectname; *p; p++)
+		{
+			*ptr++ = *p;
+			if (*p == '"')
+				*ptr++ = '"';
+		}
+		if (quote_object)
+			*ptr++ = '"';
+	}
+	*ptr = '\0';
+	return result;
+}
+
+
+/*
+ * Detect whether an identifier must be double-quoted.
+ *
+ * Note we'll quote anything that's not ASCII; the backend's quote_ident()
+ * does the same.
+ */
+static bool
+identifier_needs_quotes(const char *ident)
+{
+	if (!((ident[0] >= 'a' && ident[0] <= 'z') || ident[0] == '_'))
+		return true;
+	if (strspn(ident, "abcdefghijklmnopqrstuvwxyz0123456789_") != strlen(ident))
+		return true;
+
+	/* XXX is it worth checking for SQL reserved words? */
+
+	return false;
+}
+
+
 /*
  * Execute a query and report any errors. This should be the preferred way of
  * talking to the database in this file.
