]> granicus.if.org Git - postgresql/blobdiff - src/bin/psql/tab-complete.c
Update copyright for the year 2010.
[postgresql] / src / bin / psql / tab-complete.c
index 80441733fb1fcf64e01afe6d2c4e5345cea291e5..b529b70e2c282265a63e1af18d0e91cb929bdb3c 100644 (file)
@@ -1,9 +1,9 @@
 /*
  * psql - the PostgreSQL interactive terminal
  *
- * Copyright (c) 2000-2005, PostgreSQL Global Development Group
+ * Copyright (c) 2000-2010, PostgreSQL Global Development Group
  *
- * $PostgreSQL: pgsql/src/bin/psql/tab-complete.c,v 1.135 2005/07/28 22:14:30 momjian Exp $
+ * $PostgreSQL: pgsql/src/bin/psql/tab-complete.c,v 1.189 2010/01/02 16:58:00 momjian Exp $
  */
 
 /*----------------------------------------------------------------------
@@ -53,7 +53,7 @@
 #include "pqexpbuffer.h"
 #include "common.h"
 #include "settings.h"
-
+#include "stringutils.h"
 
 #ifdef HAVE_RL_FILENAME_COMPLETION_FUNCTION
 #define filename_completion_function rl_filename_completion_function
@@ -83,30 +83,28 @@ typedef struct SchemaQuery
        const char *catname;
 
        /*
-        * Selection condition --- only rows meeting this condition are
-        * candidates to display.  If catname mentions multiple tables,
-        * include the necessary join condition here.  For example, "c.relkind
-        * = 'r'". Write NULL (not an empty string) if not needed.
+        * Selection condition --- only rows meeting this condition are candidates
+        * to display.  If catname mentions multiple tables, include the necessary
+        * join condition here.  For example, "c.relkind = 'r'". Write NULL (not
+        * an empty string) if not needed.
         */
        const char *selcondition;
 
        /*
         * Visibility condition --- which rows are visible without schema
-        * qualification?  For example,
-        * "pg_catalog.pg_table_is_visible(c.oid)".
+        * qualification?  For example, "pg_catalog.pg_table_is_visible(c.oid)".
         */
        const char *viscondition;
 
        /*
-        * Namespace --- name of field to join to pg_namespace.oid. For
-        * example, "c.relnamespace".
+        * Namespace --- name of field to join to pg_namespace.oid. For example,
+        * "c.relnamespace".
         */
        const char *namespace;
 
        /*
-        * Result --- the appropriately-quoted name to return, in the case of
-        * an unqualified name.  For example,
-        * "pg_catalog.quote_ident(c.relname)".
+        * Result --- the appropriately-quoted name to return, in the case of an
+        * unqualified name.  For example, "pg_catalog.quote_ident(c.relname)".
         */
        const char *result;
 
@@ -128,29 +126,69 @@ static int        completion_max_records;
  * the completion callback functions.  Ugly but there is no better way.
  */
 static const char *completion_charp;   /* to pass a string */
-static const char *const * completion_charpp;  /* to pass a list of
-                                                                                                * strings */
+static const char *const * completion_charpp;  /* to pass a list of strings */
 static const char *completion_info_charp;              /* to pass a second string */
+static const char *completion_info_charp2;             /* to pass a third string */
 static const SchemaQuery *completion_squery;   /* to pass a SchemaQuery */
 
-/* A couple of macros to ease typing. You can use these to complete the given
-   string with
-   1) The results from a query you pass it. (Perhaps one of those below?)
-   2) The results from a schema query you pass it.
-   3) The items from a null-pointer-terminated list.
-   4) A string constant
-   5) The list of attributes to the given table.
-*/
+/*
+ * A few macros to ease typing. You can use these to complete the given
+ * string with
+ * 1) The results from a query you pass it. (Perhaps one of those below?)
+ * 2) The results from a schema query you pass it.
+ * 3) The items from a null-pointer-terminated list.
+ * 4) A string constant.
+ * 5) The list of attributes of the given table (possibly schema-qualified).
+ */
 #define COMPLETE_WITH_QUERY(query) \
-do { completion_charp = query; matches = completion_matches(text, complete_from_query); } while(0)
-#define COMPLETE_WITH_SCHEMA_QUERY(query,addon) \
-do { completion_squery = &(query); completion_charp = addon; matches = completion_matches(text, complete_from_schema_query); } while(0)
+do { \
+       completion_charp = query; \
+       matches = completion_matches(text, complete_from_query); \
+} while (0)
+
+#define COMPLETE_WITH_SCHEMA_QUERY(query, addon) \
+do { \
+       completion_squery = &(query); \
+       completion_charp = addon; \
+       matches = completion_matches(text, complete_from_schema_query); \
+} while (0)
+
 #define COMPLETE_WITH_LIST(list) \
-do { completion_charpp = list; matches = completion_matches(text, complete_from_list); } while(0)
+do { \
+       completion_charpp = list; \
+       matches = completion_matches(text, complete_from_list); \
+} while (0)
+
 #define COMPLETE_WITH_CONST(string) \
-do { completion_charp = string; matches = completion_matches(text, complete_from_const); } while(0)
-#define COMPLETE_WITH_ATTR(table) \
-do {completion_charp = Query_for_list_of_attributes; completion_info_charp = table; matches = completion_matches(text, complete_from_query); } while(0)
+do { \
+       completion_charp = string; \
+       matches = completion_matches(text, complete_from_const); \
+} while (0)
+
+#define COMPLETE_WITH_ATTR(relation, addon) \
+do { \
+       char   *_completion_schema; \
+       char   *_completion_table; \
+\
+       _completion_schema = strtokx(relation, " \t\n\r", ".", "\"", 0, \
+                                                                false, false, pset.encoding); \
+       (void) strtokx(NULL, " \t\n\r", ".", "\"", 0, \
+                                  false, false, pset.encoding); \
+       _completion_table = strtokx(NULL, " \t\n\r", ".", "\"", 0, \
+                                                               false, false, pset.encoding); \
+       if (_completion_table == NULL) \
+       { \
+               completion_charp = Query_for_list_of_attributes  addon; \
+               completion_info_charp = relation; \
+       } \
+       else \
+       { \
+               completion_charp = Query_for_list_of_attributes_with_schema  addon; \
+               completion_info_charp = _completion_table; \
+               completion_info_charp2 = _completion_schema; \
+       } \
+       matches = completion_matches(text, complete_from_query); \
+} while (0)
 
 /*
  * Assembly instructions for schema queries
@@ -312,11 +350,12 @@ static const SchemaQuery Query_for_list_of_views = {
 /*
  * Queries to get lists of names of various kinds of things, possibly
  * restricted to names matching a partially entered name.  In these queries,
- * %s will be replaced by the text entered so far (suitably escaped to
- * become a SQL literal string).  %d will be replaced by the length of the
- * string (in unescaped form). A second %s, if present, will be replaced
- * by a suitably-escaped version of the string provided in
- * completion_info_charp.
+ * the first %s will be replaced by the text entered so far (suitably escaped
+ * to become a SQL literal string).  %d will be replaced by the length of the
+ * string (in unescaped form). A second and third %s, if present, will be
+ * replaced by a suitably-escaped version of the string provided in
+ * completion_info_charp.  A fourth and fifth %s are similarly replaced by
+ * completion_info_charp2.
  *
  * Beware that the allowed sequences of %s and %d are determined by
  * _complete_from_query().
@@ -329,9 +368,27 @@ static const SchemaQuery Query_for_list_of_views = {
 "   AND a.attnum > 0 "\
 "   AND NOT a.attisdropped "\
 "   AND substring(pg_catalog.quote_ident(attname),1,%d)='%s' "\
-"   AND pg_catalog.quote_ident(relname)='%s' "\
+"   AND (pg_catalog.quote_ident(relname)='%s' "\
+"        OR '\"' || relname || '\"'='%s') "\
 "   AND pg_catalog.pg_table_is_visible(c.oid)"
 
+#define Query_for_list_of_attributes_with_schema \
+"SELECT pg_catalog.quote_ident(attname) "\
+"  FROM pg_catalog.pg_attribute a, pg_catalog.pg_class c, pg_catalog.pg_namespace n "\
+" WHERE c.oid = a.attrelid "\
+"   AND n.oid = c.relnamespace "\
+"   AND a.attnum > 0 "\
+"   AND NOT a.attisdropped "\
+"   AND substring(pg_catalog.quote_ident(attname),1,%d)='%s' "\
+"   AND (pg_catalog.quote_ident(relname)='%s' "\
+"        OR '\"' || relname || '\"' ='%s') "\
+"   AND (pg_catalog.quote_ident(nspname)='%s' "\
+"        OR '\"' || nspname || '\"' ='%s') "
+
+#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'"
@@ -355,31 +412,42 @@ static const SchemaQuery Query_for_list_of_views = {
 "SELECT pg_catalog.quote_ident(nspname) FROM pg_catalog.pg_namespace "\
 " WHERE substring(pg_catalog.quote_ident(nspname),1,%d)='%s'"
 
-#define Query_for_list_of_system_relations \
-"SELECT pg_catalog.quote_ident(relname) "\
-"  FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n "\
-" WHERE c.relkind IN ('r', 'v', 's', 'S') "\
-"   AND substring(pg_catalog.quote_ident(relname),1,%d)='%s' "\
-"   AND c.relnamespace = n.oid "\
-"   AND n.nspname = 'pg_catalog'"
-
-#define Query_for_list_of_users \
-" SELECT pg_catalog.quote_ident(usename) "\
-"   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'"
+#define Query_for_list_of_set_vars \
+"SELECT name FROM "\
+" (SELECT pg_catalog.lower(name) AS name FROM pg_catalog.pg_settings "\
+"  WHERE context IN ('user', 'superuser') "\
+"  UNION ALL SELECT 'constraints' "\
+"  UNION ALL SELECT 'transaction' "\
+"  UNION ALL SELECT 'session' "\
+"  UNION ALL SELECT 'role' "\
+"  UNION ALL SELECT 'tablespace' "\
+"  UNION ALL SELECT 'all') ss "\
+" WHERE substring(name,1,%d)='%s'"
+
+#define Query_for_list_of_show_vars \
+"SELECT name FROM "\
+" (SELECT pg_catalog.lower(name) AS name FROM pg_catalog.pg_settings "\
+"  UNION ALL SELECT 'session authorization' "\
+"  UNION ALL SELECT 'all') ss "\
+" WHERE substring(name,1,%d)='%s'"
+
+#define Query_for_list_of_roles \
+" SELECT pg_catalog.quote_ident(rolname) "\
+"   FROM pg_catalog.pg_roles "\
+"  WHERE substring(pg_catalog.quote_ident(rolname),1,%d)='%s'"
+
+#define Query_for_list_of_grant_roles \
+" SELECT pg_catalog.quote_ident(rolname) "\
+"   FROM pg_catalog.pg_roles "\
+"  WHERE substring(pg_catalog.quote_ident(rolname),1,%d)='%s'"\
+" UNION ALL SELECT 'PUBLIC'"
 
 /* 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) "\
 "  FROM pg_catalog.pg_class c1, pg_catalog.pg_class c2, pg_catalog.pg_index i"\
 " WHERE c1.oid=i.indrelid and i.indexrelid=c2.oid"\
-"       and (%d = length('%s'))"\
+"       and (%d = pg_catalog.length('%s'))"\
 "       and pg_catalog.quote_ident(c2.relname)='%s'"\
 "       and pg_catalog.pg_table_is_visible(c2.oid)"
 
@@ -388,7 +456,7 @@ static const SchemaQuery Query_for_list_of_views = {
 "SELECT pg_catalog.quote_ident(c2.relname) "\
 "  FROM pg_catalog.pg_class c1, pg_catalog.pg_class c2, pg_catalog.pg_index i"\
 " WHERE c1.oid=i.indrelid and i.indexrelid=c2.oid"\
-"       and (%d = length('%s'))"\
+"       and (%d = pg_catalog.length('%s'))"\
 "       and pg_catalog.quote_ident(c1.relname)='%s'"\
 "       and pg_catalog.pg_table_is_visible(c2.oid)"
 
@@ -396,11 +464,42 @@ static const SchemaQuery Query_for_list_of_views = {
 #define Query_for_list_of_tables_for_trigger \
 "SELECT pg_catalog.quote_ident(relname) "\
 "  FROM pg_catalog.pg_class"\
-" WHERE (%d = length('%s'))"\
+" WHERE (%d = pg_catalog.length('%s'))"\
 "   AND oid IN "\
 "       (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'"
+
+#define Query_for_list_of_fdws \
+" SELECT pg_catalog.quote_ident(fdwname) "\
+"   FROM pg_catalog.pg_foreign_data_wrapper "\
+"  WHERE substring(pg_catalog.quote_ident(fdwname),1,%d)='%s'"
+
+#define Query_for_list_of_servers \
+" SELECT pg_catalog.quote_ident(srvname) "\
+"   FROM pg_catalog.pg_foreign_server "\
+"  WHERE substring(pg_catalog.quote_ident(srvname),1,%d)='%s'"
+
+#define Query_for_list_of_user_mappings \
+" SELECT pg_catalog.quote_ident(usename) "\
+"   FROM pg_catalog.pg_user_mappings "\
+"  WHERE substring(pg_catalog.quote_ident(usename),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.
@@ -411,40 +510,56 @@ 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[] = {
        {"AGGREGATE", NULL, &Query_for_list_of_aggregates},
-       {"CAST", NULL, NULL},           /* Casts have complex structures for
-                                                                * names, so skip it */
-       /* CREATE CONSTRAINT TRIGGER is not supported here because it is designed to be used only by pg_dump. */
+       {"CAST", NULL, NULL},           /* Casts have complex structures for names, so
+                                                                * skip it */
+
+       /*
+        * 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},
+       {"FOREIGN DATA WRAPPER", NULL, NULL},
        {"FUNCTION", NULL, &Query_for_list_of_functions},
-       {"GROUP", "SELECT pg_catalog.quote_ident(groname) FROM pg_catalog.pg_group WHERE substring(pg_catalog.quote_ident(groname),1,%d)='%s'"},
+       {"GROUP", Query_for_list_of_roles},
        {"LANGUAGE", Query_for_list_of_languages},
        {"INDEX", NULL, &Query_for_list_of_indexes},
-       {"OPERATOR", NULL, NULL},       /* Querying for this is probably not such
-                                                                * a good idea. */
+       {"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},
        {"SEQUENCE", NULL, &Query_for_list_of_sequences},
+       {"SERVER", Query_for_list_of_servers},
        {"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_users},
+       {"USER", Query_for_list_of_roles},
+       {"USER MAPPING FOR", NULL, NULL},
        {"VIEW", NULL, &Query_for_list_of_views},
-       {NULL, NULL, NULL}                      /* end of list */
+       {NULL, NULL, NULL, false}       /* end of list */
 };
 
 
 /* Forward declaration of functions */
 static char **psql_completion(char *text, int start, int end);
 static char *create_command_generator(const char *text, int state);
+static char *drop_command_generator(const char *text, int state);
 static char *complete_from_query(const char *text, int state);
 static char *complete_from_schema_query(const char *text, int state);
 static char *_complete_from_query(int is_schema_query,
@@ -456,6 +571,8 @@ 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);
@@ -474,8 +591,8 @@ initialize_readline(void)
        completion_max_records = 1000;
 
        /*
-        * There is a variable rl_completion_query_items for this but
-        * apparently it's not defined everywhere.
+        * There is a variable rl_completion_query_items for this but apparently
+        * it's not defined everywhere.
         */
 }
 
