From 5787d50acc9bb8125de26b986cf4ea8cd874feea Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Sat, 29 Mar 2008 19:19:14 +0000
Subject: [PATCH] Improve psql's tab completion to handle completing attribute
 names in cases where the relation name was schema-qualified, for example 
 UPDATE foo.bar SET <tab> Also support cases where the relation name was
 quoted unnecessarily, for example 	UPDATE "foo" SET <tab>

Greg Sabino Mullane, slightly simplified by myself.
---
 src/bin/psql/tab-complete.c | 128 ++++++++++++++++++++++++++++--------
 1 file changed, 102 insertions(+), 26 deletions(-)

diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 24a7bcab259..2a036e279d6 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -3,7 +3,7 @@
  *
  * Copyright (c) 2000-2008, PostgreSQL Global Development Group
  *
- * $PostgreSQL: pgsql/src/bin/psql/tab-complete.c,v 1.169 2008/01/01 19:45:56 momjian Exp $
+ * $PostgreSQL: pgsql/src/bin/psql/tab-complete.c,v 1.170 2008/03/29 19:19:14 tgl Exp $
  */
 
 /*----------------------------------------------------------------------
@@ -53,6 +53,7 @@
 #include "pqexpbuffer.h"
 #include "common.h"
 #include "settings.h"
+#include "stringutils.h"
 
 #ifdef HAVE_RL_FILENAME_COMPLETION_FUNCTION
 #define filename_completion_function rl_filename_completion_function
@@ -124,29 +125,70 @@ static int	completion_max_records;
  * Communication variables set by COMPLETE_WITH_FOO macros and then used by
  * the completion callback functions.  Ugly but there is no better way.
  */
-static const char *completion_charp;	/* to pass a string */
+static const char *completion_charp;			/* to pass a string */
 static const char *const * completion_charpp;	/* to pass a list of strings */
 static const char *completion_info_charp;		/* to pass a second string */
+static const char *completion_info_charp2;		/* to pass a third string */
 static const SchemaQuery *completion_squery;	/* to pass a SchemaQuery */
 
-/* A couple of 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?)
-   2) The results from a schema query you pass it.
-   3) The items from a null-pointer-terminated list.
-   4) A string constant
-   5) The list of attributes to the given table.
-*/
+/*
+ * 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?)
+ * 2) The results from a schema query you pass it.
+ * 3) The items from a null-pointer-terminated list.
+ * 4) A string constant.
+ * 5) The list of attributes of the given table (possibly schema-qualified).
+ */
 #define COMPLETE_WITH_QUERY(query) \
-do { completion_charp = query; matches = completion_matches(text, complete_from_query); } while(0)
+do { \
+	completion_charp = query; \
+	matches = completion_matches(text, complete_from_query); \
+} while (0)
+
 #define COMPLETE_WITH_SCHEMA_QUERY(query, addon) \
-do { completion_squery = &(query); completion_charp = addon; matches = completion_matches(text, complete_from_schema_query); } while(0)
+do { \
+	completion_squery = &(query); \
+	completion_charp = addon; \
+	matches = completion_matches(text, complete_from_schema_query); \
+} while (0)
+
 #define COMPLETE_WITH_LIST(list) \
-do { completion_charpp = list; matches = completion_matches(text, complete_from_list); } while(0)
+do { \
+	completion_charpp = list; \
+	matches = completion_matches(text, complete_from_list); \
+} while (0)
+
 #define COMPLETE_WITH_CONST(string) \
