psql tab completion improvements, from Greg Sabino Mullane:
authorNeil Conway <neilc@samurai.com>
Wed, 18 May 2005 04:47:40 +0000 (04:47 +0000)
committerNeil Conway <neilc@samurai.com>
Wed, 18 May 2005 04:47:40 +0000 (04:47 +0000)
* Made DELETE into "DELETE FROM"
* Moved ANALZYE to the end of the list to ease EXPLAIN / VACUUM
  conflicts
* Removed the ANALYZE xx semicolon completion: we don't do that anywhere
  else
* Add DECLARE support
* Add parens for DROP AGGREGATE
* Add "CASCADE | RESTRICT" for DROP xx
* Make EXPLAIN <tab> a lot smarter
* GROUP "BY" and ORDER "BY"
* "ISOLATION" becomes "ISOLATION LEVEL"
* Fix error in which REVOKE xx ON yy was receiving "TO", now gets "FROM"
* Add GRANT/REVOKE xx ON yy TO/FROM choices: usernames, GROUP, PUBLIC
* PREPARE xx <tab> AS "SELECT | INSERT | UPDATE | DELETE"
* Add = at end of UPDATE xx SET yy
* Beef up VACUUM stuff

src/bin/psql/tab-complete.c

index 4aed36c66a310d73d5d8cbd09019664e5c7e9829..015dbd4779346aaa2c71314dca943b4b4878ae64 100644 (file)
@@ -3,7 +3,7 @@
  *
  * Copyright (c) 2000-2005, PostgreSQL Global Development Group
  *
- * $PostgreSQL: pgsql/src/bin/psql/tab-complete.c,v 1.127 2005/05/07 02:22:49 momjian Exp $
+ * $PostgreSQL: pgsql/src/bin/psql/tab-complete.c,v 1.128 2005/05/18 04:47:40 neilc Exp $
  */
 
 /*----------------------------------------------------------------------
@@ -368,6 +368,12 @@ static const SchemaQuery Query_for_list_of_views = {
 "   FROM pg_catalog.pg_user "\
 "  WHERE substring(pg_catalog.quote_ident(usename),1,%d)='%s'"
 
+#define Query_for_list_of_grant_users \
+" SELECT pg_catalog.quote_ident(usename) "\
+"   FROM pg_catalog.pg_user "\
+"  WHERE substring(pg_catalog.quote_ident(usename),1,%d)='%s'"\
+" UNION SELECT 'PUBLIC' UNION SELECT 'GROUP'"
+
 /* the silly-looking length condition is just to eat up the current word */
 #define Query_for_table_owning_index \
 "SELECT pg_catalog.quote_ident(c1.relname) "\
@@ -494,7 +500,7 @@ psql_completion(char *text, int start, int end)
 
    static const char *const sql_commands[] = {
        "ABORT", "ALTER", "ANALYZE", "BEGIN", "CHECKPOINT", "CLOSE", "CLUSTER", "COMMENT",
-       "COMMIT", "COPY", "CREATE", "DEALLOCATE", "DECLARE", "DELETE", "DROP", "END", "EXECUTE",
+       "COMMIT", "COPY", "CREATE", "DEALLOCATE", "DECLARE", "DELETE FROM", "DROP", "END", "EXECUTE",
        "EXPLAIN", "FETCH", "GRANT", "INSERT", "LISTEN", "LOAD", "LOCK", "MOVE", "NOTIFY",
        "PREPARE", "REINDEX", "RELEASE", "RESET", "REVOKE", "ROLLBACK", "SAVEPOINT",
        "SELECT", "SET", "SHOW", "START", "TRUNCATE", "UNLISTEN", "UPDATE", "VACUUM", NULL
@@ -920,14 +926,6 @@ psql_completion(char *text, int start, int end)
             pg_strcasecmp(prev_wd, "USER") == 0)
        COMPLETE_WITH_QUERY(Query_for_list_of_users);
 
-/* ANALYZE */
-   /* If the previous word is ANALYZE, produce list of tables. */
-   else if (pg_strcasecmp(prev_wd, "ANALYZE") == 0)
-       COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
-   /* If we have ANALYZE <table>, complete with semicolon. */
-   else if (pg_strcasecmp(prev2_wd, "ANALYZE") == 0)
-       COMPLETE_WITH_CONST(";");
-
 /* BEGIN, END, COMMIT, ABORT */
    else if (pg_strcasecmp(prev_wd, "BEGIN") == 0 ||
             pg_strcasecmp(prev_wd, "END") == 0 ||
@@ -1149,8 +1147,23 @@ psql_completion(char *text, int start, int end)
             pg_strcasecmp(prev_wd, "AS") == 0)
        COMPLETE_WITH_CONST("SELECT");
 
