]> 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 3854f7f421fc853a60ce538f6e3f2f670f454021..84d2eb4d4209a2cfa2c3c6e8bcc0be48e87ac008 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * psql - the PostgreSQL interactive terminal
  *
- * Copyright (c) 2000-2012, PostgreSQL Global Development Group
+ * Copyright (c) 2000-2013, PostgreSQL Global Development Group
  *
  * src/bin/psql/tab-complete.c
  */
@@ -132,7 +132,7 @@ static const char *const * completion_charpp;       /* to pass a list of strings */
 static const char *completion_info_charp;              /* to pass a second string */
 static const char *completion_info_charp2;             /* to pass a third string */
 static const SchemaQuery *completion_squery;   /* to pass a SchemaQuery */
-static bool completion_case_sensitive;                 /* completion is case sensitive */
+static bool completion_case_sensitive; /* completion is case sensitive */
 
 /*
  * A few macros to ease typing. You can use these to complete the given
@@ -142,6 +142,7 @@ static bool completion_case_sensitive;                      /* completion is case sensitive */
  * 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 { \
@@ -202,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
  */
@@ -328,6 +354,21 @@ static const SchemaQuery Query_for_list_of_tables = {
        NULL
 };
 
+static const SchemaQuery Query_for_list_of_constraints_with_schema = {
+       /* catname */
+       "pg_catalog.pg_constraint c",
+       /* selcondition */
+       "c.conrelid <> 0",
+       /* viscondition */
+       "true",                                         /* there is no pg_constraint_is_visible */
+       /* namespace */
+       "c.connamespace",
+       /* result */
+       "pg_catalog.quote_ident(c.conname)",
+       /* qualresult */
+       NULL
+};
+
 /* The bit masks for the following three functions come from
  * src/include/catalog/pg_trigger.h.
  */
@@ -394,11 +435,41 @@ static const SchemaQuery Query_for_list_of_relations = {
        NULL
 };
 
-static const SchemaQuery Query_for_list_of_tsvf = {
+static const SchemaQuery Query_for_list_of_tsvmf = {
+       /* catname */
+       "pg_catalog.pg_class c",
+       /* selcondition */
+       "c.relkind IN ('r', 'S', 'v', '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', 'S', 'v', 'f')",
+       "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 */
@@ -424,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
@@ -538,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) "\
@@ -583,10 +708,25 @@ static const SchemaQuery Query_for_list_of_views = {
 "   FROM pg_catalog.pg_am "\
 "  WHERE substring(pg_catalog.quote_ident(amname),1,%d)='%s'"
 
+/* the silly-looking length condition is just to eat up the current word */
 #define Query_for_list_of_arguments \
-" SELECT pg_catalog.oidvectortypes(proargtypes)||')' "\
-"   FROM pg_catalog.pg_proc "\
-"  WHERE proname='%s'"
+"SELECT pg_catalog.oidvectortypes(proargtypes)||')' "\
+"  FROM pg_catalog.pg_proc "\
+" WHERE (%d = pg_catalog.length('%s'))"\
+"   AND (pg_catalog.quote_ident(proname)='%s'"\
+"        OR '\"' || proname || '\"'='%s') "\
+"   AND (pg_catalog.pg_function_is_visible(pg_proc.oid))"
+
+/* the silly-looking length condition is just to eat up the current word */
+#define Query_for_list_of_arguments_with_schema \
+"SELECT pg_catalog.oidvectortypes(proargtypes)||')' "\
+"  FROM pg_catalog.pg_proc p, pg_catalog.pg_namespace n "\
+" WHERE (%d = pg_catalog.length('%s'))"\
+"   AND n.oid = p.pronamespace "\
+"   AND (pg_catalog.quote_ident(proname)='%s' "\
+"        OR '\"' || proname || '\"' ='%s') "\
+"   AND (pg_catalog.quote_ident(nspname)='%s' "\
+"        OR '\"' || nspname || '\"' ='%s') "
 
 #define Query_for_list_of_extensions \
 " SELECT pg_catalog.quote_ident(extname) "\
@@ -642,6 +782,7 @@ static const pgsql_thing_t words_after_create[] = {
        {"GROUP", Query_for_list_of_roles},
        {"LANGUAGE", Query_for_list_of_languages},
        {"INDEX", NULL, &Query_for_list_of_indexes},
+       {"MATERIALIZED VIEW", NULL, NULL},
        {"OPERATOR", NULL, NULL},       /* Querying for this is probably not such a
                                                                 * good idea. */
        {"OWNED", NULL, NULL, THING_NO_CREATE},         /* for DROP OWNED BY ... */
@@ -680,8 +821,9 @@ 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_same_case(const char *s, const char *ref);
+static char *pg_strdup_keyword_case(const char *s, const char *ref);
 static PGresult *exec_query(const char *query);
 
 static void get_previous_words(int point, char **previous_words, int nwords);
@@ -742,7 +884,7 @@ psql_completion(char *text, int start, int end)
                "COMMENT", "COMMIT", "COPY", "CREATE", "DEALLOCATE", "DECLARE",
                "DELETE FROM", "DISCARD", "DO", "DROP", "END", "EXECUTE", "EXPLAIN", "FETCH",
                "GRANT", "INSERT", "LISTEN", "LOAD", "LOCK", "MOVE", "NOTIFY", "PREPARE",
-               "REASSIGN", "REINDEX", "RELEASE", "RESET", "REVOKE", "ROLLBACK",
+               "REASSIGN", "REFRESH", "REINDEX", "RELEASE", "RESET", "REVOKE", "ROLLBACK",
                "SAVEPOINT", "SECURITY LABEL", "SELECT", "SET", "SHOW", "START",
                "TABLE", "TRUNCATE", "UNLISTEN", "UPDATE", "VACUUM", "VALUES", "WITH",
                NULL
@@ -752,13 +894,13 @@ psql_completion(char *text, int start, int end)
                "\\a", "\\connect", "\\conninfo", "\\C", "\\cd", "\\copy", "\\copyright",
                "\\d", "\\da", "\\db", "\\dc", "\\dC", "\\dd", "\\dD", "\\des", "\\det", "\\deu", "\\dew", "\\df",
                "\\dF", "\\dFd", "\\dFp", "\\dFt", "\\dg", "\\di", "\\dl", "\\dL",
-               "\\dn", "\\do", "\\dp", "\\drds", "\\ds", "\\dS", "\\dt", "\\dT", "\\dv", "\\du",
+               "\\dn", "\\do", "\\dp", "\\drds", "\\ds", "\\dS", "\\dt", "\\dT", "\\dv", "\\du", "\\dx",
                "\\e", "\\echo", "\\ef", "\\encoding",
-               "\\f", "\\g", "\\h", "\\help", "\\H", "\\i", "\\ir", "\\l",
+               "\\f", "\\g", "\\gset", "\\h", "\\help", "\\H", "\\i", "\\ir", "\\l",
                "\\lo_import", "\\lo_export", "\\lo_list", "\\lo_unlink",
                "\\o", "\\p", "\\password", "\\prompt", "\\pset", "\\q", "\\qecho", "\\r",
                "\\set", "\\sf", "\\t", "\\T",
-               "\\timing", "\\unset", "\\x", "\\w", "\\z", "\\!", NULL
+               "\\timing", "\\unset", "\\x", "\\w", "\\watch", "\\z", "\\!", NULL
        };
 
        (void) end;                                     /* not used */
@@ -774,9 +916,9 @@ psql_completion(char *text, int start, int end)
        completion_info_charp2 = NULL;
 
        /*
-        * 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.
+        * 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.
         */
        get_previous_words(start, previous_words, lengthof(previous_words));
 
@@ -822,8 +964,8 @@ psql_completion(char *text, int start, int end)
                static const char *const list_ALTER[] =
                {"AGGREGATE", "COLLATION", "CONVERSION", "DATABASE", "DEFAULT PRIVILEGES", "DOMAIN",
                        "EXTENSION", "FOREIGN DATA WRAPPER", "FOREIGN TABLE", "FUNCTION",
-                       "GROUP", "INDEX", "LANGUAGE", "LARGE OBJECT", "OPERATOR",
-                       "ROLE", "SCHEMA", "SERVER", "SEQUENCE", "TABLE",
+                       "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};
 
@@ -847,13 +989,7 @@ psql_completion(char *text, int start, int end)
                        COMPLETE_WITH_LIST(list_ALTERAGG);
                }
                else
-               {
-                       char       *tmp_buf = malloc(strlen(Query_for_list_of_arguments) + strlen(prev2_wd));
-
-                       sprintf(tmp_buf, Query_for_list_of_arguments, prev2_wd);
-                       COMPLETE_WITH_QUERY(tmp_buf);
-                       free(tmp_buf);
-               }
+                       COMPLETE_WITH_FUNCTION_ARG(prev2_wd);
        }
 
        /* ALTER SCHEMA <name> */
@@ -997,6 +1133,14 @@ psql_completion(char *text, int start, int end)
                COMPLETE_WITH_LIST(list_ALTERLARGEOBJECT);
        }
 
