From 3ae16798f0f9d2d941e50062b579c28c9b946c9e Mon Sep 17 00:00:00 2001 From: Andres Freund Date: Tue, 8 Sep 2015 20:57:35 +0200 Subject: [PATCH] psql: Generic tab completion support for enum and bool GUCs. Author: Pavel Stehule Reviewed-By: Andres Freund Discussion: 5594FE7A.5050205@iki.fi --- src/bin/psql/tab-complete.c | 132 ++++++++++++++++++++++++++---------- 1 file changed, 97 insertions(+), 35 deletions(-) diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c index bdee0d509d..0cb34640ed 100644 --- a/src/bin/psql/tab-complete.c +++ b/src/bin/psql/tab-complete.c @@ -759,6 +759,15 @@ static const SchemaQuery Query_for_list_of_matviews = { " (SELECT polrelid FROM pg_catalog.pg_policy "\ " WHERE pg_catalog.quote_ident(polname)='%s')" +#define Query_for_enum \ +" SELECT name FROM ( "\ +" SELECT pg_catalog.quote_ident(pg_catalog.unnest(enumvals)) AS name "\ +" FROM pg_catalog.pg_settings "\ +" WHERE pg_catalog.lower(name)=pg_catalog.lower('%s') "\ +" UNION ALL " \ +" SELECT 'DEFAULT' ) ss "\ +" WHERE pg_catalog.substring(name,1,%%d)='%%s'" + /* * 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. @@ -845,10 +854,13 @@ static char **complete_from_variables(const char *text, 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 PGresult *exec_query(const char *query); static void get_previous_words(int point, char **previous_words, int nwords); +static char *get_guctype(const char *varname); + #ifdef NOT_USED static char *quote_file_name(char *text, int match_type, char *quote_pointer); static char *dequote_file_name(char *text, char quote_char); @@ -3684,6 +3696,7 @@ psql_completion(const char *text, int start, int end) else if (pg_strcasecmp(prev3_wd, "SET") == 0 && (pg_strcasecmp(prev_wd, "TO") == 0 || strcmp(prev_wd, "=") == 0)) { + /* special cased code for individual GUCs */ if (pg_strcasecmp(prev2_wd, "DateStyle") == 0) { static const char *const my_list[] = @@ -3694,20 +3707,6 @@ psql_completion(const char *text, int start, int end) COMPLETE_WITH_LIST(my_list); } - else if (pg_strcasecmp(prev2_wd, "IntervalStyle") == 0) - { - static const char *const my_list[] = - {"postgres", "postgres_verbose", "sql_standard", "iso_8601", NULL}; - - COMPLETE_WITH_LIST(my_list); - } - else if (pg_strcasecmp(prev2_wd, "GEQO") == 0) - { - static const char *const my_list[] = - {"ON", "OFF", "DEFAULT", NULL}; - - COMPLETE_WITH_LIST(my_list); - } else if (pg_strcasecmp(prev2_wd, "search_path") == 0) { COMPLETE_WITH_QUERY(Query_for_list_of_schemas @@ -3717,10 +3716,34 @@ psql_completion(const char *text, int start, int end) } else { - static const char *const my_list[] = - {"DEFAULT", NULL}; + /* generic, type based, GUC support */ - COMPLETE_WITH_LIST(my_list); + char *guctype = get_guctype(prev2_wd); + + if (guctype && strcmp(guctype, "enum") == 0) + { + char querybuf[1024]; + + snprintf(querybuf, 1024, Query_for_enum, prev2_wd); + COMPLETE_WITH_QUERY(querybuf); + } + else if (guctype && strcmp(guctype, "bool") == 0) + { + static const char *const my_list[] = + {"on", "off", "true", "false", "yes", "no", "1", "0", "DEFAULT", NULL}; + + COMPLETE_WITH_LIST(my_list); + } + else + { + static const char *const my_list[] = + {"DEFAULT", NULL}; + + COMPLETE_WITH_LIST(my_list); + } + + if (guctype) + free(guctype); } } @@ -4263,30 +4286,15 @@ _complete_from_query(int is_schema_query, const char *text, int state) result = NULL; /* Set up suitably-escaped copies of textual inputs */ - e_text = pg_malloc(string_length * 2 + 1); - PQescapeString(e_text, text, string_length); + e_text = escape_string(text); if (completion_info_charp) - { - size_t charp_len; - - charp_len = strlen(completion_info_charp); - e_info_charp = pg_malloc(charp_len * 2 + 1); - PQescapeString(e_info_charp, completion_info_charp, - charp_len); - } + e_info_charp = escape_string(completion_info_charp); 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); - } + e_info_charp2 = escape_string(completion_info_charp2); else e_info_charp2 = NULL; @@ -4677,6 +4685,26 @@ pg_strdup_keyword_case(const char *s, const char *ref) } +/* + * escape_string - Escape argument for use as string literal. + * + * The returned value has to be freed. + */ +static char * +escape_string(const char *text) +{ + size_t text_length; + char *result; + + text_length = strlen(text); + + result = pg_malloc(text_length * 2 + 1); + PQescapeStringConn(pset.db, result, text, text_length, NULL); + + return result; +} + + /* * Execute a query and report any errors. This should be the preferred way of * talking to the database in this file. @@ -4790,6 +4818,40 @@ get_previous_words(int point, char **previous_words, int nwords) } } +/* + * Look up the type for the GUC variable with the passed name. + * + * Returns NULL if the variable is unknown. Otherwise the returned string, + * containing the type, has to be freed. + */ +static char * +get_guctype(const char *varname) +{ + PQExpBufferData query_buffer; + char *e_varname; + PGresult *result; + char *guctype = NULL; + + e_varname = escape_string(varname); + + initPQExpBuffer(&query_buffer); + appendPQExpBuffer(&query_buffer, + "SELECT vartype FROM pg_catalog.pg_settings " + "WHERE pg_catalog.lower(name) = pg_catalog.lower('%s')", + e_varname); + + result = exec_query(query_buffer.data); + termPQExpBuffer(&query_buffer); + free(e_varname); + + if (PQresultStatus(result) == PGRES_TUPLES_OK && PQntuples(result) > 0) + guctype = pg_strdup(PQgetvalue(result, 0, 0)); + + PQclear(result); + + return guctype; +} + #ifdef NOT_USED /*