Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_CBC_SHA384:256) (Exim 4.89) (envelope-from ) id 1esYyT-00023U-7d for pgsql-hackers@arkaria.postgresql.org; Sun, 04 Mar 2018 19:07:09 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.89) (envelope-from ) id 1esYyR-0004yh-T0 for pgsql-hackers@arkaria.postgresql.org; Sun, 04 Mar 2018 19:07:07 +0000 Received: from magus.postgresql.org ([2a02:c0:301:0:ffff::29]) by malur.postgresql.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_CBC_SHA384:256) (Exim 4.89) (envelope-from ) id 1esYyR-0004yX-JC for pgsql-hackers@lists.postgresql.org; Sun, 04 Mar 2018 19:07:07 +0000 Received: from sss.pgh.pa.us ([66.207.139.130]) by magus.postgresql.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_CBC_SHA384:256) (Exim 4.89) (envelope-from ) id 1esYyI-0002Fg-3Q for pgsql-hackers@lists.postgresql.org; Sun, 04 Mar 2018 19:07:07 +0000 Received: from sss1.sss.pgh.pa.us (localhost [127.0.0.1]) by sss.pgh.pa.us (8.14.4/8.14.4) with ESMTP id w24J6m8Q024315; Sun, 4 Mar 2018 14:06:48 -0500 From: Tom Lane To: Edmund Horner cc: Vik Fearing , Andres Freund , Peter Eisentraut , pgsql-hackers@lists.postgresql.org Subject: Re: PATCH: psql tab completion for SELECT In-reply-to: References: <882D6617-FA16-4C56-8FE0-884BCBBE5A4C@anarazel.de> <2738.1516234038@sss.pgh.pa.us> <3a919958-e508-3b66-686f-44a7937fb6e7@2ndquadrant.com> Comments: In-reply-to Edmund Horner message dated "Fri, 26 Jan 2018 14:09:40 +1300" MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="----- =_aaaaaaaaaa0" Content-ID: <24236.1520190295.0@sss.pgh.pa.us> Date: Sun, 04 Mar 2018 14:06:48 -0500 Message-ID: <24314.1520190408@sss.pgh.pa.us> List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Precedence: bulk ------- =_aaaaaaaaaa0 Content-Type: text/plain; charset="us-ascii" Content-ID: <24236.1520190295.1@sss.pgh.pa.us> Content-Transfer-Encoding: quoted-printable Edmund Horner writes: > On 26 January 2018 at 13:44, Vik Fearing w= rote: >> 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 ------- =_aaaaaaaaaa0 Content-Type: text/x-diff; name="version-dependent-tab-completion-1.patch"; charset="us-ascii" Content-ID: <24236.1520190295.2@sss.pgh.pa.us> Content-Description: version-dependent-tab-completion-1.patch Content-Transfer-Encoding: quoted-printable 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 =3D 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 versio= n + * it will work for. Each list must be stored in descending server vers= ion + * order, so that the first satisfactory query is the one to use. + * + * When the query string is otherwise constant, an array of VersionedQue= ry + * suffices. Terminate the array with 0/NULL. (If the search reaches t= hat + * 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-buil= t * to obtain possibly-schema-qualified names of database objects. There= is * enough similarity in the structure that we don't want to repeat it ea= ch * 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 det= ails + * 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 serv= er + * version; use catname =3D=3D NULL in the last entry if it should do n= othing. + */ + 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 string= s */ 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 VersionedQu= ery */ 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 =3D completion_matches(text, complete_from_query); \ } while (0) = + #define COMPLETE_WITH_VERSIONED_QUERY(query) \ + do { \ + completion_vquery =3D query; \ + matches =3D completion_matches(text, complete_from_versioned_query); \ + } while (0) + = #define COMPLETE_WITH_SCHEMA_QUERY(query, addon) \ do { \ completion_squery =3D &(query); \ *************** do { \ *** 160,165 **** --- 196,208 ---- matches =3D completion_matches(text, complete_from_schema_query); \ } while (0) = + #define COMPLETE_WITH_VERSIONED_SCHEMA_QUERY(query, addon) \ + do { \ + completion_squery =3D query; \ + completion_vquery =3D addon; \ + matches =3D completion_matches(text, complete_from_versioned_schema_que= ry); \ + } while (0) + = #define COMPLETE_WITH_LIST_CS(list) \ do { \ completion_charpp =3D list; \ *************** do { \ *** 345,366 **** * Assembly instructions for schema queries */ = ! static const SchemaQuery Query_for_list_of_aggregates =3D { ! /* catname */ ! "pg_catalog.pg_proc p", ! /* selcondition */ ! "p.prokind =3D '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 =3D { /* 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[] =3D { ! { ! /* min_server_version */ ! 110000, ! /* catname */ ! "pg_catalog.pg_proc p", ! /* selcondition */ ! "p.prokind =3D '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 =3D { + /* 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 =3D { + /* 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 =3D { ! /* 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 =3D { /* catname */ "pg_catalog.pg_class c", /* selcondition */ --- 460,503 ---- NULL }; = ! static const SchemaQuery Query_for_list_of_functions[] =3D { ! { ! /* 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 =3D { + /* 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 =3D { ! /* catname */ ! "pg_catalog.pg_proc p", ! /* selcondition */ ! "p.prokind =3D '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 =3D { /* catname */ "pg_catalog.pg_proc p", /* selcondition */ --- 513,541 ---- NULL }; = ! static const SchemaQuery Query_for_list_of_procedures[] =3D { ! { ! /* min_server_version */ ! 110000, ! /* catname */ ! "pg_catalog.pg_proc p", ! /* selcondition */ ! "p.prokind =3D '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 =3D { + /* 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 =3D { + /* 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 =3D { + /* 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 =3D { + /* 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 =3D { + /* 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 =3D { + /* 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 =3D { + /* 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 =3D { + /* 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 =3D { + /* 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 =3D { + /* 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 =3D { + /* 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 =3D { + /* 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 =3D { + /* 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 =3D { + /* 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 =3D { + /* 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)=3D'%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)=3D'%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)=3D'%s' "\ - " AND d.datname =3D pg_catalog.current_database() "\ - " AND s.subdbid =3D 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 =3D '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[] =3D { + {100000, + " SELECT pg_catalog.quote_ident(pubname) " + " FROM pg_catalog.pg_publication " + " WHERE substring(pg_catalog.quote_ident(pubname),1,%d)=3D'%s'" + }, + {0, NULL} + }; + = + static const VersionedQuery Query_for_list_of_subscriptions[] =3D { + {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)=3D'%s' " + " AND d.datname =3D pg_catalog.current_database() " + " AND s.subdbid =3D d.oid" + }, + {0, NULL} + }; + = + /* * This is a list of all "things" in Pgsql, which can show up after CREA= TE 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[] =3D { ! {"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(colln= ame),1,%d)=3D'%s'"}, = --- 1188,1196 ---- #define THING_NO_SHOW (THING_NO_CREATE | THING_NO_DROP | THING_NO_ALTER= ) = static const pgsql_thing_t words_after_create[] =3D { ! {"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(colln= ame),1,%d)=3D'%s'"}, = *************** static const pgsql_thing_t words_after_c *** 1059,1114 **** * CREATE CONSTRAINT TRIGGER is not supported here because it is design= ed * to be used only by pg_dump. */ ! {"CONFIGURATION", Query_for_list_of_ts_configurations, NULL, THING_NO_S= HOW}, {"CONVERSION", "SELECT pg_catalog.quote_ident(conname) FROM pg_catalog.= pg_conversion WHERE substring(pg_catalog.quote_ident(conname),1,%d)=3D'%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 OW= NED 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_ru= les WHERE substring(pg_catalog.quote_ident(rulename),1,%d)=3D'%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 TEM= P TABLE ! * ... */ ! {"TEMPLATE", Query_for_list_of_ts_templates, NULL, THING_NO_SHOW}, ! {"TEMPORARY", NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREAT= E TEMPORARY ! * TABLE ... */ ! {"TEXT SEARCH", NULL, NULL}, ! {"TRANSFORM", NULL, NULL}, {"TRIGGER", "SELECT pg_catalog.quote_ident(tgname) FROM pg_catalog.pg_t= rigger WHERE substring(pg_catalog.quote_ident(tgname),1,%d)=3D'%s' AND NOT= tgisinternal"}, ! {"TYPE", NULL, &Query_for_list_of_datatypes}, ! {"UNIQUE", NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE U= NIQUE ! * 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 design= ed * to be used only by pg_dump. */ ! {"CONFIGURATION", Query_for_list_of_ts_configurations, NULL, NULL, THIN= G_NO_SHOW}, {"CONVERSION", "SELECT pg_catalog.quote_ident(conname) FROM pg_catalog.= pg_conversion WHERE substring(pg_catalog.quote_ident(conname),1,%d)=3D'%s'= "}, {"DATABASE", Query_for_list_of_databases}, ! {"DEFAULT PRIVILEGES", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DRO= P}, ! {"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 su= ch ! * a good idea. */ ! {"OWNED", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_ALTER}, /* for D= ROP 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_ru= les WHERE substring(pg_catalog.quote_ident(rulename),1,%d)=3D'%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 CREA= TE 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_t= rigger WHERE substring(pg_catalog.quote_ident(tgname),1,%d)=3D'%s' AND NOT= tgisinternal"}, ! {"TYPE", NULL, NULL, &Query_for_list_of_datatypes}, ! {"UNIQUE", NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CR= EATE 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, NUL= L); 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 */ 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 */ 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, NUL= L); 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, NU= LL); 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, NUL= L); 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 rep= laced --- 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 =3D completion_vquery; ! = ! /* Find appropriate array element */ ! while (pset.sversion < vquery->min_server_version) ! vquery++; ! /* Fail completion if server is too old */ ! if (vquery->query =3D=3D 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 =3D completion_squery; ! const VersionedQuery *vquery =3D completion_vquery; ! = ! /* Find appropriate array element */ ! while (pset.sversion < squery->min_server_version) ! squery++; ! /* Fail completion if server is too old */ ! if (squery->catname =3D=3D NULL) ! return NULL; ! = ! /* Likewise for the add-on text, if any */ ! if (vquery) ! { ! while (pset.sversion < vquery->min_server_version) ! vquery++; ! if (vquery->query =3D=3D 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 describe= d by ! * the initial arguments. The caller has already done any work needed t= o ! * 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 rep= laced *************** 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 =3D completion_squery->qualresult; = if (qualresult =3D=3D NULL) ! qualresult =3D 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)=3D'%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 =3D schema_query->qualresult; = if (qualresult =3D=3D NULL) ! qualresult =3D 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)=3D'%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") =3D=3D 0 && strncmp(text, "pg_", 3) !=3D0) { --- 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") =3D=3D 0 && strncmp(text, "pg_", 3) !=3D0) { *************** _complete_from_query(int is_schema_query *** 3926,3936 **** "FROM %s, pg_catalog.pg_namespace n " "WHERE %s =3D 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)=3D'%s'", qualresult, char_length, e_text); --- 4126,4136 ---- "FROM %s, pg_catalog.pg_namespace n " "WHERE %s =3D 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)=3D'%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); ------- =_aaaaaaaaaa0--