+       /* ALTER MATERIALIZED VIEW */
+       else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
+                        pg_strcasecmp(prev2_wd, "MATERIALIZED") == 0 &&
+                        pg_strcasecmp(prev_wd, "VIEW") == 0)
+       {
+               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
+       }
+
        /* ALTER USER,ROLE <name> */
        else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
                         !(pg_strcasecmp(prev2_wd, "USER") == 0 && pg_strcasecmp(prev_wd, "MAPPING") == 0) &&
@@ -1008,11 +1152,28 @@ psql_completion(char *text, int start, int end)
                        "ENCRYPTED", "INHERIT", "LOGIN", "NOCREATEDB", "NOCREATEROLE",
                        "NOCREATEUSER", "NOINHERIT", "NOLOGIN", "NOREPLICATION",
                        "NOSUPERUSER", "RENAME TO", "REPLICATION", "RESET", "SET",
-               "SUPERUSER", "UNENCRYPTED", "VALID UNTIL", NULL};
+               "SUPERUSER", "UNENCRYPTED", "VALID UNTIL", "WITH", NULL};
 
                COMPLETE_WITH_LIST(list_ALTERUSER);
        }
 
+       /* ALTER USER,ROLE <name> WITH */
+       else if ((pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
+                         (pg_strcasecmp(prev3_wd, "USER") == 0 ||
+                          pg_strcasecmp(prev3_wd, "ROLE") == 0) &&
+                         pg_strcasecmp(prev_wd, "WITH") == 0))
+       {
+               /* Similar to the above, but don't complete "WITH" again. */
+               static const char *const list_ALTERUSER_WITH[] =
+               {"CONNECTION LIMIT", "CREATEDB", "CREATEROLE", "CREATEUSER",
+                       "ENCRYPTED", "INHERIT", "LOGIN", "NOCREATEDB", "NOCREATEROLE",
+                       "NOCREATEUSER", "NOINHERIT", "NOLOGIN", "NOREPLICATION",
+                       "NOSUPERUSER", "RENAME TO", "REPLICATION", "RESET", "SET",
+               "SUPERUSER", "UNENCRYPTED", "VALID UNTIL", NULL};
+
+               COMPLETE_WITH_LIST(list_ALTERUSER_WITH);
+       }
+
        /* complete ALTER USER,ROLE <name> ENCRYPTED,UNENCRYPTED with PASSWORD */
        else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
                         (pg_strcasecmp(prev3_wd, "ROLE") == 0 || pg_strcasecmp(prev3_wd, "USER") == 0) &&