@@ -499,131 +616,25 @@ psql_completion(char *text, int start, int end)
                           *prev5_wd;
 
        static const char *const sql_commands[] = {
-               "ABORT", "ALTER", "ANALYZE", "BEGIN", "CHECKPOINT", "CLOSE", "CLUSTER", "COMMENT",
-               "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
-       };
-
-       static const char *const pgsql_variables[] = {
-               /* these SET arguments are known in gram.y */
-               "CONSTRAINTS",
-               "NAMES",
-               "SESSION",
-               "TRANSACTION",
-
-               /*
-                * the rest should match USERSET and possibly SUSET entries in
-                * backend/utils/misc/guc.c.
-                */
-               "add_missing_from",
-               "australian_timezones",
-               "client_encoding",
-               "client_min_messages",
-               "commit_delay",
-               "commit_siblings",
-               "cpu_index_tuple_cost",
-               "cpu_operator_cost",
-               "cpu_tuple_cost",
-               "DateStyle",
-               "deadlock_timeout",
-               "debug_pretty_print",
-               "debug_print_parse",
-               "debug_print_plan",
-               "debug_print_rewritten",
-               "default_statistics_target",
-               "default_tablespace",
-               "default_transaction_isolation",
-               "default_transaction_read_only",
-               "default_with_oids",
-               "dynamic_library_path",
-               "effective_cache_size",
-               "enable_bitmapscan",
-               "enable_constraint_exclusion",
-               "enable_hashagg",
-               "enable_hashjoin",
-               "enable_indexscan",
-               "enable_mergejoin",
-               "enable_nestloop",
-               "enable_seqscan",
-               "enable_sort",
-               "enable_tidscan",
-               "explain_pretty_print",
-               "extra_float_digits",
-               "from_collapse_limit",
-               "fsync",
-               "geqo",
-               "geqo_effort",
-               "geqo_generations",
-               "geqo_pool_size",
-               "geqo_selection_bias",
-               "geqo_threshold",
-               "join_collapse_limit",
-               "lc_messages",
-               "lc_monetary",
-               "lc_numeric",
-               "lc_time",
-               "log_destination",
-               "log_duration",
-               "log_error_verbosity",
-               "log_executor_stats",
-               "log_min_duration_statement",
-               "log_min_error_statement",
-               "log_min_messages",
-               "log_parser_stats",
-               "log_planner_stats",
-               "log_statement",
-               "log_statement_stats",
-               "maintenance_work_mem",
-               "max_connections",
-               "max_files_per_process",
-               "max_fsm_pages",
-               "max_fsm_relations",
-               "max_locks_per_transaction",
-               "max_stack_depth",
-               "password_encryption",
-               "port",
-               "random_page_cost",
-               "regex_flavor",
-               "search_path",
-               "shared_buffers",
-               "seed",
-               "server_encoding",
-               "sql_inheritance",
-               "ssl",
-               "statement_timeout",
-               "stats_block_level",
-               "stats_command_string",
-               "stats_reset_on_server_start",
-               "stats_row_level",
-               "stats_start_collector",
-               "superuser_reserved_connections",
-               "syslog_facility",
-               "syslog_ident",
-               "temp_buffers",
-               "TimeZone",
-               "trace_notify",
-               "transform_null_equals",
-               "unix_socket_directory",
-               "unix_socket_group",
-               "unix_socket_permissions",
-               "wal_buffers",
-               "wal_debug",
-               "wal_sync_method",
-               "work_mem",
-               NULL
+               "ABORT", "ALTER", "ANALYZE", "BEGIN", "CHECKPOINT", "CLOSE", "CLUSTER",
+               "COMMENT", "COMMIT", "COPY", "CREATE", "DEALLOCATE", "DECLARE",
+               "DELETE FROM", "DISCARD", "DROP", "END", "EXECUTE", "EXPLAIN", "FETCH",
+               "GRANT", "INSERT", "LISTEN", "LOAD", "LOCK", "MOVE", "NOTIFY", "PREPARE",
+               "REASSIGN", "REINDEX", "RELEASE", "RESET", "REVOKE", "ROLLBACK",
+               "SAVEPOINT", "SELECT", "SET", "SHOW", "START", "TABLE", "TRUNCATE", "UNLISTEN",
+               "UPDATE", "VACUUM", "VALUES", "WITH", 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",
-               "\\e", "\\echo", "\\encoding",
+               "\\d", "\\da", "\\db", "\\dc", "\\dC", "\\dd", "\\dD", "\\des", "\\deu", "\\dew", "\\df",
+               "\\dF", "\\dFd", "\\dFp", "\\dFt", "\\dg", "\\di", "\\dl",
+               "\\dn", "\\do", "\\dp", "\\drds", "\\ds", "\\dS", "\\dt", "\\dT", "\\dv", "\\du",
+               "\\e", "\\echo", "\\ef", "\\encoding",
                "\\f", "\\g", "\\h", "\\help", "\\H", "\\i", "\\l",
                "\\lo_import", "\\lo_export", "\\lo_list", "\\lo_unlink",
-               "\\o", "\\p", "\\pset", "\\q", "\\qecho", "\\r", "\\set", "\\t", "\\T",
+               "\\o", "\\p", "\\password", "\\prompt", "\\pset", "\\q", "\\qecho", "\\r",
+               "\\set", "\\t", "\\T",
                "\\timing", "\\unset", "\\x", "\\w", "\\z", "\\!", NULL
        };
 
@@ -637,12 +648,12 @@ psql_completion(char *text, int start, int end)
        completion_charp = NULL;
        completion_charpp = NULL;
        completion_info_charp = NULL;
+       completion_info_charp2 = NULL;
 
        /*
-        * Scan the input line before our current position for the last four
-        * words. According to those we'll make some smart decisions on what
-        * the user is probably intending to type. TODO: Use strtokx() to do
-        * this.
+        * Scan the input line before our current position for the last five
+        * words. According to those we'll make some smart decisions on what the
+        * user is probably intending to type. TODO: Use strtokx() to do this.
         */
        prev_wd = previous_word(start, 0);
        prev2_wd = previous_word(start, 1);
@@ -658,37 +669,47 @@ psql_completion(char *text, int start, int end)
        else if (!prev_wd)
                COMPLETE_WITH_LIST(sql_commands);
 
-/* CREATE or DROP but not ALTER (TABLE|DOMAIN|GROUP) sth DROP */
-       /* complete with something you can create or drop */
-       else if (pg_strcasecmp(prev_wd, "CREATE") == 0 ||
-                        (pg_strcasecmp(prev_wd, "DROP") == 0 &&
-                         pg_strcasecmp(prev3_wd, "TABLE") != 0 &&
-                         pg_strcasecmp(prev3_wd, "DOMAIN") != 0 &&
-                         pg_strcasecmp(prev3_wd, "GROUP") != 0))
+/* CREATE */
+       /* complete with something you can create */
+       else if (pg_strcasecmp(prev_wd, "CREATE") == 0)
                matches = completion_matches(text, create_command_generator);
 