-/* DELETE */
+/* DECLARE */
+   else if (pg_strcasecmp(prev2_wd, "DECLARE") == 0)
+   {
+           static const char *const list_DECLARE[] =
+           {"BINARY", "INSENSITIVE", "SCROLL", "NO SCROLL", "CURSOR", NULL};
+           COMPLETE_WITH_LIST(list_DECLARE);
+   }
+
+   else if (pg_strcasecmp(prev_wd, "CURSOR") == 0)
+   {
+           static const char *const list_DECLARECURSOR[] =
+           {"WITH HOLD", "WITHOUT HOLD", "FOR", NULL};
+           COMPLETE_WITH_LIST(list_DECLARECURSOR);
+   }
 
+
+/* DELETE */
    /*
     * Complete DELETE with FROM (only if the word before that is not "ON"
     * (cf. rules) or "BEFORE" or "AFTER" (cf. triggers) or GRANT)
@@ -1176,16 +1189,60 @@ psql_completion(char *text, int start, int end)
    }
    /* XXX: implement tab completion for DELETE ... USING */
 
-/* EXPLAIN */
+/* DROP (when not the previous word) */
+   /* DROP AGGREGATE */
+   else if (pg_strcasecmp(prev3_wd, "DROP") == 0 &&
+            pg_strcasecmp(prev2_wd, "AGGREGATE") == 0)
+       COMPLETE_WITH_CONST("(");
+
+   /* DROP object with CASCADE / RESTRICT */
+   else if ((pg_strcasecmp(prev3_wd, "DROP") == 0 &&
+             (pg_strcasecmp(prev2_wd, "CONVERSION") == 0 ||
+              pg_strcasecmp(prev2_wd, "DOMAIN") == 0 ||
+              pg_strcasecmp(prev2_wd, "FUNCTION") == 0 ||
+              pg_strcasecmp(prev2_wd, "INDEX") == 0 ||
+              pg_strcasecmp(prev2_wd, "LANGUAGE") == 0 ||
+              pg_strcasecmp(prev2_wd, "SCHEMA") == 0 ||
+              pg_strcasecmp(prev2_wd, "SEQUENCE") == 0 ||
+              pg_strcasecmp(prev2_wd, "TABLE") == 0 ||
+              pg_strcasecmp(prev2_wd, "TYPE") == 0 ||
+              pg_strcasecmp(prev2_wd, "VIEW") == 0)) ||
+            (pg_strcasecmp(prev4_wd, "DROP") == 0 &&
+             pg_strcasecmp(prev3_wd, "AGGREGATE") == 0 &&
+             prev_wd[strlen(prev_wd) - 1] == ')'))
+   {
+           static const char *const list_DROPCR[] =
+           {"CASCADE", "RESTRICT", NULL};
+           COMPLETE_WITH_LIST(list_DROPCR);
+   }
 
+/* EXPLAIN */
    /*
-    * Complete EXPLAIN [VERBOSE] (which you'd have to type yourself) with
-    * the list of SQL commands
+    * Complete EXPLAIN [ANALYZE] [VERBOSE] with list of EXPLAIN-able commands
     */
