Re: Support tab completion for upper character inputs in psql - Mailing list pgsql-hackers

From Tom Lane
Subject Re: Support tab completion for upper character inputs in psql
Date
Msg-id 2805502.1643405157@sss.pgh.pa.us
Whole thread Raw
In response to Re: Support tab completion for upper character inputs in psql  (Tom Lane <tgl@sss.pgh.pa.us>)
Responses Re: Support tab completion for upper character inputs in psql
List pgsql-hackers
I wrote:
> It's certainly arguable that the first case is right as-is and we
> shouldn't change it.  I think that could be handled by tweaking my
> patch so that it wouldn't offer completions that start with a quote
> unless the input word does.  That would also cause I<TAB> to complete
> immediately to id, which is arguably fine.

Here's a patch series that does it like that.  I have to admit that
after playing with it, this is probably better.  There's less
magic-looking behavior involved, and it lets me drop an ugly hack
I had to work around a case where Readline didn't want to play along.

0001 also cleans up one oversight in the previous version, which
is to beware of multibyte characters in parse_identifier().  I'm
not sure there is any actual hazard there, since we weren't looking
for backslashes, but it's better to be sure.  I added the keyword
handling I'd left out before, too.

0002-0004 are largely as before.

I've also added 0005, which changes the prefix-matching clauses
in the SQL queries from "substring(foo,1,%d)='%s'" to
"foo LIKE '%s'".  This simplifies reading the queries a little bit,
but the real reason to do it is that the planner can optimize the
catalog searches a lot better.  It knows a lot about LIKE prefix
queries and exactly nothing about substring().  For example,
DROP TYPE foo<TAB> now produces a query like this:

explain SELECT t.typname, NULL::pg_catalog.text FROM pg_catalog.pg_type t WHERE (t.typrelid = 0  OR (SELECT c.relkind =
'c'    FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid)) AND t.typname !~ '^_' AND (t.typname) LIKE 'foo%' AND
pg_catalog.pg_type_is_visible(t.oid);
                                                                QUERY PLAN
                  

------------------------------------------------------------------------------------------------------------------------------------------
 Index Scan using pg_type_typname_nsp_index on pg_type t  (cost=0.28..16.63 rows=1 width=96)
   Index Cond: ((typname >= 'foo'::text) AND (typname < 'fop'::text))
   Filter: ((typname !~ '^_'::text) AND (typname ~~ 'foo%'::text) AND pg_type_is_visible(oid) AND ((typrelid =
'0'::oid)OR (SubPlan 1))) 
   SubPlan 1
     ->  Index Scan using pg_class_oid_index on pg_class c  (cost=0.28..8.30 rows=1 width=1)
           Index Cond: (oid = t.typrelid)
(6 rows)

where before you got a seqscan:

explain SELECT pg_catalog.format_type(t.oid, NULL) FROM pg_catalog.pg_type t WHERE (t.typrelid = 0  OR (SELECT
c.relkind= 'c'     FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid)) AND t.typname !~ '^_' AND
substring(pg_catalog.format_type(t.oid,NULL),1,3)='foo' AND pg_catalog.pg_type_is_visible(t.oid); 
    QUERY PLAN

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Seq Scan on pg_type t  (cost=0.00..16691.86 rows=1 width=32)
   Filter: ((typname !~ '^_'::text) AND ("substring"(format_type(oid, NULL::integer), 1, 3) = 'foo'::text) AND
pg_type_is_visible(oid)AND ((typrelid = '0'::oid) OR (SubPlan 1))) 
   SubPlan 1
     ->  Index Scan using pg_class_oid_index on pg_class c  (cost=0.28..8.30 rows=1 width=1)
           Index Cond: (oid = t.typrelid)
(5 rows)

Again, while these queries only have to run at human speed, that doesn't
mean it's okay to be wasteful.  I seem to recall hearing complaints that
they are noticeably slow in installations with many thousand tables, too.
This should help.

            regards, tom lane

diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 4c62e7b1b4..16999dd9a2 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -46,7 +46,9 @@
 #include "catalog/pg_am_d.h"
 #include "catalog/pg_class_d.h"
 #include "common.h"
+#include "common/keywords.h"
 #include "libpq-fe.h"
+#include "mb/pg_wchar.h"
 #include "pqexpbuffer.h"
 #include "settings.h"
 #include "stringutils.h"
@@ -148,16 +150,16 @@ 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 base object name to return.  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,87 @@ 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; \
+    completion_verbatim = false; \
     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; \
+    completion_verbatim = false; \
     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 +307,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 +323,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 +380,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 +407,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 +426,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 +474,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 +486,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 +494,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 +505,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 +522,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 +534,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 +542,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 +550,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 +560,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 +568,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 +576,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 +584,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 +594,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 +602,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 +611,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 +621,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 +641,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 +656,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 +668,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 +684,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 +696,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 +713,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 +721,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 +736,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 +755,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 +772,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 +794,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 +803,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 +813,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 +824,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 +849,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 +1112,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 +1138,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 +1151,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 +1168,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 +1182,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 +1277,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 +1291,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 +1735,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 +1784,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 +1867,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 +1896,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 +1906,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 +1963,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 +2058,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 +2068,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 +2089,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 +2118,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 +2160,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 +2187,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 +2251,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 +2274,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 +2295,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 +2385,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 +2447,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 +2472,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 +2489,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 +2512,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 +2575,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 +2617,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 +2645,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 +2703,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 +2729,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 +2747,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 +2772,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 +2781,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 +2804,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 +2828,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 +2856,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 +2895,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 +2919,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 +2959,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 +2986,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 +3000,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 +3011,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 +3050,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 +3118,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 +3126,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 +3231,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 +3267,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 +3351,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 +3404,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 +3446,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 +3459,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 +3528,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 +3582,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 +3613,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 +3634,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 +3656,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 +3689,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 +3702,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 +3743,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 +3774,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 +3840,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 +3875,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 +3917,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
WHEREsubstring(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 +3931,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 +3960,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 +3983,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 +4046,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 +4091,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 +4104,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 +4135,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 +4157,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 +4176,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 +4186,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 +4202,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
WHEREsubstring(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 +4224,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 +4241,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 +4281,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 +4297,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 +4323,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 +4343,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 +4358,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 +4504,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 +4523,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 +4650,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 +4666,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,
+                                completion_verbatim, 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 +4690,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,
+                                completion_verbatim, text, state);
 }


@@ -4611,35 +4702,54 @@ 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;
+                num_schema_only,
+                num_other;
     static PGresult *result = NULL;
+    static bool non_empty_object;
+    static bool schemaquoted;
+    static bool objectquoted;

     /*
      * If this is the first time for this completion, we fetch a list of our
@@ -4648,31 +4758,53 @@ _complete_from_query(const char *simple_query,
     if (state == 0)
     {
         PQExpBufferData query_buffer;
-        char       *e_text;
+        char       *schemaname;
+        char       *objectname;
+        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);
+        num_schema_only = 0;
+        num_other = 0;
+        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);
+        }
+
+        /* Remember whether the user has typed anything in the object part */
+        non_empty_object = (*objectname != '\0');

         /*
          * Count length as number of characters (not bytes), for passing to
          * substring
          */
-        while (*pstr)
-        {
-            char_length++;
-            pstr += PQmblenBounded(pstr, pset.encoding);
-        }
-
-        /* 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 +4820,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 +4838,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 +4850,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 schema names matching the input-so-far */
+            appendPQExpBuffer(&query_buffer, "\nUNION ALL\n"
+                              "SELECT NULL::pg_catalog.text, n.nspname "
+                              "FROM pg_catalog.pg_namespace n "
+                              "WHERE substring(n.nspname,1,%d)='%s'",
+                              object_length, e_objectname);
+
             /*
-             * Add in matching schema names, but only if there is more than
-             * one potential match among schema names.
+             * Likewise, suppress system schemas unless the input-so-far
+             * begins with "pg_".
              */
-            appendPQExpBuffer(&query_buffer, "\nUNION\n"
-                              "SELECT pg_catalog.quote_ident(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);
+            if (strncmp(objectname, "pg_", 3) != 0)
+                appendPQExpBufferStr(&query_buffer,
+                                     " AND n.nspname NOT LIKE 'pg\\_%'");

             /*
-             * Add in matching qualified names, but only if there is exactly
-             * one schema matching the input-so-far.
+             * Since we're matching these schema names to the object name,
+             * handle their quoting using the object name's quoting state.
              */
-            appendPQExpBuffer(&query_buffer, "\nUNION\n"
-                              "SELECT pg_catalog.quote_ident(n.nspname) || '.' || %s "
+            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 +4911,120 @@ _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);

+        /* 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;

-        while (list_index < PQntuples(result) &&
-               (item = PQgetvalue(result, list_index++, 0)))
-            if (pg_strncasecmp(text, item, byte_length) == 0)
+        while (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++;
+
+            /* In verbatim mode, we return all the items as-is */
+            if (verbatim)
                 return pg_strdup(item);
+
+            /*
+             * In normal mode, a name requiring quoting will be returned only
+             * if the input was empty or quoted.  Otherwise the user might see
+             * completion inserting a quote she didn't type, which is
+             * surprising.  This restriction also dodges some odd behaviors of
+             * some versions of readline/libedit.
+             */
+            if (non_empty_object)
+            {
+                if (item && !objectquoted && identifier_needs_quotes(item))
+                    continue;
+                if (nsp && !schemaquoted && identifier_needs_quotes(nsp))
+                    continue;
+            }
+
+            /* Count schema-only results for hack below */
+            if (item == NULL && nsp != NULL)
+                num_schema_only++;
+            else
+                num_other++;
+
+            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)
+                {
+                    num_other++;
+                    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)
+                {
+                    num_other++;
+                    return pg_strdup(item);
+                }
+            }
+        }
     }

-    /* If nothing matches, free the db structure and return null */
+    /*
+     * Hack: if we returned only bare schema names, don't let Readline add a
+     * space afterwards.  Otherwise the schema will stop being part of the
+     * completion subject text, which is not what we want.
+     */
+#ifdef HAVE_RL_COMPLETION_APPEND_CHARACTER
+    if (num_schema_only > 0 && num_other == 0)
+        rl_completion_append_character = '\0';
+#endif
+
+    /* No more matches, so free the result structure and return null */
     PQclear(result);
     result = NULL;
     return NULL;
@@ -5144,6 +5358,228 @@ 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 (!enc_is_single_byte && IS_HIGHBIT_SET(ch))
+        {
+            /*
+             * Transfer multibyte characters without further processing.  They
+             * wouldn't be affected by our downcasing rule anyway, and this
+             * avoids possibly doing the wrong thing in unsafe client
+             * encodings.
+             */
+            int            chlen = PQmblenBounded(ident - 1, pset.encoding);
+
+            *optr++ = (char) ch;
+            while (--chlen > 0)
+                *optr++ = *ident++;
+        }
+        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.  Perhaps this could be relaxed in future.
+ */
+static bool
+identifier_needs_quotes(const char *ident)
+{
+    int            kwnum;
+
+    /* Check syntax. */
+    if (!((ident[0] >= 'a' && ident[0] <= 'z') || ident[0] == '_'))
+        return true;
+    if (strspn(ident, "abcdefghijklmnopqrstuvwxyz0123456789_") != strlen(ident))
+        return true;
+
+    /*
+     * Check for keyword.  We quote keywords except for unreserved ones.
+     *
+     * It is possible that our keyword list doesn't quite agree with the
+     * server's, but this should be close enough for tab-completion purposes.
+     *
+     * Note: ScanKeywordLookup() does case-insensitive comparison, but that's
+     * fine, since we already know we have all-lower-case.
+     */
+    kwnum = ScanKeywordLookup(ident, &ScanKeywords);
+
+    if (kwnum >= 0 && ScanKeywordCategories[kwnum] != UNRESERVED_KEYWORD)
+        return true;
+
+    return false;
+}
+
+
 /*
  * Execute a query and report any errors. This should be the preferred way of
  * talking to the database in this file.
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 16999dd9a2..5658ec3759 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -110,6 +110,12 @@ typedef struct VersionedQuery
  * time.  So we put the components of each query into this struct and
  * assemble them with the common boilerplate in _complete_from_query().
  *
+ * We also use this struct to define queries that use completion_info_object,
+ * which is some object related to the one(s) we want to get the names of
+ * (for example, the table we want the indexes of).  In that usage the
+ * objects we're completing might not have a schema of their own, but the
+ * related object almost always does (passed in completion_info_schema).
+ *
  * As with VersionedQuery, we can use an array of these if the query details
  * must vary across versions.
  */
