]> granicus.if.org Git - postgresql/blobdiff - src/bin/psql/tab-complete.c
Replace pg_asprintf() with psprintf().
[postgresql] / src / bin / psql / tab-complete.c
index 2b140c150dafcbb4f7e17f795e0472a85e9dda53..84d2eb4d4209a2cfa2c3c6e8bcc0be48e87ac008 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * psql - the PostgreSQL interactive terminal
  *
- * Copyright (c) 2000-2011, PostgreSQL Global Development Group
+ * Copyright (c) 2000-2013, PostgreSQL Global Development Group
  *
  * src/bin/psql/tab-complete.c
  */
@@ -132,6 +132,7 @@ 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 */
+static bool completion_case_sensitive; /* completion is case sensitive */
 
 /*
  * A few macros to ease typing. You can use these to complete the given
@@ -141,6 +142,7 @@ static const SchemaQuery *completion_squery;        /* to pass a SchemaQuery */
  * 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).
+ * 6/ The list of arguments to the given function (possibly schema-qualified).
  */
 #define COMPLETE_WITH_QUERY(query) \
 do { \
@@ -155,15 +157,24 @@ do { \
        matches = completion_matches(text, complete_from_schema_query); \
 } while (0)
 
+#define COMPLETE_WITH_LIST_CS(list) \
+do { \
+       completion_charpp = list; \
+       completion_case_sensitive = true; \
+       matches = completion_matches(text, complete_from_list); \
+} while (0)
+
 #define COMPLETE_WITH_LIST(list) \
 do { \
        completion_charpp = list; \
+       completion_case_sensitive = false; \
        matches = completion_matches(text, complete_from_list); \
 } while (0)
 
 #define COMPLETE_WITH_CONST(string) \
 do { \
        completion_charp = string; \
+       completion_case_sensitive = false; \
        matches = completion_matches(text, complete_from_const); \
 } while (0)
 
@@ -192,6 +203,31 @@ do { \
        matches = completion_matches(text, complete_from_query); \
 } while (0)
 
+#define COMPLETE_WITH_FUNCTION_ARG(function) \
+do { \
+       char   *_completion_schema; \
+       char   *_completion_function; \
+\
+       _completion_schema = strtokx(function, " \t\n\r", ".", "\"", 0, \
+                                                                false, false, pset.encoding); \
+       (void) strtokx(NULL, " \t\n\r", ".", "\"", 0, \
+                                  false, false, pset.encoding); \
+       _completion_function = strtokx(NULL, " \t\n\r", ".", "\"", 0, \
+                                                                  false, false, pset.encoding); \
+       if (_completion_function == NULL) \
+       { \
+               completion_charp = Query_for_list_of_arguments; \
+               completion_info_charp = function; \
+       } \
+       else \
+       { \
+               completion_charp = Query_for_list_of_arguments_with_schema; \
+               completion_info_charp = _completion_function; \
+               completion_info_charp2 = _completion_schema; \
+       } \
+       matches = completion_matches(text, complete_from_query); \
+} while (0)
+
 /*
  * Assembly instructions for schema queries
  */
@@ -288,6 +324,21 @@ static const SchemaQuery Query_for_list_of_sequences = {
        NULL
 };
 
+static const SchemaQuery Query_for_list_of_foreign_tables = {
+       /* catname */
+       "pg_catalog.pg_class c",
+       /* selcondition */
+       "c.relkind IN ('f')",
+       /* viscondition */
+       "pg_catalog.pg_table_is_visible(c.oid)",
+       /* namespace */
+       "c.relnamespace",
+       /* result */
+       "pg_catalog.quote_ident(c.relname)",
+       /* qualresult */
+       NULL
+};
+
 static const SchemaQuery Query_for_list_of_tables = {
        /* catname */
        "pg_catalog.pg_class c",
@@ -303,6 +354,21 @@ static const SchemaQuery Query_for_list_of_tables = {
        NULL
 };
 
+static const SchemaQuery Query_for_list_of_constraints_with_schema = {
+       /* catname */
+       "pg_catalog.pg_constraint c",
+       /* selcondition */
+       "c.conrelid <> 0",
+       /* viscondition */
+       "true",                                         /* there is no pg_constraint_is_visible */
+       /* namespace */
+       "c.connamespace",
+       /* result */
+       "pg_catalog.quote_ident(c.conname)",
+       /* qualresult */
+       NULL
+};
+
 /* The bit masks for the following three functions come from
  * src/include/catalog/pg_trigger.h.
  */
@@ -354,11 +420,41 @@ static const SchemaQuery Query_for_list_of_updatables = {
        NULL
 };
 
-static const SchemaQuery Query_for_list_of_tisv = {
+static const SchemaQuery Query_for_list_of_relations = {
+       /* catname */
+       "pg_catalog.pg_class c",
+       /* selcondition */
+       NULL,
+       /* viscondition */
+       "pg_catalog.pg_table_is_visible(c.oid)",
+       /* namespace */
+       "c.relnamespace",
+       /* result */
+       "pg_catalog.quote_ident(c.relname)",
+       /* qualresult */
+       NULL
+};
+
+static const SchemaQuery Query_for_list_of_tsvmf = {
+       /* catname */
+       "pg_catalog.pg_class c",
+       /* selcondition */
+       "c.relkind IN ('r', 'S', 'v', 'm', 'f')",
+       /* viscondition */
+       "pg_catalog.pg_table_is_visible(c.oid)",
+       /* namespace */
+       "c.relnamespace",
+       /* result */
+       "pg_catalog.quote_ident(c.relname)",
+       /* qualresult */
+       NULL
+};
+
+static const SchemaQuery Query_for_list_of_tmf = {
        /* catname */
        "pg_catalog.pg_class c",
        /* selcondition */
-       "c.relkind IN ('r', 'i', 'S', 'v')",
+       "c.relkind IN ('r', 'm', 'f')",
        /* viscondition */
        "pg_catalog.pg_table_is_visible(c.oid)",
        /* namespace */
@@ -369,11 +465,11 @@ static const SchemaQuery Query_for_list_of_tisv = {
        NULL
 };
 
-static const SchemaQuery Query_for_list_of_tsv = {
+static const SchemaQuery Query_for_list_of_tm = {
        /* catname */
        "pg_catalog.pg_class c",
        /* selcondition */
-       "c.relkind IN ('r', 'S', 'v')",
+       "c.relkind IN ('r', 'm')",
        /* viscondition */
        "pg_catalog.pg_table_is_visible(c.oid)",
        /* namespace */
@@ -399,6 +495,21 @@ static const SchemaQuery Query_for_list_of_views = {
        NULL
 };
 
+static const SchemaQuery Query_for_list_of_matviews = {
+       /* catname */
+       "pg_catalog.pg_class c",
+       /* selcondition */
+       "c.relkind IN ('m')",
+       /* viscondition */
+       "pg_catalog.pg_table_is_visible(c.oid)",
+       /* namespace */
+       "c.relnamespace",
+       /* result */
+       "pg_catalog.quote_ident(c.relname)",
+       /* qualresult */
+       NULL
+};
+
 
 /*
  * Queries to get lists of names of various kinds of things, possibly
@@ -513,6 +624,45 @@ static const SchemaQuery Query_for_list_of_views = {
 "       and pg_catalog.quote_ident(c1.relname)='%s'"\
 "       and pg_catalog.pg_table_is_visible(c2.oid)"
 
+/* the silly-looking length condition is just to eat up the current word */
+#define Query_for_constraint_of_table \
+"SELECT pg_catalog.quote_ident(conname) "\
+"  FROM pg_catalog.pg_class c1, pg_catalog.pg_constraint con "\
+" WHERE c1.oid=conrelid and (%d = pg_catalog.length('%s'))"\
+"       and pg_catalog.quote_ident(c1.relname)='%s'"\
+"       and pg_catalog.pg_table_is_visible(c1.oid)"
+
+#define Query_for_all_table_constraints \
+"SELECT pg_catalog.quote_ident(conname) "\
+"  FROM pg_catalog.pg_constraint c "\
+" WHERE c.conrelid <> 0 "
+
+/* the silly-looking length condition is just to eat up the current word */
+#define Query_for_constraint_of_type \
+"SELECT pg_catalog.quote_ident(conname) "\
+"  FROM pg_catalog.pg_type t, pg_catalog.pg_constraint con "\
+" WHERE t.oid=contypid and (%d = pg_catalog.length('%s'))"\
+"       and pg_catalog.quote_ident(t.typname)='%s'"\
+"       and pg_catalog.pg_type_is_visible(t.oid)"
+
+/* the silly-looking length condition is just to eat up the current word */
+#define Query_for_list_of_tables_for_constraint \
+"SELECT pg_catalog.quote_ident(relname) "\
+"  FROM pg_catalog.pg_class"\
+" WHERE (%d = pg_catalog.length('%s'))"\
+"   AND oid IN "\
+"       (SELECT conrelid FROM pg_catalog.pg_constraint "\
+"         WHERE pg_catalog.quote_ident(conname)='%s')"
+
+/* the silly-looking length condition is just to eat up the current word */
+#define Query_for_list_of_tables_for_rule \
+"SELECT pg_catalog.quote_ident(relname) "\
+"  FROM pg_catalog.pg_class"\
+" WHERE (%d = pg_catalog.length('%s'))"\
+"   AND oid IN "\
+"       (SELECT ev_class FROM pg_catalog.pg_rewrite "\
+"         WHERE pg_catalog.quote_ident(rulename)='%s')"
+
 /* the silly-looking length condition is just to eat up the current word */
 #define Query_for_list_of_tables_for_trigger \
 "SELECT pg_catalog.quote_ident(relname) "\
@@ -558,10 +708,40 @@ static const SchemaQuery Query_for_list_of_views = {
 "   FROM pg_catalog.pg_am "\
 "  WHERE substring(pg_catalog.quote_ident(amname),1,%d)='%s'"
 
+/* the silly-looking length condition is just to eat up the current word */
 #define Query_for_list_of_arguments \
-" SELECT pg_catalog.oidvectortypes(proargtypes)||')' "\
-"   FROM pg_catalog.pg_proc "\
-"  WHERE proname='%s'"
+"SELECT pg_catalog.oidvectortypes(proargtypes)||')' "\
+"  FROM pg_catalog.pg_proc "\
+" WHERE (%d = pg_catalog.length('%s'))"\
+"   AND (pg_catalog.quote_ident(proname)='%s'"\
+"        OR '\"' || proname || '\"'='%s') "\
+"   AND (pg_catalog.pg_function_is_visible(pg_proc.oid))"
+
+/* the silly-looking length condition is just to eat up the current word */
+#define Query_for_list_of_arguments_with_schema \
+"SELECT pg_catalog.oidvectortypes(proargtypes)||')' "\
+"  FROM pg_catalog.pg_proc p, pg_catalog.pg_namespace n "\
+" WHERE (%d = pg_catalog.length('%s'))"\
+"   AND n.oid = p.pronamespace "\
+"   AND (pg_catalog.quote_ident(proname)='%s' "\
+"        OR '\"' || proname || '\"' ='%s') "\
+"   AND (pg_catalog.quote_ident(nspname)='%s' "\
+"        OR '\"' || nspname || '\"' ='%s') "
+
+#define Query_for_list_of_extensions \
+" SELECT pg_catalog.quote_ident(extname) "\
+"   FROM pg_catalog.pg_extension "\
+"  WHERE substring(pg_catalog.quote_ident(extname),1,%d)='%s'"
+
+#define Query_for_list_of_available_extensions \
+" SELECT pg_catalog.quote_ident(name) "\
+"   FROM pg_catalog.pg_available_extensions "\
+"  WHERE substring(pg_catalog.quote_ident(name),1,%d)='%s' AND installed_version IS NULL"
+
+#define Query_for_list_of_prepared_statements \
+" SELECT pg_catalog.quote_ident(name) "\
+"   FROM pg_catalog.pg_prepared_statements "\
+"  WHERE substring(pg_catalog.quote_ident(name),1,%d)='%s'"
 
 /*
  * This is a list of all "things" in Pgsql, which can show up after CREATE or
@@ -573,32 +753,40 @@ 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 */
+       const bits32 flags;                     /* visibility flags, see below */
 } pgsql_thing_t;
 
+#define THING_NO_CREATE                (1 << 0)        /* should not show up after CREATE */
+#define THING_NO_DROP          (1 << 1)        /* should not show up after DROP */
+#define THING_NO_SHOW          (THING_NO_CREATE | THING_NO_DROP)
+
 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 */
+       {"COLLATION", "SELECT pg_catalog.quote_ident(collname) FROM pg_catalog.pg_collation WHERE collencoding IN (-1, pg_catalog.pg_char_to_encoding(pg_catalog.getdatabaseencoding())) AND substring(pg_catalog.quote_ident(collname),1,%d)='%s'"},
 
        /*
         * 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},
+       {"CONFIGURATION", Query_for_list_of_ts_configurations, NULL, THING_NO_SHOW},
        {"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},
+       {"DICTIONARY", Query_for_list_of_ts_dictionaries, NULL, THING_NO_SHOW},
        {"DOMAIN", NULL, &Query_for_list_of_domains},
+       {"EXTENSION", Query_for_list_of_extensions},
        {"FOREIGN DATA WRAPPER", NULL, NULL},
+       {"FOREIGN TABLE", NULL, NULL},
        {"FUNCTION", NULL, &Query_for_list_of_functions},
        {"GROUP", Query_for_list_of_roles},
        {"LANGUAGE", Query_for_list_of_languages},
        {"INDEX", NULL, &Query_for_list_of_indexes},
+       {"MATERIALIZED VIEW", NULL, NULL},
        {"OPERATOR", NULL, NULL},       /* Querying for this is probably not such a
                                                                 * good idea. */
-       {"PARSER", Query_for_list_of_ts_parsers, NULL, true},
+       {"OWNED", NULL, NULL, THING_NO_CREATE},         /* for DROP OWNED BY ... */
+       {"PARSER", Query_for_list_of_ts_parsers, NULL, THING_NO_SHOW},
        {"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},
@@ -606,16 +794,18 @@ static const pgsql_thing_t words_after_create[] = {
        {"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},
+       {"TEMP", NULL, NULL, THING_NO_DROP},            /* for CREATE TEMP TABLE ... */
+       {"TEMPLATE", Query_for_list_of_ts_templates, NULL, THING_NO_SHOW},
        {"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 ... */
+       {"UNIQUE", NULL, NULL, THING_NO_DROP},          /* for CREATE UNIQUE INDEX ... */
+       {"UNLOGGED", NULL, NULL, THING_NO_DROP},        /* for CREATE UNLOGGED TABLE
+                                                                                                * ... */
        {"USER", Query_for_list_of_roles},
        {"USER MAPPING FOR", NULL, NULL},
        {"VIEW", NULL, &Query_for_list_of_views},
-       {NULL, NULL, NULL, false}       /* end of list */
+       {NULL}                                          /* end of list */
 };
 
 
@@ -630,11 +820,13 @@ static char *_complete_from_query(int is_schema_query,
 static char *complete_from_list(const char *text, int state);
 static char *complete_from_const(const char *text, int state);
 static char **complete_from_variables(char *text,
-                                                                         const char *prefix, const char *suffix);
+                                               const char *prefix, const char *suffix);
+static char *complete_from_files(const char *text, int state);
 
+static char *pg_strdup_keyword_case(const char *s, const char *ref);
 static PGresult *exec_query(const char *query);
 
-static char *previous_word(int point, int skip);
+static void get_previous_words(int point, char **previous_words, int nwords);
 
 #ifdef NOT_USED
 static char *quote_file_name(char *text, int match_type, char *quote_pointer);
@@ -676,19 +868,23 @@ psql_completion(char *text, int start, int end)
        /* This is the variable we'll return. */
        char      **matches = NULL;
 
-       /* These are going to contain some scannage of the input line. */
-       char       *prev_wd,
-                          *prev2_wd,
-                          *prev3_wd,
-                          *prev4_wd,
-                          *prev5_wd;
+       /* This array will contain some scannage of the input line. */
+       char       *previous_words[6];
+
+       /* For compactness, we use these macros to reference previous_words[]. */
+#define prev_wd   (previous_words[0])
+#define prev2_wd  (previous_words[1])
+#define prev3_wd  (previous_words[2])
+#define prev4_wd  (previous_words[3])
+#define prev5_wd  (previous_words[4])
+#define prev6_wd  (previous_words[5])
 
        static const char *const sql_commands[] = {
                "ABORT", "ALTER", "ANALYZE", "BEGIN", "CHECKPOINT", "CLOSE", "CLUSTER",
                "COMMENT", "COMMIT", "COPY", "CREATE", "DEALLOCATE", "DECLARE",
                "DELETE FROM", "DISCARD", "DO", "DROP", "END", "EXECUTE", "EXPLAIN", "FETCH",
                "GRANT", "INSERT", "LISTEN", "LOAD", "LOCK", "MOVE", "NOTIFY", "PREPARE",
-               "REASSIGN", "REINDEX", "RELEASE", "RESET", "REVOKE", "ROLLBACK",
+               "REASSIGN", "REFRESH", "REINDEX", "RELEASE", "RESET", "REVOKE", "ROLLBACK",
                "SAVEPOINT", "SECURITY LABEL", "SELECT", "SET", "SHOW", "START",
                "TABLE", "TRUNCATE", "UNLISTEN", "UPDATE", "VACUUM", "VALUES", "WITH",
                NULL
@@ -696,15 +892,15 @@ psql_completion(char *text, int start, int end)
 
        static const char *const backslash_commands[] = {
                "\\a", "\\connect", "\\conninfo", "\\C", "\\cd", "\\copy", "\\copyright",
-               "\\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",
+               "\\d", "\\da", "\\db", "\\dc", "\\dC", "\\dd", "\\dD", "\\des", "\\det", "\\deu", "\\dew", "\\df",
+               "\\dF", "\\dFd", "\\dFp", "\\dFt", "\\dg", "\\di", "\\dl", "\\dL",
+               "\\dn", "\\do", "\\dp", "\\drds", "\\ds", "\\dS", "\\dt", "\\dT", "\\dv", "\\du", "\\dx",
                "\\e", "\\echo", "\\ef", "\\encoding",
-               "\\f", "\\g", "\\h", "\\help", "\\H", "\\i", "\\l",
+               "\\f", "\\g", "\\gset", "\\h", "\\help", "\\H", "\\i", "\\ir", "\\l",
                "\\lo_import", "\\lo_export", "\\lo_list", "\\lo_unlink",
                "\\o", "\\p", "\\password", "\\prompt", "\\pset", "\\q", "\\qecho", "\\r",
                "\\set", "\\sf", "\\t", "\\T",
-               "\\timing", "\\unset", "\\x", "\\w", "\\z", "\\!", NULL
+               "\\timing", "\\unset", "\\x", "\\w", "\\watch", "\\z", "\\!", NULL
        };
 
        (void) end;                                     /* not used */
@@ -720,22 +916,29 @@ psql_completion(char *text, int start, int end)
        completion_info_charp2 = NULL;
 
        /*
-        * 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.
+        * Scan the input line before our current position for the last few words.
+        * According to those we'll make some smart decisions on what the user is
+        * probably intending to type.
         */
-       prev_wd = previous_word(start, 0);
-       prev2_wd = previous_word(start, 1);
-       prev3_wd = previous_word(start, 2);
-       prev4_wd = previous_word(start, 3);
-       prev5_wd = previous_word(start, 4);
+       get_previous_words(start, previous_words, lengthof(previous_words));
 
        /* If a backslash command was started, continue */
        if (text[0] == '\\')
-               COMPLETE_WITH_LIST(backslash_commands);
+               COMPLETE_WITH_LIST_CS(backslash_commands);
+
+       /* Variable interpolation */
+       else if (text[0] == ':' && text[1] != ':')
+       {
+               if (text[1] == '\'')
+                       matches = complete_from_variables(text, ":'", "'");
+               else if (text[1] == '"')
+                       matches = complete_from_variables(text, ":\"", "\"");
+               else
+                       matches = complete_from_variables(text, ":", "");
+       }
 
        /* If no previous word, suggest one of the basic sql commands */
-       else if (!prev_wd)
+       else if (prev_wd[0] == '\0')
                COMPLETE_WITH_LIST(sql_commands);
 
 /* CREATE */
@@ -743,10 +946,10 @@ psql_completion(char *text, int start, int end)
        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 */
+/* DROP, but not 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)
+                        prev2_wd[0] == '\0')
                matches = completion_matches(text, drop_command_generator);
 
 /* ALTER */
@@ -759,9 +962,12 @@ psql_completion(char *text, int start, int end)
                         pg_strcasecmp(prev3_wd, "TABLE") != 0)
        {
                static const char *const list_ALTER[] =
-               {"AGGREGATE", "CONVERSION", "DATABASE", "DEFAULT PRIVILEGES", "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};
+               {"AGGREGATE", "COLLATION", "CONVERSION", "DATABASE", "DEFAULT PRIVILEGES", "DOMAIN",
+                       "EXTENSION", "FOREIGN DATA WRAPPER", "FOREIGN TABLE", "FUNCTION",
+                       "GROUP", "INDEX", "LANGUAGE", "LARGE OBJECT", "MATERIALIZED VIEW", "OPERATOR",
+                       "ROLE", "RULE", "SCHEMA", "SERVER", "SEQUENCE", "TABLE",
+                       "TABLESPACE", "TEXT SEARCH", "TRIGGER", "TYPE",
+               "USER", "USER MAPPING FOR", "VIEW", NULL};
 
                COMPLETE_WITH_LIST(list_ALTER);
        }
@@ -783,18 +989,12 @@ psql_completion(char *text, int start, int end)
                        COMPLETE_WITH_LIST(list_ALTERAGG);
                }
                else
-               {
-                       char       *tmp_buf = malloc(strlen(Query_for_list_of_arguments) + strlen(prev2_wd));
-
-                       sprintf(tmp_buf, Query_for_list_of_arguments, prev2_wd);
-                       COMPLETE_WITH_QUERY(tmp_buf);
-                       free(tmp_buf);
-               }
+                       COMPLETE_WITH_FUNCTION_ARG(prev2_wd);
        }
 
        /* ALTER SCHEMA <name> */
        else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-                         pg_strcasecmp(prev2_wd, "SCHEMA") == 0)
+                        pg_strcasecmp(prev2_wd, "SCHEMA") == 0)
        {
                static const char *const list_ALTERGEN[] =
                {"OWNER TO", "RENAME TO", NULL};
@@ -802,6 +1002,16 @@ psql_completion(char *text, int start, int end)
                COMPLETE_WITH_LIST(list_ALTERGEN);
        }
 
+       /* ALTER COLLATION <name> */
+       else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
+                        pg_strcasecmp(prev2_wd, "COLLATION") == 0)
+       {
+               static const char *const list_ALTERGEN[] =
+               {"OWNER TO", "RENAME TO", "SET SCHEMA", NULL};
+
+               COMPLETE_WITH_LIST(list_ALTERGEN);
+       }
+
        /* ALTER CONVERSION <name> */
        else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
                         pg_strcasecmp(prev2_wd, "CONVERSION") == 0)
@@ -822,6 +1032,26 @@ psql_completion(char *text, int start, int end)
                COMPLETE_WITH_LIST(list_ALTERDATABASE);
        }
 
+       /* ALTER EXTENSION <name> */
+       else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
+                        pg_strcasecmp(prev2_wd, "EXTENSION") == 0)
+       {
+               static const char *const list_ALTEREXTENSION[] =
+               {"ADD", "DROP", "UPDATE", "SET SCHEMA", NULL};
+
+               COMPLETE_WITH_LIST(list_ALTEREXTENSION);
+       }
+
+       /* ALTER FOREIGN */
+       else if (pg_strcasecmp(prev2_wd, "ALTER") == 0 &&
+                        pg_strcasecmp(prev_wd, "FOREIGN") == 0)
+       {
+               static const char *const list_ALTER_FOREIGN[] =
+               {"DATA WRAPPER", "TABLE", NULL};
+
+               COMPLETE_WITH_LIST(list_ALTER_FOREIGN);
+       }
+
        /* ALTER FOREIGN DATA WRAPPER <name> */
        else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
                         pg_strcasecmp(prev4_wd, "FOREIGN") == 0 &&
@@ -829,11 +1059,22 @@ psql_completion(char *text, int start, int end)
                         pg_strcasecmp(prev2_wd, "WRAPPER") == 0)
        {
                static const char *const list_ALTER_FDW[] =
-               {"VALIDATOR", "OPTIONS", "OWNER TO", NULL};
+               {"HANDLER", "VALIDATOR", "OPTIONS", "OWNER TO", NULL};
 
                COMPLETE_WITH_LIST(list_ALTER_FDW);
        }
 
+       /* ALTER FOREIGN TABLE <name> */
+       else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
+                        pg_strcasecmp(prev3_wd, "FOREIGN") == 0 &&
+                        pg_strcasecmp(prev2_wd, "TABLE") == 0)
+       {
+               static const char *const list_ALTER_FOREIGN_TABLE[] =
+               {"ALTER", "DROP", "RENAME", "OWNER TO", "SET SCHEMA", NULL};
+
+               COMPLETE_WITH_LIST(list_ALTER_FOREIGN_TABLE);
+       }
+
        /* ALTER INDEX <name> */
        else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
                         pg_strcasecmp(prev2_wd, "INDEX") == 0)
