]> granicus.if.org Git - postgresql/commitdiff
Add infrastructure to support server-version-dependent tab completion.
authorTom Lane <tgl@sss.pgh.pa.us>
Mon, 5 Mar 2018 20:37:23 +0000 (15:37 -0500)
committerTom Lane <tgl@sss.pgh.pa.us>
Mon, 5 Mar 2018 20:37:34 +0000 (15:37 -0500)
Up to now we've not worried about whether psql's tab completion queries
would work against prior server versions.  But since we support older
server versions in describe.c, we really ought to do so here as well.
Failing to take care of this not only leads to loss of tab-completion
service when using an older server, but risks aborting a user's open
transaction when we send an incompatible query to the server.

The recent changes in pg_proc.prokind are one reason to take this more
seriously now than before, and the proposed patch for completion after
SELECT needs some such capability as well.

Hence, create some infrastructure to allow selection of one of several
versions of a query depending on server version.  For cases where we
just need to select one of several query strings, introduce VersionedQuery
and COMPLETE_WITH_VERSIONED_QUERY().  Likewise extend the SchemaQuery
infrastructure to allow versioning of those.

I went ahead and fixed the prokind issues, to serve as an illustration
of how to use versioned SchemaQuery.  To have some illustration of
VersionedQuery, change the support for completion of publication and
subscription names so that psql will not send sure-to-fail queries to
pre-v10 servers.  There is much more that should be done to make tab
completion more friendly to older servers, but this commit is mainly
meant to get the infrastructure in place, so I stopped here.

Discussion: https://postgr.es/m/24314.1520190408@sss.pgh.pa.us

src/bin/psql/tab-complete.c

index 47909ed4d92720634bc283c6ac1f14c498ef7bd7..340febedb97b54e1fc02eff7da7990d65f6e771f 100644 (file)
@@ -70,15 +70,43 @@ extern char *filename_completion_function();
  */
 PQExpBuffer tab_completion_query_buf = NULL;
 
+/*
+ * In some situations, the query to find out what names are available to
+ * complete with must vary depending on server version.  We handle this by
+ * storing a list of queries, each tagged with the minimum server version
+ * it will work for.  Each list must be stored in descending server version
+ * order, so that the first satisfactory query is the one to use.
+ *
+ * When the query string is otherwise constant, an array of VersionedQuery
+ * suffices.  Terminate the array with an entry having min_server_version = 0.
+ * That entry's query string can be a query that works in all supported older
+ * server versions, or NULL to give up and do no completion.
+ */
+typedef struct VersionedQuery
+{
+       int                     min_server_version;
+       const char *query;
+} VersionedQuery;
+
 /*
  * This struct is used to define "schema queries", which are custom-built
  * to obtain possibly-schema-qualified names of database objects.  There is
  * enough similarity in the structure that we don't want to repeat it each
  * time.  So we put the components of each query into this struct and
  * assemble them with the common boilerplate in _complete_from_query().
+ *
+ * As with VersionedQuery, we can use an array of these if the query details
+ * must vary across versions.
  */
 typedef struct SchemaQuery
 {
+       /*
+        * If not zero, minimum server version this struct applies to.  If not
+        * zero, there should be a following struct with a smaller minimum server
+        * version; use catname == NULL in the last entry if we should do nothing.
+        */
+       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.
@@ -133,6 +161,7 @@ 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 bool completion_case_sensitive; /* completion is case sensitive */
 
@@ -140,7 +169,9 @@ static bool completion_case_sensitive;      /* completion is case sensitive */
  * A few macros to ease typing. You can use these to complete the given
  * string with
  * 1) The results from a query you pass it. (Perhaps one of those below?)
+ *       We support both simple and versioned queries.
  * 2) The results from a schema query you pass it.
+ *       We support both simple and versioned schema queries.
  * 3) The items from a null-pointer-terminated list (with or without
  *       case-sensitive comparison; see also COMPLETE_WITH_LISTn, below).
  * 4) A string constant.
@@ -153,6 +184,12 @@ do { \
        matches = completion_matches(text, complete_from_query); \
 } while (0)
 
