]> 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 24e50d071d924382337089077924a10616f635c5..84d2eb4d4209a2cfa2c3c6e8bcc0be48e87ac008 100644 (file)
@@ -1,9 +1,9 @@
 /*
  * psql - the PostgreSQL interactive terminal
  *
- * Copyright (c) 2000-2008, PostgreSQL Global Development Group
+ * Copyright (c) 2000-2013, PostgreSQL Global Development Group
  *
- * $PostgreSQL: pgsql/src/bin/psql/tab-complete.c,v 1.174 2008/11/07 18:25:07 tgl Exp $
+ * src/bin/psql/tab-complete.c
  */
 
 /*----------------------------------------------------------------------
@@ -66,6 +66,8 @@ extern char *filename_completion_function();
 #define completion_matches rl_completion_matches
 #endif
 
+/* word break characters */
+#define WORD_BREAKS            "\t\n@$><=;|&{() "
 
 /*
  * This struct is used to define "schema queries", which are custom-built
@@ -125,11 +127,12 @@ static int        completion_max_records;
  * Communication variables set by COMPLETE_WITH_FOO macros and then used by
  * the completion callback functions.  Ugly but there is no better way.
  */
-static const char *completion_charp;                   /* to pass a string */
+static const char *completion_charp;   /* to pass a string */
 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
@@ -139,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 { \
@@ -153,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)
 
@@ -190,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
  */
@@ -286,6 +324,21 @@ static const SchemaQuery Query_for_list_of_sequences = {
        NULL
 };
 
+static const SchemaQuery Query_for_list_of_foreign_tables = {
+       /* catname */
+       "pg_catalog.pg_class c",
+       /* selcondition */
+       "c.relkind IN ('f')",
+       /* viscondition */
+       "pg_catalog.pg_table_is_visible(c.oid)",
+       /* namespace */
+       "c.relnamespace",
+       /* result */
+       "pg_catalog.quote_ident(c.relname)",
+       /* qualresult */
+       NULL
+};
+
 static const SchemaQuery Query_for_list_of_tables = {
        /* catname */
        "pg_catalog.pg_class c",
@@ -301,11 +354,77 @@ static const SchemaQuery Query_for_list_of_tables = {
        NULL
 };
 
-static const SchemaQuery Query_for_list_of_tisv = {
+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.
+ */
+static const SchemaQuery Query_for_list_of_insertables = {
+       /* catname */
+       "pg_catalog.pg_class c",
+       /* selcondition */
+       "(c.relkind = 'r' OR (c.relkind = 'v' AND c.relhastriggers AND EXISTS "
+       "(SELECT 1 FROM pg_catalog.pg_trigger t WHERE t.tgrelid = c.oid AND t.tgtype & (1 << 2) <> 0)))",
+       /* 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_deletables = {
+       /* catname */
+       "pg_catalog.pg_class c",
+       /* selcondition */
+       "(c.relkind = 'r' OR (c.relkind = 'v' AND c.relhastriggers AND EXISTS "
+       "(SELECT 1 FROM pg_catalog.pg_trigger t WHERE t.tgrelid = c.oid AND t.tgtype & (1 << 3) <> 0)))",
+       /* 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_updatables = {
+       /* catname */
+       "pg_catalog.pg_class c",
+       /* selcondition */
+       "(c.relkind = 'r' OR (c.relkind = 'v' AND c.relhastriggers AND EXISTS "
+       "(SELECT 1 FROM pg_catalog.pg_trigger t WHERE t.tgrelid = c.oid AND t.tgtype & (1 << 4) <> 0)))",
+       /* 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_relations = {
        /* catname */
        "pg_catalog.pg_class c",
        /* selcondition */
-       "c.relkind IN ('r', 'i', 'S', 'v')",
+       NULL,
        /* viscondition */
        "pg_catalog.pg_table_is_visible(c.oid)",
        /* namespace */
@@ -316,11 +435,41 @@ static const SchemaQuery Query_for_list_of_tisv = {
        NULL
 };
 
-static const SchemaQuery Query_for_list_of_tsv = {
+static const SchemaQuery Query_for_list_of_tsvmf = {
        /* catname */
        "pg_catalog.pg_class c",
        /* selcondition */
-       "c.relkind IN ('r', 'S', 'v')",
+       "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 */
@@ -346,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
@@ -387,7 +551,7 @@ static const SchemaQuery Query_for_list_of_views = {
 
 #define Query_for_list_of_template_databases \
 "SELECT pg_catalog.quote_ident(datname) FROM pg_catalog.pg_database "\
-" WHERE substring(pg_catalog.quote_ident(datname),1,%d)='%s' and datistemplate IS TRUE"
+" WHERE substring(pg_catalog.quote_ident(datname),1,%d)='%s' AND datistemplate"
 
 #define Query_for_list_of_databases \
 "SELECT pg_catalog.quote_ident(datname) FROM pg_catalog.pg_database "\
@@ -404,9 +568,9 @@ static const SchemaQuery Query_for_list_of_views = {
 
 #define Query_for_list_of_languages \
 "SELECT pg_catalog.quote_ident(lanname) "\
-"  FROM pg_language "\
+"  FROM pg_catalog.pg_language "\
 " WHERE lanname != 'internal' "\
-"   AND substring(pg_catalog.quote_ident(lanname),1,%d)='%s' "
+"   AND substring(pg_catalog.quote_ident(lanname),1,%d)='%s'"
 
 #define Query_for_list_of_schemas \
 "SELECT pg_catalog.quote_ident(nspname) FROM pg_catalog.pg_namespace "\
@@ -431,18 +595,6 @@ static const SchemaQuery Query_for_list_of_views = {
 "  UNION ALL SELECT 'all') ss "\
 " WHERE substring(name,1,%d)='%s'"
 
-/*
- * Note: As of Pg 8.2, we no longer use relkind 's', but we keep it here
- * for compatibility with older servers
- */
-#define Query_for_list_of_system_relations \
-"SELECT pg_catalog.quote_ident(relname) "\
-"  FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n "\
-" WHERE c.relkind IN ('r', 'v', 's', 'S') "\
-"   AND substring(pg_catalog.quote_ident(relname),1,%d)='%s' "\
-"   AND c.relnamespace = n.oid "\
-"   AND n.nspname = 'pg_catalog'"
-
 #define Query_for_list_of_roles \
 " SELECT pg_catalog.quote_ident(rolname) "\
 "   FROM pg_catalog.pg_roles "\
@@ -472,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) "\
@@ -497,6 +688,61 @@ static const SchemaQuery Query_for_list_of_views = {
 "SELECT pg_catalog.quote_ident(tmplname) FROM pg_catalog.pg_ts_template "\
 " WHERE substring(pg_catalog.quote_ident(tmplname),1,%d)='%s'"
 
+#define Query_for_list_of_fdws \
+" SELECT pg_catalog.quote_ident(fdwname) "\
+"   FROM pg_catalog.pg_foreign_data_wrapper "\
+"  WHERE substring(pg_catalog.quote_ident(fdwname),1,%d)='%s'"
+
+#define Query_for_list_of_servers \
+" SELECT pg_catalog.quote_ident(srvname) "\
+"   FROM pg_catalog.pg_foreign_server "\
+"  WHERE substring(pg_catalog.quote_ident(srvname),1,%d)='%s'"
+
+#define Query_for_list_of_user_mappings \
+" SELECT pg_catalog.quote_ident(usename) "\
+"   FROM pg_catalog.pg_user_mappings "\
+"  WHERE substring(pg_catalog.quote_ident(usename),1,%d)='%s'"
+
+#define Query_for_list_of_access_methods \
+" SELECT pg_catalog.quote_ident(amname) "\
+"   FROM pg_catalog.pg_am "\
+"  WHERE substring(pg_catalog.quote_ident(amname),1,%d)='%s'"
+
+/* 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 (%d = pg_catalog.length('%s'))"\
+"   AND (pg_catalog.quote_ident(proname)='%s'"\
+"        OR '\"' || proname || '\"'='%s') "\
+"   AND (pg_catalog.pg_function_is_visible(pg_proc.oid))"
+
+/* the silly-looking length condition is just to eat up the current word */
+#define Query_for_list_of_arguments_with_schema \
+"SELECT pg_catalog.oidvectortypes(proargtypes)||')' "\
+"  FROM pg_catalog.pg_proc p, pg_catalog.pg_namespace n "\
+" WHERE (%d = pg_catalog.length('%s'))"\
+"   AND n.oid = p.pronamespace "\
+"   AND (pg_catalog.quote_ident(proname)='%s' "\
+"        OR '\"' || proname || '\"' ='%s') "\
+"   AND (pg_catalog.quote_ident(nspname)='%s' "\
+"        OR '\"' || nspname || '\"' ='%s') "
+
+#define Query_for_list_of_extensions \
+" SELECT pg_catalog.quote_ident(extname) "\
+"   FROM pg_catalog.pg_extension "\
+"  WHERE substring(pg_catalog.quote_ident(extname),1,%d)='%s'"
+
+#define Query_for_list_of_available_extensions \
+" SELECT pg_catalog.quote_ident(name) "\
+"   FROM pg_catalog.pg_available_extensions "\
+"  WHERE substring(pg_catalog.quote_ident(name),1,%d)='%s' AND installed_version IS NULL"
+
+#define Query_for_list_of_prepared_statements \
+" SELECT pg_catalog.quote_ident(name) "\
+"   FROM pg_catalog.pg_prepared_statements "\
+"  WHERE substring(pg_catalog.quote_ident(name),1,%d)='%s'"
+
 /*
  * This is a list of all "things" in Pgsql, which can show up after CREATE or
  * DROP; and there is also a query to get a list of them.
@@ -507,46 +753,59 @@ typedef struct
        const char *name;
        const char *query;                      /* simple query, or NULL */
        const SchemaQuery *squery;      /* schema query, or NULL */
-       const bool      noshow;                 /* NULL or true if this word should not show
-                                                                * up after CREATE or DROP */
+       const bits32 flags;                     /* visibility flags, see below */
 } pgsql_thing_t;
 
+#define THING_NO_CREATE                (1 << 0)        /* should not show up after CREATE */
+#define THING_NO_DROP          (1 << 1)        /* should not show up after DROP */
+#define THING_NO_SHOW          (THING_NO_CREATE | THING_NO_DROP)
+
 static const pgsql_thing_t words_after_create[] = {
        {"AGGREGATE", NULL, &Query_for_list_of_aggregates},
        {"CAST", NULL, NULL},           /* Casts have complex structures for names, so
                                                                 * skip it */
+       {"COLLATION", "SELECT pg_catalog.quote_ident(collname) FROM pg_catalog.pg_collation WHERE collencoding IN (-1, pg_catalog.pg_char_to_encoding(pg_catalog.getdatabaseencoding())) AND substring(pg_catalog.quote_ident(collname),1,%d)='%s'"},
 
        /*
         * CREATE CONSTRAINT TRIGGER is not supported here because it is designed
         * to be used only by pg_dump.
         */
-       {"CONFIGURATION", Query_for_list_of_ts_configurations, NULL, true},
+       {"CONFIGURATION", Query_for_list_of_ts_configurations, NULL, THING_NO_SHOW},
        {"CONVERSION", "SELECT pg_catalog.quote_ident(conname) FROM pg_catalog.pg_conversion WHERE substring(pg_catalog.quote_ident(conname),1,%d)='%s'"},
        {"DATABASE", Query_for_list_of_databases},
-       {"DICTIONARY", Query_for_list_of_ts_dictionaries, NULL, true},
+       {"DICTIONARY", Query_for_list_of_ts_dictionaries, NULL, THING_NO_SHOW},
        {"DOMAIN", NULL, &Query_for_list_of_domains},
+       {"EXTENSION", Query_for_list_of_extensions},
+       {"FOREIGN DATA WRAPPER", NULL, NULL},
+       {"FOREIGN TABLE", NULL, NULL},
        {"FUNCTION", NULL, &Query_for_list_of_functions},
        {"GROUP", Query_for_list_of_roles},
        {"LANGUAGE", Query_for_list_of_languages},
        {"INDEX", NULL, &Query_for_list_of_indexes},
+       {"MATERIALIZED VIEW", NULL, NULL},
        {"OPERATOR", NULL, NULL},       /* Querying for this is probably not such a
                                                                 * good idea. */
-       {"PARSER", Query_for_list_of_ts_parsers, NULL, true},
+       {"OWNED", NULL, NULL, THING_NO_CREATE},         /* for DROP OWNED BY ... */
+       {"PARSER", Query_for_list_of_ts_parsers, NULL, THING_NO_SHOW},
        {"ROLE", Query_for_list_of_roles},
        {"RULE", "SELECT pg_catalog.quote_ident(rulename) FROM pg_catalog.pg_rules WHERE substring(pg_catalog.quote_ident(rulename),1,%d)='%s'"},
        {"SCHEMA", Query_for_list_of_schemas},
        {"SEQUENCE", NULL, &Query_for_list_of_sequences},
+       {"SERVER", Query_for_list_of_servers},
        {"TABLE", NULL, &Query_for_list_of_tables},
        {"TABLESPACE", Query_for_list_of_tablespaces},
-       {"TEMP", NULL, NULL},           /* for CREATE TEMP TABLE ... */
-       {"TEMPLATE", Query_for_list_of_ts_templates, NULL, true},
+       {"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 */
 };
 
 
@@ -558,29 +817,33 @@ static char *complete_from_query(const char *text, int state);
 static char *complete_from_schema_query(const char *text, int state);
 static char *_complete_from_query(int is_schema_query,
                                         const char *text, int state);
-static char *complete_from_const(const char *text, int state);
 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);
+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 int     find_open_parenthesis(int end);
+static void get_previous_words(int point, char **previous_words, int nwords);
 
-#if 0
+#ifdef NOT_USED
 static char *quote_file_name(char *text, int match_type, char *quote_pointer);
 static char *dequote_file_name(char *text, char quote_char);
 #endif
 
 
-/* Initialize the readline library for our purposes. */
+/*
+ * Initialize the readline library for our purposes.
+ */
 void
 initialize_readline(void)
 {
        rl_readline_name = (char *) pset.progname;
        rl_attempted_completion_function = (void *) psql_completion;
 
-       rl_basic_word_break_characters = "\t\n@$><=;|&{( ";
+       rl_basic_word_break_characters = WORD_BREAKS;
 
        completion_max_records = 1000;
 
@@ -591,45 +854,53 @@ initialize_readline(void)
 }
 
 
-/* The completion function. Acc. to readline spec this gets passed the text
-   entered to far and its start and end in the readline buffer. The return value
-   is some partially obscure list format that can be generated by the readline
-   libraries completion_matches() function, so we don't have to worry about it.
-*/
+/*
+ * The completion function.
+ *
+ * According to readline spec this gets passed the text entered so far and its
+ * start and end positions in the readline buffer. The return value is some
+ * partially obscure list format that can be generated by readline's
+ * completion_matches() function, so we don't have to worry about it.
+ */
 static char **
 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", "DROP", "END", "EXECUTE", "EXPLAIN", "FETCH",
+               "DELETE FROM", "DISCARD", "DO", "DROP", "END", "EXECUTE", "EXPLAIN", "FETCH",
                "GRANT", "INSERT", "LISTEN", "LOAD", "LOCK", "MOVE", "NOTIFY", "PREPARE",
-               "REASSIGN", "REINDEX", "RELEASE", "RESET", "REVOKE", "ROLLBACK",
-               "SAVEPOINT", "SELECT", "SET", "SHOW", "START", "TRUNCATE", "UNLISTEN",
-               "UPDATE", "VACUUM", "VALUES", "WITH", NULL
+               "REASSIGN", "REFRESH", "REINDEX", "RELEASE", "RESET", "REVOKE", "ROLLBACK",
+               "SAVEPOINT", "SECURITY LABEL", "SELECT", "SET", "SHOW", "START",
+               "TABLE", "TRUNCATE", "UNLISTEN", "UPDATE", "VACUUM", "VALUES", "WITH",
+               NULL
        };
 
        static const char *const backslash_commands[] = {
-               "\\a", "\\connect", "\\C", "\\cd", "\\copy", "\\copyright",
-               "\\d", "\\da", "\\db", "\\dc", "\\dC", "\\dd", "\\dD", "\\df",
-               "\\dF", "\\dFd", "\\dFp", "\\dFt", "\\dg", "\\di", "\\dl",
-               "\\dn", "\\do", "\\dp", "\\ds", "\\dS", "\\dt", "\\dT", "\\dv", "\\du",
-               "\\e", "\\echo", "\\encoding",
-               "\\f", "\\g", "\\h", "\\help", "\\H", "\\i", "\\l",
+               "\\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", "\\dx",
+               "\\e", "\\echo", "\\ef", "\\encoding",
+               "\\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", "\\t", "\\T",
-               "\\timing", "\\unset", "\\x", "\\w", "\\z", "\\!", NULL
+               "\\set", "\\sf", "\\t", "\\T",
+               "\\timing", "\\unset", "\\x", "\\w", "\\watch", "\\z", "\\!", NULL
        };
 
        (void) end;                                     /* not used */
@@ -645,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 */
@@ -668,12 +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, except ALTER (TABLE|DOMAIN|GROUP) sth DROP */
+/* DROP, but not DROP embedded in other commands */
        /* complete with something you can drop */
        else if (pg_strcasecmp(prev_wd, "DROP") == 0 &&
-                        pg_strcasecmp(prev3_wd, "TABLE") != 0 &&
-                        pg_strcasecmp(prev3_wd, "DOMAIN") != 0 &&
-                        pg_strcasecmp(prev3_wd, "GROUP") != 0)
+                        prev2_wd[0] == '\0')
                matches = completion_matches(text, drop_command_generator);
 
 /* ALTER */
@@ -686,9 +962,12 @@ psql_completion(char *text, int start, int end)
                         pg_strcasecmp(prev3_wd, "TABLE") != 0)
        {
                static const char *const list_ALTER[] =
-               {"AGGREGATE", "CONVERSION", "DATABASE", "DOMAIN", "FUNCTION",
-                       "GROUP", "INDEX", "LANGUAGE", "OPERATOR", "ROLE", "SCHEMA", "SEQUENCE", "TABLE",
-               "TABLESPACE", "TEXT SEARCH", "TRIGGER", "TYPE", "USER", "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);
        }
@@ -696,17 +975,26 @@ psql_completion(char *text, int start, int end)
        else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
                         (pg_strcasecmp(prev2_wd, "AGGREGATE") == 0 ||
                          pg_strcasecmp(prev2_wd, "FUNCTION") == 0))
+               COMPLETE_WITH_CONST("(");
+       /* ALTER AGGREGATE,FUNCTION <name> (...) */
+       else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
+                        (pg_strcasecmp(prev3_wd, "AGGREGATE") == 0 ||
+                         pg_strcasecmp(prev3_wd, "FUNCTION") == 0))
        {
-               static const char *const list_ALTERAGG[] =
-               {"OWNER TO", "RENAME TO", "SET SCHEMA", NULL};
+               if (prev_wd[strlen(prev_wd) - 1] == ')')
+               {
+                       static const char *const list_ALTERAGG[] =
+                       {"OWNER TO", "RENAME TO", "SET SCHEMA", NULL};
 
-               COMPLETE_WITH_LIST(list_ALTERAGG);
+                       COMPLETE_WITH_LIST(list_ALTERAGG);
+               }
+               else
+                       COMPLETE_WITH_FUNCTION_ARG(prev2_wd);
        }
 
-       /* ALTER CONVERSION,SCHEMA <name> */
+       /* ALTER SCHEMA <name> */
        else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
-                        (pg_strcasecmp(prev2_wd, "CONVERSION") == 0 ||
-                         pg_strcasecmp(prev2_wd, "SCHEMA") == 0))
+                        pg_strcasecmp(prev2_wd, "SCHEMA") == 0)
        {
                static const char *const list_ALTERGEN[] =
                {"OWNER TO", "RENAME TO", NULL};
@@ -714,6 +1002,26 @@ 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)
+       {
+               static const char *const list_ALTERGEN[] =
+               {"OWNER TO", "RENAME TO", "SET SCHEMA", NULL};
+
+               COMPLETE_WITH_LIST(list_ALTERGEN);
+       }
+
        /* ALTER DATABASE <name> */
        else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
                         pg_strcasecmp(prev2_wd, "DATABASE") == 0)
