Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1nCUyc-0007NP-VR for pgsql-hackers@arkaria.postgresql.org; Tue, 25 Jan 2022 23:11:51 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.92) (envelope-from ) id 1nCUyb-0005pF-RU for pgsql-hackers@arkaria.postgresql.org; Tue, 25 Jan 2022 23:11:49 +0000 Received: from makus.postgresql.org ([2001:4800:3e1:1::229]) by malur.postgresql.org with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1nCUyb-0005o6-5K for pgsql-hackers@lists.postgresql.org; Tue, 25 Jan 2022 23:11:49 +0000 Received: from sss.pgh.pa.us ([66.207.139.130]) by makus.postgresql.org with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1nCUyW-00069u-9j for pgsql-hackers@lists.postgresql.org; Tue, 25 Jan 2022 23:11:48 +0000 Received: from sss1.sss.pgh.pa.us (localhost [127.0.0.1]) by sss.pgh.pa.us (8.15.2/8.15.2) with ESMTP id 20PNBROq2304295; Tue, 25 Jan 2022 18:11:27 -0500 From: Tom Lane To: "tanghy.fnst@fujitsu.com" cc: Peter Eisentraut , Japin Li , Kyotaro Horiguchi , Laurenz Albe , "smithpb2250@gmail.com" , "david.zhang@highgo.ca" , "pgsql-hackers@lists.postgresql.org" Subject: Re: Support tab completion for upper character inputs in psql In-reply-to: References: <20210423.115812.900808736988307020.horikyota.ntt@gmail.com> <80b093ae-88bd-8d8a-68b9-b535a3ab11be@enterprisedb.com> <54bd7839-f463-ef03-f2d2-ccb8184b9b27@enterprisedb.com> <1068530.1641480996@sss.pgh.pa.us> <3547066.1642272686@sss.pgh.pa.us> <17d022ce-2543-b4a3-2b55-fc04! ae87a220@enterprisedb.com> Comments: In-reply-to "tanghy.fnst@fujitsu.com" message dated "Tue, 25 Jan 2022 05:22:32 +0000" MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="----- =_aaaaaaaaaa0" Content-ID: <2303996.1643152044.0@sss.pgh.pa.us> Date: Tue, 25 Jan 2022 18:11:27 -0500 Message-ID: <2304294.1643152287@sss.pgh.pa.us> List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk ------- =_aaaaaaaaaa0 Content-Type: text/plain; charset="us-ascii" Content-ID: <2303996.1643152044.1@sss.pgh.pa.us> I spent some time contemplating my navel about the concerns I raised upthread about double-quoted identifiers. I concluded that the reason things don't work well in that area is that we're trying to get all the work done by applying quote_ident() on the backend side and then ignoring quoting considerations in tab-complete itself. That sort of works, but not terribly well. The currently proposed patch is sticking a toe into the water of dealing with quoting/downcasing in tab-complete, but we need to go a lot further. I propose that we ought to drop the use of quote_ident() in the tab completion queries altogether, instead having the backend return names as-is, and doing all the dequoting and requoting work in tab-complete. Attached is a very-much-WIP patch along these lines. I make no pretense that it's complete; no doubt some of the individual queries are broken or don't return quite the results we want. But it seems to act the way I think it should for relation names. One thing I'm particularly unsure what to do with is the queries for type names, which want to match against the output of format_type, which'll already have applied quote_ident. We can probably hack something up there, but I ran out of time to mess with that for today. Anyway, I wanted to post this just to see what people think of going in this direction. regards, tom lane PS: I omitted the proposed regression test changes here. Many of them are not at all portable --- different versions of readline/libedit will produce different control character sequences for backspacing, for example. I got a lot of failures when I tried to use those tests with this patch; I've not run down which ones are test portability problems, which are due to intentional behavior changes in this patch, and which are due to breakage I've not fixed yet. ------- =_aaaaaaaaaa0 Content-Type: text/x-diff; name="rethink-tab-complete-quote-handling-wip.patch"; charset="us-ascii" Content-ID: <2303996.1643152044.2@sss.pgh.pa.us> Content-Description: rethink-tab-complete-quote-handling-wip.patch Content-Transfer-Encoding: quoted-printable diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c index 502b5c5751..2dadf7d945 100644 --- a/src/bin/psql/tab-complete.c +++ b/src/bin/psql/tab-complete.c @@ -47,6 +47,7 @@ #include "catalog/pg_class_d.h" #include "common.h" #include "libpq-fe.h" +#include "mb/pg_wchar.h" #include "pqexpbuffer.h" #include "settings.h" #include "stringutils.h" @@ -148,8 +149,8 @@ typedef struct SchemaQuery const char *namespace; = /* - * Result --- the appropriately-quoted name to return, in the case of an - * unqualified name. For example, "pg_catalog.quote_ident(c.relname)". + * Result --- the (unquoted) name to return, in the case of an unqualifi= ed + * name. For example, "c.relname". */ const char *result; = @@ -315,7 +316,7 @@ do { \ completion_info_charp =3D _completion_type; \ completion_info_charp2 =3D _completion_schema; \ } \ - matches =3D rl_completion_matches(text, complete_from_query); \ + matches =3D rl_completion_matches(text, complete_from_query_verbatim); \ } while (0) = #define COMPLETE_WITH_FUNCTION_ARG(function) \ @@ -357,14 +358,14 @@ static const SchemaQuery Query_for_list_of_aggregate= s[] =3D { .selcondition =3D "p.prokind =3D 'a'", .viscondition =3D "pg_catalog.pg_function_is_visible(p.oid)", .namespace =3D "p.pronamespace", - .result =3D "pg_catalog.quote_ident(p.proname)", + .result =3D "p.proname", }, { .catname =3D "pg_catalog.pg_proc p", .selcondition =3D "p.proisagg", .viscondition =3D "pg_catalog.pg_function_is_visible(p.oid)", .namespace =3D "p.pronamespace", - .result =3D "pg_catalog.quote_ident(p.proname)", + .result =3D "p.proname", } }; = @@ -378,7 +379,7 @@ static const SchemaQuery Query_for_list_of_datatypes =3D= { .viscondition =3D "pg_catalog.pg_type_is_visible(t.oid)", .namespace =3D "t.typnamespace", .result =3D "pg_catalog.format_type(t.oid, NULL)", - .qualresult =3D "pg_catalog.quote_ident(t.typname)", + .qualresult =3D "t.typname", }; = static const SchemaQuery Query_for_list_of_composite_datatypes =3D { @@ -390,7 +391,7 @@ static const SchemaQuery Query_for_list_of_composite_d= atatypes =3D { .viscondition =3D "pg_catalog.pg_type_is_visible(t.oid)", .namespace =3D "t.typnamespace", .result =3D "pg_catalog.format_type(t.oid, NULL)", - .qualresult =3D "pg_catalog.quote_ident(t.typname)", + .qualresult =3D "t.typname", }; = static const SchemaQuery Query_for_list_of_domains =3D { @@ -398,7 +399,7 @@ static const SchemaQuery Query_for_list_of_domains =3D= { .selcondition =3D "t.typtype =3D 'd'", .viscondition =3D "pg_catalog.pg_type_is_visible(t.oid)", .namespace =3D "t.typnamespace", - .result =3D "pg_catalog.quote_ident(t.typname)", + .result =3D "t.typname", }; = /* Note: this intentionally accepts aggregates as well as plain functions= */ @@ -409,13 +410,13 @@ static const SchemaQuery Query_for_list_of_functions= [] =3D { .selcondition =3D "p.prokind !=3D 'p'", .viscondition =3D "pg_catalog.pg_function_is_visible(p.oid)", .namespace =3D "p.pronamespace", - .result =3D "pg_catalog.quote_ident(p.proname)", + .result =3D "p.proname", }, { .catname =3D "pg_catalog.pg_proc p", .viscondition =3D "pg_catalog.pg_function_is_visible(p.oid)", .namespace =3D "p.pronamespace", - .result =3D "pg_catalog.quote_ident(p.proname)", + .result =3D "p.proname", } }; = @@ -426,7 +427,7 @@ static const SchemaQuery Query_for_list_of_procedures[= ] =3D { .selcondition =3D "p.prokind =3D 'p'", .viscondition =3D "pg_catalog.pg_function_is_visible(p.oid)", .namespace =3D "p.pronamespace", - .result =3D "pg_catalog.quote_ident(p.proname)", + .result =3D "p.proname", }, { /* not supported in older versions */ @@ -438,7 +439,7 @@ static const SchemaQuery Query_for_list_of_routines =3D= { .catname =3D "pg_catalog.pg_proc p", .viscondition =3D "pg_catalog.pg_function_is_visible(p.oid)", .namespace =3D "p.pronamespace", - .result =3D "pg_catalog.quote_ident(p.proname)", + .result =3D "p.proname", }; = static const SchemaQuery Query_for_list_of_sequences =3D { @@ -446,7 +447,7 @@ static const SchemaQuery Query_for_list_of_sequences =3D= { .selcondition =3D "c.relkind IN (" CppAsString2(RELKIND_SEQUENCE) ")", .viscondition =3D "pg_catalog.pg_table_is_visible(c.oid)", .namespace =3D "c.relnamespace", - .result =3D "pg_catalog.quote_ident(c.relname)", + .result =3D "c.relname", }; = static const SchemaQuery Query_for_list_of_foreign_tables =3D { @@ -454,7 +455,7 @@ static const SchemaQuery Query_for_list_of_foreign_tab= les =3D { .selcondition =3D "c.relkind IN (" CppAsString2(RELKIND_FOREIGN_TABLE) "= )", .viscondition =3D "pg_catalog.pg_table_is_visible(c.oid)", .namespace =3D "c.relnamespace", - .result =3D "pg_catalog.quote_ident(c.relname)", + .result =3D "c.relname", }; = static const SchemaQuery Query_for_list_of_tables =3D { @@ -464,7 +465,7 @@ static const SchemaQuery Query_for_list_of_tables =3D = { CppAsString2(RELKIND_PARTITIONED_TABLE) ")", .viscondition =3D "pg_catalog.pg_table_is_visible(c.oid)", .namespace =3D "c.relnamespace", - .result =3D "pg_catalog.quote_ident(c.relname)", + .result =3D "c.relname", }; = static const SchemaQuery Query_for_list_of_partitioned_tables =3D { @@ -472,7 +473,7 @@ static const SchemaQuery Query_for_list_of_partitioned= _tables =3D { .selcondition =3D "c.relkind IN (" CppAsString2(RELKIND_PARTITIONED_TABL= E) ")", .viscondition =3D "pg_catalog.pg_table_is_visible(c.oid)", .namespace =3D "c.relnamespace", - .result =3D "pg_catalog.quote_ident(c.relname)", + .result =3D "c.relname", }; = static const SchemaQuery Query_for_list_of_views =3D { @@ -480,7 +481,7 @@ static const SchemaQuery Query_for_list_of_views =3D { .selcondition =3D "c.relkind IN (" CppAsString2(RELKIND_VIEW) ")", .viscondition =3D "pg_catalog.pg_table_is_visible(c.oid)", .namespace =3D "c.relnamespace", - .result =3D "pg_catalog.quote_ident(c.relname)", + .result =3D "c.relname", }; = static const SchemaQuery Query_for_list_of_matviews =3D { @@ -488,7 +489,7 @@ static const SchemaQuery Query_for_list_of_matviews =3D= { .selcondition =3D "c.relkind IN (" CppAsString2(RELKIND_MATVIEW) ")", .viscondition =3D "pg_catalog.pg_table_is_visible(c.oid)", .namespace =3D "c.relnamespace", - .result =3D "pg_catalog.quote_ident(c.relname)", + .result =3D "c.relname", }; = static const SchemaQuery Query_for_list_of_indexes =3D { @@ -498,7 +499,7 @@ static const SchemaQuery Query_for_list_of_indexes =3D= { CppAsString2(RELKIND_PARTITIONED_INDEX) ")", .viscondition =3D "pg_catalog.pg_table_is_visible(c.oid)", .namespace =3D "c.relnamespace", - .result =3D "pg_catalog.quote_ident(c.relname)", + .result =3D "c.relname", }; = static const SchemaQuery Query_for_list_of_partitioned_indexes =3D { @@ -506,7 +507,7 @@ static const SchemaQuery Query_for_list_of_partitioned= _indexes =3D { .selcondition =3D "c.relkind =3D " CppAsString2(RELKIND_PARTITIONED_INDE= X), .viscondition =3D "pg_catalog.pg_table_is_visible(c.oid)", .namespace =3D "c.relnamespace", - .result =3D "pg_catalog.quote_ident(c.relname)", + .result =3D "c.relname", }; = = @@ -515,7 +516,7 @@ static const SchemaQuery Query_for_list_of_relations =3D= { .catname =3D "pg_catalog.pg_class c", .viscondition =3D "pg_catalog.pg_table_is_visible(c.oid)", .namespace =3D "c.relnamespace", - .result =3D "pg_catalog.quote_ident(c.relname)", + .result =3D "c.relname", }; = /* partitioned relations */ @@ -525,14 +526,14 @@ static const SchemaQuery Query_for_list_of_partition= ed_relations =3D { ", " CppAsString2(RELKIND_PARTITIONED_INDEX) ")", .viscondition =3D "pg_catalog.pg_table_is_visible(c.oid)", .namespace =3D "c.relnamespace", - .result =3D "pg_catalog.quote_ident(c.relname)", + .result =3D "c.relname", }; = static const SchemaQuery Query_for_list_of_operator_families =3D { .catname =3D "pg_catalog.pg_opfamily c", .viscondition =3D "pg_catalog.pg_opfamily_is_visible(c.oid)", .namespace =3D "c.opfnamespace", - .result =3D "pg_catalog.quote_ident(c.opfname)", + .result =3D "c.opfname", }; = /* Relations supporting INSERT, UPDATE or DELETE */ @@ -545,7 +546,7 @@ static const SchemaQuery Query_for_list_of_updatables = =3D { CppAsString2(RELKIND_PARTITIONED_TABLE) ")", .viscondition =3D "pg_catalog.pg_table_is_visible(c.oid)", .namespace =3D "c.relnamespace", - .result =3D "pg_catalog.quote_ident(c.relname)", + .result =3D "c.relname", }; = /* Relations supporting SELECT */ @@ -560,7 +561,7 @@ static const SchemaQuery Query_for_list_of_selectables= =3D { CppAsString2(RELKIND_PARTITIONED_TABLE) ")", .viscondition =3D "pg_catalog.pg_table_is_visible(c.oid)", .namespace =3D "c.relnamespace", - .result =3D "pg_catalog.quote_ident(c.relname)", + .result =3D "c.relname", }; = /* Relations supporting TRUNCATE */ @@ -572,7 +573,7 @@ static const SchemaQuery Query_for_list_of_truncatable= s =3D { CppAsString2(RELKIND_PARTITIONED_TABLE) ")", .viscondition =3D "pg_catalog.pg_table_is_visible(c.oid)", .namespace =3D "c.relnamespace", - .result =3D "pg_catalog.quote_ident(c.relname)", + .result =3D "c.relname", }; = /* Relations supporting GRANT are currently same as those supporting SELE= CT */ @@ -588,7 +589,7 @@ static const SchemaQuery Query_for_list_of_analyzables= =3D { CppAsString2(RELKIND_FOREIGN_TABLE) ")", .viscondition =3D "pg_catalog.pg_table_is_visible(c.oid)", .namespace =3D "c.relnamespace", - .result =3D "pg_catalog.quote_ident(c.relname)", + .result =3D "c.relname", }; = /* Relations supporting index creation */ @@ -600,7 +601,7 @@ static const SchemaQuery Query_for_list_of_indexables = =3D { CppAsString2(RELKIND_MATVIEW) ")", .viscondition =3D "pg_catalog.pg_table_is_visible(c.oid)", .namespace =3D "c.relnamespace", - .result =3D "pg_catalog.quote_ident(c.relname)", + .result =3D "c.relname", }; = /* @@ -617,7 +618,7 @@ static const SchemaQuery Query_for_list_of_clusterable= s =3D { CppAsString2(RELKIND_MATVIEW) ")", .viscondition =3D "pg_catalog.pg_table_is_visible(c.oid)", .namespace =3D "c.relnamespace", - .result =3D "pg_catalog.quote_ident(c.relname)", + .result =3D "c.relname", }; = static const SchemaQuery Query_for_list_of_constraints_with_schema =3D { @@ -625,14 +626,14 @@ static const SchemaQuery Query_for_list_of_constrain= ts_with_schema =3D { .selcondition =3D "c.conrelid <> 0", .viscondition =3D "true", /* there is no pg_constraint_is_visible */ .namespace =3D "c.connamespace", - .result =3D "pg_catalog.quote_ident(c.conname)", + .result =3D "c.conname", }; = static const SchemaQuery Query_for_list_of_statistics =3D { .catname =3D "pg_catalog.pg_statistic_ext s", .viscondition =3D "pg_catalog.pg_statistics_obj_is_visible(s.oid)", .namespace =3D "s.stxnamespace", - .result =3D "pg_catalog.quote_ident(s.stxname)", + .result =3D "s.stxname", }; = static const SchemaQuery Query_for_list_of_collations =3D { @@ -640,7 +641,7 @@ static const SchemaQuery Query_for_list_of_collations = =3D { .selcondition =3D "c.collencoding IN (-1, pg_catalog.pg_char_to_encoding= (pg_catalog.getdatabaseencoding()))", .viscondition =3D "pg_catalog.pg_collation_is_visible(c.oid)", .namespace =3D "c.collnamespace", - .result =3D "pg_catalog.quote_ident(c.collname)", + .result =3D "c.collname", }; = = @@ -659,13 +660,13 @@ static const SchemaQuery Query_for_list_of_collation= s =3D { */ = #define Query_for_list_of_attributes \ -"SELECT pg_catalog.quote_ident(attname) "\ +"SELECT attname "\ " FROM pg_catalog.pg_attribute a, pg_catalog.pg_class c "\ " WHERE c.oid =3D a.attrelid "\ " AND a.attnum > 0 "\ " AND NOT a.attisdropped "\ -" AND substring(pg_catalog.quote_ident(attname),1,%d)=3D'%s' "\ -" AND (pg_catalog.quote_ident(relname)=3D'%s' "\ +" AND substring(attname,1,%d)=3D'%s' "\ +" AND (relname=3D'%s' "\ " OR '\"' || relname || '\"'=3D'%s') "\ " AND pg_catalog.pg_table_is_visible(c.oid)" = @@ -676,21 +677,21 @@ static const SchemaQuery Query_for_list_of_collation= s =3D { " AND a.attnum > 0 "\ " AND NOT a.attisdropped "\ " AND substring(attnum::pg_catalog.text,1,%d)=3D'%s' "\ -" AND (pg_catalog.quote_ident(relname)=3D'%s' "\ +" AND (relname=3D'%s' "\ " OR '\"' || relname || '\"'=3D'%s') "\ " AND pg_catalog.pg_table_is_visible(c.oid)" = #define Query_for_list_of_attributes_with_schema \ -"SELECT pg_catalog.quote_ident(attname) "\ +"SELECT attname "\ " FROM pg_catalog.pg_attribute a, pg_catalog.pg_class c, pg_catalog.pg_n= amespace n "\ " WHERE c.oid =3D a.attrelid "\ " AND n.oid =3D c.relnamespace "\ " AND a.attnum > 0 "\ " AND NOT a.attisdropped "\ -" AND substring(pg_catalog.quote_ident(attname),1,%d)=3D'%s' "\ -" AND (pg_catalog.quote_ident(relname)=3D'%s' "\ +" AND substring(attname,1,%d)=3D'%s' "\ +" AND (relname=3D'%s' "\ " OR '\"' || relname || '\"' =3D'%s') "\ -" AND (pg_catalog.quote_ident(nspname)=3D'%s' "\ +" AND (nspname=3D'%s' "\ " OR '\"' || nspname || '\"' =3D'%s') " = #define Query_for_list_of_enum_values_quoted \ @@ -698,7 +699,7 @@ static const SchemaQuery Query_for_list_of_collations = =3D { " FROM pg_catalog.pg_enum e, pg_catalog.pg_type t "\ " WHERE t.oid =3D e.enumtypid "\ " AND substring(pg_catalog.quote_literal(enumlabel),1,%d)=3D'%s' "\ -" AND (pg_catalog.quote_ident(typname)=3D'%s' "\ +" AND (typname=3D'%s' "\ " OR '\"' || typname || '\"'=3D'%s') "\ " AND pg_catalog.pg_type_is_visible(t.oid)" = @@ -707,7 +708,7 @@ static const SchemaQuery Query_for_list_of_collations = =3D { " FROM pg_catalog.pg_enum e, pg_catalog.pg_type t "\ " WHERE t.oid =3D e.enumtypid "\ " AND substring(enumlabel,1,%d)=3D'%s' "\ -" AND (pg_catalog.quote_ident(typname)=3D'%s' "\ +" AND (typname=3D'%s' "\ " OR '\"' || typname || '\"'=3D'%s') "\ " AND pg_catalog.pg_type_is_visible(t.oid)" = @@ -717,9 +718,9 @@ static const SchemaQuery Query_for_list_of_collations = =3D { " WHERE t.oid =3D e.enumtypid "\ " AND n.oid =3D t.typnamespace "\ " AND substring(pg_catalog.quote_literal(enumlabel),1,%d)=3D'%s' "\ -" AND (pg_catalog.quote_ident(typname)=3D'%s' "\ +" AND (typname=3D'%s' "\ " OR '\"' || typname || '\"'=3D'%s') "\ -" AND (pg_catalog.quote_ident(nspname)=3D'%s' "\ +" AND (nspname=3D'%s' "\ " OR '\"' || nspname || '\"' =3D'%s') " = #define Query_for_list_of_enum_values_with_schema_unquoted \ @@ -728,24 +729,24 @@ static const SchemaQuery Query_for_list_of_collation= s =3D { " WHERE t.oid =3D e.enumtypid "\ " AND n.oid =3D t.typnamespace "\ " AND substring(enumlabel,1,%d)=3D'%s' "\ -" AND (pg_catalog.quote_ident(typname)=3D'%s' "\ +" AND (typname=3D'%s' "\ " OR '\"' || typname || '\"'=3D'%s') "\ -" AND (pg_catalog.quote_ident(nspname)=3D'%s' "\ +" AND (nspname=3D'%s' "\ " OR '\"' || nspname || '\"' =3D'%s') " = #define Query_for_list_of_template_databases \ -"SELECT pg_catalog.quote_ident(d.datname) "\ +"SELECT d.datname "\ " FROM pg_catalog.pg_database d "\ -" WHERE substring(pg_catalog.quote_ident(d.datname),1,%d)=3D'%s' "\ +" WHERE substring(d.datname,1,%d)=3D'%s' "\ " AND (d.datistemplate OR pg_catalog.pg_has_role(d.datdba, 'USAGE'))" = #define Query_for_list_of_databases \ -"SELECT pg_catalog.quote_ident(datname) FROM pg_catalog.pg_database "\ -" WHERE substring(pg_catalog.quote_ident(datname),1,%d)=3D'%s'" +"SELECT datname FROM pg_catalog.pg_database "\ +" WHERE substring(datname,1,%d)=3D'%s'" = #define Query_for_list_of_tablespaces \ -"SELECT pg_catalog.quote_ident(spcname) FROM pg_catalog.pg_tablespace "\ -" WHERE substring(pg_catalog.quote_ident(spcname),1,%d)=3D'%s'" +"SELECT spcname FROM pg_catalog.pg_tablespace "\ +" WHERE substring(spcname,1,%d)=3D'%s'" = #define Query_for_list_of_encodings \ " SELECT DISTINCT pg_catalog.pg_encoding_to_char(conforencoding) "\ @@ -753,14 +754,14 @@ static const SchemaQuery Query_for_list_of_collation= s =3D { " WHERE substring(pg_catalog.pg_encoding_to_char(conforencoding),1,%d)=3D= UPPER('%s')" = #define Query_for_list_of_languages \ -"SELECT pg_catalog.quote_ident(lanname) "\ +"SELECT lanname "\ " FROM pg_catalog.pg_language "\ " WHERE lanname !=3D 'internal' "\ -" AND substring(pg_catalog.quote_ident(lanname),1,%d)=3D'%s'" +" AND substring(lanname,1,%d)=3D'%s'" = #define Query_for_list_of_schemas \ -"SELECT pg_catalog.quote_ident(nspname) FROM pg_catalog.pg_namespace "\ -" WHERE substring(pg_catalog.quote_ident(nspname),1,%d)=3D'%s'" +"SELECT nspname FROM pg_catalog.pg_namespace "\ +" WHERE substring(nspname,1,%d)=3D'%s'" = #define Query_for_list_of_alter_system_set_vars \ "SELECT name FROM "\ @@ -789,14 +790,14 @@ static const SchemaQuery Query_for_list_of_collation= s =3D { " WHERE substring(name,1,%d)=3D'%s'" = #define Query_for_list_of_roles \ -" SELECT pg_catalog.quote_ident(rolname) "\ +" SELECT rolname "\ " FROM pg_catalog.pg_roles "\ -" WHERE substring(pg_catalog.quote_ident(rolname),1,%d)=3D'%s'" +" WHERE substring(rolname,1,%d)=3D'%s'" = #define Query_for_list_of_grant_roles \ -" SELECT pg_catalog.quote_ident(rolname) "\ +" SELECT rolname "\ " FROM pg_catalog.pg_roles "\ -" WHERE substring(pg_catalog.quote_ident(rolname),1,%d)=3D'%s'"\ +" WHERE substring(rolname,1,%d)=3D'%s'"\ " UNION ALL SELECT 'PUBLIC'"\ " UNION ALL SELECT 'CURRENT_ROLE'"\ " UNION ALL SELECT 'CURRENT_USER'"\ @@ -804,11 +805,11 @@ static const SchemaQuery Query_for_list_of_collation= s =3D { = /* the silly-looking length condition is just to eat up the current word = */ #define Query_for_index_of_table \ -"SELECT pg_catalog.quote_ident(c2.relname) "\ +"SELECT c2.relname "\ " FROM pg_catalog.pg_class c1, pg_catalog.pg_class c2, pg_catalog.pg_ind= ex i"\ " WHERE c1.oid=3Di.indrelid and i.indexrelid=3Dc2.oid"\ " and (%d =3D pg_catalog.length('%s'))"\ -" and pg_catalog.quote_ident(c1.relname)=3D'%s'"\ +" and c1.relname=3D'%s'"\ " and pg_catalog.pg_table_is_visible(c2.oid)" = #define Query_for_unique_index_of_table \ @@ -817,124 +818,124 @@ Query_for_index_of_table \ = /* the silly-looking length condition is just to eat up the current word = */ #define Query_for_constraint_of_table \ -"SELECT pg_catalog.quote_ident(conname) "\ +"SELECT conname "\ " FROM pg_catalog.pg_class c1, pg_catalog.pg_constraint con "\ " WHERE c1.oid=3Dconrelid and (%d =3D pg_catalog.length('%s'))"\ -" and pg_catalog.quote_ident(c1.relname)=3D'%s'"\ +" and c1.relname=3D'%s'"\ " and pg_catalog.pg_table_is_visible(c1.oid)" = /* the silly-looking length condition is just to eat up the current word = */ #define Query_for_constraint_of_table_not_validated \ -"SELECT pg_catalog.quote_ident(conname) "\ +"SELECT conname "\ " FROM pg_catalog.pg_class c1, pg_catalog.pg_constraint con "\ " WHERE c1.oid=3Dconrelid and (%d =3D pg_catalog.length('%s'))"\ -" and pg_catalog.quote_ident(c1.relname)=3D'%s'"\ +" and c1.relname=3D'%s'"\ " and pg_catalog.pg_table_is_visible(c1.oid)" \ " and not con.convalidated" = #define Query_for_all_table_constraints \ -"SELECT pg_catalog.quote_ident(conname) "\ +"SELECT conname "\ " FROM pg_catalog.pg_constraint c "\ " WHERE c.conrelid <> 0 " = /* the silly-looking length condition is just to eat up the current word = */ #define Query_for_constraint_of_type \ -"SELECT pg_catalog.quote_ident(conname) "\ +"SELECT conname "\ " FROM pg_catalog.pg_type t, pg_catalog.pg_constraint con "\ " WHERE t.oid=3Dcontypid and (%d =3D pg_catalog.length('%s'))"\ -" and pg_catalog.quote_ident(t.typname)=3D'%s'"\ +" and t.typname=3D'%s'"\ " and pg_catalog.pg_type_is_visible(t.oid)" = /* the silly-looking length condition is just to eat up the current word = */ #define Query_for_list_of_tables_for_constraint \ -"SELECT pg_catalog.quote_ident(relname) "\ +"SELECT relname "\ " FROM pg_catalog.pg_class"\ " WHERE (%d =3D pg_catalog.length('%s'))"\ " AND oid IN "\ " (SELECT conrelid FROM pg_catalog.pg_constraint "\ -" WHERE pg_catalog.quote_ident(conname)=3D'%s')" +" WHERE conname=3D'%s')" = /* the silly-looking length condition is just to eat up the current word = */ #define Query_for_rule_of_table \ -"SELECT pg_catalog.quote_ident(rulename) "\ +"SELECT rulename "\ " FROM pg_catalog.pg_class c1, pg_catalog.pg_rewrite "\ " WHERE c1.oid=3Dev_class and (%d =3D pg_catalog.length('%s'))"\ -" and pg_catalog.quote_ident(c1.relname)=3D'%s'"\ +" and c1.relname=3D'%s'"\ " and pg_catalog.pg_table_is_visible(c1.oid)" = /* the silly-looking length condition is just to eat up the current word = */ #define Query_for_list_of_tables_for_rule \ -"SELECT pg_catalog.quote_ident(relname) "\ +"SELECT relname "\ " FROM pg_catalog.pg_class"\ " WHERE (%d =3D pg_catalog.length('%s'))"\ " AND oid IN "\ " (SELECT ev_class FROM pg_catalog.pg_rewrite "\ -" WHERE pg_catalog.quote_ident(rulename)=3D'%s')" +" WHERE rulename=3D'%s')" = /* the silly-looking length condition is just to eat up the current word = */ #define Query_for_trigger_of_table \ -"SELECT pg_catalog.quote_ident(tgname) "\ +"SELECT tgname "\ " FROM pg_catalog.pg_class c1, pg_catalog.pg_trigger "\ " WHERE c1.oid=3Dtgrelid and (%d =3D pg_catalog.length('%s'))"\ -" and pg_catalog.quote_ident(c1.relname)=3D'%s'"\ +" and c1.relname=3D'%s'"\ " and pg_catalog.pg_table_is_visible(c1.oid)"\ " and not tgisinternal" = /* the silly-looking length condition is just to eat up the current word = */ #define Query_for_list_of_tables_for_trigger \ -"SELECT pg_catalog.quote_ident(relname) "\ +"SELECT relname "\ " FROM pg_catalog.pg_class"\ " WHERE (%d =3D pg_catalog.length('%s'))"\ " AND oid IN "\ " (SELECT tgrelid FROM pg_catalog.pg_trigger "\ -" WHERE pg_catalog.quote_ident(tgname)=3D'%s')" +" WHERE tgname=3D'%s')" = #define Query_for_list_of_ts_configurations \ -"SELECT pg_catalog.quote_ident(cfgname) FROM pg_catalog.pg_ts_config "\ -" WHERE substring(pg_catalog.quote_ident(cfgname),1,%d)=3D'%s'" +"SELECT cfgname FROM pg_catalog.pg_ts_config "\ +" WHERE substring(cfgname,1,%d)=3D'%s'" = #define Query_for_list_of_ts_dictionaries \ -"SELECT pg_catalog.quote_ident(dictname) FROM pg_catalog.pg_ts_dict "\ -" WHERE substring(pg_catalog.quote_ident(dictname),1,%d)=3D'%s'" +"SELECT dictname FROM pg_catalog.pg_ts_dict "\ +" WHERE substring(dictname,1,%d)=3D'%s'" = #define Query_for_list_of_ts_parsers \ -"SELECT pg_catalog.quote_ident(prsname) FROM pg_catalog.pg_ts_parser "\ -" WHERE substring(pg_catalog.quote_ident(prsname),1,%d)=3D'%s'" +"SELECT prsname FROM pg_catalog.pg_ts_parser "\ +" WHERE substring(prsname,1,%d)=3D'%s'" = #define Query_for_list_of_ts_templates \ -"SELECT pg_catalog.quote_ident(tmplname) FROM pg_catalog.pg_ts_template "= \ -" WHERE substring(pg_catalog.quote_ident(tmplname),1,%d)=3D'%s'" +"SELECT tmplname FROM pg_catalog.pg_ts_template "\ +" WHERE substring(tmplname,1,%d)=3D'%s'" = #define Query_for_list_of_fdws \ -" SELECT pg_catalog.quote_ident(fdwname) "\ +" SELECT fdwname "\ " FROM pg_catalog.pg_foreign_data_wrapper "\ -" WHERE substring(pg_catalog.quote_ident(fdwname),1,%d)=3D'%s'" +" WHERE substring(fdwname,1,%d)=3D'%s'" = #define Query_for_list_of_servers \ -" SELECT pg_catalog.quote_ident(srvname) "\ +" SELECT srvname "\ " FROM pg_catalog.pg_foreign_server "\ -" WHERE substring(pg_catalog.quote_ident(srvname),1,%d)=3D'%s'" +" WHERE substring(srvname,1,%d)=3D'%s'" = #define Query_for_list_of_user_mappings \ -" SELECT pg_catalog.quote_ident(usename) "\ +" SELECT usename "\ " FROM pg_catalog.pg_user_mappings "\ -" WHERE substring(pg_catalog.quote_ident(usename),1,%d)=3D'%s'" +" WHERE substring(usename,1,%d)=3D'%s'" = #define Query_for_list_of_access_methods \ -" SELECT pg_catalog.quote_ident(amname) "\ +" SELECT amname "\ " FROM pg_catalog.pg_am "\ -" WHERE substring(pg_catalog.quote_ident(amname),1,%d)=3D'%s'" +" WHERE substring(amname,1,%d)=3D'%s'" = #define Query_for_list_of_index_access_methods \ -" SELECT pg_catalog.quote_ident(amname) "\ +" SELECT amname "\ " FROM pg_catalog.pg_am "\ -" WHERE substring(pg_catalog.quote_ident(amname),1,%d)=3D'%s' AND "\ +" WHERE substring(amname,1,%d)=3D'%s' AND "\ " amtype=3D" CppAsString2(AMTYPE_INDEX) = #define Query_for_list_of_table_access_methods \ -" SELECT pg_catalog.quote_ident(amname) "\ +" SELECT amname "\ " FROM pg_catalog.pg_am "\ -" WHERE substring(pg_catalog.quote_ident(amname),1,%d)=3D'%s' AND "\ +" WHERE substring(amname,1,%d)=3D'%s' AND "\ " amtype=3D" CppAsString2(AMTYPE_TABLE) = /* the silly-looking length condition is just to eat up the current word = */ @@ -942,7 +943,7 @@ Query_for_index_of_table \ "SELECT pg_catalog.oidvectortypes(proargtypes)||')' "\ " FROM pg_catalog.pg_proc "\ " WHERE (%d =3D pg_catalog.length('%s'))"\ -" AND (pg_catalog.quote_ident(proname)=3D'%s'"\ +" AND (proname=3D'%s'"\ " OR '\"' || proname || '\"'=3D'%s') "\ " AND (pg_catalog.pg_function_is_visible(pg_proc.oid))" = @@ -952,68 +953,68 @@ Query_for_index_of_table \ " FROM pg_catalog.pg_proc p, pg_catalog.pg_namespace n "\ " WHERE (%d =3D pg_catalog.length('%s'))"\ " AND n.oid =3D p.pronamespace "\ -" AND (pg_catalog.quote_ident(proname)=3D'%s' "\ +" AND (proname=3D'%s' "\ " OR '\"' || proname || '\"' =3D'%s') "\ -" AND (pg_catalog.quote_ident(nspname)=3D'%s' "\ +" AND (nspname=3D'%s' "\ " OR '\"' || nspname || '\"' =3D'%s') " = #define Query_for_list_of_extensions \ -" SELECT pg_catalog.quote_ident(extname) "\ +" SELECT extname "\ " FROM pg_catalog.pg_extension "\ -" WHERE substring(pg_catalog.quote_ident(extname),1,%d)=3D'%s'" +" WHERE substring(extname,1,%d)=3D'%s'" = #define Query_for_list_of_available_extensions \ -" SELECT pg_catalog.quote_ident(name) "\ +" SELECT name "\ " FROM pg_catalog.pg_available_extensions "\ -" WHERE substring(pg_catalog.quote_ident(name),1,%d)=3D'%s' AND installe= d_version IS NULL" +" WHERE substring(name,1,%d)=3D'%s' AND installed_version IS NULL" = /* the silly-looking length condition is just to eat up the current word = */ #define Query_for_list_of_available_extension_versions \ -" SELECT pg_catalog.quote_ident(version) "\ +" SELECT version "\ " FROM pg_catalog.pg_available_extension_versions "\ " WHERE (%d =3D pg_catalog.length('%s'))"\ -" AND pg_catalog.quote_ident(name)=3D'%s'" +" AND name=3D'%s'" = /* the silly-looking length condition is just to eat up the current word = */ #define Query_for_list_of_available_extension_versions_with_TO \ -" SELECT 'TO ' || pg_catalog.quote_ident(version) "\ +" SELECT 'TO ' || version "\ " FROM pg_catalog.pg_available_extension_versions "\ " WHERE (%d =3D pg_catalog.length('%s'))"\ -" AND pg_catalog.quote_ident(name)=3D'%s'" +" AND name=3D'%s'" = #define Query_for_list_of_prepared_statements \ -" SELECT pg_catalog.quote_ident(name) "\ +" SELECT name "\ " FROM pg_catalog.pg_prepared_statements "\ -" WHERE substring(pg_catalog.quote_ident(name),1,%d)=3D'%s'" +" WHERE substring(name,1,%d)=3D'%s'" = #define Query_for_list_of_event_triggers \ -" SELECT pg_catalog.quote_ident(evtname) "\ +" SELECT evtname "\ " FROM pg_catalog.pg_event_trigger "\ -" WHERE substring(pg_catalog.quote_ident(evtname),1,%d)=3D'%s'" +" WHERE substring(evtname,1,%d)=3D'%s'" = #define Query_for_list_of_tablesample_methods \ -" SELECT pg_catalog.quote_ident(proname) "\ +" SELECT proname "\ " FROM pg_catalog.pg_proc "\ " WHERE prorettype =3D 'pg_catalog.tsm_handler'::pg_catalog.regtype AND = "\ " proargtypes[0] =3D 'pg_catalog.internal'::pg_catalog.regtype AND= "\ -" substring(pg_catalog.quote_ident(proname),1,%d)=3D'%s'" +" substring(proname,1,%d)=3D'%s'" = #define Query_for_list_of_policies \ -" SELECT pg_catalog.quote_ident(polname) "\ +" SELECT polname "\ " FROM pg_catalog.pg_policy "\ -" WHERE substring(pg_catalog.quote_ident(polname),1,%d)=3D'%s'" +" WHERE substring(polname,1,%d)=3D'%s'" = #define Query_for_list_of_tables_for_policy \ -"SELECT pg_catalog.quote_ident(relname) "\ +"SELECT relname "\ " FROM pg_catalog.pg_class"\ " WHERE (%d =3D pg_catalog.length('%s'))"\ " AND oid IN "\ " (SELECT polrelid FROM pg_catalog.pg_policy "\ -" WHERE pg_catalog.quote_ident(polname)=3D'%s')" +" WHERE polname=3D'%s')" = #define Query_for_enum \ " SELECT name FROM ( "\ -" SELECT pg_catalog.quote_ident(pg_catalog.unnest(enumvals)) AS name "\ +" SELECT pg_catalog.unnest(enumvals) AS name "\ " FROM pg_catalog.pg_settings "\ " WHERE pg_catalog.lower(name)=3Dpg_catalog.lower('%s') "\ " UNION ALL " \ @@ -1022,18 +1023,18 @@ Query_for_index_of_table \ = /* the silly-looking length condition is just to eat up the current word = */ #define Query_for_partition_of_table \ -"SELECT pg_catalog.quote_ident(c2.relname) "\ +"SELECT c2.relname "\ " FROM pg_catalog.pg_class c1, pg_catalog.pg_class c2, pg_catalog.pg_inh= erits i"\ " WHERE c1.oid=3Di.inhparent and i.inhrelid=3Dc2.oid"\ " and (%d =3D pg_catalog.length('%s'))"\ -" and pg_catalog.quote_ident(c1.relname)=3D'%s'"\ +" and c1.relname=3D'%s'"\ " and pg_catalog.pg_table_is_visible(c2.oid)"\ " and c2.relispartition =3D 'true'" = #define Query_for_list_of_cursors \ -" SELECT pg_catalog.quote_ident(name) "\ +" SELECT name "\ " FROM pg_catalog.pg_cursors "\ -" WHERE substring(pg_catalog.quote_ident(name),1,%d)=3D'%s'" +" WHERE substring(name,1,%d)=3D'%s'" = /* * These object types were introduced later than our support cutoff of @@ -1043,18 +1044,18 @@ Query_for_index_of_table \ = static const VersionedQuery Query_for_list_of_publications[] =3D { {100000, - " SELECT pg_catalog.quote_ident(pubname) " + " SELECT pubname " " FROM pg_catalog.pg_publication " - " WHERE substring(pg_catalog.quote_ident(pubname),1,%d)=3D'%s'" + " WHERE substring(pubname,1,%d)=3D'%s'" }, {0, NULL} }; = static const VersionedQuery Query_for_list_of_subscriptions[] =3D { {100000, - " SELECT pg_catalog.quote_ident(s.subname) " + " SELECT s.subname " " FROM pg_catalog.pg_subscription s, pg_catalog.pg_database d " - " WHERE substring(pg_catalog.quote_ident(s.subname),1,%d)=3D'%s' " + " WHERE substring(s.subname,1,%d)=3D'%s' " " AND d.datname =3D pg_catalog.current_database() " " AND s.subdbid =3D d.oid" }, @@ -1092,7 +1093,7 @@ static const pgsql_thing_t words_after_create[] =3D = { * 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.p= g_conversion WHERE substring(pg_catalog.quote_ident(conname),1,%d)=3D'%s'"= }, + {"CONVERSION", "SELECT conname FROM pg_catalog.pg_conversion WHERE subst= ring(conname,1,%d)=3D'%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_S= HOW}, @@ -1117,7 +1118,7 @@ static const pgsql_thing_t words_after_create[] =3D = { {"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_rul= es WHERE substring(pg_catalog.quote_ident(rulename),1,%d)=3D'%s'"}, + {"RULE", "SELECT rulename FROM pg_catalog.pg_rules WHERE substring(rulen= ame,1,%d)=3D'%s'"}, {"SCHEMA", Query_for_list_of_schemas}, {"SEQUENCE", NULL, NULL, &Query_for_list_of_sequences}, {"SERVER", Query_for_list_of_servers}, @@ -1133,7 +1134,7 @@ static const pgsql_thing_t words_after_create[] =3D = { * TABLE ... */ {"TEXT SEARCH", NULL, NULL, NULL}, {"TRANSFORM", NULL, NULL, NULL, THING_NO_ALTER}, - {"TRIGGER", "SELECT pg_catalog.quote_ident(tgname) FROM pg_catalog.pg_tr= igger WHERE substring(pg_catalog.quote_ident(tgname),1,%d)=3D'%s' AND NOT = tgisinternal"}, + {"TRIGGER", "SELECT tgname FROM pg_catalog.pg_trigger WHERE substring(tg= name,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 CRE= ATE UNIQUE * INDEX ... */ @@ -1195,11 +1196,13 @@ 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_query_verbatim(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 s= tate); static char *_complete_from_query(const char *simple_query, const SchemaQuery *schema_query, + bool verbatim, const char *text, int state); static char *complete_from_list(const char *text, int state); static char *complete_from_const(const char *text, int state); @@ -1212,6 +1215,12 @@ static char *complete_from_files(const char *text, = int state); = static char *pg_strdup_keyword_case(const char *s, const char *ref); static char *escape_string(const char *text); +static void parse_identifier(const char *ident, + char **schemaname, char **objectname, + bool *schemaquoted, bool *objectquoted); +static char *requote_identifier(const char *schemaname, const char *objec= tname, + bool quote_schema, bool quote_object); +static bool identifier_needs_quotes(const char *ident); static PGresult *exec_query(const char *query); = static char **get_previous_words(int point, char **buffer, int *nwords); @@ -1651,7 +1660,7 @@ psql_completion(const char *text, int start, int end= ) /* ALTER TABLE */ else if (Matches("ALTER", "TABLE")) COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, - "UNION SELECT 'ALL IN TABLESPACE'"); + "UNION SELECT 'ALL IN TABLESPACE', NULL, true"); = /* ALTER something */ else if (Matches("ALTER")) @@ -1812,7 +1821,7 @@ psql_completion(const char *text, int start, int end= ) /* ALTER INDEX */ else if (Matches("ALTER", "INDEX")) COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, - "UNION SELECT 'ALL IN TABLESPACE'"); + "UNION SELECT 'ALL IN TABLESPACE', NULL, true"); /* ALTER INDEX */ else if (Matches("ALTER", "INDEX", MatchAny)) COMPLETE_WITH("ALTER COLUMN", "OWNER TO", "RENAME TO", "SET", @@ -1879,7 +1888,7 @@ psql_completion(const char *text, int start, int end= ) /* ALTER MATERIALIZED VIEW */ else if (Matches("ALTER", "MATERIALIZED", "VIEW")) COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, - "UNION SELECT 'ALL IN TABLESPACE'"); + "UNION SELECT 'ALL IN TABLESPACE', NULL, true"); = /* ALTER USER,ROLE */ else if (Matches("ALTER", "USER|ROLE", MatchAny) && @@ -2164,13 +2173,13 @@ psql_completion(const char *text, int start, int e= nd) } /* ALTER TABLE xxx INHERIT */ else if (Matches("ALTER", "TABLE", MatchAny, "INHERIT")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, ""); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL); /* ALTER TABLE xxx NO */ else if (Matches("ALTER", "TABLE", MatchAny, "NO")) COMPLETE_WITH("FORCE ROW LEVEL SECURITY", "INHERIT"); /* ALTER TABLE xxx NO INHERIT */ else if (Matches("ALTER", "TABLE", MatchAny, "NO", "INHERIT")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, ""); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL); /* ALTER TABLE xxx DISABLE */ else if (Matches("ALTER", "TABLE", MatchAny, "DISABLE")) COMPLETE_WITH("ROW LEVEL SECURITY", "RULE", "TRIGGER"); @@ -2298,7 +2307,7 @@ psql_completion(const char *text, int start, int end= ) * tables. */ else if (Matches("ALTER", "TABLE", MatchAny, "ATTACH", "PARTITION")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, ""); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL); /* Limited completion support for partition bound specification */ else if (TailMatches("ATTACH", "PARTITION", MatchAny)) COMPLETE_WITH("FOR VALUES", "DEFAULT"); @@ -2386,7 +2395,7 @@ psql_completion(const char *text, int start, int end= ) */ else if (Matches("ANALYZE")) COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_analyzables, - " UNION SELECT 'VERBOSE'"); + " UNION SELECT 'VERBOSE', NULL, true"); else if (HeadMatches("ANALYZE", "(*") && !HeadMatches("ANALYZE", "(*)")) { @@ -2434,7 +2443,8 @@ psql_completion(const char *text, int start, int end= ) " UNION SELECT 'ALL'"); /* CLUSTER */ else if (Matches("CLUSTER")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_clusterables, "UNION SELEC= T 'VERBOSE'"); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_clusterables, + "UNION SELECT 'VERBOSE', NULL, true"); else if (Matches("CLUSTER", "VERBOSE") || Matches("CLUSTER", "(*)")) COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_clusterables, NULL); @@ -2558,7 +2568,7 @@ psql_completion(const char *text, int start, int end= ) */ else if (Matches("COPY|\\copy")) COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, - " UNION ALL SELECT '('"); + " UNION ALL SELECT '(', NULL, true"); /* Complete COPY ( with legal query commands */ else if (Matches("COPY|\\copy", "(")) COMPLETE_WITH("SELECT", "TABLE", "VALUES", "INSERT INTO", "UPDATE", "DE= LETE FROM", "WITH"); @@ -2686,8 +2696,8 @@ psql_completion(const char *text, int start, int end= ) */ else if (TailMatches("CREATE|UNIQUE", "INDEX")) COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, - " UNION SELECT 'ON'" - " UNION SELECT 'CONCURRENTLY'"); + " UNION SELECT 'ON', NULL, true" + " UNION SELECT 'CONCURRENTLY', NULL, true"); = /* * Complete ... INDEX|CONCURRENTLY [] ON with a list of relations @@ -2703,7 +2713,7 @@ psql_completion(const char *text, int start, int end= ) */ else if (TailMatches("CREATE|UNIQUE", "INDEX", "CONCURRENTLY")) COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, - " UNION SELECT 'ON'"); + " UNION SELECT 'ON', NULL, true"); /* Complete CREATE|UNIQUE INDEX [CONCURRENTLY] with "ON" */ else if (TailMatches("CREATE|UNIQUE", "INDEX", MatchAny) || TailMatches("CREATE|UNIQUE", "INDEX", "CONCURRENTLY", MatchAny)) @@ -2912,7 +2922,7 @@ psql_completion(const char *text, int start, int end= ) COMPLETE_WITH("RANGE (", "LIST (", "HASH ("); /* If we have xxx PARTITION OF, provide a list of partitioned tables */ else if (TailMatches("PARTITION", "OF")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_partitioned_tables, ""); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_partitioned_tables, NULL); /* Limited completion support for partition bound specification */ else if (TailMatches("PARTITION", "OF", MatchAny)) COMPLETE_WITH("FOR VALUES", "DEFAULT"); @@ -3359,7 +3369,7 @@ psql_completion(const char *text, int start, int end= ) /* DROP INDEX */ else if (Matches("DROP", "INDEX")) COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, - " UNION SELECT 'CONCURRENTLY'"); + " UNION SELECT 'CONCURRENTLY', NULL, true"); else if (Matches("DROP", "INDEX", "CONCURRENTLY")) COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL); else if (Matches("DROP", "INDEX", MatchAny)) @@ -3618,25 +3628,25 @@ psql_completion(const char *text, int start, int e= nd) COMPLETE_WITH("TABLES", "SEQUENCES", "FUNCTIONS", "PROCEDURES", "ROUTI= NES", "TYPES", "SCHEMAS"); else COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_grantables, - " UNION SELECT 'ALL FUNCTIONS IN SCHEMA'" - " UNION SELECT 'ALL PROCEDURES IN SCHEMA'" - " UNION SELECT 'ALL ROUTINES IN SCHEMA'" - " UNION SELECT 'ALL SEQUENCES IN SCHEMA'" - " UNION SELECT 'ALL TABLES IN SCHEMA'" - " UNION SELECT 'DATABASE'" - " UNION SELECT 'DOMAIN'" - " UNION SELECT 'FOREIGN DATA WRAPPER'" - " UNION SELECT 'FOREIGN SERVER'" - " UNION SELECT 'FUNCTION'" - " UNION SELECT 'LANGUAGE'" - " UNION SELECT 'LARGE OBJECT'" - " UNION SELECT 'PROCEDURE'" - " UNION SELECT 'ROUTINE'" - " UNION SELECT 'SCHEMA'" - " UNION SELECT 'SEQUENCE'" - " UNION SELECT 'TABLE'" - " UNION SELECT 'TABLESPACE'" - " UNION SELECT 'TYPE'"); + " UNION SELECT 'ALL FUNCTIONS IN SCHEMA', NULL, true" + " UNION SELECT 'ALL PROCEDURES IN SCHEMA', NULL, true" + " UNION SELECT 'ALL ROUTINES IN SCHEMA', NULL, true" + " UNION SELECT 'ALL SEQUENCES IN SCHEMA', NULL, true" + " UNION SELECT 'ALL TABLES IN SCHEMA', NULL, true" + " UNION SELECT 'DATABASE', NULL, true" + " UNION SELECT 'DOMAIN', NULL, true" + " UNION SELECT 'FOREIGN DATA WRAPPER', NULL, true" + " UNION SELECT 'FOREIGN SERVER', NULL, true" + " UNION SELECT 'FUNCTION', NULL, true" + " UNION SELECT 'LANGUAGE', NULL, true" + " UNION SELECT 'LARGE OBJECT', NULL, true" + " UNION SELECT 'PROCEDURE', NULL, true" + " UNION SELECT 'ROUTINE', NULL, true" + " UNION SELECT 'SCHEMA', NULL, true" + " UNION SELECT 'SEQUENCE', NULL, true" + " UNION SELECT 'TABLE', NULL, true" + " UNION SELECT 'TABLESPACE', NULL, true" + " UNION SELECT 'TYPE', NULL, true"); } else if (TailMatches("GRANT|REVOKE", MatchAny, "ON", "ALL")) COMPLETE_WITH("FUNCTIONS IN SCHEMA", @@ -3789,11 +3799,11 @@ psql_completion(const char *text, int start, int e= nd) /* Complete LOCK [TABLE] [ONLY] with a list of tables */ else if (Matches("LOCK")) COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, - " UNION SELECT 'TABLE'" - " UNION SELECT 'ONLY'"); + " UNION SELECT 'TABLE', NULL, true" + " UNION SELECT 'ONLY', NULL, true"); else if (Matches("LOCK", "TABLE")) COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, - " UNION SELECT 'ONLY'"); + " UNION SELECT 'ONLY', NULL, true"); else if (Matches("LOCK", "TABLE", "ONLY") || Matches("LOCK", "ONLY")) COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL); /* For the following, handle the case of a single table only for now */ @@ -3831,7 +3841,7 @@ psql_completion(const char *text, int start, int end= ) = /* NOTIFY --- can be inside EXPLAIN, RULE, etc */ else if (TailMatches("NOTIFY")) - COMPLETE_WITH_QUERY("SELECT pg_catalog.quote_ident(channel) FROM pg_cat= alog.pg_listening_channels() AS channel WHERE substring(pg_catalog.quote_i= dent(channel),1,%d)=3D'%s'"); + COMPLETE_WITH_QUERY("SELECT channel FROM pg_catalog.pg_listening_channe= ls() AS channel WHERE substring(channel,1,%d)=3D'%s'"); = /* OPTIONS */ else if (TailMatches("OPTIONS")) @@ -3875,7 +3885,7 @@ psql_completion(const char *text, int start, int end= ) COMPLETE_WITH("VIEW"); else if (Matches("REFRESH", "MATERIALIZED", "VIEW")) COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, - " UNION SELECT 'CONCURRENTLY'"); + " UNION SELECT 'CONCURRENTLY', NULL, true"); else if (Matches("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY")) COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL); else if (Matches("REFRESH", "MATERIALIZED", "VIEW", MatchAny)) @@ -3898,15 +3908,15 @@ psql_completion(const char *text, int start, int e= nd) else if (Matches("REINDEX", "TABLE") || Matches("REINDEX", "(*)", "TABLE")) COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexables, - " UNION SELECT 'CONCURRENTLY'"); + " UNION SELECT 'CONCURRENTLY', NULL, true"); else if (Matches("REINDEX", "INDEX") || Matches("REINDEX", "(*)", "INDEX")) COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, - " UNION SELECT 'CONCURRENTLY'"); + " UNION SELECT 'CONCURRENTLY', NULL, true"); else if (Matches("REINDEX", "SCHEMA") || Matches("REINDEX", "(*)", "SCHEMA")) COMPLETE_WITH_QUERY(Query_for_list_of_schemas - " UNION SELECT 'CONCURRENTLY'"); + " UNION SELECT 'CONCURRENTLY', NULL, true"); else if (Matches("REINDEX", "SYSTEM|DATABASE") || Matches("REINDEX", "(*)", "SYSTEM|DATABASE")) COMPLETE_WITH_QUERY(Query_for_list_of_databases @@ -3997,7 +4007,8 @@ psql_completion(const char *text, int start, int end= ) COMPLETE_WITH("ONLY", "WRITE"); /* SET CONSTRAINTS */ else if (Matches("SET", "CONSTRAINTS")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_constraints_with_schema, "= UNION SELECT 'ALL'"); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_constraints_with_schema, + "UNION SELECT 'ALL', NULL, true"); /* Complete SET CONSTRAINTS with DEFERRED|IMMEDIATE */ else if (Matches("SET", "CONSTRAINTS", MatchAny)) COMPLETE_WITH("DEFERRED", "IMMEDIATE"); @@ -4091,11 +4102,11 @@ psql_completion(const char *text, int start, int e= nd) /* TRUNCATE */ else if (Matches("TRUNCATE")) COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_truncatables, - " UNION SELECT 'TABLE'" - " UNION SELECT 'ONLY'"); + " UNION SELECT 'TABLE', NULL, true" + " UNION SELECT 'ONLY', NULL, true"); else if (Matches("TRUNCATE", "TABLE")) COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_truncatables, - " UNION SELECT 'ONLY'"); + " UNION SELECT 'ONLY', NULL, true"); else if (HeadMatches("TRUNCATE") && TailMatches("ONLY")) COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_truncatables, NULL); else if (Matches("TRUNCATE", MatchAny) || @@ -4107,7 +4118,7 @@ psql_completion(const char *text, int start, int end= ) = /* UNLISTEN */ else if (Matches("UNLISTEN")) - COMPLETE_WITH_QUERY("SELECT pg_catalog.quote_ident(channel) FROM pg_cat= alog.pg_listening_channels() AS channel WHERE substring(pg_catalog.quote_i= dent(channel),1,%d)=3D'%s' UNION SELECT '*'"); + COMPLETE_WITH_QUERY("SELECT channel FROM pg_catalog.pg_listening_channe= ls() AS channel WHERE substring(channel,1,%d)=3D'%s' UNION SELECT '*'"); = /* UPDATE --- can be inside EXPLAIN, RULE, etc */ /* If prev. word is UPDATE suggest a list of tables */ @@ -4145,25 +4156,25 @@ psql_completion(const char *text, int start, int e= nd) */ else if (Matches("VACUUM")) COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_vacuumables, - " UNION SELECT 'FULL'" - " UNION SELECT 'FREEZE'" - " UNION SELECT 'ANALYZE'" - " UNION SELECT 'VERBOSE'"); + " UNION SELECT 'FULL', NULL, true" + " UNION SELECT 'FREEZE', NULL, true" + " UNION SELECT 'ANALYZE', NULL, true" + " UNION SELECT 'VERBOSE', NULL, true"); else if (Matches("VACUUM", "FULL")) COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_vacuumables, - " UNION SELECT 'FREEZE'" - " UNION SELECT 'ANALYZE'" - " UNION SELECT 'VERBOSE'"); + " UNION SELECT 'FREEZE', NULL, true" + " UNION SELECT 'ANALYZE', NULL, true" + " UNION SELECT 'VERBOSE', NULL, true"); else if (Matches("VACUUM", "FREEZE") || Matches("VACUUM", "FULL", "FREEZE")) COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_vacuumables, - " UNION SELECT 'VERBOSE'" - " UNION SELECT 'ANALYZE'"); + " UNION SELECT 'VERBOSE', NULL, true" + " UNION SELECT 'ANALYZE', NULL, true"); else if (Matches("VACUUM", "VERBOSE") || Matches("VACUUM", "FULL|FREEZE", "VERBOSE") || Matches("VACUUM", "FULL", "FREEZE", "VERBOSE")) COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_vacuumables, - " UNION SELECT 'ANALYZE'"); + " UNION SELECT 'ANALYZE', NULL, true"); else if (HeadMatches("VACUUM", "(*") && !HeadMatches("VACUUM", "(*)")) { @@ -4551,7 +4562,14 @@ static char * complete_from_query(const char *text, int state) { /* query is assumed to work for any server version */ - return _complete_from_query(completion_charp, NULL, text, state); + return _complete_from_query(completion_charp, NULL, false, text, state); +} + +static char * +complete_from_query_verbatim(const char *text, int state) +{ + /* query is assumed to work for any server version */ + return _complete_from_query(completion_charp, NULL, true, text, state); } = static char * @@ -4566,7 +4584,7 @@ complete_from_versioned_query(const char *text, int = state) if (vquery->query =3D=3D NULL) return NULL; = - return _complete_from_query(vquery->query, NULL, text, state); + return _complete_from_query(vquery->query, NULL, false, text, state); } = static char * @@ -4574,7 +4592,7 @@ complete_from_schema_query(const char *text, int sta= te) { /* query is assumed to work for any server version */ return _complete_from_query(completion_charp, completion_squery, - text, state); + false, text, state); } = static char * @@ -4600,7 +4618,7 @@ complete_from_versioned_schema_query(const char *tex= t, int state) } = return _complete_from_query(vquery ? vquery->query : NULL, - squery, text, state); + squery, false, text, state); } = = @@ -4618,28 +4636,40 @@ complete_from_versioned_schema_query(const char *t= ext, int state) * completion_info_charp2. * * 2. A schema query used for completion of both schema and relation name= s. - * These are more complex and must contain in the following order: - * %d %s %d %s %d %s %s %d %s - * where %d is the string length of the text and %s the text itself. + * This is represented by a SchemaQuery object; see that typedef for deta= ils. + * + * In either case, the query can return up to three columns: an object na= me + * column, a schema name column, and a boolean column which if TRUE indic= ates + * that the object-name column should be returned verbatim. If the boole= an + * column is false or omitted, we will combine and appropriately quote th= e + * schema and object names. * * If both simple_query and schema_query are non-NULL, then we construct * a schema query and append the (uninterpreted) string simple_query to i= t. + * The simple query must return all three columns in this case. * * 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. * + * If "verbatim" is true, then we use the given text as-is to match the + * query results; otherwise we parse it as a possibly-qualified identifie= r, + * and reconstruct suitable quoting afterward. This also changes the + * default assumption about whether the results are verbatim. + * * "text" and "state" are supplied by readline. */ static char * _complete_from_query(const char *simple_query, const SchemaQuery *schema_query, + bool verbatim, const char *text, int state) { - static int list_index, - byte_length; + static int list_index; static PGresult *result =3D NULL; + static bool schemaquoted; + static bool objectquoted; = /* * If this is the first time for this completion, we fetch a list of our @@ -4648,31 +4678,56 @@ _complete_from_query(const char *simple_query, if (state =3D=3D 0) { PQExpBufferData query_buffer; - char *e_text; + char *schemaname; + char *objectname; + int schema_length =3D 0; + int object_length =3D 0; + char *e_schemaname; + char *e_objectname; char *e_info_charp; char *e_info_charp2; - const char *pstr =3D text; - int char_length =3D 0; = + /* Reset static state, ensuring no memory leaks */ list_index =3D 0; - byte_length =3D strlen(text); + PQclear(result); + result =3D NULL; + + if (verbatim) + { + objectname =3D pg_strdup(text); + schemaname =3D NULL; + } + else + { + /* Parse text, splitting into schema and object name if needed */ + parse_identifier(text, + &schemaname, &objectname, + &schemaquoted, &objectquoted); + } = /* - * Count length as number of characters (not bytes), for passing to + * Count lengths as number of characters (not bytes), for passing to * substring */ - while (*pstr) + if (schemaname) { - char_length++; - pstr +=3D PQmblenBounded(pstr, pset.encoding); + for (const char *p =3D schemaname; + *p; + p +=3D PQmblenBounded(p, pset.encoding)) + schema_length++; } - - /* Free any prior result */ - PQclear(result); - result =3D NULL; + for (const char *p =3D objectname; + *p; + p +=3D PQmblenBounded(p, pset.encoding)) + object_length++; = /* Set up suitably-escaped copies of textual inputs */ - e_text =3D escape_string(text); + if (schemaname) + e_schemaname =3D escape_string(schemaname); + else + e_schemaname =3D NULL; + + e_objectname =3D escape_string(objectname); = if (completion_info_charp) e_info_charp =3D escape_string(completion_info_charp); @@ -4694,8 +4749,14 @@ _complete_from_query(const char *simple_query, if (qualresult =3D=3D NULL) qualresult =3D schema_query->result; = + /* + * We issue different queries depending on whether the input is + * qualified or not. + */ + if (schemaname =3D=3D NULL) + { /* Get unqualified names matching the input-so-far */ - appendPQExpBuffer(&query_buffer, "SELECT %s FROM %s WHERE ", + appendPQExpBuffer(&query_buffer, "SELECT %s, NULL::pg_catalog.text, fa= lse FROM %s WHERE ", schema_query->result, schema_query->catname); if (schema_query->selcondition) @@ -4703,7 +4764,7 @@ _complete_from_query(const char *simple_query, schema_query->selcondition); appendPQExpBuffer(&query_buffer, "substring(%s,1,%d)=3D'%s'", schema_query->result, - char_length, e_text); + object_length, e_objectname); appendPQExpBuffer(&query_buffer, " AND %s", schema_query->viscondition); = @@ -4715,35 +4776,32 @@ _complete_from_query(const char *simple_query, */ if (strcmp(schema_query->catname, "pg_catalog.pg_class c") =3D=3D 0 && - strncmp(text, "pg_", 3) !=3D 0) + strncmp(objectname, "pg_", 3) !=3D 0) { appendPQExpBufferStr(&query_buffer, " AND c.relnamespace <> (SELECT oid FROM" " pg_catalog.pg_namespace WHERE nspname =3D 'pg_catalog')"); } = - /* - * Add in matching schema names, but only if there is more than - * one potential match among schema names. - */ + /* Add in schema names matching the input-so-far */ appendPQExpBuffer(&query_buffer, "\nUNION\n" - "SELECT pg_catalog.quote_ident(n.nspname) || '.' " + "SELECT NULL::pg_catalog.text, n.nspname, false " "FROM pg_catalog.pg_namespace n " - "WHERE substring(pg_catalog.quote_ident(n.nspname) || '.',1,%d)=3D= '%s'", - char_length, e_text); - appendPQExpBuffer(&query_buffer, - " AND (SELECT pg_catalog.count(*)" - " FROM pg_catalog.pg_namespace" - " WHERE substring(pg_catalog.quote_ident(nspname) || '.',1,%d) =3D= " - " substring('%s',1,pg_catalog.length(pg_catalog.quote_ident(nspn= ame))+1)) > 1", - char_length, e_text); + "WHERE substring(n.nspname,1,%d)=3D'%s'", + object_length, e_objectname); = /* - * Add in matching qualified names, but only if there is exactly - * one schema matching the input-so-far. + * Likewise, suppress system schemas unless the input-so-far + * begins with "pg_". */ - appendPQExpBuffer(&query_buffer, "\nUNION\n" - "SELECT pg_catalog.quote_ident(n.nspname) || '.' || %s " + if (strncmp(objectname, "pg_", 3) !=3D 0) + appendPQExpBufferStr(&query_buffer, + " AND n.nspname NOT LIKE 'pg\\_%'"); + } + else + { + /* Input is qualified, so produce only qualified names */ + appendPQExpBuffer(&query_buffer, "SELECT %s, n.nspname, false " "FROM %s, pg_catalog.pg_namespace n " "WHERE %s =3D n.oid AND ", qualresult, @@ -4752,24 +4810,12 @@ _complete_from_query(const char *simple_query, if (schema_query->selcondition) appendPQExpBuffer(&query_buffer, "%s AND ", schema_query->selcondition); - appendPQExpBuffer(&query_buffer, "substring(pg_catalog.quote_ident(n.n= spname) || '.' || %s,1,%d)=3D'%s'", + appendPQExpBuffer(&query_buffer, "substring(%s,1,%d)=3D'%s' AND ", qualresult, - char_length, e_text); - - /* - * This condition exploits the single-matching-schema rule to - * speed up the query - */ - appendPQExpBuffer(&query_buffer, - " AND substring(pg_catalog.quote_ident(n.nspname) || '.',1,%d) =3D= " - " substring('%s',1,pg_catalog.length(pg_catalog.quote_ident(n.ns= pname))+1)", - char_length, e_text); - appendPQExpBuffer(&query_buffer, - " AND (SELECT pg_catalog.count(*)" - " FROM pg_catalog.pg_namespace" - " WHERE substring(pg_catalog.quote_ident(nspname) || '.',1,%d) =3D= " - " substring('%s',1,pg_catalog.length(pg_catalog.quote_ident(nspn= ame))+1)) =3D 1", - char_length, e_text); + object_length, e_objectname); + appendPQExpBuffer(&query_buffer, "substring(n.nspname,1,%d)=3D'%s'", + schema_length, e_schemaname); + } = /* If an addon query was provided, use it */ if (simple_query) @@ -4780,7 +4826,7 @@ _complete_from_query(const char *simple_query, Assert(simple_query); /* simple_query is an sprintf-style format string */ appendPQExpBuffer(&query_buffer, simple_query, - char_length, e_text, + object_length, e_objectname, e_info_charp, e_info_charp, e_info_charp2, e_info_charp2); } @@ -4792,25 +4838,49 @@ _complete_from_query(const char *simple_query, result =3D exec_query(query_buffer.data); = termPQExpBuffer(&query_buffer); - free(e_text); + if (e_schemaname) + free(e_schemaname); + free(e_objectname); if (e_info_charp) free(e_info_charp); if (e_info_charp2) free(e_info_charp2); } = - /* Find something that matches */ - if (result && PQresultStatus(result) =3D=3D PGRES_TUPLES_OK) + /* Return the next result, if there is one */ + if (result && PQresultStatus(result) =3D=3D PGRES_TUPLES_OK && + list_index < PQntuples(result)) { - const char *item; + const char *item =3D NULL; + const char *nsp =3D NULL; + bool thisverbatim =3D verbatim; + + if (!PQgetisnull(result, list_index, 0)) + item =3D PQgetvalue(result, list_index, 0); + if (PQnfields(result) > 1 && + !PQgetisnull(result, list_index, 1)) + nsp =3D PQgetvalue(result, list_index, 1); + if (PQnfields(result) > 2) + thisverbatim =3D (PQgetvalue(result, list_index, 2)[0] =3D=3D 't'); + list_index++; + + if (thisverbatim) + return pg_strdup(item); = - while (list_index < PQntuples(result) && - (item =3D PQgetvalue(result, list_index++, 0))) - if (pg_strncasecmp(text, item, byte_length) =3D=3D 0) - return pg_strdup(item); + /* + * Hack: if we're returning one single schema name, don't let Readline + * add a space after it. Otherwise it'll stop being part of the + * completion subject text, which is not what we want. + */ +#ifdef HAVE_RL_COMPLETION_APPEND_CHARACTER + if (item =3D=3D NULL && nsp !=3D NULL && PQntuples(result) =3D=3D 1) + rl_completion_append_character =3D '\0'; +#endif + + return requote_identifier(nsp, item, schemaquoted, objectquoted); } = - /* If nothing matches, free the db structure and return null */ + /* If nothing (else) matches, free the db structure and return null */ PQclear(result); result =3D NULL; return NULL; @@ -5144,6 +5214,199 @@ escape_string(const char *text) } = = +/* + * parse_identifier - Parse a possibly-schema-qualified SQL identifier. + * + * This involves splitting off the schema name if present, de-quoting, + * and downcasing any unquoted text. We are a bit laxer than the backend + * in that we allow just portions of a name to be quoted --- that's becau= se + * psql metacommands have traditionally behaved that way. + * + * Outputs are a malloc'd schema name (NULL if none), malloc'd object nam= e, + * and booleans telling whether any part of the schema and object name wa= s + * double-quoted. + */ +static void +parse_identifier(const char *ident, + char **schemaname, char **objectname, + bool *schemaquoted, bool *objectquoted) +{ + size_t buflen =3D strlen(ident) + 1; + bool enc_is_single_byte =3D (pg_encoding_max_length(pset.encoding) =3D=3D= 1); + char *sname; + char *oname; + char *optr; + bool inquotes; + + /* Initialize, making a certainly-large-enough output buffer */ + sname =3D NULL; + oname =3D pg_malloc(buflen); + *schemaquoted =3D *objectquoted =3D false; + /* Scan */ + optr =3D oname; + inquotes =3D false; + while (*ident) + { + unsigned char ch =3D (unsigned char) *ident++; + + if (ch =3D=3D '"') + { + if (inquotes && *ident =3D=3D '"') + { + /* two quote marks within a quoted identifier =3D emit quote */ + *optr++ =3D '"'; + ident++; + } + else + { + inquotes =3D !inquotes; + *objectquoted =3D true; + } + } + else if (ch =3D=3D '.' && !inquotes) + { + /* Found a schema name, transfer it to sname / *schemaquoted */ + *optr =3D '\0'; + free(sname); /* drop any catalog name */ + sname =3D oname; + oname =3D pg_malloc(buflen); + optr =3D oname; + *schemaquoted =3D *objectquoted; + *objectquoted =3D false; + } + else + { + if (!inquotes) + { + /* + * This downcasing transformation should match the backend's + * downcase_identifier() as best we can. We do not know the + * backend's locale, though, so it's necessarily approximate. + * We assume that psql is operating in the same locale and + * encoding as the backend. + */ + if (ch >=3D 'A' && ch <=3D 'Z') + ch +=3D 'a' - 'A'; + else if (enc_is_single_byte && IS_HIGHBIT_SET(ch) && isupper(ch)) + ch =3D tolower(ch); + } + *optr++ =3D (char) ch; + } + } + + *optr =3D '\0'; + *schemaname =3D sname; + *objectname =3D oname; +} + + +/* + * requote_identifier - Reconstruct a possibly-schema-qualified SQL ident= ifier. + * + * Build a malloc'd string containing the identifier, with quoting applie= d + * as necessary. This is more or less the inverse of parse_identifier; + * in particular, if an input component was quoted, we'll quote the outpu= t + * even when that isn't strictly required. + * + * Unlike parse_identifier, we handle the case where a schema and no + * object name is provided, producing just "schema.". + */ +static char * +requote_identifier(const char *schemaname, const char *objectname, + bool quote_schema, bool quote_object) +{ + char *result; + size_t buflen =3D 1; /* count the trailing \0 */ + char *ptr; + + /* + * We could use PQescapeIdentifier for some of this, but not all, and it + * adds more notational cruft than it seems worth. + */ + if (schemaname) + { + buflen +=3D strlen(schemaname) + 1; /* +1 for the dot */ + if (!quote_schema) + quote_schema =3D identifier_needs_quotes(schemaname); + if (quote_schema) + { + buflen +=3D 2; /* account for quote marks */ + for (const char *p =3D schemaname; *p; p++) + { + if (*p =3D=3D '"') + buflen++; + } + } + } + if (objectname) + { + buflen +=3D strlen(objectname); + if (!quote_object) + quote_object =3D identifier_needs_quotes(objectname); + if (quote_object) + { + buflen +=3D 2; /* account for quote marks */ + for (const char *p =3D objectname; *p; p++) + { + if (*p =3D=3D '"') + buflen++; + } + } + } + result =3D pg_malloc(buflen); + ptr =3D result; + if (schemaname) + { + if (quote_schema) + *ptr++ =3D '"'; + for (const char *p =3D schemaname; *p; p++) + { + *ptr++ =3D *p; + if (*p =3D=3D '"') + *ptr++ =3D '"'; + } + if (quote_schema) + *ptr++ =3D '"'; + *ptr++ =3D '.'; + } + if (objectname) + { + if (quote_object) + *ptr++ =3D '"'; + for (const char *p =3D objectname; *p; p++) + { + *ptr++ =3D *p; + if (*p =3D=3D '"') + *ptr++ =3D '"'; + } + if (quote_object) + *ptr++ =3D '"'; + } + *ptr =3D '\0'; + return result; +} + + +/* + * Detect whether an identifier must be double-quoted. + * + * Note we'll quote anything that's not ASCII; the backend's quote_ident(= ) + * does the same. + */ +static bool +identifier_needs_quotes(const char *ident) +{ + if (!((ident[0] >=3D 'a' && ident[0] <=3D 'z') || ident[0] =3D=3D '_')) + return true; + if (strspn(ident, "abcdefghijklmnopqrstuvwxyz0123456789_") !=3D strlen(i= dent)) + return true; + + /* XXX is it worth checking for SQL reserved words? */ + + return false; +} + + /* * Execute a query and report any errors. This should be the preferred wa= y of * talking to the database in this file. ------- =_aaaaaaaaaa0--