-do { completion_charp = string; matches = completion_matches(text, complete_from_const); } while(0)
-#define COMPLETE_WITH_ATTR(table, addon) \
-do {completion_charp = Query_for_list_of_attributes addon; completion_info_charp = table; matches = completion_matches(text, complete_from_query); } while(0)
+do { \
+	completion_charp = string; \
+	matches = completion_matches(text, complete_from_const); \
+} while (0)
+
+#define COMPLETE_WITH_ATTR(relation, addon) \
+do { \
+	char   *_completion_schema; \
+	char   *_completion_table; \
+\
+	_completion_schema = strtokx(relation, " \t\n\r", ".", "\"", 0, \
+								 false, false, pset.encoding); \
+	(void) strtokx(NULL, " \t\n\r", ".", "\"", 0, \
+				   false, false, pset.encoding); \
+	_completion_table = strtokx(NULL, " \t\n\r", ".", "\"", 0, \
+								false, false, pset.encoding); \
+	if (_completion_table == NULL) \
+	{ \
+		completion_charp = Query_for_list_of_attributes  addon; \
+		completion_info_charp = relation; \
+	} \
+	else \
+	{ \
+		completion_charp = Query_for_list_of_attributes_with_schema  addon; \
+		completion_info_charp = _completion_table; \
+		completion_info_charp2 = _completion_schema; \
+	} \
+	matches = completion_matches(text, complete_from_query); \
+} while (0)
 
 /*
  * Assembly instructions for schema queries
@@ -308,11 +350,12 @@ static const SchemaQuery Query_for_list_of_views = {
 /*
  * Queries to get lists of names of various kinds of things, possibly
  * restricted to names matching a partially entered name.  In these queries,
- * %s will be replaced by the text entered so far (suitably escaped to
- * become a SQL literal string).  %d will be replaced by the length of the
- * string (in unescaped form).	A second %s, if present, will be replaced
- * by a suitably-escaped version of the string provided in
- * completion_info_charp.
+ * the first %s will be replaced by the text entered so far (suitably escaped
+ * to become a SQL literal string).  %d will be replaced by the length of the
+ * string (in unescaped form).	A second and third %s, if present, will be
+ * replaced by a suitably-escaped version of the string provided in
+ * completion_info_charp.  A fourth and fifth %s are similarly replaced by
+ * completion_info_charp2.
  *
  * Beware that the allowed sequences of %s and %d are determined by
  * _complete_from_query().
@@ -325,9 +368,23 @@ static const SchemaQuery Query_for_list_of_views = {
 "   AND a.attnum > 0 "\
 "   AND NOT a.attisdropped "\
 "   AND substring(pg_catalog.quote_ident(attname),1,%d)='%s' "\
-"   AND pg_catalog.quote_ident(relname)='%s' "\
+"   AND (pg_catalog.quote_ident(relname)='%s' "\
+"        OR '\"' || relname || '\"'='%s') "\
 "   AND pg_catalog.pg_table_is_visible(c.oid)"
 
+#define Query_for_list_of_attributes_with_schema \
+"SELECT pg_catalog.quote_ident(attname) "\
+"  FROM pg_catalog.pg_attribute a, pg_catalog.pg_class c, pg_catalog.pg_namespace n "\
+" WHERE c.oid = a.attrelid "\
+"   AND n.oid = c.relnamespace "\
+"   AND a.attnum > 0 "\
+"   AND NOT a.attisdropped "\
+"   AND substring(pg_catalog.quote_ident(attname),1,%d)='%s' "\
+"   AND (pg_catalog.quote_ident(relname)='%s' "\
+"        OR '\"' || relname || '\"' ='%s') "\
+"   AND (pg_catalog.quote_ident(nspname)='%s' "\
+"        OR '\"' || nspname || '\"' ='%s') "
+
 #define Query_for_list_of_template_databases \
 "SELECT pg_catalog.quote_ident(datname) FROM pg_catalog.pg_database "\
 " WHERE substring(pg_catalog.quote_ident(datname),1,%d)='%s' and datistemplate IS TRUE"
@@ -584,9 +641,10 @@ psql_completion(char *text, int start, int end)
 	completion_charp = NULL;
 	completion_charpp = NULL;
 	completion_info_charp = NULL;
+	completion_info_charp2 = NULL;
 
 	/*
-	 * Scan the input line before our current position for the last four
+	 * Scan the input line before our current position for the last five
 	 * words. According to those we'll make some smart decisions on what the
 	 * user is probably intending to type. TODO: Use strtokx() to do this.
 	 */
@@ -2225,8 +2283,9 @@ complete_from_schema_query(const char *text, int state)
    The query can be one of two kinds:
    - A simple query which must contain a %d and a %s, which will be replaced
    by the string length of the text and the text itself. The query may also
-   have another %s in it, which will be replaced by the value of
-   completion_info_charp.
+   have up to four more %s in it; the first two such will be replaced by the
+   value of completion_info_charp, the next two by the value of
+   completion_info_charp2.
 	 or:
    - A schema query used for completion of both schema and relation names;
    these are more complex and must contain in the following order:
@@ -2255,6 +2314,7 @@ _complete_from_query(int is_schema_query, const char *text, int state)
 		PQExpBufferData query_buffer;
 		char	   *e_text;
 		char	   *e_info_charp;
+		char	   *e_info_charp2;
 
 		list_index = 0;
 		string_length = strlen(text);
@@ -2279,6 +2339,18 @@ _complete_from_query(int is_schema_query, const char *text, int state)
 		else
 			e_info_charp = NULL;
 
+		if (completion_info_charp2)
+		{
+			size_t		charp_len;
+
+			charp_len = strlen(completion_info_charp2);
+			e_info_charp2 = pg_malloc(charp_len * 2 + 1);
+			PQescapeString(e_info_charp2, completion_info_charp2,
+						   charp_len);
+		}
+		else
+			e_info_charp2 = NULL;
+
 		initPQExpBuffer(&query_buffer);
 
 		if (is_schema_query)
@@ -2374,7 +2446,9 @@ _complete_from_query(int is_schema_query, const char *text, int state)
 		{
 			/* completion_charp is an sprintf-style format string */
 			appendPQExpBuffer(&query_buffer, completion_charp,
-							  string_length, e_text, e_info_charp);
+							  string_length, e_text,
+							  e_info_charp, e_info_charp,
+							  e_info_charp2, e_info_charp2);
 		}
 
 		/* Limit the number of records in the result */
@@ -2387,6 +2461,8 @@ _complete_from_query(int is_schema_query, const char *text, int state)
 		free(e_text);
 		if (e_info_charp)
 			free(e_info_charp);
+		if (e_info_charp2)
+			free(e_info_charp2);
 	}
 
 	/* Find something that matches */
-- 
GitLab