@@ -892,6 +1133,14 @@ psql_completion(char *text, int start, int end)
                COMPLETE_WITH_LIST(list_ALTERLARGEOBJECT);
        }
 
+       /* ALTER MATERIALIZED VIEW */
+       else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
+                        pg_strcasecmp(prev2_wd, "MATERIALIZED") == 0 &&
+                        pg_strcasecmp(prev_wd, "VIEW") == 0)
+       {
+               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
+       }
+
        /* ALTER USER,ROLE <name> */
        else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
                         !(pg_strcasecmp(prev2_wd, "USER") == 0 && pg_strcasecmp(prev_wd, "MAPPING") == 0) &&
@@ -899,14 +1148,32 @@ psql_completion(char *text, int start, int end)
                          pg_strcasecmp(prev2_wd, "ROLE") == 0))
        {
                static const char *const list_ALTERUSER[] =
-               {"ENCRYPTED", "UNENCRYPTED", "CREATEDB", "NOCREATEDB", "CREATEUSER",
-                       "NOCREATEUSER", "CREATEROLE", "NOCREATEROLE", "INHERIT", "NOINHERIT",
-                       "LOGIN", "NOLOGIN", "CONNECTION LIMIT", "VALID UNTIL", "RENAME TO",
-               "SUPERUSER", "NOSUPERUSER", "SET", "RESET", NULL};
+               {"CONNECTION LIMIT", "CREATEDB", "CREATEROLE", "CREATEUSER",
+                       "ENCRYPTED", "INHERIT", "LOGIN", "NOCREATEDB", "NOCREATEROLE",
+                       "NOCREATEUSER", "NOINHERIT", "NOLOGIN", "NOREPLICATION",
+                       "NOSUPERUSER", "RENAME TO", "REPLICATION", "RESET", "SET",
+               "SUPERUSER", "UNENCRYPTED", "VALID UNTIL", "WITH", NULL};
 
                COMPLETE_WITH_LIST(list_ALTERUSER);
        }
 
