Support new syntax and improve handling of parentheses in psql tab-completion.
authorItagaki Takahiro
Wed, 17 Feb 2010 04:09:40 +0000 (04:09 +0000)
committerItagaki Takahiro
Wed, 17 Feb 2010 04:09:40 +0000 (04:09 +0000)
Newly supported syntax are:
  - ALTER {TABLE|INDEX|TABLESPACE} {SET|RESET} with options
  - ALTER TABLE ALTER COLUMN {SET|RESET} with options
  - ALTER TABLE ALTER COLUMN SET STORAGE
  - CREATE INDEX CONCURRENTLY
  - CREATE INDEX ON (without name)
  - CREATE INDEX ... USING with pg_am.amname instead of hard-corded names
  - CREATE TRIGGER with events
  - DROP AGGREGATE function with arguments

src/bin/psql/tab-complete.c

index 0b10472c12e2fa76e8df10dde751e0c3db6acb2b..b3dbfb20217c30c293aafb09ed1d803b83e8fdec 100644 (file)
@@ -3,7 +3,7 @@
  *
  * Copyright (c) 2000-2010, PostgreSQL Global Development Group
  *
- * $PostgreSQL: pgsql/src/bin/psql/tab-complete.c,v 1.194 2010/02/16 22:34:50 tgl Exp $
+ * $PostgreSQL: pgsql/src/bin/psql/tab-complete.c,v 1.195 2010/02/17 04:09:40 itagaki Exp $
  */
 
 /*----------------------------------------------------------------------
@@ -66,6 +66,8 @@ extern char *filename_completion_function();
 #define completion_matches rl_completion_matches
 #endif
 
+/* word break characters */
+#define WORD_BREAKS        "\t\n@$><=;|&{() "
 
 /*
  * This struct is used to define "schema queries", which are custom-built
@@ -500,6 +502,11 @@ static const SchemaQuery Query_for_list_of_views = {
 "   FROM pg_catalog.pg_user_mappings "\
 "  WHERE substring(pg_catalog.quote_ident(usename),1,%d)='%s'"
 
+#define Query_for_list_of_access_methods \
+" SELECT pg_catalog.quote_ident(amname) "\
+"   FROM pg_catalog.pg_am "\
+"  WHERE substring(pg_catalog.quote_ident(amname),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.
@@ -571,8 +578,6 @@ static PGresult *exec_query(const char *query);
 
 static char *previous_word(int point, int skip);
 
-static int find_open_parenthesis(int end);
-
 #if 0
 static char *quote_file_name(char *text, int match_type, char *quote_pointer);
 static char *dequote_file_name(char *text, char quote_char);
@@ -586,7 +591,7 @@ initialize_readline(void)
    rl_readline_name = (char *) pset.progname;
    rl_attempted_completion_function = (void *) psql_completion;
 
-   rl_basic_word_break_characters = "\t\n@$><=;|&{( ";
+   rl_basic_word_break_characters = WORD_BREAKS;
 
    completion_max_records = 1000;
 
@@ -749,6 +754,33 @@ psql_completion(char *text, int start, int end)
 
        COMPLETE_WITH_LIST(list_ALTERINDEX);
    }
+   /* ALTER INDEX  SET */
+   else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
+            pg_strcasecmp(prev3_wd, "INDEX") == 0 &&
+            pg_strcasecmp(prev_wd, "SET") == 0)
+   {
+       static const char *const list_ALTERINDEXSET[] =
+       {"(", "TABLESPACE", NULL};
+
+       COMPLETE_WITH_LIST(list_ALTERINDEXSET);
+   }
+   /* ALTER INDEX  RESET */
+   else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
+            pg_strcasecmp(prev3_wd, "INDEX") == 0 &&
+            pg_strcasecmp(prev_wd, "RESET") == 0)
+       COMPLETE_WITH_CONST("(");
+   /* ALTER INDEX  SET|RESET ( */
+   else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
+            pg_strcasecmp(prev4_wd, "INDEX") == 0 &&
+            (pg_strcasecmp(prev2_wd, "SET") == 0 ||
+             pg_strcasecmp(prev2_wd, "RESET") == 0) &&
+            pg_strcasecmp(prev_wd, "(") == 0)
+   {
+       static const char *const list_INDEXOPTIONS[] =
+       {"fillfactor", "fastupdate", NULL};
+
+       COMPLETE_WITH_LIST(list_INDEXOPTIONS);
+   }
 
    /* ALTER LANGUAGE  */
    else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