-   else if (pg_strcasecmp(prev_wd, "EXPLAIN") == 0 ||
-            (pg_strcasecmp(prev2_wd, "EXPLAIN") == 0 &&
-             pg_strcasecmp(prev_wd, "VERBOSE") == 0))
-       COMPLETE_WITH_LIST(sql_commands);
+   else if (pg_strcasecmp(prev_wd, "EXPLAIN") == 0)
+   {
+           static const char *const list_EXPLAIN[] =
+           {"SELECT","INSERT","DELETE","UPDATE","DECLARE","ANALYZE","VERBOSE",NULL};
+           COMPLETE_WITH_LIST(list_EXPLAIN);
+   }
+   else if (pg_strcasecmp(prev2_wd, "EXPLAIN") == 0 &&
+                    pg_strcasecmp(prev_wd, "ANALYZE") == 0)
+   {
+           static const char *const list_EXPLAIN[] =
+           {"SELECT","INSERT","DELETE","UPDATE","DECLARE","VERBOSE",NULL};
+           COMPLETE_WITH_LIST(list_EXPLAIN);
+   }
+   else if (pg_strcasecmp(prev_wd, "VERBOSE") == 0 &&
+            pg_strcasecmp(prev3_wd, "VACUUM") != 0 &&
+            pg_strcasecmp(prev4_wd, "VACUUM") != 0 &&
+            (pg_strcasecmp(prev2_wd, "ANALYZE") == 0 ||
+             pg_strcasecmp(prev2_wd, "EXPLAIN") == 0))
+   {
+           static const char *const list_EXPLAIN[] =
+           {"SELECT","INSERT","DELETE","UPDATE","DECLARE",NULL};
+           COMPLETE_WITH_LIST(list_EXPLAIN);
+   }
 
 /* FETCH && MOVE */
    /* Complete FETCH with one of FORWARD, BACKWARD, RELATIVE */
@@ -1273,15 +1330,24 @@ psql_completion(char *text, int start, int end)
            COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
        else if (pg_strcasecmp(prev_wd, "TABLESPACE") == 0)
            COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
-       else
+       else if (pg_strcasecmp(prev4_wd, "GRANT") == 0)
            COMPLETE_WITH_CONST("TO");
+       else
+           COMPLETE_WITH_CONST("FROM");
    }
 
-   /*
-    * TODO: to complete with user name we need prev5_wd -- wait for a
-    * more general solution there same for GRANT <sth> ON { DATABASE |
-    * FUNCTION | LANGUAGE | SCHEMA | TABLESPACE } xxx TO
-    */
+   /* Complete "GRANT/REVOKE * ON * TO/FROM" with username, GROUP, or PUBLIC */
+   else if (pg_strcasecmp(prev3_wd, "ON") == 0 &&
+            ((pg_strcasecmp(prev5_wd, "GRANT") == 0 &&
+              pg_strcasecmp(prev_wd, "TO") == 0) ||
+             (pg_strcasecmp(prev5_wd, "REVOKE") == 0 &&
+              pg_strcasecmp(prev_wd, "FROM") == 0)))
+       COMPLETE_WITH_QUERY(Query_for_list_of_grant_users);
+
+/* GROUP BY */
+   else if (pg_strcasecmp(prev3_wd, "FROM") == 0 &&
+            pg_strcasecmp(prev_wd, "GROUP") == 0)
+       COMPLETE_WITH_CONST("BY");
 
 /* INSERT */
    /* Complete INSERT with "INTO" */
@@ -1360,10 +1426,32 @@ psql_completion(char *text, int start, int end)
 /* NOTIFY */
    else if (pg_strcasecmp(prev_wd, "NOTIFY") == 0)
        COMPLETE_WITH_QUERY("SELECT pg_catalog.quote_ident(relname) FROM pg_catalog.pg_listener WHERE substring(pg_catalog.quote_ident(relname),1,%d)='%s'");
+
 /* OWNER TO  - complete with available users*/
    else if (pg_strcasecmp(prev2_wd, "OWNER") == 0 &&
             pg_strcasecmp(prev_wd, "TO") == 0)
        COMPLETE_WITH_QUERY(Query_for_list_of_users);
+
+/* ORDER BY */
+   else if (pg_strcasecmp(prev3_wd, "FROM") == 0 &&
+            pg_strcasecmp(prev_wd, "ORDER") == 0)
+       COMPLETE_WITH_CONST("BY");
+   else if (pg_strcasecmp(prev4_wd, "FROM") == 0 &&
+            pg_strcasecmp(prev2_wd, "ORDER") == 0 &&
+            pg_strcasecmp(prev_wd, "BY") == 0)
+       COMPLETE_WITH_ATTR(prev3_wd);
+
+/* PREPARE xx AS */
+   else if (pg_strcasecmp(prev_wd, "AS") == 0 &&
+            pg_strcasecmp(prev3_wd, "PREPARE") == 0)
+   {
+       static const char *const list_PREPARE[] =
+       {"SELECT", "UPDATE", "INSERT", "DELETE", NULL};
+
+       COMPLETE_WITH_LIST(list_PREPARE);
+   }
+
+
 /* REINDEX */
    else if (pg_strcasecmp(prev_wd, "REINDEX") == 0)
    {
@@ -1407,7 +1495,7 @@ psql_completion(char *text, int start, int end)
                 && pg_strcasecmp(prev_wd, "TRANSACTION") == 0))
    {
        static const char *const my_list[] =
-       {"ISOLATION", "READ", NULL};
+       {"ISOLATION LEVEL", "READ", NULL};
 
        COMPLETE_WITH_LIST(my_list);
    }