@@ -724,6 +1032,49 @@ psql_completion(char *text, int start, int end)
                COMPLETE_WITH_LIST(list_ALTERDATABASE);
        }
 
+       /* ALTER EXTENSION <name> */
+       else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
+                        pg_strcasecmp(prev2_wd, "EXTENSION") == 0)
+       {
+               static const char *const list_ALTEREXTENSION[] =
+               {"ADD", "DROP", "UPDATE", "SET SCHEMA", NULL};
+
+               COMPLETE_WITH_LIST(list_ALTEREXTENSION);
+       }
+
+       /* ALTER FOREIGN */
+       else if (pg_strcasecmp(prev2_wd, "ALTER") == 0 &&
+                        pg_strcasecmp(prev_wd, "FOREIGN") == 0)
+       {
+               static const char *const list_ALTER_FOREIGN[] =
+               {"DATA WRAPPER", "TABLE", NULL};
+
+               COMPLETE_WITH_LIST(list_ALTER_FOREIGN);
+       }
+
+       /* ALTER FOREIGN DATA WRAPPER <name> */
+       else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
+                        pg_strcasecmp(prev4_wd, "FOREIGN") == 0 &&
+                        pg_strcasecmp(prev3_wd, "DATA") == 0 &&
+                        pg_strcasecmp(prev2_wd, "WRAPPER") == 0)
+       {
+               static const char *const list_ALTER_FDW[] =
+               {"HANDLER", "VALIDATOR", "OPTIONS", "OWNER TO", NULL};
+
+               COMPLETE_WITH_LIST(list_ALTER_FDW);
+       }
+
+       /* ALTER FOREIGN TABLE <name> */
+       else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
+                        pg_strcasecmp(prev3_wd, "FOREIGN") == 0 &&
+                        pg_strcasecmp(prev2_wd, "TABLE") == 0)
+       {
+               static const char *const list_ALTER_FOREIGN_TABLE[] =
+               {"ALTER", "DROP", "RENAME", "OWNER TO", "SET SCHEMA", NULL};
+
+               COMPLETE_WITH_LIST(list_ALTER_FOREIGN_TABLE);
+       }
+
        /* ALTER INDEX <name> */
        else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
                         pg_strcasecmp(prev2_wd, "INDEX") == 0)
@@ -733,6 +1084,33 @@ psql_completion(char *text, int start, int end)
 
                COMPLETE_WITH_LIST(list_ALTERINDEX);
        }
+       /* ALTER INDEX <name> SET */
+       else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
+                        pg_strcasecmp(prev3_wd, "INDEX") == 0 &&
+                        pg_strcasecmp(prev_wd, "SET") == 0)
+       {
+               static const char *const list_ALTERINDEXSET[] =
+               {"(", "TABLESPACE", NULL};
+
+               COMPLETE_WITH_LIST(list_ALTERINDEXSET);
+       }
+       /* ALTER INDEX <name> RESET */
+       else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
+                        pg_strcasecmp(prev3_wd, "INDEX") == 0 &&
+                        pg_strcasecmp(prev_wd, "RESET") == 0)
+               COMPLETE_WITH_CONST("(");
+       /* ALTER INDEX <foo> SET|RESET ( */
+       else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
+                        pg_strcasecmp(prev4_wd, "INDEX") == 0 &&
+                        (pg_strcasecmp(prev2_wd, "SET") == 0 ||
+                         pg_strcasecmp(prev2_wd, "RESET") == 0) &&
+                        pg_strcasecmp(prev_wd, "(") == 0)
+       {
+               static const char *const list_INDEXOPTIONS[] =
+               {"fillfactor", "fastupdate", NULL};
+
+               COMPLETE_WITH_LIST(list_INDEXOPTIONS);
+       }
 
        /* ALTER LANGUAGE <name> */
        else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