@@ -123,8 +129,9 @@ typedef struct SchemaQuery
     int            min_server_version;

     /*
-     * Name of catalog or catalogs to be queried, with alias, eg.
-     * "pg_catalog.pg_class c".  Note that "pg_namespace n" will be added.
+     * Name of catalog or catalogs to be queried, with alias(es), eg.
+     * "pg_catalog.pg_class c".  Note that "pg_namespace n" and/or
+     * "pg_namespace ni" will be added automatically when needed.
      */
     const char *catname;

@@ -140,12 +147,15 @@ typedef struct SchemaQuery
     /*
      * Visibility condition --- which rows are visible without schema
      * qualification?  For example, "pg_catalog.pg_table_is_visible(c.oid)".
+     * NULL if not needed.
      */
     const char *viscondition;

     /*
-     * Namespace --- name of field to join to pg_namespace.oid. For example,
-     * "c.relnamespace".
+     * Namespace --- name of field to join to pg_namespace.oid when there is
+     * schema qualification.  For example, "c.relnamespace".  NULL if we don't
+     * want to join to pg_namespace (then any schema part in the input word
+     * will be ignored).
      */
     const char *namespace;

@@ -154,12 +164,43 @@ typedef struct SchemaQuery
      */
     const char *result;

+    /*
+     * In some cases, it's difficult to keep the query from returning the same
+     * object multiple times.  Specify use_distinct to filter out duplicates.
+     */
+    bool        use_distinct;
+
     /*
      * 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 *const *keywords;
+
+    /*
+     * If this query uses completion_info_object/completion_info_schema,
+     * populate the remaining fields, else leave them NULL.  When using this
+     * capability, catname must include the catalog that defines the
+     * completion_info_object, and selcondition must include the join
+     * condition that connects it to the result's catalog.
+     *
+     * infoname is the field that should be equated to completion_info_object,
+     * for example "ci.relname".
+     */
+    const char *infoname;
+
+    /*
+     * Visibility condition to use when completion_info_schema is not set. For
+     * example, "pg_catalog.pg_table_is_visible(ci.oid)".  NULL if not needed.
+     */
+    const char *infoviscondition;
+
+    /*
+     * Name of field to join to pg_namespace.oid when completion_info_schema
+     * is set.  For example, "ci.relnamespace".  NULL if we don't want to
+     * consider completion_info_schema.
+     */
+    const char *infonamespace;
 } SchemaQuery;


@@ -176,10 +217,10 @@ static int    completion_max_records;
 static char completion_last_char;    /* last char of input word */
 static const char *completion_charp;    /* to pass a string */
 static const char *const *completion_charpp;    /* to pass a list of strings */
-static const char *completion_info_charp;    /* to pass a second string */
-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 char *completion_info_object;    /* name of a related object */
+static char *completion_info_schema;    /* schema name of a related object */
 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 */
@@ -257,6 +298,14 @@ do { \
     COMPLETE_WITH_SCHEMA_QUERY_LIST(query, list); \
 } while (0)

+#define COMPLETE_WITH_SCHEMA_QUERY_VERBATIM(query) \
+do { \
+    completion_squery = &(query); \
+    completion_charpp = NULL; \
+    completion_verbatim = true; \
+    matches = rl_completion_matches(text, complete_from_schema_query); \
+} while (0)
+
 #define COMPLETE_WITH_VERSIONED_SCHEMA_QUERY(query) \
     COMPLETE_WITH_VERSIONED_SCHEMA_QUERY_LIST(query, NULL)