@@ -1501,8 +1589,8 @@ psql_completion(char *text, int start, int end)
        {
            static const char *const my_list[] =
            {"ISO", "SQL", "Postgres", "German",
-               "YMD", "DMY", "MDY",
-               "US", "European", "NonEuropean",
+            "YMD", "DMY", "MDY",
+            "US", "European", "NonEuropean",
            "DEFAULT", NULL};
 
            COMPLETE_WITH_LIST(my_list);
@@ -1551,16 +1639,56 @@ psql_completion(char *text, int start, int end)
    else if (pg_strcasecmp(prev_wd, "SET") == 0)
        COMPLETE_WITH_ATTR(prev2_wd);
 
-/* VACUUM */
+/* UPDATE xx SET yy = */
+   else if (pg_strcasecmp(prev2_wd, "SET") == 0 &&
+                    pg_strcasecmp(prev4_wd, "UPDATE") == 0)
+       COMPLETE_WITH_CONST("=");
+
+/*
+ * VACUUM [ FULL | FREEZE ] [ VERBOSE ] [ table ]
+ * VACUUM [ FULL | FREEZE ] [ VERBOSE ] ANALYZE [ table [ (column [, ...] ) ] ]
+ */
    else if (pg_strcasecmp(prev_wd, "VACUUM") == 0)
        COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
                                   " UNION SELECT 'FULL'"
+                                  " UNION SELECT 'FREEZE'"
                                   " UNION SELECT 'ANALYZE'"
                                   " UNION SELECT 'VERBOSE'");
    else if (pg_strcasecmp(prev2_wd, "VACUUM") == 0 &&
             (pg_strcasecmp(prev_wd, "FULL") == 0 ||
-             pg_strcasecmp(prev_wd, "ANALYZE") == 0 ||
-             pg_strcasecmp(prev_wd, "VERBOSE") == 0))
+             pg_strcasecmp(prev_wd, "FREEZE") == 0))
+       COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
+                                  " UNION SELECT 'ANALYZE'"
+                                  " UNION SELECT 'VERBOSE'");
+   else if (pg_strcasecmp(prev3_wd, "VACUUM") == 0 &&
+            pg_strcasecmp(prev_wd, "ANALYZE") == 0 &&
+            (pg_strcasecmp(prev2_wd, "FULL") == 0 ||
+             pg_strcasecmp(prev2_wd, "FREEZE") == 0))
+       COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
+                                  " UNION SELECT 'VERBOSE'");
+   else if (pg_strcasecmp(prev3_wd, "VACUUM") == 0 &&
+            pg_strcasecmp(prev_wd, "VERBOSE") == 0 &&
+            (pg_strcasecmp(prev2_wd, "FULL") == 0 ||
+             pg_strcasecmp(prev2_wd, "FREEZE") == 0))
+       COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
+                                  " UNION SELECT 'ANALYZE'");
+   else if (pg_strcasecmp(prev2_wd, "VACUUM") == 0 &&
+            pg_strcasecmp(prev_wd, "VERBOSE") == 0)
+       COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
+                                  " UNION SELECT 'ANALYZE'");
+   else if (pg_strcasecmp(prev2_wd, "VACUUM") == 0 &&
+            pg_strcasecmp(prev_wd, "ANALYZE") == 0)
+       COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
+                                  " UNION SELECT 'VERBOSE'");
+   else if ((pg_strcasecmp(prev_wd, "ANALYZE") == 0 &&
+             pg_strcasecmp(prev2_wd, "VERBOSE") == 0) ||
+            (pg_strcasecmp(prev_wd, "VERBOSE") == 0 &&
+             pg_strcasecmp(prev2_wd, "ANALYZE") == 0))
+       COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+
+/* ANALZYE */
+   /* If the previous word is ANALYZE, produce list of tables */
+   else if (pg_strcasecmp(prev_wd, "ANALYZE") == 0)
        COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 
 /* WHERE */