+/* DROP, but watch out for DROP embedded in other commands */
+       /* complete with something you can drop */
+       else if (pg_strcasecmp(prev_wd, "DROP") == 0 &&
+                        pg_strcasecmp(prev2_wd, "DROP") == 0)
+               matches = completion_matches(text, drop_command_generator);
+
 /* ALTER */
 
        /*
-        * complete with what you can alter (TABLE, GROUP, USER, ...) unless
-        * we're in ALTER TABLE sth ALTER
+        * complete with what you can alter (TABLE, GROUP, USER, ...) unless we're
+        * in ALTER TABLE sth ALTER
         */
        else if (pg_strcasecmp(prev_wd, "ALTER") == 0 &&
                         pg_strcasecmp(prev3_wd, "TABLE") != 0)
        {
                static const char *const list_ALTER[] =
-               {"AGGREGATE", "CONVERSION", "DATABASE", "DOMAIN", "FUNCTION",
-                       "GROUP", "INDEX", "LANGUAGE", "OPERATOR", "SCHEMA", "SEQUENCE", "TABLE",
-               "TABLESPACE", "TRIGGER", "TYPE", "USER", NULL};
+               {"AGGREGATE", "CONVERSION", "DATABASE", "DOMAIN", "FOREIGN DATA WRAPPER", "FUNCTION",
+                       "GROUP", "INDEX", "LANGUAGE", "LARGE OBJECT", "OPERATOR", "ROLE", "SCHEMA", "SERVER", "SEQUENCE", "TABLE",
+               "TABLESPACE", "TEXT SEARCH", "TRIGGER", "TYPE", "USER", "USER MAPPING FOR", "VIEW", NULL};
 
                COMPLETE_WITH_LIST(list_ALTER);
        }
-
-       /* ALTER AGGREGATE,CONVERSION,FUNCTION,SCHEMA <name> */
+       /* ALTER AGGREGATE,FUNCTION <name> */
        else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
                         (pg_strcasecmp(prev2_wd, "AGGREGATE") == 0 ||
-                         pg_strcasecmp(prev2_wd, "CONVERSION") == 0 ||
-                         pg_strcasecmp(prev2_wd, "FUNCTION") == 0 ||
+                         pg_strcasecmp(prev2_wd, "FUNCTION") == 0))
+       {
+               static const char *const list_ALTERAGG[] =
+               {"OWNER TO", "RENAME TO", "SET SCHEMA", NULL};
+
+               COMPLETE_WITH_LIST(list_ALTERAGG);
+       }
+
+       /* ALTER CONVERSION,SCHEMA <name> */
+       else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
+                        (pg_strcasecmp(prev2_wd, "CONVERSION") == 0 ||
                          pg_strcasecmp(prev2_wd, "SCHEMA") == 0))
        {
                static const char *const list_ALTERGEN[] =
@@ -702,17 +723,29 @@ psql_completion(char *text, int start, int end)
                         pg_strcasecmp(prev2_wd, "DATABASE") == 0)
        {
                static const char *const list_ALTERDATABASE[] =
-               {"RESET", "SET", "OWNER TO", "RENAME TO", NULL};
+               {"RESET", "SET", "OWNER TO", "RENAME TO", "CONNECTION LIMIT", NULL};
 
                COMPLETE_WITH_LIST(list_ALTERDATABASE);
        }
 
+       /* ALTER FOREIGN DATA WRAPPER <name> */
+       else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
+                        pg_strcasecmp(prev4_wd, "FOREIGN") == 0 &&
+                        pg_strcasecmp(prev3_wd, "DATA") == 0 &&
+                        pg_strcasecmp(prev2_wd, "WRAPPER") == 0)
+       {
+               static const char *const list_ALTER_FDW[] =
+               {"VALIDATOR", "OPTIONS", "OWNER TO", NULL};
+
+               COMPLETE_WITH_LIST(list_ALTER_FDW);
+       }
+
        /* ALTER INDEX <name> */
        else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
                         pg_strcasecmp(prev2_wd, "INDEX") == 0)
        {
                static const char *const list_ALTERINDEX[] =
-               {"SET TABLESPACE", "OWNER TO", "RENAME TO", NULL};
+               {"OWNER TO", "RENAME TO", "SET", "RESET", NULL};
 
                COMPLETE_WITH_LIST(list_ALTERINDEX);
        }
@@ -720,19 +753,46 @@ psql_completion(char *text, int start, int end)
        /* ALTER LANGUAGE <name> */
        else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
                         pg_strcasecmp(prev2_wd, "LANGUAGE") == 0)
-               COMPLETE_WITH_CONST("RENAME TO");
+       {
+               static const char *const list_ALTERLANGUAGE[] =
+               {"OWNER TO", "RENAME TO", NULL};
+
+               COMPLETE_WITH_LIST(list_ALTERLANGUAGE);
+       }
+
+       /* ALTER LARGE OBJECT <oid> */
+       else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
+                        pg_strcasecmp(prev3_wd, "LARGE") == 0 &&
+                        pg_strcasecmp(prev2_wd, "OBJECT") == 0)
+       {
+               static const char *const list_ALTERLARGEOBJECT[] =
+               {"OWNER TO", NULL};
+
+               COMPLETE_WITH_LIST(list_ALTERLARGEOBJECT);
+       }
 
-       /* ALTER USER <name> */
+       /* ALTER USER,ROLE <name> */
        else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-                        pg_strcasecmp(prev2_wd, "USER") == 0)
+                        !(pg_strcasecmp(prev2_wd, "USER") == 0 && pg_strcasecmp(prev_wd, "MAPPING") == 0) &&
+                        (pg_strcasecmp(prev2_wd, "USER") == 0 ||
+                         pg_strcasecmp(prev2_wd, "ROLE") == 0))
        {
                static const char *const list_ALTERUSER[] =
                {"ENCRYPTED", "UNENCRYPTED", "CREATEDB", "NOCREATEDB", "CREATEUSER",
-                "NOCREATEUSER", "VALID UNTIL", "RENAME TO", "SET", "RESET", NULL};
+                       "NOCREATEUSER", "CREATEROLE", "NOCREATEROLE", "INHERIT", "NOINHERIT",
+                       "LOGIN", "NOLOGIN", "CONNECTION LIMIT", "VALID UNTIL", "RENAME TO",
+               "SUPERUSER", "NOSUPERUSER", "SET", "RESET", NULL};
 
                COMPLETE_WITH_LIST(list_ALTERUSER);
        }
 
+       /* complete ALTER USER,ROLE <name> ENCRYPTED,UNENCRYPTED with PASSWORD */
+       else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
+                        (pg_strcasecmp(prev3_wd, "ROLE") == 0 || pg_strcasecmp(prev3_wd, "USER") == 0) &&
+                        (pg_strcasecmp(prev_wd, "ENCRYPTED") == 0 || pg_strcasecmp(prev_wd, "UNENCRYPTED") == 0))
+       {
+               COMPLETE_WITH_CONST("PASSWORD");
+       }
        /* ALTER DOMAIN <name> */
        else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
                         pg_strcasecmp(prev2_wd, "DOMAIN") == 0)
@@ -758,7 +818,7 @@ psql_completion(char *text, int start, int end)
                         pg_strcasecmp(prev_wd, "SET") == 0)
        {
                static const char *const list_ALTERDOMAIN3[] =
-               {"DEFAULT", "NOT NULL", NULL};
+               {"DEFAULT", "NOT NULL", "SCHEMA", NULL};
 
                COMPLETE_WITH_LIST(list_ALTERDOMAIN3);
        }
@@ -766,20 +826,38 @@ psql_completion(char *text, int start, int end)
        else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
                         pg_strcasecmp(prev2_wd, "SEQUENCE") == 0)
        {
-                       static const char *const list_ALTERSCHEMA[] =
-                       {"INCREMENT", "MINVALUE", "MAXVALUE", "RESTART", "NO", "CACHE", "CYCLE", NULL};
+               static const char *const list_ALTERSEQUENCE[] =
+               {"INCREMENT", "MINVALUE", "MAXVALUE", "RESTART", "NO", "CACHE", "CYCLE",
+               "SET SCHEMA", "OWNED BY", "RENAME TO", NULL};
 
-                       COMPLETE_WITH_LIST(list_ALTERSCHEMA);
+               COMPLETE_WITH_LIST(list_ALTERSEQUENCE);
        }
        /* ALTER SEQUENCE <name> NO */
        else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
                         pg_strcasecmp(prev3_wd, "SEQUENCE") == 0 &&
                         pg_strcasecmp(prev_wd, "NO") == 0)
        {
-                       static const char *const list_ALTERSCHEMA2[] =
-                       {"MINVALUE", "MAXVALUE", "CYCLE", NULL};
+               static const char *const list_ALTERSEQUENCE2[] =
+               {"MINVALUE", "MAXVALUE", "CYCLE", NULL};
+
+               COMPLETE_WITH_LIST(list_ALTERSEQUENCE2);
+       }
+       /* ALTER SERVER <name> */
+       else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
+                        pg_strcasecmp(prev2_wd, "SERVER") == 0)
+       {
+               static const char *const list_ALTER_SERVER[] =
+               {"VERSION", "OPTIONS", "OWNER TO", NULL};
 
-                       COMPLETE_WITH_LIST(list_ALTERSCHEMA2);
+               COMPLETE_WITH_LIST(list_ALTER_SERVER);
+       }
+       /* ALTER VIEW <name> */
+       else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
+                        pg_strcasecmp(prev2_wd, "VIEW") == 0)
+       {
+               static const char *const list_ALTERVIEW[] = {"RENAME TO", NULL};
+
+               COMPLETE_WITH_LIST(list_ALTERVIEW);
        }
        /* ALTER TRIGGER <name>, add ON */
        else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
@@ -814,16 +892,56 @@ 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 ||
                          pg_strcasecmp(prev_wd, "RENAME") == 0))
-               COMPLETE_WITH_ATTR(prev2_wd);
+               COMPLETE_WITH_ATTR(prev2_wd, " UNION SELECT 'COLUMN'");
+
+       /*
+        * If we have TABLE <sth> ALTER COLUMN|RENAME COLUMN, provide list of
+        * columns
+        */
+       else if (pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
+                        (pg_strcasecmp(prev2_wd, "ALTER") == 0 ||
+                         pg_strcasecmp(prev2_wd, "RENAME") == 0) &&
+                        pg_strcasecmp(prev_wd, "COLUMN") == 0)
+               COMPLETE_WITH_ATTR(prev3_wd, "");
 
        /* ALTER TABLE xxx RENAME yyy */
        else if (pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
@@ -831,6 +949,13 @@ psql_completion(char *text, int start, int end)
                         pg_strcasecmp(prev_wd, "TO") != 0)
                COMPLETE_WITH_CONST("TO");
 
+       /* ALTER TABLE xxx RENAME COLUMN yyy */
+       else if (pg_strcasecmp(prev5_wd, "TABLE") == 0 &&
+                        pg_strcasecmp(prev3_wd, "RENAME") == 0 &&
+                        pg_strcasecmp(prev2_wd, "COLUMN") == 0 &&
+                        pg_strcasecmp(prev_wd, "TO") != 0)
+               COMPLETE_WITH_CONST("TO");
+
        /* If we have TABLE <sth> DROP, provide COLUMN or CONSTRAINT */
        else if (pg_strcasecmp(prev3_wd, "TABLE") == 0 &&
                         pg_strcasecmp(prev_wd, "DROP") == 0)