@@ -1057,7 +1218,7 @@ psql_completion(char *text, int start, int end)
                         pg_strcasecmp(prev2_wd, "DOMAIN") == 0)
        {
                static const char *const list_ALTERDOMAIN[] =
-               {"ADD", "DROP", "OWNER TO", "SET", NULL};
+               {"ADD", "DROP", "OWNER TO", "RENAME", "SET", "VALIDATE CONSTRAINT", NULL};
 
                COMPLETE_WITH_LIST(list_ALTERDOMAIN);
        }
@@ -1071,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 &&
@@ -1119,6 +1307,36 @@ psql_completion(char *text, int start, int end)
 
                COMPLETE_WITH_LIST(list_ALTERVIEW);
        }
+       /* ALTER MATERIALIZED VIEW <name> */
+       else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
+                        pg_strcasecmp(prev3_wd, "MATERIALIZED") == 0 &&
+                        pg_strcasecmp(prev2_wd, "VIEW") == 0)
+       {
+               static const char *const list_ALTERMATVIEW[] =
+               {"ALTER COLUMN", "OWNER TO", "RENAME TO", "SET SCHEMA", NULL};
+
+               COMPLETE_WITH_LIST(list_ALTERMATVIEW);
+       }
+
+       /* ALTER RULE <name>, add ON */
+       else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
+                        pg_strcasecmp(prev2_wd, "RULE") == 0)
+               COMPLETE_WITH_CONST("ON");
+
+       /* If we have ALTER RULE <name> ON, then add the correct tablename */
+       else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
+                        pg_strcasecmp(prev3_wd, "RULE") == 0 &&
+                        pg_strcasecmp(prev_wd, "ON") == 0)
+       {
+               completion_info_charp = prev2_wd;
+               COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_rule);
+       }
+
+       /* ALTER RULE <name> ON <name> */
+       else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
+                        pg_strcasecmp(prev4_wd, "RULE") == 0)
+               COMPLETE_WITH_CONST("RENAME TO");
+
        /* ALTER TRIGGER <name>, add ON */
        else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
                         pg_strcasecmp(prev2_wd, "TRIGGER") == 0)