@@ -977,10 +1009,11 @@ psql_completion(char *text, int start, int end)
              pg_strcasecmp(prev2_wd, "ALTER") == 0))
    {
        static const char *const list_COLUMNALTER[] =
-       {"TYPE", "SET", "DROP", NULL};
+       {"TYPE", "SET", "RESET", "DROP", NULL};
 
        COMPLETE_WITH_LIST(list_COLUMNALTER);
    }
+   /* ALTER TABLE ALTER [COLUMN]  SET */
    else if (((pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
               pg_strcasecmp(prev3_wd, "COLUMN") == 0) ||
              (pg_strcasecmp(prev5_wd, "TABLE") == 0 &&
@@ -988,10 +1021,35 @@ psql_completion(char *text, int start, int end)
             pg_strcasecmp(prev_wd, "SET") == 0)
    {
        static const char *const list_COLUMNSET[] =
-       {"DEFAULT", "NOT NULL", "STATISTICS", "STORAGE", NULL};
+       {"(", "DEFAULT", "NOT NULL", "STATISTICS", "STORAGE", NULL};
 
        COMPLETE_WITH_LIST(list_COLUMNSET);
    }
+   /* ALTER TABLE ALTER [COLUMN]  SET ( */
+   else if (((pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
+              pg_strcasecmp(prev4_wd, "COLUMN") == 0) ||
+             pg_strcasecmp(prev4_wd, "ALTER") == 0) &&
+            pg_strcasecmp(prev2_wd, "SET") == 0 &&
+            pg_strcasecmp(prev_wd, "(") == 0)
+   {
+       static const char *const list_COLUMNOPTIONS[] =
+       {"n_distinct", "n_distinct_inherited", NULL};
+
+       COMPLETE_WITH_LIST(list_COLUMNOPTIONS);
+   }
+   /* ALTER TABLE ALTER [COLUMN]  SET STORAGE */
+   else if (((pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
+              pg_strcasecmp(prev4_wd, "COLUMN") == 0) ||
+             pg_strcasecmp(prev4_wd, "ALTER") == 0) &&
+            pg_strcasecmp(prev2_wd, "SET") == 0 &&
+            pg_strcasecmp(prev_wd, "STORAGE") == 0)
+   {
+       static const char *const list_COLUMNSTORAGE[] =
+       {"PLAIN", "EXTERNAL", "EXTENDED", "MAIN", NULL};
+
+       COMPLETE_WITH_LIST(list_COLUMNSTORAGE);
+   }
+   /* ALTER TABLE ALTER [COLUMN]  DROP */
    else if (((pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
               pg_strcasecmp(prev3_wd, "COLUMN") == 0) ||
              (pg_strcasecmp(prev5_wd, "TABLE") == 0 &&
@@ -1018,7 +1076,7 @@ psql_completion(char *text, int start, int end)
             pg_strcasecmp(prev_wd, "SET") == 0)
    {
        static const char *const list_TABLESET[] =
-       {"WITHOUT", "TABLESPACE", "SCHEMA", NULL};
+       {"(", "WITHOUT", "TABLESPACE", "SCHEMA", NULL};
 
        COMPLETE_WITH_LIST(list_TABLESET);
    }
@@ -1037,15 +1095,73 @@ psql_completion(char *text, int start, int end)
 
        COMPLETE_WITH_LIST(list_TABLESET2);
    }
-   /* we have ALTER TABLESPACE, so suggest RENAME TO, OWNER TO */
+   /* ALTER TABLE  RESET */
+   else if (pg_strcasecmp(prev3_wd, "TABLE") == 0 &&
+            pg_strcasecmp(prev_wd, "RESET") == 0)
+       COMPLETE_WITH_CONST("(");
+   /* ALTER TABLE  SET|RESET ( */
+   else if (pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
+            (pg_strcasecmp(prev2_wd, "SET") == 0 ||
+             pg_strcasecmp(prev2_wd, "RESET") == 0) &&
+            pg_strcasecmp(prev_wd, "(") == 0)
+   {
+       static const char *const list_TABLEOPTIONS[] =
+       {
+           "autovacuum_analyze_scale_factor",
+           "autovacuum_analyze_threshold",
+           "autovacuum_enabled",
+           "autovacuum_freeze_max_age",
+           "autovacuum_freeze_min_age",
+           "autovacuum_freeze_table_age",
+           "autovacuum_vacuum_cost_delay",
+           "autovacuum_vacuum_cost_limit",
+           "autovacuum_vacuum_scale_factor",
+           "autovacuum_vacuum_threshold",
+           "fillfactor",
+           "toast.autovacuum_analyze_scale_factor",
+           "toast.autovacuum_analyze_threshold",
+           "toast.autovacuum_enabled",
+           "toast.autovacuum_freeze_max_age",
+           "toast.autovacuum_freeze_min_age",
+           "toast.autovacuum_freeze_table_age",
+           "toast.autovacuum_vacuum_cost_delay",
+           "toast.autovacuum_vacuum_cost_limit",
+           "toast.autovacuum_vacuum_scale_factor",
+           "toast.autovacuum_vacuum_threshold",
+           NULL
+       };
+
+       COMPLETE_WITH_LIST(list_TABLEOPTIONS);
+   }
+
+   /* ALTER TABLESPACE  with RENAME TO, OWNER TO, SET, RESET */
    else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
             pg_strcasecmp(prev2_wd, "TABLESPACE") == 0)
    {
        static const char *const list_ALTERTSPC[] =
-       {"RENAME TO", "OWNER TO", NULL};
+       {"RENAME TO", "OWNER TO", "SET", "RESET", NULL};
 
        COMPLETE_WITH_LIST(list_ALTERTSPC);
    }
+   /* ALTER TABLESPACE  SET|RESET */
+   else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
+            pg_strcasecmp(prev3_wd, "TABLESPACE") == 0 &&
+            (pg_strcasecmp(prev_wd, "SET") == 0 ||
+             pg_strcasecmp(prev_wd, "RESET") == 0))
+       COMPLETE_WITH_CONST("(");
+   /* ALTER TABLESPACE  SET|RESET ( */
+   else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
+            pg_strcasecmp(prev4_wd, "TABLESPACE") == 0 &&
+            (pg_strcasecmp(prev2_wd, "SET") == 0 ||
+             pg_strcasecmp(prev2_wd, "RESET") == 0) &&
+            pg_strcasecmp(prev_wd, "(") == 0)
+   {
+       static const char *const list_TABLESPACEOPTIONS[] =
+       {"seq_page_cost", "random_page_cost", NULL};
+
+       COMPLETE_WITH_LIST(list_TABLESPACEOPTIONS);
+   }
+
    /* ALTER TEXT SEARCH */
    else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
             pg_strcasecmp(prev2_wd, "TEXT") == 0 &&
