]> granicus.if.org Git - postgresql/commitdiff
psql: when tab-completing, use quotes on file names that need them
authorAlvaro Herrera <alvherre@alvh.no-ip.org>
Tue, 28 Feb 2012 04:06:29 +0000 (01:06 -0300)
committerAlvaro Herrera <alvherre@alvh.no-ip.org>
Tue, 28 Feb 2012 04:06:29 +0000 (01:06 -0300)
psql backslash commands that deal with file or directory names require
quotes around those that have spaces, single quotes, or backslashes.
However, tab-completing such names does not provide said quotes, and is
thus almost useless with them.

This patch fixes the problem by having a wrapper function around
rl_filename_completion_function that dequotes on input and quotes on
output.  This eases dealing with such names.

Author: Noah Misch

src/bin/psql/stringutils.c
src/bin/psql/stringutils.h
src/bin/psql/tab-complete.c

index 3b5ce1ba4bf3c54b2388474d0afa691bdb8bac09..77387dcf3deaab0bac0b58751e8359279afe4134 100644 (file)
@@ -272,3 +272,72 @@ strip_quotes(char *source, char quote, char escape, int encoding)
 
        *dst = '\0';
 }
+
+
+/*
+ * quote_if_needed
+ *
+ * Opposite of strip_quotes().  If "source" denotes itself literally without
+ * quoting or escaping, returns NULL.  Otherwise, returns a malloc'd copy with
+ * quoting and escaping applied:
+ *
+ * source -                    string to parse
+ * entails_quote -     any of these present?  need outer quotes
+ * quote -                     doubled within string, affixed to both ends
+ * escape -                    doubled within string
+ * encoding -          the active character-set encoding
+ *
+ * Do not use this as a substitute for PQescapeStringConn().  Use it for
+ * strings to be parsed by strtokx() or psql_scan_slash_option().
+ */
+char *
+quote_if_needed(const char *source, const char *entails_quote,
+                               char quote, char escape, int encoding)
+{
+       const char *src;
+       char       *ret;
+       char       *dst;
+       bool            need_quotes = false;
+
+       psql_assert(source);
+       psql_assert(quote);
+
+       src = source;
+       dst = ret = pg_malloc(2 * strlen(src) + 3);     /* excess */
+
+       *dst++ = quote;
+
+       while (*src)
+       {
+               char            c = *src;
+               int                     i;
+
+               if (c == quote)
+               {
+                       need_quotes = true;
+                       *dst++ = quote;
+               }
+               else if (c == escape)
+               {
+                       need_quotes = true;
+                       *dst++ = escape;
+               }
+               else if (strchr(entails_quote, c))
+                       need_quotes = true;
+
+               i = PQmblen(src, encoding);
+               while (i--)
+                       *dst++ = *src++;
+       }
+
+       *dst++ = quote;
+       *dst = '\0';
+
+       if (!need_quotes)
+       {
+               free(ret);
+               ret = NULL;
+       }
+
+       return ret;
+}
index c7c5f3877d92fbea189b0619eda316d55ac57042..c64fc584585f0957b1db94119d994f6b7ca4ba7b 100644 (file)
@@ -19,4 +19,7 @@ extern char *strtokx(const char *s,
                bool del_quotes,
                int encoding);
 
+extern char *quote_if_needed(const char *source, const char *entails_quote,
+                               char quote, char escape, int encoding);
+
 #endif   /* STRINGUTILS_H */
index 3854f7f421fc853a60ce538f6e3f2f670f454021..6f481bb24dd40ebc59c1895be0c754a3fa45ea93 100644 (file)
@@ -680,6 +680,7 @@ static char *complete_from_list(const char *text, int state);
 static char *complete_from_const(const char *text, int state);
 static char **complete_from_variables(char *text,
                                                const char *prefix, const char *suffix);
+static char *complete_from_files(const char *text, int state);
 
 static char *pg_strdup_same_case(const char *s, const char *ref);
 static PGresult *exec_query(const char *query);
@@ -1630,7 +1631,10 @@ psql_completion(char *text, int start, int end)
                          pg_strcasecmp(prev3_wd, "BINARY") == 0) &&
                         (pg_strcasecmp(prev_wd, "FROM") == 0 ||
                          pg_strcasecmp(prev_wd, "TO") == 0))
-               matches = completion_matches(text, filename_completion_function);
+       {
+               completion_charp = "";
+               matches = completion_matches(text, complete_from_files);
+       }
 
        /* Handle COPY|BINARY <sth> FROM|TO filename */
        else if ((pg_strcasecmp(prev4_wd, "COPY") == 0 ||
@@ -2953,7 +2957,10 @@ psql_completion(char *text, int start, int end)
                         strcmp(prev_wd, "\\s") == 0 ||
                         strcmp(prev_wd, "\\w") == 0 || strcmp(prev_wd, "\\write") == 0
                )
-               matches = completion_matches(text, filename_completion_function);
+       {
+               completion_charp = "\\";
+               matches = completion_matches(text, complete_from_files);
+       }
 
        /*
         * Finally, we look through the list of "things", such as TABLE, INDEX and
@@ -3426,6 +3433,53 @@ complete_from_variables(char *text, const char *prefix, const char *suffix)
 }
 
 
+/*
+ * This function wraps rl_filename_completion_function() to strip quotes from
+ * the input before searching for matches and to quote any matches for which
+ * the consuming command will require it.
+ */
+static char *
+complete_from_files(const char *text, int state)
+{
+       static const char *unquoted_text;
+       char       *unquoted_match;
+       char       *ret = NULL;
+
+       if (state == 0)
+       {
+               /* Initialization: stash the unquoted input. */
+               unquoted_text = strtokx(text, "", NULL, "'", *completion_charp,
+                                                               false, true, pset.encoding);
+               /* expect a NULL return for the empty string only */
+               if (!unquoted_text)
+               {
+                       psql_assert(!*text);
+                       unquoted_text = text;
+               }
+       }
+
+       unquoted_match = filename_completion_function(unquoted_text, state);
+       if (unquoted_match)
+       {
+               /*
+                * Caller sets completion_charp to a zero- or one-character string
+                * containing the escape character.  This is necessary since \copy has
+                * no escape character, but every other backslash command recognizes
+                * "\" as an escape character.  Since we have only two callers, don't
+                * bother providing a macro to simplify this.
+                */
+               ret = quote_if_needed(unquoted_match, " \t\r\n\"`",
+                                                         '\'', *completion_charp, pset.encoding);
+               if (ret)
+                       free(unquoted_match);
+               else
+                       ret = unquoted_match;
+       }
+
+       return ret;
+}
+
+
 /* HELPER FUNCTIONS */