@@ -744,20 +1122,58 @@ psql_completion(char *text, int start, int end)
                COMPLETE_WITH_LIST(list_ALTERLANGUAGE);
        }
 
+       /* ALTER LARGE OBJECT <oid> */
+       else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
+                        pg_strcasecmp(prev3_wd, "LARGE") == 0 &&
+                        pg_strcasecmp(prev2_wd, "OBJECT") == 0)
+       {
+               static const char *const list_ALTERLARGEOBJECT[] =
+               {"OWNER TO", NULL};
+
+               COMPLETE_WITH_LIST(list_ALTERLARGEOBJECT);
+       }
+
+       /* ALTER 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) &&
                         (pg_strcasecmp(prev2_wd, "USER") == 0 ||
                          pg_strcasecmp(prev2_wd, "ROLE") == 0))
        {
                static const char *const list_ALTERUSER[] =
-               {"ENCRYPTED", "UNENCRYPTED", "CREATEDB", "NOCREATEDB", "CREATEUSER",
-                       "NOCREATEUSER", "CREATEROLE", "NOCREATEROLE", "INHERIT", "NOINHERIT",
-                       "LOGIN", "NOLOGIN", "CONNECTION LIMIT", "VALID UNTIL", "RENAME TO",
-               "SUPERUSER", "NOSUPERUSER", "SET", "RESET", NULL};
+               {"CONNECTION LIMIT", "CREATEDB", "CREATEROLE", "CREATEUSER",
+                       "ENCRYPTED", "INHERIT", "LOGIN", "NOCREATEDB", "NOCREATEROLE",
+                       "NOCREATEUSER", "NOINHERIT", "NOLOGIN", "NOREPLICATION",
+                       "NOSUPERUSER", "RENAME TO", "REPLICATION", "RESET", "SET",
+               "SUPERUSER", "UNENCRYPTED", "VALID UNTIL", "WITH", NULL};
 
                COMPLETE_WITH_LIST(list_ALTERUSER);
        }
 
+       /* ALTER USER,ROLE <name> WITH */
+       else if ((pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
+                         (pg_strcasecmp(prev3_wd, "USER") == 0 ||
+                          pg_strcasecmp(prev3_wd, "ROLE") == 0) &&
+                         pg_strcasecmp(prev_wd, "WITH") == 0))
+       {
+               /* Similar to the above, but don't complete "WITH" again. */
+               static const char *const list_ALTERUSER_WITH[] =
+               {"CONNECTION LIMIT", "CREATEDB", "CREATEROLE", "CREATEUSER",
+                       "ENCRYPTED", "INHERIT", "LOGIN", "NOCREATEDB", "NOCREATEROLE",
+                       "NOCREATEUSER", "NOINHERIT", "NOLOGIN", "NOREPLICATION",
+                       "NOSUPERUSER", "RENAME TO", "REPLICATION", "RESET", "SET",
+               "SUPERUSER", "UNENCRYPTED", "VALID UNTIL", NULL};
+
+               COMPLETE_WITH_LIST(list_ALTERUSER_WITH);
+       }
+
        /* complete ALTER USER,ROLE <name> ENCRYPTED,UNENCRYPTED with PASSWORD */
        else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
                         (pg_strcasecmp(prev3_wd, "ROLE") == 0 || pg_strcasecmp(prev3_wd, "USER") == 0) &&
@@ -765,12 +1181,44 @@ psql_completion(char *text, int start, int end)
        {
                COMPLETE_WITH_CONST("PASSWORD");
        }
+       /* ALTER DEFAULT PRIVILEGES */
+       else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
+                        pg_strcasecmp(prev2_wd, "DEFAULT") == 0 &&
+                        pg_strcasecmp(prev_wd, "PRIVILEGES") == 0)
+       {
+               static const char *const list_ALTER_DEFAULT_PRIVILEGES[] =
+               {"FOR ROLE", "FOR USER", "IN SCHEMA", NULL};
+
+               COMPLETE_WITH_LIST(list_ALTER_DEFAULT_PRIVILEGES);
+       }
+       /* ALTER DEFAULT PRIVILEGES FOR */
+       else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
+                        pg_strcasecmp(prev3_wd, "DEFAULT") == 0 &&
+                        pg_strcasecmp(prev2_wd, "PRIVILEGES") == 0 &&
+                        pg_strcasecmp(prev_wd, "FOR") == 0)
+       {
+               static const char *const list_ALTER_DEFAULT_PRIVILEGES_FOR[] =
+               {"ROLE", "USER", NULL};
+
+               COMPLETE_WITH_LIST(list_ALTER_DEFAULT_PRIVILEGES_FOR);
+       }
+       /* ALTER DEFAULT PRIVILEGES { FOR ROLE ... | IN SCHEMA ... } */
+       else if (pg_strcasecmp(prev5_wd, "DEFAULT") == 0 &&
+                        pg_strcasecmp(prev4_wd, "PRIVILEGES") == 0 &&
+                        (pg_strcasecmp(prev3_wd, "FOR") == 0 ||
+                         pg_strcasecmp(prev3_wd, "IN") == 0))
+       {
+               static const char *const list_ALTER_DEFAULT_PRIVILEGES_REST[] =
+               {"GRANT", "REVOKE", NULL};
+
+               COMPLETE_WITH_LIST(list_ALTER_DEFAULT_PRIVILEGES_REST);
+       }
        /* ALTER DOMAIN <name> */
        else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
                         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);
        }
@@ -784,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 &&
@@ -800,7 +1275,7 @@ psql_completion(char *text, int start, int end)
        {
                static const char *const list_ALTERSEQUENCE[] =
                {"INCREMENT", "MINVALUE", "MAXVALUE", "RESTART", "NO", "CACHE", "CYCLE",
-               "SET SCHEMA", "OWNED BY", "RENAME TO", NULL};
+               "SET SCHEMA", "OWNED BY", "OWNER TO", "RENAME TO", NULL};
 
                COMPLETE_WITH_LIST(list_ALTERSEQUENCE);
        }
@@ -814,14 +1289,54 @@ psql_completion(char *text, int start, int end)
 
                COMPLETE_WITH_LIST(list_ALTERSEQUENCE2);
        }
+       /* ALTER SERVER <name> */
+       else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
+                        pg_strcasecmp(prev2_wd, "SERVER") == 0)
+       {
+               static const char *const list_ALTER_SERVER[] =
+               {"VERSION", "OPTIONS", "OWNER TO", NULL};
+
+               COMPLETE_WITH_LIST(list_ALTER_SERVER);
+       }
        /* ALTER VIEW <name> */
        else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
                         pg_strcasecmp(prev2_wd, "VIEW") == 0)
        {
-               static const char *const list_ALTERVIEW[] = {"RENAME TO", NULL};
+               static const char *const list_ALTERVIEW[] =
+               {"ALTER COLUMN", "OWNER TO", "RENAME TO", "SET SCHEMA", NULL};
+
+               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");
 
-               COMPLETE_WITH_LIST(list_ALTERVIEW);
-       }
        /* ALTER TRIGGER <name>, add ON */
        else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
                         pg_strcasecmp(prev2_wd, "TRIGGER") == 0)
@@ -848,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);
        }
@@ -880,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)
@@ -890,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
@@ -909,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");
 
@@ -928,24 +1466,86 @@ 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) ||
                         (pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
                          pg_strcasecmp(prev2_wd, "ALTER") == 0))
        {
-               /* DROP ... does not work well yet */
                static const char *const list_COLUMNALTER[] =
-               {"TYPE", "SET DEFAULT", "DROP DEFAULT", "SET NOT NULL",
-               "DROP NOT NULL", "SET STATISTICS", "SET STORAGE", NULL};
+               {"TYPE", "SET", "RESET", "DROP", NULL};
 
                COMPLETE_WITH_LIST(list_COLUMNALTER);
        }
+       /* ALTER TABLE ALTER [COLUMN] <foo> SET */
+       else if (((pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
+                          pg_strcasecmp(prev3_wd, "COLUMN") == 0) ||
+                         (pg_strcasecmp(prev5_wd, "TABLE") == 0 &&
+                          pg_strcasecmp(prev3_wd, "ALTER") == 0)) &&
+                        pg_strcasecmp(prev_wd, "SET") == 0)
+       {
+               static const char *const list_COLUMNSET[] =
+               {"(", "DEFAULT", "NOT NULL", "STATISTICS", "STORAGE", NULL};
+
+               COMPLETE_WITH_LIST(list_COLUMNSET);
+       }
+       /* ALTER TABLE ALTER [COLUMN] <foo> SET ( */
+       else if (((pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
+                          pg_strcasecmp(prev4_wd, "COLUMN") == 0) ||
+                         pg_strcasecmp(prev4_wd, "ALTER") == 0) &&
+                        pg_strcasecmp(prev2_wd, "SET") == 0 &&
+                        pg_strcasecmp(prev_wd, "(") == 0)
+       {
+               static const char *const list_COLUMNOPTIONS[] =
+               {"n_distinct", "n_distinct_inherited", NULL};
+
+               COMPLETE_WITH_LIST(list_COLUMNOPTIONS);
+       }
+       /* ALTER TABLE ALTER [COLUMN] <foo> SET STORAGE */
+       else if (((pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
+                          pg_strcasecmp(prev4_wd, "COLUMN") == 0) ||
+                         pg_strcasecmp(prev4_wd, "ALTER") == 0) &&
+                        pg_strcasecmp(prev2_wd, "SET") == 0 &&
+                        pg_strcasecmp(prev_wd, "STORAGE") == 0)
+       {
+               static const char *const list_COLUMNSTORAGE[] =
+               {"PLAIN", "EXTERNAL", "EXTENDED", "MAIN", NULL};
+
+               COMPLETE_WITH_LIST(list_COLUMNSTORAGE);
+       }
+       /* ALTER TABLE ALTER [COLUMN] <foo> DROP */
+       else if (((pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
+                          pg_strcasecmp(prev3_wd, "COLUMN") == 0) ||
+                         (pg_strcasecmp(prev5_wd, "TABLE") == 0 &&
+                          pg_strcasecmp(prev3_wd, "ALTER") == 0)) &&
+                        pg_strcasecmp(prev_wd, "DROP") == 0)
+       {
+               static const char *const list_COLUMNDROP[] =
+               {"DEFAULT", "NOT NULL", NULL};
+
+               COMPLETE_WITH_LIST(list_COLUMNDROP);
+       }
        else if (pg_strcasecmp(prev3_wd, "TABLE") == 0 &&
                         pg_strcasecmp(prev_wd, "CLUSTER") == 0)
                COMPLETE_WITH_CONST("ON");
@@ -961,7 +1561,7 @@ psql_completion(char *text, int start, int end)
                         pg_strcasecmp(prev_wd, "SET") == 0)
        {
                static const char *const list_TABLESET[] =
-               {"WITHOUT", "TABLESPACE", "SCHEMA", NULL};
+               {"(", "WITHOUT", "TABLESPACE", "SCHEMA", NULL};
 
                COMPLETE_WITH_LIST(list_TABLESET);
        }
@@ -980,15 +1580,71 @@ psql_completion(char *text, int start, int end)
 
                COMPLETE_WITH_LIST(list_TABLESET2);
        }
