summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/bin/psql/stringutils.c69
-rw-r--r--src/bin/psql/stringutils.h3
-rw-r--r--src/bin/psql/tab-complete.c58
3 files changed, 128 insertions, 2 deletions
diff --git a/src/bin/psql/stringutils.c b/src/bin/psql/stringutils.c
index 3b5ce1ba4bf..77387dcf3de 100644
--- a/src/bin/psql/stringutils.c
+++ b/src/bin/psql/stringutils.c
@@ -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;
+}
diff --git a/src/bin/psql/stringutils.h b/src/bin/psql/stringutils.h
index c7c5f3877d9..c64fc584585 100644
--- a/src/bin/psql/stringutils.h
+++ b/src/bin/psql/stringutils.h
@@ -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 */
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 3854f7f421f..6f481bb24dd 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -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 */