@@ -1177,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)
@@ -1187,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
@@ -1206,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");
 
@@ -1225,11 +1466,27 @@ psql_completion(char *text, int start, int end)
 
                COMPLETE_WITH_LIST(list_TABLEDROP);
        }
-       /* If we have TABLE <sth> DROP COLUMN, provide list of columns */
-       else if (pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
+       /* If we have ALTER TABLE <sth> DROP COLUMN, provide list of columns */
+       else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
+                        pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
                         pg_strcasecmp(prev2_wd, "DROP") == 0 &&
                         pg_strcasecmp(prev_wd, "COLUMN") == 0)
                COMPLETE_WITH_ATTR(prev3_wd, "");
+
+       /*
+        * If we have ALTER TABLE <sth> DROP|RENAME|VALIDATE CONSTRAINT, provide
+        * list of constraints
+        */
+       else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
+                        pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
+                        (pg_strcasecmp(prev2_wd, "DROP") == 0 ||
+                         pg_strcasecmp(prev2_wd, "RENAME") == 0 ||
+                         pg_strcasecmp(prev2_wd, "VALIDATE") == 0) &&
+                        pg_strcasecmp(prev_wd, "CONSTRAINT") == 0)
+       {
+               completion_info_charp = prev3_wd;
+               COMPLETE_WITH_QUERY(Query_for_constraint_of_table);
+       }
        /* ALTER TABLE ALTER [COLUMN] <foo> */
        else if ((pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
                          pg_strcasecmp(prev2_wd, "COLUMN") == 0) ||
@@ -1538,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");
        }
@@ -1560,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");
@@ -1570,7 +1853,7 @@ psql_completion(char *text, int start, int end)
                {"CAST", "COLLATION", "CONVERSION", "DATABASE", "EXTENSION",
                        "FOREIGN DATA WRAPPER", "FOREIGN TABLE",
                        "SERVER", "INDEX", "LANGUAGE", "RULE", "SCHEMA", "SEQUENCE",
-                       "TABLE", "TYPE", "VIEW", "COLUMN", "AGGREGATE", "FUNCTION",
+                       "TABLE", "TYPE", "VIEW", "MATERIALIZED VIEW", "COLUMN", "AGGREGATE", "FUNCTION",
                        "OPERATOR", "TRIGGER", "CONSTRAINT", "DOMAIN", "LARGE OBJECT",
                "TABLESPACE", "TEXT SEARCH", "ROLE", NULL};
 
