From 0f80c643c1afb7865e765b5a638f15103356f949 Mon Sep 17 00:00:00 2001 From: tanghy Date: Tue, 25 Jan 2022 14:18:43 +0900 Subject: [PATCH v13] Support tab completion with a query result for upper diff --git a/src/bin/psql/t/010_tab_completion.pl b/src/bin/psql/t/010_tab_completion.pl index d3d1bd650e..69cd75a7f9 100644 --- a/src/bin/psql/t/010_tab_completion.pl +++ b/src/bin/psql/t/010_tab_completion.pl @@ -41,9 +41,13 @@ $node->start; # set up a few database objects $node->safe_psql('postgres', "CREATE TABLE tab1 (f1 int, f2 text);\n" + . "CREATE TABLE onetab1 (f1 int);\n" . "CREATE TABLE mytab123 (f1 int, f2 text);\n" . "CREATE TABLE mytab246 (f1 int, f2 text);\n" - . "CREATE TYPE enum1 AS ENUM ('foo', 'bar', 'baz');\n"); + . "CREATE TABLE \"myTAB123\" (\"aF1\" int, f2 text);\n" + . "CREATE TYPE enum1 AS ENUM ('foo', 'bar', 'baz');\n" + . "CREATE TYPE myenum1 as enum ('BLACK');\n"); + # Developers would not appreciate this test adding a bunch of junk to # their ~/.psql_history, so be sure to redirect history into a temp file. @@ -150,6 +154,66 @@ check_completion("SEL\t", qr/SELECT /, "complete SEL to SELECT"); clear_query(); +# check set query command(upper case) completion for upper character inputs +check_completion("set BYT\t", qq/set BYT\b\b\bbytea_output /, "complete set BYT to set bytea_output"); + +clear_query(); + +# check set query command(lower case) completion for upper character inputs +check_completion("set bYT\t", qq/set bYT\b\bytea_output /, "complete set bYT to set bytea_output"); + +clear_query(); + +# check query command(upper case) completion for empty input +check_completion("update onetab1 \t", qr/update onetab1 SET /, "complete SQL key words for onetab1 with empty input"); + +clear_query(); + +# check query command(lower case) completion for empty input +check_completion("update onetab1 SET \t", qr/update onetab1 SET f1 /, "complete column name for onetab1 with empty input"); + +clear_query(); + +# check query command completion for upper character relation name +check_completion("update TAB1 SET \t", qr/update TAB1 SET \af/, "complete column name for TAB1"); + +clear_query(); + +# check quoted identifiers in table +check_completion("update \"my\t", qr/update \"myTAB123\" /, "complete quoted string1"); + +clear_query(); + +# check quoted identifiers in column +check_completion("update \"myTAB123\" SET \"aF\t", qr/update \"myTAB123\" SET \"aF1\" /, "complete quoted string2"); + +clear_query(); + +# check schema query(lower case) which is case-insensitive +check_completion("select oid from pg_Cla\t", qq/select oid from pg_Cla\b\b\bclass /, "complete schema query with lower case string"); + +clear_query(); + +# check schema query(upper case) which is case-insensitive +check_completion("select oid from Pg_cla\t", qq/select oid from Pg_cla\b\b\b\b\b\bpg_class /, "complete schema query with uppper case string"); + +clear_query(); + +# check schema.table query which is case-insensitive +check_completion("alter table PUBLIC.tab\t", qq/alter table PUBLIC.tab\b\b\b\b\b\b\b\b\b\bpublic.tab1 /, "complete schema.table without quoted identifiers"); + +clear_query(); + +# check schema.table query which is case-sensitive +check_completion("alter table PUBLIC.\"my\t", qq/alter table PUBLIC.\"my\b\b\b\b\b\b\b\b\b\bpublic.\"myTAB123\" /, "complete schema.table with quoted identifiers"); + +clear_query(); + +# check enum values which are case-insensitive +check_completion("ALTER TYPE myenum1 RENAME VALUE 'B\t", qr|'BLACK'|, "complete enum values"); + +clear_line(); + # check case variation is honored check_completion("sel\t", qr/select /, "complete sel to select"); diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c index 6bd33a06cb..97e5a3958e 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" @@ -286,6 +287,7 @@ do { \ */ #define COMPLETE_WITH_ENUM_VALUE(type) \ do { \ + completion_case_sensitive = true; \ char *_completion_schema; \ char *_completion_type; \ bool use_quotes; \ @@ -1062,7 +1064,7 @@ static const VersionedQuery Query_for_list_of_subscriptions[] = { }; /* - * This is a list of all "things" in Pgsql, which can show up after CREATE or + * This is a list of all "things" in pgsql, which can show up after CREATE or * DROP; and there is also a query to get a list of them. */ @@ -1212,6 +1214,7 @@ static char *complete_from_files(const char *text, int state); static char *pg_strdup_keyword_case(const char *s, const char *ref); static char *escape_string(const char *text); +static char *pg_string_tolower_if_ascii(const char *text); static PGresult *exec_query(const char *query); static char **get_previous_words(int point, char **buffer, int *nwords); @@ -4639,6 +4642,7 @@ _complete_from_query(const char *simple_query, { static int list_index, byte_length; + static bool casesensitive; static PGresult *result = NULL; /* @@ -4651,8 +4655,10 @@ _complete_from_query(const char *simple_query, char *e_text; char *e_info_charp; char *e_info_charp2; + char *le_str; const char *pstr = text; int char_length = 0; + casesensitive = completion_case_sensitive; list_index = 0; byte_length = strlen(text); @@ -4674,13 +4680,37 @@ _complete_from_query(const char *simple_query, /* Set up suitably-escaped copies of textual inputs */ e_text = escape_string(text); + if (!casesensitive) + { + /* Change the textual inputs to lower case if they're written by ASCII characters*/ + le_str = pg_string_tolower_if_ascii(e_text); + free(e_text); + e_text = le_str; + } + if (completion_info_charp) + { e_info_charp = escape_string(completion_info_charp); + if (!casesensitive) + { + le_str = pg_string_tolower_if_ascii(e_info_charp); + free(e_info_charp); + e_info_charp = le_str; + } + } else e_info_charp = NULL; if (completion_info_charp2) + { e_info_charp2 = escape_string(completion_info_charp2); + if (!casesensitive) + { + le_str = pg_string_tolower_if_ascii(e_info_charp2); + free(e_info_charp2); + e_info_charp2 = le_str; + } + } else e_info_charp2 = NULL; @@ -4715,7 +4745,7 @@ _complete_from_query(const char *simple_query, */ if (strcmp(schema_query->catname, "pg_catalog.pg_class c") == 0 && - strncmp(text, "pg_", 3) != 0) + pg_strncasecmp(text, "pg_", 3) != 0) { appendPQExpBufferStr(&query_buffer, " AND c.relnamespace <> (SELECT oid FROM" @@ -5144,6 +5174,46 @@ escape_string(const char *text) } +/* + * pg_string_tolower_if_ascii - Fold a string to lower case if the string is + * not quoted and written by ASCII characters. + * + * The returned value has to be freed. + */ +static char * +pg_string_tolower_if_ascii(const char *text) +{ + char *ret, + *p; + bool enc_is_single_byte; + bool notquoted = true; + int count = 0; + + ret = pg_strdup(text); + enc_is_single_byte = pg_wchar_table[pset.encoding].maxmblen == 1; + + for (p = ret; *p; p++) + { + if (p[0] == '"') + { + count++; + notquoted = false; + } + if (notquoted && (count % 2 == 0)) + { + unsigned char ch = (unsigned char) *p; + if (ch >= 'A' && ch <= 'Z') + ch += 'a' - 'A'; + else if (enc_is_single_byte && IS_HIGHBIT_SET(ch) && isupper(ch)) + ch = tolower(ch); + *p = ch; + } + } + + return ret; +} + + /* * Execute a query and report any errors. This should be the preferred way of * talking to the database in this file. -- 2.33.0.windows.2