@@ -844,20 +969,51 @@ psql_completion(char *text, int start, int end)
        else if (pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
                         pg_strcasecmp(prev2_wd, "DROP") == 0 &&
                         pg_strcasecmp(prev_wd, "COLUMN") == 0)
-               COMPLETE_WITH_ATTR(prev3_wd);
+               COMPLETE_WITH_ATTR(prev3_wd, "");
        /* ALTER TABLE ALTER [COLUMN] <foo> */
        else if ((pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
                          pg_strcasecmp(prev2_wd, "COLUMN") == 0) ||
                         (pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
                          pg_strcasecmp(prev2_wd, "ALTER") == 0))
        {
-               /* DROP ... does not work well yet */
-               static const char *const list_COLUMNALTER[] = 
-               {"TYPE", "SET DEFAULT", "DROP DEFAULT", "SET NOT NULL",
-                "DROP NOT NULL", "SET STATISTICS", "SET STORAGE", NULL};
+               static const char *const list_COLUMNALTER[] =
+               {"TYPE", "SET", "DROP", NULL};
 
                COMPLETE_WITH_LIST(list_COLUMNALTER);
        }
+       else if (((pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
+                          pg_strcasecmp(prev3_wd, "COLUMN") == 0) ||
+                         (pg_strcasecmp(prev5_wd, "TABLE") == 0 &&
+                          pg_strcasecmp(prev3_wd, "ALTER") == 0)) &&
+                        pg_strcasecmp(prev_wd, "SET") == 0)
+       {
+               static const char *const list_COLUMNSET[] =
+               {"DEFAULT", "NOT NULL", "STATISTICS", "STORAGE", NULL};
+
+               COMPLETE_WITH_LIST(list_COLUMNSET);
+       }
+       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, "STATISTICS") == 0)
+       {
+               static const char *const list_COLUMNSETSTATS[] =
+               {"DISTINCT", NULL};
+
+               COMPLETE_WITH_LIST(list_COLUMNSETSTATS);
+       }
+       else if (((pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
+                          pg_strcasecmp(prev3_wd, "COLUMN") == 0) ||
+                         (pg_strcasecmp(prev5_wd, "TABLE") == 0 &&
+                          pg_strcasecmp(prev3_wd, "ALTER") == 0)) &&
+                        pg_strcasecmp(prev_wd, "DROP") == 0)
+       {
+               static const char *const list_COLUMNDROP[] =
+               {"DEFAULT", "NOT NULL", NULL};
+
+               COMPLETE_WITH_LIST(list_COLUMNDROP);
+       }
        else if (pg_strcasecmp(prev3_wd, "TABLE") == 0 &&
                         pg_strcasecmp(prev_wd, "CLUSTER") == 0)
                COMPLETE_WITH_CONST("ON");
@@ -868,12 +1024,12 @@ psql_completion(char *text, int start, int end)
                completion_info_charp = prev3_wd;
                COMPLETE_WITH_QUERY(Query_for_index_of_table);
        }
-       /* If we have TABLE <sth> SET, provide WITHOUT or TABLESPACE */
+       /* If we have TABLE <sth> SET, provide WITHOUT,TABLESPACE and SCHEMA */
        else if (pg_strcasecmp(prev3_wd, "TABLE") == 0 &&
                         pg_strcasecmp(prev_wd, "SET") == 0)
        {
                static const char *const list_TABLESET[] =
-               {"WITHOUT", "TABLESPACE", NULL};
+               {"WITHOUT", "TABLESPACE", "SCHEMA", NULL};
 
                COMPLETE_WITH_LIST(list_TABLESET);
        }
@@ -901,10 +1057,54 @@ psql_completion(char *text, int start, int end)
 
                COMPLETE_WITH_LIST(list_ALTERTSPC);
        }
-       /* complete ALTER TYPE <foo> with OWNER TO */
+       /* 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)
-               COMPLETE_WITH_CONST("OWNER TO");
+       {
+               static const char *const list_ALTERTYPE[] =
+               {"OWNER TO", "SET SCHEMA", NULL};
+
+               COMPLETE_WITH_LIST(list_ALTERTYPE);
+       }
        /* complete ALTER GROUP <foo> */
        else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
                         pg_strcasecmp(prev2_wd, "GROUP") == 0)
@@ -925,7 +1125,7 @@ psql_completion(char *text, int start, int end)
                         (pg_strcasecmp(prev2_wd, "ADD") == 0 ||
                          pg_strcasecmp(prev2_wd, "DROP") == 0) &&
                         pg_strcasecmp(prev_wd, "USER") == 0)
-               COMPLETE_WITH_QUERY(Query_for_list_of_users);
+               COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 
 /* BEGIN, END, ABORT */
        else if (pg_strcasecmp(prev_wd, "BEGIN") == 0 ||
@@ -936,9 +1136,9 @@ psql_completion(char *text, int start, int end)
                {"WORK", "TRANSACTION", NULL};
 
                COMPLETE_WITH_LIST(list_TRANS);
-       } 
+       }
 /* COMMIT */