@@ -1595,6 +1878,33 @@ 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, "COMMENT") == 0 &&
@@ -1630,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 ||
@@ -1721,7 +2034,7 @@ psql_completion(char *text, int start, int end)
                          pg_strcasecmp(prev2_wd, "INDEX") == 0 ||
                          pg_strcasecmp(prev2_wd, "CONCURRENTLY") == 0) &&
                         pg_strcasecmp(prev_wd, "ON") == 0)
-               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
        /* If we have CREATE|UNIQUE INDEX <sth> CONCURRENTLY, then add "ON" */
        else if ((pg_strcasecmp(prev3_wd, "INDEX") == 0 ||
                          pg_strcasecmp(prev2_wd, "INDEX") == 0) &&
@@ -1827,7 +2140,10 @@ psql_completion(char *text, int start, int end)
        else if (pg_strcasecmp(prev2_wd, "CREATE") == 0 &&
                         pg_strcasecmp(prev_wd, "UNLOGGED") == 0)
        {
-               COMPLETE_WITH_CONST("TABLE");
+               static const char *const list_UNLOGGED[] =
+               {"TABLE", "MATERIALIZED VIEW", NULL};
+
+               COMPLETE_WITH_LIST(list_UNLOGGED);
        }
 
 /* CREATE TABLESPACE */
@@ -1928,7 +2244,7 @@ psql_completion(char *text, int start, int end)
                         prev2_wd[0] != '\0')
                COMPLETE_WITH_CONST("PROCEDURE");
 
-/* CREATE ROLE,USER,GROUP */
+/* CREATE ROLE,USER,GROUP <name> */
        else if (pg_strcasecmp(prev3_wd, "CREATE") == 0 &&
                         !(pg_strcasecmp(prev2_wd, "USER") == 0 && pg_strcasecmp(prev_wd, "MAPPING") == 0) &&
                         (pg_strcasecmp(prev2_wd, "ROLE") == 0 ||
@@ -1939,11 +2255,29 @@ psql_completion(char *text, int start, int end)
                        "ENCRYPTED", "IN", "INHERIT", "LOGIN", "NOCREATEDB",
                        "NOCREATEROLE", "NOCREATEUSER", "NOINHERIT", "NOLOGIN",
                        "NOREPLICATION", "NOSUPERUSER", "REPLICATION", "ROLE",
-               "SUPERUSER", "SYSID", "UNENCRYPTED", "VALID UNTIL", NULL};
+               "SUPERUSER", "SYSID", "UNENCRYPTED", "VALID UNTIL", "WITH", NULL};
 
                COMPLETE_WITH_LIST(list_CREATEROLE);
        }
 