-       /* we have ALTER TABLESPACE, so suggest RENAME TO, OWNER TO */
+       /* ALTER TABLE <foo> RESET */
+       else if (pg_strcasecmp(prev3_wd, "TABLE") == 0 &&
+                        pg_strcasecmp(prev_wd, "RESET") == 0)
+               COMPLETE_WITH_CONST("(");
+       /* ALTER TABLE <foo> SET|RESET ( */
+       else if (pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
+                        (pg_strcasecmp(prev2_wd, "SET") == 0 ||
+                         pg_strcasecmp(prev2_wd, "RESET") == 0) &&
+                        pg_strcasecmp(prev_wd, "(") == 0)
+       {
+               static const char *const list_TABLEOPTIONS[] =
+               {
+                       "autovacuum_analyze_scale_factor",
+                       "autovacuum_analyze_threshold",
+                       "autovacuum_enabled",
+                       "autovacuum_freeze_max_age",
+                       "autovacuum_freeze_min_age",
+                       "autovacuum_freeze_table_age",
+                       "autovacuum_vacuum_cost_delay",
+                       "autovacuum_vacuum_cost_limit",
+                       "autovacuum_vacuum_scale_factor",
+                       "autovacuum_vacuum_threshold",
+                       "fillfactor",
+                       "toast.autovacuum_enabled",
+                       "toast.autovacuum_freeze_max_age",
+                       "toast.autovacuum_freeze_min_age",
+                       "toast.autovacuum_freeze_table_age",
+                       "toast.autovacuum_vacuum_cost_delay",
+                       "toast.autovacuum_vacuum_cost_limit",
+                       "toast.autovacuum_vacuum_scale_factor",
+                       "toast.autovacuum_vacuum_threshold",
+                       NULL
+               };
+
+               COMPLETE_WITH_LIST(list_TABLEOPTIONS);
+       }
+
+       /* ALTER TABLESPACE <foo> with RENAME TO, OWNER TO, SET, RESET */
        else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
                         pg_strcasecmp(prev2_wd, "TABLESPACE") == 0)
        {
                static const char *const list_ALTERTSPC[] =
-               {"RENAME TO", "OWNER TO", NULL};
+               {"RENAME TO", "OWNER TO", "SET", "RESET", NULL};
 
                COMPLETE_WITH_LIST(list_ALTERTSPC);
        }
+       /* ALTER TABLESPACE <foo> SET|RESET */
+       else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
+                        pg_strcasecmp(prev3_wd, "TABLESPACE") == 0 &&
+                        (pg_strcasecmp(prev_wd, "SET") == 0 ||
+                         pg_strcasecmp(prev_wd, "RESET") == 0))
+               COMPLETE_WITH_CONST("(");
+       /* ALTER TABLESPACE <foo> SET|RESET ( */
+       else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
+                        pg_strcasecmp(prev4_wd, "TABLESPACE") == 0 &&
+                        (pg_strcasecmp(prev2_wd, "SET") == 0 ||
+                         pg_strcasecmp(prev2_wd, "RESET") == 0) &&
+                        pg_strcasecmp(prev_wd, "(") == 0)
+       {
+               static const char *const list_TABLESPACEOPTIONS[] =
+               {"seq_page_cost", "random_page_cost", NULL};
+
+               COMPLETE_WITH_LIST(list_TABLESPACEOPTIONS);
+       }
+
        /* ALTER TEXT SEARCH */
        else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
                         pg_strcasecmp(prev2_wd, "TEXT") == 0 &&
@@ -1004,17 +1660,22 @@ psql_completion(char *text, int start, int end)
                         pg_strcasecmp(prev3_wd, "SEARCH") == 0 &&
                         (pg_strcasecmp(prev2_wd, "TEMPLATE") == 0 ||
                          pg_strcasecmp(prev2_wd, "PARSER") == 0))
-               COMPLETE_WITH_CONST("RENAME TO");
+       {
+               static const char *const list_ALTERTEXTSEARCH2[] =
+               {"RENAME TO", "SET SCHEMA", NULL};
+
+               COMPLETE_WITH_LIST(list_ALTERTEXTSEARCH2);
+       }
 
        else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
                         pg_strcasecmp(prev4_wd, "TEXT") == 0 &&
                         pg_strcasecmp(prev3_wd, "SEARCH") == 0 &&
                         pg_strcasecmp(prev2_wd, "DICTIONARY") == 0)
        {
-               static const char *const list_ALTERTEXTSEARCH2[] =
-               {"OWNER TO", "RENAME TO", NULL};
+               static const char *const list_ALTERTEXTSEARCH3[] =
+               {"OWNER TO", "RENAME TO", "SET SCHEMA", NULL};
 
-               COMPLETE_WITH_LIST(list_ALTERTEXTSEARCH2);
+               COMPLETE_WITH_LIST(list_ALTERTEXTSEARCH3);
        }
 
        else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
@@ -1022,21 +1683,64 @@ psql_completion(char *text, int start, int end)
                         pg_strcasecmp(prev3_wd, "SEARCH") == 0 &&
                         pg_strcasecmp(prev2_wd, "CONFIGURATION") == 0)
        {
-               static const char *const list_ALTERTEXTSEARCH3[] =
-               {"ADD MAPPING FOR", "ALTER MAPPING", "DROP MAPPING FOR", "OWNER TO", "RENAME TO", NULL};
+               static const char *const list_ALTERTEXTSEARCH4[] =
+               {"ADD MAPPING FOR", "ALTER MAPPING", "DROP MAPPING FOR", "OWNER TO", "RENAME TO", "SET SCHEMA", NULL};
 
-               COMPLETE_WITH_LIST(list_ALTERTEXTSEARCH3);
+               COMPLETE_WITH_LIST(list_ALTERTEXTSEARCH4);
        }
 
-       /* complete ALTER TYPE <foo> with OWNER TO, SET SCHEMA */
+       /* complete ALTER TYPE <foo> with actions */
        else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
                         pg_strcasecmp(prev2_wd, "TYPE") == 0)
        {
                static const char *const list_ALTERTYPE[] =
-               {"OWNER TO", "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      */
+       else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
+                        pg_strcasecmp(prev3_wd, "TYPE") == 0 &&
+                        pg_strcasecmp(prev_wd, "RENAME") == 0)
+       {
+               static const char *const list_ALTERTYPE[] =
+               {"ATTRIBUTE", "TO", NULL};
 
                COMPLETE_WITH_LIST(list_ALTERTYPE);
        }
+       /* ALTER TYPE xxx RENAME ATTRIBUTE yyy */
+       else if (pg_strcasecmp(prev5_wd, "TYPE") == 0 &&
+                        pg_strcasecmp(prev3_wd, "RENAME") == 0 &&
+                        pg_strcasecmp(prev2_wd, "ATTRIBUTE") == 0)
+               COMPLETE_WITH_CONST("TO");
+
+       /*
+        * 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 ||
+                         pg_strcasecmp(prev2_wd, "RENAME") == 0) &&
+                        pg_strcasecmp(prev_wd, "ATTRIBUTE") == 0)
+               COMPLETE_WITH_ATTR(prev3_wd, "");
+       /* ALTER TYPE ALTER ATTRIBUTE <foo> */
+       else if ((pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
+                         pg_strcasecmp(prev2_wd, "ATTRIBUTE") == 0))
+       {
+               COMPLETE_WITH_CONST("TYPE");
+       }
        /* complete ALTER GROUP <foo> */
        else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
                         pg_strcasecmp(prev2_wd, "GROUP") == 0)
@@ -1091,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");
        }
@@ -1113,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");
@@ -1120,13 +1850,24 @@ psql_completion(char *text, int start, int end)
                         pg_strcasecmp(prev_wd, "ON") == 0)
        {
                static const char *const list_COMMENT[] =
-               {"CAST", "CONVERSION", "DATABASE", "INDEX", "LANGUAGE", "RULE", "SCHEMA",
-                       "SEQUENCE", "TABLE", "TYPE", "VIEW", "COLUMN", "AGGREGATE", "FUNCTION",
+               {"CAST", "COLLATION", "CONVERSION", "DATABASE", "EXTENSION",
+                       "FOREIGN DATA WRAPPER", "FOREIGN TABLE",
+                       "SERVER", "INDEX", "LANGUAGE", "RULE", "SCHEMA", "SEQUENCE",
+                       "TABLE", "TYPE", "VIEW", "MATERIALIZED VIEW", "COLUMN", "AGGREGATE", "FUNCTION",
                        "OPERATOR", "TRIGGER", "CONSTRAINT", "DOMAIN", "LARGE OBJECT",
                "TABLESPACE", "TEXT SEARCH", "ROLE", NULL};
 
                COMPLETE_WITH_LIST(list_COMMENT);
        }
+       else if (pg_strcasecmp(prev3_wd, "COMMENT") == 0 &&
+                        pg_strcasecmp(prev2_wd, "ON") == 0 &&
+                        pg_strcasecmp(prev_wd, "FOREIGN") == 0)
+       {
+               static const char *const list_TRANS2[] =
+               {"DATA WRAPPER", "TABLE", NULL};
+
+               COMPLETE_WITH_LIST(list_TRANS2);
+       }
        else if (pg_strcasecmp(prev4_wd, "COMMENT") == 0 &&
                         pg_strcasecmp(prev3_wd, "ON") == 0 &&
                         pg_strcasecmp(prev2_wd, "TEXT") == 0 &&
@@ -1137,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 */
@@ -1171,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 ||
@@ -1181,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);
        }
@@ -1192,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);
        }
@@ -1213,49 +1985,103 @@ psql_completion(char *text, int start, int end)
                         pg_strcasecmp(prev_wd, "TEMPLATE") == 0)
                COMPLETE_WITH_QUERY(Query_for_list_of_template_databases);
 
+       /* CREATE EXTENSION */
+       /* Complete with available extensions rather than installed ones. */
+       else if (pg_strcasecmp(prev2_wd, "CREATE") == 0 &&
+                        pg_strcasecmp(prev_wd, "EXTENSION") == 0)
+               COMPLETE_WITH_QUERY(Query_for_list_of_available_extensions);
+       /* CREATE EXTENSION <name> */
+       else if (pg_strcasecmp(prev3_wd, "CREATE") == 0 &&
+                        pg_strcasecmp(prev2_wd, "EXTENSION") == 0)
+               COMPLETE_WITH_CONST("WITH SCHEMA");
+
+       /* CREATE FOREIGN */
+       else if (pg_strcasecmp(prev2_wd, "CREATE") == 0 &&
+                        pg_strcasecmp(prev_wd, "FOREIGN") == 0)
+       {
+               static const char *const list_CREATE_FOREIGN[] =
+               {"DATA WRAPPER", "TABLE", NULL};
+
+               COMPLETE_WITH_LIST(list_CREATE_FOREIGN);
+       }
+
+       /* CREATE FOREIGN DATA WRAPPER */
+       else if (pg_strcasecmp(prev5_wd, "CREATE") == 0 &&
+                        pg_strcasecmp(prev4_wd, "FOREIGN") == 0 &&
+                        pg_strcasecmp(prev3_wd, "DATA") == 0 &&
+                        pg_strcasecmp(prev2_wd, "WRAPPER") == 0)
+       {
+               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" */
        else if (pg_strcasecmp(prev2_wd, "CREATE") == 0 &&
                         pg_strcasecmp(prev_wd, "UNIQUE") == 0)
                COMPLETE_WITH_CONST("INDEX");
-       /* If we have CREATE|UNIQUE INDEX <sth>, then add "ON" */
-       else if (pg_strcasecmp(prev2_wd, "INDEX") == 0 &&
-                        (pg_strcasecmp(prev3_wd, "CREATE") == 0 ||
-                         pg_strcasecmp(prev3_wd, "UNIQUE") == 0))
-               COMPLETE_WITH_CONST("ON");
-       /* Complete ... INDEX <name> ON with a list of tables  */
-       else if (pg_strcasecmp(prev3_wd, "INDEX") == 0 &&
+       /* If we have CREATE|UNIQUE INDEX, then add "ON" and existing indexes */
+       else if (pg_strcasecmp(prev_wd, "INDEX") == 0 &&
+                        (pg_strcasecmp(prev2_wd, "CREATE") == 0 ||
+                         pg_strcasecmp(prev2_wd, "UNIQUE") == 0))
+               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
+                                                                  " UNION SELECT 'ON'"
+                                                                  " UNION SELECT 'CONCURRENTLY'");
+       /* Complete ... INDEX [<name>] ON with a list of tables  */
+       else if ((pg_strcasecmp(prev3_wd, "INDEX") == 0 ||
+                         pg_strcasecmp(prev2_wd, "INDEX") == 0 ||
+                         pg_strcasecmp(prev2_wd, "CONCURRENTLY") == 0) &&
                         pg_strcasecmp(prev_wd, "ON") == 0)
-               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+               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) &&
+                        pg_strcasecmp(prev_wd, "CONCURRENTLY") == 0)
+               COMPLETE_WITH_CONST("ON");
+       /* If we have CREATE|UNIQUE INDEX <sth>, then add "ON" or "CONCURRENTLY" */
+       else if ((pg_strcasecmp(prev3_wd, "CREATE") == 0 ||
+                         pg_strcasecmp(prev3_wd, "UNIQUE") == 0) &&
+                        pg_strcasecmp(prev2_wd, "INDEX") == 0)
+       {
+               static const char *const list_CREATE_INDEX[] =
+               {"CONCURRENTLY", "ON", NULL};
+
+               COMPLETE_WITH_LIST(list_CREATE_INDEX);
+       }
 
        /*
         * Complete INDEX <name> ON <table> with a list of table columns (which
         * should really be in parens)
         */
