psql: Generic tab completion support for enum and bool GUCs.
authorAndres Freund <andres@anarazel.de>
Tue, 8 Sep 2015 18:57:35 +0000 (20:57 +0200)
committerAndres Freund <andres@anarazel.de>
Tue, 8 Sep 2015 18:57:35 +0000 (20:57 +0200)
Author: Pavel Stehule
Reviewed-By: Andres Freund
Discussion: 5594FE7A.5050205@iki.fi

src/bin/psql/tab-complete.c

index bdee0d509d49e5ff8d0107f6ea29393dc236d6f1..0cb34640eded0f1e317890bc1ac7371f64571367 100644 (file)
@@ -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
 
 /*