+/* CREATE ROLE,USER,GROUP <name> WITH */
+       else if ((pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
+                         (pg_strcasecmp(prev3_wd, "ROLE") == 0 ||
+                          pg_strcasecmp(prev3_wd, "GROUP") == 0 ||
+                          pg_strcasecmp(prev3_wd, "USER") == 0) &&
+                         pg_strcasecmp(prev_wd, "WITH") == 0))
+       {
+               /* Similar to the above, but don't complete "WITH" again. */
+               static const char *const list_CREATEROLE_WITH[] =
+               {"ADMIN", "CONNECTION LIMIT", "CREATEDB", "CREATEROLE", "CREATEUSER",
+                       "ENCRYPTED", "IN", "INHERIT", "LOGIN", "NOCREATEDB",
+                       "NOCREATEROLE", "NOCREATEUSER", "NOINHERIT", "NOLOGIN",
+                       "NOREPLICATION", "NOSUPERUSER", "REPLICATION", "ROLE",
+               "SUPERUSER", "SYSID", "UNENCRYPTED", "VALID UNTIL", NULL};
+
+               COMPLETE_WITH_LIST(list_CREATEROLE_WITH);
+       }
+
        /*
         * complete CREATE ROLE,USER,GROUP <name> ENCRYPTED,UNENCRYPTED with
         * PASSWORD
@@ -1978,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)
        {
@@ -2028,7 +2378,7 @@ psql_completion(char *text, int start, int end)
        else if (pg_strcasecmp(prev_wd, "DISCARD") == 0)
        {
                static const char *const list_DISCARD[] =
-               {"ALL", "PLANS", "TEMP", NULL};
+               {"ALL", "PLANS", "SEQUENCES", "TEMP", NULL};
 
                COMPLETE_WITH_LIST(list_DISCARD);
        }
@@ -2104,17 +2454,25 @@ psql_completion(char *text, int start, int end)
 
                COMPLETE_WITH_LIST(drop_CREATE_FOREIGN);
        }
+
+       /* DROP MATERIALIZED VIEW */
+       else if (pg_strcasecmp(prev2_wd, "DROP") == 0 &&
+                        pg_strcasecmp(prev_wd, "MATERIALIZED") == 0)
+       {
+               COMPLETE_WITH_CONST("VIEW");
+       }
+       else if (pg_strcasecmp(prev3_wd, "DROP") == 0 &&
+                        pg_strcasecmp(prev2_wd, "MATERIALIZED") == 0 &&
+                        pg_strcasecmp(prev_wd, "VIEW") == 0)
+       {
+               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
+       }
+
        else if (pg_strcasecmp(prev4_wd, "DROP") == 0 &&
                         (pg_strcasecmp(prev3_wd, "AGGREGATE") == 0 ||
                          pg_strcasecmp(prev3_wd, "FUNCTION") == 0) &&
                         pg_strcasecmp(prev_wd, "(") == 0)
-       {
-               char       *tmp_buf = malloc(strlen(Query_for_list_of_arguments) + strlen(prev2_wd));
-
-               sprintf(tmp_buf, Query_for_list_of_arguments, prev2_wd);
-               COMPLETE_WITH_QUERY(tmp_buf);
-               free(tmp_buf);
-       }
+               COMPLETE_WITH_FUNCTION_ARG(prev2_wd);
        /* DROP OWNED BY */
        else if (pg_strcasecmp(prev2_wd, "DROP") == 0 &&
                         pg_strcasecmp(prev_wd, "OWNED") == 0)
@@ -2239,7 +2597,11 @@ psql_completion(char *text, int start, int end)
                                                        " UNION SELECT 'USAGE'"
                                                        " UNION SELECT 'ALL'");
        }
-       /* Complete GRANT/REVOKE <privilege> with "ON", GRANT/REVOKE <role> with TO/FROM */
+
+       /*
+        * 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)
        {
@@ -2281,7 +2643,7 @@ psql_completion(char *text, int start, int end)
        else if ((pg_strcasecmp(prev3_wd, "GRANT") == 0 ||
                          pg_strcasecmp(prev3_wd, "REVOKE") == 0) &&
                         pg_strcasecmp(prev_wd, "ON") == 0)
-               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvf,
+               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf,
                                                                   " UNION SELECT 'DATABASE'"
                                                                   " UNION SELECT 'DOMAIN'"
                                                                   " UNION SELECT 'FOREIGN DATA WRAPPER'"
@@ -2500,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)
        {
@@ -2511,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 ||
@@ -2543,9 +2953,9 @@ psql_completion(char *text, int start, int end)
                          pg_strcasecmp(prev_wd, "ON") == 0))
        {
                static const char *const list_SECURITY_LABEL[] =
-               {"LANGUAGE", "SCHEMA", "SEQUENCE", "TABLE", "TYPE", "VIEW", "COLUMN",
-                       "AGGREGATE", "FUNCTION", "DOMAIN", "LARGE OBJECT",
-               NULL};
+               {"LANGUAGE", "SCHEMA", "SEQUENCE", "TABLE", "TYPE", "VIEW",
+                       "MATERIALIZED VIEW", "COLUMN", "AGGREGATE", "FUNCTION", "DOMAIN",
+               "LARGE OBJECT", NULL};
 
                COMPLETE_WITH_LIST(list_SECURITY_LABEL);
        }
@@ -2637,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)
@@ -2674,6 +3090,7 @@ psql_completion(char *text, int start, int end)
                         pg_strcasecmp(prev_wd, "TABLESPACE") != 0 &&
                         pg_strcasecmp(prev_wd, "SCHEMA") != 0 &&
                         prev_wd[strlen(prev_wd) - 1] != ')' &&
+                        prev_wd[strlen(prev_wd) - 1] != '=' &&
                         pg_strcasecmp(prev4_wd, "DOMAIN") != 0)
                COMPLETE_WITH_CONST("TO");
        /* Suggest possible variable values */