@@ -1282,44 +1398,66 @@ psql_completion(char *text, int start, int end)
    else if (pg_strcasecmp(prev2_wd, "CREATE") == 0 &&
             pg_strcasecmp(prev_wd, "UNIQUE") == 0)
        COMPLETE_WITH_CONST("INDEX");
-   /* If we have CREATE|UNIQUE INDEX , then add "ON" */
-   else if (pg_strcasecmp(prev2_wd, "INDEX") == 0 &&
-            (pg_strcasecmp(prev3_wd, "CREATE") == 0 ||
-             pg_strcasecmp(prev3_wd, "UNIQUE") == 0))
-       COMPLETE_WITH_CONST("ON");
-   /* Complete ... INDEX  ON with a list of tables  */
-   else if (pg_strcasecmp(prev3_wd, "INDEX") == 0 &&
+   /* If we have CREATE|UNIQUE INDEX, then add "ON" and existing indexes */
+   else if (pg_strcasecmp(prev_wd, "INDEX") == 0 &&
+            (pg_strcasecmp(prev2_wd, "CREATE") == 0 ||
+             pg_strcasecmp(prev2_wd, "UNIQUE") == 0))
+       COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
+                                  " UNION SELECT 'ON'"
+                                  " UNION SELECT 'CONCURRENTLY'");
+   /* Complete ... INDEX [] ON with a list of tables  */
+   else if ((pg_strcasecmp(prev3_wd, "INDEX") == 0 ||
+             pg_strcasecmp(prev2_wd, "INDEX") == 0 ||
+             pg_strcasecmp(prev2_wd, "CONCURRENTLY") == 0) &&
             pg_strcasecmp(prev_wd, "ON") == 0)
        COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+   /* If we have CREATE|UNIQUE INDEX  CONCURRENTLY, then add "ON" */
