]> 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 c01b02ae3a11fab9f9590ddcef7d36be00df0639..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
  */
@@ -318,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.
  */
@@ -369,11 +420,11 @@ static const SchemaQuery Query_for_list_of_updatables = {
        NULL
 };
 
-static const SchemaQuery Query_for_list_of_tisvf = {
+static const SchemaQuery Query_for_list_of_relations = {
        /* catname */
        "pg_catalog.pg_class c",
        /* selcondition */
-       "c.relkind IN ('r', 'i', 'S', 'v', 'f')",
+       NULL,
        /* viscondition */
        "pg_catalog.pg_table_is_visible(c.oid)",
        /* namespace */
@@ -384,11 +435,41 @@ static const SchemaQuery Query_for_list_of_tisvf = {
        NULL
 };
 
-static const SchemaQuery Query_for_list_of_tsvf = {
+static const SchemaQuery Query_for_list_of_tsvmf = {
        /* catname */
        "pg_catalog.pg_class c",
        /* selcondition */
-       "c.relkind IN ('r', 'S', 'v', 'f')",
+       "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', '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_tm = {
+       /* catname */
+       "pg_catalog.pg_class c",
+       /* selcondition */
+       "c.relkind IN ('r', 'm')",
        /* viscondition */
        "pg_catalog.pg_table_is_visible(c.oid)",
        /* namespace */
@@ -414,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
@@ -528,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) "\
@@ -573,10 +708,25 @@ 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) "\
@@ -586,7 +736,12 @@ static const SchemaQuery Query_for_list_of_views = {
 #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 IS NULL"
+"  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
@@ -598,23 +753,27 @@ 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},
@@ -623,9 +782,11 @@ static const pgsql_thing_t words_after_create[] = {
        {"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},
@@ -633,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 */
 };
 
 
@@ -657,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);
@@ -703,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
@@ -725,13 +894,13 @@ psql_completion(char *text, int start, int end)
                "\\a", "\\connect", "\\conninfo", "\\C", "\\cd", "\\copy", "\\copyright",
                "\\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",
+               "\\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 */
@@ -747,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 */
@@ -770,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 */
@@ -786,12 +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",
-                "EXTENSION", "FOREIGN DATA WRAPPER", "FOREIGN TABLE", "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);
        }
@@ -813,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};
@@ -832,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)
@@ -857,7 +1037,7 @@ psql_completion(char *text, int start, int end)
                         pg_strcasecmp(prev2_wd, "EXTENSION") == 0)
        {
                static const char *const list_ALTEREXTENSION[] =
-               {"ADD", "DROP", "SET SCHEMA", NULL};
+               {"ADD", "DROP", "UPDATE", "SET SCHEMA", NULL};
 
                COMPLETE_WITH_LIST(list_ALTEREXTENSION);
        }
@@ -879,7 +1059,7 @@ 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);
        }
@@ -953,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) &&
@@ -964,11 +1152,28 @@ psql_completion(char *text, int start, int end)
                        "ENCRYPTED", "INHERIT", "LOGIN", "NOCREATEDB", "NOCREATEROLE",
                        "NOCREATEUSER", "NOINHERIT", "NOLOGIN", "NOREPLICATION",
                        "NOSUPERUSER", "RENAME TO", "REPLICATION", "RESET", "SET",
-               "SUPERUSER", "UNENCRYPTED", "VALID UNTIL", NULL};
+               "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) &&
@@ -1013,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);
        }
@@ -1027,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 &&
@@ -1075,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)
@@ -1101,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);
        }
@@ -1133,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)
@@ -1143,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
@@ -1162,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");
 
@@ -1181,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) ||
@@ -1393,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)
@@ -1414,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 ||
@@ -1481,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");
        }
@@ -1503,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");
@@ -1510,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", "FOREIGN TABLE", "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 &&
@@ -1527,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 */
@@ -1561,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 ||
@@ -1571,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);
        }
@@ -1582,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);
        }
@@ -1628,7 +2010,12 @@ psql_completion(char *text, int start, int end)
                         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" */
@@ -1647,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) &&
@@ -1749,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 &&
@@ -1803,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};
@@ -1824,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 ||
@@ -1839,11 +2255,29 @@ psql_completion(char *text, int start, int end)
                        "ENCRYPTED", "IN", "INHERIT", "LOGIN", "NOCREATEDB",
                        "NOCREATEROLE", "NOCREATEUSER", "NOINHERIT", "NOLOGIN",
                        "NOREPLICATION", "NOSUPERUSER", "REPLICATION", "ROLE",
-               "SUPERUSER", "SYSID", "UNENCRYPTED", "VALID UNTIL", NULL};
+               "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
@@ -1878,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)
        {
@@ -1928,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);
        }
@@ -1954,7 +2404,8 @@ 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 ||
@@ -2003,17 +2454,25 @@ psql_completion(char *text, int start, int end)
 
                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)
@@ -2033,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 */
 
        /*
@@ -2111,24 +2575,59 @@ psql_completion(char *text, int start, int end)
        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);
+               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,
@@ -2144,23 +2643,24 @@ 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_tsvf,
+               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 'FOREIGN TABLE'"
                                                                   " 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 &&
                         pg_strcasecmp(prev_wd, "FOREIGN") == 0)
        {
                static const char *const list_privilege_foreign[] =
-               {"DATA WRAPPER", "SERVER", "TABLE", NULL};
+               {"DATA WRAPPER", "SERVER", NULL};
 
                COMPLETE_WITH_LIST(list_privilege_foreign);
        }
@@ -2172,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)
@@ -2180,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
@@ -2204,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)
@@ -2268,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");
@@ -2346,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)
        {
@@ -2357,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 ||
@@ -2372,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 &&
@@ -2382,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);
        }
@@ -2482,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)
@@ -2519,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 */
@@ -2562,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);
@@ -2625,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'"
@@ -2633,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 */
@@ -2681,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_tsvf, 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 */
@@ -2721,7 +3308,7 @@ psql_completion(char *text, int start, int end)
                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_tsvf, 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)
@@ -2733,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_tisvf, 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);
@@ -2754,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)
        {
@@ -2766,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);
        }
 
        /*
@@ -2820,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;
@@ -2846,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;
@@ -2867,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 */
@@ -3156,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;
        }
 
@@ -3178,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);
+               }
        }
 
        /*
@@ -3208,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;
 }
@@ -3228,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");
@@ -3253,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.
@@ -3301,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