+#define COMPLETE_WITH_VERSIONED_QUERY(query) \
+do { \
+       completion_vquery = query; \
+       matches = completion_matches(text, complete_from_versioned_query); \
+} while (0)
+
 #define COMPLETE_WITH_SCHEMA_QUERY(query, addon) \
 do { \
        completion_squery = &(query); \
@@ -160,6 +197,13 @@ do { \
        matches = completion_matches(text, complete_from_schema_query); \
 } while (0)
 
+#define COMPLETE_WITH_VERSIONED_SCHEMA_QUERY(query, addon) \
+do { \
+       completion_squery = query; \
+       completion_vquery = addon; \
+       matches = completion_matches(text, complete_from_versioned_schema_query); \
+} while (0)
+
 #define COMPLETE_WITH_LIST_CS(list) \
 do { \
        completion_charpp = list; \
@@ -345,22 +389,44 @@ do { \
  * Assembly instructions for schema queries
  */
 
-static const SchemaQuery Query_for_list_of_aggregates = {
-       /* catname */
-       "pg_catalog.pg_proc p",
-       /* selcondition */
-       "p.prokind = 'a'",
-       /* viscondition */
-       "pg_catalog.pg_function_is_visible(p.oid)",
-       /* namespace */
-       "p.pronamespace",
-       /* result */
-       "pg_catalog.quote_ident(p.proname)",
-       /* qualresult */
-       NULL
+static const SchemaQuery Query_for_list_of_aggregates[] = {
+       {
+               /* min_server_version */
+               110000,
+               /* catname */
+               "pg_catalog.pg_proc p",
+               /* selcondition */
+               "p.prokind = 'a'",
+               /* viscondition */
+               "pg_catalog.pg_function_is_visible(p.oid)",
+               /* namespace */
+               "p.pronamespace",
+               /* result */
+               "pg_catalog.quote_ident(p.proname)",
+               /* qualresult */
+               NULL
+       },
+       {
+               /* min_server_version */
+               0,
+               /* 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)",
+               /* qualresult */
+               NULL
+       }
 };
 
 static const SchemaQuery Query_for_list_of_datatypes = {
+       /* min_server_version */
+       0,
        /* catname */
        "pg_catalog.pg_type t",
        /* selcondition --- ignore table rowtypes and array types */
@@ -379,6 +445,8 @@ static const SchemaQuery Query_for_list_of_datatypes = {
 };
 
 static const SchemaQuery Query_for_list_of_domains = {
+       /* min_server_version */
+       0,
        /* catname */
        "pg_catalog.pg_type t",
        /* selcondition */
@@ -393,22 +461,44 @@ static const SchemaQuery Query_for_list_of_domains = {
        NULL
 };
 
-static const SchemaQuery Query_for_list_of_functions = {
-       /* catname */
-       "pg_catalog.pg_proc p",
-       /* selcondition */
-       "p.prokind IN ('f', 'w')",
-       /* viscondition */
-       "pg_catalog.pg_function_is_visible(p.oid)",
-       /* namespace */
-       "p.pronamespace",
-       /* result */
-       "pg_catalog.quote_ident(p.proname)",
-       /* qualresult */
-       NULL
+static const SchemaQuery Query_for_list_of_functions[] = {
+       {
+               /* min_server_version */
+               110000,
+               /* catname */
+               "pg_catalog.pg_proc p",
+               /* selcondition */
+               "p.prokind IN ('f', 'w')",
+               /* viscondition */
+               "pg_catalog.pg_function_is_visible(p.oid)",
+               /* namespace */
+               "p.pronamespace",
+               /* result */
+               "pg_catalog.quote_ident(p.proname)",
+               /* qualresult */
+               NULL
+       },
+       {
+               /* min_server_version */
+               0,
+               /* catname */
+               "pg_catalog.pg_proc p",
+               /* selcondition */
+               NULL,
+               /* viscondition */
+               "pg_catalog.pg_function_is_visible(p.oid)",
+               /* namespace */
+               "p.pronamespace",
+               /* result */
+               "pg_catalog.quote_ident(p.proname)",
+               /* qualresult */
+               NULL
+       }
 };
 
 static const SchemaQuery Query_for_list_of_indexes = {
+       /* min_server_version */
+       0,
        /* catname */
        "pg_catalog.pg_class c",
        /* selcondition */
@@ -424,22 +514,29 @@ static const SchemaQuery Query_for_list_of_indexes = {
        NULL
 };
 
-static const SchemaQuery Query_for_list_of_procedures = {
-       /* catname */
-       "pg_catalog.pg_proc p",
-       /* selcondition */
-       "p.prokind = 'p'",
-       /* viscondition */
-       "pg_catalog.pg_function_is_visible(p.oid)",
-       /* namespace */
-       "p.pronamespace",
-       /* result */
-       "pg_catalog.quote_ident(p.proname)",
-       /* qualresult */
-       NULL
+static const SchemaQuery Query_for_list_of_procedures[] = {
+       {
+               /* min_server_version */
+               110000,
+               /* catname */
+               "pg_catalog.pg_proc p",
+               /* selcondition */
+               "p.prokind = 'p'",
+               /* viscondition */
+               "pg_catalog.pg_function_is_visible(p.oid)",
+               /* namespace */
+               "p.pronamespace",
+               /* result */
+               "pg_catalog.quote_ident(p.proname)",
+               /* qualresult */
+               NULL
+       },
+       {0, NULL}
 };
 
 static const SchemaQuery Query_for_list_of_routines = {
+       /* min_server_version */
+       0,
        /* catname */
        "pg_catalog.pg_proc p",
        /* selcondition */
@@ -455,6 +552,8 @@ static const SchemaQuery Query_for_list_of_routines = {
 };
 
 static const SchemaQuery Query_for_list_of_sequences = {
+       /* min_server_version */
+       0,
        /* catname */
        "pg_catalog.pg_class c",
        /* selcondition */
@@ -470,6 +569,8 @@ static const SchemaQuery Query_for_list_of_sequences = {
 };
 
 static const SchemaQuery Query_for_list_of_foreign_tables = {
+       /* min_server_version */
+       0,
        /* catname */
        "pg_catalog.pg_class c",
        /* selcondition */
@@ -485,6 +586,8 @@ static const SchemaQuery Query_for_list_of_foreign_tables = {
 };
 
 static const SchemaQuery Query_for_list_of_tables = {
+       /* min_server_version */
+       0,
        /* catname */
        "pg_catalog.pg_class c",
        /* selcondition */
@@ -501,6 +604,8 @@ static const SchemaQuery Query_for_list_of_tables = {
 };
 
 static const SchemaQuery Query_for_list_of_partitioned_tables = {
+       /* min_server_version */
+       0,
        /* catname */
        "pg_catalog.pg_class c",
        /* selcondition */
@@ -516,6 +621,8 @@ static const SchemaQuery Query_for_list_of_partitioned_tables = {
 };
 
 static const SchemaQuery Query_for_list_of_constraints_with_schema = {
+       /* min_server_version */
+       0,
        /* catname */
        "pg_catalog.pg_constraint c",
        /* selcondition */
@@ -532,6 +639,8 @@ static const SchemaQuery Query_for_list_of_constraints_with_schema = {
 
 /* Relations supporting INSERT, UPDATE or DELETE */
 static const SchemaQuery Query_for_list_of_updatables = {
+       /* min_server_version */
+       0,
        /* catname */
        "pg_catalog.pg_class c",
        /* selcondition */
@@ -550,6 +659,8 @@ static const SchemaQuery Query_for_list_of_updatables = {
 };
 
 static const SchemaQuery Query_for_list_of_relations = {
+       /* min_server_version */
+       0,
        /* catname */
        "pg_catalog.pg_class c",
        /* selcondition */
@@ -565,6 +676,8 @@ static const SchemaQuery Query_for_list_of_relations = {
 };
 
 static const SchemaQuery Query_for_list_of_tsvmf = {
+       /* min_server_version */
+       0,
        /* catname */
        "pg_catalog.pg_class c",
        /* selcondition */
@@ -585,6 +698,8 @@ static const SchemaQuery Query_for_list_of_tsvmf = {
 };
 
 static const SchemaQuery Query_for_list_of_tmf = {
+       /* min_server_version */
+       0,
        /* catname */
        "pg_catalog.pg_class c",
        /* selcondition */
@@ -602,6 +717,8 @@ static const SchemaQuery Query_for_list_of_tmf = {
 };
 
 static const SchemaQuery Query_for_list_of_tpm = {
+       /* min_server_version */
+       0,
        /* catname */
        "pg_catalog.pg_class c",
        /* selcondition */
@@ -619,6 +736,8 @@ static const SchemaQuery Query_for_list_of_tpm = {
 };
 
 static const SchemaQuery Query_for_list_of_tm = {
+       /* min_server_version */
+       0,
        /* catname */
        "pg_catalog.pg_class c",
        /* selcondition */
@@ -635,6 +754,8 @@ static const SchemaQuery Query_for_list_of_tm = {
 };
 
 static const SchemaQuery Query_for_list_of_views = {
+       /* min_server_version */
+       0,
        /* catname */
        "pg_catalog.pg_class c",
        /* selcondition */
@@ -650,6 +771,8 @@ static const SchemaQuery Query_for_list_of_views = {
 };
 
 static const SchemaQuery Query_for_list_of_matviews = {
+       /* min_server_version */
+       0,
        /* catname */
        "pg_catalog.pg_class c",
        /* selcondition */
@@ -665,6 +788,8 @@ static const SchemaQuery Query_for_list_of_matviews = {
 };
 
 static const SchemaQuery Query_for_list_of_statistics = {
+       /* min_server_version */
+       0,
        /* catname */
        "pg_catalog.pg_statistic_ext s",
        /* selcondition */
@@ -925,18 +1050,6 @@ static const SchemaQuery Query_for_list_of_statistics = {
 "   FROM pg_catalog.pg_am "\
 "  WHERE substring(pg_catalog.quote_ident(amname),1,%d)='%s'"
 
-#define Query_for_list_of_publications \
-" SELECT pg_catalog.quote_ident(pubname) "\
-"   FROM pg_catalog.pg_publication "\
-"  WHERE substring(pg_catalog.quote_ident(pubname),1,%d)='%s'"
-
-#define Query_for_list_of_subscriptions \
-" SELECT pg_catalog.quote_ident(s.subname) "\
-"   FROM pg_catalog.pg_subscription s, pg_catalog.pg_database d "\
-"  WHERE substring(pg_catalog.quote_ident(s.subname),1,%d)='%s' "\
-"    AND d.datname = pg_catalog.current_database() "\
-"    AND s.subdbid = d.oid"
-
 /* the silly-looking length condition is just to eat up the current word */
 #define Query_for_list_of_arguments \
 "SELECT pg_catalog.oidvectortypes(proargtypes)||')' "\
@@ -1030,6 +1143,32 @@ static const SchemaQuery Query_for_list_of_statistics = {
 "       and pg_catalog.pg_table_is_visible(c2.oid)"\
 "       and c2.relispartition = 'true'"
 
+/*
+ * These object types were introduced later than our support cutoff of
+ * server version 7.4.  We use the VersionedQuery infrastructure so that
+ * we don't send certain-to-fail queries to older servers.
+ */
+
+static const VersionedQuery Query_for_list_of_publications[] = {
+       {100000,
+               " SELECT pg_catalog.quote_ident(pubname) "
+               "   FROM pg_catalog.pg_publication "
+               "  WHERE substring(pg_catalog.quote_ident(pubname),1,%d)='%s'"
+       },
+       {0, NULL}
+};
+
+static const VersionedQuery Query_for_list_of_subscriptions[] = {
+       {100000,
+               " SELECT pg_catalog.quote_ident(s.subname) "
+               "   FROM pg_catalog.pg_subscription s, pg_catalog.pg_database d "
+               "  WHERE substring(pg_catalog.quote_ident(s.subname),1,%d)='%s' "
+               "    AND d.datname = pg_catalog.current_database() "
+               "    AND s.subdbid = d.oid"
+       },
+       {0, NULL}
+};
+
 /*
  * This is a list of all "things" in Pgsql, which can show up after CREATE or
  * DROP; and there is also a query to get a list of them.
@@ -1039,6 +1178,7 @@ typedef struct
 {
        const char *name;
        const char *query;                      /* simple query, or NULL */
+       const VersionedQuery *vquery;   /* versioned query, or NULL */
        const SchemaQuery *squery;      /* schema query, or NULL */
        const bits32 flags;                     /* visibility flags, see below */
 } pgsql_thing_t;
@@ -1049,9 +1189,9 @@ typedef struct
 #define THING_NO_SHOW          (THING_NO_CREATE | THING_NO_DROP | THING_NO_ALTER)
 
 static const pgsql_thing_t words_after_create[] = {
-       {"ACCESS METHOD", NULL, NULL, THING_NO_ALTER},
-       {"AGGREGATE", NULL, &Query_for_list_of_aggregates},
-       {"CAST", NULL, NULL},           /* Casts have complex structures for names, so
+       {"ACCESS METHOD", 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 */
        {"COLLATION", "SELECT pg_catalog.quote_ident(collname) FROM pg_catalog.pg_collation WHERE collencoding IN (-1, pg_catalog.pg_char_to_encoding(pg_catalog.getdatabaseencoding())) AND substring(pg_catalog.quote_ident(collname),1,%d)='%s'"},
 
@@ -1059,56 +1199,56 @@ 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, THING_NO_SHOW},
+       {"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'"},
        {"DATABASE", Query_for_list_of_databases},
-       {"DEFAULT PRIVILEGES", NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
-       {"DICTIONARY", Query_for_list_of_ts_dictionaries, NULL, THING_NO_SHOW},
-       {"DOMAIN", NULL, &Query_for_list_of_domains},
-       {"EVENT TRIGGER", NULL, NULL},
+       {"DEFAULT PRIVILEGES", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
+       {"DICTIONARY", Query_for_list_of_ts_dictionaries, NULL, NULL, THING_NO_SHOW},
+       {"DOMAIN", NULL, NULL, &Query_for_list_of_domains},
+       {"EVENT TRIGGER", NULL, NULL, NULL},
        {"EXTENSION", Query_for_list_of_extensions},
-       {"FOREIGN DATA WRAPPER", NULL, NULL},
-       {"FOREIGN TABLE", NULL, NULL},
-       {"FUNCTION", NULL, &Query_for_list_of_functions},
+       {"FOREIGN DATA WRAPPER", NULL, NULL, NULL},
+       {"FOREIGN TABLE", NULL, NULL, NULL},
+       {"FUNCTION", NULL, NULL, Query_for_list_of_functions},
        {"GROUP", Query_for_list_of_roles},
-       {"INDEX", NULL, &Query_for_list_of_indexes},
+       {"INDEX", NULL, NULL, &Query_for_list_of_indexes},
        {"LANGUAGE", Query_for_list_of_languages},
-       {"LARGE OBJECT", NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
-       {"MATERIALIZED VIEW", NULL, &Query_for_list_of_matviews},
-       {"OPERATOR", NULL, NULL},       /* Querying for this is probably not such a
-                                                                * good idea. */
-       {"OWNED", NULL, NULL, THING_NO_CREATE | THING_NO_ALTER},        /* for DROP OWNED BY ... */
-       {"PARSER", Query_for_list_of_ts_parsers, NULL, THING_NO_SHOW},
-       {"POLICY", NULL, NULL},
-       {"PROCEDURE", NULL, &Query_for_list_of_procedures},
-       {"PUBLICATION", Query_for_list_of_publications},
+       {"LARGE OBJECT", 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. */
+       {"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},
+       {"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, &Query_for_list_of_routines, THING_NO_CREATE},
+       {"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'"},
        {"SCHEMA", Query_for_list_of_schemas},
-       {"SEQUENCE", NULL, &Query_for_list_of_sequences},
+       {"SEQUENCE", NULL, NULL, &Query_for_list_of_sequences},
        {"SERVER", Query_for_list_of_servers},
-       {"STATISTICS", NULL, &Query_for_list_of_statistics},
-       {"SUBSCRIPTION", Query_for_list_of_subscriptions},
-       {"SYSTEM", NULL, NULL, THING_NO_CREATE | THING_NO_DROP},
-       {"TABLE", NULL, &Query_for_list_of_tables},
+       {"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},
+       {"TABLE", NULL, NULL, &Query_for_list_of_tables},
        {"TABLESPACE", Query_for_list_of_tablespaces},
-       {"TEMP", NULL, NULL, THING_NO_DROP | THING_NO_ALTER},   /* for CREATE TEMP TABLE
-                                                                                                                        * ... */
-       {"TEMPLATE", Query_for_list_of_ts_templates, NULL, THING_NO_SHOW},
-       {"TEMPORARY", NULL, NULL, THING_NO_DROP | THING_NO_ALTER},      /* for CREATE TEMPORARY
-                                                                                                                                * TABLE ... */
-       {"TEXT SEARCH", NULL, NULL},
-       {"TRANSFORM", NULL, NULL},
+       {"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 ... */
+       {"TEXT SEARCH", NULL, NULL, NULL},
+       {"TRANSFORM", NULL, NULL, NULL},
        {"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"},
-       {"TYPE", NULL, &Query_for_list_of_datatypes},
-       {"UNIQUE", NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE UNIQUE
-                                                                                                                        * INDEX ... */
-       {"UNLOGGED", NULL, NULL, THING_NO_DROP | THING_NO_ALTER},       /* for CREATE UNLOGGED
-                                                                                                                                * TABLE ... */
+       {"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'"},
-       {"USER MAPPING FOR", NULL, NULL},
-       {"VIEW", NULL, &Query_for_list_of_views},
+       {"USER MAPPING FOR", NULL, NULL, NULL},
+       {"VIEW", NULL, NULL, &Query_for_list_of_views},
        {NULL}                                          /* end of list */
 };
 
@@ -1119,8 +1259,11 @@ static char *create_command_generator(const char *text, int state);
 static char *drop_command_generator(const char *text, int state);
 static char *alter_command_generator(const char *text, int state);
 static char *complete_from_query(const char *text, int state);
+static char *complete_from_versioned_query(const char *text, int state);
 static char *complete_from_schema_query(const char *text, int state);
-static char *_complete_from_query(int is_schema_query,
+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 *text, int state);
 static char *complete_from_list(const char *text, int state);
 static char *complete_from_const(const char *text, int state);
@@ -2208,7 +2351,7 @@ psql_completion(const char *text, int start, int end)
                COMPLETE_WITH_LIST4("WORK", "TRANSACTION", "TO SAVEPOINT", "PREPARED");
 /* CALL */
        else if (Matches1("CALL"))
-               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_procedures, NULL);
+               COMPLETE_WITH_VERSIONED_SCHEMA_QUERY(Query_for_list_of_procedures, NULL);
        else if (Matches2("CALL", MatchAny))
                COMPLETE_WITH_CONST("(");
 /* CLUSTER */
@@ -2654,7 +2797,7 @@ psql_completion(const char *text, int start, int end)
        else if (HeadMatches2("CREATE", "TRIGGER") && TailMatches1("EXECUTE"))
                COMPLETE_WITH_CONST("PROCEDURE");
        else if (HeadMatches2("CREATE", "TRIGGER") && TailMatches2("EXECUTE", "PROCEDURE"))
-               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
+               COMPLETE_WITH_VERSIONED_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
 
 /* CREATE ROLE,USER,GROUP <name> */
        else if (Matches3("CREATE", "ROLE|GROUP|USER", MatchAny) &&
@@ -3008,11 +3151,11 @@ psql_completion(const char *text, int start, int end)
                else if (TailMatches1("DOMAIN"))
                        COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains, NULL);
                else if (TailMatches1("FUNCTION"))
-                       COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
+                       COMPLETE_WITH_VERSIONED_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
                else if (TailMatches1("LANGUAGE"))
                        COMPLETE_WITH_QUERY(Query_for_list_of_languages);
                else if (TailMatches1("PROCEDURE"))
-                       COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_procedures, NULL);
+                       COMPLETE_WITH_VERSIONED_SCHEMA_QUERY(Query_for_list_of_procedures, NULL);
                else if (TailMatches1("ROUTINE"))
                        COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_routines, NULL);
                else if (TailMatches1("SCHEMA"))
@@ -3483,7 +3626,7 @@ psql_completion(const char *text, int start, int end)
                        COMPLETE_WITH_QUERY(Query_for_list_of_roles);
        }
        else if (TailMatchesCS1("\\da*"))
-               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_aggregates, NULL);
+               COMPLETE_WITH_VERSIONED_SCHEMA_QUERY(Query_for_list_of_aggregates, NULL);
        else if (TailMatchesCS1("\\dA*"))
                COMPLETE_WITH_QUERY(Query_for_list_of_access_methods);
        else if (TailMatchesCS1("\\db*"))
@@ -3497,7 +3640,7 @@ psql_completion(const char *text, int start, int end)
        else if (TailMatchesCS1("\\dew*"))
                COMPLETE_WITH_QUERY(Query_for_list_of_fdws);
        else if (TailMatchesCS1("\\df*"))
-               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
+               COMPLETE_WITH_VERSIONED_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
 
        else if (TailMatchesCS1("\\dFd*"))
                COMPLETE_WITH_QUERY(Query_for_list_of_ts_dictionaries);
@@ -3541,7 +3684,7 @@ psql_completion(const char *text, int start, int end)
                COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_relations, NULL);
 
        else if (TailMatchesCS1("\\ef"))
-               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
+               COMPLETE_WITH_VERSIONED_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
        else if (TailMatchesCS1("\\ev"))
                COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
 
@@ -3650,7 +3793,7 @@ psql_completion(const char *text, int start, int end)
                        COMPLETE_WITH_LIST_CS3("default", "verbose", "terse");
        }
        else if (TailMatchesCS1("\\sf*"))
-               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
+               COMPLETE_WITH_VERSIONED_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
        else if (TailMatchesCS1("\\sv*"))
                COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
        else if (TailMatchesCS1("\\cd|\\e|\\edit|\\g|\\i|\\include|"
@@ -3676,9 +3819,11 @@ psql_completion(const char *text, int start, int end)
                        {
                                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_SCHEMA_QUERY(*words_after_create[i].squery,
-                                                                                          NULL);
+                                       COMPLETE_WITH_VERSIONED_SCHEMA_QUERY(words_after_create[i].squery,
+                                                                                                                NULL);
                                break;
                        }
                }
@@ -3777,24 +3922,73 @@ alter_command_generator(const char *text, int state)
        return create_or_drop_command_generator(text, state, THING_NO_ALTER);
 }
 
-/* The following two functions are wrappers for _complete_from_query */
+/*
+ * These functions generate lists using server queries.
+ * They are all wrappers for _complete_from_query.
+ */
 
 static char *
 complete_from_query(const char *text, int state)
 {
-       return _complete_from_query(0, text, state);
+       /* query is assumed to work for any server version */
+       return _complete_from_query(completion_charp, NULL, text, state);
+}
+
+static char *
+complete_from_versioned_query(const char *text, int state)
+{
+       const VersionedQuery *vquery = completion_vquery;
+
+       /* Find appropriate array element */
+       while (pset.sversion < vquery->min_server_version)
+               vquery++;
+       /* Fail completion if server is too old */
+       if (vquery->query == NULL)
+               return NULL;
+
+       return _complete_from_query(vquery->query, NULL, text, state);
 }
 
 static char *
 complete_from_schema_query(const char *text, int state)
 {
-       return _complete_from_query(1, text, state);
+       /* query is assumed to work for any server version */
+       return _complete_from_query(completion_charp, completion_squery,
+                                                               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)
+               squery++;
+       /* Fail completion if server is too old */
+       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);
 }
 
 
 /*
- * This creates a list of matching things, according to a query pointed to
- * by completion_charp.
+ * This creates a list of matching things, according to a query described by
+ * the initial arguments.  The caller has already done any work needed to
+ * select the appropriate query for the server's version.
+ *
  * The query can be one of two kinds:
  *
  * 1. A simple query which must contain a %d and a %s, which will be replaced
@@ -3808,13 +4002,20 @@ complete_from_schema_query(const char *text, int state)
  * %d %s %d %s %d %s %s %d %s
  * where %d is the string length of the text and %s the text itself.
  *
+ * 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.
+ *
  * It is assumed that strings should be escaped to become SQL literals
  * (that is, what is in the query is actually ... '%s' ...)
  *
  * See top of file for examples of both kinds of query.
+ *
+ * "text" and "state" are supplied by readline.
  */
 static char *
-_complete_from_query(int is_schema_query, const char *text, int state)
+_complete_from_query(const char *simple_query,
+                                        const SchemaQuery *schema_query,
+                                        const char *text, int state)
 {
        static int      list_index,
                                byte_length;
@@ -3865,26 +4066,26 @@ _complete_from_query(int is_schema_query, const char *text, int state)
 
                initPQExpBuffer(&query_buffer);
 
-               if (is_schema_query)
+               if (schema_query)
                {
-                       /* completion_squery gives us the pieces to assemble */
-                       const char *qualresult = completion_squery->qualresult;
+                       /* schema_query gives us the pieces to assemble */
+                       const char *qualresult = schema_query->qualresult;
 
                        if (qualresult == NULL)
-                               qualresult = completion_squery->result;
+                               qualresult = schema_query->result;
 
                        /* Get unqualified names matching the input-so-far */
                        appendPQExpBuffer(&query_buffer, "SELECT %s FROM %s WHERE ",
-                                                         completion_squery->result,
-                                                         completion_squery->catname);
-                       if (completion_squery->selcondition)
+                                                         schema_query->result,
+                                                         schema_query->catname);
+                       if (schema_query->selcondition)
                                appendPQExpBuffer(&query_buffer, "%s AND ",
-                                                                 completion_squery->selcondition);
+                                                                 schema_query->selcondition);
                        appendPQExpBuffer(&query_buffer, "substring(%s,1,%d)='%s'",
-                                                         completion_squery->result,
+                                                         schema_query->result,
                                                          char_length, e_text);
                        appendPQExpBuffer(&query_buffer, " AND %s",
-                                                         completion_squery->viscondition);
+                                                         schema_query->viscondition);
 
                        /*
                         * When fetching relation names, suppress system catalogs unless
@@ -3892,7 +4093,7 @@ _complete_from_query(int is_schema_query, const char *text, int state)
                         * between not offering system catalogs for completion at all, and
                         * having them swamp the result when the input is just "p".
                         */
-                       if (strcmp(completion_squery->catname,
+                       if (strcmp(schema_query->catname,
                                           "pg_catalog.pg_class c") == 0 &&
                                strncmp(text, "pg_", 3) !=0)
                        {
@@ -3926,11 +4127,11 @@ _complete_from_query(int is_schema_query, const char *text, int state)
                                                          "FROM %s, pg_catalog.pg_namespace n "
                                                          "WHERE %s = n.oid AND ",
                                                          qualresult,
-                                                         completion_squery->catname,
-                                                         completion_squery->namespace);
-                       if (completion_squery->selcondition)
+                                                         schema_query->catname,
+                                                         schema_query->namespace);
+                       if (schema_query->selcondition)
                                appendPQExpBuffer(&query_buffer, "%s AND ",
-                                                                 completion_squery->selcondition);
+                                                                 schema_query->selcondition);
                        appendPQExpBuffer(&query_buffer, "substring(pg_catalog.quote_ident(n.nspname) || '.' || %s,1,%d)='%s'",
                                                          qualresult,
                                                          char_length, e_text);
@@ -3951,13 +4152,14 @@ _complete_from_query(int is_schema_query, const char *text, int state)
                                                          char_length, e_text);
 
                        /* If an addon query was provided, use it */
-                       if (completion_charp)
-                               appendPQExpBuffer(&query_buffer, "\n%s", completion_charp);
+                       if (simple_query)
+                               appendPQExpBuffer(&query_buffer, "\n%s", simple_query);
                }
                else
                {
-                       /* completion_charp is an sprintf-style format string */
-                       appendPQExpBuffer(&query_buffer, completion_charp,
+                       Assert(simple_query);
+                       /* simple_query is an sprintf-style format string */
+                       appendPQExpBuffer(&query_buffer, simple_query,
                                                          char_length, e_text,
                                                          e_info_charp, e_info_charp,
                                                          e_info_charp2, e_info_charp2);