-       else if (pg_strcasecmp(prev4_wd, "INDEX") == 0 &&
+       else if ((pg_strcasecmp(prev4_wd, "INDEX") == 0 ||
+                         pg_strcasecmp(prev3_wd, "INDEX") == 0 ||
+                         pg_strcasecmp(prev3_wd, "CONCURRENTLY") == 0) &&
                         pg_strcasecmp(prev2_wd, "ON") == 0)
        {
-               if (find_open_parenthesis(end))
-                       COMPLETE_WITH_ATTR(prev_wd, "");
-               else
-                       COMPLETE_WITH_CONST("(");
+               static const char *const list_CREATE_INDEX2[] =
+               {"(", "USING", NULL};
+
+               COMPLETE_WITH_LIST(list_CREATE_INDEX2);
        }
-       else if (pg_strcasecmp(prev5_wd, "INDEX") == 0 &&
+       else if ((pg_strcasecmp(prev5_wd, "INDEX") == 0 ||
+                         pg_strcasecmp(prev4_wd, "INDEX") == 0 ||
+                         pg_strcasecmp(prev4_wd, "CONCURRENTLY") == 0) &&
                         pg_strcasecmp(prev3_wd, "ON") == 0 &&
                         pg_strcasecmp(prev_wd, "(") == 0)
                COMPLETE_WITH_ATTR(prev2_wd, "");
        /* same if you put in USING */
-       else if (pg_strcasecmp(prev4_wd, "ON") == 0 &&
-                        pg_strcasecmp(prev2_wd, "USING") == 0)
-               COMPLETE_WITH_ATTR(prev3_wd, "");
+       else if (pg_strcasecmp(prev5_wd, "ON") == 0 &&
+                        pg_strcasecmp(prev3_wd, "USING") == 0 &&
+                        pg_strcasecmp(prev_wd, "(") == 0)
+               COMPLETE_WITH_ATTR(prev4_wd, "");
        /* Complete USING with an index method */
        else if (pg_strcasecmp(prev_wd, "USING") == 0)
-       {
-               static const char *const index_mth[] =
-               {"BTREE", "HASH", "GIN", "GIST", NULL};
-
-               COMPLETE_WITH_LIST(index_mth);
-       }
+               COMPLETE_WITH_QUERY(Query_for_list_of_access_methods);
+       else if (pg_strcasecmp(prev4_wd, "ON") == 0 &&
+                        pg_strcasecmp(prev2_wd, "USING") == 0)
+               COMPLETE_WITH_CONST("(");
 
 /* CREATE RULE */
        /* Complete "CREATE RULE <sth>" with "AS" */
@@ -1289,6 +2115,16 @@ psql_completion(char *text, int start, int end)
                         pg_strcasecmp(prev_wd, "TO") == 0)
                COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
 
+/* CREATE SERVER <name> */
+       else if (pg_strcasecmp(prev3_wd, "CREATE") == 0 &&
+                        pg_strcasecmp(prev2_wd, "SERVER") == 0)
+       {
+               static const char *const list_CREATE_SERVER[] =
+               {"TYPE", "VERSION", "FOREIGN DATA WRAPPER", NULL};
+
+               COMPLETE_WITH_LIST(list_CREATE_SERVER);
+       }
+
 /* CREATE TABLE */
        /* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */
        else if (pg_strcasecmp(prev2_wd, "CREATE") == 0 &&
@@ -1300,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 &&
@@ -1339,15 +2184,40 @@ psql_completion(char *text, int start, int end)
                         pg_strcasecmp(prev2_wd, "TRIGGER") == 0)
        {
                static const char *const list_CREATETRIGGER[] =
-               {"BEFORE", "AFTER", NULL};
+               {"BEFORE", "AFTER", "INSTEAD OF", NULL};
 
                COMPLETE_WITH_LIST(list_CREATETRIGGER);
        }
-       /* complete CREATE TRIGGER <name> BEFORE,AFTER sth with OR,ON */
+       /* complete CREATE TRIGGER <name> BEFORE,AFTER with an event */
+       else if (pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
+                        pg_strcasecmp(prev3_wd, "TRIGGER") == 0 &&
+                        (pg_strcasecmp(prev_wd, "BEFORE") == 0 ||
+                         pg_strcasecmp(prev_wd, "AFTER") == 0))
+       {
+               static const char *const list_CREATETRIGGER_EVENTS[] =
+               {"INSERT", "DELETE", "UPDATE", "TRUNCATE", NULL};
+
+               COMPLETE_WITH_LIST(list_CREATETRIGGER_EVENTS);
+       }
+       /* complete CREATE TRIGGER <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};
@@ -1355,20 +2225,59 @@ psql_completion(char *text, int start, int end)
                COMPLETE_WITH_LIST(list_CREATETRIGGER2);
        }
 
-/* CREATE ROLE,USER,GROUP */
+       /*
+        * complete CREATE TRIGGER <name> BEFORE,AFTER event ON with a list of
+        * tables
+        */
+       else if (pg_strcasecmp(prev5_wd, "TRIGGER") == 0 &&
+                        (pg_strcasecmp(prev3_wd, "BEFORE") == 0 ||
+                         pg_strcasecmp(prev3_wd, "AFTER") == 0) &&
+                        pg_strcasecmp(prev_wd, "ON") == 0)
+               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+       /* complete CREATE TRIGGER ... 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 &&
+                        prev2_wd[0] != '\0')
+               COMPLETE_WITH_CONST("PROCEDURE");
+
+/* 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 ||
                          pg_strcasecmp(prev2_wd, "GROUP") == 0 || pg_strcasecmp(prev2_wd, "USER") == 0))
        {
                static const char *const list_CREATEROLE[] =
                {"ADMIN", "CONNECTION LIMIT", "CREATEDB", "CREATEROLE", "CREATEUSER",
-                       "ENCRYPTED", "IN", "INHERIT", "LOGIN", "NOINHERIT", "NOLOGIN", "NOCREATEDB",
-                       "NOCREATEROLE", "NOCREATEUSER", "NOSUPERUSER", "ROLE", "SUPERUSER", "SYSID",
-               "UNENCRYPTED", NULL};
+                       "ENCRYPTED", "IN", "INHERIT", "LOGIN", "NOCREATEDB",
+                       "NOCREATEROLE", "NOCREATEUSER", "NOINHERIT", "NOLOGIN",
+                       "NOREPLICATION", "NOSUPERUSER", "REPLICATION", "ROLE",
+               "SUPERUSER", "SYSID", "UNENCRYPTED", "VALID UNTIL", "WITH", NULL};
 
                COMPLETE_WITH_LIST(list_CREATEROLE);
        }
 
+/* CREATE ROLE,USER,GROUP <name> WITH */
+       else if ((pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
+                         (pg_strcasecmp(prev3_wd, "ROLE") == 0 ||
+                          pg_strcasecmp(prev3_wd, "GROUP") == 0 ||
+                          pg_strcasecmp(prev3_wd, "USER") == 0) &&
+                         pg_strcasecmp(prev_wd, "WITH") == 0))
+       {
+               /* Similar to the above, but don't complete "WITH" again. */
+               static const char *const list_CREATEROLE_WITH[] =
+               {"ADMIN", "CONNECTION LIMIT", "CREATEDB", "CREATEROLE", "CREATEUSER",
+                       "ENCRYPTED", "IN", "INHERIT", "LOGIN", "NOCREATEDB",
+                       "NOCREATEROLE", "NOCREATEUSER", "NOINHERIT", "NOLOGIN",
+                       "NOREPLICATION", "NOSUPERUSER", "REPLICATION", "ROLE",
+               "SUPERUSER", "SYSID", "UNENCRYPTED", "VALID UNTIL", NULL};
+
+               COMPLETE_WITH_LIST(list_CREATEROLE_WITH);
+       }
+
        /*
         * complete CREATE ROLE,USER,GROUP <name> ENCRYPTED,UNENCRYPTED with
         * PASSWORD
@@ -1403,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)
        {
@@ -1412,6 +2337,7 @@ psql_completion(char *text, int start, int end)
                COMPLETE_WITH_LIST(list_DECLARE);
        }
 
+/* CURSOR */
        else if (pg_strcasecmp(prev_wd, "CURSOR") == 0)
        {
                static const char *const list_DECLARECURSOR[] =
@@ -1436,7 +2362,7 @@ psql_completion(char *text, int start, int end)
        /* Complete DELETE FROM with a list of tables */
        else if (pg_strcasecmp(prev2_wd, "DELETE") == 0 &&
                         pg_strcasecmp(prev_wd, "FROM") == 0)
-               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_deletables, NULL);
        /* Complete DELETE FROM <table> */
        else if (pg_strcasecmp(prev3_wd, "DELETE") == 0 &&
                         pg_strcasecmp(prev2_wd, "FROM") == 0)
@@ -1452,11 +2378,24 @@ 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);
        }
 
