]> granicus.if.org Git - postgresql/commitdiff
Add tsearch tab completion support:
authorBruce Momjian <bruce@momjian.us>
Fri, 14 Sep 2007 04:25:24 +0000 (04:25 +0000)
committerBruce Momjian <bruce@momjian.us>
Fri, 14 Sep 2007 04:25:24 +0000 (04:25 +0000)
* adds a few missing words to some commands (like adding GIN as a valid
index type or OWNED BY for ALTER SEQUENCE,...)

* support for ALTER TABLE foo ENABLE/DISABLE REPLICA TRIGGER/RULE

* autocomplete CREATE DATABASE foo TEMPLATE (mostly done to prevent
conflicts with the TEMPLATE keyword for text search)

* support for ALTER/CREATE/DROP TEXT SEARCH as well as COMMENT ON TEXT
SEARCH and the corresponding psql backslash commands.
This proved a little more difficult than expected due to the fact that
words_after_create[] is used for two purposes - one is to provide a list
of words that follow immediatly after CREATE (or DROP) and the other
purpose is to use it for autocompleting anywhere in the statement if the
word in that struct is found with a query.
Since TEXT SEARCH CONFIGURATION|DICTIONARY|TEMPLATE|PARSER results in 3
words instead of one (as all the other words in that list are) I added a
flag to the struct to tell create_command_generator() to skip that entry
 for autocompleting immediatly after CREATE which feels like a dirty
hack (but that holds true for a lot of code in tab-complete.c).

Stefan Kaltenbrunner

src/bin/psql/tab-complete.c

index 9a9e952ac96c8b5761538fb8dda606656f7ae9ab..c30758f1f31fadc60e1632a184e4263c2cef3eb1 100644 (file)
@@ -3,7 +3,7 @@
  *
  * Copyright (c) 2000-2007, PostgreSQL Global Development Group
  *
- * $PostgreSQL: pgsql/src/bin/psql/tab-complete.c,v 1.166 2007/07/03 01:30:37 neilc Exp $
+ * $PostgreSQL: pgsql/src/bin/psql/tab-complete.c,v 1.167 2007/09/14 04:25:24 momjian Exp $
  */
 
 /*----------------------------------------------------------------------
@@ -328,6 +328,10 @@ static const SchemaQuery Query_for_list_of_views = {
 "   AND pg_catalog.quote_ident(relname)='%s' "\
 "   AND pg_catalog.pg_table_is_visible(c.oid)"
 
+#define Query_for_list_of_template_databases \
+"SELECT pg_catalog.quote_ident(datname) FROM pg_catalog.pg_database "\
+" WHERE substring(pg_catalog.quote_ident(datname),1,%d)='%s' and datistemplate IS TRUE"
+
 #define Query_for_list_of_databases \
 "SELECT pg_catalog.quote_ident(datname) FROM pg_catalog.pg_database "\
 " WHERE substring(pg_catalog.quote_ident(datname),1,%d)='%s'"
@@ -419,6 +423,22 @@ static const SchemaQuery Query_for_list_of_views = {
 "       (SELECT tgrelid FROM pg_catalog.pg_trigger "\
 "         WHERE pg_catalog.quote_ident(tgname)='%s')"
 
+#define Query_for_list_of_ts_configurations \
+"SELECT pg_catalog.quote_ident(cfgname) FROM pg_catalog.pg_ts_config "\
+" WHERE substring(pg_catalog.quote_ident(cfgname),1,%d)='%s'"
+
+#define Query_for_list_of_ts_dictionaries \
+"SELECT pg_catalog.quote_ident(dictname) FROM pg_catalog.pg_ts_dict "\
+" WHERE substring(pg_catalog.quote_ident(dictname),1,%d)='%s'"
+
+#define Query_for_list_of_ts_parsers \
+"SELECT pg_catalog.quote_ident(prsname) FROM pg_catalog.pg_ts_parser "\
+" WHERE substring(pg_catalog.quote_ident(prsname),1,%d)='%s'"
+
+#define Query_for_list_of_ts_templates \
+"SELECT pg_catalog.quote_ident(tmplname) FROM pg_catalog.pg_ts_template "\
+" WHERE substring(pg_catalog.quote_ident(tmplname),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.
@@ -429,6 +449,7 @@ typedef struct
        const char *name;
        const char *query;                      /* simple query, or NULL */
        const SchemaQuery *squery;      /* schema query, or NULL */