@@ -2785,7 +3202,7 @@ psql_completion(char *text, int start, int end)
  * VACUUM [ FULL | FREEZE ] [ VERBOSE ] ANALYZE [ table [ (column [, ...] ) ] ]
  */
        else if (pg_strcasecmp(prev_wd, "VACUUM") == 0)
-               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
+               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm,
                                                                   " UNION SELECT 'FULL'"
                                                                   " UNION SELECT 'FREEZE'"
                                                                   " UNION SELECT 'ANALYZE'"
@@ -2793,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 */
@@ -2841,11 +3264,11 @@ psql_completion(char *text, int start, int end)
        else if (pg_strcasecmp(prev_wd, "FROM") == 0 &&
                         pg_strcasecmp(prev3_wd, "COPY") != 0 &&
                         pg_strcasecmp(prev3_wd, "\\copy") != 0)
-               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvf, NULL);
+               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
 
 /* ... JOIN ... */
        else if (pg_strcasecmp(prev_wd, "JOIN") == 0)
-               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvf, NULL);
+               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
 
 /* Backslash commands */
 /* TODO:  \dc \dd \dl */
@@ -2885,7 +3308,7 @@ psql_completion(char *text, int start, int end)
                COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
        else if (strncmp(prev_wd, "\\dp", strlen("\\dp")) == 0
                         || strncmp(prev_wd, "\\z", strlen("\\z")) == 0)
-               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvf, NULL);
+               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
        else if (strncmp(prev_wd, "\\ds", strlen("\\ds")) == 0)
                COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences, NULL);
        else if (strncmp(prev_wd, "\\dt", strlen("\\dt")) == 0)
@@ -2897,6 +3320,10 @@ psql_completion(char *text, int start, int end)
                COMPLETE_WITH_QUERY(Query_for_list_of_roles);
        else if (strncmp(prev_wd, "\\dv", strlen("\\dv")) == 0)
                COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL);
+       else if (strncmp(prev_wd, "\\dx", strlen("\\dx")) == 0)
+               COMPLETE_WITH_QUERY(Query_for_list_of_extensions);
+       else if (strncmp(prev_wd, "\\dm", strlen("\\dm")) == 0)
+               COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
 
        /* must be at end of \d list */
        else if (strncmp(prev_wd, "\\d", strlen("\\d")) == 0)
@@ -2948,12 +3375,16 @@ psql_completion(char *text, int start, int end)
                         strcmp(prev_wd, "\\e") == 0 || strcmp(prev_wd, "\\edit") == 0 ||
                         strcmp(prev_wd, "\\g") == 0 ||
                  strcmp(prev_wd, "\\i") == 0 || strcmp(prev_wd, "\\include") == 0 ||