+/* DO */
+
+       /*
+        * Complete DO with LANGUAGE.
+        */
+       else if (pg_strcasecmp(prev_wd, "DO") == 0)
+       {
+               static const char *const list_DO[] =
+               {"LANGUAGE", NULL};
+
+               COMPLETE_WITH_LIST(list_DO);
+       }
+
 /* DROP (when not the previous word) */
        /* DROP AGGREGATE */
        else if (pg_strcasecmp(prev3_wd, "DROP") == 0 &&
@@ -1465,19 +2404,26 @@ psql_completion(char *text, int start, int end)
 
        /* DROP object with CASCADE / RESTRICT */
        else if ((pg_strcasecmp(prev3_wd, "DROP") == 0 &&
-                         (pg_strcasecmp(prev2_wd, "CONVERSION") == 0 ||
+                         (pg_strcasecmp(prev2_wd, "COLLATION") == 0 ||
+                          pg_strcasecmp(prev2_wd, "CONVERSION") == 0 ||
                           pg_strcasecmp(prev2_wd, "DOMAIN") == 0 ||
+                          pg_strcasecmp(prev2_wd, "EXTENSION") == 0 ||
                           pg_strcasecmp(prev2_wd, "FUNCTION") == 0 ||
                           pg_strcasecmp(prev2_wd, "INDEX") == 0 ||
                           pg_strcasecmp(prev2_wd, "LANGUAGE") == 0 ||
                           pg_strcasecmp(prev2_wd, "SCHEMA") == 0 ||
                           pg_strcasecmp(prev2_wd, "SEQUENCE") == 0 ||
+                          pg_strcasecmp(prev2_wd, "SERVER") == 0 ||
                           pg_strcasecmp(prev2_wd, "TABLE") == 0 ||
                           pg_strcasecmp(prev2_wd, "TYPE") == 0 ||
                           pg_strcasecmp(prev2_wd, "VIEW") == 0)) ||
                         (pg_strcasecmp(prev4_wd, "DROP") == 0 &&
                          pg_strcasecmp(prev3_wd, "AGGREGATE") == 0 &&
                          prev_wd[strlen(prev_wd) - 1] == ')') ||
+                        (pg_strcasecmp(prev5_wd, "DROP") == 0 &&
+                         pg_strcasecmp(prev4_wd, "FOREIGN") == 0 &&
+                         pg_strcasecmp(prev3_wd, "DATA") == 0 &&
+                         pg_strcasecmp(prev2_wd, "WRAPPER") == 0) ||
                         (pg_strcasecmp(prev5_wd, "DROP") == 0 &&
                          pg_strcasecmp(prev4_wd, "TEXT") == 0 &&
                          pg_strcasecmp(prev3_wd, "SEARCH") == 0 &&
@@ -1487,21 +2433,10 @@ psql_completion(char *text, int start, int end)
                           pg_strcasecmp(prev2_wd, "TEMPLATE") == 0))
                )
        {
-               if ((pg_strcasecmp(prev3_wd, "DROP") == 0) && (pg_strcasecmp(prev2_wd, "FUNCTION") == 0))
+               if (pg_strcasecmp(prev3_wd, "DROP") == 0 &&
+                       pg_strcasecmp(prev2_wd, "FUNCTION") == 0)
                {
-                       if (find_open_parenthesis(end))
-                       {
-                               static const char func_args_query[] = "select pg_catalog.oidvectortypes(proargtypes)||')' from pg_proc where proname='%s'";
-                               char       *tmp_buf = malloc(strlen(func_args_query) + strlen(prev_wd));
-
-                               sprintf(tmp_buf, func_args_query, prev_wd);
-                               COMPLETE_WITH_QUERY(tmp_buf);
-                               free(tmp_buf);
-                       }
-                       else
-                       {
-                               COMPLETE_WITH_CONST("(");
-                       }
+                       COMPLETE_WITH_CONST("(");
                }
                else
                {
@@ -1511,17 +2446,33 @@ psql_completion(char *text, int start, int end)
                        COMPLETE_WITH_LIST(list_DROPCR);
                }
        }
-       else if (pg_strcasecmp(prev4_wd, "DROP") == 0 &&
-                        pg_strcasecmp(prev3_wd, "FUNCTION") == 0 &&
-                        pg_strcasecmp(prev_wd, "(") == 0)
+       else if (pg_strcasecmp(prev2_wd, "DROP") == 0 &&
+                        pg_strcasecmp(prev_wd, "FOREIGN") == 0)
        {
-               static const char func_args_query[] = "select pg_catalog.oidvectortypes(proargtypes)||')' from pg_proc where proname='%s'";
-               char       *tmp_buf = malloc(strlen(func_args_query) + strlen(prev2_wd));
+               static const char *const drop_CREATE_FOREIGN[] =
+               {"DATA WRAPPER", "TABLE", NULL};
+
+               COMPLETE_WITH_LIST(drop_CREATE_FOREIGN);
+       }
 
-               sprintf(tmp_buf, func_args_query, prev2_wd);
-               COMPLETE_WITH_QUERY(tmp_buf);
-               free(tmp_buf);
+       /* 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)
+               COMPLETE_WITH_FUNCTION_ARG(prev2_wd);
        /* DROP OWNED BY */
        else if (pg_strcasecmp(prev2_wd, "DROP") == 0 &&
                         pg_strcasecmp(prev_wd, "OWNED") == 0)
@@ -1541,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 */
 
        /*
@@ -1561,11 +2517,11 @@ psql_completion(char *text, int start, int end)
 
                COMPLETE_WITH_LIST(list_EXPLAIN);
        }
-       else if (pg_strcasecmp(prev_wd, "VERBOSE") == 0 &&
-                        pg_strcasecmp(prev3_wd, "VACUUM") != 0 &&
-                        pg_strcasecmp(prev4_wd, "VACUUM") != 0 &&
-                        (pg_strcasecmp(prev2_wd, "ANALYZE") == 0 ||
-                         pg_strcasecmp(prev2_wd, "EXPLAIN") == 0))
+       else if ((pg_strcasecmp(prev2_wd, "EXPLAIN") == 0 &&
+                         pg_strcasecmp(prev_wd, "VERBOSE") == 0) ||
+                        (pg_strcasecmp(prev3_wd, "EXPLAIN") == 0 &&
+                         pg_strcasecmp(prev2_wd, "ANALYZE") == 0 &&
+                         pg_strcasecmp(prev_wd, "VERBOSE") == 0))
        {
                static const char *const list_EXPLAIN[] =
                {"SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE", NULL};
@@ -1607,22 +2563,71 @@ psql_completion(char *text, int start, int end)
                COMPLETE_WITH_LIST(list_FROMIN);
        }
 
-/* GRANT && REVOKE*/
-       /* Complete GRANT/REVOKE with a list of privileges */
+/* FOREIGN DATA WRAPPER */
+       /* applies in ALTER/DROP FDW and in CREATE SERVER */
+       else if (pg_strcasecmp(prev4_wd, "CREATE") != 0 &&
+                        pg_strcasecmp(prev3_wd, "FOREIGN") == 0 &&
+                        pg_strcasecmp(prev2_wd, "DATA") == 0 &&
+                        pg_strcasecmp(prev_wd, "WRAPPER") == 0)
+               COMPLETE_WITH_QUERY(Query_for_list_of_fdws);
+
+/* FOREIGN TABLE */
+       else if (pg_strcasecmp(prev3_wd, "CREATE") != 0 &&
+                        pg_strcasecmp(prev2_wd, "FOREIGN") == 0 &&
+                        pg_strcasecmp(prev_wd, "TABLE") == 0)
+               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, NULL);
+
+/* GRANT && REVOKE */
+       /* Complete GRANT/REVOKE with a list of roles and privileges */
        else if (pg_strcasecmp(prev_wd, "GRANT") == 0 ||
                         pg_strcasecmp(prev_wd, "REVOKE") == 0)
        {
-               static const char *const list_privileg[] =
-               {"SELECT", "INSERT", "UPDATE", "DELETE", "TRUNCATE", "REFERENCES",
-                "TRIGGER", "CREATE", "CONNECT", "TEMPORARY", "EXECUTE", "USAGE",
-                "ALL", NULL};
-
-               COMPLETE_WITH_LIST(list_privileg);
+               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,
@@ -1638,20 +2643,37 @@ psql_completion(char *text, int start, int end)
        else if ((pg_strcasecmp(prev3_wd, "GRANT") == 0 ||
                          pg_strcasecmp(prev3_wd, "REVOKE") == 0) &&
                         pg_strcasecmp(prev_wd, "ON") == 0)
-               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsv,
+               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf,
                                                                   " UNION SELECT 'DATABASE'"
+                                                                  " UNION SELECT 'DOMAIN'"
+                                                                  " UNION SELECT 'FOREIGN DATA WRAPPER'"
+                                                                  " UNION SELECT 'FOREIGN SERVER'"
                                                                   " UNION SELECT 'FUNCTION'"
                                                                   " UNION SELECT 'LANGUAGE'"
+                                                                  " UNION SELECT 'LARGE OBJECT'"
                                                                   " UNION SELECT 'SCHEMA'"
-                                                                  " UNION SELECT 'TABLESPACE'");
+                                                                  " UNION SELECT 'TABLESPACE'"
+                                                                  " UNION SELECT 'TYPE'");
+       else if ((pg_strcasecmp(prev4_wd, "GRANT") == 0 ||
+                         pg_strcasecmp(prev4_wd, "REVOKE") == 0) &&
+                        pg_strcasecmp(prev2_wd, "ON") == 0 &&
+                        pg_strcasecmp(prev_wd, "FOREIGN") == 0)
+       {
+               static const char *const list_privilege_foreign[] =
+               {"DATA WRAPPER", "SERVER", NULL};
 
-       /* Complete "GRANT/REVOKE * ON * " with "TO" */
+               COMPLETE_WITH_LIST(list_privilege_foreign);
+       }
+
+       /* Complete "GRANT/REVOKE * ON * " with "TO/FROM" */
        else if ((pg_strcasecmp(prev4_wd, "GRANT") == 0 ||
                          pg_strcasecmp(prev4_wd, "REVOKE") == 0) &&
                         pg_strcasecmp(prev2_wd, "ON") == 0)
        {
                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)
@@ -1660,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
@@ -1667,12 +2691,34 @@ psql_completion(char *text, int start, int end)
        }
 
        /* Complete "GRANT/REVOKE * ON * TO/FROM" with username, GROUP, or PUBLIC */
-       else if (pg_strcasecmp(prev3_wd, "ON") == 0 &&
-                        ((pg_strcasecmp(prev5_wd, "GRANT") == 0 &&
-                          pg_strcasecmp(prev_wd, "TO") == 0) ||
-                         (pg_strcasecmp(prev5_wd, "REVOKE") == 0 &&
-                          pg_strcasecmp(prev_wd, "FROM") == 0)))
+       else if (pg_strcasecmp(prev5_wd, "GRANT") == 0 &&
+                        pg_strcasecmp(prev3_wd, "ON") == 0)
+       {
+               if (pg_strcasecmp(prev_wd, "TO") == 0)
+                       COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
+               else
+                       COMPLETE_WITH_CONST("TO");
+       }
+       else if (pg_strcasecmp(prev5_wd, "REVOKE") == 0 &&
+                        pg_strcasecmp(prev3_wd, "ON") == 0)
+       {
+               if (pg_strcasecmp(prev_wd, "FROM") == 0)
+                       COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
+               else
+                       COMPLETE_WITH_CONST("FROM");
+       }
+
+       /* 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 &&
@@ -1686,32 +2732,36 @@ psql_completion(char *text, int start, int end)
        /* Complete INSERT INTO with table names */
        else if (pg_strcasecmp(prev2_wd, "INSERT") == 0 &&
                         pg_strcasecmp(prev_wd, "INTO") == 0)
-               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_insertables, NULL);
        /* Complete "INSERT INTO <table> (" with attribute names */
-       else if (rl_line_buffer[start - 1] == '(' &&
-                        pg_strcasecmp(prev3_wd, "INSERT") == 0 &&
-                        pg_strcasecmp(prev2_wd, "INTO") == 0)
-               COMPLETE_WITH_ATTR(prev_wd, "");
+       else if (pg_strcasecmp(prev4_wd, "INSERT") == 0 &&
+                        pg_strcasecmp(prev3_wd, "INTO") == 0 &&
+                        pg_strcasecmp(prev_wd, "(") == 0)
+               COMPLETE_WITH_ATTR(prev2_wd, "");
 
        /*
-        * Complete INSERT INTO <table> with "VALUES" or "SELECT" or "DEFAULT
-        * VALUES"
+        * Complete INSERT INTO <table> with "(" or "VALUES" or "SELECT" or
+        * "TABLE" or "DEFAULT VALUES"
         */
        else if (pg_strcasecmp(prev3_wd, "INSERT") == 0 &&
                         pg_strcasecmp(prev2_wd, "INTO") == 0)
        {
                static const char *const list_INSERT[] =
-               {"DEFAULT VALUES", "SELECT", "VALUES", NULL};
+               {"(", "DEFAULT VALUES", "SELECT", "TABLE", "VALUES", NULL};
 
                COMPLETE_WITH_LIST(list_INSERT);
        }
-       /* Complete INSERT INTO <table> (attribs) with "VALUES" or "SELECT" */
+
+       /*
+        * Complete INSERT INTO <table> (attribs) with "VALUES" or "SELECT" or
+        * "TABLE"
+        */
        else if (pg_strcasecmp(prev4_wd, "INSERT") == 0 &&
                         pg_strcasecmp(prev3_wd, "INTO") == 0 &&
                         prev_wd[strlen(prev_wd) - 1] == ')')
        {
                static const char *const list_INSERT[] =
-               {"SELECT", "VALUES", NULL};
+               {"SELECT", "TABLE", "VALUES", NULL};
 
                COMPLETE_WITH_LIST(list_INSERT);
        }