+       const bool noshow;      /* NULL or true if this word should not show up after CREATE or DROP */
 } pgsql_thing_t;
 
 static const pgsql_thing_t words_after_create[] = {
@@ -440,8 +461,10 @@ static const pgsql_thing_t words_after_create[] = {
         * CREATE CONSTRAINT TRIGGER is not supported here because it is designed
         * to be used only by pg_dump.
         */
+       {"CONFIGURATION", Query_for_list_of_ts_configurations, NULL, true},
        {"CONVERSION", "SELECT pg_catalog.quote_ident(conname) FROM pg_catalog.pg_conversion WHERE substring(pg_catalog.quote_ident(conname),1,%d)='%s'"},
        {"DATABASE", Query_for_list_of_databases},
+       {"DICTIONARY", Query_for_list_of_ts_dictionaries, NULL, true},
        {"DOMAIN", NULL, &Query_for_list_of_domains},
        {"FUNCTION", NULL, &Query_for_list_of_functions},
        {"GROUP", Query_for_list_of_roles},
@@ -449,6 +472,7 @@ static const pgsql_thing_t words_after_create[] = {
        {"INDEX", NULL, &Query_for_list_of_indexes},
        {"OPERATOR", NULL, NULL},       /* Querying for this is probably not such a
                                                                 * good idea. */
+       {"PARSER", Query_for_list_of_ts_parsers, NULL, true},
        {"ROLE", Query_for_list_of_roles},
        {"RULE", "SELECT pg_catalog.quote_ident(rulename) FROM pg_catalog.pg_rules WHERE substring(pg_catalog.quote_ident(rulename),1,%d)='%s'"},
        {"SCHEMA", Query_for_list_of_schemas},
@@ -456,12 +480,14 @@ static const pgsql_thing_t words_after_create[] = {
        {"TABLE", NULL, &Query_for_list_of_tables},
        {"TABLESPACE", Query_for_list_of_tablespaces},
        {"TEMP", NULL, NULL},           /* for CREATE TEMP TABLE ... */
+       {"TEMPLATE", Query_for_list_of_ts_templates, NULL, true},
+       {"TEXT SEARCH", NULL, NULL},
        {"TRIGGER", "SELECT pg_catalog.quote_ident(tgname) FROM pg_catalog.pg_trigger WHERE substring(pg_catalog.quote_ident(tgname),1,%d)='%s'"},
        {"TYPE", NULL, &Query_for_list_of_datatypes},
        {"UNIQUE", NULL, NULL},         /* for CREATE UNIQUE INDEX ... */
        {"USER", Query_for_list_of_roles},
        {"VIEW", NULL, &Query_for_list_of_views},
-       {NULL, NULL, NULL}                      /* end of list */
+       {NULL, NULL, NULL, false}                       /* end of list */
 };
 
 
@@ -531,14 +557,14 @@ psql_completion(char *text, int start, int end)
                "GRANT", "INSERT", "LISTEN", "LOAD", "LOCK", "MOVE", "NOTIFY", "PREPARE",
                "REASSIGN", "REINDEX", "RELEASE", "RESET", "REVOKE", "ROLLBACK",
                "SAVEPOINT", "SELECT", "SET", "SHOW", "START", "TRUNCATE", "UNLISTEN",
-               "UPDATE", "VACUUM", NULL
+               "UPDATE", "VACUUM", "VALUES", NULL
        };
 
        static const char *const backslash_commands[] = {
                "\\a", "\\connect", "\\C", "\\cd", "\\copy", "\\copyright",
                "\\d", "\\da", "\\db", "\\dc", "\\dC", "\\dd", "\\dD", "\\df",
-               "\\dg", "\\di", "\\dl", "\\dn", "\\do", "\\dp", "\\ds", "\\dS",
-               "\\dt", "\\dT", "\\dv", "\\du",
+               "\\dF", "\\dFd", "\\dFp", "\\dFt", "\\dg", "\\di", "\\dl", 
+               "\\dn", "\\do", "\\dp", "\\ds", "\\dS", "\\dt", "\\dT", "\\dv", "\\du",
                "\\e", "\\echo", "\\encoding",
                "\\f", "\\g", "\\h", "\\help", "\\H", "\\i", "\\l",
                "\\lo_import", "\\lo_export", "\\lo_list", "\\lo_unlink",
@@ -602,7 +628,7 @@ psql_completion(char *text, int start, int end)
                static const char *const list_ALTER[] =
                {"AGGREGATE", "CONVERSION", "DATABASE", "DOMAIN", "FUNCTION",
                        "GROUP", "INDEX", "LANGUAGE", "OPERATOR", "ROLE", "SCHEMA", "SEQUENCE", "TABLE",
-               "TABLESPACE", "TRIGGER", "TYPE", "USER", "VIEW", NULL};
+               "TABLESPACE", "TEXT SEARCH", "TRIGGER", "TYPE", "USER", "VIEW", NULL};
 
                COMPLETE_WITH_LIST(list_ALTER);
        }
@@ -643,7 +669,7 @@ psql_completion(char *text, int start, int end)
                         pg_strcasecmp(prev2_wd, "INDEX") == 0)
        {
                static const char *const list_ALTERINDEX[] =
-               {"SET TABLESPACE", "OWNER TO", "RENAME TO", NULL};
+               {"SET TABLESPACE", "OWNER TO", "RENAME TO", "SET", "RESET", NULL};
 
                COMPLETE_WITH_LIST(list_ALTERINDEX);
        }