+   else if ((pg_strcasecmp(prev3_wd, "INDEX") == 0 ||
+             pg_strcasecmp(prev2_wd, "INDEX") == 0) &&
+            pg_strcasecmp(prev_wd, "CONCURRENTLY") == 0)
+       COMPLETE_WITH_CONST("ON");
+   /* If we have CREATE|UNIQUE INDEX , then add "ON" or "CONCURRENTLY" */
+   else if ((pg_strcasecmp(prev3_wd, "CREATE") == 0 ||
+             pg_strcasecmp(prev3_wd, "UNIQUE") == 0) &&
+            pg_strcasecmp(prev2_wd, "INDEX") == 0)
+   {
+       static const char *const list_CREATE_INDEX[] =
+       {"CONCURRENTLY", "ON", NULL};
+
+       COMPLETE_WITH_LIST(list_CREATE_INDEX);
+   }
 
    /*
     * Complete INDEX  ON  with a list of table columns (which
     * should really be in parens)
     */
-   else if (pg_strcasecmp(prev4_wd, "INDEX") == 0 &&
+   else if ((pg_strcasecmp(prev4_wd, "INDEX") == 0 ||
+             pg_strcasecmp(prev3_wd, "INDEX") == 0 ||
+             pg_strcasecmp(prev3_wd, "CONCURRENTLY") == 0) &&
             pg_strcasecmp(prev2_wd, "ON") == 0)
    {
-       if (find_open_parenthesis(end))
-           COMPLETE_WITH_ATTR(prev_wd, "");
-       else
-           COMPLETE_WITH_CONST("(");
+       static const char *const list_CREATE_INDEX2[] =
+       {"(", "USING",  NULL};
+
+       COMPLETE_WITH_LIST(list_CREATE_INDEX2);
    }
-   else if (pg_strcasecmp(prev5_wd, "INDEX") == 0 &&
+   else if ((pg_strcasecmp(prev5_wd, "INDEX") == 0 ||
+             pg_strcasecmp(prev4_wd, "INDEX") == 0 ||
+             pg_strcasecmp(prev4_wd, "CONCURRENTLY") == 0) &&
             pg_strcasecmp(prev3_wd, "ON") == 0 &&
             pg_strcasecmp(prev_wd, "(") == 0)
        COMPLETE_WITH_ATTR(prev2_wd, "");
    /* same if you put in USING */
-   else if (pg_strcasecmp(prev4_wd, "ON") == 0 &&
-            pg_strcasecmp(prev2_wd, "USING") == 0)
-       COMPLETE_WITH_ATTR(prev3_wd, "");
+   else if (pg_strcasecmp(prev5_wd, "ON") == 0 &&
+            pg_strcasecmp(prev3_wd, "USING") == 0 &&
+            pg_strcasecmp(prev_wd, "(") == 0)
+       COMPLETE_WITH_ATTR(prev4_wd, "");
    /* Complete USING with an index method */
    else if (pg_strcasecmp(prev_wd, "USING") == 0)
-   {
-       static const char *const index_mth[] =
-       {"BTREE", "HASH", "GIN", "GIST", NULL};
-
-       COMPLETE_WITH_LIST(index_mth);
-   }
+       COMPLETE_WITH_QUERY(Query_for_list_of_access_methods);
+   else if (pg_strcasecmp(prev4_wd, "ON") == 0 &&
+            pg_strcasecmp(prev2_wd, "USING") == 0)
+       COMPLETE_WITH_CONST("(");
 
 /* CREATE RULE */
    /* Complete "CREATE RULE " with "AS" */
@@ -1417,6 +1555,17 @@ psql_completion(char *text, int start, int end)
 
        COMPLETE_WITH_LIST(list_CREATETRIGGER);
    }
+   /* complete CREATE TRIGGER  BEFORE,AFTER with an event */
+   else if (pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
+            pg_strcasecmp(prev3_wd, "TRIGGER") == 0 &&
+            (pg_strcasecmp(prev_wd, "BEFORE") == 0 ||
+             pg_strcasecmp(prev_wd, "AFTER") == 0))
+   {
+       static const char *const list_CREATETRIGGER_EVENTS[] =
+       {"INSERT", "DELETE", "UPDATE", "TRUNCATE", NULL};
+
+       COMPLETE_WITH_LIST(list_CREATETRIGGER_EVENTS);
+   }
    /* complete CREATE TRIGGER  BEFORE,AFTER sth with OR,ON */
    else if (pg_strcasecmp(prev5_wd, "CREATE") == 0 &&
             pg_strcasecmp(prev4_wd, "TRIGGER") == 0 &&
@@ -1428,6 +1577,15 @@ psql_completion(char *text, int start, int end)
 
        COMPLETE_WITH_LIST(list_CREATETRIGGER2);
    }
+   /* complete CREATE TRIGGER  BEFORE,AFTER event ON with a list of tables */
+   else if (pg_strcasecmp(prev5_wd, "TRIGGER") == 0 &&
+            (pg_strcasecmp(prev3_wd, "BEFORE") == 0 ||
+             pg_strcasecmp(prev3_wd, "AFTER") == 0) &&
+            pg_strcasecmp(prev_wd, "ON") == 0)
+       COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+   /* complete CREATE TRIGGER ... EXECUTE with PROCEDURE */
+   else if (pg_strcasecmp(prev_wd, "EXECUTE") == 0)
+       COMPLETE_WITH_CONST("PROCEDURE");
 
 /* CREATE ROLE,USER,GROUP */
    else if (pg_strcasecmp(prev3_wd, "CREATE") == 0 &&
@@ -1487,6 +1645,7 @@ psql_completion(char *text, int start, int end)
        COMPLETE_WITH_LIST(list_DECLARE);
    }
 
+/* CURSOR */
    else if (pg_strcasecmp(prev_wd, "CURSOR") == 0)
    {
        static const char *const list_DECLARECURSOR[] =
@@ -1579,21 +1738,10 @@ psql_completion(char *text, int start, int end)
               pg_strcasecmp(prev2_wd, "TEMPLATE") == 0))
        )
    {
-       if ((pg_strcasecmp(prev3_wd, "DROP") == 0) && (pg_strcasecmp(prev2_wd, "FUNCTION") == 0))
+       if (pg_strcasecmp(prev3_wd, "DROP") == 0 &&
+           pg_strcasecmp(prev2_wd, "FUNCTION") == 0)
        {
-           if (find_open_parenthesis(end))
-           {
-               static const char func_args_query[] = "select pg_catalog.oidvectortypes(proargtypes)||')' from pg_proc where proname='%s'";
-               char       *tmp_buf = malloc(strlen(func_args_query) + strlen(prev_wd));
-
-               sprintf(tmp_buf, func_args_query, prev_wd);
-               COMPLETE_WITH_QUERY(tmp_buf);
-               free(tmp_buf);
-           }
-           else
-           {
-               COMPLETE_WITH_CONST("(");
-           }
+           COMPLETE_WITH_CONST("(");
        }
        else
        {
@@ -1604,7 +1752,8 @@ psql_completion(char *text, int start, int end)
        }
    }
    else if (pg_strcasecmp(prev4_wd, "DROP") == 0 &&
-            pg_strcasecmp(prev3_wd, "FUNCTION") == 0 &&
+            (pg_strcasecmp(prev3_wd, "AGGREGATE") == 0 ||
+             pg_strcasecmp(prev3_wd, "FUNCTION") == 0) &&
             pg_strcasecmp(prev_wd, "(") == 0)
    {
        static const char func_args_query[] = "select pg_catalog.oidvectortypes(proargtypes)||')' from pg_proc where proname='%s'";
@@ -1712,12 +1861,12 @@ psql_completion(char *text, int start, int end)
    else if (pg_strcasecmp(prev_wd, "GRANT") == 0 ||
             pg_strcasecmp(prev_wd, "REVOKE") == 0)
    {
-       static const char *const list_privileg[] =
+       static const char *const list_privilege[] =
        {"SELECT", "INSERT", "UPDATE", "DELETE", "TRUNCATE", "REFERENCES",
            "TRIGGER", "CREATE", "CONNECT", "TEMPORARY", "EXECUTE", "USAGE",
        "ALL", NULL};
 
-       COMPLETE_WITH_LIST(list_privileg);
+       COMPLETE_WITH_LIST(list_privilege);
    }
    /* Complete GRANT/REVOKE  with "ON" */
    else if (pg_strcasecmp(prev2_wd, "GRANT") == 0 ||
@@ -1747,8 +1896,18 @@ psql_completion(char *text, int start, int end)
                                   " UNION SELECT 'LARGE OBJECT'"
                                   " UNION SELECT 'SCHEMA'"
                                   " UNION SELECT 'TABLESPACE'");
+   else if ((pg_strcasecmp(prev4_wd, "GRANT") == 0 ||
+             pg_strcasecmp(prev4_wd, "REVOKE") == 0) &&
+            pg_strcasecmp(prev2_wd, "ON") == 0 &&
+            pg_strcasecmp(prev_wd, "FOREIGN") == 0)
+   {
+       static const char *const list_privilege_foreign[] =
+       {"DATA WRAPPER", "SERVER", NULL};
+
+       COMPLETE_WITH_LIST(list_privilege_foreign);
+   }
 
-   /* Complete "GRANT/REVOKE * ON * " with "TO" */
+   /* Complete "GRANT/REVOKE * ON * " with "TO/FROM" */
    else if ((pg_strcasecmp(prev4_wd, "GRANT") == 0 ||
              pg_strcasecmp(prev4_wd, "REVOKE") == 0) &&
             pg_strcasecmp(prev2_wd, "ON") == 0)
@@ -1770,12 +1929,22 @@ psql_completion(char *text, int start, int end)
    }
 
    /* 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_roles);
+   else if (pg_strcasecmp(prev5_wd, "GRANT") == 0 &&
+            pg_strcasecmp(prev3_wd, "ON") == 0)
+   {
+       if (pg_strcasecmp(prev_wd, "TO") == 0)
+           COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
+       else
+           COMPLETE_WITH_CONST("TO");
+   }
+   else if (pg_strcasecmp(prev5_wd, "REVOKE") == 0 &&
+            pg_strcasecmp(prev3_wd, "ON") == 0)
+   {
+       if (pg_strcasecmp(prev_wd, "FROM") == 0)
+           COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
+       else
+           COMPLETE_WITH_CONST("FROM");
+   }
 
 /* GROUP BY */
    else if (pg_strcasecmp(prev3_wd, "FROM") == 0 &&
@@ -1791,20 +1960,20 @@ psql_completion(char *text, int start, int end)
             pg_strcasecmp(prev_wd, "INTO") == 0)
        COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
    /* Complete "INSERT INTO 
 (" with attribute names */