+       /* ALTER USER,ROLE <name> WITH */
+       else if ((pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
+                         (pg_strcasecmp(prev3_wd, "USER") == 0 ||
+                          pg_strcasecmp(prev3_wd, "ROLE") == 0) &&
+                         pg_strcasecmp(prev_wd, "WITH") == 0))
+       {
+               /* Similar to the above, but don't complete "WITH" again. */
+               static const char *const list_ALTERUSER_WITH[] =
+               {"CONNECTION LIMIT", "CREATEDB", "CREATEROLE", "CREATEUSER",
+                       "ENCRYPTED", "INHERIT", "LOGIN", "NOCREATEDB", "NOCREATEROLE",
+                       "NOCREATEUSER", "NOINHERIT", "NOLOGIN", "NOREPLICATION",
+                       "NOSUPERUSER", "RENAME TO", "REPLICATION", "RESET", "SET",
+               "SUPERUSER", "UNENCRYPTED", "VALID UNTIL", NULL};
+
+               COMPLETE_WITH_LIST(list_ALTERUSER_WITH);
+       }
+
        /* 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) &&
@@ -951,7 +1218,7 @@ psql_completion(char *text, int start, int end)
                         pg_strcasecmp(prev2_wd, "DOMAIN") == 0)
        {
                static const char *const list_ALTERDOMAIN[] =
-               {"ADD", "DROP", "OWNER TO", "SET", NULL};
+               {"ADD", "DROP", "OWNER TO", "RENAME", "SET", "VALIDATE CONSTRAINT", NULL};
 
                COMPLETE_WITH_LIST(list_ALTERDOMAIN);
        }
@@ -965,6 +1232,33 @@ psql_completion(char *text, int start, int end)
 
                COMPLETE_WITH_LIST(list_ALTERDOMAIN2);
        }
+       /* ALTER DOMAIN <sth> DROP|RENAME|VALIDATE CONSTRAINT */
+       else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
+                        pg_strcasecmp(prev4_wd, "DOMAIN") == 0 &&
+                        (pg_strcasecmp(prev2_wd, "DROP") == 0 ||
+                         pg_strcasecmp(prev2_wd, "RENAME") == 0 ||
+                         pg_strcasecmp(prev2_wd, "VALIDATE") == 0) &&
+                        pg_strcasecmp(prev_wd, "CONSTRAINT") == 0)
+       {
+               completion_info_charp = prev3_wd;
+               COMPLETE_WITH_QUERY(Query_for_constraint_of_type);
+       }
+       /* ALTER DOMAIN <sth> RENAME */
+       else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
+                        pg_strcasecmp(prev3_wd, "DOMAIN") == 0 &&
+                        pg_strcasecmp(prev_wd, "RENAME") == 0)
+       {
+               static const char *const list_ALTERDOMAIN[] =
+               {"CONSTRAINT", "TO", NULL};
+
+               COMPLETE_WITH_LIST(list_ALTERDOMAIN);
+       }
+       /* ALTER DOMAIN <sth> RENAME CONSTRAINT <sth> */
+       else if (pg_strcasecmp(prev5_wd, "DOMAIN") == 0 &&
+                        pg_strcasecmp(prev3_wd, "RENAME") == 0 &&
+                        pg_strcasecmp(prev2_wd, "CONSTRAINT") == 0)
+               COMPLETE_WITH_CONST("TO");
+
        /* ALTER DOMAIN <sth> SET */
        else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
                         pg_strcasecmp(prev3_wd, "DOMAIN") == 0 &&
@@ -1013,6 +1307,36 @@ psql_completion(char *text, int start, int end)
 
                COMPLETE_WITH_LIST(list_ALTERVIEW);
        }
+       /* ALTER MATERIALIZED VIEW <name> */
+       else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
+                        pg_strcasecmp(prev3_wd, "MATERIALIZED") == 0 &&
+                        pg_strcasecmp(prev2_wd, "VIEW") == 0)
+       {
+               static const char *const list_ALTERMATVIEW[] =
+               {"ALTER COLUMN", "OWNER TO", "RENAME TO", "SET SCHEMA", NULL};
+
+               COMPLETE_WITH_LIST(list_ALTERMATVIEW);
+       }
+
+       /* ALTER RULE <name>, add ON */
+       else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
+                        pg_strcasecmp(prev2_wd, "RULE") == 0)
+               COMPLETE_WITH_CONST("ON");
+
+       /* If we have ALTER RULE <name> ON, then add the correct tablename */
+       else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
+                        pg_strcasecmp(prev3_wd, "RULE") == 0 &&
+                        pg_strcasecmp(prev_wd, "ON") == 0)
+       {
+               completion_info_charp = prev2_wd;
+               COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_rule);
+       }
+
+       /* ALTER RULE <name> ON <name> */
+       else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
+                        pg_strcasecmp(prev4_wd, "RULE") == 0)
+               COMPLETE_WITH_CONST("RENAME TO");
+
        /* ALTER TRIGGER <name>, add ON */
        else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
                         pg_strcasecmp(prev2_wd, "TRIGGER") == 0)
@@ -1039,15 +1363,15 @@ psql_completion(char *text, int start, int end)
                COMPLETE_WITH_CONST("RENAME TO");
 
        /*
-        * If we detect ALTER TABLE <name>, suggest either ADD, DROP, ALTER,
-        * RENAME, CLUSTER ON or OWNER
+        * If we detect ALTER TABLE <name>, suggest sub commands
         */
        else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
                         pg_strcasecmp(prev2_wd, "TABLE") == 0)
        {
                static const char *const list_ALTER2[] =
                {"ADD", "ALTER", "CLUSTER ON", "DISABLE", "DROP", "ENABLE", "INHERIT",
-               "NO INHERIT", "RENAME", "RESET", "OWNER TO", "SET", NULL};
+                       "NO INHERIT", "RENAME", "RESET", "OWNER TO", "SET",
+               "VALIDATE CONSTRAINT", NULL};
 
                COMPLETE_WITH_LIST(list_ALTER2);
        }
@@ -1071,6 +1395,21 @@ psql_completion(char *text, int start, int end)
 
                COMPLETE_WITH_LIST(list_ALTERENABLE2);
        }
+       /* ALTER TABLE xxx INHERIT */
+       else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
+                        pg_strcasecmp(prev3_wd, "TABLE") == 0 &&
+                        pg_strcasecmp(prev_wd, "INHERIT") == 0)
+       {
+               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
+       }
+       /* ALTER TABLE xxx NO INHERIT */
+       else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
+                        pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
+                        pg_strcasecmp(prev2_wd, "NO") == 0 &&
+                        pg_strcasecmp(prev_wd, "INHERIT") == 0)
+       {
+               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, "");
+       }
        else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
                         pg_strcasecmp(prev3_wd, "TABLE") == 0 &&
                         pg_strcasecmp(prev_wd, "DISABLE") == 0)
@@ -1081,12 +1420,18 @@ psql_completion(char *text, int start, int end)
                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))
+       /* ALTER TABLE xxx ALTER */
+       else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
+                        pg_strcasecmp(prev3_wd, "TABLE") == 0 &&
+                        pg_strcasecmp(prev_wd, "ALTER") == 0)
                COMPLETE_WITH_ATTR(prev2_wd, " UNION SELECT 'COLUMN'");
 