@@ -714,7 +740,7 @@ psql_completion(char *text, int start, int end)
        {
                static const char *const list_ALTERSEQUENCE[] =
                {"INCREMENT", "MINVALUE", "MAXVALUE", "RESTART", "NO", "CACHE", "CYCLE",
-               "SET SCHEMA", "RENAME TO", NULL};
+               "SET SCHEMA", "OWNED BY", "RENAME TO", NULL};
 
                COMPLETE_WITH_LIST(list_ALTERSEQUENCE);
        }
@@ -769,11 +795,38 @@ psql_completion(char *text, int start, int end)
                         pg_strcasecmp(prev2_wd, "TABLE") == 0)
        {
                static const char *const list_ALTER2[] =
-               {"ADD", "ALTER", "CLUSTER ON", "DROP", "RENAME", "OWNER TO",
-               "SET", NULL};
+               {"ADD", "ALTER", "CLUSTER ON", "DISABLE", "DROP", "ENABLE", "INHERIT",
+               "NO INHERIT", "RENAME", "RESET", "OWNER TO", "SET", NULL};
 
                COMPLETE_WITH_LIST(list_ALTER2);
        }
+       /* ALTER TABLE xxx ENABLE */
+       else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
+                        pg_strcasecmp(prev3_wd, "TABLE") == 0 &&
+                        pg_strcasecmp(prev_wd, "ENABLE") == 0)
+       {
+               static const char *const list_ALTERENABLE[] =
+               {"ALWAYS","REPLICA","RULE", "TRIGGER", NULL};
+               COMPLETE_WITH_LIST(list_ALTERENABLE);
+       }
+       else if (pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
+                       pg_strcasecmp(prev2_wd, "ENABLE") == 0 &&
+                       (pg_strcasecmp(prev_wd, "REPLICA") == 0 ||
+                        pg_strcasecmp(prev_wd, "ALWAYS") == 0))
+       {
+               static const char *const list_ALTERENABLE2[] =
+               {"RULE", "TRIGGER", NULL};
+               COMPLETE_WITH_LIST(list_ALTERENABLE2);
+       }
+       else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&       
+                        pg_strcasecmp(prev3_wd, "TABLE") == 0 &&
+                        pg_strcasecmp(prev_wd, "DISABLE") == 0)
+       {
+               static const char *const list_ALTERDISABLE[] =
+               {"RULE", "TRIGGER", NULL};
+               COMPLETE_WITH_LIST(list_ALTERDISABLE);
+       }
+               
        /* If we have TABLE <sth> ALTER|RENAME, provide list of columns */
        else if (pg_strcasecmp(prev3_wd, "TABLE") == 0 &&
                         (pg_strcasecmp(prev_wd, "ALTER") == 0 ||
@@ -871,6 +924,45 @@ psql_completion(char *text, int start, int end)
 
                COMPLETE_WITH_LIST(list_ALTERTSPC);
        }
+       /* ALTER TEXT SEARCH */
+       else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
+                        pg_strcasecmp(prev2_wd, "TEXT") == 0 &&
+                        pg_strcasecmp(prev_wd, "SEARCH") == 0)
+       {
+               static const char *const list_ALTERTEXTSEARCH[] =
+               {"CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE", NULL};
+
+               COMPLETE_WITH_LIST(list_ALTERTEXTSEARCH);
+       }
+       else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
+                        pg_strcasecmp(prev4_wd, "TEXT") == 0 &&
+                        pg_strcasecmp(prev3_wd, "SEARCH") == 0 &&
+                        (pg_strcasecmp(prev2_wd, "TEMPLATE") == 0 ||
+                        pg_strcasecmp(prev2_wd, "PARSER") == 0)) 
+               COMPLETE_WITH_CONST("RENAME TO");
+
+       else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
+                        pg_strcasecmp(prev4_wd, "TEXT") == 0 &&
+                        pg_strcasecmp(prev3_wd, "SEARCH") == 0 &&
+                        pg_strcasecmp(prev2_wd, "DICTIONARY") == 0) 
+       {
+               static const char *const list_ALTERTEXTSEARCH2[] =
+               {"OWNER TO", "RENAME TO", NULL};
+
+               COMPLETE_WITH_LIST(list_ALTERTEXTSEARCH2);
+       }
+
+       else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
+                        pg_strcasecmp(prev4_wd, "TEXT") == 0 &&
+                        pg_strcasecmp(prev3_wd, "SEARCH") == 0 &&
+                        pg_strcasecmp(prev2_wd, "CONFIGURATION") == 0)
+       {
+               static const char *const list_ALTERTEXTSEARCH3[] =
+               {"ADD MAPPING FOR", "ALTER MAPPING", "DROP MAPPING FOR", "OWNER TO", "RENAME TO", NULL};
+
+               COMPLETE_WITH_LIST(list_ALTERTEXTSEARCH3);
+       }
+
        /* complete ALTER TYPE <foo> with OWNER TO, SET SCHEMA */
        else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
                         pg_strcasecmp(prev2_wd, "TYPE") == 0)
