Edmund Horner <ejr...@gmail.com> writes:
> On 26 January 2018 at 13:44, Vik Fearing <vik.fear...@2ndquadrant.com> wrote:
>> On 01/26/2018 01:28 AM, Edmund Horner wrote:
>>> The patch mentioned attempts to put savepoints around the tab
>>> completion query where appropriate.

>> I am -1 on this idea.

> May I ask why?  It doesn't stop psql working against older versions,
> as it checks that the server supports savepoints.

I looked into this patch and was disappointed to discover that it had
only a very ad-hoc solution to the problem of version-dependent tab
completion queries.  We need something better --- in particular, the
recent prokind changes mean that there needs to be a way to make
SchemaQuery queries version-dependent.

So ... here is a modest proposal.  It invents a VersionedQuery concept
and also extends the SchemaQuery infrastructure to allow those to be
versioned.  I have not taken this nearly as far as it could be taken,
since it's mostly just proposing mechanism.  To illustrate the
VersionedQuery infrastructure, I fixed it so it wouldn't send
publication/subscription queries to pre-v10 servers, and to illustrate
the versioned SchemaQuery infrastructure, I fixed the prokind problems.

If people like this approach, I propose to commit this more or less
as-is.  The select-tab-completion patch would then need to be rewritten
to use this infrastructure, but I think that should be straightforward.
As a separate line of work, the infrastructure could be applied to fix
the pre-existing places where tab completion fails against old servers.
But that's probably work for v12 or beyond, unless somebody's really
motivated to do it right now.

                        regards, tom lane

diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 47909ed..9d0d45b 100644
*** a/src/bin/psql/tab-complete.c
--- b/src/bin/psql/tab-complete.c
*************** extern char *filename_completion_functio
*** 71,85 ****
--- 71,112 ----
  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 0/NULL.  (If the search reaches that
+  * entry, we 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 it 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.
  	 */
*************** static const char *completion_charp;	/* 
*** 133,138 ****
--- 160,166 ----
  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 */
  
*************** static bool completion_case_sensitive;	/
*** 140,146 ****
--- 168,176 ----
   * 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.
*************** do { \
*** 153,158 ****
--- 183,194 ----
  	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); \
*************** do { \
*** 160,165 ****
--- 196,208 ----
  	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; \
*************** do { \
*** 345,366 ****
   * 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_datatypes = {
  	/* catname */
  	"pg_catalog.pg_type t",
  	/* selcondition --- ignore table rowtypes and array types */
--- 388,431 ----
   * Assembly instructions for schema queries
   */
  
! 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 */
*************** static const SchemaQuery Query_for_list_
*** 379,384 ****
--- 444,451 ----
  };
  
  static const SchemaQuery Query_for_list_of_domains = {
+ 	/* min_server_version */
+ 	0,
  	/* catname */
  	"pg_catalog.pg_type t",
  	/* selcondition */
*************** static const SchemaQuery Query_for_list_
*** 393,414 ****
  	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_indexes = {
  	/* catname */
  	"pg_catalog.pg_class c",
  	/* selcondition */
--- 460,503 ----
  	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 */
*************** static const SchemaQuery Query_for_list_
*** 424,445 ****
  	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_routines = {
  	/* catname */
  	"pg_catalog.pg_proc p",
  	/* selcondition */
--- 513,541 ----
  	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 */
*************** static const SchemaQuery Query_for_list_
*** 455,460 ****
--- 551,558 ----
  };
  
  static const SchemaQuery Query_for_list_of_sequences = {
+ 	/* min_server_version */
+ 	0,
  	/* catname */
  	"pg_catalog.pg_class c",
  	/* selcondition */
*************** static const SchemaQuery Query_for_list_
*** 470,475 ****
--- 568,575 ----
  };
  
  static const SchemaQuery Query_for_list_of_foreign_tables = {
+ 	/* min_server_version */
+ 	0,
  	/* catname */
  	"pg_catalog.pg_class c",
  	/* selcondition */
*************** static const SchemaQuery Query_for_list_
*** 485,490 ****
--- 585,592 ----
  };
  
  static const SchemaQuery Query_for_list_of_tables = {
+ 	/* min_server_version */
+ 	0,
  	/* catname */
  	"pg_catalog.pg_class c",
  	/* selcondition */
*************** static const SchemaQuery Query_for_list_
*** 501,506 ****
--- 603,610 ----
  };
  
  static const SchemaQuery Query_for_list_of_partitioned_tables = {
+ 	/* min_server_version */
+ 	0,
  	/* catname */
  	"pg_catalog.pg_class c",
  	/* selcondition */
*************** static const SchemaQuery Query_for_list_
*** 516,521 ****
--- 620,627 ----
  };
  
  static const SchemaQuery Query_for_list_of_constraints_with_schema = {
+ 	/* min_server_version */
+ 	0,
  	/* catname */
  	"pg_catalog.pg_constraint c",
  	/* selcondition */
*************** static const SchemaQuery Query_for_list_
*** 532,537 ****
--- 638,645 ----
  
  /* 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 */
*************** static const SchemaQuery Query_for_list_
*** 550,555 ****
--- 658,665 ----
  };
  
  static const SchemaQuery Query_for_list_of_relations = {
+ 	/* min_server_version */
+ 	0,
  	/* catname */
  	"pg_catalog.pg_class c",
  	/* selcondition */
*************** static const SchemaQuery Query_for_list_
*** 565,570 ****
--- 675,682 ----
  };
  
  static const SchemaQuery Query_for_list_of_tsvmf = {
+ 	/* min_server_version */
+ 	0,
  	/* catname */
  	"pg_catalog.pg_class c",
  	/* selcondition */
*************** static const SchemaQuery Query_for_list_
*** 585,590 ****
--- 697,704 ----
  };
  
  static const SchemaQuery Query_for_list_of_tmf = {
+ 	/* min_server_version */
+ 	0,
  	/* catname */
  	"pg_catalog.pg_class c",
  	/* selcondition */
*************** static const SchemaQuery Query_for_list_
*** 602,607 ****
--- 716,723 ----
  };
  
  static const SchemaQuery Query_for_list_of_tpm = {
+ 	/* min_server_version */
+ 	0,
  	/* catname */
  	"pg_catalog.pg_class c",
  	/* selcondition */
*************** static const SchemaQuery Query_for_list_
*** 619,624 ****
--- 735,742 ----
  };
  
  static const SchemaQuery Query_for_list_of_tm = {
+ 	/* min_server_version */
+ 	0,
  	/* catname */
  	"pg_catalog.pg_class c",
  	/* selcondition */
*************** static const SchemaQuery Query_for_list_
*** 635,640 ****
--- 753,760 ----
  };
  
  static const SchemaQuery Query_for_list_of_views = {
+ 	/* min_server_version */
+ 	0,
  	/* catname */
  	"pg_catalog.pg_class c",
  	/* selcondition */
*************** static const SchemaQuery Query_for_list_
*** 650,655 ****
--- 770,777 ----
  };
  
  static const SchemaQuery Query_for_list_of_matviews = {
+ 	/* min_server_version */
+ 	0,
  	/* catname */
  	"pg_catalog.pg_class c",
  	/* selcondition */
*************** static const SchemaQuery Query_for_list_
*** 665,670 ****
--- 787,794 ----
  };
  
  static const SchemaQuery Query_for_list_of_statistics = {
+ 	/* min_server_version */
+ 	0,
  	/* catname */
  	"pg_catalog.pg_statistic_ext s",
  	/* selcondition */
*************** static const SchemaQuery Query_for_list_
*** 925,942 ****
  "   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)||')' "\
--- 1049,1054 ----
*************** static const SchemaQuery Query_for_list_
*** 1031,1036 ****
--- 1143,1174 ----
  "       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.
   */
*************** typedef struct
*** 1039,1044 ****
--- 1177,1183 ----
  {
  	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;
*************** typedef struct
*** 1049,1057 ****
  #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
  								 * 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'"},
  
--- 1188,1196 ----
  #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, 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'"},
  
*************** static const pgsql_thing_t words_after_c
*** 1059,1114 ****
  	 * 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},
  	{"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},
  	{"EXTENSION", Query_for_list_of_extensions},
! 	{"FOREIGN DATA WRAPPER", NULL, NULL},
! 	{"FOREIGN TABLE", NULL, NULL},
! 	{"FUNCTION", NULL, &Query_for_list_of_functions},
  	{"GROUP", Query_for_list_of_roles},
! 	{"INDEX", 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},
  	{"ROLE", Query_for_list_of_roles},
! 	{"ROUTINE", 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},
  	{"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},
  	{"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},
  	{"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 ... */
  	{"USER", Query_for_list_of_roles " UNION SELECT 'MAPPING FOR'"},
! 	{"USER MAPPING FOR", NULL, NULL},
! 	{"VIEW", NULL, &Query_for_list_of_views},
  	{NULL}						/* end of list */
  };
  
--- 1198,1253 ----
  	 * 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'"},
  	{"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},
! 	{"DOMAIN", NULL, NULL, &Query_for_list_of_domains},
! 	{"EVENT TRIGGER", NULL, NULL, NULL},
  	{"EXTENSION", Query_for_list_of_extensions},
! 	{"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, NULL, &Query_for_list_of_indexes},
  	{"LANGUAGE", Query_for_list_of_languages},
! 	{"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, 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, 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},
! 	{"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 ... */
! 	{"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, 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, NULL},
! 	{"VIEW", NULL, NULL, &Query_for_list_of_views},
  	{NULL}						/* end of list */
  };
  
*************** static char *create_command_generator(co
*** 1119,1126 ****
  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_schema_query(const char *text, int state);
! static char *_complete_from_query(int is_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);
--- 1258,1268 ----
  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_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);
*************** psql_completion(const char *text, int st
*** 2208,2214 ****
  		COMPLETE_WITH_LIST4("WORK", "TRANSACTION", "TO SAVEPOINT", "PREPARED");
  /* CALL */
  	else if (Matches1("CALL"))
! 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_procedures, NULL);
  	else if (Matches2("CALL", MatchAny))
  		COMPLETE_WITH_CONST("(");
  /* CLUSTER */
--- 2350,2356 ----
  		COMPLETE_WITH_LIST4("WORK", "TRANSACTION", "TO SAVEPOINT", "PREPARED");
  /* CALL */
  	else if (Matches1("CALL"))
! 		COMPLETE_WITH_VERSIONED_SCHEMA_QUERY(Query_for_list_of_procedures, NULL);
  	else if (Matches2("CALL", MatchAny))
  		COMPLETE_WITH_CONST("(");
  /* CLUSTER */
*************** psql_completion(const char *text, int st
*** 2654,2660 ****
  	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);
  
  /* CREATE ROLE,USER,GROUP <name> */
  	else if (Matches3("CREATE", "ROLE|GROUP|USER", MatchAny) &&
--- 2796,2802 ----
  	else if (HeadMatches2("CREATE", "TRIGGER") && TailMatches1("EXECUTE"))
  		COMPLETE_WITH_CONST("PROCEDURE");
  	else if (HeadMatches2("CREATE", "TRIGGER") && TailMatches2("EXECUTE", "PROCEDURE"))
! 		COMPLETE_WITH_VERSIONED_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
  
  /* CREATE ROLE,USER,GROUP <name> */
  	else if (Matches3("CREATE", "ROLE|GROUP|USER", MatchAny) &&
*************** psql_completion(const char *text, int st
*** 3008,3018 ****
  		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);
  		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);
  		else if (TailMatches1("ROUTINE"))
  			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_routines, NULL);
  		else if (TailMatches1("SCHEMA"))
--- 3150,3160 ----
  		else if (TailMatches1("DOMAIN"))
  			COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains, NULL);
  		else if (TailMatches1("FUNCTION"))
! 			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_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"))
*************** psql_completion(const char *text, int st
*** 3483,3489 ****
  			COMPLETE_WITH_QUERY(Query_for_list_of_roles);
  	}
  	else if (TailMatchesCS1("\\da*"))
! 		COMPLETE_WITH_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*"))
--- 3625,3631 ----
  			COMPLETE_WITH_QUERY(Query_for_list_of_roles);
  	}
  	else if (TailMatchesCS1("\\da*"))
! 		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*"))
*************** psql_completion(const char *text, int st
*** 3497,3503 ****
  	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);
  
  	else if (TailMatchesCS1("\\dFd*"))
  		COMPLETE_WITH_QUERY(Query_for_list_of_ts_dictionaries);
--- 3639,3645 ----
  	else if (TailMatchesCS1("\\dew*"))
  		COMPLETE_WITH_QUERY(Query_for_list_of_fdws);
  	else if (TailMatchesCS1("\\df*"))
! 		COMPLETE_WITH_VERSIONED_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
  
  	else if (TailMatchesCS1("\\dFd*"))
  		COMPLETE_WITH_QUERY(Query_for_list_of_ts_dictionaries);
*************** psql_completion(const char *text, int st
*** 3541,3547 ****
  		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_relations, NULL);
  
  	else if (TailMatchesCS1("\\ef"))
! 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
  	else if (TailMatchesCS1("\\ev"))
  		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
  
--- 3683,3689 ----
  		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_relations, NULL);
  
  	else if (TailMatchesCS1("\\ef"))
! 		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);
  
*************** psql_completion(const char *text, int st
*** 3650,3656 ****
  			COMPLETE_WITH_LIST_CS3("default", "verbose", "terse");
  	}
  	else if (TailMatchesCS1("\\sf*"))
! 		COMPLETE_WITH_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|"
--- 3792,3798 ----
  			COMPLETE_WITH_LIST_CS3("default", "verbose", "terse");
  	}
  	else if (TailMatchesCS1("\\sf*"))
! 		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|"
*************** psql_completion(const char *text, int st
*** 3676,3684 ****
  			{
  				if (words_after_create[i].query)
  					COMPLETE_WITH_QUERY(words_after_create[i].query);
  				else if (words_after_create[i].squery)
! 					COMPLETE_WITH_SCHEMA_QUERY(*words_after_create[i].squery,
! 											   NULL);
  				break;
  			}
  		}
--- 3818,3828 ----
  			{
  				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);
  				break;
  			}
  		}
*************** alter_command_generator(const char *text
*** 3777,3800 ****
  	return create_or_drop_command_generator(text, state, THING_NO_ALTER);
  }
  
! /* The following two functions are wrappers for _complete_from_query */
  
  static char *
  complete_from_query(const char *text, int state)
  {
! 	return _complete_from_query(0, text, state);
  }
  
  static char *
  complete_from_schema_query(const char *text, int state)
  {
! 	return _complete_from_query(1, text, state);
  }
  
  
  /*
!  * This creates a list of matching things, according to a query pointed to
!  * by completion_charp.
   * The query can be one of two kinds:
   *
   * 1. A simple query which must contain a %d and a %s, which will be replaced
--- 3921,3993 ----
  	return create_or_drop_command_generator(text, state, THING_NO_ALTER);
  }
  
! /*
!  * 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)
  {
! 	/* 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)
  {
! 	/* 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 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
*************** complete_from_schema_query(const char *t
*** 3808,3820 ****
   * %d %s %d %s %d %s %s %d %s
   * where %d is the string length of the text and %s the text itself.
   *
   * 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.
   */
  static char *
! _complete_from_query(int is_schema_query, const char *text, int state)
  {
  	static int	list_index,
  				byte_length;
--- 4001,4020 ----
   * %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(const char *simple_query,
! 					 const SchemaQuery *schema_query,
! 					 const char *text, int state)
  {
  	static int	list_index,
  				byte_length;
*************** _complete_from_query(int is_schema_query
*** 3865,3890 ****
  
  		initPQExpBuffer(&query_buffer);
  
! 		if (is_schema_query)
  		{
! 			/* completion_squery gives us the pieces to assemble */
! 			const char *qualresult = completion_squery->qualresult;
  
  			if (qualresult == NULL)
! 				qualresult = completion_squery->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)
  				appendPQExpBuffer(&query_buffer, "%s AND ",
! 								  completion_squery->selcondition);
  			appendPQExpBuffer(&query_buffer, "substring(%s,1,%d)='%s'",
! 							  completion_squery->result,
  							  char_length, e_text);
  			appendPQExpBuffer(&query_buffer, " AND %s",
! 							  completion_squery->viscondition);
  
  			/*
  			 * When fetching relation names, suppress system catalogs unless
--- 4065,4090 ----
  
  		initPQExpBuffer(&query_buffer);
  
! 		if (schema_query)
  		{
! 			/* schema_query gives us the pieces to assemble */
! 			const char *qualresult = schema_query->qualresult;
  
  			if (qualresult == NULL)
! 				qualresult = schema_query->result;
  
  			/* Get unqualified names matching the input-so-far */
  			appendPQExpBuffer(&query_buffer, "SELECT %s FROM %s WHERE ",
! 							  schema_query->result,
! 							  schema_query->catname);
! 			if (schema_query->selcondition)
  				appendPQExpBuffer(&query_buffer, "%s AND ",
! 								  schema_query->selcondition);
  			appendPQExpBuffer(&query_buffer, "substring(%s,1,%d)='%s'",
! 							  schema_query->result,
  							  char_length, e_text);
  			appendPQExpBuffer(&query_buffer, " AND %s",
! 							  schema_query->viscondition);
  
  			/*
  			 * When fetching relation names, suppress system catalogs unless
*************** _complete_from_query(int is_schema_query
*** 3892,3898 ****
  			 * 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,
  					   "pg_catalog.pg_class c") == 0 &&
  				strncmp(text, "pg_", 3) !=0)
  			{
--- 4092,4098 ----
  			 * 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(text, "pg_", 3) !=0)
  			{
*************** _complete_from_query(int is_schema_query
*** 3926,3936 ****
  							  "FROM %s, pg_catalog.pg_namespace n "
  							  "WHERE %s = n.oid AND ",
  							  qualresult,
! 							  completion_squery->catname,
! 							  completion_squery->namespace);
! 			if (completion_squery->selcondition)
  				appendPQExpBuffer(&query_buffer, "%s AND ",
! 								  completion_squery->selcondition);
  			appendPQExpBuffer(&query_buffer, "substring(pg_catalog.quote_ident(n.nspname) || '.' || %s,1,%d)='%s'",
  							  qualresult,
  							  char_length, e_text);
--- 4126,4136 ----
  							  "FROM %s, pg_catalog.pg_namespace n "
  							  "WHERE %s = n.oid AND ",
  							  qualresult,
! 							  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);
*************** _complete_from_query(int is_schema_query
*** 3951,3963 ****
  							  char_length, e_text);
  
  			/* If an addon query was provided, use it */
! 			if (completion_charp)
! 				appendPQExpBuffer(&query_buffer, "\n%s", completion_charp);
  		}
  		else
  		{
! 			/* completion_charp is an sprintf-style format string */
! 			appendPQExpBuffer(&query_buffer, completion_charp,
  							  char_length, e_text,
  							  e_info_charp, e_info_charp,
  							  e_info_charp2, e_info_charp2);
--- 4151,4164 ----
  							  char_length, e_text);
  
  			/* If an addon query was provided, use it */
! 			if (simple_query)
! 				appendPQExpBuffer(&query_buffer, "\n%s", simple_query);
  		}
  		else
  		{
! 			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);

Reply via email to