@@ -312,29 +361,11 @@ do { \

 #define COMPLETE_WITH_ATTR_LIST(relation, list) \
 do { \
-    char   *_completion_schema; \
-    char   *_completion_table; \
-\
-    _completion_schema = strtokx(relation, " \t\n\r", ".", "\"", 0, \
-                                 false, false, pset.encoding); \
-    (void) strtokx(NULL, " \t\n\r", ".", "\"", 0, \
-                   false, false, pset.encoding); \
-    _completion_table = strtokx(NULL, " \t\n\r", ".", "\"", 0, \
-                                false, false, pset.encoding); \
-    if (_completion_table == NULL) \
-    { \
-        completion_charp = Query_for_list_of_attributes; \
-        completion_info_charp = relation; \
-    } \
-    else \
-    { \
-        completion_charp = Query_for_list_of_attributes_with_schema; \
-        completion_info_charp = _completion_table; \
-        completion_info_charp2 = _completion_schema; \
-    } \
+    setup_completion_info(relation); \
+    completion_squery = &(Query_for_list_of_attributes); \
     completion_charpp = list; \
     completion_verbatim = false; \
-    matches = rl_completion_matches(text, complete_from_query); \
+    matches = rl_completion_matches(text, complete_from_schema_query); \
 } while (0)

 #define COMPLETE_WITH_ATTR_PLUS(relation, ...) \
@@ -351,65 +382,24 @@ do { \
  */
 #define COMPLETE_WITH_ENUM_VALUE(type) \
 do { \
-    char   *_completion_schema; \
-    char   *_completion_type; \
-    bool    use_quotes; \
-\
-    _completion_schema = strtokx(type, " \t\n\r", ".", "\"", 0, \
-                                 false, false, pset.encoding); \
-    (void) strtokx(NULL, " \t\n\r", ".", "\"", 0, \
-                   false, false, pset.encoding); \
-    _completion_type = strtokx(NULL, " \t\n\r", ".", "\"", 0, \
-                               false, false, pset.encoding); \
-    use_quotes = (text[0] == '\'' || \
-                  start == 0 || rl_line_buffer[start - 1] != '\''); \
-    if (_completion_type == NULL) \
-    { \
-        if (use_quotes) \
-            completion_charp = Query_for_list_of_enum_values_quoted; \
-        else \
-            completion_charp = Query_for_list_of_enum_values_unquoted; \
-        completion_info_charp = type; \
-    } \
+    setup_completion_info(type); \
+    if (text[0] == '\'' || \
+        start == 0 || rl_line_buffer[start - 1] != '\'') \
+        completion_squery = &(Query_for_list_of_enum_values_quoted); \
     else \
-    { \
-        if (use_quotes) \
-            completion_charp = Query_for_list_of_enum_values_with_schema_quoted; \
-        else \
-            completion_charp = Query_for_list_of_enum_values_with_schema_unquoted; \
-        completion_info_charp = _completion_type; \
-        completion_info_charp2 = _completion_schema; \
-    } \
+        completion_squery = &(Query_for_list_of_enum_values_unquoted); \
     completion_charpp = NULL; \
     completion_verbatim = true; \
-    matches = rl_completion_matches(text, complete_from_query); \
+    matches = rl_completion_matches(text, complete_from_schema_query); \
 } while (0)

 #define COMPLETE_WITH_FUNCTION_ARG(function) \
 do { \
-    char   *_completion_schema; \
-    char   *_completion_function; \
-\
-    _completion_schema = strtokx(function, " \t\n\r", ".", "\"", 0, \
-                                 false, false, pset.encoding); \
-    (void) strtokx(NULL, " \t\n\r", ".", "\"", 0, \
-                   false, false, pset.encoding); \
-    _completion_function = strtokx(NULL, " \t\n\r", ".", "\"", 0, \
-                                   false, false, pset.encoding); \
-    if (_completion_function == NULL) \
-    { \
-        completion_charp = Query_for_list_of_arguments; \
-        completion_info_charp = function; \
-    } \
-    else \
-    { \
-        completion_charp = Query_for_list_of_arguments_with_schema; \
-        completion_info_charp = _completion_function; \
-        completion_info_charp2 = _completion_schema; \
-    } \
+    setup_completion_info(function); \
+    completion_squery = &(Query_for_list_of_arguments); \
     completion_charpp = NULL; \
     completion_verbatim = true; \
-    matches = rl_completion_matches(text, complete_from_query); \
+    matches = rl_completion_matches(text, complete_from_schema_query); \
 } while (0)

 /*
@@ -419,6 +409,51 @@ do { \
  * unnecessary bloat in the completions generated.
  */

+static const SchemaQuery Query_for_constraint_of_table = {
+    .catname = "pg_catalog.pg_constraint con, pg_catalog.pg_class c1",
+    .selcondition = "con.conrelid=c1.oid",
+    .result = "con.conname",
+    .infoname = "c1.relname",
+    .infoviscondition = "pg_catalog.pg_table_is_visible(c1.oid)",
+    .infonamespace = "c1.relnamespace",
+};
+
+static const SchemaQuery Query_for_constraint_of_table_not_validated = {
+    .catname = "pg_catalog.pg_constraint con, pg_catalog.pg_class c1",
+    .selcondition = "con.conrelid=c1.oid and not con.convalidated",
+    .result = "con.conname",
+    .infoname = "c1.relname",
+    .infoviscondition = "pg_catalog.pg_table_is_visible(c1.oid)",
+    .infonamespace = "c1.relnamespace",
+};
+
+static const SchemaQuery Query_for_constraint_of_type = {
+    .catname = "pg_catalog.pg_constraint con, pg_catalog.pg_type t",
+    .selcondition = "con.contypid=t.oid",
+    .result = "con.conname",
+    .infoname = "t.typname",
+    .infoviscondition = "pg_catalog.pg_type_is_visible(t.oid)",
+    .infonamespace = "t.typnamespace",
+};
+
+static const SchemaQuery Query_for_index_of_table = {
+    .catname = "pg_catalog.pg_class c1, pg_catalog.pg_class c2, pg_catalog.pg_index i",
+    .selcondition = "c1.oid=i.indrelid and i.indexrelid=c2.oid",
+    .result = "c2.relname",
+    .infoname = "c1.relname",
+    .infoviscondition = "pg_catalog.pg_table_is_visible(c1.oid)",
+    .infonamespace = "c1.relnamespace",
+};
+
+static const SchemaQuery Query_for_unique_index_of_table = {
+    .catname = "pg_catalog.pg_class c1, pg_catalog.pg_class c2, pg_catalog.pg_index i",
+    .selcondition = "c1.oid=i.indrelid and i.indexrelid=c2.oid and i.indisunique",
+    .result = "c2.relname",
+    .infoname = "c1.relname",
+    .infoviscondition = "pg_catalog.pg_table_is_visible(c1.oid)",
+    .infonamespace = "c1.relnamespace",
+};
+
 static const SchemaQuery Query_for_list_of_aggregates[] = {
     {
         .min_server_version = 110000,
@@ -437,6 +472,32 @@ static const SchemaQuery Query_for_list_of_aggregates[] = {
     }
 };

+static const SchemaQuery Query_for_list_of_arguments = {
+    .catname = "pg_catalog.pg_proc p",
+    .result = "pg_catalog.oidvectortypes(p.proargtypes)||')'",
+    .infoname = "p.proname",
+    .infoviscondition = "pg_catalog.pg_function_is_visible(p.oid)",
+    .infonamespace = "p.pronamespace",
+};
+
+static const SchemaQuery Query_for_list_of_attributes = {
+    .catname = "pg_catalog.pg_attribute a, pg_catalog.pg_class c",
+    .selcondition = "c.oid = a.attrelid and a.attnum > 0 and not a.attisdropped",
+    .result = "a.attname",
+    .infoname = "c.relname",
+    .infoviscondition = "pg_catalog.pg_table_is_visible(c.oid)",
+    .infonamespace = "c.relnamespace",
+};
+
+static const SchemaQuery Query_for_list_of_attribute_numbers = {
+    .catname = "pg_catalog.pg_attribute a, pg_catalog.pg_class c",
+    .selcondition = "c.oid = a.attrelid and a.attnum > 0 and not a.attisdropped",
+    .result = "a.attnum::pg_catalog.text",
+    .infoname = "c.relname",
+    .infoviscondition = "pg_catalog.pg_table_is_visible(c.oid)",
+    .infonamespace = "c.relnamespace",
+};
+
 static const char *const Keywords_for_list_of_datatypes[] = {
     "bigint",
     "boolean",
@@ -497,6 +558,24 @@ static const SchemaQuery Query_for_list_of_domains = {
     .result = "t.typname",
 };

+static const SchemaQuery Query_for_list_of_enum_values_quoted = {
+    .catname = "pg_catalog.pg_enum e, pg_catalog.pg_type t",
+    .selcondition = "t.oid = e.enumtypid",
+    .result = "pg_catalog.quote_literal(enumlabel)",
+    .infoname = "t.typname",
+    .infoviscondition = "pg_catalog.pg_type_is_visible(t.oid)",
+    .infonamespace = "t.typnamespace",
+};
+
+static const SchemaQuery Query_for_list_of_enum_values_unquoted = {
+    .catname = "pg_catalog.pg_enum e, pg_catalog.pg_type t",
+    .selcondition = "t.oid = e.enumtypid",
+    .result = "e.enumlabel",
+    .infoname = "t.typname",
+    .infoviscondition = "pg_catalog.pg_type_is_visible(t.oid)",
+    .infonamespace = "t.typnamespace",
+};
+
 /* Note: this intentionally accepts aggregates as well as plain functions */
 static const SchemaQuery Query_for_list_of_functions[] = {
     {
@@ -571,6 +650,48 @@ static const SchemaQuery Query_for_list_of_partitioned_tables = {
     .result = "c.relname",
 };

+static const SchemaQuery Query_for_list_of_tables_for_constraint = {
+    .catname = "pg_catalog.pg_class c, pg_catalog.pg_constraint con",
+    .selcondition = "c.oid=con.conrelid and c.relkind IN ("
+    CppAsString2(RELKIND_RELATION) ", "
+    CppAsString2(RELKIND_PARTITIONED_TABLE) ")",
+    .viscondition = "pg_catalog.pg_table_is_visible(c.oid)",
+    .namespace = "c.relnamespace",
+    .result = "c.relname",
+    .use_distinct = true,
+    .infoname = "con.conname",
+};
+
+static const SchemaQuery Query_for_list_of_tables_for_policy = {
+    .catname = "pg_catalog.pg_class c, pg_catalog.pg_policy p",
+    .selcondition = "c.oid=p.polrelid",
+    .viscondition = "pg_catalog.pg_table_is_visible(c.oid)",
+    .namespace = "c.relnamespace",
+    .result = "c.relname",
+    .use_distinct = true,
+    .infoname = "p.polname",
+};
+
+static const SchemaQuery Query_for_list_of_tables_for_rule = {
+    .catname = "pg_catalog.pg_class c, pg_catalog.pg_rewrite r",
+    .selcondition = "c.oid=r.ev_class",
+    .viscondition = "pg_catalog.pg_table_is_visible(c.oid)",
+    .namespace = "c.relnamespace",
+    .result = "c.relname",
+    .use_distinct = true,
+    .infoname = "r.rulename",
+};
+
+static const SchemaQuery Query_for_list_of_tables_for_trigger = {
+    .catname = "pg_catalog.pg_class c, pg_catalog.pg_trigger t",
+    .selcondition = "c.oid=t.tgrelid",
+    .viscondition = "pg_catalog.pg_table_is_visible(c.oid)",
+    .namespace = "c.relnamespace",
+    .result = "c.relname",
+    .use_distinct = true,
+    .infoname = "t.tgname",
+};
+
 static const SchemaQuery Query_for_list_of_views = {
     .catname = "pg_catalog.pg_class c",
     .selcondition = "c.relkind IN (" CppAsString2(RELKIND_VIEW) ")",
@@ -719,7 +840,6 @@ static const SchemaQuery Query_for_list_of_clusterables = {
 static const SchemaQuery Query_for_list_of_constraints_with_schema = {
     .catname = "pg_catalog.pg_constraint c",
     .selcondition = "c.conrelid <> 0",
-    .viscondition = "true",        /* there is no pg_constraint_is_visible */
     .namespace = "c.connamespace",
     .result = "c.conname",
 };
@@ -739,96 +859,56 @@ static const SchemaQuery Query_for_list_of_collations = {
     .result = "c.collname",
 };

+static const SchemaQuery Query_for_partition_of_table = {
+    .catname = "pg_catalog.pg_class c1, pg_catalog.pg_class c2, pg_catalog.pg_inherits i",
+    .selcondition = "c1.oid=i.inhparent and i.inhrelid=c2.oid and c2.relispartition",
+    .viscondition = "pg_catalog.pg_table_is_visible(c2.oid)",
+    .namespace = "c2.relnamespace",
+    .result = "c2.relname",
+    .infoname = "c1.relname",
+    .infoviscondition = "pg_catalog.pg_table_is_visible(c1.oid)",
+    .infonamespace = "c1.relnamespace",
+};
+
+static const SchemaQuery Query_for_rule_of_table = {
+    .catname = "pg_catalog.pg_rewrite r, pg_catalog.pg_class c1",
+    .selcondition = "r.ev_class=c1.oid",
+    .result = "r.rulename",
+    .infoname = "c1.relname",
+    .infoviscondition = "pg_catalog.pg_table_is_visible(c1.oid)",
+    .infonamespace = "c1.relnamespace",
+};
+
+static const SchemaQuery Query_for_trigger_of_table = {
+    .catname = "pg_catalog.pg_trigger t, pg_catalog.pg_class c1",
+    .selcondition = "t.tgrelid=c1.oid and not t.tgisinternal",
+    .result = "t.tgname",
+    .infoname = "c1.relname",
+    .infoviscondition = "pg_catalog.pg_table_is_visible(c1.oid)",
+    .infonamespace = "c1.relnamespace",
+};
+

 /*
  * Queries to get lists of names of various kinds of things, possibly
- * restricted to names matching a partially entered name.  In these queries,
- * the first %s will be replaced by the text entered so far (suitably escaped
- * to become a SQL literal string).  %d will be replaced by the length of the
- * string (in unescaped form).  A second and third %s, if present, will be
- * replaced by a suitably-escaped version of the string provided in
- * completion_info_charp.  A fourth and fifth %s are similarly replaced by
- * completion_info_charp2.
+ * restricted to names matching a partially entered name.  Don't use
+ * this method where the user might wish to enter a schema-qualified
+ * name; make a SchemaQuery instead.
+ *
+ * In these queries, there must be 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.
  *
- * Beware that the allowed sequences of %s and %d are determined by
- * _complete_from_query().
+ * There can be a second '%s', which will be replaced by a suitably-escaped
+ * version of the string provided in completion_info_object.  If there is a
+ * third '%s', it will be replaced by a suitably-escaped version of the string
+ * provided in completion_info_schema.  NOTE: using completion_info_object
+ * that way is usually the wrong thing, and using completion_info_schema
+ * that way is always the wrong thing.  Make a SchemaQuery instead.
  */

-#define Query_for_list_of_attributes \
-"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(attname,1,%d)='%s' "\
-"   AND (relname='%s' "\
-"        OR '\"' || relname || '\"'='%s') "\
-"   AND pg_catalog.pg_table_is_visible(c.oid)"
-
-#define Query_for_list_of_attribute_numbers \
-"SELECT attnum "\
-"  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(attnum::pg_catalog.text,1,%d)='%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 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(attname,1,%d)='%s' "\
-"   AND (relname='%s' "\
-"        OR '\"' || relname || '\"' ='%s') "\
-"   AND (nspname='%s' "\
-"        OR '\"' || nspname || '\"' ='%s') "
-
-#define Query_for_list_of_enum_values_quoted \
-"SELECT pg_catalog.quote_literal(enumlabel) "\
-"  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 (typname='%s' "\
-"        OR '\"' || typname || '\"'='%s') "\
-"   AND pg_catalog.pg_type_is_visible(t.oid)"
-
-#define Query_for_list_of_enum_values_unquoted \
-"SELECT enumlabel "\
-"  FROM pg_catalog.pg_enum e, pg_catalog.pg_type t "\
-" WHERE t.oid = e.enumtypid "\
-"   AND substring(enumlabel,1,%d)='%s' "\
-"   AND (typname='%s' "\
-"        OR '\"' || typname || '\"'='%s') "\
-"   AND pg_catalog.pg_type_is_visible(t.oid)"
-
-#define Query_for_list_of_enum_values_with_schema_quoted \
-"SELECT pg_catalog.quote_literal(enumlabel) "\
-"  FROM pg_catalog.pg_enum e, pg_catalog.pg_type t, pg_catalog.pg_namespace n "\
-" WHERE t.oid = e.enumtypid "\
-"   AND n.oid = t.typnamespace "\
-"   AND substring(pg_catalog.quote_literal(enumlabel),1,%d)='%s' "\
-"   AND (typname='%s' "\
-"        OR '\"' || typname || '\"'='%s') "\
-"   AND (nspname='%s' "\
-"        OR '\"' || nspname || '\"' ='%s') "
-
-#define Query_for_list_of_enum_values_with_schema_unquoted \
-"SELECT enumlabel "\
-"  FROM pg_catalog.pg_enum e, pg_catalog.pg_type t, pg_catalog.pg_namespace n "\
-" WHERE t.oid = e.enumtypid "\
-"   AND n.oid = t.typnamespace "\
-"   AND substring(enumlabel,1,%d)='%s' "\
-"   AND (typname='%s' "\
-"        OR '\"' || typname || '\"'='%s') "\
-"   AND (nspname='%s' "\
-"        OR '\"' || nspname || '\"' ='%s') "
-
 #define Query_for_list_of_template_databases \
 "SELECT d.datname "\
 "  FROM pg_catalog.pg_database d "\
@@ -887,81 +967,12 @@ static const SchemaQuery Query_for_list_of_collations = {
 #define Keywords_for_list_of_grant_roles \
 "PUBLIC", "CURRENT_ROLE", "CURRENT_USER", "SESSION_USER"

-#define Query_for_index_of_table \
-"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 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"
-
-#define Query_for_constraint_of_table \
-"SELECT conname "\
-"  FROM pg_catalog.pg_class c1, pg_catalog.pg_constraint con "\
-" WHERE c1.oid=conrelid and substring(conname,1,%d)='%s'"\
-"       and c1.relname='%s'"\
-"       and pg_catalog.pg_table_is_visible(c1.oid)"
-
-#define Query_for_constraint_of_table_not_validated \
-Query_for_constraint_of_table \
-"       and not con.convalidated"
-
 #define Query_for_all_table_constraints \
 "SELECT conname "\
 "  FROM pg_catalog.pg_constraint c "\
 " WHERE c.conrelid <> 0 "\
 "       and substring(conname,1,%d)='%s'"

-#define Query_for_constraint_of_type \
-"SELECT conname "\
-"  FROM pg_catalog.pg_type t, pg_catalog.pg_constraint con "\
-" WHERE t.oid=contypid and substring(conname,1,%d)='%s'"\
-"       and t.typname='%s'"\
-"       and pg_catalog.pg_type_is_visible(t.oid)"
-
-#define Query_for_list_of_tables_for_constraint \
-"SELECT relname "\
-"  FROM pg_catalog.pg_class"\
-" WHERE substring(relname,1,%d)='%s'"\
-"   AND oid IN "\
-"       (SELECT conrelid FROM pg_catalog.pg_constraint "\
-"         WHERE conname='%s')"
-
-#define Query_for_rule_of_table \
-"SELECT rulename "\
-"  FROM pg_catalog.pg_class c1, pg_catalog.pg_rewrite "\
-" WHERE c1.oid=ev_class and substring(rulename,1,%d)='%s'"\
-"       and c1.relname='%s'"\
-"       and pg_catalog.pg_table_is_visible(c1.oid)"
-
-#define Query_for_list_of_tables_for_rule \
-"SELECT relname "\
-"  FROM pg_catalog.pg_class"\
-" WHERE substring(relname,1,%d)='%s'"\
-"   AND oid IN "\
-"       (SELECT ev_class FROM pg_catalog.pg_rewrite "\
-"         WHERE rulename='%s')"
-
-#define Query_for_trigger_of_table \
-"SELECT tgname "\
-"  FROM pg_catalog.pg_class c1, pg_catalog.pg_trigger "\
-" 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"
-
-#define Query_for_list_of_tables_for_trigger \
-"SELECT relname "\
-"  FROM pg_catalog.pg_class"\
-" WHERE substring(relname,1,%d)='%s'"\
-"   AND oid IN "\
-"       (SELECT tgrelid FROM pg_catalog.pg_trigger "\
-"         WHERE tgname='%s')"
-
 #define Query_for_list_of_ts_configurations \
 "SELECT cfgname FROM pg_catalog.pg_ts_config "\
 " WHERE substring(cfgname,1,%d)='%s'"
@@ -1010,24 +1021,6 @@ Query_for_constraint_of_table \
 "  WHERE substring(amname,1,%d)='%s' AND "\
 "   amtype=" CppAsString2(AMTYPE_TABLE)

-#define Query_for_list_of_arguments \
-"SELECT pg_catalog.oidvectortypes(proargtypes)||')' "\
-"  FROM pg_catalog.pg_proc "\
-" 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))"
-
-#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 substring(pg_catalog.oidvectortypes(proargtypes)||')',1,%d)='%s'"\
-"   AND n.oid = p.pronamespace "\
-"   AND (proname='%s' "\
-"        OR '\"' || proname || '\"' ='%s') "\
-"   AND (nspname='%s' "\
-"        OR '\"' || nspname || '\"' ='%s') "
-
 #define Query_for_list_of_extensions \
 " SELECT extname "\
 "   FROM pg_catalog.pg_extension "\
@@ -1074,30 +1067,18 @@ Query_for_constraint_of_table \
 "   FROM pg_catalog.pg_policy "\
 "  WHERE substring(polname,1,%d)='%s'"

-#define Query_for_list_of_tables_for_policy \
-"SELECT relname "\
-"  FROM pg_catalog.pg_class"\
-" WHERE substring(relname,1,%d)='%s'"\
-"   AND oid IN "\
-"       (SELECT polrelid FROM pg_catalog.pg_policy "\
-"         WHERE polname='%s')"
-
-#define Query_for_enum \
-" SELECT name FROM ( "\
-"   SELECT pg_catalog.unnest(enumvals) AS name "\
+#define Query_for_values_of_enum_GUC \
+" SELECT val FROM ( "\
+"   SELECT name, pg_catalog.unnest(enumvals) AS val "\
 "     FROM pg_catalog.pg_settings "\
-"    WHERE pg_catalog.lower(name)=pg_catalog.lower('%s') "\
 "    ) ss "\
-"  WHERE pg_catalog.substring(name,1,%%d)='%%s'"
+"  WHERE substring(val,1,%d)='%s'"\
+"        and pg_catalog.lower(name)=pg_catalog.lower('%s')"

-#define Query_for_partition_of_table \
-"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 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_channels \
+" SELECT channel "\
+"   FROM pg_catalog.pg_listening_channels() AS channel "\
+"  WHERE substring(channel,1,%d)='%s'"

 #define Query_for_list_of_cursors \
 " SELECT name "\
@@ -1280,6 +1261,7 @@ static char *_complete_from_query(const char *simple_query,
                                   const char *const *keywords,
                                   bool verbatim,
                                   const char *text, int state);
+static void setup_completion_info(const char *word);
 static char *complete_from_list(const char *text, int state);
 static char *complete_from_const(const char *text, int state);
 static void append_variable_names(char ***varnames, int *nvars,
@@ -1685,8 +1667,10 @@ psql_completion(const char *text, int start, int end)
     /* Clear a few things. */
     completion_charp = NULL;
     completion_charpp = NULL;
-    completion_info_charp = NULL;
-    completion_info_charp2 = NULL;
+    completion_vquery = NULL;
+    completion_squery = NULL;
+    completion_info_object = NULL;
+    completion_info_schema = NULL;

     /*
      * Scan the input line to extract the words before our current position.
@@ -1866,14 +1850,14 @@ psql_completion(const char *text, int start, int end)
     /* ALTER EXTENSION <name> UPDATE */
     else if (Matches("ALTER", "EXTENSION", MatchAny, "UPDATE"))
     {
-        completion_info_charp = prev2_wd;
+        setup_completion_info(prev2_wd);
         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;
+        setup_completion_info(prev3_wd);
         COMPLETE_WITH_QUERY_VERBATIM(Query_for_list_of_available_extension_versions);
     }

@@ -1913,8 +1897,8 @@ psql_completion(const char *text, int start, int end)
     /* ALTER INDEX <name> ALTER COLUMN */
     else if (Matches("ALTER", "INDEX", MatchAny, "ALTER", "COLUMN"))
     {
-        completion_info_charp = prev3_wd;
-        COMPLETE_WITH_QUERY(Query_for_list_of_attribute_numbers);
+        setup_completion_info(prev3_wd);
+        COMPLETE_WITH_SCHEMA_QUERY_VERBATIM(Query_for_list_of_attribute_numbers);
     }
     /* ALTER INDEX <name> ALTER COLUMN <colnum> */
     else if (Matches("ALTER", "INDEX", MatchAny, "ALTER", "COLUMN", MatchAny))
@@ -2024,8 +2008,8 @@ psql_completion(const char *text, int start, int end)
     /* ALTER DOMAIN <sth> DROP|RENAME|VALIDATE CONSTRAINT */
     else if (Matches("ALTER", "DOMAIN", MatchAny, "DROP|RENAME|VALIDATE", "CONSTRAINT"))
     {
-        completion_info_charp = prev3_wd;
-        COMPLETE_WITH_QUERY(Query_for_constraint_of_type);
+        setup_completion_info(prev3_wd);
+        COMPLETE_WITH_SCHEMA_QUERY(Query_for_constraint_of_type);
     }
     /* ALTER DOMAIN <sth> RENAME */
     else if (Matches("ALTER", "DOMAIN", MatchAny, "RENAME"))
@@ -2110,8 +2094,8 @@ psql_completion(const char *text, int start, int end)
     /* ALTER POLICY <name> ON <table> */
     else if (Matches("ALTER", "POLICY", MatchAny, "ON"))
     {
-        completion_info_charp = prev2_wd;
-        COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_policy);
+        setup_completion_info(prev2_wd);
+        COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables_for_policy);
     }
     /* ALTER POLICY <name> ON <table> - show options */
     else if (Matches("ALTER", "POLICY", MatchAny, "ON", MatchAny))
@@ -2134,8 +2118,8 @@ psql_completion(const char *text, int start, int end)
     /* If we have ALTER RULE <name> ON, then add the correct tablename */
     else if (Matches("ALTER", "RULE", MatchAny, "ON"))
     {
-        completion_info_charp = prev2_wd;
-        COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_rule);
+        setup_completion_info(prev2_wd);
+        COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables_for_rule);
     }

     /* ALTER RULE <name> ON <name> */
@@ -2150,18 +2134,12 @@ psql_completion(const char *text, int start, int end)
     else if (Matches("ALTER", "TRIGGER", MatchAny))
         COMPLETE_WITH("ON");

-    else if (Matches("ALTER", "TRIGGER", MatchAny, MatchAny))
+    else if (Matches("ALTER", "TRIGGER", MatchAny, "ON"))
     {
-        completion_info_charp = prev2_wd;
-        COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger);
+        setup_completion_info(prev2_wd);
+        COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables_for_trigger);
     }

-    /*
-     * 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);
-
     /* ALTER TRIGGER <name> ON <name> */
     else if (Matches("ALTER", "TRIGGER", MatchAny, "ON", MatchAny))
         COMPLETE_WITH("RENAME TO", "DEPENDS ON EXTENSION",
@@ -2200,28 +2178,28 @@ psql_completion(const char *text, int start, int end)
     /* ALTER TABLE xxx ADD PRIMARY KEY USING INDEX */
     else if (Matches("ALTER", "TABLE", MatchAny, "ADD", "PRIMARY", "KEY", "USING", "INDEX"))
     {
-        completion_info_charp = prev6_wd;
-        COMPLETE_WITH_QUERY(Query_for_unique_index_of_table);
+        setup_completion_info(prev6_wd);
+        COMPLETE_WITH_SCHEMA_QUERY(Query_for_unique_index_of_table);
     }
     /* ALTER TABLE xxx ADD UNIQUE USING INDEX */
     else if (Matches("ALTER", "TABLE", MatchAny, "ADD", "UNIQUE", "USING", "INDEX"))
     {
-        completion_info_charp = prev5_wd;
-        COMPLETE_WITH_QUERY(Query_for_unique_index_of_table);
+        setup_completion_info(prev5_wd);
+        COMPLETE_WITH_SCHEMA_QUERY(Query_for_unique_index_of_table);
     }
     /* ALTER TABLE xxx ADD CONSTRAINT yyy PRIMARY KEY USING INDEX */
     else if (Matches("ALTER", "TABLE", MatchAny, "ADD", "CONSTRAINT", MatchAny,
                      "PRIMARY", "KEY", "USING", "INDEX"))
     {
-        completion_info_charp = prev8_wd;
-        COMPLETE_WITH_QUERY(Query_for_unique_index_of_table);
+        setup_completion_info(prev8_wd);
+        COMPLETE_WITH_SCHEMA_QUERY(Query_for_unique_index_of_table);
     }
     /* ALTER TABLE xxx ADD CONSTRAINT yyy UNIQUE USING INDEX */
     else if (Matches("ALTER", "TABLE", MatchAny, "ADD", "CONSTRAINT", MatchAny,
                      "UNIQUE", "USING", "INDEX"))
     {
-        completion_info_charp = prev7_wd;
-        COMPLETE_WITH_QUERY(Query_for_unique_index_of_table);
+        setup_completion_info(prev7_wd);
+        COMPLETE_WITH_SCHEMA_QUERY(Query_for_unique_index_of_table);
     }
     /* ALTER TABLE xxx ENABLE */
     else if (Matches("ALTER", "TABLE", MatchAny, "ENABLE"))
@@ -2231,23 +2209,23 @@ psql_completion(const char *text, int start, int end)
         COMPLETE_WITH("RULE", "TRIGGER");
     else if (Matches("ALTER", "TABLE", MatchAny, "ENABLE", "RULE"))
     {
-        completion_info_charp = prev3_wd;
-        COMPLETE_WITH_QUERY(Query_for_rule_of_table);
+        setup_completion_info(prev3_wd);
+        COMPLETE_WITH_SCHEMA_QUERY(Query_for_rule_of_table);
     }
     else if (Matches("ALTER", "TABLE", MatchAny, "ENABLE", MatchAny, "RULE"))
     {
-        completion_info_charp = prev4_wd;
-        COMPLETE_WITH_QUERY(Query_for_rule_of_table);
+        setup_completion_info(prev4_wd);
+        COMPLETE_WITH_SCHEMA_QUERY(Query_for_rule_of_table);
     }
     else if (Matches("ALTER", "TABLE", MatchAny, "ENABLE", "TRIGGER"))
     {
-        completion_info_charp = prev3_wd;
-        COMPLETE_WITH_QUERY(Query_for_trigger_of_table);
+        setup_completion_info(prev3_wd);
+        COMPLETE_WITH_SCHEMA_QUERY(Query_for_trigger_of_table);
     }
     else if (Matches("ALTER", "TABLE", MatchAny, "ENABLE", MatchAny, "TRIGGER"))
     {
-        completion_info_charp = prev4_wd;
-        COMPLETE_WITH_QUERY(Query_for_trigger_of_table);
+        setup_completion_info(prev4_wd);
+        COMPLETE_WITH_SCHEMA_QUERY(Query_for_trigger_of_table);
     }
     /* ALTER TABLE xxx INHERIT */
     else if (Matches("ALTER", "TABLE", MatchAny, "INHERIT"))
@@ -2263,13 +2241,13 @@ psql_completion(const char *text, int start, int end)
         COMPLETE_WITH("ROW LEVEL SECURITY", "RULE", "TRIGGER");
     else if (Matches("ALTER", "TABLE", MatchAny, "DISABLE", "RULE"))
     {
-        completion_info_charp = prev3_wd;
-        COMPLETE_WITH_QUERY(Query_for_rule_of_table);
+        setup_completion_info(prev3_wd);
+        COMPLETE_WITH_SCHEMA_QUERY(Query_for_rule_of_table);
     }
     else if (Matches("ALTER", "TABLE", MatchAny, "DISABLE", "TRIGGER"))
     {
-        completion_info_charp = prev3_wd;
-        COMPLETE_WITH_QUERY(Query_for_trigger_of_table);
+        setup_completion_info(prev3_wd);
+        COMPLETE_WITH_SCHEMA_QUERY(Query_for_trigger_of_table);
     }

     /* ALTER TABLE xxx ALTER */
@@ -2299,14 +2277,14 @@ psql_completion(const char *text, int start, int end)
     /* ALTER TABLE <sth> ALTER|DROP|RENAME CONSTRAINT <constraint> */
     else if (Matches("ALTER", "TABLE", MatchAny, "ALTER|DROP|RENAME", "CONSTRAINT"))
     {
-        completion_info_charp = prev3_wd;
-        COMPLETE_WITH_QUERY(Query_for_constraint_of_table);
+        setup_completion_info(prev3_wd);
+        COMPLETE_WITH_SCHEMA_QUERY(Query_for_constraint_of_table);
     }
     /* ALTER TABLE <sth> VALIDATE CONSTRAINT <non-validated constraint> */
     else if (Matches("ALTER", "TABLE", MatchAny, "VALIDATE", "CONSTRAINT"))
     {
-        completion_info_charp = prev3_wd;
-        COMPLETE_WITH_QUERY(Query_for_constraint_of_table_not_validated);
+        setup_completion_info(prev3_wd);
+        COMPLETE_WITH_SCHEMA_QUERY(Query_for_constraint_of_table_not_validated);
     }
     /* ALTER TABLE ALTER [COLUMN] <foo> */
     else if (Matches("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny) ||
@@ -2338,8 +2316,8 @@ psql_completion(const char *text, int start, int end)
         COMPLETE_WITH("ON");
     else if (Matches("ALTER", "TABLE", MatchAny, "CLUSTER", "ON"))
     {
-        completion_info_charp = prev3_wd;
-        COMPLETE_WITH_QUERY(Query_for_index_of_table);
+        setup_completion_info(prev3_wd);
+        COMPLETE_WITH_SCHEMA_QUERY(Query_for_index_of_table);
     }
     /* If we have ALTER TABLE <sth> SET, provide list of attributes and '(' */
     else if (Matches("ALTER", "TABLE", MatchAny, "SET"))
@@ -2370,8 +2348,8 @@ psql_completion(const char *text, int start, int end)
         COMPLETE_WITH_LIST(table_storage_parameters);
     else if (Matches("ALTER", "TABLE", MatchAny, "REPLICA", "IDENTITY", "USING", "INDEX"))
     {
-        completion_info_charp = prev5_wd;
-        COMPLETE_WITH_QUERY(Query_for_index_of_table);
+        setup_completion_info(prev5_wd);
+        COMPLETE_WITH_SCHEMA_QUERY(Query_for_index_of_table);
     }
     else if (Matches("ALTER", "TABLE", MatchAny, "REPLICA", "IDENTITY", "USING"))
         COMPLETE_WITH("INDEX");
@@ -2398,8 +2376,8 @@ psql_completion(const char *text, int start, int end)
      */
     else if (Matches("ALTER", "TABLE", MatchAny, "DETACH", "PARTITION"))
     {
-        completion_info_charp = prev3_wd;
-        COMPLETE_WITH_QUERY(Query_for_partition_of_table);
+        setup_completion_info(prev3_wd);
+        COMPLETE_WITH_SCHEMA_QUERY(Query_for_partition_of_table);
     }
     else if (Matches("ALTER", "TABLE", MatchAny, "DETACH", "PARTITION", MatchAny))
         COMPLETE_WITH("CONCURRENTLY", "FINALIZE");
@@ -2536,8 +2514,8 @@ psql_completion(const char *text, int start, int end)
     else if (Matches("CLUSTER", MatchAny, "USING") ||
              Matches("CLUSTER", "VERBOSE|(*)", MatchAny, "USING"))
     {
-        completion_info_charp = prev2_wd;
-        COMPLETE_WITH_QUERY(Query_for_index_of_table);
+        setup_completion_info(prev2_wd);
+        COMPLETE_WITH_SCHEMA_QUERY(Query_for_index_of_table);
     }
     else if (HeadMatches("CLUSTER", "(*") &&
              !HeadMatches("CLUSTER", "(*)"))
@@ -2574,9 +2552,9 @@ psql_completion(const char *text, int start, int end)
         COMPLETE_WITH("ON");
     else if (Matches("COMMENT", "ON", "CONSTRAINT", MatchAny, "ON"))
     {
-        completion_info_charp = prev2_wd;
-        COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_tables_for_constraint,
-                                 "DOMAIN");
+        setup_completion_info(prev2_wd);
+        COMPLETE_WITH_SCHEMA_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);
@@ -2594,8 +2572,8 @@ psql_completion(const char *text, int start, int end)
         COMPLETE_WITH("ON");
     else if (Matches("COMMENT", "ON", "POLICY", MatchAny, "ON"))
     {
-        completion_info_charp = prev2_wd;
-        COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_policy);
+        setup_completion_info(prev2_wd);
+        COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables_for_policy);
     }
     else if (Matches("COMMENT", "ON", "PROCEDURAL", "LANGUAGE"))
         COMPLETE_WITH_QUERY(Query_for_list_of_languages);
@@ -2603,8 +2581,8 @@ psql_completion(const char *text, int start, int end)
         COMPLETE_WITH("ON");
     else if (Matches("COMMENT", "ON", "RULE", MatchAny, "ON"))
     {
-        completion_info_charp = prev2_wd;
-        COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_rule);
+        setup_completion_info(prev2_wd);
+        COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables_for_rule);
     }
     else if (Matches("COMMENT", "ON", "TEXT", "SEARCH"))
         COMPLETE_WITH("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE");
@@ -2622,15 +2600,15 @@ psql_completion(const char *text, int start, int end)
         COMPLETE_WITH("LANGUAGE");
     else if (Matches("COMMENT", "ON", "TRANSFORM", "FOR", MatchAny, "LANGUAGE"))
     {
-        completion_info_charp = prev2_wd;
+        setup_completion_info(prev2_wd);
         COMPLETE_WITH_QUERY(Query_for_list_of_languages);
     }
     else if (Matches("COMMENT", "ON", "TRIGGER", MatchAny))
         COMPLETE_WITH("ON");
     else if (Matches("COMMENT", "ON", "TRIGGER", MatchAny, "ON"))
     {
-        completion_info_charp = prev2_wd;
-        COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger);
+        setup_completion_info(prev2_wd);
+        COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables_for_trigger);
     }
     else if (Matches("COMMENT", "ON", MatchAny, MatchAnyExcept("IS")) ||
              Matches("COMMENT", "ON", MatchAny, MatchAny, MatchAnyExcept("IS")) ||
@@ -2746,7 +2724,7 @@ psql_completion(const char *text, int start, int end)
     /* CREATE EXTENSION <name> VERSION */
     else if (Matches("CREATE", "EXTENSION", MatchAny, "VERSION"))
     {
-        completion_info_charp = prev2_wd;
+        setup_completion_info(prev2_wd);
         COMPLETE_WITH_QUERY_VERBATIM(Query_for_list_of_available_extension_versions);
     }

@@ -3057,7 +3035,7 @@ psql_completion(const char *text, int start, int end)
     else if (Matches("CREATE", "TRANSFORM", "FOR", MatchAny, "LANGUAGE") ||
              Matches("CREATE", "OR", "REPLACE", "TRANSFORM", "FOR", MatchAny, "LANGUAGE"))
     {
-        completion_info_charp = prev2_wd;
+        setup_completion_info(prev2_wd);
         COMPLETE_WITH_QUERY(Query_for_list_of_languages);
     }

@@ -3480,8 +3458,8 @@ psql_completion(const char *text, int start, int end)
         COMPLETE_WITH("ON");
     else if (Matches("DROP", "TRIGGER", MatchAny, "ON"))
     {
-        completion_info_charp = prev2_wd;
-        COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger);
+        setup_completion_info(prev2_wd);
+        COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables_for_trigger);
     }
     else if (Matches("DROP", "TRIGGER", MatchAny, "ON", MatchAny))
         COMPLETE_WITH("CASCADE", "RESTRICT");
