From 81e6534c8a52c6856bd357e5e532bd8f7571cd98 Mon Sep 17 00:00:00 2001
From: Haiying Tang <tanghy.fnst@fujitsu.com>
Date: Wed, 23 Jun 2021 21:31:04 +0900
Subject: [PATCH] Support tab completion with a query result for upper
 character inputs in psql


diff --git a/src/bin/psql/t/010_tab_completion.pl b/src/bin/psql/t/010_tab_completion.pl
index 3c58d50118..2239f7f6fd 100644
--- a/src/bin/psql/t/010_tab_completion.pl
+++ b/src/bin/psql/t/010_tab_completion.pl
@@ -41,8 +41,10 @@ $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 TABLE mytab246 (f1 int, f2 text);\n"
+	  . "CREATE TABLE \"myTAB123\" (\"aF1\" int, f2 text);\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.
@@ -145,6 +147,61 @@ check_completion("SEL\t", qr/SELECT /, "complete SEL<tab> 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<tab> 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<tab> 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 case variation is honored
 check_completion("sel\t", qr/select /, "complete sel<tab> to select");
 
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 38af5682f2..7bdd7c9c85 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1014,7 +1014,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.
  */
 
@@ -1164,6 +1164,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(const char *text);
 static PGresult *exec_query(const char *query);
 
 static char **get_previous_words(int point, char **buffer, int *nwords);
@@ -1416,6 +1417,31 @@ ends_with(const char *s, char c)
 	return (length > 0 && s[length - 1] == c);
 }
 
+/*
+ * Check whether an input text only contain alphabet letters,
+ * dot, underscore, digits, backward slash and double quotes.
+ */
+static bool
+valid_input_text(const char *text)
+{
+	const unsigned char *ptr = (const unsigned char *) text;
+
+	/* Mustn't be zero-length */
+	if (*ptr == '\0')
+		return false;
+
+	while (*ptr)
+	{
+		if (strchr("ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz"
+				   "._0123456789\\\"", *ptr) != NULL)
+			ptr++;
+		else
+			return false;
+	}
+
+	return true;
+}
+
 /*
  * The completion function.
  *
@@ -4387,6 +4413,7 @@ _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;
 
@@ -4410,13 +4437,39 @@ _complete_from_query(const char *simple_query,
 		/* Set up suitably-escaped copies of textual inputs */
 		e_text = escape_string(text);
 
+		/* Change the textual inputs to lower case if the input text
+		 * only contain alphabet letters, dot, underscore, digits,
+		 * backward slash and double quotes.
+		 */
+		if(valid_input_text(e_text))
+		{
+			le_str = pg_string_tolower(e_text);
+			free(e_text);
+			e_text = le_str;
+		}
 		if (completion_info_charp)
+		{
 			e_info_charp = escape_string(completion_info_charp);
+			if(valid_input_text(e_info_charp))
+			{
+				le_str = pg_string_tolower(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(valid_input_text(e_info_charp2))
+			{
+				le_str = pg_string_tolower(e_info_charp2);
+				free(e_info_charp2);
+				e_info_charp2 = le_str;
+			}
+		}
 		else
 			e_info_charp2 = NULL;
 
@@ -4451,7 +4504,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"
@@ -4880,6 +4933,33 @@ escape_string(const char *text)
 }
 
 
+/*
+ * pg_string_tolower - Fold a string to lower case if the string is not quoted.
+ */
+static char *
+pg_string_tolower(const char *text)
+{
+	char	*ret,
+			*p;
+	bool	notquoted = true;
+	int		count = 0;
+
+	ret = pg_strdup(text);
+
+	for (p = ret; *p; p++)
+	{
+		if (p[0] == '"')
+		{
+			count++;
+			notquoted = false;
+		}
+		if (notquoted && (count % 2 == 0))
+			*p = pg_tolower((unsigned char) *p);
+	}
+	return ret;
+}
+
+
 /*
  * Execute a query and report any errors. This should be the preferred way of
  * talking to the database in this file.
-- 
2.31.1.windows.1