-       else if(pg_strcasecmp(prev_wd, "COMMIT") == 0)
+       else if (pg_strcasecmp(prev_wd, "COMMIT") == 0)
        {
                static const char *const list_COMMIT[] =
                {"WORK", "TRANSACTION", "PREPARED", NULL};
@@ -959,26 +1159,26 @@ psql_completion(char *text, int start, int end)
 /* CLUSTER */
 
        /*
-        * If the previous word is CLUSTER and not without produce list of
-        * indexes.
+        * If the previous word is CLUSTER and not without produce list of tables
         */
        else if (pg_strcasecmp(prev_wd, "CLUSTER") == 0 &&
                         pg_strcasecmp(prev2_wd, "WITHOUT") != 0)
-               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
-       /* If we have CLUSTER <sth>, then add "ON" */
+               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+       /* If we have CLUSTER <sth>, then add "USING" */
        else if (pg_strcasecmp(prev2_wd, "CLUSTER") == 0 &&
                         pg_strcasecmp(prev_wd, "ON") != 0)
-               COMPLETE_WITH_CONST("ON");
+       {
+               COMPLETE_WITH_CONST("USING");
+       }
 
        /*
-        * If we have CLUSTER <sth> ON, then add the correct tablename 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, "ON") == 0)
+                        pg_strcasecmp(prev_wd, "USING") == 0)
        {
                completion_info_charp = prev2_wd;
-               COMPLETE_WITH_QUERY(Query_for_table_owning_index);
+               COMPLETE_WITH_QUERY(Query_for_index_of_table);
        }
 
 /* COMMENT */
@@ -990,12 +1190,26 @@ psql_completion(char *text, int start, int end)
                static const char *const list_COMMENT[] =
                {"CAST", "CONVERSION", "DATABASE", "INDEX", "LANGUAGE", "RULE", "SCHEMA",
                        "SEQUENCE", "TABLE", "TYPE", "VIEW", "COLUMN", "AGGREGATE", "FUNCTION",
-               "OPERATOR", "TRIGGER", "CONSTRAINT", "DOMAIN", NULL};
+                       "OPERATOR", "TRIGGER", "CONSTRAINT", "DOMAIN", "LARGE OBJECT",
+               "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 */
@@ -1013,12 +1227,12 @@ psql_completion(char *text, int start, int end)
        else if (pg_strcasecmp(prev2_wd, "COPY") == 0 ||
                         pg_strcasecmp(prev2_wd, "\\copy") == 0 ||
                         pg_strcasecmp(prev2_wd, "BINARY") == 0)
-               {
-                       static const char *const list_FROMTO[] =
-                       {"FROM", "TO", NULL};
+       {
+               static const char *const list_FROMTO[] =
+               {"FROM", "TO", NULL};
 
-                       COMPLETE_WITH_LIST(list_FROMTO);
-               }
+               COMPLETE_WITH_LIST(list_FROMTO);
+       }
        /* If we have COPY|BINARY <sth> FROM|TO, complete with filename */
        else if ((pg_strcasecmp(prev3_wd, "COPY") == 0 ||
                          pg_strcasecmp(prev3_wd, "\\copy") == 0 ||
@@ -1033,25 +1247,48 @@ psql_completion(char *text, int start, int end)
                          pg_strcasecmp(prev4_wd, "BINARY") == 0) &&
                         (pg_strcasecmp(prev2_wd, "FROM") == 0 ||
                          pg_strcasecmp(prev2_wd, "TO") == 0))
-               {
-                       static const char *const list_COPY[] =
-                       {"BINARY", "OIDS", "DELIMITER", "NULL", "CSV", NULL};
+       {
+               static const char *const list_COPY[] =
+               {"BINARY", "OIDS", "DELIMITER", "NULL", "CSV", NULL};
 
-                       COMPLETE_WITH_LIST(list_COPY);
-               }
+               COMPLETE_WITH_LIST(list_COPY);
+       }
 
        /* Handle COPY|BINARY <sth> FROM|TO filename CSV */
-       else if (pg_strcasecmp(prev_wd, "CSV") == 0 && 
+       else if (pg_strcasecmp(prev_wd, "CSV") == 0 &&
                         (pg_strcasecmp(prev3_wd, "FROM") == 0 ||
                          pg_strcasecmp(prev3_wd, "TO") == 0))
-               {
-                       static const char *const list_CSV[] =
-                       {"HEADER", "QUOTE", "ESCAPE", "FORCE QUOTE", NULL};
+       {
+               static const char *const list_CSV[] =
+               {"HEADER", "QUOTE", "ESCAPE", "FORCE QUOTE", NULL};
 
-                       COMPLETE_WITH_LIST(list_CSV);
-               }
+               COMPLETE_WITH_LIST(list_CSV);
+       }
+
+       /* CREATE DATABASE */
+       else if (pg_strcasecmp(prev3_wd, "CREATE") == 0 &&
+                        pg_strcasecmp(prev2_wd, "DATABASE") == 0)
+       {
+               static const char *const list_DATABASE[] =
+               {"OWNER", "TEMPLATE", "ENCODING", "TABLESPACE", "CONNECTION LIMIT",
+               NULL};
+
+               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 FOREIGN DATA WRAPPER */
+       else if (pg_strcasecmp(prev5_wd, "CREATE") == 0 &&
+                        pg_strcasecmp(prev4_wd, "FOREIGN") == 0 &&
+                        pg_strcasecmp(prev3_wd, "DATA") == 0 &&
+                        pg_strcasecmp(prev2_wd, "WRAPPER") == 0)
+               COMPLETE_WITH_CONST("VALIDATOR");
 
-/* CREATE INDEX */
+       /* CREATE INDEX */
        /* First off we complete CREATE UNIQUE with "INDEX" */
        else if (pg_strcasecmp(prev2_wd, "CREATE") == 0 &&
                         pg_strcasecmp(prev_wd, "UNIQUE") == 0)
@@ -1067,21 +1304,30 @@ psql_completion(char *text, int start, int end)
                COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 
        /*
-        * Complete INDEX <name> ON <table> with a list of table columns
-        * (which should really be in parens)
+        * Complete INDEX <name> ON <table> with a list of table columns (which
+        * should really be in parens)
         */
        else if (pg_strcasecmp(prev4_wd, "INDEX") == 0 &&
                         pg_strcasecmp(prev2_wd, "ON") == 0)
-               COMPLETE_WITH_ATTR(prev_wd);
+       {
+               if (find_open_parenthesis(end))
+                       COMPLETE_WITH_ATTR(prev_wd, "");
+               else
+                       COMPLETE_WITH_CONST("(");
+       }
+       else if (pg_strcasecmp(prev5_wd, "INDEX") == 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);
+               COMPLETE_WITH_ATTR(prev3_wd, "");
        /* Complete USING with an index method */
        else if (pg_strcasecmp(prev_wd, "USING") == 0)
        {
                static const char *const index_mth[] =
-               {"BTREE", "RTREE", "HASH", "GIST", NULL};
+               {"BTREE", "HASH", "GIN", "GIST", NULL};
 
                COMPLETE_WITH_LIST(index_mth);
        }
@@ -1109,8 +1355,8 @@ psql_completion(char *text, int start, int end)
        /* Complete "AS ON <sth with a 'T' :)>" with a "TO" */
        else if (pg_strcasecmp(prev3_wd, "AS") == 0 &&
                         pg_strcasecmp(prev2_wd, "ON") == 0 &&
-                        (toupper((unsigned char) prev_wd[4]) == 'T' ||
-                         toupper((unsigned char) prev_wd[5]) == 'T'))
+                        (pg_toupper((unsigned char) prev_wd[4]) == 'T' ||
+                         pg_toupper((unsigned char) prev_wd[5]) == 'T'))
                COMPLETE_WITH_CONST("TO");
        /* Complete "AS ON <sth> TO" with a table name */
        else if (pg_strcasecmp(prev4_wd, "AS") == 0 &&
@@ -1118,11 +1364,27 @@ psql_completion(char *text, int start, int end)
                         pg_strcasecmp(prev_wd, "TO") == 0)
                COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 
+/* CREATE SERVER <name> */
+       else if (pg_strcasecmp(prev3_wd, "CREATE") == 0 &&
+                        pg_strcasecmp(prev2_wd, "SERVER") == 0)
+       {
+               static const char *const list_CREATE_SERVER[] =
+               {"TYPE", "VERSION", "FOREIGN DATA WRAPPER", NULL};
+
+               COMPLETE_WITH_LIST(list_CREATE_SERVER);
+       }
+
 /* CREATE TABLE */
-       /* Complete CREATE TEMP with "TABLE" */
+       /* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
        else if (pg_strcasecmp(prev2_wd, "CREATE") == 0 &&
-                        pg_strcasecmp(prev_wd, "TEMP") == 0)
-               COMPLETE_WITH_CONST("TABLE");
+                        (pg_strcasecmp(prev_wd, "TEMP") == 0 ||
+                         pg_strcasecmp(prev_wd, "TEMPORARY") == 0))
+       {
+               static const char *const list_TEMP[] =
+               {"SEQUENCE", "TABLE", "VIEW", NULL};
+
+               COMPLETE_WITH_LIST(list_TEMP);
+       }
 
 /* CREATE TABLESPACE */
        else if (pg_strcasecmp(prev3_wd, "CREATE") == 0 &&
@@ -1141,11 +1403,83 @@ 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 */
-       /* is on the agenda . . . */
+       /* complete CREATE TRIGGER <name> with BEFORE,AFTER */
+       else if (pg_strcasecmp(prev3_wd, "CREATE") == 0 &&
+                        pg_strcasecmp(prev2_wd, "TRIGGER") == 0)
+       {
+               static const char *const list_CREATETRIGGER[] =
+               {"BEFORE", "AFTER", NULL};
+
+               COMPLETE_WITH_LIST(list_CREATETRIGGER);
+       }
+       /* complete CREATE TRIGGER <name> BEFORE,AFTER sth with OR,ON */
+       else if (pg_strcasecmp(prev5_wd, "CREATE") == 0 &&
+                        pg_strcasecmp(prev4_wd, "TRIGGER") == 0 &&
+                        (pg_strcasecmp(prev2_wd, "BEFORE") == 0 ||
+                         pg_strcasecmp(prev2_wd, "AFTER") == 0))
+       {
+               static const char *const list_CREATETRIGGER2[] =
+               {"ON", "OR", NULL};
+
+               COMPLETE_WITH_LIST(list_CREATETRIGGER2);
+       }
+
+/* CREATE ROLE,USER,GROUP */
+       else if (pg_strcasecmp(prev3_wd, "CREATE") == 0 &&
+                        !(pg_strcasecmp(prev2_wd, "USER") == 0 && pg_strcasecmp(prev_wd, "MAPPING") == 0) &&
+                        (pg_strcasecmp(prev2_wd, "ROLE") == 0 ||
+                         pg_strcasecmp(prev2_wd, "GROUP") == 0 || pg_strcasecmp(prev2_wd, "USER") == 0))
+       {
+               static const char *const list_CREATEROLE[] =
+               {"ADMIN", "CONNECTION LIMIT", "CREATEDB", "CREATEROLE", "CREATEUSER",
+                       "ENCRYPTED", "IN", "INHERIT", "LOGIN", "NOINHERIT", "NOLOGIN", "NOCREATEDB",
+                       "NOCREATEROLE", "NOCREATEUSER", "NOSUPERUSER", "ROLE", "SUPERUSER", "SYSID",
+               "UNENCRYPTED", NULL};
+
+               COMPLETE_WITH_LIST(list_CREATEROLE);
+       }
+
+       /*
+        * complete CREATE ROLE,USER,GROUP <name> ENCRYPTED,UNENCRYPTED with
+        * PASSWORD
+        */
+       else if (pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
+                        (pg_strcasecmp(prev3_wd, "ROLE") == 0 ||
+                         pg_strcasecmp(prev3_wd, "GROUP") == 0 || pg_strcasecmp(prev3_wd, "USER") == 0) &&
+                        (pg_strcasecmp(prev_wd, "ENCRYPTED") == 0 || pg_strcasecmp(prev_wd, "UNENCRYPTED") == 0))
+       {
+               COMPLETE_WITH_CONST("PASSWORD");
+       }
+       /* complete CREATE ROLE,USER,GROUP <name> IN with ROLE,GROUP */
+       else if (pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
+                        (pg_strcasecmp(prev3_wd, "ROLE") == 0 ||
+                         pg_strcasecmp(prev3_wd, "GROUP") == 0 || pg_strcasecmp(prev3_wd, "USER") == 0) &&
+                        pg_strcasecmp(prev_wd, "IN") == 0)
+       {
+               static const char *const list_CREATEROLE3[] =
+               {"GROUP", "ROLE", NULL};
+
+               COMPLETE_WITH_LIST(list_CREATEROLE3);
+       }
 
 /* CREATE VIEW */
-       /* Complete "CREATE VIEW <name>" with "AS" */
+       /* Complete CREATE VIEW <name> with AS */
        else if (pg_strcasecmp(prev3_wd, "CREATE") == 0 &&
                         pg_strcasecmp(prev2_wd, "VIEW") == 0)
                COMPLETE_WITH_CONST("AS");
@@ -1158,20 +1492,23 @@ psql_completion(char *text, int start, int end)
 /* 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);
+               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);
+               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)
@@ -1197,6 +1534,15 @@ psql_completion(char *text, int start, int end)
        }
        /* XXX: implement tab completion for DELETE ... USING */
 
+/* DISCARD */
+       else if (pg_strcasecmp(prev_wd, "DISCARD") == 0)
+       {
+               static const char *const list_DISCARD[] =
+               {"ALL", "PLANS", "TEMP", NULL};
+
+               COMPLETE_WITH_LIST(list_DISCARD);
+       }
+
 /* DROP (when not the previous word) */
        /* DROP AGGREGATE */
        else if (pg_strcasecmp(prev3_wd, "DROP") == 0 &&
@@ -1212,44 +1558,110 @@ psql_completion(char *text, int start, int end)
                           pg_strcasecmp(prev2_wd, "LANGUAGE") == 0 ||
                           pg_strcasecmp(prev2_wd, "SCHEMA") == 0 ||
                           pg_strcasecmp(prev2_wd, "SEQUENCE") == 0 ||
+                          pg_strcasecmp(prev2_wd, "SERVER") == 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] == ')'))
+                         prev_wd[strlen(prev_wd) - 1] == ')') ||
+                        (pg_strcasecmp(prev5_wd, "DROP") == 0 &&
+                         pg_strcasecmp(prev4_wd, "FOREIGN") == 0 &&
+                         pg_strcasecmp(prev3_wd, "DATA") == 0 &&
+                         pg_strcasecmp(prev2_wd, "WRAPPER") == 0) ||
+                        (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))
+               {
+                       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("(");
+                       }
+               }
+               else
+               {
                        static const char *const list_DROPCR[] =
                        {"CASCADE", "RESTRICT", NULL};
+
                        COMPLETE_WITH_LIST(list_DROPCR);
+               }
+       }
+       else if (pg_strcasecmp(prev4_wd, "DROP") == 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'";
+               char       *tmp_buf = malloc(strlen(func_args_query) + strlen(prev2_wd));
+
+               sprintf(tmp_buf, func_args_query, prev2_wd);
+               COMPLETE_WITH_QUERY(tmp_buf);
+               free(tmp_buf);
+       }
+       /* DROP OWNED BY */
+       else if (pg_strcasecmp(prev2_wd, "DROP") == 0 &&
+                        pg_strcasecmp(prev_wd, "OWNED") == 0)
+               COMPLETE_WITH_CONST("BY");
+       else if (pg_strcasecmp(prev3_wd, "DROP") == 0 &&
+                        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 */
+
        /*
         * Complete EXPLAIN [ANALYZE] [VERBOSE] with list of EXPLAIN-able 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);
+               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)
+                        pg_strcasecmp(prev_wd, "ANALYZE") == 0)
        {
-                       static const char *const list_EXPLAIN[] =
-                       {"SELECT","INSERT","DELETE","UPDATE","DECLARE","VERBOSE",NULL};
-                       COMPLETE_WITH_LIST(list_EXPLAIN);
+               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))
+       else if ((pg_strcasecmp(prev2_wd, "EXPLAIN") == 0 &&
+                         pg_strcasecmp(prev_wd, "VERBOSE") == 0) ||
+                        (pg_strcasecmp(prev3_wd, "EXPLAIN") == 0 &&
+                         pg_strcasecmp(prev2_wd, "ANALYZE") == 0 &&
+                         pg_strcasecmp(prev_wd, "VERBOSE") == 0))
        {
-                       static const char *const list_EXPLAIN[] =
-                       {"SELECT","INSERT","DELETE","UPDATE","DECLARE",NULL};
-                       COMPLETE_WITH_LIST(list_EXPLAIN);
+               static const char *const list_EXPLAIN[] =
+               {"SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE", NULL};
+
+               COMPLETE_WITH_LIST(list_EXPLAIN);
        }
 
 /* FETCH && MOVE */
@@ -1273,9 +1685,9 @@ psql_completion(char *text, int start, int end)
        }
 
        /*
-        * Complete FETCH <sth1> <sth2> with "FROM" or "IN". These are
-        * equivalent, but we may as well tab-complete both: perhaps some
-        * users prefer one variant or the other.
+        * Complete FETCH <sth1> <sth2> with "FROM" or "IN". These are equivalent,
+        * but we may as well tab-complete both: perhaps some users prefer one
+        * variant or the other.
         */
        else if (pg_strcasecmp(prev3_wd, "FETCH") == 0 ||
                         pg_strcasecmp(prev3_wd, "MOVE") == 0)