-                 strcmp(prev_wd, "\\ir") == 0 || strcmp(prev_wd, "\\include_relative") == 0 ||
+                        strcmp(prev_wd, "\\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
@@ -3041,7 +3472,7 @@ create_or_drop_command_generator(const char *text, int state, bits32 excluded)
        {
                if ((pg_strncasecmp(name, text, string_length) == 0) &&
                        !(words_after_create[list_index - 1].flags & excluded))
-                       return pg_strdup_same_case(name, text);
+                       return pg_strdup_keyword_case(name, text);
        }
        /* if nothing matches, return NULL */
        return NULL;
@@ -3302,7 +3733,7 @@ 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)
@@ -3328,9 +3759,12 @@ complete_from_list(const char *text, int state)
                        if (completion_case_sensitive)
                                return pg_strdup(item);
                        else
-                               /* If case insensitive matching was requested initially, return
-                                * it in the case of what was already entered. */
-                               return pg_strdup_same_case(item, text);
+
+                               /*
+                                * If case insensitive matching was requested initially,
+                                * adjust the case according to setting.
+                                */
+                               return pg_strdup_keyword_case(item, text);
                }
        }
 
@@ -3361,15 +3795,18 @@ complete_from_list(const char *text, int state)
 static char *
 complete_from_const(const char *text, int state)
 {
-       psql_assert(completion_charp);
+       Assert(completion_charp != NULL);
        if (state == 0)
        {
                if (completion_case_sensitive)
                        return pg_strdup(completion_charp);
                else
-                       /* If case insensitive matching was requested initially, return it
-                        * in the case of what was already entered. */
-                       return pg_strdup_same_case(completion_charp, text);
+
+                       /*
+                        * If case insensitive matching was requested initially, adjust
+                        * the case according to setting.
+                        */
+                       return pg_strdup_keyword_case(completion_charp, text);
        }
        else
                return NULL;
@@ -3385,7 +3822,6 @@ static char **
 complete_from_variables(char *text, const char *prefix, const char *suffix)
 {
        char      **matches;
-       int                     overhead = strlen(prefix) + strlen(suffix) + 1;
        char      **varnames;
        int                     nvars = 0;
        int                     maxvars = 100;
@@ -3396,8 +3832,6 @@ complete_from_variables(char *text, const char *prefix, const char *suffix)
 
        for (ptr = pset.vars->next; ptr; ptr = ptr->next)
        {
-               char       *buffer;
-
                if (nvars >= maxvars)
                {
                        maxvars *= 2;
@@ -3410,13 +3844,11 @@ complete_from_variables(char *text, const char *prefix, const char *suffix)
                        }
                }
 
-               buffer = (char *) pg_malloc(strlen(ptr->name) + overhead);
-               sprintf(buffer, "%s%s%s", prefix, ptr->name, suffix);
-               varnames[nvars++] = buffer;
+               varnames[nvars++] = psprintf("%s%s%s", prefix, ptr->name, suffix);
        }
 
        varnames[nvars] = NULL;
-       COMPLETE_WITH_LIST_CS((const char * const *) varnames);
+       COMPLETE_WITH_LIST_CS((const char *const *) varnames);
 
        for (i = 0; i < nvars; i++)
                free(varnames[i]);
@@ -3426,31 +3858,100 @@ complete_from_variables(char *text, const char *prefix, const char *suffix)
 }
 
 
+/*
+ * 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 it to the same case as ref.
+ * 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_same_case(const char *s, const char *ref)
+pg_strdup_keyword_case(const char *s, const char *ref)
 {
-       char *ret, *p;
+       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;
 
-       if (isalpha(first))
-       {
-               ret = pg_strdup(s);
-               if (islower(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;
-       }
+       /* 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
-               return pg_strdup(s);
+               for (p = ret; *p; p++)
+                       *p = pg_toupper((unsigned char) *p);
+
+       return ret;
 }
 
 
@@ -3483,7 +3984,7 @@ exec_query(const char *query)
 
 
 /*
- * Return the nwords word(s) before point.  Words are returned right to left,
+ * 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.
@@ -3541,7 +4042,7 @@ get_previous_words(int point, char **previous_words, int nwords)
                        {
                                if (buf[start] == '"')
                                        inquotes = !inquotes;
-                               else if (!inquotes)
+                               if (!inquotes)
                                {
                                        if (buf[start] == ')')
                                                parentheses++;