-   else if (rl_line_buffer[start - 1] == '(' &&
-            pg_strcasecmp(prev3_wd, "INSERT") == 0 &&
-            pg_strcasecmp(prev2_wd, "INTO") == 0)
-       COMPLETE_WITH_ATTR(prev_wd, "");
+   else if (pg_strcasecmp(prev4_wd, "INSERT") == 0 &&
+            pg_strcasecmp(prev3_wd, "INTO") == 0 &&
+            pg_strcasecmp(prev_wd, "(") == 0)
+       COMPLETE_WITH_ATTR(prev2_wd, "");
 
    /*
-    * Complete INSERT INTO 
 with "VALUES" or "SELECT" or "TABLE" or
-    * "DEFAULT VALUES"
+    * Complete INSERT INTO 
 with "(" or "VALUES" or "SELECT" or "TABLE"
+    * or "DEFAULT VALUES"
     */
    else if (pg_strcasecmp(prev3_wd, "INSERT") == 0 &&
             pg_strcasecmp(prev2_wd, "INTO") == 0)
    {
        static const char *const list_INSERT[] =
-       {"DEFAULT VALUES", "SELECT", "TABLE", "VALUES", NULL};
+       {"(", "DEFAULT VALUES", "SELECT", "TABLE", "VALUES", NULL};
 
        COMPLETE_WITH_LIST(list_INSERT);
    }