+       /* ALTER TABLE xxx RENAME */
+       else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
+                        pg_strcasecmp(prev3_wd, "TABLE") == 0 &&
+                        pg_strcasecmp(prev_wd, "RENAME") == 0)
+               COMPLETE_WITH_ATTR(prev2_wd, " UNION SELECT 'COLUMN' UNION SELECT 'CONSTRAINT' UNION SELECT 'TO'");
+
        /*
         * If we have TABLE <sth> ALTER COLUMN|RENAME COLUMN, provide list of
         * columns
@@ -1100,13 +1445,15 @@ psql_completion(char *text, int start, int end)
        /* ALTER TABLE xxx RENAME yyy */
        else if (pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
                         pg_strcasecmp(prev2_wd, "RENAME") == 0 &&
+                        pg_strcasecmp(prev_wd, "CONSTRAINT") != 0 &&
                         pg_strcasecmp(prev_wd, "TO") != 0)
                COMPLETE_WITH_CONST("TO");
 
-       /* ALTER TABLE xxx RENAME COLUMN yyy */
+       /* ALTER TABLE xxx RENAME COLUMN/CONSTRAINT yyy */
        else if (pg_strcasecmp(prev5_wd, "TABLE") == 0 &&
                         pg_strcasecmp(prev3_wd, "RENAME") == 0 &&
-                        pg_strcasecmp(prev2_wd, "COLUMN") == 0 &&
+                        (pg_strcasecmp(prev2_wd, "COLUMN") == 0 ||
+                         pg_strcasecmp(prev2_wd, "CONSTRAINT") == 0) &&
                         pg_strcasecmp(prev_wd, "TO") != 0)
                COMPLETE_WITH_CONST("TO");
 
@@ -1119,11 +1466,27 @@ psql_completion(char *text, int start, int end)
 
                COMPLETE_WITH_LIST(list_TABLEDROP);
        }
-       /* If we have TABLE <sth> DROP COLUMN, provide list of columns */
-       else if (pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
+       /* If we have ALTER TABLE <sth> DROP COLUMN, provide list of columns */
+       else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
+                        pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
                         pg_strcasecmp(prev2_wd, "DROP") == 0 &&
                         pg_strcasecmp(prev_wd, "COLUMN") == 0)
                COMPLETE_WITH_ATTR(prev3_wd, "");
+
+       /*
+        * If we have ALTER TABLE <sth> DROP|RENAME|VALIDATE CONSTRAINT, provide
+        * list of constraints
+        */
+       else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
+                        pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
+                        (pg_strcasecmp(prev2_wd, "DROP") == 0 ||
+                         pg_strcasecmp(prev2_wd, "RENAME") == 0 ||
+                         pg_strcasecmp(prev2_wd, "VALIDATE") == 0) &&
+                        pg_strcasecmp(prev_wd, "CONSTRAINT") == 0)
+       {
+               completion_info_charp = prev3_wd;
+               COMPLETE_WITH_QUERY(Query_for_constraint_of_table);
+       }
        /* ALTER TABLE ALTER [COLUMN] <foo> */
        else if ((pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
                          pg_strcasecmp(prev2_wd, "COLUMN") == 0) ||
@@ -1331,12 +1694,22 @@ psql_completion(char *text, int start, int end)
                         pg_strcasecmp(prev2_wd, "TYPE") == 0)
        {
                static const char *const list_ALTERTYPE[] =
-               {"ADD ATTRIBUTE", "ALTER ATTRIBUTE", "DROP ATTRIBUTE",
-                "OWNER TO", "RENAME", "SET SCHEMA", NULL};
+               {"ADD ATTRIBUTE", "ADD VALUE", "ALTER ATTRIBUTE", "DROP ATTRIBUTE",
+               "OWNER TO", "RENAME", "SET SCHEMA", NULL};
+
+               COMPLETE_WITH_LIST(list_ALTERTYPE);
+       }
+       /* complete ALTER TYPE <foo> ADD with actions */
+       else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
+                        pg_strcasecmp(prev3_wd, "TYPE") == 0 &&
+                        pg_strcasecmp(prev_wd, "ADD") == 0)
+       {
+               static const char *const list_ALTERTYPE[] =
+               {"ATTRIBUTE", "VALUE", NULL};
 
                COMPLETE_WITH_LIST(list_ALTERTYPE);
        }
-       /* ALTER TYPE <foo> RENAME  */
+       /* ALTER TYPE <foo> RENAME      */
        else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
                         pg_strcasecmp(prev3_wd, "TYPE") == 0 &&
                         pg_strcasecmp(prev_wd, "RENAME") == 0)
@@ -1352,7 +1725,10 @@ psql_completion(char *text, int start, int end)
                         pg_strcasecmp(prev2_wd, "ATTRIBUTE") == 0)
                COMPLETE_WITH_CONST("TO");
 
-       /* If we have TYPE <sth> ALTER/DROP/RENAME ATTRIBUTE, provide list of attributes */
+       /*
+        * If we have TYPE <sth> ALTER/DROP/RENAME ATTRIBUTE, provide list of
+        * attributes
+        */
        else if (pg_strcasecmp(prev4_wd, "TYPE") == 0 &&
                         (pg_strcasecmp(prev2_wd, "ALTER") == 0 ||
                          pg_strcasecmp(prev2_wd, "DROP") == 0 ||
@@ -1419,14 +1795,29 @@ psql_completion(char *text, int start, int end)
 /* CLUSTER */
 
        /*
-        * If the previous word is CLUSTER and not without produce list of tables
+        * 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_tables, NULL);
+               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, "UNION SELECT 'VERBOSE'");
+
+       /*
+        * If the previous words are CLUSTER VERBOSE produce list of tables
+        */
+       else if (pg_strcasecmp(prev_wd, "VERBOSE") == 0 &&
+                        pg_strcasecmp(prev2_wd, "CLUSTER") == 0)
+               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
+
        /* If we have CLUSTER <sth>, then add "USING" */
        else if (pg_strcasecmp(prev2_wd, "CLUSTER") == 0 &&
-                        pg_strcasecmp(prev_wd, "ON") != 0)
+                        pg_strcasecmp(prev_wd, "ON") != 0 &&
+                        pg_strcasecmp(prev_wd, "VERBOSE") != 0)
+       {
+               COMPLETE_WITH_CONST("USING");
+       }
+       /* If we have CLUSTER VERBOSE <sth>, then add "USING" */
+       else if (pg_strcasecmp(prev3_wd, "CLUSTER") == 0 &&
+                        pg_strcasecmp(prev2_wd, "VERBOSE") == 0)
        {
                COMPLETE_WITH_CONST("USING");
        }
@@ -1441,6 +1832,17 @@ psql_completion(char *text, int start, int end)
                COMPLETE_WITH_QUERY(Query_for_index_of_table);
        }
 
+       /*
+        * If we have CLUSTER VERBOSE <sth> USING, then add the index as well.
+        */
+       else if (pg_strcasecmp(prev4_wd, "CLUSTER") == 0 &&
+                        pg_strcasecmp(prev3_wd, "VERBOSE") == 0 &&
+                        pg_strcasecmp(prev_wd, "USING") == 0)
+       {
+               completion_info_charp = prev2_wd;
+               COMPLETE_WITH_QUERY(Query_for_index_of_table);
+       }
+
 /* COMMENT */
        else if (pg_strcasecmp(prev_wd, "COMMENT") == 0)
                COMPLETE_WITH_CONST("ON");
@@ -1448,13 +1850,24 @@ psql_completion(char *text, int start, int end)
                         pg_strcasecmp(prev_wd, "ON") == 0)
        {
                static const char *const list_COMMENT[] =
-               {"CAST", "CONVERSION", "DATABASE", "INDEX", "LANGUAGE", "RULE", "SCHEMA",
-                       "SEQUENCE", "TABLE", "TYPE", "VIEW", "COLUMN", "AGGREGATE", "FUNCTION",
+               {"CAST", "COLLATION", "CONVERSION", "DATABASE", "EXTENSION",
+                       "FOREIGN DATA WRAPPER", "FOREIGN TABLE",
+                       "SERVER", "INDEX", "LANGUAGE", "RULE", "SCHEMA", "SEQUENCE",
+                       "TABLE", "TYPE", "VIEW", "MATERIALIZED VIEW", "COLUMN", "AGGREGATE", "FUNCTION",
                        "OPERATOR", "TRIGGER", "CONSTRAINT", "DOMAIN", "LARGE OBJECT",
                "TABLESPACE", "TEXT SEARCH", "ROLE", NULL};
 
                COMPLETE_WITH_LIST(list_COMMENT);
        }
+       else if (pg_strcasecmp(prev3_wd, "COMMENT") == 0 &&
+                        pg_strcasecmp(prev2_wd, "ON") == 0 &&
+                        pg_strcasecmp(prev_wd, "FOREIGN") == 0)
+       {
+               static const char *const list_TRANS2[] =
+               {"DATA WRAPPER", "TABLE", NULL};
+
+               COMPLETE_WITH_LIST(list_TRANS2);
+       }
        else if (pg_strcasecmp(prev4_wd, "COMMENT") == 0 &&
                         pg_strcasecmp(prev3_wd, "ON") == 0 &&
                         pg_strcasecmp(prev2_wd, "TEXT") == 0 &&
@@ -1465,11 +1878,39 @@ psql_completion(char *text, int start, int end)
 
                COMPLETE_WITH_LIST(list_TRANS2);
        }
+       else if (pg_strcasecmp(prev3_wd, "COMMENT") == 0 &&
+                        pg_strcasecmp(prev2_wd, "ON") == 0 &&
+                        pg_strcasecmp(prev_wd, "CONSTRAINT") == 0)
+       {
+               COMPLETE_WITH_QUERY(Query_for_all_table_constraints);
+       }
+       else if (pg_strcasecmp(prev4_wd, "COMMENT") == 0 &&
+                        pg_strcasecmp(prev3_wd, "ON") == 0 &&
+                        pg_strcasecmp(prev2_wd, "CONSTRAINT") == 0)
+       {
+               COMPLETE_WITH_CONST("ON");
+       }
+       else if (pg_strcasecmp(prev5_wd, "COMMENT") == 0 &&
+                        pg_strcasecmp(prev4_wd, "ON") == 0 &&
+                        pg_strcasecmp(prev3_wd, "CONSTRAINT") == 0 &&
+                        pg_strcasecmp(prev_wd, "ON") == 0)
+       {
+               completion_info_charp = prev2_wd;
+               COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_constraint);
+       }
+       else if (pg_strcasecmp(prev4_wd, "COMMENT") == 0 &&
+                        pg_strcasecmp(prev3_wd, "ON") == 0 &&
+                        pg_strcasecmp(prev2_wd, "MATERIALIZED") == 0 &&
+                        pg_strcasecmp(prev_wd, "VIEW") == 0)
+       {
+               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
+       }
        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))
+                        (pg_strcasecmp(prev5_wd, "COMMENT") == 0 &&
+                         pg_strcasecmp(prev4_wd, "ON") == 0) ||
+                        (pg_strcasecmp(prev6_wd, "COMMENT") == 0 &&
+                         pg_strcasecmp(prev5_wd, "ON") == 0))
                COMPLETE_WITH_CONST("IS");
 
 /* COPY */
@@ -1499,7 +1940,10 @@ psql_completion(char *text, int start, int end)
                          pg_strcasecmp(prev3_wd, "BINARY") == 0) &&
                         (pg_strcasecmp(prev_wd, "FROM") == 0 ||
                          pg_strcasecmp(prev_wd, "TO") == 0))