@@ -1286,14 +1698,23 @@ psql_completion(char *text, int start, int end)
                COMPLETE_WITH_LIST(list_FROMIN);
        }
 
+/* FOREIGN DATA WRAPPER */
+       /* applies in ALTER/DROP FDW and in CREATE SERVER */
+       else if (pg_strcasecmp(prev4_wd, "CREATE") != 0 &&
+                        pg_strcasecmp(prev3_wd, "FOREIGN") == 0 &&
+                        pg_strcasecmp(prev2_wd, "DATA") == 0 &&
+                        pg_strcasecmp(prev_wd, "WRAPPER") == 0)
+               COMPLETE_WITH_QUERY(Query_for_list_of_fdws);
+
 /* GRANT && REVOKE*/
        /* Complete GRANT/REVOKE with a list of privileges */
        else if (pg_strcasecmp(prev_wd, "GRANT") == 0 ||
                         pg_strcasecmp(prev_wd, "REVOKE") == 0)
        {
                static const char *const list_privileg[] =
-               {"SELECT", "INSERT", "UPDATE", "DELETE", "RULE", "REFERENCES",
-               "TRIGGER", "CREATE", "TEMPORARY", "EXECUTE", "USAGE", "ALL", NULL};
+               {"SELECT", "INSERT", "UPDATE", "DELETE", "TRUNCATE", "REFERENCES",
+                       "TRIGGER", "CREATE", "CONNECT", "TEMPORARY", "EXECUTE", "USAGE",
+               "ALL", NULL};
 
                COMPLETE_WITH_LIST(list_privileg);
        }
@@ -1303,23 +1724,26 @@ psql_completion(char *text, int start, int end)
                COMPLETE_WITH_CONST("ON");
 
        /*
-        * Complete GRANT/REVOKE <sth> ON with a list of tables, views,
-        * sequences, and indexes
+        * Complete GRANT/REVOKE <sth> ON with a list of tables, views, sequences,
+        * and indexes
         *
-        * keywords DATABASE, FUNCTION, LANGUAGE, SCHEMA added to query result
-        * via UNION; seems to work intuitively
+        * keywords DATABASE, FUNCTION, LANGUAGE, SCHEMA added to query result via
+        * UNION; seems to work intuitively
         *
-        * Note: GRANT/REVOKE can get quite complex; tab-completion as
-        * implemented here will only work if the privilege list contains
-        * exactly one privilege
+        * Note: GRANT/REVOKE can get quite complex; tab-completion as implemented
+        * here will only work if the privilege list contains exactly one
+        * privilege
         */
        else if ((pg_strcasecmp(prev3_wd, "GRANT") == 0 ||
                          pg_strcasecmp(prev3_wd, "REVOKE") == 0) &&
                         pg_strcasecmp(prev_wd, "ON") == 0)
                COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsv,
                                                                   " UNION SELECT 'DATABASE'"
+                                                                  " UNION SELECT 'FOREIGN DATA WRAPPER'"
+                                                                  " UNION SELECT 'FOREIGN SERVER'"
                                                                   " UNION SELECT 'FUNCTION'"
                                                                   " UNION SELECT 'LANGUAGE'"
+                                                                  " UNION SELECT 'LARGE OBJECT'"
                                                                   " UNION SELECT 'SCHEMA'"
                                                                   " UNION SELECT 'TABLESPACE'");
 
@@ -1350,7 +1774,7 @@ psql_completion(char *text, int start, int end)
                           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);
+               COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
 
 /* GROUP BY */
        else if (pg_strcasecmp(prev3_wd, "FROM") == 0 &&
@@ -1369,27 +1793,31 @@ psql_completion(char *text, int start, int end)
        else if (rl_line_buffer[start - 1] == '(' &&
                         pg_strcasecmp(prev3_wd, "INSERT") == 0 &&
                         pg_strcasecmp(prev2_wd, "INTO") == 0)
-               COMPLETE_WITH_ATTR(prev_wd);
+               COMPLETE_WITH_ATTR(prev_wd, "");
 
        /*
-        * Complete INSERT INTO <table> with "VALUES" or "SELECT" or "DEFAULT
-        * VALUES"
+        * Complete INSERT INTO <table> with "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", "VALUES", NULL};
+               {"DEFAULT VALUES", "SELECT", "TABLE", "VALUES", NULL};
 
                COMPLETE_WITH_LIST(list_INSERT);
        }
-       /* Complete INSERT INTO <table> (attribs) with "VALUES" or "SELECT" */
+
+       /*
+        * Complete INSERT INTO <table> (attribs) with "VALUES" or "SELECT" or
+        * "TABLE"
+        */
        else if (pg_strcasecmp(prev4_wd, "INSERT") == 0 &&
                         pg_strcasecmp(prev3_wd, "INTO") == 0 &&
                         prev_wd[strlen(prev_wd) - 1] == ')')
        {
                static const char *const list_INSERT[] =
-               {"SELECT", "VALUES", NULL};
+               {"SELECT", "TABLE", "VALUES", NULL};
 
                COMPLETE_WITH_LIST(list_INSERT);
        }
@@ -1401,10 +1829,12 @@ psql_completion(char *text, int start, int end)
 
 /* LOCK */
        /* Complete LOCK [TABLE] with a list of tables */
-       else if (pg_strcasecmp(prev_wd, "LOCK") == 0 ||
-                        (pg_strcasecmp(prev_wd, "TABLE") == 0 &&
-                         pg_strcasecmp(prev2_wd, "LOCK") == 0))
-               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+       else if (pg_strcasecmp(prev_wd, "LOCK") == 0)
+               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
+                                                                  " UNION SELECT 'TABLE'");
+       else if (pg_strcasecmp(prev_wd, "TABLE") == 0 &&
+                        pg_strcasecmp(prev2_wd, "LOCK") == 0)
+               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
 
        /* For the following, handle the case of a single table only for now */
 
@@ -1435,10 +1865,14 @@ psql_completion(char *text, int start, int end)
        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*/
+/* OPTIONS */
+       else if (pg_strcasecmp(prev_wd, "OPTIONS") == 0)
+               COMPLETE_WITH_CONST("(");
+
+/* OWNER TO  - complete with available roles */
        else if (pg_strcasecmp(prev2_wd, "OWNER") == 0 &&
                         pg_strcasecmp(prev_wd, "TO") == 0)
-               COMPLETE_WITH_QUERY(Query_for_list_of_users);
+               COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 
 /* ORDER BY */
        else if (pg_strcasecmp(prev3_wd, "FROM") == 0 &&
@@ -1447,7 +1881,7 @@ psql_completion(char *text, int start, int end)
        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);
+               COMPLETE_WITH_ATTR(prev3_wd, "");
 
 /* PREPARE xx AS */
        else if (pg_strcasecmp(prev_wd, "AS") == 0 &&
@@ -1459,6 +1893,25 @@ psql_completion(char *text, int start, int end)
                COMPLETE_WITH_LIST(list_PREPARE);
        }
 
+/* REASSIGN OWNED BY xxx TO yyy */
+       else if (pg_strcasecmp(prev_wd, "REASSIGN") == 0)
+               COMPLETE_WITH_CONST("OWNED");
+       else if (pg_strcasecmp(prev_wd, "OWNED") == 0 &&
+                        pg_strcasecmp(prev2_wd, "REASSIGN") == 0)
+               COMPLETE_WITH_CONST("BY");
+       else if (pg_strcasecmp(prev_wd, "BY") == 0 &&
+                        pg_strcasecmp(prev2_wd, "OWNED") == 0 &&
+                        pg_strcasecmp(prev3_wd, "REASSIGN") == 0)
+               COMPLETE_WITH_QUERY(Query_for_list_of_roles);
+       else if (pg_strcasecmp(prev2_wd, "BY") == 0 &&
+                        pg_strcasecmp(prev3_wd, "OWNED") == 0 &&
+                        pg_strcasecmp(prev4_wd, "REASSIGN") == 0)
+               COMPLETE_WITH_CONST("TO");
+       else if (pg_strcasecmp(prev_wd, "TO") == 0 &&
+                        pg_strcasecmp(prev3_wd, "BY") == 0 &&
+                        pg_strcasecmp(prev4_wd, "OWNED") == 0 &&
+                        pg_strcasecmp(prev5_wd, "REASSIGN") == 0)
+               COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 
 /* REINDEX */
        else if (pg_strcasecmp(prev_wd, "REINDEX") == 0)
@@ -1486,9 +1939,10 @@ psql_completion(char *text, int start, int end)
        /* Complete with a variable name */
        else if ((pg_strcasecmp(prev_wd, "SET") == 0 &&
                          pg_strcasecmp(prev3_wd, "UPDATE") != 0) ||
-                        pg_strcasecmp(prev_wd, "RESET") == 0 ||
-                        pg_strcasecmp(prev_wd, "SHOW") == 0)
-               COMPLETE_WITH_LIST(pgsql_variables);
+                        pg_strcasecmp(prev_wd, "RESET") == 0)
+               COMPLETE_WITH_QUERY(Query_for_list_of_set_vars);
+       else if (pg_strcasecmp(prev_wd, "SHOW") == 0)
+               COMPLETE_WITH_QUERY(Query_for_list_of_show_vars);
        /* Complete "SET TRANSACTION" */
        else if ((pg_strcasecmp(prev2_wd, "SET") == 0 &&
                          pg_strcasecmp(prev_wd, "TRANSACTION") == 0)
@@ -1570,6 +2024,10 @@ psql_completion(char *text, int start, int end)
 
                COMPLETE_WITH_LIST(constraint_list);
        }
+       /* Complete SET ROLE */
+       else if (pg_strcasecmp(prev2_wd, "SET") == 0 &&
+                        pg_strcasecmp(prev_wd, "ROLE") == 0)
+               COMPLETE_WITH_QUERY(Query_for_list_of_roles);
        /* Complete SET SESSION with AUTHORIZATION or CHARACTERISTICS... */
        else if (pg_strcasecmp(prev2_wd, "SET") == 0 &&
                         pg_strcasecmp(prev_wd, "SESSION") == 0)
@@ -1583,7 +2041,11 @@ psql_completion(char *text, int start, int end)
        else if (pg_strcasecmp(prev3_wd, "SET") == 0
                         && pg_strcasecmp(prev2_wd, "SESSION") == 0
                         && pg_strcasecmp(prev_wd, "AUTHORIZATION") == 0)