@@ -1734,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");
@@ -1757,7 +2807,11 @@ psql_completion(char *text, int start, int end)
 
 /* NOTIFY */
        else if (pg_strcasecmp(prev_wd, "NOTIFY") == 0)
-               COMPLETE_WITH_QUERY("SELECT pg_catalog.quote_ident(relname) FROM pg_catalog.pg_listener WHERE substring(pg_catalog.quote_ident(relname),1,%d)='%s'");
+               COMPLETE_WITH_QUERY("SELECT pg_catalog.quote_ident(channel) FROM pg_catalog.pg_listening_channels() AS channel WHERE substring(pg_catalog.quote_ident(channel),1,%d)='%s'");
+
+/* OPTIONS */
+       else if (pg_strcasecmp(prev_wd, "OPTIONS") == 0)
+               COMPLETE_WITH_CONST("(");
 
 /* OWNER TO  - complete with available roles */
        else if (pg_strcasecmp(prev2_wd, "OWNER") == 0 &&
@@ -1783,6 +2837,11 @@ psql_completion(char *text, int start, int end)
                COMPLETE_WITH_LIST(list_PREPARE);
        }
 
+/*
+ * PREPARE TRANSACTION is missing on purpose. It's intended for transaction
+ * managers, not for manual use in interactive sessions.
+ */
+
 /* REASSIGN OWNED BY xxx TO yyy */
        else if (pg_strcasecmp(prev_wd, "REASSIGN") == 0)
                COMPLETE_WITH_CONST("OWNED");
@@ -1803,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)
        {
@@ -1814,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 ||
@@ -1822,6 +2929,41 @@ psql_completion(char *text, int start, int end)
                        COMPLETE_WITH_QUERY(Query_for_list_of_databases);
        }
 
+/* SECURITY LABEL */
+       else if (pg_strcasecmp(prev_wd, "SECURITY") == 0)
+               COMPLETE_WITH_CONST("LABEL");
+       else if (pg_strcasecmp(prev2_wd, "SECURITY") == 0 &&
+                        pg_strcasecmp(prev_wd, "LABEL") == 0)
+       {
+               static const char *const list_SECURITY_LABEL_preposition[] =
+               {"ON", "FOR"};
+
+               COMPLETE_WITH_LIST(list_SECURITY_LABEL_preposition);
+       }
+       else if (pg_strcasecmp(prev4_wd, "SECURITY") == 0 &&
+                        pg_strcasecmp(prev3_wd, "LABEL") == 0 &&
+                        pg_strcasecmp(prev2_wd, "FOR") == 0)
+               COMPLETE_WITH_CONST("ON");
+       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(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",
+                       "MATERIALIZED VIEW", "COLUMN", "AGGREGATE", "FUNCTION", "DOMAIN",
+               "LARGE OBJECT", NULL};
+
+               COMPLETE_WITH_LIST(list_SECURITY_LABEL);
+       }
+       else if (pg_strcasecmp(prev5_wd, "SECURITY") == 0 &&
+                        pg_strcasecmp(prev4_wd, "LABEL") == 0 &&
+                        pg_strcasecmp(prev3_wd, "ON") == 0)
+               COMPLETE_WITH_CONST("IS");
+
 /* SELECT */
        /* naah . . . */
 
@@ -1905,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)
@@ -1940,6 +3088,9 @@ psql_completion(char *text, int start, int end)
        else if (pg_strcasecmp(prev2_wd, "SET") == 0 &&
                         pg_strcasecmp(prev4_wd, "UPDATE") != 0 &&
                         pg_strcasecmp(prev_wd, "TABLESPACE") != 0 &&
+                        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 */
@@ -1956,6 +3107,13 @@ psql_completion(char *text, int start, int end)
 
                        COMPLETE_WITH_LIST(my_list);
                }
+               else if (pg_strcasecmp(prev2_wd, "IntervalStyle") == 0)
+               {
+                       static const char *const my_list[] =
+                       {"postgres", "postgres_verbose", "sql_standard", "iso_8601", NULL};
+
+                       COMPLETE_WITH_LIST(my_list);
+               }
                else if (pg_strcasecmp(prev2_wd, "GEQO") == 0)
                {
                        static const char *const my_list[] =
@@ -1976,18 +3134,23 @@ 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);
 
 /* UNLISTEN */
        else if (pg_strcasecmp(prev_wd, "UNLISTEN") == 0)
-               COMPLETE_WITH_QUERY("SELECT pg_catalog.quote_ident(relname) FROM pg_catalog.pg_listener WHERE substring(pg_catalog.quote_ident(relname),1,%d)='%s' UNION SELECT '*'");
+               COMPLETE_WITH_QUERY("SELECT pg_catalog.quote_ident(channel) FROM pg_catalog.pg_listening_channels() AS channel WHERE substring(pg_catalog.quote_ident(channel),1,%d)='%s' UNION SELECT '*'");
 
 /* UPDATE */
        /* If prev. word is UPDATE suggest a list of tables */
        else if (pg_strcasecmp(prev_wd, "UPDATE") == 0)
-               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, NULL);
        /* Complete UPDATE <table> with "SET" */
        else if (pg_strcasecmp(prev2_wd, "UPDATE") == 0)
                COMPLETE_WITH_CONST("SET");
@@ -2005,12 +3168,41 @@ psql_completion(char *text, int start, int end)
                         pg_strcasecmp(prev4_wd, "UPDATE") == 0)
                COMPLETE_WITH_CONST("=");
 
+/* USER MAPPING */
+       else if ((pg_strcasecmp(prev3_wd, "ALTER") == 0 ||
+                         pg_strcasecmp(prev3_wd, "CREATE") == 0 ||
+                         pg_strcasecmp(prev3_wd, "DROP") == 0) &&
+                        pg_strcasecmp(prev2_wd, "USER") == 0 &&
+                        pg_strcasecmp(prev_wd, "MAPPING") == 0)
+               COMPLETE_WITH_CONST("FOR");
+       else if (pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
+                        pg_strcasecmp(prev3_wd, "USER") == 0 &&
+                        pg_strcasecmp(prev2_wd, "MAPPING") == 0 &&
+                        pg_strcasecmp(prev_wd, "FOR") == 0)
+               COMPLETE_WITH_QUERY(Query_for_list_of_roles
+                                                       " UNION SELECT 'CURRENT_USER'"
+                                                       " UNION SELECT 'PUBLIC'"
+                                                       " UNION SELECT 'USER'");
+       else if ((pg_strcasecmp(prev4_wd, "ALTER") == 0 ||
+                         pg_strcasecmp(prev4_wd, "DROP") == 0) &&
+                        pg_strcasecmp(prev3_wd, "USER") == 0 &&
+                        pg_strcasecmp(prev2_wd, "MAPPING") == 0 &&
+                        pg_strcasecmp(prev_wd, "FOR") == 0)
+               COMPLETE_WITH_QUERY(Query_for_list_of_user_mappings);
+       else if ((pg_strcasecmp(prev5_wd, "CREATE") == 0 ||
+                         pg_strcasecmp(prev5_wd, "ALTER") == 0 ||
+                         pg_strcasecmp(prev5_wd, "DROP") == 0) &&
+                        pg_strcasecmp(prev4_wd, "USER") == 0 &&
+                        pg_strcasecmp(prev3_wd, "MAPPING") == 0 &&
+                        pg_strcasecmp(prev2_wd, "FOR") == 0)
+               COMPLETE_WITH_CONST("SERVER");
+
 /*
  * VACUUM [ FULL | FREEZE ] [ VERBOSE ] [ table ]
  * VACUUM [ FULL | FREEZE ] [ VERBOSE ] ANALYZE [ table [ (column [, ...] ) ] ]
  */
        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'"
@@ -2018,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 */
@@ -2066,49 +3264,74 @@ psql_completion(char *text, int start, int end)
        else if (pg_strcasecmp(prev_wd, "FROM") == 0 &&
                         pg_strcasecmp(prev3_wd, "COPY") != 0 &&
                         pg_strcasecmp(prev3_wd, "\\copy") != 0)
-               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsv, NULL);
+               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
 
+/* ... JOIN ... */
+       else if (pg_strcasecmp(prev_wd, "JOIN") == 0)
+               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
 
 /* Backslash commands */
 /* TODO:  \dc \dd \dl */
        else if (strcmp(prev_wd, "\\connect") == 0 || strcmp(prev_wd, "\\c") == 0)
                COMPLETE_WITH_QUERY(Query_for_list_of_databases);
-       else if (strcmp(prev_wd, "\\d") == 0 || strcmp(prev_wd, "\\d+") == 0)
-               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tisv, NULL);
-       else if (strcmp(prev_wd, "\\da") == 0)
+
+       else if (strncmp(prev_wd, "\\da", strlen("\\da")) == 0)
                COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_aggregates, NULL);
-       else if (strcmp(prev_wd, "\\db") == 0)
+       else if (strncmp(prev_wd, "\\db", strlen("\\db")) == 0)
                COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
-       else if (strcmp(prev_wd, "\\dD") == 0)
+       else if (strncmp(prev_wd, "\\dD", strlen("\\dD")) == 0)
                COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains, NULL);
-       else if (strcmp(prev_wd, "\\df") == 0 || strcmp(prev_wd, "\\df+") == 0)
+       else if (strncmp(prev_wd, "\\des", strlen("\\des")) == 0)
+               COMPLETE_WITH_QUERY(Query_for_list_of_servers);
+       else if (strncmp(prev_wd, "\\deu", strlen("\\deu")) == 0)
+               COMPLETE_WITH_QUERY(Query_for_list_of_user_mappings);
+       else if (strncmp(prev_wd, "\\dew", strlen("\\dew")) == 0)
+               COMPLETE_WITH_QUERY(Query_for_list_of_fdws);
+
+       else if (strncmp(prev_wd, "\\df", strlen("\\df")) == 0)
                COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
-       else if (strcmp(prev_wd, "\\dF") == 0 || strcmp(prev_wd, "\\dF+") == 0)
-               COMPLETE_WITH_QUERY(Query_for_list_of_ts_configurations);
-       else if (strcmp(prev_wd, "\\dFd") == 0 || strcmp(prev_wd, "\\dFd+") == 0)
+       else if (strncmp(prev_wd, "\\dFd", strlen("\\dFd")) == 0)
                COMPLETE_WITH_QUERY(Query_for_list_of_ts_dictionaries);
-       else if (strcmp(prev_wd, "\\dFp") == 0 || strcmp(prev_wd, "\\dFp+") == 0)
+       else if (strncmp(prev_wd, "\\dFp", strlen("\\dFp")) == 0)
                COMPLETE_WITH_QUERY(Query_for_list_of_ts_parsers);
-       else if (strcmp(prev_wd, "\\dFt") == 0 || strcmp(prev_wd, "\\dFt+") == 0)
+       else if (strncmp(prev_wd, "\\dFt", strlen("\\dFt")) == 0)
                COMPLETE_WITH_QUERY(Query_for_list_of_ts_templates);
-       else if (strcmp(prev_wd, "\\di") == 0 || strcmp(prev_wd, "\\di+") == 0)
+       /* must be at end of \dF */
+       else if (strncmp(prev_wd, "\\dF", strlen("\\dF")) == 0)
+               COMPLETE_WITH_QUERY(Query_for_list_of_ts_configurations);
+
+       else if (strncmp(prev_wd, "\\di", strlen("\\di")) == 0)
                COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
-       else if (strcmp(prev_wd, "\\dn") == 0)
+       else if (strncmp(prev_wd, "\\dL", strlen("\\dL")) == 0)
+               COMPLETE_WITH_QUERY(Query_for_list_of_languages);
+       else if (strncmp(prev_wd, "\\dn", strlen("\\dn")) == 0)
                COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