@@ -2056,6 +2225,7 @@ psql_completion(char *text, int start, int end)
    else if (pg_strcasecmp(prev2_wd, "SET") == 0 &&
             pg_strcasecmp(prev4_wd, "UPDATE") != 0 &&
             pg_strcasecmp(prev_wd, "TABLESPACE") != 0 &&
+            prev_wd[strlen(prev_wd) - 1] != ')' &&
             pg_strcasecmp(prev4_wd, "DOMAIN") != 0)
        COMPLETE_WITH_CONST("TO");
    /* Suggest possible variable values */
@@ -2775,17 +2945,21 @@ previous_word(int point, int skip)
                end = -1,
                inquotes = 0;
    char       *s;
+   const char *buf = rl_line_buffer;   /* alias */
+
+   /* first we look for a space or a parenthesis before the current word */
+   for (i = point - 1; i >= 0; i--)
+       if (strchr(WORD_BREAKS, buf[i]))
+           break;
+   point = i;
 
    while (skip-- >= 0)
    {
-       /* first we look for a space before the current word */
-       for (i = point; i >= 0; i--)
-           if (rl_line_buffer[i] == ' ')
-               break;
+       int     parentheses = 0;
 
        /* now find the first non-space which then constitutes the end */
-       for (; i >= 0; i--)
-           if (rl_line_buffer[i] != ' ')
+       for (i = point; i >= 0; i--)
+           if (buf[i] != ' ')
            {
                end = i;
                break;
@@ -2801,52 +2975,37 @@ previous_word(int point, int skip)
        /*
         * Otherwise we now look for the start. The start is either the last
         * character before any space going backwards from the end, or it's
-        * simply character 0
+        * simply character 0. We also handle open quotes and parentheses.
         */
        for (start = end; start > 0; start--)
        {
-           if (rl_line_buffer[start] == '"')
+           if (buf[start] == '"')
                inquotes = !inquotes;
-           if ((rl_line_buffer[start - 1] == ' ') && inquotes == 0)
-               break;
+           if (inquotes == 0)
+           {
+               if (buf[start] == ')')
+                   parentheses++;
+               else if (buf[start] == '(')
+               {
+                   if (--parentheses <= 0)
+                       break;
+               }
+               else if (parentheses == 0 &&
+                        strchr(WORD_BREAKS, buf[start - 1]))
+                   break;
+           }
        }
 
-       point = start;
+       point = start - 1;
    }
 
    /* make a copy */
    s = pg_malloc(end - start + 2);
-   strlcpy(s, &rl_line_buffer[start], end - start + 2);
+   strlcpy(s, &buf[start], end - start + 2);
 
    return s;
 }
 
-/* Find the parenthesis after the last word */
-
-
-static int
-find_open_parenthesis(int end)
-{
-   int         i = end - 1;
-
-   while ((rl_line_buffer[i] != ' ') && (i >= 0))
-   {
-       if (rl_line_buffer[i] == '(')
-           return 1;
-       i--;
-   }
-   while ((rl_line_buffer[i] == ' ') && (i >= 0))
-   {
-       i--;
-   }
-   if (rl_line_buffer[i] == '(')
-   {
-       return 1;
-   }
-   return 0;
-
-}
-
 #if 0
 
 /*