-               matches = completion_matches(text, filename_completion_function);
+       {
+               completion_charp = "";
+               matches = completion_matches(text, complete_from_files);
+       }
 
        /* Handle COPY|BINARY <sth> FROM|TO filename */
        else if ((pg_strcasecmp(prev4_wd, "COPY") == 0 ||
@@ -1509,7 +1953,7 @@ psql_completion(char *text, int start, int end)
                          pg_strcasecmp(prev2_wd, "TO") == 0))
        {
                static const char *const list_COPY[] =
-               {"BINARY", "OIDS", "DELIMITER", "NULL", "CSV", NULL};
+               {"BINARY", "OIDS", "DELIMITER", "NULL", "CSV", "ENCODING", NULL};
 
                COMPLETE_WITH_LIST(list_COPY);
        }
@@ -1520,7 +1964,7 @@ psql_completion(char *text, int start, int end)
                          pg_strcasecmp(prev3_wd, "TO") == 0))
        {
                static const char *const list_CSV[] =
-               {"HEADER", "QUOTE", "ESCAPE", "FORCE QUOTE", NULL};
+               {"HEADER", "QUOTE", "ESCAPE", "FORCE QUOTE", "FORCE NOT NULL", NULL};
 
                COMPLETE_WITH_LIST(list_CSV);
        }
@@ -1541,12 +1985,37 @@ psql_completion(char *text, int start, int end)
                         pg_strcasecmp(prev_wd, "TEMPLATE") == 0)
                COMPLETE_WITH_QUERY(Query_for_list_of_template_databases);
 
+       /* CREATE EXTENSION */
+       /* Complete with available extensions rather than installed ones. */
+       else if (pg_strcasecmp(prev2_wd, "CREATE") == 0 &&
+                        pg_strcasecmp(prev_wd, "EXTENSION") == 0)
+               COMPLETE_WITH_QUERY(Query_for_list_of_available_extensions);
+       /* CREATE EXTENSION <name> */
+       else if (pg_strcasecmp(prev3_wd, "CREATE") == 0 &&
+                        pg_strcasecmp(prev2_wd, "EXTENSION") == 0)
+               COMPLETE_WITH_CONST("WITH SCHEMA");
+
+       /* CREATE FOREIGN */
+       else if (pg_strcasecmp(prev2_wd, "CREATE") == 0 &&
+                        pg_strcasecmp(prev_wd, "FOREIGN") == 0)
+       {
+               static const char *const list_CREATE_FOREIGN[] =
+               {"DATA WRAPPER", "TABLE", NULL};
+
+               COMPLETE_WITH_LIST(list_CREATE_FOREIGN);
+       }
+
        /* 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");
+       {
+               static const char *const list_CREATE_FOREIGN_DATA_WRAPPER[] =
+               {"HANDLER", "VALIDATOR", NULL};
+
+               COMPLETE_WITH_LIST(list_CREATE_FOREIGN_DATA_WRAPPER);
+       }
 
        /* CREATE INDEX */
        /* First off we complete CREATE UNIQUE with "INDEX" */
@@ -1565,7 +2034,7 @@ psql_completion(char *text, int start, int end)
                          pg_strcasecmp(prev2_wd, "INDEX") == 0 ||
                          pg_strcasecmp(prev2_wd, "CONCURRENTLY") == 0) &&
                         pg_strcasecmp(prev_wd, "ON") == 0)
-               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
        /* If we have CREATE|UNIQUE INDEX <sth> CONCURRENTLY, then add "ON" */
        else if ((pg_strcasecmp(prev3_wd, "INDEX") == 0 ||
                          pg_strcasecmp(prev2_wd, "INDEX") == 0) &&
@@ -1667,6 +2136,15 @@ psql_completion(char *text, int start, int end)
 
                COMPLETE_WITH_LIST(list_TEMP);
        }
+       /* Complete "CREATE UNLOGGED" with TABLE */
+       else if (pg_strcasecmp(prev2_wd, "CREATE") == 0 &&
+                        pg_strcasecmp(prev_wd, "UNLOGGED") == 0)
+       {
+               static const char *const list_UNLOGGED[] =
+               {"TABLE", "MATERIALIZED VIEW", NULL};
+
+               COMPLETE_WITH_LIST(list_UNLOGGED);
+       }
 
 /* CREATE TABLESPACE */
        else if (pg_strcasecmp(prev3_wd, "CREATE") == 0 &&
@@ -1721,11 +2199,25 @@ psql_completion(char *text, int start, int end)
 
                COMPLETE_WITH_LIST(list_CREATETRIGGER_EVENTS);
        }
-       /* complete CREATE TRIGGER <name> BEFORE,AFTER sth with OR,ON */
+       /* complete CREATE TRIGGER <name> INSTEAD OF with an event */
        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))
+                        pg_strcasecmp(prev2_wd, "INSTEAD") == 0 &&
+                        pg_strcasecmp(prev_wd, "OF") == 0)
+       {
+               static const char *const list_CREATETRIGGER_EVENTS[] =
+               {"INSERT", "DELETE", "UPDATE", NULL};
+
+               COMPLETE_WITH_LIST(list_CREATETRIGGER_EVENTS);
+       }
+       /* 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)) ||
+                        (pg_strcasecmp(prev5_wd, "TRIGGER") == 0 &&
+                         pg_strcasecmp(prev3_wd, "INSTEAD") == 0 &&
+                         pg_strcasecmp(prev2_wd, "OF") == 0))
        {
                static const char *const list_CREATETRIGGER2[] =
                {"ON", "OR", NULL};
@@ -1742,11 +2234,17 @@ psql_completion(char *text, int start, int end)
                          pg_strcasecmp(prev3_wd, "AFTER") == 0) &&
                         pg_strcasecmp(prev_wd, "ON") == 0)
                COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+       /* complete CREATE TRIGGER ... INSTEAD OF event ON with a list of views */
+       else if (pg_strcasecmp(prev4_wd, "INSTEAD") == 0 &&
+                        pg_strcasecmp(prev3_wd, "OF") == 0 &&
+                        pg_strcasecmp(prev_wd, "ON") == 0)
+               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
        /* complete CREATE TRIGGER ... EXECUTE with PROCEDURE */
-       else if (pg_strcasecmp(prev_wd, "EXECUTE") == 0)
+       else if (pg_strcasecmp(prev_wd, "EXECUTE") == 0 &&
+                        prev2_wd[0] != '\0')
                COMPLETE_WITH_CONST("PROCEDURE");
 
-/* CREATE ROLE,USER,GROUP */
+/* CREATE ROLE,USER,GROUP <name> */
        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 ||
@@ -1754,13 +2252,32 @@ psql_completion(char *text, int start, int end)
        {
                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};
+                       "ENCRYPTED", "IN", "INHERIT", "LOGIN", "NOCREATEDB",
+                       "NOCREATEROLE", "NOCREATEUSER", "NOINHERIT", "NOLOGIN",
+                       "NOREPLICATION", "NOSUPERUSER", "REPLICATION", "ROLE",
+               "SUPERUSER", "SYSID", "UNENCRYPTED", "VALID UNTIL", "WITH", NULL};
 
                COMPLETE_WITH_LIST(list_CREATEROLE);
        }
 