@@ -3507,8 +3485,8 @@ psql_completion(const char *text, int start, int end)
     /* DROP POLICY <name> ON <table> */
     else if (Matches("DROP", "POLICY", MatchAny, "ON"))
     {
-        completion_info_charp = prev2_wd;
-        COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_policy);
+        setup_completion_info(prev2_wd);
+        COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables_for_policy);
     }
     else if (Matches("DROP", "POLICY", MatchAny, "ON", MatchAny))
         COMPLETE_WITH("CASCADE", "RESTRICT");
@@ -3518,8 +3496,8 @@ psql_completion(const char *text, int start, int end)
         COMPLETE_WITH("ON");
     else if (Matches("DROP", "RULE", MatchAny, "ON"))
     {
-        completion_info_charp = prev2_wd;
-        COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_rule);
+        setup_completion_info(prev2_wd);
+        COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables_for_rule);
     }
     else if (Matches("DROP", "RULE", MatchAny, "ON", MatchAny))
         COMPLETE_WITH("CASCADE", "RESTRICT");
@@ -3533,7 +3511,7 @@ psql_completion(const char *text, int start, int end)
         COMPLETE_WITH("LANGUAGE");
     else if (Matches("DROP", "TRANSFORM", "FOR", MatchAny, "LANGUAGE"))
     {
-        completion_info_charp = prev2_wd;
+        setup_completion_info(prev2_wd);
         COMPLETE_WITH_QUERY(Query_for_list_of_languages);
     }
     else if (Matches("DROP", "TRANSFORM", "FOR", MatchAny, "LANGUAGE", MatchAny))