-               COMPLETE_WITH_QUERY(Query_for_list_of_users);
+               COMPLETE_WITH_QUERY(Query_for_list_of_roles " UNION SELECT 'DEFAULT'");
+       /* Complete RESET SESSION with AUTHORIZATION */
+       else if (pg_strcasecmp(prev2_wd, "RESET") == 0 &&
+                        pg_strcasecmp(prev_wd, "SESSION") == 0)
+               COMPLETE_WITH_CONST("AUTHORIZATION");
        /* Complete SET <var> with "TO" */
        else if (pg_strcasecmp(prev2_wd, "SET") == 0 &&
                         pg_strcasecmp(prev4_wd, "UPDATE") != 0 &&
@@ -1592,18 +2054,25 @@ psql_completion(char *text, int start, int end)
                COMPLETE_WITH_CONST("TO");
        /* Suggest possible variable values */
        else if (pg_strcasecmp(prev3_wd, "SET") == 0 &&
-               (pg_strcasecmp(prev_wd, "TO") == 0 || strcmp(prev_wd, "=") == 0))
+                        (pg_strcasecmp(prev_wd, "TO") == 0 || strcmp(prev_wd, "=") == 0))
        {
                if (pg_strcasecmp(prev2_wd, "DateStyle") == 0)
                {
                        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);
                }
+               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[] =
@@ -1641,18 +2110,44 @@ psql_completion(char *text, int start, int end)
                COMPLETE_WITH_CONST("SET");
 
        /*
-        * If the previous word is SET (and it wasn't caught above as the
-        * _first_ word) the word before it was (hopefully) a table name and
-        * we'll now make a list of attributes.
+        * If the previous word is SET (and it wasn't caught above as the _first_
+        * word) the word before it was (hopefully) a table name and we'll now
+        * make a list of attributes.
         */
        else if (pg_strcasecmp(prev_wd, "SET") == 0)
-               COMPLETE_WITH_ATTR(prev2_wd);
+               COMPLETE_WITH_ATTR(prev2_wd, "");
 
 /* UPDATE xx SET yy = */
        else if (pg_strcasecmp(prev2_wd, "SET") == 0 &&
-                                        pg_strcasecmp(prev4_wd, "UPDATE") == 0)
+                        pg_strcasecmp(prev4_wd, "UPDATE") == 0)
                COMPLETE_WITH_CONST("=");
 
+/* USER MAPPING */
+       else if ((pg_strcasecmp(prev3_wd, "ALTER") == 0 ||
+                         pg_strcasecmp(prev3_wd, "CREATE") == 0 ||
+                         pg_strcasecmp(prev3_wd, "DROP") == 0) &&
+                        pg_strcasecmp(prev2_wd, "USER") == 0 &&
+                        pg_strcasecmp(prev_wd, "MAPPING") == 0)
+               COMPLETE_WITH_CONST("FOR");
+       else if (pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
+                        pg_strcasecmp(prev3_wd, "USER") == 0 &&
+                        pg_strcasecmp(prev2_wd, "MAPPING") == 0 &&
+                        pg_strcasecmp(prev_wd, "FOR") == 0)
+               COMPLETE_WITH_QUERY(Query_for_list_of_roles);
+       else if ((pg_strcasecmp(prev4_wd, "ALTER") == 0 ||
+                         pg_strcasecmp(prev4_wd, "DROP") == 0) &&
+                        pg_strcasecmp(prev3_wd, "USER") == 0 &&
+                        pg_strcasecmp(prev2_wd, "MAPPING") == 0 &&
+                        pg_strcasecmp(prev_wd, "FOR") == 0)
+               COMPLETE_WITH_QUERY(Query_for_list_of_user_mappings);
+       else if ((pg_strcasecmp(prev5_wd, "CREATE") == 0 ||
+                         pg_strcasecmp(prev5_wd, "ALTER") == 0 ||
+                         pg_strcasecmp(prev5_wd, "DROP") == 0) &&
+                        pg_strcasecmp(prev4_wd, "USER") == 0 &&
+                        pg_strcasecmp(prev3_wd, "MAPPING") == 0 &&
+                        pg_strcasecmp(prev2_wd, "FOR") == 0)
+               COMPLETE_WITH_CONST("SERVER");
+
 /*
  * VACUUM [ FULL | FREEZE ] [ VERBOSE ] [ table ]
  * VACUUM [ FULL | FREEZE ] [ VERBOSE ] ANALYZE [ table [ (column [, ...] ) ] ]
@@ -1695,7 +2190,11 @@ psql_completion(char *text, int start, int end)
                          pg_strcasecmp(prev2_wd, "ANALYZE") == 0))
                COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 
-/* ANALZYE */
+/* WITH [RECURSIVE] */
+       else if (pg_strcasecmp(prev_wd, "WITH") == 0)
+               COMPLETE_WITH_CONST("RECURSIVE");
+
+/* 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);
@@ -1703,7 +2202,7 @@ psql_completion(char *text, int start, int end)
 /* WHERE */
        /* Simple case of the word before the where being the table name */
        else if (pg_strcasecmp(prev_wd, "WHERE") == 0)
-               COMPLETE_WITH_ATTR(prev2_wd);
+               COMPLETE_WITH_ATTR(prev2_wd, "");
 
 /* ... FROM ... */
 /* TODO: also include SRF ? */
@@ -1717,62 +2216,86 @@ psql_completion(char *text, int start, int end)
 /* TODO:  \dc \dd \dl */
        else if (strcmp(prev_wd, "\\connect") == 0 || strcmp(prev_wd, "\\c") == 0)
                COMPLETE_WITH_QUERY(Query_for_list_of_databases);
-       else if (strcmp(prev_wd, "\\d") == 0 || strcmp(prev_wd, "\\d+") == 0)
-               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tisv, NULL);
-       else if (strcmp(prev_wd, "\\da") == 0)
+
+       else if (strncmp(prev_wd, "\\da", strlen("\\da")) == 0)
                COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_aggregates, NULL);
-       else if (strcmp(prev_wd, "\\db") == 0)
+       else if (strncmp(prev_wd, "\\db", strlen("\\db")) == 0)
                COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
-       else if (strcmp(prev_wd, "\\dD") == 0)
+       else if (strncmp(prev_wd, "\\dD", strlen("\\dD")) == 0)
                COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains, NULL);
-       else if (strcmp(prev_wd, "\\df") == 0 || strcmp(prev_wd, "\\df+") == 0)
+       else if (strncmp(prev_wd, "\\des", strlen("\\des")) == 0)
+               COMPLETE_WITH_QUERY(Query_for_list_of_servers);
+       else if (strncmp(prev_wd, "\\deu", strlen("\\deu")) == 0)
+               COMPLETE_WITH_QUERY(Query_for_list_of_user_mappings);
+       else if (strncmp(prev_wd, "\\dew", strlen("\\dew")) == 0)
+               COMPLETE_WITH_QUERY(Query_for_list_of_fdws);
+
+       else if (strncmp(prev_wd, "\\df", strlen("\\df")) == 0)
                COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
-       else if (strcmp(prev_wd, "\\di") == 0 || strcmp(prev_wd, "\\di+") == 0)
+       else if (strncmp(prev_wd, "\\dFd", strlen("\\dFd")) == 0)
+               COMPLETE_WITH_QUERY(Query_for_list_of_ts_dictionaries);
+       else if (strncmp(prev_wd, "\\dFp", strlen("\\dFp")) == 0)
+               COMPLETE_WITH_QUERY(Query_for_list_of_ts_parsers);
+       else if (strncmp(prev_wd, "\\dFt", strlen("\\dFt")) == 0)
+               COMPLETE_WITH_QUERY(Query_for_list_of_ts_templates);
+       /* must be at end of \dF */
+       else if (strncmp(prev_wd, "\\dF", strlen("\\dF")) == 0)
+               COMPLETE_WITH_QUERY(Query_for_list_of_ts_configurations);
+
+       else if (strncmp(prev_wd, "\\di", strlen("\\di")) == 0)
                COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
-       else if (strcmp(prev_wd, "\\dn") == 0)
+       else if (strncmp(prev_wd, "\\dn", strlen("\\dn")) == 0)
                COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
-       else if (strcmp(prev_wd, "\\dp") == 0 || strcmp(prev_wd, "\\z") == 0)
+       else if (strncmp(prev_wd, "\\dp", strlen("\\dp")) == 0)
                COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsv, NULL);
-       else if (strcmp(prev_wd, "\\ds") == 0 || strcmp(prev_wd, "\\ds+") == 0)
+       else if (strncmp(prev_wd, "\\ds", strlen("\\ds")) == 0)
                COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences, NULL);
-       else if (strcmp(prev_wd, "\\dS") == 0 || strcmp(prev_wd, "\\dS+") == 0)
-               COMPLETE_WITH_QUERY(Query_for_list_of_system_relations);
-       else if (strcmp(prev_wd, "\\dt") == 0 || strcmp(prev_wd, "\\dt+") == 0)
+       else if (strncmp(prev_wd, "\\dt", strlen("\\dt")) == 0)
                COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
-       else if (strcmp(prev_wd, "\\dT") == 0 || strcmp(prev_wd, "\\dT+") == 0)
+       else if (strncmp(prev_wd, "\\dT", strlen("\\dT")) == 0)
                COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL);
-       else if (strcmp(prev_wd, "\\du") == 0)
-               COMPLETE_WITH_QUERY(Query_for_list_of_users);
-       else if (strcmp(prev_wd, "\\dv") == 0 || strcmp(prev_wd, "\\dv+") == 0)
+       else if (strncmp(prev_wd, "\\du", strlen("\\du")) == 0)
+               COMPLETE_WITH_QUERY(Query_for_list_of_roles);
+       else if (strncmp(prev_wd, "\\dv", strlen("\\dv")) == 0)
                COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
+
+       /* must be at end of \d list */
+       else if (strncmp(prev_wd, "\\d", strlen("\\d")) == 0)
+               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tisv, NULL);
+
+       else if (strcmp(prev_wd, "\\ef") == 0)
+               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
+
        else if (strcmp(prev_wd, "\\encoding") == 0)
                COMPLETE_WITH_QUERY(Query_for_list_of_encodings);
        else if (strcmp(prev_wd, "\\h") == 0 || strcmp(prev_wd, "\\help") == 0)
                COMPLETE_WITH_LIST(sql_commands);