+/* CREATE ROLE,USER,GROUP <name> WITH */
+       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, "WITH") == 0))
+       {
+               /* Similar to the above, but don't complete "WITH" again. */
+               static const char *const list_CREATEROLE_WITH[] =
+               {"ADMIN", "CONNECTION LIMIT", "CREATEDB", "CREATEROLE", "CREATEUSER",
+                       "ENCRYPTED", "IN", "INHERIT", "LOGIN", "NOCREATEDB",
+                       "NOCREATEROLE", "NOCREATEUSER", "NOINHERIT", "NOLOGIN",
+                       "NOREPLICATION", "NOSUPERUSER", "REPLICATION", "ROLE",
+               "SUPERUSER", "SYSID", "UNENCRYPTED", "VALID UNTIL", NULL};
+
+               COMPLETE_WITH_LIST(list_CREATEROLE_WITH);
+       }
+
        /*
         * complete CREATE ROLE,USER,GROUP <name> ENCRYPTED,UNENCRYPTED with
         * PASSWORD
@@ -1795,6 +2312,22 @@ psql_completion(char *text, int start, int end)
                         pg_strcasecmp(prev_wd, "AS") == 0)
                COMPLETE_WITH_CONST("SELECT");
 
+/* CREATE MATERIALIZED VIEW */
+       else if (pg_strcasecmp(prev2_wd, "CREATE") == 0 &&
+                        pg_strcasecmp(prev_wd, "MATERIALIZED") == 0)
+               COMPLETE_WITH_CONST("VIEW");
+       /* Complete CREATE MATERIALIZED VIEW <name> with AS */
+       else if (pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
+                        pg_strcasecmp(prev3_wd, "MATERIALIZED") == 0 &&
+                        pg_strcasecmp(prev2_wd, "VIEW") == 0)
+               COMPLETE_WITH_CONST("AS");
+       /* Complete "CREATE MATERIALIZED VIEW <sth> AS with "SELECT" */
+       else if (pg_strcasecmp(prev5_wd, "CREATE") == 0 &&
+                        pg_strcasecmp(prev4_wd, "MATERIALIZED") == 0 &&
+                        pg_strcasecmp(prev3_wd, "VIEW") == 0 &&
+                        pg_strcasecmp(prev_wd, "AS") == 0)
+               COMPLETE_WITH_CONST("SELECT");
+
 /* DECLARE */
        else if (pg_strcasecmp(prev2_wd, "DECLARE") == 0)
        {
@@ -1845,7 +2378,7 @@ psql_completion(char *text, int start, int end)
        else if (pg_strcasecmp(prev_wd, "DISCARD") == 0)
        {
                static const char *const list_DISCARD[] =
-               {"ALL", "PLANS", "TEMP", NULL};
+               {"ALL", "PLANS", "SEQUENCES", "TEMP", NULL};
 
                COMPLETE_WITH_LIST(list_DISCARD);
        }
@@ -1871,8 +2404,10 @@ psql_completion(char *text, int start, int end)
 
        /* DROP object with CASCADE / RESTRICT */
        else if ((pg_strcasecmp(prev3_wd, "DROP") == 0 &&
-                         (pg_strcasecmp(prev2_wd, "CONVERSION") == 0 ||
+                         (pg_strcasecmp(prev2_wd, "COLLATION") == 0 ||
+                          pg_strcasecmp(prev2_wd, "CONVERSION") == 0 ||
                           pg_strcasecmp(prev2_wd, "DOMAIN") == 0 ||
+                          pg_strcasecmp(prev2_wd, "EXTENSION") == 0 ||
                           pg_strcasecmp(prev2_wd, "FUNCTION") == 0 ||
                           pg_strcasecmp(prev2_wd, "INDEX") == 0 ||
                           pg_strcasecmp(prev2_wd, "LANGUAGE") == 0 ||
@@ -1911,17 +2446,33 @@ psql_completion(char *text, int start, int end)
                        COMPLETE_WITH_LIST(list_DROPCR);
                }
        }
+       else if (pg_strcasecmp(prev2_wd, "DROP") == 0 &&
+                        pg_strcasecmp(prev_wd, "FOREIGN") == 0)
+       {
+               static const char *const drop_CREATE_FOREIGN[] =
+               {"DATA WRAPPER", "TABLE", NULL};
+
+               COMPLETE_WITH_LIST(drop_CREATE_FOREIGN);
+       }
+
+       /* DROP MATERIALIZED VIEW */
+       else if (pg_strcasecmp(prev2_wd, "DROP") == 0 &&
+                        pg_strcasecmp(prev_wd, "MATERIALIZED") == 0)
+       {
+               COMPLETE_WITH_CONST("VIEW");
+       }
+       else if (pg_strcasecmp(prev3_wd, "DROP") == 0 &&
+                        pg_strcasecmp(prev2_wd, "MATERIALIZED") == 0 &&
+                        pg_strcasecmp(prev_wd, "VIEW") == 0)
+       {
+               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
+       }
+
        else if (pg_strcasecmp(prev4_wd, "DROP") == 0 &&
                         (pg_strcasecmp(prev3_wd, "AGGREGATE") == 0 ||
                          pg_strcasecmp(prev3_wd, "FUNCTION") == 0) &&
                         pg_strcasecmp(prev_wd, "(") == 0)
-       {
-               char       *tmp_buf = malloc(strlen(Query_for_list_of_arguments) + strlen(prev2_wd));
-
-               sprintf(tmp_buf, Query_for_list_of_arguments, prev2_wd);
-               COMPLETE_WITH_QUERY(tmp_buf);
-               free(tmp_buf);
-       }
+               COMPLETE_WITH_FUNCTION_ARG(prev2_wd);
        /* DROP OWNED BY */
        else if (pg_strcasecmp(prev2_wd, "DROP") == 0 &&
                         pg_strcasecmp(prev_wd, "OWNED") == 0)
@@ -1941,6 +2492,11 @@ psql_completion(char *text, int start, int end)
                COMPLETE_WITH_LIST(list_ALTERTEXTSEARCH);
        }
 
+/* EXECUTE, but not EXECUTE embedded in other commands */
+       else if (pg_strcasecmp(prev_wd, "EXECUTE") == 0 &&
+                        prev2_wd[0] == '\0')
+               COMPLETE_WITH_QUERY(Query_for_list_of_prepared_statements);
+
 /* EXPLAIN */
 
        /*
@@ -2015,22 +2571,63 @@ psql_completion(char *text, int start, int end)
                         pg_strcasecmp(prev_wd, "WRAPPER") == 0)
                COMPLETE_WITH_QUERY(Query_for_list_of_fdws);
 
+/* FOREIGN TABLE */
+       else if (pg_strcasecmp(prev3_wd, "CREATE") != 0 &&
+                        pg_strcasecmp(prev2_wd, "FOREIGN") == 0 &&
+                        pg_strcasecmp(prev_wd, "TABLE") == 0)
+               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, NULL);
+
 /* GRANT && REVOKE */
-       /* Complete GRANT/REVOKE with a list of privileges */
+       /* Complete GRANT/REVOKE with a list of roles and privileges */
        else if (pg_strcasecmp(prev_wd, "GRANT") == 0 ||
                         pg_strcasecmp(prev_wd, "REVOKE") == 0)
        {
-               static const char *const list_privilege[] =
-               {"SELECT", "INSERT", "UPDATE", "DELETE", "TRUNCATE", "REFERENCES",
-                       "TRIGGER", "CREATE", "CONNECT", "TEMPORARY", "EXECUTE", "USAGE",
-               "ALL", NULL};
-
-               COMPLETE_WITH_LIST(list_privilege);
+               COMPLETE_WITH_QUERY(Query_for_list_of_roles
+                                                       " UNION SELECT 'SELECT'"
+                                                       " UNION SELECT 'INSERT'"
+                                                       " UNION SELECT 'UPDATE'"
+                                                       " UNION SELECT 'DELETE'"
+                                                       " UNION SELECT 'TRUNCATE'"
+                                                       " UNION SELECT 'REFERENCES'"
+                                                       " UNION SELECT 'TRIGGER'"
+                                                       " UNION SELECT 'CREATE'"
+                                                       " UNION SELECT 'CONNECT'"
+                                                       " UNION SELECT 'TEMPORARY'"
+                                                       " UNION SELECT 'EXECUTE'"
+                                                       " UNION SELECT 'USAGE'"
+                                                       " UNION SELECT 'ALL'");
        }
-       /* Complete GRANT/REVOKE <sth> with "ON" */
+
+       /*
+        * Complete GRANT/REVOKE <privilege> with "ON", GRANT/REVOKE <role> with
+        * TO/FROM
+        */
        else if (pg_strcasecmp(prev2_wd, "GRANT") == 0 ||
                         pg_strcasecmp(prev2_wd, "REVOKE") == 0)
-               COMPLETE_WITH_CONST("ON");
+       {
+               if (pg_strcasecmp(prev_wd, "SELECT") == 0
+                       || pg_strcasecmp(prev_wd, "INSERT") == 0
+                       || pg_strcasecmp(prev_wd, "UPDATE") == 0
+                       || pg_strcasecmp(prev_wd, "DELETE") == 0
+                       || pg_strcasecmp(prev_wd, "TRUNCATE") == 0
+                       || pg_strcasecmp(prev_wd, "REFERENCES") == 0
+                       || pg_strcasecmp(prev_wd, "TRIGGER") == 0
+                       || pg_strcasecmp(prev_wd, "CREATE") == 0
+                       || pg_strcasecmp(prev_wd, "CONNECT") == 0
+                       || pg_strcasecmp(prev_wd, "TEMPORARY") == 0
+                       || pg_strcasecmp(prev_wd, "TEMP") == 0
+                       || pg_strcasecmp(prev_wd, "EXECUTE") == 0
+                       || pg_strcasecmp(prev_wd, "USAGE") == 0
+                       || pg_strcasecmp(prev_wd, "ALL") == 0)
+                       COMPLETE_WITH_CONST("ON");
+               else
+               {
+                       if (pg_strcasecmp(prev2_wd, "GRANT") == 0)
+                               COMPLETE_WITH_CONST("TO");
+                       else
+                               COMPLETE_WITH_CONST("FROM");
+               }
+       }
 
        /*
         * Complete GRANT/REVOKE <sth> ON with a list of tables, views, sequences,
@@ -2046,15 +2643,17 @@ psql_completion(char *text, int start, int end)
        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,
+               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf,
                                                                   " UNION SELECT 'DATABASE'"
+                                                                  " UNION SELECT 'DOMAIN'"
                                                                   " 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'");
+                                                                  " UNION SELECT 'TABLESPACE'"
+                                                                  " UNION SELECT 'TYPE'");
        else if ((pg_strcasecmp(prev4_wd, "GRANT") == 0 ||
                          pg_strcasecmp(prev4_wd, "REVOKE") == 0) &&
                         pg_strcasecmp(prev2_wd, "ON") == 0 &&
@@ -2073,6 +2672,8 @@ psql_completion(char *text, int start, int end)
        {
                if (pg_strcasecmp(prev_wd, "DATABASE") == 0)
                        COMPLETE_WITH_QUERY(Query_for_list_of_databases);
+               else if (pg_strcasecmp(prev_wd, "DOMAIN") == 0)
+                       COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains, NULL);
                else if (pg_strcasecmp(prev_wd, "FUNCTION") == 0)
                        COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
                else if (pg_strcasecmp(prev_wd, "LANGUAGE") == 0)
@@ -2081,6 +2682,8 @@ psql_completion(char *text, int start, int end)
                        COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
                else if (pg_strcasecmp(prev_wd, "TABLESPACE") == 0)
                        COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
+               else if (pg_strcasecmp(prev_wd, "TYPE") == 0)
+                       COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL);
                else if (pg_strcasecmp(prev4_wd, "GRANT") == 0)
                        COMPLETE_WITH_CONST("TO");
                else
@@ -2105,6 +2708,18 @@ psql_completion(char *text, int start, int end)
                        COMPLETE_WITH_CONST("FROM");
        }
 
+       /* Complete "GRANT/REVOKE * TO/FROM" with username, GROUP, or PUBLIC */
+       else if (pg_strcasecmp(prev3_wd, "GRANT") == 0 &&
+                        pg_strcasecmp(prev_wd, "TO") == 0)
+       {
+               COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
+       }
+       else if (pg_strcasecmp(prev3_wd, "REVOKE") == 0 &&
+                        pg_strcasecmp(prev_wd, "FROM") == 0)
+       {
+               COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
+       }
+
 /* GROUP BY */
        else if (pg_strcasecmp(prev3_wd, "FROM") == 0 &&
                         pg_strcasecmp(prev_wd, "GROUP") == 0)
@@ -2169,7 +2784,7 @@ psql_completion(char *text, int start, int end)
 
        /* Complete LOCK [TABLE] <table> with "IN" */
        else if ((pg_strcasecmp(prev2_wd, "LOCK") == 0 &&
-                         pg_strcasecmp(prev_wd, "TABLE")) ||
+                         pg_strcasecmp(prev_wd, "TABLE") != 0) ||
                         (pg_strcasecmp(prev2_wd, "TABLE") == 0 &&
                          pg_strcasecmp(prev3_wd, "LOCK") == 0))
                COMPLETE_WITH_CONST("IN");
@@ -2247,6 +2862,54 @@ psql_completion(char *text, int start, int end)
                         pg_strcasecmp(prev5_wd, "REASSIGN") == 0)
                COMPLETE_WITH_QUERY(Query_for_list_of_roles);
 