-       else if (strcmp(prev_wd, "\\dp") == 0 || strcmp(prev_wd, "\\z") == 0)
-               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsv, NULL);
-       else if (strcmp(prev_wd, "\\ds") == 0 || strcmp(prev_wd, "\\ds+") == 0)
+       else if (strncmp(prev_wd, "\\dp", strlen("\\dp")) == 0
+                        || strncmp(prev_wd, "\\z", strlen("\\z")) == 0)
+               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 (strcmp(prev_wd, "\\dS") == 0 || strcmp(prev_wd, "\\dS+") == 0)
-               COMPLETE_WITH_QUERY(Query_for_list_of_system_relations);
-       else if (strcmp(prev_wd, "\\dt") == 0 || strcmp(prev_wd, "\\dt+") == 0)
+       else if (strncmp(prev_wd, "\\dt", strlen("\\dt")) == 0)
                COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
-       else if (strcmp(prev_wd, "\\dT") == 0 || strcmp(prev_wd, "\\dT+") == 0)
+       else if (strncmp(prev_wd, "\\dT", strlen("\\dT")) == 0)
                COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL);
-       else if (strcmp(prev_wd, "\\du") == 0)
+       else if (strncmp(prev_wd, "\\du", strlen("\\du")) == 0
+                        || (strncmp(prev_wd, "\\dg", strlen("\\dg")) == 0))
                COMPLETE_WITH_QUERY(Query_for_list_of_roles);
-       else if (strcmp(prev_wd, "\\dv") == 0 || strcmp(prev_wd, "\\dv+") == 0)
+       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_relations, NULL);
+
+       else if (strcmp(prev_wd, "\\ef") == 0)
+               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
+
        else if (strcmp(prev_wd, "\\encoding") == 0)
                COMPLETE_WITH_QUERY(Query_for_list_of_encodings);
        else if (strcmp(prev_wd, "\\h") == 0 || strcmp(prev_wd, "\\help") == 0)
@@ -2119,21 +3342,49 @@ psql_completion(char *text, int start, int end)
        {
                static const char *const my_list[] =
                {"format", "border", "expanded",
-                       "null", "fieldsep", "tuples_only", "title", "tableattr", "pager",
-               "recordsep", NULL};
+                       "null", "fieldsep", "tuples_only", "title", "tableattr",
+               "linestyle", "pager", "recordsep", NULL};
 
-               COMPLETE_WITH_LIST(my_list);
+               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)
+       {
+               matches = complete_from_variables(text, "", "");
+       }
+       else if (strcmp(prev_wd, "\\sf") == 0 || strcmp(prev_wd, "\\sf+") == 0)
+               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
        else if (strcmp(prev_wd, "\\cd") == 0 ||
                         strcmp(prev_wd, "\\e") == 0 || strcmp(prev_wd, "\\edit") == 0 ||
                         strcmp(prev_wd, "\\g") == 0 ||
                  strcmp(prev_wd, "\\i") == 0 || strcmp(prev_wd, "\\include") == 0 ||
+                        strcmp(prev_wd, "\\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);
-
+       {
+               completion_charp = "\\";
+               matches = completion_matches(text, complete_from_files);
+       }
 
        /*
         * Finally, we look through the list of "things", such as TABLE, INDEX and
@@ -2172,36 +3423,38 @@ 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;
 }
 
 
+/*
+ * GENERATOR FUNCTIONS
+ *
+ * These functions do all the actual work of completing the input. They get
+ * passed the text so far and the count how many times they have been called
+ * so far with the same text.
+ * If you read the above carefully, you'll see that these don't get called
+ * directly but through the readline interface.
+ * The return value is expected to be the full completion of the text, going
+ * through a list each time, or NULL if there are no more matches. The string
+ * will be free()'d by readline, so you must run it through strdup() or
+ * something of that sort.
+ */
 
-/* GENERATOR FUNCTIONS
-
-   These functions do all the actual work of completing the input. They get
-   passed the text so far and the count how many times they have been called so
-   far with the same text.
-   If you read the above carefully, you'll see that these don't get called
-   directly but through the readline interface.
-   The return value is expected to be the full completion of the text, going
-   through a list each time, or NULL if there are no more matches. The string
-   will be free()'d by readline, so you must run it through strdup() or
-   something of that sort.
-*/
-
-/* 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;
@@ -2217,57 +3470,31 @@ create_command_generator(const char *text, int state)
        /* find something that matches */
        while ((name = words_after_create[list_index++].name))
        {
-               if ((pg_strncasecmp(name, text, string_length) == 0) && !words_after_create[list_index - 1].noshow)
-                       return pg_strdup(name);
+               if ((pg_strncasecmp(name, text, string_length) == 0) &&
+                       !(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 */
@@ -2285,26 +3512,27 @@ complete_from_schema_query(const char *text, int state)
 }
 
 
-/* This creates a list of matching things, according to a query pointed to
-   by completion_charp.
-   The query can be one of two kinds:
-   - A simple query which must contain a %d and a %s, which will be replaced
-   by the string length of the text and the text itself. The query may also
-   have up to four more %s in it; the first two such will be replaced by the
-   value of completion_info_charp, the next two by the value of
-   completion_info_charp2.
-        or:
-   - A schema query used for completion of both schema and relation names;
-   these are more complex and must contain in the following order:
-        %d %s %d %s %d %s %s %d %s
-   where %d is the string length of the text and %s the text itself.
-
-   It is assumed that strings should be escaped to become SQL literals
-   (that is, what is in the query is actually ... '%s' ...)
-
-   See top of file for examples of both kinds of query.
-*/
-
+/*
+ * This creates a list of matching things, according to a query pointed to
+ * by completion_charp.
+ * The query can be one of two kinds:
+ *
+ * 1. A simple query which must contain a %d and a %s, which will be replaced
+ * by the string length of the text and the text itself. The query may also
+ * have up to four more %s in it; the first two such will be replaced by the
+ * value of completion_info_charp, the next two by the value of
+ * completion_info_charp2.
+ *
+ * 2. A schema query used for completion of both schema and relation names.
+ * These are more complex and must contain in the following order:
+ * %d %s %d %s %d %s %s %d %s
+ * where %d is the string length of the text and %s the text itself.
+ *
+ * It is assumed that strings should be escaped to become SQL literals
+ * (that is, what is in the query is actually ... '%s' ...)
+ *
+ * See top of file for examples of both kinds of query.
+ */
 static char *
 _complete_from_query(int is_schema_query, const char *text, int state)
 {
@@ -2490,10 +3718,11 @@ _complete_from_query(int is_schema_query, const char *text, int state)
 }
 
 
-/* This function returns in order one of a fixed, NULL pointer terminated list
-   of strings (if matching). This can be used if there are only a fixed number
-   SQL words that can appear at certain spot.
-*/
+/*
+ * This function returns in order one of a fixed, NULL pointer terminated list
+ * of strings (if matching). This can be used if there are only a fixed number
+ * SQL words that can appear at certain spot.
+ */
 static char *
 complete_from_list(const char *text, int state)
 {
@@ -2504,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;
        }
 
@@ -2526,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);
+               }
        }
 
        /*
@@ -2546,30 +3785,176 @@ complete_from_list(const char *text, int state)
 }
 
 
-/* This function returns one fixed string the first time even if it doesn't
-   match what's there, and nothing the second time. This should be used if there
-   is only one possibility that can appear at a certain spot, so misspellings
-   will be overwritten.
-   The string to be passed must be in completion_charp.
-*/
+/*
+ * This function returns one fixed string the first time even if it doesn't
+ * match what's there, and nothing the second time. This should be used if
+ * there is only one possibility that can appear at a certain spot, so
+ * misspellings will be overwritten.  The string to be passed must be in
+ * completion_charp.
+ */
 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;
 }
 
 
+/*
+ * This function supports completion with the name of a psql variable.
+ * The variable names can be prefixed and suffixed with additional text
+ * to support quoting usages.
+ */
+static char **
+complete_from_variables(char *text, const char *prefix, const char *suffix)
+{
+       char      **matches;
+       char      **varnames;
+       int                     nvars = 0;
+       int                     maxvars = 100;
+       int                     i;
+       struct _variable *ptr;
+
+       varnames = (char **) pg_malloc((maxvars + 1) * sizeof(char *));
+
+       for (ptr = pset.vars->next; ptr; ptr = ptr->next)
+       {
+               if (nvars >= maxvars)
+               {
+                       maxvars *= 2;
+                       varnames = (char **) realloc(varnames,
+                                                                                (maxvars + 1) * sizeof(char *));
+                       if (!varnames)
+                       {
+                               psql_error("out of memory\n");
+                               exit(EXIT_FAILURE);
+                       }
+               }
+
+               varnames[nvars++] = psprintf("%s%s%s", prefix, ptr->name, suffix);
+       }
+
+       varnames[nvars] = NULL;
+       COMPLETE_WITH_LIST_CS((const char *const *) varnames);
+
+       for (i = 0; i < nvars; 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.
@@ -2586,7 +3971,7 @@ exec_query(const char *query)
 
        if (PQresultStatus(result) != PGRES_TUPLES_OK)
        {
-#if 0
+#ifdef NOT_USED
                psql_error("tab completion query failed: %s\nQuery was:\n%s\n",
                                   PQerrorMessage(pset.db), query);
 #endif
@@ -2598,93 +3983,92 @@ 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;
 
-       while (skip-- >= 0)
+       /* 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 (nwords-- > 0)
        {
-               /* first we look for a space before the current word */
-               for (i = point; i >= 0; i--)
-                       if (rl_line_buffer[i] == ' ')
-                               break;
+               int                     start,
+                                       end;
+               char       *s;
 
                /* now find the first non-space which then constitutes the end */
-               for (; i >= 0; i--)
-                       if (rl_line_buffer[i] != ' ')
+               end = -1;
+               for (i = point; i >= 0; 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
+                * 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 (rl_line_buffer[start] == '"')
-                               inquotes = !inquotes;
-                       if ((rl_line_buffer[start - 1] == ' ') && inquotes == 0)
-                               break;
+                       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;
 
-               point = start;
-       }
-
-       /* make a copy */
-       s = pg_malloc(end - start + 2);
-       strlcpy(s, &rl_line_buffer[start], end - start + 2);
-
-       return s;
-}
-
-/* Find the parenthesis after the last word */
+                       for (start = end; start > 0; start--)
+                       {
+                               if (buf[start] == '"')
+                                       inquotes = !inquotes;
+                               if (!inquotes)
+                               {
+                                       if (buf[start] == ')')
+                                               parentheses++;
+                                       else if (buf[start] == '(')
+                                       {
+                                               if (--parentheses <= 0)
+                                                       break;
+                                       }
+                                       else if (parentheses == 0 &&
+                                                        strchr(WORD_BREAKS, buf[start - 1]))
+                                               break;
+                               }
+                       }
 
+                       point = start - 1;
 
-static int
-find_open_parenthesis(int end)
-{
-       int                     i = end - 1;
+                       /* make a copy of chars from start to end inclusive */
+                       s = pg_malloc(end - start + 2);
+                       strlcpy(s, &buf[start], end - start + 2);
+               }
 
-       while ((rl_line_buffer[i] != ' ') && (i >= 0))
-       {
-               if (rl_line_buffer[i] == '(')
-                       return 1;
-               i--;
-       }
-       while ((rl_line_buffer[i] == ' ') && (i >= 0))
-       {
-               i--;
-       }
-       if (rl_line_buffer[i] == '(')
-       {
-               return 1;
+               *previous_words++ = s;
        }
-       return 0;
-
 }
 
-#if 0
+#ifdef NOT_USED
 
 /*
  * Surround a string with single quotes. This works for both SQL and
@@ -2709,8 +4093,6 @@ quote_file_name(char *text, int match_type, char *quote_pointer)
        return s;
 }
 
-
-
 static char *
 dequote_file_name(char *text, char quote_char)
 {
@@ -2726,6 +4108,6 @@ dequote_file_name(char *text, char quote_char)
 
        return s;
 }
-#endif   /* 0 */
+#endif   /* NOT_USED */
 
 #endif   /* USE_READLINE */