+       else if (strcmp(prev_wd, "\\password") == 0)
+               COMPLETE_WITH_QUERY(Query_for_list_of_roles);
        else if (strcmp(prev_wd, "\\pset") == 0)
        {
                static const char *const my_list[] =
                {"format", "border", "expanded",
-                       "null", "fieldsep", "tuples_only", "title", "tableattr", "pager",
-               "recordsep", NULL};
+                "null", "fieldsep", "tuples_only", "title", "tableattr",
+                "linestyle", "pager", "recordsep", NULL};
 
                COMPLETE_WITH_LIST(my_list);
        }
        else if (strcmp(prev_wd, "\\cd") == 0 ||
-                strcmp(prev_wd, "\\e") == 0 || strcmp(prev_wd, "\\edit") == 0 ||
+                        strcmp(prev_wd, "\\e") == 0 || strcmp(prev_wd, "\\edit") == 0 ||
                         strcmp(prev_wd, "\\g") == 0 ||
-                        strcmp(prev_wd, "\\i") == 0 || strcmp(prev_wd, "\\include") == 0 ||
-                 strcmp(prev_wd, "\\o") == 0 || strcmp(prev_wd, "\\out") == 0 ||
+                 strcmp(prev_wd, "\\i") == 0 || strcmp(prev_wd, "\\include") == 0 ||
+                        strcmp(prev_wd, "\\o") == 0 || strcmp(prev_wd, "\\out") == 0 ||
                         strcmp(prev_wd, "\\s") == 0 ||
-                  strcmp(prev_wd, "\\w") == 0 || strcmp(prev_wd, "\\write") == 0
+                        strcmp(prev_wd, "\\w") == 0 || strcmp(prev_wd, "\\write") == 0
                )
                matches = completion_matches(text, filename_completion_function);
 
 
        /*
-        * Finally, we look through the list of "things", such as TABLE, INDEX
-        * and check if that was the previous word. If so, execute the query
-        * to get a list of them.
+        * Finally, we look through the list of "things", such as TABLE, INDEX and
+        * check if that was the previous word. If so, execute the query to get a
+        * list of them.
         */
        else
        {
@@ -1793,10 +2316,9 @@ psql_completion(char *text, int start, int end)
        }
 
        /*
-        * If we still don't have anything to match we have to fabricate some
-        * sort of default list. If we were to just return NULL, readline
-        * automatically attempts filename completion, and that's usually no
-        * good.
+        * If we still don't have anything to match we have to fabricate some sort
+        * of default list. If we were to just return NULL, readline automatically
+        * attempts filename completion, and that's usually no good.
         */
        if (matches == NULL)
        {
@@ -1832,7 +2354,7 @@ psql_completion(char *text, int start, int end)
    something of that sort.
 */
 
-/* This one gives you one from a list of things you can put after CREATE or DROP
+/* This one gives you one from a list of things you can put after CREATE
    as defined above.
 */
 static char *
@@ -1851,13 +2373,59 @@ 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)
+       {
+               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;
 }
 
+/*
+ * This function gives you a list of things you can put after a DROP command.
+ * Very similar to create_command_generator, but has an additional entry for
+ * OWNED BY.  (We do it this way in order not to duplicate the
+ * words_after_create list.)
+ */
+static char *
+drop_command_generator(const char *text, int state)
+{
+       static int      list_index,
+                               string_length;
+       const char *name;
+
+       if (state == 0)
+       {
+               /* If this is the first time for this completion, init some values */
+               list_index = 0;
+               string_length = strlen(text);
+
+               /*
+                * DROP can be followed by "OWNED BY", which is not found in the list
+                * for CREATE matches, so make it the first state. (We do not make it
+                * the last state because it would be more difficult to detect when we
+                * have to return NULL instead.)
+                *
+                * Make sure we advance to the next state.
+                */
+               list_index++;
+               if (pg_strncasecmp("OWNED", text, string_length) == 0)
+                       return pg_strdup("OWNED");
+       }
+
+       /*
+        * In subsequent attempts, try to complete with the same items we use for
+        * CREATE
+        */
+       while ((name = words_after_create[list_index++ - 1].name))
+       {
+               if ((pg_strncasecmp(name, text, string_length) == 0) && (!words_after_create[list_index - 2].noshow))
+                       return pg_strdup(name);
+       }
+
+       /* if nothing matches, return NULL */
+       return NULL;
+}
 
 /* The following two functions are wrappers for _complete_from_query */
 
@@ -1879,8 +2447,9 @@ complete_from_schema_query(const char *text, int state)
    The query can be one of two kinds:
    - A simple query which must contain a %d and a %s, which will be replaced
    by the string length of the text and the text itself. The query may also
-   have another %s in it, which will be replaced by the value of
-   completion_info_charp.
+   have up to four more %s in it; the first two such will be replaced by the
+   value of completion_info_charp, the next two by the value of
+   completion_info_charp2.
         or:
    - A schema query used for completion of both schema and relation names;
    these are more complex and must contain in the following order:
@@ -1901,14 +2470,15 @@ _complete_from_query(int is_schema_query, const char *text, int state)
        static PGresult *result = NULL;
 
        /*
-        * If this is the first time for this completion, we fetch a list of
-        * our "things" from the backend.
+        * If this is the first time for this completion, we fetch a list of our
+        * "things" from the backend.
         */
        if (state == 0)
        {
                PQExpBufferData query_buffer;
                char       *e_text;
                char       *e_info_charp;
+               char       *e_info_charp2;
 
                list_index = 0;
                string_length = strlen(text);
@@ -1933,6 +2503,18 @@ _complete_from_query(int is_schema_query, const char *text, int state)
                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);
+               }
+               else
+                       e_info_charp2 = NULL;
+
                initPQExpBuffer(&query_buffer);
 
                if (is_schema_query)
@@ -1957,43 +2539,42 @@ _complete_from_query(int is_schema_query, const char *text, int state)
                                                          completion_squery->viscondition);
 
                        /*
-                        * When fetching relation names, suppress system catalogs
-                        * unless the input-so-far begins with "pg_".  This is a
-                        * compromise between not offering system catalogs for
-                        * completion at all, and having them swamp the result when
-                        * the input is just "p".
+                        * When fetching relation names, suppress system catalogs unless
+                        * the input-so-far begins with "pg_".  This is a compromise
+                        * between not offering system catalogs for completion at all, and
+                        * having them swamp the result when the input is just "p".
                         */
                        if (strcmp(completion_squery->catname,
                                           "pg_catalog.pg_class c") == 0 &&
                                strncmp(text, "pg_", 3) !=0)
                        {
                                appendPQExpBuffer(&query_buffer,
-                                                               " AND c.relnamespace <> (SELECT oid FROM"
-                               " pg_catalog.pg_namespace WHERE nspname = 'pg_catalog')");
+                                                                 " AND c.relnamespace <> (SELECT oid FROM"
+                                  " pg_catalog.pg_namespace WHERE nspname = 'pg_catalog')");
                        }
 
                        /*
-                        * Add in matching schema names, but only if there is more
-                        * than one potential match among schema names.
+                        * Add in matching schema names, but only if there is more than
+                        * one potential match among schema names.
                         */
                        appendPQExpBuffer(&query_buffer, "\nUNION\n"
-                                          "SELECT pg_catalog.quote_ident(n.nspname) || '.' "
+                                                  "SELECT pg_catalog.quote_ident(n.nspname) || '.' "
                                                          "FROM pg_catalog.pg_namespace n "
                                                          "WHERE substring(pg_catalog.quote_ident(n.nspname) || '.',1,%d)='%s'",
                                                          string_length, e_text);
                        appendPQExpBuffer(&query_buffer,
                                                          " AND (SELECT pg_catalog.count(*)"
                                                          " FROM pg_catalog.pg_namespace"
-                                                         " WHERE substring(pg_catalog.quote_ident(nspname) || '.',1,%d) ="
+                       " WHERE substring(pg_catalog.quote_ident(nspname) || '.',1,%d) ="
                                                          " substring('%s',1,pg_catalog.length(pg_catalog.quote_ident(nspname))+1)) > 1",
                                                          string_length, e_text);
 
                        /*
-                        * Add in matching qualified names, but only if there is
-                        * exactly one schema matching the input-so-far.
+                        * Add in matching qualified names, but only if there is exactly
+                        * one schema matching the input-so-far.
                         */
                        appendPQExpBuffer(&query_buffer, "\nUNION\n"
-                                "SELECT pg_catalog.quote_ident(n.nspname) || '.' || %s "
+                                        "SELECT pg_catalog.quote_ident(n.nspname) || '.' || %s "
                                                          "FROM %s, pg_catalog.pg_namespace n "
                                                          "WHERE %s = n.oid AND ",
                                                          qualresult,
@@ -2011,13 +2592,13 @@ _complete_from_query(int is_schema_query, const char *text, int state)
                         * speed up the query
                         */
                        appendPQExpBuffer(&query_buffer,
-                                                         " AND substring(pg_catalog.quote_ident(n.nspname) || '.',1,%d) ="
+                       " AND substring(pg_catalog.quote_ident(n.nspname) || '.',1,%d) ="
                                                          " substring('%s',1,pg_catalog.length(pg_catalog.quote_ident(n.nspname))+1)",
                                                          string_length, e_text);
                        appendPQExpBuffer(&query_buffer,
                                                          " AND (SELECT pg_catalog.count(*)"
                                                          " FROM pg_catalog.pg_namespace"
-                                                         " WHERE substring(pg_catalog.quote_ident(nspname) || '.',1,%d) ="
+                       " WHERE substring(pg_catalog.quote_ident(nspname) || '.',1,%d) ="
                                                          " substring('%s',1,pg_catalog.length(pg_catalog.quote_ident(nspname))+1)) = 1",
                                                          string_length, e_text);
 
@@ -2029,7 +2610,9 @@ _complete_from_query(int is_schema_query, const char *text, int state)
                {
                        /* completion_charp is an sprintf-style format string */
                        appendPQExpBuffer(&query_buffer, completion_charp,
-                                                         string_length, e_text, e_info_charp);
+                                                         string_length, e_text,
+                                                         e_info_charp, e_info_charp,
+                                                         e_info_charp2, e_info_charp2);
                }
 
                /* Limit the number of records in the result */
@@ -2042,6 +2625,8 @@ _complete_from_query(int is_schema_query, const char *text, int state)
                free(e_text);
                if (e_info_charp)
                        free(e_info_charp);
+               if (e_info_charp2)
+                       free(e_info_charp2);
        }
 
        /* Find something that matches */
@@ -2102,15 +2687,15 @@ complete_from_list(const char *text, int state)
        }
 
        /*
-        * No matches found. If we're not case insensitive already, lets
-        * switch to being case insensitive and try again
+        * No matches found. If we're not case insensitive already, lets switch to
+        * being case insensitive and try again
         */
        if (casesensitive && matches == 0)
        {
                casesensitive = false;
                list_index = 0;
                state++;
-               return (complete_from_list(text, state));
+               return complete_from_list(text, state);
        }
 
        /* If no more matches, return null. */
@@ -2156,11 +2741,11 @@ exec_query(const char *query)
 
        result = PQexec(pset.db, query);
 
-       if (result != NULL && PQresultStatus(result) != PGRES_TUPLES_OK)
+       if (PQresultStatus(result) != PGRES_TUPLES_OK)
        {
 #if 0
-               psql_error("tab completion: %s failed - %s\n",
-                                  query, PQresStatus(PQresultStatus(result)));
+               psql_error("tab completion query failed: %s\nQuery was:\n%s\n",
+                                  PQerrorMessage(pset.db), query);
 #endif
                PQclear(result);
                result = NULL;
@@ -2201,16 +2786,16 @@ previous_word(int point, int skip)
                        }
 
                /*
-                * If no end found we return null, because there is no word before
-                * the point
+                * If no end found we return null, because there is no word before the
+                * point
                 */
                if (end == -1)
                        return NULL;
 
                /*
-                * 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
+                * 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
                 */
                for (start = end; start > 0; start--)
                {
@@ -2225,15 +2810,37 @@ previous_word(int point, int skip)
 
        /* make a copy */
        s = pg_malloc(end - start + 2);
-
-       strncpy(s, &rl_line_buffer[start], end - start + 1);
-       s[end - start + 1] = '\0';
+       strlcpy(s, &rl_line_buffer[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
 
 /*
@@ -2272,8 +2879,7 @@ dequote_file_name(char *text, char quote_char)
 
        length = strlen(text);
        s = pg_malloc(length - 2 + 1);
-       strncpy(s, text +1, length - 2);
-       s[length] = '\0';
+       strlcpy(s, text +1, length - 2 + 1);
 
        return s;
 }