+/* REFRESH MATERIALIZED VIEW */
+       else if (pg_strcasecmp(prev_wd, "REFRESH") == 0)
+               COMPLETE_WITH_CONST("MATERIALIZED VIEW");
+       else if (pg_strcasecmp(prev2_wd, "REFRESH") == 0 &&
+                        pg_strcasecmp(prev_wd, "MATERIALIZED") == 0)
+               COMPLETE_WITH_CONST("VIEW");
+       else if (pg_strcasecmp(prev3_wd, "REFRESH") == 0 &&
+                        pg_strcasecmp(prev2_wd, "MATERIALIZED") == 0 &&
+                        pg_strcasecmp(prev_wd, "VIEW") == 0)
+               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
+                                                                  " UNION SELECT 'CONCURRENTLY'");
+       else if (pg_strcasecmp(prev4_wd, "REFRESH") == 0 &&
+                        pg_strcasecmp(prev3_wd, "MATERIALIZED") == 0 &&
+                        pg_strcasecmp(prev2_wd, "VIEW") == 0 &&
+                        pg_strcasecmp(prev_wd, "CONCURRENTLY") == 0)
+               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
+       else if (pg_strcasecmp(prev4_wd, "REFRESH") == 0 &&
+                        pg_strcasecmp(prev3_wd, "MATERIALIZED") == 0 &&
+                        pg_strcasecmp(prev2_wd, "VIEW") == 0)
+               COMPLETE_WITH_CONST("WITH");
+       else if (pg_strcasecmp(prev5_wd, "REFRESH") == 0 &&
+                        pg_strcasecmp(prev4_wd, "MATERIALIZED") == 0 &&
+                        pg_strcasecmp(prev3_wd, "VIEW") == 0 &&
+                        pg_strcasecmp(prev2_wd, "CONCURRENTLY") == 0)
+               COMPLETE_WITH_CONST("WITH DATA");
+       else if (pg_strcasecmp(prev5_wd, "REFRESH") == 0 &&
+                        pg_strcasecmp(prev4_wd, "MATERIALIZED") == 0 &&
+                        pg_strcasecmp(prev3_wd, "VIEW") == 0 &&
+                        pg_strcasecmp(prev_wd, "WITH") == 0)
+       {
+               static const char *const list_WITH_DATA[] =
+               {"NO DATA", "DATA", NULL};
+
+               COMPLETE_WITH_LIST(list_WITH_DATA);
+       }
+       else if (pg_strcasecmp(prev6_wd, "REFRESH") == 0 &&
+                        pg_strcasecmp(prev5_wd, "MATERIALIZED") == 0 &&
+                        pg_strcasecmp(prev4_wd, "VIEW") == 0 &&
+                        pg_strcasecmp(prev3_wd, "CONCURRENTLY") == 0 &&
+                        pg_strcasecmp(prev_wd, "WITH") == 0)
+               COMPLETE_WITH_CONST("DATA");
+       else if (pg_strcasecmp(prev6_wd, "REFRESH") == 0 &&
+                        pg_strcasecmp(prev5_wd, "MATERIALIZED") == 0 &&
+                        pg_strcasecmp(prev4_wd, "VIEW") == 0 &&
+                        pg_strcasecmp(prev2_wd, "WITH") == 0 &&
+                        pg_strcasecmp(prev_wd, "NO") == 0)
+               COMPLETE_WITH_CONST("DATA");
+
 /* REINDEX */
        else if (pg_strcasecmp(prev_wd, "REINDEX") == 0)
        {
@@ -2258,7 +2921,7 @@ psql_completion(char *text, int start, int end)
        else if (pg_strcasecmp(prev2_wd, "REINDEX") == 0)
        {
                if (pg_strcasecmp(prev_wd, "TABLE") == 0)
-                       COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+                       COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
                else if (pg_strcasecmp(prev_wd, "INDEX") == 0)
                        COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
                else if (pg_strcasecmp(prev_wd, "SYSTEM") == 0 ||
@@ -2273,7 +2936,8 @@ psql_completion(char *text, int start, int end)
                         pg_strcasecmp(prev_wd, "LABEL") == 0)
        {
                static const char *const list_SECURITY_LABEL_preposition[] =
-                       {"ON", "FOR"};
+               {"ON", "FOR"};
+
                COMPLETE_WITH_LIST(list_SECURITY_LABEL_preposition);
        }
        else if (pg_strcasecmp(prev4_wd, "SECURITY") == 0 &&
@@ -2283,15 +2947,15 @@ psql_completion(char *text, int start, int end)
        else if ((pg_strcasecmp(prev3_wd, "SECURITY") == 0 &&
                          pg_strcasecmp(prev2_wd, "LABEL") == 0 &&
                          pg_strcasecmp(prev_wd, "ON") == 0) ||
-                (pg_strcasecmp(prev5_wd, "SECURITY") == 0 &&
+                        (pg_strcasecmp(prev5_wd, "SECURITY") == 0 &&
                          pg_strcasecmp(prev4_wd, "LABEL") == 0 &&
                          pg_strcasecmp(prev3_wd, "FOR") == 0 &&
                          pg_strcasecmp(prev_wd, "ON") == 0))
        {
                static const char *const list_SECURITY_LABEL[] =
-               {"LANGUAGE", "SCHEMA", "SEQUENCE", "TABLE", "TYPE", "VIEW", "COLUMN",
-                "AGGREGATE", "FUNCTION", "DOMAIN", "LARGE OBJECT",
-               NULL};
+               {"LANGUAGE", "SCHEMA", "SEQUENCE", "TABLE", "TYPE", "VIEW",
+                       "MATERIALIZED VIEW", "COLUMN", "AGGREGATE", "FUNCTION", "DOMAIN",
+               "LARGE OBJECT", NULL};
 
                COMPLETE_WITH_LIST(list_SECURITY_LABEL);
        }
@@ -2383,6 +3047,12 @@ psql_completion(char *text, int start, int end)
 
                COMPLETE_WITH_LIST(my_list);
        }
+       /* SET CONSTRAINTS */
+       else if (pg_strcasecmp(prev2_wd, "SET") == 0 &&
+                        pg_strcasecmp(prev_wd, "CONSTRAINTS") == 0)
+       {
+               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_constraints_with_schema, "UNION SELECT 'ALL'");
+       }
        /* Complete SET CONSTRAINTS <foo> with DEFERRED|IMMEDIATE */
        else if (pg_strcasecmp(prev3_wd, "SET") == 0 &&
                         pg_strcasecmp(prev2_wd, "CONSTRAINTS") == 0)
@@ -2420,6 +3090,7 @@ psql_completion(char *text, int start, int end)
                         pg_strcasecmp(prev_wd, "TABLESPACE") != 0 &&
                         pg_strcasecmp(prev_wd, "SCHEMA") != 0 &&
                         prev_wd[strlen(prev_wd) - 1] != ')' &&
+                        prev_wd[strlen(prev_wd) - 1] != '=' &&
                         pg_strcasecmp(prev4_wd, "DOMAIN") != 0)
                COMPLETE_WITH_CONST("TO");
        /* Suggest possible variable values */
@@ -2463,6 +3134,11 @@ psql_completion(char *text, int start, int end)
        else if (pg_strcasecmp(prev_wd, "START") == 0)
                COMPLETE_WITH_CONST("TRANSACTION");
 
+/* TABLE, but not TABLE embedded in other commands */
+       else if (pg_strcasecmp(prev_wd, "TABLE") == 0 &&
+                        prev2_wd[0] == '\0')
+               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_relations, NULL);
+
 /* TRUNCATE */
        else if (pg_strcasecmp(prev_wd, "TRUNCATE") == 0)
                COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
@@ -2526,7 +3202,7 @@ psql_completion(char *text, int start, int end)
  * VACUUM [ FULL | FREEZE ] [ VERBOSE ] ANALYZE [ table [ (column [, ...] ) ] ]
  */
        else if (pg_strcasecmp(prev_wd, "VACUUM") == 0)
-               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
+               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
                                                                   " UNION SELECT 'FULL'"
                                                                   " UNION SELECT 'FREEZE'"
                                                                   " UNION SELECT 'ANALYZE'"
@@ -2534,43 +3210,49 @@ psql_completion(char *text, int start, int end)
        else if (pg_strcasecmp(prev2_wd, "VACUUM") == 0 &&
                         (pg_strcasecmp(prev_wd, "FULL") == 0 ||
                          pg_strcasecmp(prev_wd, "FREEZE") == 0))
-               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
+               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
                                                                   " UNION SELECT 'ANALYZE'"
                                                                   " UNION SELECT 'VERBOSE'");
        else if (pg_strcasecmp(prev3_wd, "VACUUM") == 0 &&
                         pg_strcasecmp(prev_wd, "ANALYZE") == 0 &&
                         (pg_strcasecmp(prev2_wd, "FULL") == 0 ||
                          pg_strcasecmp(prev2_wd, "FREEZE") == 0))
-               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
+               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
                                                                   " UNION SELECT 'VERBOSE'");
        else if (pg_strcasecmp(prev3_wd, "VACUUM") == 0 &&
                         pg_strcasecmp(prev_wd, "VERBOSE") == 0 &&
                         (pg_strcasecmp(prev2_wd, "FULL") == 0 ||
                          pg_strcasecmp(prev2_wd, "FREEZE") == 0))
-               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
+               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
                                                                   " UNION SELECT 'ANALYZE'");
        else if (pg_strcasecmp(prev2_wd, "VACUUM") == 0 &&
                         pg_strcasecmp(prev_wd, "VERBOSE") == 0)
-               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
+               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
                                                                   " UNION SELECT 'ANALYZE'");
        else if (pg_strcasecmp(prev2_wd, "VACUUM") == 0 &&
                         pg_strcasecmp(prev_wd, "ANALYZE") == 0)
-               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
+               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
                                                                   " UNION SELECT 'VERBOSE'");
        else if ((pg_strcasecmp(prev_wd, "ANALYZE") == 0 &&
                          pg_strcasecmp(prev2_wd, "VERBOSE") == 0) ||
                         (pg_strcasecmp(prev_wd, "VERBOSE") == 0 &&
                          pg_strcasecmp(prev2_wd, "ANALYZE") == 0))
-               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
 
 /* WITH [RECURSIVE] */
-       else if (pg_strcasecmp(prev_wd, "WITH") == 0)
+
+       /*
+        * Only match when WITH is the first word, as WITH may appear in many
+        * other contexts.
+        */
+       else if (pg_strcasecmp(prev_wd, "WITH") == 0 &&
+                        prev2_wd[0] == '\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);
+               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tmf, NULL);
 
 /* WHERE */
        /* Simple case of the word before the where being the table name */
@@ -2582,7 +3264,11 @@ psql_completion(char *text, int start, int end)
        else if (pg_strcasecmp(prev_wd, "FROM") == 0 &&
                         pg_strcasecmp(prev3_wd, "COPY") != 0 &&
                         pg_strcasecmp(prev3_wd, "\\copy") != 0)
-               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsv, NULL);
+               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
+
+/* ... JOIN ... */
+       else if (pg_strcasecmp(prev_wd, "JOIN") == 0)
+               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
 
 /* Backslash commands */
 /* TODO:  \dc \dd \dl */
@@ -2616,11 +3302,13 @@ psql_completion(char *text, int start, int end)
 
        else if (strncmp(prev_wd, "\\di", strlen("\\di")) == 0)
                COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
+       else if (strncmp(prev_wd, "\\dL", strlen("\\dL")) == 0)
+               COMPLETE_WITH_QUERY(Query_for_list_of_languages);
        else if (strncmp(prev_wd, "\\dn", strlen("\\dn")) == 0)
                COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
        else if (strncmp(prev_wd, "\\dp", strlen("\\dp")) == 0
                         || strncmp(prev_wd, "\\z", strlen("\\z")) == 0)
-               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsv, NULL);
+               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
        else if (strncmp(prev_wd, "\\ds", strlen("\\ds")) == 0)
                COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences, NULL);
        else if (strncmp(prev_wd, "\\dt", strlen("\\dt")) == 0)
@@ -2632,10 +3320,14 @@ psql_completion(char *text, int start, int end)
                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);
+       else if (strncmp(prev_wd, "\\dx", strlen("\\dx")) == 0)
+               COMPLETE_WITH_QUERY(Query_for_list_of_extensions);
+       else if (strncmp(prev_wd, "\\dm", strlen("\\dm")) == 0)
+               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, 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);
+               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_relations, NULL);
 
        else if (strcmp(prev_wd, "\\ef") == 0)
                COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
@@ -2653,7 +3345,25 @@ psql_completion(char *text, int start, int end)
                        "null", "fieldsep", "tuples_only", "title", "tableattr",
                "linestyle", "pager", "recordsep", NULL};
 
-               COMPLETE_WITH_LIST(my_list);
+               COMPLETE_WITH_LIST_CS(my_list);
+       }
+       else if (strcmp(prev2_wd, "\\pset") == 0)
+       {
+               if (strcmp(prev_wd, "format") == 0)
+               {
+                       static const char *const my_list[] =
+                       {"unaligned", "aligned", "wrapped", "html", "latex",
+                       "troff-ms", NULL};
+
+                       COMPLETE_WITH_LIST_CS(my_list);
+               }
+               else if (strcmp(prev_wd, "linestyle") == 0)
+               {
+                       static const char *const my_list[] =
+                       {"ascii", "old-ascii", "unicode", NULL};
+
+                       COMPLETE_WITH_LIST_CS(my_list);
+               }
        }
        else if (strcmp(prev_wd, "\\set") == 0)
        {
@@ -2665,21 +3375,15 @@ psql_completion(char *text, int start, int end)
                         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, "\\ir") == 0 || strcmp(prev_wd, "\\include_relative") == 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 ||
+                        strcmp(prev_wd, "\\lo_import") == 0
                )
-               matches = completion_matches(text, filename_completion_function);
-
-/* Variable interpolation */
-       else if (text[0] == ':' && text[1] != ':')
        {
-               if (text[1] == '\'')
-                       matches = complete_from_variables(text, ":'", "'");
-               else if (text[1] == '"')
-                       matches = complete_from_variables(text, ":\"", "\"");
-               else
-                       matches = complete_from_variables(text, ":", "");
+               completion_charp = "\\";
+               matches = completion_matches(text, complete_from_files);
        }
 
        /*
@@ -2719,11 +3423,12 @@ psql_completion(char *text, int start, int end)
        }
 
        /* free storage */