@@ -947,7 +1039,7 @@ psql_completion(char *text, int start, int end)
        }
 
        /*
-        * If we have CLUSTER <sth> ORDER BY, then add the index as well.
+        * If we have CLUSTER <sth> USING, then add the index as well.
         */
        else if (pg_strcasecmp(prev3_wd, "CLUSTER") == 0 &&
                         pg_strcasecmp(prev_wd, "USING") == 0)
@@ -966,12 +1058,25 @@ psql_completion(char *text, int start, int end)
                {"CAST", "CONVERSION", "DATABASE", "INDEX", "LANGUAGE", "RULE", "SCHEMA",
                        "SEQUENCE", "TABLE", "TYPE", "VIEW", "COLUMN", "AGGREGATE", "FUNCTION",
                        "OPERATOR", "TRIGGER", "CONSTRAINT", "DOMAIN", "LARGE OBJECT",
-               "TABLESPACE", "ROLE", NULL};
+               "TABLESPACE", "TEXT SEARCH", "ROLE", NULL};
 
                COMPLETE_WITH_LIST(list_COMMENT);
        }
        else if (pg_strcasecmp(prev4_wd, "COMMENT") == 0 &&
-                        pg_strcasecmp(prev3_wd, "ON") == 0)
+                        pg_strcasecmp(prev3_wd, "ON") == 0  &&
+                        pg_strcasecmp(prev2_wd, "TEXT") == 0  &&
+                        pg_strcasecmp(prev_wd, "SEARCH") == 0)
+       {
+               static const char *const list_TRANS2[] =
+               {"CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE", NULL};
+
+               COMPLETE_WITH_LIST(list_TRANS2);
+       }
+       else if ((pg_strcasecmp(prev4_wd, "COMMENT") == 0 &&
+                        pg_strcasecmp(prev3_wd, "ON") == 0) ||
+                        (pg_strcasecmp(prev5_wd, "ON") == 0 &&
+                         pg_strcasecmp(prev4_wd, "TEXT") == 0 &&
+                         pg_strcasecmp(prev3_wd, "SEARCH") == 0))
                COMPLETE_WITH_CONST("IS");
 
 /* COPY */