@@ -3917,7 +3895,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 channel FROM pg_catalog.pg_listening_channels() AS channel WHERE
substring(channel,1,%d)='%s'");
+        COMPLETE_WITH_QUERY(Query_for_list_of_channels);

 /* OPTIONS */
     else if (TailMatches("OPTIONS"))
@@ -4153,11 +4131,9 @@ psql_completion(const char *text, int start, int end)
             {
                 if (strcmp(guctype, "enum") == 0)
                 {
-                    char        querybuf[1024];
-
-                    snprintf(querybuf, sizeof(querybuf),
-                             Query_for_enum, prev2_wd);
-                    COMPLETE_WITH_QUERY_PLUS(querybuf, "DEFAULT");
+                    setup_completion_info(prev2_wd);
+                    COMPLETE_WITH_QUERY_PLUS(Query_for_values_of_enum_GUC,
+                                             "DEFAULT");
                 }
                 else if (strcmp(guctype, "bool") == 0)
                     COMPLETE_WITH("on", "off", "true", "false", "yes", "no",
@@ -4202,9 +4178,7 @@ psql_completion(const char *text, int start, int end)

 /* UNLISTEN */
     else if (Matches("UNLISTEN"))
-        COMPLETE_WITH_QUERY_PLUS("SELECT channel FROM pg_catalog.pg_listening_channels() AS channel"
-                                 " WHERE substring(channel,1,%d)='%s'",
-                                 "*");
+        COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_channels, "*");

 /* UPDATE --- can be inside EXPLAIN, RULE, etc */
     /* If prev. word is UPDATE suggest a list of tables */
@@ -4405,7 +4379,7 @@ psql_completion(const char *text, int start, int end)
         COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views);

     else if (TailMatchesCS("\\encoding"))
-        COMPLETE_WITH_QUERY(Query_for_list_of_encodings);
+        COMPLETE_WITH_QUERY_VERBATIM(Query_for_list_of_encodings);
     else if (TailMatchesCS("\\h|\\help"))
         COMPLETE_WITH_LIST(sql_commands);
     else if (TailMatchesCS("\\h|\\help", MatchAny))
@@ -4564,6 +4538,12 @@ psql_completion(const char *text, int start, int end)
     free(previous_words);
     free(words_buffer);
     free(text_copy);
+    if (completion_info_object)
+        free(completion_info_object);
+    completion_info_object = NULL;
+    if (completion_info_schema)
+        free(completion_info_schema);
+    completion_info_schema = NULL;

     /* Return our Grand List O' Matches */
     return matches;
@@ -4707,10 +4687,11 @@ complete_from_versioned_schema_query(const char *text, int state)
  * 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'.)
+ * There can be a second '%s', which will be replaced by a suitably-escaped
+ * version of the string provided in completion_info_object.  If there is a
+ * third '%s', it will be replaced by a suitably-escaped version of the string
+ * provided in completion_info_schema.  Those strings should be set up
+ * by calling setup_completion_info().
  * 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.