-       free(prev_wd);
-       free(prev2_wd);
-       free(prev3_wd);
-       free(prev4_wd);
-       free(prev5_wd);
+       {
+               int                     i;
+
+               for (i = 0; i < lengthof(previous_words); i++)
+                       free(previous_words[i]);
+       }
 
        /* Return our Grand List O' Matches */
        return matches;
@@ -2745,11 +3450,11 @@ psql_completion(char *text, int start, int end)
  */
 
 /*
- * This one gives you one from a list of things you can put after CREATE
- * as defined above.
+ * Common routine for create_command_generator and drop_command_generator.
+ * Entries that have 'excluded' flags are not returned.
  */
 static char *
-create_command_generator(const char *text, int state)
+create_or_drop_command_generator(const char *text, int state, bits32 excluded)
 {
        static int      list_index,
                                string_length;
@@ -2766,57 +3471,30 @@ create_command_generator(const char *text, int state)
        while ((name = words_after_create[list_index++].name))
        {
                if ((pg_strncasecmp(name, text, string_length) == 0) &&
-                       !words_after_create[list_index - 1].noshow)
-                       return pg_strdup(name);
+                       !(words_after_create[list_index - 1].flags & excluded))
+                       return pg_strdup_keyword_case(name, text);
        }
        /* if nothing matches, return NULL */
        return NULL;
 }
 
+/*
+ * This one gives you one from a list of things you can put after CREATE
+ * as defined above.
+ */
+static char *
+create_command_generator(const char *text, int state)
+{
+       return create_or_drop_command_generator(text, state, THING_NO_CREATE);
+}
+
 /*
  * 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;
+       return create_or_drop_command_generator(text, state, THING_NO_DROP);
 }
 
 /* The following two functions are wrappers for _complete_from_query */
@@ -3055,14 +3733,14 @@ complete_from_list(const char *text, int state)
        const char *item;
 
        /* need to have a list */
-       psql_assert(completion_charpp);
+       Assert(completion_charpp != NULL);
 
        /* Initialization */
        if (state == 0)
        {
                list_index = 0;
                string_length = strlen(text);
-               casesensitive = true;
+               casesensitive = completion_case_sensitive;
                matches = 0;
        }
 
@@ -3077,7 +3755,17 @@ complete_from_list(const char *text, int state)
 
                /* Second pass is case insensitive, don't bother counting matches */
                if (!casesensitive && pg_strncasecmp(text, item, string_length) == 0)
-                       return pg_strdup(item);
+               {
+                       if (completion_case_sensitive)
+                               return pg_strdup(item);
+                       else
+
+                               /*
+                                * If case insensitive matching was requested initially,
+                                * adjust the case according to setting.
+                                */
+                               return pg_strdup_keyword_case(item, text);
+               }
        }
 
        /*
@@ -3107,12 +3795,19 @@ complete_from_list(const char *text, int state)
 static char *
 complete_from_const(const char *text, int state)
 {
-       (void) text;                            /* We don't care about what was entered
-                                                                * already. */
-
-       psql_assert(completion_charp);
+       Assert(completion_charp != NULL);
        if (state == 0)
-               return pg_strdup(completion_charp);
+       {
+               if (completion_case_sensitive)
+                       return pg_strdup(completion_charp);
+               else
+
+                       /*
+                        * If case insensitive matching was requested initially, adjust
+                        * the case according to setting.
+                        */
+                       return pg_strdup_keyword_case(completion_charp, text);
+       }
        else
                return NULL;
 }
@@ -3127,24 +3822,21 @@ static char **
 complete_from_variables(char *text, const char *prefix, const char *suffix)
 {
        char      **matches;
-       int                     overhead = strlen(prefix) + strlen(suffix) + 1;
-       const char **varnames;
+       char      **varnames;
        int                     nvars = 0;
        int                     maxvars = 100;
        int                     i;
        struct _variable *ptr;
 
-       varnames = (const char **) pg_malloc((maxvars + 1) * sizeof(char *));
+       varnames = (char **) pg_malloc((maxvars + 1) * sizeof(char *));
 
        for (ptr = pset.vars->next; ptr; ptr = ptr->next)
        {
-               char   *buffer;
-
                if (nvars >= maxvars)
                {
                        maxvars *= 2;
-                       varnames = (const char **) realloc(varnames,
-                                                                                          (maxvars + 1) * sizeof(char *));
+                       varnames = (char **) realloc(varnames,
+                                                                                (maxvars + 1) * sizeof(char *));
                        if (!varnames)
                        {
                                psql_error("out of memory\n");
@@ -3152,25 +3844,117 @@ complete_from_variables(char *text, const char *prefix, const char *suffix)
                        }
                }
 
-               buffer = (char *) pg_malloc(strlen(ptr->name) + overhead);
-               sprintf(buffer, "%s%s%s", prefix, ptr->name, suffix);
-               varnames[nvars++] = buffer;
+               varnames[nvars++] = psprintf("%s%s%s", prefix, ptr->name, suffix);
        }
 
        varnames[nvars] = NULL;
-       COMPLETE_WITH_LIST(varnames);
+       COMPLETE_WITH_LIST_CS((const char *const *) varnames);
 
        for (i = 0; i < nvars; i++)
-               free((void *) varnames[i]);
+               free(varnames[i]);
        free(varnames);
 
        return matches;
 }
 
 
+/*
+ * This function wraps rl_filename_completion_function() to strip quotes from
+ * the input before searching for matches and to quote any matches for which
+ * the consuming command will require it.
+ */
+static char *
+complete_from_files(const char *text, int state)
+{
+       static const char *unquoted_text;
+       char       *unquoted_match;
+       char       *ret = NULL;
+
+       if (state == 0)
+       {
+               /* Initialization: stash the unquoted input. */
+               unquoted_text = strtokx(text, "", NULL, "'", *completion_charp,
+                                                               false, true, pset.encoding);
+               /* expect a NULL return for the empty string only */
+               if (!unquoted_text)
+               {
+                       Assert(*text == '\0');
+                       unquoted_text = text;
+               }
+       }
+
+       unquoted_match = filename_completion_function(unquoted_text, state);
+       if (unquoted_match)
+       {
+               /*
+                * Caller sets completion_charp to a zero- or one-character string
+                * containing the escape character.  This is necessary since \copy has
+                * no escape character, but every other backslash command recognizes
+                * "\" as an escape character.  Since we have only two callers, don't
+                * bother providing a macro to simplify this.
+                */
+               ret = quote_if_needed(unquoted_match, " \t\r\n\"`",
+                                                         '\'', *completion_charp, pset.encoding);
+               if (ret)
+                       free(unquoted_match);
+               else
+                       ret = unquoted_match;
+       }
+
+       return ret;
+}
+
+
 /* HELPER FUNCTIONS */
 
 
+/*
+ * Make a pg_strdup copy of s and convert the case according to
+ * COMP_KEYWORD_CASE variable, using ref as the text that was already entered.
+ */
+static char *
+pg_strdup_keyword_case(const char *s, const char *ref)
+{
+       char       *ret,
+                          *p;
+       unsigned char first = ref[0];
+       int                     tocase;
+       const char *varval;
+
+       varval = GetVariable(pset.vars, "COMP_KEYWORD_CASE");
+       if (!varval)
+               tocase = 0;
+       else if (strcmp(varval, "lower") == 0)
+               tocase = -2;
+       else if (strcmp(varval, "preserve-lower") == 0)
+               tocase = -1;
+       else if (strcmp(varval, "preserve-upper") == 0)
+               tocase = +1;
+       else if (strcmp(varval, "upper") == 0)
+               tocase = +2;
+       else
+               tocase = 0;
+
+       /* default */
+       if (tocase == 0)
+               tocase = +1;
+
+       ret = pg_strdup(s);
+
+       if (tocase == -2
+               || ((tocase == -1 || tocase == +1) && islower(first))
+               || (tocase == -1 && !isalpha(first))
+               )
+               for (p = ret; *p; p++)
+                       *p = pg_tolower((unsigned char) *p);
+       else
+               for (p = ret; *p; p++)
+                       *p = pg_toupper((unsigned char) *p);
+
+       return ret;
+}
+
+
 /*
  * Execute a query and report any errors. This should be the preferred way of
  * talking to the database in this file.
@@ -3200,77 +3984,88 @@ exec_query(const char *query)
 
 
 /*
- * Return the word (space delimited) before point. Set skip > 0 to
- * skip that many words; e.g. skip=1 finds the word before the
- * previous one. Return value is NULL or a malloc'ed string.
+ * Return the nwords word(s) before point.     Words are returned right to left,
+ * that is, previous_words[0] gets the last word before point.
+ * If we run out of words, remaining array elements are set to empty strings.
+ * Each array element is filled with a malloc'd string.
  */
-static char *
-previous_word(int point, int skip)
+static void
+get_previous_words(int point, char **previous_words, int nwords)
 {
-       int                     i,
-                               start = 0,
-                               end = -1,
-                               inquotes = 0;
-       char       *s;
        const char *buf = rl_line_buffer;       /* alias */
+       int                     i;
 
-       /* first we look for a space or a parenthesis before the current word */
+       /* first we look for a non-word char before the current point */
        for (i = point - 1; i >= 0; i--)
                if (strchr(WORD_BREAKS, buf[i]))
                        break;
        point = i;
 
-       while (skip-- >= 0)
+       while (nwords-- > 0)
        {
-               int                     parentheses = 0;
+               int                     start,
+                                       end;
+               char       *s;
 
                /* now find the first non-space which then constitutes the end */
+               end = -1;
                for (i = point; i >= 0; i--)
-                       if (buf[i] != ' ')
+               {
+                       if (!isspace((unsigned char) buf[i]))
                        {
                                end = i;
                                break;
                        }
+               }
 
                /*
-                * 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. We also handle open quotes and parentheses.
+                * If no end found we return an empty string, because there is no word
+                * before the point
                 */
-               for (start = end; start > 0; start--)
+               if (end < 0)
                {
-                       if (buf[start] == '"')
-                               inquotes = !inquotes;
-                       if (inquotes == 0)
+                       point = end;
+                       s = pg_strdup("");
+               }
+               else
+               {
+                       /*
+                        * Otherwise we now look for the start. The start is either the
+                        * last character before any word-break character going backwards
+                        * from the end, or it's simply character 0. We also handle open
+                        * quotes and parentheses.
+                        */
+                       bool            inquotes = false;
+                       int                     parentheses = 0;
+
+                       for (start = end; start > 0; start--)
                        {
-                               if (buf[start] == ')')
-                                       parentheses++;
-                               else if (buf[start] == '(')
+                               if (buf[start] == '"')
+                                       inquotes = !inquotes;
+                               if (!inquotes)
                                {
-                                       if (--parentheses <= 0)
+                                       if (buf[start] == ')')
+                                               parentheses++;
+                                       else if (buf[start] == '(')
+                                       {
+                                               if (--parentheses <= 0)
+                                                       break;
+                                       }
+                                       else if (parentheses == 0 &&
+                                                        strchr(WORD_BREAKS, buf[start - 1]))
                                                break;
                                }
-                               else if (parentheses == 0 &&
-                                                strchr(WORD_BREAKS, buf[start - 1]))
-                                       break;
                        }
-               }
 
-               point = start - 1;
-       }
+                       point = start - 1;
 
-       /* make a copy */
-       s = pg_malloc(end - start + 2);
-       strlcpy(s, &buf[start], end - start + 2);
+                       /* make a copy of chars from start to end inclusive */
+                       s = pg_malloc(end - start + 2);
+                       strlcpy(s, &buf[start], end - start + 2);
+               }
 
-       return s;
+               *previous_words++ = s;
+       }
 }
 
 #ifdef NOT_USED