@@ -1038,6 +1143,11 @@ psql_completion(char *text, int start, int end)
                COMPLETE_WITH_LIST(list_DATABASE);
        }
 
+       else if (pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
+                        pg_strcasecmp(prev3_wd, "DATABASE") == 0 &&
+                        pg_strcasecmp(prev_wd, "TEMPLATE") == 0)
+               COMPLETE_WITH_QUERY(Query_for_list_of_template_databases);
+
        /* CREATE INDEX */
        /* First off we complete CREATE UNIQUE with "INDEX" */
        else if (pg_strcasecmp(prev2_wd, "CREATE") == 0 &&
@@ -1077,7 +1187,7 @@ psql_completion(char *text, int start, int end)
        else if (pg_strcasecmp(prev_wd, "USING") == 0)
        {
                static const char *const index_mth[] =
-               {"BTREE", "HASH", "GIST", NULL};
+               {"BTREE", "HASH", "GIN", "GIST", NULL};
 
                COMPLETE_WITH_LIST(index_mth);
        }
@@ -1143,6 +1253,21 @@ psql_completion(char *text, int start, int end)
                COMPLETE_WITH_CONST("LOCATION");
        }
 
+/* CREATE TEXT SEARCH */
+       else if (pg_strcasecmp(prev3_wd, "CREATE") == 0 &&
+                        pg_strcasecmp(prev2_wd, "TEXT") == 0 &&
+                        pg_strcasecmp(prev_wd, "SEARCH") == 0)
+       {
+               static const char *const list_CREATETEXTSEARCH[] =
+               {"CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE", NULL};
+
+               COMPLETE_WITH_LIST(list_CREATETEXTSEARCH);
+       }
+       else if (pg_strcasecmp(prev4_wd, "TEXT") == 0 &&
+                       pg_strcasecmp(prev3_wd, "SEARCH") == 0 &&
+                       pg_strcasecmp(prev2_wd, "CONFIGURATION") == 0)
+       COMPLETE_WITH_CONST("(");
+
 /* CREATE TRIGGER */
        /* complete CREATE TRIGGER <name> with BEFORE,AFTER */
        else if (pg_strcasecmp(prev3_wd, "CREATE") == 0 &&
@@ -1287,7 +1412,15 @@ psql_completion(char *text, int start, int end)
                           pg_strcasecmp(prev2_wd, "VIEW") == 0)) ||
                         (pg_strcasecmp(prev4_wd, "DROP") == 0 &&
                          pg_strcasecmp(prev3_wd, "AGGREGATE") == 0 &&
-                         prev_wd[strlen(prev_wd) - 1] == ')'))
+                         prev_wd[strlen(prev_wd) - 1] == ')') ||
+                        (pg_strcasecmp(prev5_wd, "DROP") == 0  &&
+                         pg_strcasecmp(prev4_wd, "TEXT") == 0 &&
+                         pg_strcasecmp(prev3_wd, "SEARCH") == 0 &&
+                        (pg_strcasecmp(prev2_wd, "CONFIGURATION") == 0 ||
+                         pg_strcasecmp(prev2_wd, "DICTIONARY") == 0 ||
+                         pg_strcasecmp(prev2_wd, "PARSER") == 0 ||
+                         pg_strcasecmp(prev2_wd, "TEMPLATE") == 0))
+                        )
        {
                if ((pg_strcasecmp(prev3_wd, "DROP") == 0) && (pg_strcasecmp(prev2_wd, "FUNCTION") == 0))
                {
@@ -1332,9 +1465,17 @@ psql_completion(char *text, int start, int end)
                         pg_strcasecmp(prev2_wd, "OWNED") == 0 &&
                         pg_strcasecmp(prev_wd, "BY") == 0)
                COMPLETE_WITH_QUERY(Query_for_list_of_roles);
+       else if (pg_strcasecmp(prev3_wd, "DROP") == 0 &&
+                        pg_strcasecmp(prev2_wd, "TEXT") == 0 &&
+                         pg_strcasecmp(prev_wd, "SEARCH") == 0)
+       {
 
+               static const char *const list_ALTERTEXTSEARCH[] =
+               {"CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE", NULL};
 
-
+               COMPLETE_WITH_LIST(list_ALTERTEXTSEARCH);
+       }
+       
 /* EXPLAIN */
 
        /*
@@ -1873,6 +2014,14 @@ psql_completion(char *text, int start, int end)
                COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains, NULL);
        else if (strcmp(prev_wd, "\\df") == 0 || strcmp(prev_wd, "\\df+") == 0)
                COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
+       else if (strcmp(prev_wd, "\\dF") == 0 || strcmp(prev_wd, "\\dF+") == 0)
+               COMPLETE_WITH_QUERY(Query_for_list_of_ts_configurations);
+       else if (strcmp(prev_wd, "\\dFd") == 0 || strcmp(prev_wd, "\\dFd+") == 0)
+               COMPLETE_WITH_QUERY(Query_for_list_of_ts_dictionaries);
+       else if (strcmp(prev_wd, "\\dFp") == 0 || strcmp(prev_wd, "\\dFp+") == 0)
+               COMPLETE_WITH_QUERY(Query_for_list_of_ts_parsers);
+       else if (strcmp(prev_wd, "\\dFt") == 0 || strcmp(prev_wd, "\\dFt+") == 0)
+               COMPLETE_WITH_QUERY(Query_for_list_of_ts_templates);
        else if (strcmp(prev_wd, "\\di") == 0 || strcmp(prev_wd, "\\di+") == 0)
                COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
        else if (strcmp(prev_wd, "\\dn") == 0)
@@ -1985,8 +2134,7 @@ psql_completion(char *text, int start, int end)
 static char *
 create_command_generator(const char *text, int state)
 {
-       static int      list_index,
-                               string_length;
+       static int list_index, string_length;
        const char *name;
 
        /* If this is the first time for this completion, init some values */
@@ -1998,9 +2146,10 @@ create_command_generator(const char *text, int state)
 
        /* find something that matches */
        while ((name = words_after_create[list_index++].name))
-               if (pg_strncasecmp(name, text, string_length) == 0)
-                       return pg_strdup(name);
-
+       {
+               if ((pg_strncasecmp(name, text, string_length) == 0) && !words_after_create[list_index - 1].noshow)
+                       return pg_strdup(name);
+       }
        /* if nothing matches, return NULL */
        return NULL;
 }
@@ -2014,8 +2163,7 @@ create_command_generator(const char *text, int state)
 static char *
 drop_command_generator(const char *text, int state)
 {
-       static int      list_index,
-                               string_length;
+       static int      list_index, string_length;
        const char *name;
 
        if (state == 0)
@@ -2043,7 +2191,7 @@ drop_command_generator(const char *text, int state)
         */
        while ((name = words_after_create[list_index++ - 1].name))
        {
-               if (pg_strncasecmp(name, text, string_length) == 0)
+               if ((pg_strncasecmp(name, text, string_length) == 0) && (!words_after_create[list_index - 2].noshow))
                        return pg_strdup(name);
        }