@@ -4763,8 +4744,8 @@ _complete_from_query(const char *simple_query,
         int            object_length = 0;
         char       *e_schemaname;
         char       *e_objectname;
-        char       *e_info_charp;
-        char       *e_info_charp2;
+        char       *e_info_object;
+        char       *e_info_schema;

         /* Reset static state, ensuring no memory leaks */
         list_index = 0;
@@ -4806,15 +4787,15 @@ _complete_from_query(const char *simple_query,

         e_objectname = escape_string(objectname);

-        if (completion_info_charp)
-            e_info_charp = escape_string(completion_info_charp);
+        if (completion_info_object)
+            e_info_object = escape_string(completion_info_object);
         else
-            e_info_charp = NULL;
+            e_info_object = NULL;

-        if (completion_info_charp2)
-            e_info_charp2 = escape_string(completion_info_charp2);
+        if (completion_info_schema)
+            e_info_schema = escape_string(completion_info_schema);
         else
-            e_info_charp2 = NULL;
+            e_info_schema = NULL;

         initPQExpBuffer(&query_buffer);

@@ -4827,20 +4808,44 @@ _complete_from_query(const char *simple_query,
              * already qualified or not.  schema_query gives us the pieces to
              * assemble.
              */
-            if (schemaname == NULL)
+            if (schemaname == NULL || schema_query->namespace == NULL)
             {
             /* Get unqualified names matching the input-so-far */
-            appendPQExpBuffer(&query_buffer, "SELECT %s, NULL::pg_catalog.text FROM %s WHERE ",
+            appendPQExpBufferStr(&query_buffer, "SELECT ");
+            if (schema_query->use_distinct)
+                appendPQExpBufferStr(&query_buffer, "DISTINCT ");
+            appendPQExpBuffer(&query_buffer,
+                              "%s, NULL::pg_catalog.text FROM %s",
                               schema_query->result,
                               schema_query->catname);
+            if (schema_query->infonamespace && completion_info_schema)
+                appendPQExpBufferStr(&query_buffer,
+                                     ", pg_catalog.pg_namespace ni");
+            appendPQExpBufferStr(&query_buffer, " WHERE ");
             if (schema_query->selcondition)
                 appendPQExpBuffer(&query_buffer, "%s AND ",
                                   schema_query->selcondition);
             appendPQExpBuffer(&query_buffer, "substring(%s,1,%d)='%s'",
                               schema_query->result,
                               object_length, e_objectname);
+            if (schema_query->viscondition)
             appendPQExpBuffer(&query_buffer, " AND %s",
                               schema_query->viscondition);
+            if (schema_query->infoname)
+            {
+                Assert(completion_info_object);
+                appendPQExpBuffer(&query_buffer, " AND %s = '%s'",
+                                  schema_query->infoname, e_info_object);
+                if (schema_query->infonamespace && completion_info_schema)
+                    appendPQExpBuffer(&query_buffer,
+                                      " AND %s = ni.oid AND ni.nspname = '%s'",
+                                      schema_query->infonamespace,
+                                      e_info_schema);
+                else if (schema_query->infoviscondition)
+                    appendPQExpBuffer(&query_buffer,
+                                      " AND %s",
+                                      schema_query->infoviscondition);
+            }

             /*
              * When fetching relation names, suppress system catalogs unless
@@ -4857,7 +4862,12 @@ _complete_from_query(const char *simple_query,
                                      " pg_catalog.pg_namespace WHERE nspname = 'pg_catalog')");
             }

-            /* Add in schema names matching the input-so-far */
+            /*
+             * If the target object type can be schema-qualified, add in
+             * schema names matching the input-so-far.
+             */
+            if (schema_query->namespace)
+            {
             appendPQExpBuffer(&query_buffer, "\nUNION ALL\n"
                               "SELECT NULL::pg_catalog.text, n.nspname "
                               "FROM pg_catalog.pg_namespace n "
@@ -4878,14 +4888,21 @@ _complete_from_query(const char *simple_query,
              */
             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 ",
+            appendPQExpBufferStr(&query_buffer, "SELECT ");
+            if (schema_query->use_distinct)
+                appendPQExpBufferStr(&query_buffer, "DISTINCT ");
+            appendPQExpBuffer(&query_buffer, "%s, n.nspname "
+                              "FROM %s, pg_catalog.pg_namespace n",
                               schema_query->result,
-                              schema_query->catname,
+                              schema_query->catname);
+            if (schema_query->infonamespace && completion_info_schema)
+                appendPQExpBufferStr(&query_buffer,
+                                     ", pg_catalog.pg_namespace ni");
+            appendPQExpBuffer(&query_buffer, " WHERE %s = n.oid AND ",
                               schema_query->namespace);
             if (schema_query->selcondition)
                 appendPQExpBuffer(&query_buffer, "%s AND ",
@@ -4895,6 +4912,21 @@ _complete_from_query(const char *simple_query,
                               object_length, e_objectname);
             appendPQExpBuffer(&query_buffer, "n.nspname = '%s'",
                               e_schemaname);
+            if (schema_query->infoname)
+            {
+                Assert(completion_info_object);
+                appendPQExpBuffer(&query_buffer, " AND %s = '%s'",
+                                  schema_query->infoname, e_info_object);
+                if (schema_query->infonamespace && completion_info_schema)
+                    appendPQExpBuffer(&query_buffer,
+                                      " AND %s = ni.oid AND ni.nspname = '%s'",
+                                      schema_query->infonamespace,
+                                      e_info_schema);
+                else if (schema_query->infoviscondition)
+                    appendPQExpBuffer(&query_buffer,
+                                      " AND %s",
+                                      schema_query->infoviscondition);
+            }
             }
         }
         else
@@ -4903,8 +4935,7 @@ _complete_from_query(const char *simple_query,
             /* simple_query is an sprintf-style format string */
             appendPQExpBuffer(&query_buffer, simple_query,
                               object_length, e_objectname,
-                              e_info_charp, e_info_charp,
-                              e_info_charp2, e_info_charp2);
+                              e_info_object, e_info_schema);
         }

         /* Limit the number of records in the result */
@@ -4919,10 +4950,10 @@ _complete_from_query(const char *simple_query,
         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);
+        if (e_info_object)
+            free(e_info_object);
+        if (e_info_schema)
+            free(e_info_schema);
     }

     /* Return the next result, if any, but not if the query failed */
@@ -5031,6 +5062,23 @@ _complete_from_query(const char *simple_query,
 }


+/*
+ * Set up completion_info_object and completion_info_schema
+ * by parsing the given word.  These variables can then be
+ * used in a query passed to _complete_from_query.
+ */
+static void
+setup_completion_info(const char *word)
+{
+    bool        schemaquoted,
+                objectquoted;
+
+    parse_identifier(word,
+                     &completion_info_schema, &completion_info_object,
+                     &schemaquoted, &objectquoted);
+}
+
+
 /*
  * This function returns in order one of a fixed, NULL pointer terminated list
  * of strings (if matching). This can be used if there are only a fixed number
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 5658ec3759..01441f9bf4 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -4810,123 +4810,125 @@ _complete_from_query(const char *simple_query,
              */
             if (schemaname == NULL || schema_query->namespace == NULL)
             {
-            /* Get unqualified names matching the input-so-far */
-            appendPQExpBufferStr(&query_buffer, "SELECT ");
-            if (schema_query->use_distinct)
-                appendPQExpBufferStr(&query_buffer, "DISTINCT ");
-            appendPQExpBuffer(&query_buffer,
-                              "%s, NULL::pg_catalog.text FROM %s",
-                              schema_query->result,
-                              schema_query->catname);
-            if (schema_query->infonamespace && completion_info_schema)
-                appendPQExpBufferStr(&query_buffer,
-                                     ", pg_catalog.pg_namespace ni");
-            appendPQExpBufferStr(&query_buffer, " WHERE ");
-            if (schema_query->selcondition)
-                appendPQExpBuffer(&query_buffer, "%s AND ",
-                                  schema_query->selcondition);
-            appendPQExpBuffer(&query_buffer, "substring(%s,1,%d)='%s'",
-                              schema_query->result,
-                              object_length, e_objectname);
-            if (schema_query->viscondition)
-            appendPQExpBuffer(&query_buffer, " AND %s",
-                              schema_query->viscondition);
-            if (schema_query->infoname)
-            {
-                Assert(completion_info_object);
-                appendPQExpBuffer(&query_buffer, " AND %s = '%s'",
-                                  schema_query->infoname, e_info_object);
+                /* Get unqualified names matching the input-so-far */
+                appendPQExpBufferStr(&query_buffer, "SELECT ");
+                if (schema_query->use_distinct)
+                    appendPQExpBufferStr(&query_buffer, "DISTINCT ");
+                appendPQExpBuffer(&query_buffer,
+                                  "%s, NULL::pg_catalog.text FROM %s",
+                                  schema_query->result,
+                                  schema_query->catname);
                 if (schema_query->infonamespace && completion_info_schema)
-                    appendPQExpBuffer(&query_buffer,
-                                      " AND %s = ni.oid AND ni.nspname = '%s'",
-                                      schema_query->infonamespace,
-                                      e_info_schema);
-                else if (schema_query->infoviscondition)
-                    appendPQExpBuffer(&query_buffer,
-                                      " AND %s",
-                                      schema_query->infoviscondition);
-            }
-
-            /*
-             * When fetching relation names, suppress system catalogs unless
-             * the input-so-far begins with "pg_".  This is a compromise
-             * between not offering system catalogs for completion at all, and
-             * having them swamp the result when the input is just "p".
-             */
-            if (strcmp(schema_query->catname,
-                       "pg_catalog.pg_class c") == 0 &&
-                strncmp(objectname, "pg_", 3) != 0)
-            {
-                appendPQExpBufferStr(&query_buffer,
-                                     " AND c.relnamespace <> (SELECT oid FROM"
-                                     " pg_catalog.pg_namespace WHERE nspname = 'pg_catalog')");
-            }
-
-            /*
-             * If the target object type can be schema-qualified, add in
-             * schema names matching the input-so-far.
-             */
-            if (schema_query->namespace)
-            {
-            appendPQExpBuffer(&query_buffer, "\nUNION ALL\n"
-                              "SELECT NULL::pg_catalog.text, n.nspname "
-                              "FROM pg_catalog.pg_namespace n "
-                              "WHERE substring(n.nspname,1,%d)='%s'",
-                              object_length, e_objectname);
+                    appendPQExpBufferStr(&query_buffer,
+                                         ", pg_catalog.pg_namespace ni");
+                appendPQExpBufferStr(&query_buffer, " WHERE ");
+                if (schema_query->selcondition)
+                    appendPQExpBuffer(&query_buffer, "%s AND ",
+                                      schema_query->selcondition);
+                appendPQExpBuffer(&query_buffer, "substring(%s,1,%d)='%s'",
+                                  schema_query->result,
+                                  object_length, e_objectname);
+                if (schema_query->viscondition)
+                    appendPQExpBuffer(&query_buffer, " AND %s",
+                                      schema_query->viscondition);
+                if (schema_query->infoname)
+                {
+                    Assert(completion_info_object);
+                    appendPQExpBuffer(&query_buffer, " AND %s = '%s'",
+                                      schema_query->infoname, e_info_object);
+                    if (schema_query->infonamespace && completion_info_schema)
+                        appendPQExpBuffer(&query_buffer,
+                                          " AND %s = ni.oid AND ni.nspname = '%s'",
+                                          schema_query->infonamespace,
+                                          e_info_schema);
+                    else if (schema_query->infoviscondition)
+                        appendPQExpBuffer(&query_buffer,
+                                          " AND %s",
+                                          schema_query->infoviscondition);
+                }
 
-            /*
-             * Likewise, suppress system schemas unless the input-so-far
-             * begins with "pg_".
-             */
-            if (strncmp(objectname, "pg_", 3) != 0)
-                appendPQExpBufferStr(&query_buffer,
-                                     " AND n.nspname NOT LIKE 'pg\\_%'");
+                /*
+                 * When fetching relation names, suppress system catalogs
+                 * unless the input-so-far begins with "pg_".  This is a
+                 * compromise between not offering system catalogs for
+                 * completion at all, and having them swamp the result when
+                 * the input is just "p".
+                 */
+                if (strcmp(schema_query->catname,
+                           "pg_catalog.pg_class c") == 0 &&
+                    strncmp(objectname, "pg_", 3) != 0)
+                {
+                    appendPQExpBufferStr(&query_buffer,
+                                         " AND c.relnamespace <> (SELECT oid FROM"
+                                         " pg_catalog.pg_namespace WHERE nspname = 'pg_catalog')");
+                }
 
-            /*
-             * Since we're matching these schema names to the object name,
-             * handle their quoting using the object name's quoting state.
-             */
-            schemaquoted = objectquoted;
-            }
+                /*
+                 * If the target object type can be schema-qualified, add in
+                 * schema names matching the input-so-far.
+                 */
+                if (schema_query->namespace)
+                {
+                    appendPQExpBuffer(&query_buffer, "\nUNION ALL\n"
+                                      "SELECT NULL::pg_catalog.text, n.nspname "
+                                      "FROM pg_catalog.pg_namespace n "
+                                      "WHERE substring(n.nspname,1,%d)='%s'",
+                                      object_length, e_objectname);
+
+                    /*
+                     * Likewise, suppress system schemas unless the
+                     * input-so-far begins with "pg_".
+                     */
+                    if (strncmp(objectname, "pg_", 3) != 0)
+                        appendPQExpBufferStr(&query_buffer,
+                                             " AND n.nspname NOT LIKE 'pg\\_%'");
+
+                    /*
+                     * Since we're matching these schema names to the object
+                     * name, handle their quoting using the object name's
+                     * quoting state.
+                     */
+                    schemaquoted = objectquoted;
+                }
             }
             else
             {
-            /* Input is qualified, so produce only qualified names */
-            appendPQExpBufferStr(&query_buffer, "SELECT ");
-            if (schema_query->use_distinct)
-                appendPQExpBufferStr(&query_buffer, "DISTINCT ");
-            appendPQExpBuffer(&query_buffer, "%s, n.nspname "
-                              "FROM %s, pg_catalog.pg_namespace n",
-                              schema_query->result,
-                              schema_query->catname);
-            if (schema_query->infonamespace && completion_info_schema)
-                appendPQExpBufferStr(&query_buffer,
-                                     ", pg_catalog.pg_namespace ni");
-            appendPQExpBuffer(&query_buffer, " WHERE %s = n.oid AND ",
-                              schema_query->namespace);
-            if (schema_query->selcondition)
-                appendPQExpBuffer(&query_buffer, "%s AND ",
-                                  schema_query->selcondition);
-            appendPQExpBuffer(&query_buffer, "substring(%s,1,%d)='%s' AND ",
-                              schema_query->result,
-                              object_length, e_objectname);
-            appendPQExpBuffer(&query_buffer, "n.nspname = '%s'",
-                              e_schemaname);
-            if (schema_query->infoname)
-            {
-                Assert(completion_info_object);
-                appendPQExpBuffer(&query_buffer, " AND %s = '%s'",
-                                  schema_query->infoname, e_info_object);
+                /* Input is qualified, so produce only qualified names */
+                appendPQExpBufferStr(&query_buffer, "SELECT ");
+                if (schema_query->use_distinct)
+                    appendPQExpBufferStr(&query_buffer, "DISTINCT ");
+                appendPQExpBuffer(&query_buffer, "%s, n.nspname "
+                                  "FROM %s, pg_catalog.pg_namespace n",
+                                  schema_query->result,
+                                  schema_query->catname);
                 if (schema_query->infonamespace && completion_info_schema)
-                    appendPQExpBuffer(&query_buffer,
-                                      " AND %s = ni.oid AND ni.nspname = '%s'",
-                                      schema_query->infonamespace,
-                                      e_info_schema);
-                else if (schema_query->infoviscondition)
-                    appendPQExpBuffer(&query_buffer,
-                                      " AND %s",
-                                      schema_query->infoviscondition);
-            }
+                    appendPQExpBufferStr(&query_buffer,
+                                         ", pg_catalog.pg_namespace ni");
+                appendPQExpBuffer(&query_buffer, " WHERE %s = n.oid AND ",
+                                  schema_query->namespace);
+                if (schema_query->selcondition)
+                    appendPQExpBuffer(&query_buffer, "%s AND ",
+                                      schema_query->selcondition);
+                appendPQExpBuffer(&query_buffer, "substring(%s,1,%d)='%s' AND ",
+                                  schema_query->result,
+                                  object_length, e_objectname);
+                appendPQExpBuffer(&query_buffer, "n.nspname = '%s'",
+                                  e_schemaname);
+                if (schema_query->infoname)
+                {
+                    Assert(completion_info_object);
+                    appendPQExpBuffer(&query_buffer, " AND %s = '%s'",
+                                      schema_query->infoname, e_info_object);
+                    if (schema_query->infonamespace && completion_info_schema)
+                        appendPQExpBuffer(&query_buffer,
+                                          " AND %s = ni.oid AND ni.nspname = '%s'",
+                                          schema_query->infonamespace,
+                                          e_info_schema);
+                    else if (schema_query->infoviscondition)
+                        appendPQExpBuffer(&query_buffer,
+                                          " AND %s",
+                                          schema_query->infoviscondition);
+                }
             }
         }
         else
diff --git a/src/bin/psql/t/010_tab_completion.pl b/src/bin/psql/t/010_tab_completion.pl
index d3d1bd650e..8fda7c7f40 100644
--- a/src/bin/psql/t/010_tab_completion.pl
+++ b/src/bin/psql/t/010_tab_completion.pl
@@ -40,10 +40,11 @@ $node->start;

 # set up a few database objects
 $node->safe_psql('postgres',
-        "CREATE TABLE tab1 (f1 int, f2 text);\n"
+        "CREATE TABLE tab1 (f1 int primary key, f2 text);\n"
       . "CREATE TABLE mytab123 (f1 int, f2 text);\n"
       . "CREATE TABLE mytab246 (f1 int, f2 text);\n"
-      . "CREATE TYPE enum1 AS ENUM ('foo', 'bar', 'baz');\n");
+      . "CREATE TABLE \"mixedName\" (f1 int, f2 text);\n"
+      . "CREATE TYPE enum1 AS ENUM ('foo', 'bar', 'baz', 'BLACK');\n");

 # Developers would not appreciate this test adding a bunch of junk to
 # their ~/.psql_history, so be sure to redirect history into a temp file.
@@ -176,6 +177,38 @@ check_completion("2\t", qr/246 /,

 clear_query();

+# check handling of quoted names
+check_completion(
+    "select * from \"my\t",
+    qr/select \* from "my\a?tab/,
+    "complete \"my<tab> to \"mytab when there are multiple choices");
+
+check_completion(
+    "\t\t",
+    qr/"mytab123" +"mytab246"/,
+    "offer multiple quoted table choices");
+
+check_completion("2\t", qr/246" /,
+    "finish completion of one of multiple quoted table choices");
+
+clear_query();
+
+# check handling of mixed-case names
+check_completion(
+    "select * from \"mi\t",
+    qr/"mixedName"/,
+    "complete a mixed-case name");
+
+clear_query();
+
+# check case folding
+check_completion(
+    "select * from TAB\t",
+    qr/tab1 /,
+    "automatically fold case");
+
+clear_query();
+
 # check case-sensitive keyword replacement
 # note: various versions of readline/libedit handle backspacing
 # differently, so just check that the replacement comes out correctly
@@ -183,6 +216,48 @@ check_completion("\\DRD\t", qr/drds /, "complete \\DRD<tab> to \\drds");

 clear_query();

+# check completion of a schema-qualified name
+check_completion(
+    "select * from pub\t",
+    qr/public\./,
+    "complete schema when relevant");
+
+check_completion(
+    "tab\t",
+    qr/tab1 /,
+    "complete schema-qualified name");
+
+clear_query();
+
+check_completion(
+    "select * from PUBLIC.t\t",
+    qr/public\.tab1 /,
+    "automatically fold case in schema-qualified name");
+
+clear_query();
+
+# check interpretation of referenced names
+check_completion(
+    "alter table tab1 drop constraint \t",
+    qr/tab1_pkey /,
+    "complete index name for referenced table");
+
+clear_query();
+
+check_completion(
+    "alter table TAB1 drop constraint \t",
+    qr/tab1_pkey /,
+    "complete index name for referenced table, with downcasing");
+
+clear_query();
+
+check_completion(
+    "alter table public.\"tab1\" drop constraint \t",
+    qr/tab1_pkey /,
+    "complete index name for referenced table, with schema and quoting");
+
+clear_query();
+
 # check filename completion
 check_completion(
     "\\lo_import tmp_check/some\t",
@@ -234,6 +309,14 @@ check_completion(

 clear_line();

+# enum labels are case sensitive, so this should complete BLACK immediately
+check_completion(
+    "ALTER TYPE enum1 RENAME VALUE 'B\t",
+    qr|BLACK|,
+    "enum labels are case sensitive");
+
+clear_line();
+
 # send psql an explicit \q to shut it down, else pty won't close properly
 $timer->start(5);
 $in .= "\\q\n";
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 01441f9bf4..f7efa4b947 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -896,10 +896,9 @@ static const SchemaQuery Query_for_trigger_of_table = {
  * name; make a SchemaQuery instead.
  *
  * In these queries, there must be 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.
+ *        output LIKE '%s'
+ * where "output" is the same string that the query returns.  The %s
+ * will be replaced by a LIKE pattern to match the already-typed text.
  *
  * There can be a second '%s', which will be replaced by a suitably-escaped
  * version of the string provided in completion_info_object.  If there is a
@@ -912,56 +911,56 @@ static const SchemaQuery Query_for_trigger_of_table = {
 #define Query_for_list_of_template_databases \
 "SELECT d.datname "\
 "  FROM pg_catalog.pg_database d "\
-" WHERE substring(d.datname,1,%d)='%s' "\
+" WHERE d.datname LIKE '%s' "\
 "   AND (d.datistemplate OR pg_catalog.pg_has_role(d.datdba, 'USAGE'))"

 #define Query_for_list_of_databases \
 "SELECT datname FROM pg_catalog.pg_database "\
-" WHERE substring(datname,1,%d)='%s'"
+" WHERE datname LIKE '%s'"

 #define Query_for_list_of_tablespaces \
 "SELECT spcname FROM pg_catalog.pg_tablespace "\
-" WHERE substring(spcname,1,%d)='%s'"
+" WHERE spcname LIKE '%s'"

 #define Query_for_list_of_encodings \
 " SELECT DISTINCT pg_catalog.pg_encoding_to_char(conforencoding) "\
 "   FROM pg_catalog.pg_conversion "\
-"  WHERE substring(pg_catalog.pg_encoding_to_char(conforencoding),1,%d)=UPPER('%s')"
+"  WHERE pg_catalog.pg_encoding_to_char(conforencoding) LIKE UPPER('%s')"

 #define Query_for_list_of_languages \
 "SELECT lanname "\
 "  FROM pg_catalog.pg_language "\
 " WHERE lanname != 'internal' "\
-"   AND substring(lanname,1,%d)='%s'"
+"   AND lanname LIKE '%s'"

 #define Query_for_list_of_schemas \
 "SELECT nspname FROM pg_catalog.pg_namespace "\
-" WHERE substring(nspname,1,%d)='%s'"
+" WHERE nspname LIKE '%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' "\
 " ) ss "\
-" WHERE substring(name,1,%d)='%s'"
+" WHERE name LIKE '%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') "\
 " ) ss "\
-" WHERE substring(name,1,%d)='%s'"
+" WHERE name LIKE '%s'"

 #define Query_for_list_of_show_vars \
 "SELECT name FROM "\
 " (SELECT pg_catalog.lower(name) AS name FROM pg_catalog.pg_settings "\
 " ) ss "\
-" WHERE substring(name,1,%d)='%s'"
+" WHERE name LIKE '%s'"

 #define Query_for_list_of_roles \
 " SELECT rolname "\
 "   FROM pg_catalog.pg_roles "\
-"  WHERE substring(rolname,1,%d)='%s'"
+"  WHERE rolname LIKE '%s'"

 /* add these to Query_for_list_of_roles in GRANT contexts */
 #define Keywords_for_list_of_grant_roles \
@@ -971,119 +970,119 @@ static const SchemaQuery Query_for_trigger_of_table = {
 "SELECT conname "\
 "  FROM pg_catalog.pg_constraint c "\
 " WHERE c.conrelid <> 0 "\
-"       and substring(conname,1,%d)='%s'"
+"       and conname LIKE '%s'"

 #define Query_for_list_of_ts_configurations \
 "SELECT cfgname FROM pg_catalog.pg_ts_config "\
-" WHERE substring(cfgname,1,%d)='%s'"
+" WHERE cfgname LIKE '%s'"

 #define Query_for_list_of_ts_dictionaries \
 "SELECT dictname FROM pg_catalog.pg_ts_dict "\
-" WHERE substring(dictname,1,%d)='%s'"
+" WHERE dictname LIKE '%s'"

 #define Query_for_list_of_ts_parsers \
 "SELECT prsname FROM pg_catalog.pg_ts_parser "\
-" WHERE substring(prsname,1,%d)='%s'"
+" WHERE prsname LIKE '%s'"

 #define Query_for_list_of_ts_templates \
 "SELECT tmplname FROM pg_catalog.pg_ts_template "\
-" WHERE substring(tmplname,1,%d)='%s'"
+" WHERE tmplname LIKE '%s'"

 #define Query_for_list_of_fdws \
 " SELECT fdwname "\
 "   FROM pg_catalog.pg_foreign_data_wrapper "\
-"  WHERE substring(fdwname,1,%d)='%s'"
+"  WHERE fdwname LIKE '%s'"

 #define Query_for_list_of_servers \
 " SELECT srvname "\
 "   FROM pg_catalog.pg_foreign_server "\
-"  WHERE substring(srvname,1,%d)='%s'"
+"  WHERE srvname LIKE '%s'"

 #define Query_for_list_of_user_mappings \
 " SELECT usename "\
 "   FROM pg_catalog.pg_user_mappings "\
-"  WHERE substring(usename,1,%d)='%s'"
+"  WHERE usename LIKE '%s'"

 #define Query_for_list_of_access_methods \
 " SELECT amname "\
 "   FROM pg_catalog.pg_am "\
-"  WHERE substring(amname,1,%d)='%s'"
+"  WHERE amname LIKE '%s'"

 #define Query_for_list_of_index_access_methods \
 " SELECT amname "\
 "   FROM pg_catalog.pg_am "\
-"  WHERE substring(amname,1,%d)='%s' AND "\
+"  WHERE amname LIKE '%s' AND "\
 "   amtype=" CppAsString2(AMTYPE_INDEX)

 #define Query_for_list_of_table_access_methods \
 " SELECT amname "\
 "   FROM pg_catalog.pg_am "\
-"  WHERE substring(amname,1,%d)='%s' AND "\
+"  WHERE amname LIKE '%s' AND "\
 "   amtype=" CppAsString2(AMTYPE_TABLE)

 #define Query_for_list_of_extensions \
 " SELECT extname "\
 "   FROM pg_catalog.pg_extension "\
-"  WHERE substring(extname,1,%d)='%s'"
+"  WHERE extname LIKE '%s'"

 #define Query_for_list_of_available_extensions \
 " SELECT name "\
 "   FROM pg_catalog.pg_available_extensions "\
-"  WHERE substring(name,1,%d)='%s' AND installed_version IS NULL"
+"  WHERE name LIKE '%s' AND installed_version IS NULL"

 /* the result of this query is not an identifier, so use VERBATIM */
 #define Query_for_list_of_available_extension_versions \
 " SELECT version "\
 "   FROM pg_catalog.pg_available_extension_versions "\
-"  WHERE substring(version,1,%d)='%s'"\
+"  WHERE version LIKE '%s'"\
 "    AND name='%s'"

 /* the result of this query is not an identifier, so use VERBATIM */
 #define Query_for_list_of_available_extension_versions_with_TO \
 " SELECT 'TO ' || version "\
 "   FROM pg_catalog.pg_available_extension_versions "\
-"  WHERE substring('TO ' || version,1,%d)='%s'"\
+"  WHERE ('TO ' || version) LIKE '%s'"\
 "    AND name='%s'"

 #define Query_for_list_of_prepared_statements \
 " SELECT name "\
 "   FROM pg_catalog.pg_prepared_statements "\
-"  WHERE substring(name,1,%d)='%s'"
+"  WHERE name LIKE '%s'"

 #define Query_for_list_of_event_triggers \
 " SELECT evtname "\
 "   FROM pg_catalog.pg_event_trigger "\
-"  WHERE substring(evtname,1,%d)='%s'"
+"  WHERE evtname LIKE '%s'"

 #define Query_for_list_of_tablesample_methods \
 " 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(proname,1,%d)='%s'"
+"        proname LIKE '%s'"

 #define Query_for_list_of_policies \
 " SELECT polname "\
 "   FROM pg_catalog.pg_policy "\
-"  WHERE substring(polname,1,%d)='%s'"
+"  WHERE polname LIKE '%s'"

 #define Query_for_values_of_enum_GUC \
 " SELECT val FROM ( "\
 "   SELECT name, pg_catalog.unnest(enumvals) AS val "\
 "     FROM pg_catalog.pg_settings "\
 "    ) ss "\
-"  WHERE substring(val,1,%d)='%s'"\
+"  WHERE val LIKE '%s'"\
 "        and pg_catalog.lower(name)=pg_catalog.lower('%s')"

 #define Query_for_list_of_channels \
 " SELECT channel "\
 "   FROM pg_catalog.pg_listening_channels() AS channel "\
-"  WHERE substring(channel,1,%d)='%s'"
+"  WHERE channel LIKE '%s'"

 #define Query_for_list_of_cursors \
 " SELECT name "\
 "   FROM pg_catalog.pg_cursors "\
-"  WHERE substring(name,1,%d)='%s'"
+"  WHERE name LIKE '%s'"

 /*
  * These object types were introduced later than our support cutoff of
@@ -1095,7 +1094,7 @@ static const VersionedQuery Query_for_list_of_publications[] = {
     {100000,
         " SELECT pubname "
         "   FROM pg_catalog.pg_publication "
-        "  WHERE substring(pubname,1,%d)='%s'"
+        "  WHERE pubname LIKE '%s'"
     },
     {0, NULL}
 };
@@ -1104,7 +1103,7 @@ static const VersionedQuery Query_for_list_of_subscriptions[] = {
     {100000,
         " SELECT s.subname "
         "   FROM pg_catalog.pg_subscription s, pg_catalog.pg_database d "
-        "  WHERE substring(s.subname,1,%d)='%s' "
+        "  WHERE s.subname LIKE '%s' "
         "    AND d.datname = pg_catalog.current_database() "
         "    AND s.subdbid = d.oid"
     },
@@ -1150,7 +1149,7 @@ static const pgsql_thing_t words_after_create[] = {
      * to be used only by pg_dump.
      */
     {"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'"},
+    {"CONVERSION", "SELECT conname FROM pg_catalog.pg_conversion WHERE conname LIKE '%s'"},
     {"DATABASE", Query_for_list_of_databases},
     {"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},
@@ -1175,7 +1174,7 @@ static const pgsql_thing_t words_after_create[] = {
     {"PUBLICATION", NULL, Query_for_list_of_publications},
     {"ROLE", Query_for_list_of_roles},
     {"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'"},
+    {"RULE", "SELECT rulename FROM pg_catalog.pg_rules WHERE rulename LIKE '%s'"},
     {"SCHEMA", Query_for_list_of_schemas},
     {"SEQUENCE", NULL, NULL, &Query_for_list_of_sequences},
     {"SERVER", Query_for_list_of_servers},
@@ -1191,7 +1190,7 @@ static const pgsql_thing_t words_after_create[] = {
                                                                              * TABLE ... */
     {"TEXT SEARCH", NULL, NULL, NULL},
     {"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"},
+    {"TRIGGER", "SELECT tgname FROM pg_catalog.pg_trigger WHERE tgname LIKE '%s' AND NOT tgisinternal"},
     {"TYPE", NULL, NULL, &Query_for_list_of_datatypes},
     {"UNIQUE", NULL, NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE UNIQUE
                                                                          * INDEX ... */
@@ -1273,6 +1272,7 @@ 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 char *make_like_pattern(const char *word);
 static void parse_identifier(const char *ident,
                              char **schemaname, char **objectname,
                              bool *schemaquoted, bool *objectquoted);
@@ -1769,9 +1769,7 @@ psql_completion(const char *text, int start, int end)
         COMPLETE_WITH("(", "ALL TABLES IN SCHEMA", "TABLE");
     else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD|DROP|SET", "ALL", "TABLES", "IN", "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%%' ",
+                                 " AND nspname NOT LIKE E'pg\\\\_%'",
                                  "CURRENT_SCHEMA");
     /* ALTER PUBLICATION <name> SET ( */
     else if (HeadMatches("ALTER", "PUBLICATION", MatchAny) && TailMatches("SET", "("))
@@ -2905,9 +2903,7 @@ psql_completion(const char *text, int start, int end)
      */
     else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES", "IN", "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%%' ",
+                                 " AND nspname NOT LIKE E'pg\\\\_%'",
                                  "CURRENT_SCHEMA");
     else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES", "IN", "SCHEMA", MatchAny) &&
(!ends_with(prev_wd,','))) 
         COMPLETE_WITH("WITH (");
@@ -4113,10 +4109,13 @@ psql_completion(const char *text, int start, int end)
                           "US", "European", "NonEuropean",
                           "DEFAULT");
         else if (TailMatches("search_path", "TO|="))
+        {
+            /* Here, we want to allow pg_catalog, so use narrower exclusion */
             COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_schemas
-                                     " AND nspname not like 'pg\\_toast%%' "
-                                     " AND nspname not like 'pg\\_temp%%' ",
+                                     " AND nspname NOT LIKE E'pg\\\\_toast%%'"
+                                     " AND nspname NOT LIKE E'pg\\\\_temp%%'",
                                      "DEFAULT");
+        }
         else
         {
             /* generic, type based, GUC support */
@@ -4683,10 +4682,9 @@ 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 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.
+ *        output LIKE '%s'
+ * where "output" is the same string that the query returns.  The %s
+ * will be replaced by a LIKE pattern to match the already-typed text.
  * There can be a second '%s', which will be replaced by a suitably-escaped
  * version of the string provided in completion_info_object.  If there is a
  * third '%s', it will be replaced by a suitably-escaped version of the string
@@ -4741,9 +4739,8 @@ _complete_from_query(const char *simple_query,
         PQExpBufferData query_buffer;
         char       *schemaname;
         char       *objectname;
-        int            object_length = 0;
+        char       *e_object_like;
         char       *e_schemaname;
-        char       *e_objectname;
         char       *e_info_object;
         char       *e_info_schema;

@@ -4771,22 +4768,16 @@ _complete_from_query(const char *simple_query,
         non_empty_object = (*objectname != '\0');

         /*
-         * Count length as number of characters (not bytes), for passing to
-         * substring
+         * Convert objectname to a LIKE prefix pattern (e.g. 'foo%'), and set
+         * up suitably-escaped copies of all the strings we need.
          */
-        for (const char *p = objectname;
-             *p;
-             p += PQmblenBounded(p, pset.encoding))
-            object_length++;
+        e_object_like = make_like_pattern(objectname);

-        /* Set up suitably-escaped copies of textual inputs */
         if (schemaname)
             e_schemaname = escape_string(schemaname);
         else
             e_schemaname = NULL;

-        e_objectname = escape_string(objectname);
-
         if (completion_info_object)
             e_info_object = escape_string(completion_info_object);
         else
@@ -4825,9 +4816,9 @@ _complete_from_query(const char *simple_query,
                 if (schema_query->selcondition)
                     appendPQExpBuffer(&query_buffer, "%s AND ",
                                       schema_query->selcondition);
-                appendPQExpBuffer(&query_buffer, "substring(%s,1,%d)='%s'",
+                appendPQExpBuffer(&query_buffer, "(%s) LIKE '%s'",
                                   schema_query->result,
-                                  object_length, e_objectname);
+                                  e_object_like);
                 if (schema_query->viscondition)
                     appendPQExpBuffer(&query_buffer, " AND %s",
                                       schema_query->viscondition);
@@ -4872,8 +4863,8 @@ _complete_from_query(const char *simple_query,
                     appendPQExpBuffer(&query_buffer, "\nUNION ALL\n"
                                       "SELECT NULL::pg_catalog.text, n.nspname "
                                       "FROM pg_catalog.pg_namespace n "
-                                      "WHERE substring(n.nspname,1,%d)='%s'",
-                                      object_length, e_objectname);
+                                      "WHERE n.nspname LIKE '%s'",
+                                      e_object_like);

                     /*
                      * Likewise, suppress system schemas unless the
@@ -4881,7 +4872,7 @@ _complete_from_query(const char *simple_query,
                      */
                     if (strncmp(objectname, "pg_", 3) != 0)
                         appendPQExpBufferStr(&query_buffer,
-                                             " AND n.nspname NOT LIKE 'pg\\_%'");
+                                             " AND n.nspname NOT LIKE E'pg\\\\_%'");

                     /*
                      * Since we're matching these schema names to the object
@@ -4909,9 +4900,9 @@ _complete_from_query(const char *simple_query,
                 if (schema_query->selcondition)
                     appendPQExpBuffer(&query_buffer, "%s AND ",
                                       schema_query->selcondition);
-                appendPQExpBuffer(&query_buffer, "substring(%s,1,%d)='%s' AND ",
+                appendPQExpBuffer(&query_buffer, "(%s) LIKE '%s' AND ",
                                   schema_query->result,
-                                  object_length, e_objectname);
+                                  e_object_like);
                 appendPQExpBuffer(&query_buffer, "n.nspname = '%s'",
                                   e_schemaname);
                 if (schema_query->infoname)
@@ -4936,7 +4927,7 @@ _complete_from_query(const char *simple_query,
             Assert(simple_query);
             /* simple_query is an sprintf-style format string */
             appendPQExpBuffer(&query_buffer, simple_query,
-                              object_length, e_objectname,
+                              e_object_like,
                               e_info_object, e_info_schema);
         }

@@ -4949,9 +4940,9 @@ _complete_from_query(const char *simple_query,

         /* Clean up */
         termPQExpBuffer(&query_buffer);
+        free(e_object_like);
         if (e_schemaname)
             free(e_schemaname);
-        free(e_objectname);
         if (e_info_object)
             free(e_info_object);
         if (e_info_schema)
@@ -5408,6 +5399,48 @@ escape_string(const char *text)
 }


+/*
+ * make_like_pattern - Convert argument to a LIKE prefix pattern.
+ *
+ * We escape _ and % in the given text by backslashing, append a % to
+ * represent "any subsequent characters", and then pass the string through
+ * escape_string() so it's ready to insert in a query.  The result needs
+ * to be freed.
+ */
+static char *
+make_like_pattern(const char *word)
+{
+    char       *result;
+    char       *buffer = pg_malloc(strlen(word) * 2 + 2);
+    char       *bptr = buffer;
+
+    while (*word)
+    {
+        if (*word == '_' || *word == '%')
+            *bptr++ = '\\';
+        if (IS_HIGHBIT_SET(*word))
+        {
+            /*
+             * Transfer multibyte characters without further processing, to
+             * avoid getting confused in unsafe client encodings.
+             */
+            int            chlen = PQmblenBounded(word, pset.encoding);
+
+            while (chlen-- > 0)
+                *bptr++ = *word++;
+        }
+        else
+            *bptr++ = *word++;
+    }
+    *bptr++ = '%';
+    *bptr = '\0';
+
+    result = escape_string(buffer);
+    free(buffer);
+    return result;
+}
+
+
 /*
  * parse_identifier - Parse a possibly-schema-qualified SQL identifier.
  *

pgsql-hackers by date:

Previous
From: Alvaro Herrera
Date:
Subject: Re: support for MERGE
Next
From: Tom Lane
Date:
Subject: Re: Support tab completion for upper character inputs in psql