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
 
 /*