diff --git a/src/bin/psql/input.h b/src/bin/psql/input.h index bcabd6d96..fb74c8c71 100644 --- a/src/bin/psql/input.h +++ b/src/bin/psql/input.h @@ -16,8 +16,6 @@ */ #define USE_READLINE 1 -#include - #ifdef HAVE_LIBREADLINE #define USE_READLINE 1 @@ -37,6 +35,8 @@ #include #endif #endif /* HAVE_READLINE_READLINE_H, etc */ +#else +#include #endif /* HAVE_LIBREADLINE */ #include "libpq/pqexpbuffer.h" diff --git a/src/bin/psql/tab-complete.cpp b/src/bin/psql/tab-complete.cpp index 6b555f4f1..274a1cda2 100644 --- a/src/bin/psql/tab-complete.cpp +++ b/src/bin/psql/tab-complete.cpp @@ -121,13 +121,11 @@ 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* const* completion_charpp; /* to pass a list of strings */ -#ifdef NOT_USED +static const char* const* completion_charpp; /* to pass a list of strings */ static const char* completion_charp; /* to pass a string */ 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 */ -#endif static bool completion_case_sensitive; /* completion is case sensitive */ /* @@ -728,9 +726,8 @@ static const pgsql_thing_t words_after_create[] = { {NULL, NULL, NULL, 0} /* end of list */ }; -#ifdef NOT_USED /* Forward declaration of functions */ -/*psql_completion is deleted because it's too complex and not be used at all. */ +static char **psql_completion(const char *text, int start, int end); static char* create_command_generator(const char* text, int state); static char* drop_command_generator(const char* text, int state); static char* complete_from_query(const char* text, int state); @@ -738,16 +735,14 @@ 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_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_variables(const char* text, const char* prefix, const char* suffix); static char* complete_from_files(const char* text, int state); -#endif static char* pg_strdup_keyword_case(const char* s, const char* ref); -#ifdef NOT_USED static PGresult* exec_query(const char* query); static void get_previous_words(int point, char** previous_words, int nwords); - +#ifdef NOT_USED static char* quote_file_name(char* text, int match_type, char* quote_pointer); static char* dequote_file_name(char* text, char quote_char); #endif @@ -760,7 +755,7 @@ void initialize_readline(void) rl_readline_name = (char*)pset.progname; /* psql_completion is deleted because it's too complex and not be used at all. */ - rl_attempted_completion_function = NULL; + rl_attempted_completion_function = psql_completion; rl_basic_word_break_characters = WORD_BREAKS; @@ -772,7 +767,6 @@ void initialize_readline(void) */ } -#ifdef NOT_USED /* * GENERATOR FUNCTIONS * @@ -837,6 +831,2371 @@ static char* complete_from_schema_query(const char* text, int state) { return _complete_from_query(1, text, state); } +/* + * 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(const char *text, int start, int end) +{ + /* This is the variable we'll return. */ + char **matches = NULL; + + /* This array will contain some scannage of the input line. */ + char *previous_words[6]; + + /* For compactness, we use these macros to reference previous_words[]. */ +#define prev_wd (previous_words[0]) +#define prev2_wd (previous_words[1]) +#define prev3_wd (previous_words[2]) +#define prev4_wd (previous_words[3]) +#define prev5_wd (previous_words[4]) +#define prev6_wd (previous_words[5]) + + static const char *const sql_commands[] = { + "ABORT", "ALTER", "ANALYZE", "BEGIN", "CHECKPOINT", "CLOSE", "CLUSTER", + "COMMENT", "COMMIT", "COPY", "CREATE", "DEALLOCATE", "DECLARE", + "DELETE FROM", "DISCARD", "DO", "DROP", "END", "EXECUTE", "EXPLAIN", "FETCH", + "GRANT", "INSERT", "LISTEN", "LOAD", "LOCK", "MOVE", "NOTIFY", "PREPARE", + "REASSIGN", "REINDEX", "RELEASE", "RESET", "REVOKE", "ROLLBACK", + "SAVEPOINT", "SECURITY LABEL", "SELECT", "SET", "SHOW", "START", + "TABLE", "TRUNCATE", "UNLISTEN", "UPDATE", "VACUUM", "VALUES", "WITH", + NULL + }; + + static const char *const backslash_commands[] = { + "\\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", + "\\e", "\\echo", "\\ef", "\\encoding", + "\\f", "\\g", "\\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 + }; + + (void) end; /* not used */ + +#ifdef HAVE_RL_COMPLETION_APPEND_CHARACTER + rl_completion_append_character = ' '; +#endif + + /* Clear a few things. */ + completion_charp = NULL; + completion_charpp = NULL; + completion_info_charp = NULL; + completion_info_charp2 = NULL; + + /* + * Scan the input line before our current position for the last 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)); + + /* If a backslash command was started, continue */ + if (text[0] == '\\') + 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[0] == '\0') + COMPLETE_WITH_LIST(sql_commands); + +/* CREATE */ + /* complete with something you can create */ + else if (pg_strcasecmp(prev_wd, "CREATE") == 0) + matches = completion_matches(text, create_command_generator); + +/* DROP, but not DROP embedded in other commands */ + /* complete with something you can drop */ + else if (pg_strcasecmp(prev_wd, "DROP") == 0 && + prev2_wd[0] == '\0') + matches = completion_matches(text, drop_command_generator); + +/* ALTER */ + + /* + * complete with what you can alter (TABLE, GROUP, USER, ...) unless we're + * in ALTER TABLE sth ALTER + */ + else if (pg_strcasecmp(prev_wd, "ALTER") == 0 && + pg_strcasecmp(prev3_wd, "TABLE") != 0) + { + 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", + "TABLESPACE", "TEXT SEARCH", "TRIGGER", "TYPE", + "USER", "USER MAPPING FOR", "VIEW", NULL}; + + COMPLETE_WITH_LIST(list_ALTER); + } + /* ALTER AGGREGATE,FUNCTION */ + 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 (...) */ + else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 && + (pg_strcasecmp(prev3_wd, "AGGREGATE") == 0 || + pg_strcasecmp(prev3_wd, "FUNCTION") == 0)) + { + 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); + } + else + { + char *tmp_buf = (char *)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); + } + } + + /* ALTER SCHEMA */ + else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 && + pg_strcasecmp(prev2_wd, "SCHEMA") == 0) + { + static const char *const list_ALTERGEN[] = + {"OWNER TO", "RENAME TO", NULL}; + + COMPLETE_WITH_LIST(list_ALTERGEN); + } + + /* ALTER COLLATION */ + 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 */ + 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 */ + else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 && + pg_strcasecmp(prev2_wd, "DATABASE") == 0) + { + static const char *const list_ALTERDATABASE[] = + {"RESET", "SET", "OWNER TO", "RENAME TO", "CONNECTION LIMIT", NULL}; + + COMPLETE_WITH_LIST(list_ALTERDATABASE); + } + + /* ALTER EXTENSION */ + 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 */ + 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 */ + 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 */ + else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 && + pg_strcasecmp(prev2_wd, "INDEX") == 0) + { + static const char *const list_ALTERINDEX[] = + {"OWNER TO", "RENAME TO", "SET", "RESET", NULL}; + + COMPLETE_WITH_LIST(list_ALTERINDEX); + } + /* ALTER INDEX 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 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 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 */ + else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 && + pg_strcasecmp(prev2_wd, "LANGUAGE") == 0) + { + static const char *const list_ALTERLANGUAGE[] = + {"OWNER TO", "RENAME TO", NULL}; + + COMPLETE_WITH_LIST(list_ALTERLANGUAGE); + } + + /* ALTER LARGE OBJECT */ + else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 && + pg_strcasecmp(prev3_wd, "LARGE") == 0 && + pg_strcasecmp(prev2_wd, "OBJECT") == 0) + { + static const char *const list_ALTERLARGEOBJECT[] = + {"OWNER TO", NULL}; + + COMPLETE_WITH_LIST(list_ALTERLARGEOBJECT); + } + + /* ALTER USER,ROLE */ + 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[] = + {"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 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 ENCRYPTED,UNENCRYPTED with PASSWORD */ + else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 && + (pg_strcasecmp(prev3_wd, "ROLE") == 0 || pg_strcasecmp(prev3_wd, "USER") == 0) && + (pg_strcasecmp(prev_wd, "ENCRYPTED") == 0 || pg_strcasecmp(prev_wd, "UNENCRYPTED") == 0)) + { + COMPLETE_WITH_CONST("PASSWORD"); + } + /* ALTER 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 */ + else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 && + pg_strcasecmp(prev2_wd, "DOMAIN") == 0) + { + static const char *const list_ALTERDOMAIN[] = + {"ADD", "DROP", "OWNER TO", "RENAME", "SET", "VALIDATE CONSTRAINT", NULL}; + + COMPLETE_WITH_LIST(list_ALTERDOMAIN); + } + /* ALTER DOMAIN DROP */ + else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 && + pg_strcasecmp(prev3_wd, "DOMAIN") == 0 && + pg_strcasecmp(prev_wd, "DROP") == 0) + { + static const char *const list_ALTERDOMAIN2[] = + {"CONSTRAINT", "DEFAULT", "NOT NULL", NULL}; + + COMPLETE_WITH_LIST(list_ALTERDOMAIN2); + } + /* ALTER DOMAIN 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 RENAME CONSTRAINT */ + 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 SET */ + else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 && + pg_strcasecmp(prev3_wd, "DOMAIN") == 0 && + pg_strcasecmp(prev_wd, "SET") == 0) + { + static const char *const list_ALTERDOMAIN3[] = + {"DEFAULT", "NOT NULL", "SCHEMA", NULL}; + + COMPLETE_WITH_LIST(list_ALTERDOMAIN3); + } + /* ALTER SEQUENCE */ + else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 && + pg_strcasecmp(prev2_wd, "SEQUENCE") == 0) + { + static const char *const list_ALTERSEQUENCE[] = + {"INCREMENT", "MINVALUE", "MAXVALUE", "RESTART", "NO", "CACHE", "CYCLE", + "SET SCHEMA", "OWNED BY", "OWNER TO", "RENAME TO", NULL}; + + COMPLETE_WITH_LIST(list_ALTERSEQUENCE); + } + /* ALTER SEQUENCE NO */ + else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 && + pg_strcasecmp(prev3_wd, "SEQUENCE") == 0 && + pg_strcasecmp(prev_wd, "NO") == 0) + { + static const char *const list_ALTERSEQUENCE2[] = + {"MINVALUE", "MAXVALUE", "CYCLE", NULL}; + + COMPLETE_WITH_LIST(list_ALTERSEQUENCE2); + } + /* ALTER SERVER */ + 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 */ + else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 && + pg_strcasecmp(prev2_wd, "VIEW") == 0) + { + static const char *const list_ALTERVIEW[] = + {"ALTER COLUMN", "OWNER TO", "RENAME TO", "SET SCHEMA", NULL}; + + COMPLETE_WITH_LIST(list_ALTERVIEW); + } + /* ALTER TRIGGER , add ON */ + else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 && + pg_strcasecmp(prev2_wd, "TRIGGER") == 0) + COMPLETE_WITH_CONST("ON"); + + else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 && + pg_strcasecmp(prev3_wd, "TRIGGER") == 0) + { + completion_info_charp = prev2_wd; + COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger); + } + + /* + * If we have ALTER TRIGGER ON, then add the correct tablename + */ + else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 && + pg_strcasecmp(prev3_wd, "TRIGGER") == 0 && + pg_strcasecmp(prev_wd, "ON") == 0) + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL); + + /* ALTER TRIGGER ON */ + else if (pg_strcasecmp(prev4_wd, "TRIGGER") == 0 && + pg_strcasecmp(prev2_wd, "ON") == 0) + COMPLETE_WITH_CONST("RENAME TO"); + + /* + * If we detect ALTER TABLE , 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", + "VALIDATE CONSTRAINT", NULL}; + + COMPLETE_WITH_LIST(list_ALTER2); + } + /* ALTER TABLE xxx ENABLE */ + else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 && + pg_strcasecmp(prev3_wd, "TABLE") == 0 && + pg_strcasecmp(prev_wd, "ENABLE") == 0) + { + static const char *const list_ALTERENABLE[] = + {"ALWAYS", "REPLICA", "RULE", "TRIGGER", NULL}; + + COMPLETE_WITH_LIST(list_ALTERENABLE); + } + else if (pg_strcasecmp(prev4_wd, "TABLE") == 0 && + pg_strcasecmp(prev2_wd, "ENABLE") == 0 && + (pg_strcasecmp(prev_wd, "REPLICA") == 0 || + pg_strcasecmp(prev_wd, "ALWAYS") == 0)) + { + static const char *const list_ALTERENABLE2[] = + {"RULE", "TRIGGER", NULL}; + + COMPLETE_WITH_LIST(list_ALTERENABLE2); + } + else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 && + pg_strcasecmp(prev3_wd, "TABLE") == 0 && + pg_strcasecmp(prev_wd, "DISABLE") == 0) + { + static const char *const list_ALTERDISABLE[] = + {"RULE", "TRIGGER", NULL}; + + COMPLETE_WITH_LIST(list_ALTERDISABLE); + } + + /* 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 ALTER COLUMN|RENAME COLUMN, provide list of + * columns + */ + else if (pg_strcasecmp(prev4_wd, "TABLE") == 0 && + (pg_strcasecmp(prev2_wd, "ALTER") == 0 || + pg_strcasecmp(prev2_wd, "RENAME") == 0) && + pg_strcasecmp(prev_wd, "COLUMN") == 0) + COMPLETE_WITH_ATTR(prev3_wd, ""); + + /* ALTER TABLE xxx RENAME yyy */ + else if (pg_strcasecmp(prev4_wd, "TABLE") == 0 && + 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/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, "CONSTRAINT") == 0) && + pg_strcasecmp(prev_wd, "TO") != 0) + COMPLETE_WITH_CONST("TO"); + + /* If we have TABLE DROP, provide COLUMN or CONSTRAINT */ + else if (pg_strcasecmp(prev3_wd, "TABLE") == 0 && + pg_strcasecmp(prev_wd, "DROP") == 0) + { + static const char *const list_TABLEDROP[] = + {"COLUMN", "CONSTRAINT", NULL}; + + COMPLETE_WITH_LIST(list_TABLEDROP); + } + /* If we have TABLE DROP COLUMN, provide list of columns */ + else if (pg_strcasecmp(prev4_wd, "TABLE") == 0 && + pg_strcasecmp(prev2_wd, "DROP") == 0 && + pg_strcasecmp(prev_wd, "COLUMN") == 0) + COMPLETE_WITH_ATTR(prev3_wd, ""); + /* ALTER TABLE ALTER [COLUMN] */ + 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)) + { + static const char *const list_COLUMNALTER[] = + {"TYPE", "SET", "RESET", "DROP", NULL}; + + COMPLETE_WITH_LIST(list_COLUMNALTER); + } + /* ALTER TABLE ALTER [COLUMN] 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] 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] 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] 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"); + else if (pg_strcasecmp(prev4_wd, "TABLE") == 0 && + pg_strcasecmp(prev2_wd, "CLUSTER") == 0 && + pg_strcasecmp(prev_wd, "ON") == 0) + { + completion_info_charp = prev3_wd; + COMPLETE_WITH_QUERY(Query_for_index_of_table); + } + /* If we have TABLE SET, provide WITHOUT,TABLESPACE and SCHEMA */ + else if (pg_strcasecmp(prev3_wd, "TABLE") == 0 && + pg_strcasecmp(prev_wd, "SET") == 0) + { + static const char *const list_TABLESET[] = + {"(", "WITHOUT", "TABLESPACE", "SCHEMA", NULL}; + + COMPLETE_WITH_LIST(list_TABLESET); + } + /* If we have TABLE SET TABLESPACE provide a list of tablespaces */ + else if (pg_strcasecmp(prev4_wd, "TABLE") == 0 && + pg_strcasecmp(prev2_wd, "SET") == 0 && + pg_strcasecmp(prev_wd, "TABLESPACE") == 0) + COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces); + /* If we have TABLE SET WITHOUT provide CLUSTER or OIDS */ + else if (pg_strcasecmp(prev4_wd, "TABLE") == 0 && + pg_strcasecmp(prev2_wd, "SET") == 0 && + pg_strcasecmp(prev_wd, "WITHOUT") == 0) + { + static const char *const list_TABLESET2[] = + {"CLUSTER", "OIDS", NULL}; + + COMPLETE_WITH_LIST(list_TABLESET2); + } + /* ALTER TABLE RESET */ + else if (pg_strcasecmp(prev3_wd, "TABLE") == 0 && + pg_strcasecmp(prev_wd, "RESET") == 0) + COMPLETE_WITH_CONST("("); + /* ALTER TABLE 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 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", "SET", "RESET", NULL}; + + COMPLETE_WITH_LIST(list_ALTERTSPC); + } + /* ALTER TABLESPACE 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 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 && + pg_strcasecmp(prev_wd, "SEARCH") == 0) + { + static const char *const list_ALTERTEXTSEARCH[] = + {"CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE", NULL}; + + COMPLETE_WITH_LIST(list_ALTERTEXTSEARCH); + } + else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 && + pg_strcasecmp(prev4_wd, "TEXT") == 0 && + pg_strcasecmp(prev3_wd, "SEARCH") == 0 && + (pg_strcasecmp(prev2_wd, "TEMPLATE") == 0 || + pg_strcasecmp(prev2_wd, "PARSER") == 0)) + { + 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_ALTERTEXTSEARCH3[] = + {"OWNER TO", "RENAME TO", "SET SCHEMA", NULL}; + + COMPLETE_WITH_LIST(list_ALTERTEXTSEARCH3); + } + + else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 && + pg_strcasecmp(prev4_wd, "TEXT") == 0 && + pg_strcasecmp(prev3_wd, "SEARCH") == 0 && + pg_strcasecmp(prev2_wd, "CONFIGURATION") == 0) + { + static const char *const list_ALTERTEXTSEARCH4[] = + {"ADD MAPPING FOR", "ALTER MAPPING", "DROP MAPPING FOR", "OWNER TO", "RENAME TO", "SET SCHEMA", NULL}; + + COMPLETE_WITH_LIST(list_ALTERTEXTSEARCH4); + } + + /* complete ALTER TYPE with actions */ + else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 && + pg_strcasecmp(prev2_wd, "TYPE") == 0) + { + static const char *const list_ALTERTYPE[] = + {"ADD ATTRIBUTE", "ADD VALUE", "ALTER ATTRIBUTE", "DROP ATTRIBUTE", + "OWNER TO", "RENAME", "SET SCHEMA", NULL}; + + COMPLETE_WITH_LIST(list_ALTERTYPE); + } + /* complete ALTER TYPE 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 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 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 */ + else if ((pg_strcasecmp(prev3_wd, "ALTER") == 0 && + pg_strcasecmp(prev2_wd, "ATTRIBUTE") == 0)) + { + COMPLETE_WITH_CONST("TYPE"); + } + /* complete ALTER GROUP */ + else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 && + pg_strcasecmp(prev2_wd, "GROUP") == 0) + { + static const char *const list_ALTERGROUP[] = + {"ADD USER", "DROP USER", "RENAME TO", NULL}; + + COMPLETE_WITH_LIST(list_ALTERGROUP); + } + /* complete ALTER GROUP ADD|DROP with USER */ + else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 && + pg_strcasecmp(prev3_wd, "GROUP") == 0 && + (pg_strcasecmp(prev_wd, "ADD") == 0 || + pg_strcasecmp(prev_wd, "DROP") == 0)) + COMPLETE_WITH_CONST("USER"); + /* complete {ALTER} GROUP ADD|DROP USER with a user name */ + else if (pg_strcasecmp(prev4_wd, "GROUP") == 0 && + (pg_strcasecmp(prev2_wd, "ADD") == 0 || + pg_strcasecmp(prev2_wd, "DROP") == 0) && + pg_strcasecmp(prev_wd, "USER") == 0) + COMPLETE_WITH_QUERY(Query_for_list_of_roles); + +/* BEGIN, END, ABORT */ + else if (pg_strcasecmp(prev_wd, "BEGIN") == 0 || + pg_strcasecmp(prev_wd, "END") == 0 || + pg_strcasecmp(prev_wd, "ABORT") == 0) + { + static const char *const list_TRANS[] = + {"WORK", "TRANSACTION", NULL}; + + COMPLETE_WITH_LIST(list_TRANS); + } +/* COMMIT */ + else if (pg_strcasecmp(prev_wd, "COMMIT") == 0) + { + static const char *const list_COMMIT[] = + {"WORK", "TRANSACTION", "PREPARED", NULL}; + + COMPLETE_WITH_LIST(list_COMMIT); + } +/* RELEASE SAVEPOINT */ + else if (pg_strcasecmp(prev_wd, "RELEASE") == 0) + COMPLETE_WITH_CONST("SAVEPOINT"); +/* ROLLBACK*/ + else if (pg_strcasecmp(prev_wd, "ROLLBACK") == 0) + { + static const char *const list_TRANS[] = + {"WORK", "TRANSACTION", "TO SAVEPOINT", "PREPARED", NULL}; + + COMPLETE_WITH_LIST(list_TRANS); + } +/* CLUSTER */ + + /* + * 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); + /* If we have CLUSTER , then add "USING" */ + else if (pg_strcasecmp(prev2_wd, "CLUSTER") == 0 && + pg_strcasecmp(prev_wd, "ON") != 0) + { + COMPLETE_WITH_CONST("USING"); + } + + /* + * If we have CLUSTER USING, then add the index as well. + */ + else if (pg_strcasecmp(prev3_wd, "CLUSTER") == 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"); + else if (pg_strcasecmp(prev2_wd, "COMMENT") == 0 && + pg_strcasecmp(prev_wd, "ON") == 0) + { + static const char *const list_COMMENT[] = + {"CAST", "COLLATION", "CONVERSION", "DATABASE", "EXTENSION", + "FOREIGN DATA WRAPPER", "FOREIGN TABLE", + "SERVER", "INDEX", "LANGUAGE", "RULE", "SCHEMA", "SEQUENCE", + "TABLE", "TYPE", "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 && + pg_strcasecmp(prev_wd, "SEARCH") == 0) + { + static const char *const list_TRANS2[] = + {"CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE", NULL}; + + COMPLETE_WITH_LIST(list_TRANS2); + } + else if ((pg_strcasecmp(prev4_wd, "COMMENT") == 0 && + pg_strcasecmp(prev3_wd, "ON") == 0) || + (pg_strcasecmp(prev5_wd, "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 */ + + /* + * If we have COPY [BINARY] (which you'd have to type yourself), offer + * list of tables (Also cover the analogous backslash command) + */ + else if (pg_strcasecmp(prev_wd, "COPY") == 0 || + pg_strcasecmp(prev_wd, "\\copy") == 0 || + (pg_strcasecmp(prev2_wd, "COPY") == 0 && + pg_strcasecmp(prev_wd, "BINARY") == 0)) + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL); + /* If we have COPY|BINARY , complete it with "TO" or "FROM" */ + else if (pg_strcasecmp(prev2_wd, "COPY") == 0 || + pg_strcasecmp(prev2_wd, "\\copy") == 0 || + pg_strcasecmp(prev2_wd, "BINARY") == 0) + { + static const char *const list_FROMTO[] = + {"FROM", "TO", NULL}; + + COMPLETE_WITH_LIST(list_FROMTO); + } + /* If we have COPY|BINARY FROM|TO, complete with filename */ + else if ((pg_strcasecmp(prev3_wd, "COPY") == 0 || + pg_strcasecmp(prev3_wd, "\\copy") == 0 || + pg_strcasecmp(prev3_wd, "BINARY") == 0) && + (pg_strcasecmp(prev_wd, "FROM") == 0 || + pg_strcasecmp(prev_wd, "TO") == 0)) + { + completion_charp = ""; + matches = completion_matches(text, complete_from_files); + } + + /* Handle COPY|BINARY FROM|TO filename */ + else if ((pg_strcasecmp(prev4_wd, "COPY") == 0 || + pg_strcasecmp(prev4_wd, "\\copy") == 0 || + pg_strcasecmp(prev4_wd, "BINARY") == 0) && + (pg_strcasecmp(prev2_wd, "FROM") == 0 || + pg_strcasecmp(prev2_wd, "TO") == 0)) + { + static const char *const list_COPY[] = + {"BINARY", "OIDS", "DELIMITER", "NULL", "CSV", "ENCODING", NULL}; + + COMPLETE_WITH_LIST(list_COPY); + } + + /* Handle COPY|BINARY FROM|TO filename CSV */ + else if (pg_strcasecmp(prev_wd, "CSV") == 0 && + (pg_strcasecmp(prev3_wd, "FROM") == 0 || + pg_strcasecmp(prev3_wd, "TO") == 0)) + { + static const char *const list_CSV[] = + {"HEADER", "QUOTE", "ESCAPE", "FORCE QUOTE", "FORCE NOT NULL", NULL}; + + COMPLETE_WITH_LIST(list_CSV); + } + + /* CREATE DATABASE */ + else if (pg_strcasecmp(prev3_wd, "CREATE") == 0 && + pg_strcasecmp(prev2_wd, "DATABASE") == 0) + { + static const char *const list_DATABASE[] = + {"OWNER", "TEMPLATE", "ENCODING", "TABLESPACE", "CONNECTION LIMIT", + NULL}; + + COMPLETE_WITH_LIST(list_DATABASE); + } + + else if (pg_strcasecmp(prev4_wd, "CREATE") == 0 && + pg_strcasecmp(prev3_wd, "DATABASE") == 0 && + pg_strcasecmp(prev_wd, "TEMPLATE") == 0) + COMPLETE_WITH_QUERY(Query_for_list_of_template_databases); + + /* CREATE 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 */ + 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, 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 [] 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); + /* If we have CREATE|UNIQUE INDEX 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 , 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 ON with a list of table columns (which + * should really be in parens) + */ + 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) + { + static const char *const list_CREATE_INDEX2[] = + {"(", "USING", NULL}; + + COMPLETE_WITH_LIST(list_CREATE_INDEX2); + } + 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(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) + 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 " with "AS" */ + else if (pg_strcasecmp(prev3_wd, "CREATE") == 0 && + pg_strcasecmp(prev2_wd, "RULE") == 0) + COMPLETE_WITH_CONST("AS"); + /* Complete "CREATE RULE AS with "ON" */ + else if (pg_strcasecmp(prev4_wd, "CREATE") == 0 && + pg_strcasecmp(prev3_wd, "RULE") == 0 && + pg_strcasecmp(prev_wd, "AS") == 0) + COMPLETE_WITH_CONST("ON"); + /* Complete "RULE * AS ON" with SELECT|UPDATE|DELETE|INSERT */ + else if (pg_strcasecmp(prev4_wd, "RULE") == 0 && + pg_strcasecmp(prev2_wd, "AS") == 0 && + pg_strcasecmp(prev_wd, "ON") == 0) + { + static const char *const rule_events[] = + {"SELECT", "UPDATE", "INSERT", "DELETE", NULL}; + + COMPLETE_WITH_LIST(rule_events); + } + /* Complete "AS ON " with a "TO" */ + else if (pg_strcasecmp(prev3_wd, "AS") == 0 && + pg_strcasecmp(prev2_wd, "ON") == 0 && + (pg_toupper((unsigned char) prev_wd[4]) == 'T' || + pg_toupper((unsigned char) prev_wd[5]) == 'T')) + COMPLETE_WITH_CONST("TO"); + /* Complete "AS ON TO" with a table name */ + else if (pg_strcasecmp(prev4_wd, "AS") == 0 && + pg_strcasecmp(prev3_wd, "ON") == 0 && + pg_strcasecmp(prev_wd, "TO") == 0) + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL); + +/* CREATE SERVER */ + 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 && + (pg_strcasecmp(prev_wd, "TEMP") == 0 || + pg_strcasecmp(prev_wd, "TEMPORARY") == 0)) + { + static const char *const list_TEMP[] = + {"SEQUENCE", "TABLE", "VIEW", NULL}; + + COMPLETE_WITH_LIST(list_TEMP); + } + /* Complete "CREATE UNLOGGED" with TABLE */ + else if (pg_strcasecmp(prev2_wd, "CREATE") == 0 && + pg_strcasecmp(prev_wd, "UNLOGGED") == 0) + { + COMPLETE_WITH_CONST("TABLE"); + } + +/* CREATE TABLESPACE */ + else if (pg_strcasecmp(prev3_wd, "CREATE") == 0 && + pg_strcasecmp(prev2_wd, "TABLESPACE") == 0) + { + static const char *const list_CREATETABLESPACE[] = + {"OWNER", "LOCATION", NULL}; + + COMPLETE_WITH_LIST(list_CREATETABLESPACE); + } + /* Complete CREATE TABLESPACE name OWNER name with "LOCATION" */ + else if (pg_strcasecmp(prev5_wd, "CREATE") == 0 && + pg_strcasecmp(prev4_wd, "TABLESPACE") == 0 && + pg_strcasecmp(prev2_wd, "OWNER") == 0) + { + COMPLETE_WITH_CONST("LOCATION"); + } + +/* CREATE TEXT SEARCH */ + else if (pg_strcasecmp(prev3_wd, "CREATE") == 0 && + pg_strcasecmp(prev2_wd, "TEXT") == 0 && + pg_strcasecmp(prev_wd, "SEARCH") == 0) + { + static const char *const list_CREATETEXTSEARCH[] = + {"CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE", NULL}; + + COMPLETE_WITH_LIST(list_CREATETEXTSEARCH); + } + else if (pg_strcasecmp(prev4_wd, "TEXT") == 0 && + pg_strcasecmp(prev3_wd, "SEARCH") == 0 && + pg_strcasecmp(prev2_wd, "CONFIGURATION") == 0) + COMPLETE_WITH_CONST("("); + +/* CREATE TRIGGER */ + /* complete CREATE TRIGGER with BEFORE,AFTER */ + else if (pg_strcasecmp(prev3_wd, "CREATE") == 0 && + pg_strcasecmp(prev2_wd, "TRIGGER") == 0) + { + static const char *const list_CREATETRIGGER[] = + {"BEFORE", "AFTER", "INSTEAD OF", NULL}; + + COMPLETE_WITH_LIST(list_CREATETRIGGER); + } + /* complete CREATE TRIGGER 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 INSTEAD OF with an event */ + else if (pg_strcasecmp(prev5_wd, "CREATE") == 0 && + pg_strcasecmp(prev4_wd, "TRIGGER") == 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 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}; + + COMPLETE_WITH_LIST(list_CREATETRIGGER2); + } + + /* + * complete CREATE TRIGGER 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 */ + 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", "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 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 ENCRYPTED,UNENCRYPTED with + * PASSWORD + */ + else if (pg_strcasecmp(prev4_wd, "CREATE") == 0 && + (pg_strcasecmp(prev3_wd, "ROLE") == 0 || + pg_strcasecmp(prev3_wd, "GROUP") == 0 || pg_strcasecmp(prev3_wd, "USER") == 0) && + (pg_strcasecmp(prev_wd, "ENCRYPTED") == 0 || pg_strcasecmp(prev_wd, "UNENCRYPTED") == 0)) + { + COMPLETE_WITH_CONST("PASSWORD"); + } + /* complete CREATE ROLE,USER,GROUP IN with ROLE,GROUP */ + else if (pg_strcasecmp(prev4_wd, "CREATE") == 0 && + (pg_strcasecmp(prev3_wd, "ROLE") == 0 || + pg_strcasecmp(prev3_wd, "GROUP") == 0 || pg_strcasecmp(prev3_wd, "USER") == 0) && + pg_strcasecmp(prev_wd, "IN") == 0) + { + static const char *const list_CREATEROLE3[] = + {"GROUP", "ROLE", NULL}; + + COMPLETE_WITH_LIST(list_CREATEROLE3); + } + +/* CREATE VIEW */ + /* Complete CREATE VIEW with AS */ + else if (pg_strcasecmp(prev3_wd, "CREATE") == 0 && + pg_strcasecmp(prev2_wd, "VIEW") == 0) + COMPLETE_WITH_CONST("AS"); + /* Complete "CREATE VIEW AS with "SELECT" */ + else if (pg_strcasecmp(prev4_wd, "CREATE") == 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) + { + static const char *const list_DECLARE[] = + {"BINARY", "INSENSITIVE", "SCROLL", "NO SCROLL", "CURSOR", NULL}; + + COMPLETE_WITH_LIST(list_DECLARE); + } + +/* CURSOR */ + else if (pg_strcasecmp(prev_wd, "CURSOR") == 0) + { + static const char *const list_DECLARECURSOR[] = + {"WITH HOLD", "WITHOUT HOLD", "FOR", NULL}; + + COMPLETE_WITH_LIST(list_DECLARECURSOR); + } + + +/* DELETE */ + + /* + * Complete DELETE with FROM (only if the word before that is not "ON" + * (cf. rules) or "BEFORE" or "AFTER" (cf. triggers) or GRANT) + */ + else if (pg_strcasecmp(prev_wd, "DELETE") == 0 && + !(pg_strcasecmp(prev2_wd, "ON") == 0 || + pg_strcasecmp(prev2_wd, "GRANT") == 0 || + pg_strcasecmp(prev2_wd, "BEFORE") == 0 || + pg_strcasecmp(prev2_wd, "AFTER") == 0)) + COMPLETE_WITH_CONST("FROM"); + /* 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_deletables, NULL); + /* Complete DELETE FROM
*/ + else if (pg_strcasecmp(prev3_wd, "DELETE") == 0 && + pg_strcasecmp(prev2_wd, "FROM") == 0) + { + static const char *const list_DELETE[] = + {"USING", "WHERE", "SET", NULL}; + + COMPLETE_WITH_LIST(list_DELETE); + } + /* XXX: implement tab completion for DELETE ... USING */ + +/* DISCARD */ + else if (pg_strcasecmp(prev_wd, "DISCARD") == 0) + { + static const char *const list_DISCARD[] = + {"ALL", "PLANS", "TEMP", NULL}; + + COMPLETE_WITH_LIST(list_DISCARD); + } + +/* 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 && + pg_strcasecmp(prev2_wd, "AGGREGATE") == 0) + COMPLETE_WITH_CONST("("); + + /* DROP object with CASCADE / RESTRICT */ + else if ((pg_strcasecmp(prev3_wd, "DROP") == 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 && + (pg_strcasecmp(prev2_wd, "CONFIGURATION") == 0 || + pg_strcasecmp(prev2_wd, "DICTIONARY") == 0 || + pg_strcasecmp(prev2_wd, "PARSER") == 0 || + pg_strcasecmp(prev2_wd, "TEMPLATE") == 0)) + ) + { + if (pg_strcasecmp(prev3_wd, "DROP") == 0 && + pg_strcasecmp(prev2_wd, "FUNCTION") == 0) + { + COMPLETE_WITH_CONST("("); + } + else + { + static const char *const list_DROPCR[] = + {"CASCADE", "RESTRICT", NULL}; + + COMPLETE_WITH_LIST(list_DROPCR); + } + } + else if (pg_strcasecmp(prev2_wd, "DROP") == 0 && + pg_strcasecmp(prev_wd, "FOREIGN") == 0) + { + static const char *const drop_CREATE_FOREIGN[] = + {"DATA WRAPPER", "TABLE", NULL}; + + COMPLETE_WITH_LIST(drop_CREATE_FOREIGN); + } + 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 = (char *)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); + } + /* DROP OWNED BY */ + else if (pg_strcasecmp(prev2_wd, "DROP") == 0 && + pg_strcasecmp(prev_wd, "OWNED") == 0) + COMPLETE_WITH_CONST("BY"); + else if (pg_strcasecmp(prev3_wd, "DROP") == 0 && + pg_strcasecmp(prev2_wd, "OWNED") == 0 && + pg_strcasecmp(prev_wd, "BY") == 0) + COMPLETE_WITH_QUERY(Query_for_list_of_roles); + else if (pg_strcasecmp(prev3_wd, "DROP") == 0 && + pg_strcasecmp(prev2_wd, "TEXT") == 0 && + pg_strcasecmp(prev_wd, "SEARCH") == 0) + { + + static const char *const list_ALTERTEXTSEARCH[] = + {"CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE", NULL}; + + COMPLETE_WITH_LIST(list_ALTERTEXTSEARCH); + } + +/* 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 */ + + /* + * Complete EXPLAIN [ANALYZE] [VERBOSE] with list of EXPLAIN-able commands + */ + else if (pg_strcasecmp(prev_wd, "EXPLAIN") == 0) + { + static const char *const list_EXPLAIN[] = + {"SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE", "ANALYZE", "VERBOSE", NULL}; + + COMPLETE_WITH_LIST(list_EXPLAIN); + } + else if (pg_strcasecmp(prev2_wd, "EXPLAIN") == 0 && + pg_strcasecmp(prev_wd, "ANALYZE") == 0) + { + static const char *const list_EXPLAIN[] = + {"SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE", "VERBOSE", NULL}; + + COMPLETE_WITH_LIST(list_EXPLAIN); + } + else if ((pg_strcasecmp(prev2_wd, "EXPLAIN") == 0 && + pg_strcasecmp(prev_wd, "VERBOSE") == 0) || + (pg_strcasecmp(prev3_wd, "EXPLAIN") == 0 && + pg_strcasecmp(prev2_wd, "ANALYZE") == 0 && + pg_strcasecmp(prev_wd, "VERBOSE") == 0)) + { + static const char *const list_EXPLAIN[] = + {"SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE", NULL}; + + COMPLETE_WITH_LIST(list_EXPLAIN); + } + +/* FETCH && MOVE */ + /* Complete FETCH with one of FORWARD, BACKWARD, RELATIVE */ + else if (pg_strcasecmp(prev_wd, "FETCH") == 0 || + pg_strcasecmp(prev_wd, "MOVE") == 0) + { + static const char *const list_FETCH1[] = + {"ABSOLUTE", "BACKWARD", "FORWARD", "RELATIVE", NULL}; + + COMPLETE_WITH_LIST(list_FETCH1); + } + /* Complete FETCH with one of ALL, NEXT, PRIOR */ + else if (pg_strcasecmp(prev2_wd, "FETCH") == 0 || + pg_strcasecmp(prev2_wd, "MOVE") == 0) + { + static const char *const list_FETCH2[] = + {"ALL", "NEXT", "PRIOR", NULL}; + + COMPLETE_WITH_LIST(list_FETCH2); + } + + /* + * Complete FETCH with "FROM" or "IN". These are equivalent, + * but we may as well tab-complete both: perhaps some users prefer one + * variant or the other. + */ + else if (pg_strcasecmp(prev3_wd, "FETCH") == 0 || + pg_strcasecmp(prev3_wd, "MOVE") == 0) + { + static const char *const list_FROMIN[] = + {"FROM", "IN", NULL}; + + COMPLETE_WITH_LIST(list_FROMIN); + } + +/* FOREIGN DATA WRAPPER */ + /* applies in ALTER/DROP FDW and in CREATE SERVER */ + else if (pg_strcasecmp(prev4_wd, "CREATE") != 0 && + pg_strcasecmp(prev3_wd, "FOREIGN") == 0 && + pg_strcasecmp(prev2_wd, "DATA") == 0 && + pg_strcasecmp(prev_wd, "WRAPPER") == 0) + COMPLETE_WITH_QUERY(Query_for_list_of_fdws); + +/* 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) + { + 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 with "ON", GRANT/REVOKE with + * TO/FROM + */ + else if (pg_strcasecmp(prev2_wd, "GRANT") == 0 || + pg_strcasecmp(prev2_wd, "REVOKE") == 0) + { + 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 ON with a list of tables, views, sequences, + * and indexes + * + * keywords DATABASE, FUNCTION, LANGUAGE, SCHEMA added to query result via + * UNION; seems to work intuitively + * + * Note: GRANT/REVOKE can get quite complex; tab-completion as implemented + * here will only work if the privilege list contains exactly one + * privilege + */ + else if ((pg_strcasecmp(prev3_wd, "GRANT") == 0 || + pg_strcasecmp(prev3_wd, "REVOKE") == 0) && + pg_strcasecmp(prev_wd, "ON") == 0) + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvf, + " 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 '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_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) + COMPLETE_WITH_QUERY(Query_for_list_of_languages); + else if (pg_strcasecmp(prev_wd, "SCHEMA") == 0) + 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 + COMPLETE_WITH_CONST("FROM"); + } + + /* Complete "GRANT/REVOKE * ON * TO/FROM" with username, GROUP, or PUBLIC */ + 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 && + pg_strcasecmp(prev_wd, "GROUP") == 0) + COMPLETE_WITH_CONST("BY"); + +/* INSERT */ + /* Complete INSERT with "INTO" */ + else if (pg_strcasecmp(prev_wd, "INSERT") == 0) + COMPLETE_WITH_CONST("INTO"); + /* 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_insertables, NULL); + /* Complete "INSERT INTO
(" with attribute names */ + 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
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", "TABLE", "VALUES", NULL}; + + COMPLETE_WITH_LIST(list_INSERT); + } + + /* + * Complete INSERT INTO
(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", "TABLE", "VALUES", NULL}; + + COMPLETE_WITH_LIST(list_INSERT); + } + + /* Insert an open parenthesis after "VALUES" */ + else if (pg_strcasecmp(prev_wd, "VALUES") == 0 && + pg_strcasecmp(prev2_wd, "DEFAULT") != 0) + COMPLETE_WITH_CONST("("); + +/* LOCK */ + /* Complete LOCK [TABLE] with a list of tables */ + else if (pg_strcasecmp(prev_wd, "LOCK") == 0) + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, + " UNION SELECT 'TABLE'"); + else if (pg_strcasecmp(prev_wd, "TABLE") == 0 && + pg_strcasecmp(prev2_wd, "LOCK") == 0) + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, ""); + + /* For the following, handle the case of a single table only for now */ + + /* Complete LOCK [TABLE]
with "IN" */ + else if ((pg_strcasecmp(prev2_wd, "LOCK") == 0 && + pg_strcasecmp(prev_wd, "TABLE") != 0) || + (pg_strcasecmp(prev2_wd, "TABLE") == 0 && + pg_strcasecmp(prev3_wd, "LOCK") == 0)) + COMPLETE_WITH_CONST("IN"); + + /* Complete LOCK [TABLE]
IN with a lock mode */ + else if (pg_strcasecmp(prev_wd, "IN") == 0 && + (pg_strcasecmp(prev3_wd, "LOCK") == 0 || + (pg_strcasecmp(prev3_wd, "TABLE") == 0 && + pg_strcasecmp(prev4_wd, "LOCK") == 0))) + { + static const char *const lock_modes[] = + {"ACCESS SHARE MODE", + "ROW SHARE MODE", "ROW EXCLUSIVE MODE", + "SHARE UPDATE EXCLUSIVE MODE", "SHARE MODE", + "SHARE ROW EXCLUSIVE MODE", + "EXCLUSIVE MODE", "ACCESS EXCLUSIVE MODE", NULL}; + + COMPLETE_WITH_LIST(lock_modes); + } + +/* NOTIFY */ + else if (pg_strcasecmp(prev_wd, "NOTIFY") == 0) + 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 && + pg_strcasecmp(prev_wd, "TO") == 0) + COMPLETE_WITH_QUERY(Query_for_list_of_roles); + +/* ORDER BY */ + else if (pg_strcasecmp(prev3_wd, "FROM") == 0 && + pg_strcasecmp(prev_wd, "ORDER") == 0) + COMPLETE_WITH_CONST("BY"); + else if (pg_strcasecmp(prev4_wd, "FROM") == 0 && + pg_strcasecmp(prev2_wd, "ORDER") == 0 && + pg_strcasecmp(prev_wd, "BY") == 0) + COMPLETE_WITH_ATTR(prev3_wd, ""); + +/* PREPARE xx AS */ + else if (pg_strcasecmp(prev_wd, "AS") == 0 && + pg_strcasecmp(prev3_wd, "PREPARE") == 0) + { + static const char *const list_PREPARE[] = + {"SELECT", "UPDATE", "INSERT", "DELETE", NULL}; + + 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"); + else if (pg_strcasecmp(prev_wd, "OWNED") == 0 && + pg_strcasecmp(prev2_wd, "REASSIGN") == 0) + COMPLETE_WITH_CONST("BY"); + else if (pg_strcasecmp(prev_wd, "BY") == 0 && + pg_strcasecmp(prev2_wd, "OWNED") == 0 && + pg_strcasecmp(prev3_wd, "REASSIGN") == 0) + COMPLETE_WITH_QUERY(Query_for_list_of_roles); + else if (pg_strcasecmp(prev2_wd, "BY") == 0 && + pg_strcasecmp(prev3_wd, "OWNED") == 0 && + pg_strcasecmp(prev4_wd, "REASSIGN") == 0) + COMPLETE_WITH_CONST("TO"); + else if (pg_strcasecmp(prev_wd, "TO") == 0 && + pg_strcasecmp(prev3_wd, "BY") == 0 && + pg_strcasecmp(prev4_wd, "OWNED") == 0 && + pg_strcasecmp(prev5_wd, "REASSIGN") == 0) + COMPLETE_WITH_QUERY(Query_for_list_of_roles); + +/* REINDEX */ + else if (pg_strcasecmp(prev_wd, "REINDEX") == 0) + { + static const char *const list_REINDEX[] = + {"TABLE", "INDEX", "SYSTEM", "DATABASE", NULL}; + + COMPLETE_WITH_LIST(list_REINDEX); + } + 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); + 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 || + pg_strcasecmp(prev_wd, "DATABASE") == 0) + 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", "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 . . . */ + +/* SET, RESET, SHOW */ + /* Complete with a variable name */ + else if ((pg_strcasecmp(prev_wd, "SET") == 0 && + pg_strcasecmp(prev3_wd, "UPDATE") != 0) || + pg_strcasecmp(prev_wd, "RESET") == 0) + COMPLETE_WITH_QUERY(Query_for_list_of_set_vars); + else if (pg_strcasecmp(prev_wd, "SHOW") == 0) + COMPLETE_WITH_QUERY(Query_for_list_of_show_vars); + /* Complete "SET TRANSACTION" */ + else if ((pg_strcasecmp(prev2_wd, "SET") == 0 && + pg_strcasecmp(prev_wd, "TRANSACTION") == 0) + || (pg_strcasecmp(prev2_wd, "START") == 0 + && pg_strcasecmp(prev_wd, "TRANSACTION") == 0) + || (pg_strcasecmp(prev2_wd, "BEGIN") == 0 + && pg_strcasecmp(prev_wd, "WORK") == 0) + || (pg_strcasecmp(prev2_wd, "BEGIN") == 0 + && pg_strcasecmp(prev_wd, "TRANSACTION") == 0) + || (pg_strcasecmp(prev4_wd, "SESSION") == 0 + && pg_strcasecmp(prev3_wd, "CHARACTERISTICS") == 0 + && pg_strcasecmp(prev2_wd, "AS") == 0 + && pg_strcasecmp(prev_wd, "TRANSACTION") == 0)) + { + static const char *const my_list[] = + {"ISOLATION LEVEL", "READ", NULL}; + + COMPLETE_WITH_LIST(my_list); + } + else if ((pg_strcasecmp(prev3_wd, "SET") == 0 + || pg_strcasecmp(prev3_wd, "BEGIN") == 0 + || pg_strcasecmp(prev3_wd, "START") == 0 + || (pg_strcasecmp(prev4_wd, "CHARACTERISTICS") == 0 + && pg_strcasecmp(prev3_wd, "AS") == 0)) + && (pg_strcasecmp(prev2_wd, "TRANSACTION") == 0 + || pg_strcasecmp(prev2_wd, "WORK") == 0) + && pg_strcasecmp(prev_wd, "ISOLATION") == 0) + COMPLETE_WITH_CONST("LEVEL"); + else if ((pg_strcasecmp(prev4_wd, "SET") == 0 + || pg_strcasecmp(prev4_wd, "BEGIN") == 0 + || pg_strcasecmp(prev4_wd, "START") == 0 + || pg_strcasecmp(prev4_wd, "AS") == 0) + && (pg_strcasecmp(prev3_wd, "TRANSACTION") == 0 + || pg_strcasecmp(prev3_wd, "WORK") == 0) + && pg_strcasecmp(prev2_wd, "ISOLATION") == 0 + && pg_strcasecmp(prev_wd, "LEVEL") == 0) + { + static const char *const my_list[] = + {"READ", "REPEATABLE", "SERIALIZABLE", NULL}; + + COMPLETE_WITH_LIST(my_list); + } + else if ((pg_strcasecmp(prev4_wd, "TRANSACTION") == 0 || + pg_strcasecmp(prev4_wd, "WORK") == 0) && + pg_strcasecmp(prev3_wd, "ISOLATION") == 0 && + pg_strcasecmp(prev2_wd, "LEVEL") == 0 && + pg_strcasecmp(prev_wd, "READ") == 0) + { + static const char *const my_list[] = + {"UNCOMMITTED", "COMMITTED", NULL}; + + COMPLETE_WITH_LIST(my_list); + } + else if ((pg_strcasecmp(prev4_wd, "TRANSACTION") == 0 || + pg_strcasecmp(prev4_wd, "WORK") == 0) && + pg_strcasecmp(prev3_wd, "ISOLATION") == 0 && + pg_strcasecmp(prev2_wd, "LEVEL") == 0 && + pg_strcasecmp(prev_wd, "REPEATABLE") == 0) + COMPLETE_WITH_CONST("READ"); + else if ((pg_strcasecmp(prev3_wd, "SET") == 0 || + pg_strcasecmp(prev3_wd, "BEGIN") == 0 || + pg_strcasecmp(prev3_wd, "START") == 0 || + pg_strcasecmp(prev3_wd, "AS") == 0) && + (pg_strcasecmp(prev2_wd, "TRANSACTION") == 0 || + pg_strcasecmp(prev2_wd, "WORK") == 0) && + pg_strcasecmp(prev_wd, "READ") == 0) + { + static const char *const my_list[] = + {"ONLY", "WRITE", NULL}; + + COMPLETE_WITH_LIST(my_list); + } + /* Complete SET CONSTRAINTS with DEFERRED|IMMEDIATE */ + else if (pg_strcasecmp(prev3_wd, "SET") == 0 && + pg_strcasecmp(prev2_wd, "CONSTRAINTS") == 0) + { + static const char *const constraint_list[] = + {"DEFERRED", "IMMEDIATE", NULL}; + + COMPLETE_WITH_LIST(constraint_list); + } + /* Complete SET ROLE */ + else if (pg_strcasecmp(prev2_wd, "SET") == 0 && + pg_strcasecmp(prev_wd, "ROLE") == 0) + COMPLETE_WITH_QUERY(Query_for_list_of_roles); + /* Complete SET SESSION with AUTHORIZATION or CHARACTERISTICS... */ + else if (pg_strcasecmp(prev2_wd, "SET") == 0 && + pg_strcasecmp(prev_wd, "SESSION") == 0) + { + static const char *const my_list[] = + {"AUTHORIZATION", "CHARACTERISTICS AS TRANSACTION", NULL}; + + COMPLETE_WITH_LIST(my_list); + } + /* Complete SET SESSION AUTHORIZATION with username */ + else if (pg_strcasecmp(prev3_wd, "SET") == 0 + && pg_strcasecmp(prev2_wd, "SESSION") == 0 + && pg_strcasecmp(prev_wd, "AUTHORIZATION") == 0) + COMPLETE_WITH_QUERY(Query_for_list_of_roles " UNION SELECT 'DEFAULT'"); + /* Complete RESET SESSION with AUTHORIZATION */ + else if (pg_strcasecmp(prev2_wd, "RESET") == 0 && + pg_strcasecmp(prev_wd, "SESSION") == 0) + COMPLETE_WITH_CONST("AUTHORIZATION"); + /* Complete SET with "TO" */ + 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] != ')' && + pg_strcasecmp(prev4_wd, "DOMAIN") != 0) + COMPLETE_WITH_CONST("TO"); + /* Suggest possible variable values */ + else if (pg_strcasecmp(prev3_wd, "SET") == 0 && + (pg_strcasecmp(prev_wd, "TO") == 0 || strcmp(prev_wd, "=") == 0)) + { + if (pg_strcasecmp(prev2_wd, "DateStyle") == 0) + { + static const char *const my_list[] = + {"ISO", "SQL", "Postgres", "German", + "YMD", "DMY", "MDY", + "US", "European", "NonEuropean", + "DEFAULT", NULL}; + + COMPLETE_WITH_LIST(my_list); + } + else if (pg_strcasecmp(prev2_wd, "IntervalStyle") == 0) + { + static const char *const my_list[] = + {"postgres", "postgres_verbose", "sql_standard", "iso_8601", NULL}; + + COMPLETE_WITH_LIST(my_list); + } + else if (pg_strcasecmp(prev2_wd, "GEQO") == 0) + { + static const char *const my_list[] = + {"ON", "OFF", "DEFAULT", NULL}; + + COMPLETE_WITH_LIST(my_list); + } + else + { + static const char *const my_list[] = + {"DEFAULT", NULL}; + + COMPLETE_WITH_LIST(my_list); + } + } + +/* START TRANSACTION */ + 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(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_updatables, NULL); + /* Complete UPDATE
with "SET" */ + else if (pg_strcasecmp(prev2_wd, "UPDATE") == 0) + COMPLETE_WITH_CONST("SET"); + + /* + * If the previous word is SET (and it wasn't caught above as the _first_ + * word) the word before it was (hopefully) a table name and we'll now + * make a list of attributes. + */ + else if (pg_strcasecmp(prev_wd, "SET") == 0) + COMPLETE_WITH_ATTR(prev2_wd, ""); + +/* UPDATE xx SET yy = */ + else if (pg_strcasecmp(prev2_wd, "SET") == 0 && + pg_strcasecmp(prev4_wd, "UPDATE") == 0) + COMPLETE_WITH_CONST("="); + +/* USER MAPPING */ + else if ((pg_strcasecmp(prev3_wd, "ALTER") == 0 || + pg_strcasecmp(prev3_wd, "CREATE") == 0 || + pg_strcasecmp(prev3_wd, "DROP") == 0) && + pg_strcasecmp(prev2_wd, "USER") == 0 && + pg_strcasecmp(prev_wd, "MAPPING") == 0) + COMPLETE_WITH_CONST("FOR"); + else if (pg_strcasecmp(prev4_wd, "CREATE") == 0 && + pg_strcasecmp(prev3_wd, "USER") == 0 && + pg_strcasecmp(prev2_wd, "MAPPING") == 0 && + pg_strcasecmp(prev_wd, "FOR") == 0) + COMPLETE_WITH_QUERY(Query_for_list_of_roles + " 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, + " UNION SELECT 'FULL'" + " UNION SELECT 'FREEZE'" + " UNION SELECT 'ANALYZE'" + " UNION SELECT 'VERBOSE'"); + 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, + " 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, + " 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, + " 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, + " 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, + " 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); + +/* WITH [RECURSIVE] */ + + /* + * 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_tf, NULL); + +/* WHERE */ + /* Simple case of the word before the where being the table name */ + else if (pg_strcasecmp(prev_wd, "WHERE") == 0) + COMPLETE_WITH_ATTR(prev2_wd, ""); + +/* ... FROM ... */ +/* TODO: also include SRF ? */ + 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); + +/* ... JOIN ... */ + else if (pg_strcasecmp(prev_wd, "JOIN") == 0) + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvf, 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 (strncmp(prev_wd, "\\da", strlen("\\da")) == 0) + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_aggregates, NULL); + else if (strncmp(prev_wd, "\\db", strlen("\\db")) == 0) + COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces); + else if (strncmp(prev_wd, "\\dD", strlen("\\dD")) == 0) + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains, NULL); + 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 (strncmp(prev_wd, "\\dFd", strlen("\\dFd")) == 0) + COMPLETE_WITH_QUERY(Query_for_list_of_ts_dictionaries); + else if (strncmp(prev_wd, "\\dFp", strlen("\\dFp")) == 0) + COMPLETE_WITH_QUERY(Query_for_list_of_ts_parsers); + else if (strncmp(prev_wd, "\\dFt", strlen("\\dFt")) == 0) + COMPLETE_WITH_QUERY(Query_for_list_of_ts_templates); + /* must be at end of \dF */ + else if (strncmp(prev_wd, "\\dF", strlen("\\dF")) == 0) + COMPLETE_WITH_QUERY(Query_for_list_of_ts_configurations); + + else if (strncmp(prev_wd, "\\di", strlen("\\di")) == 0) + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL); + else if (strncmp(prev_wd, "\\dL", strlen("\\dL")) == 0) + COMPLETE_WITH_QUERY(Query_for_list_of_languages); + else if (strncmp(prev_wd, "\\dn", strlen("\\dn")) == 0) + COMPLETE_WITH_QUERY(Query_for_list_of_schemas); + else if (strncmp(prev_wd, "\\dp", strlen("\\dp")) == 0 + || strncmp(prev_wd, "\\z", strlen("\\z")) == 0) + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvf, 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) + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL); + else if (strncmp(prev_wd, "\\dT", strlen("\\dT")) == 0) + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL); + 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 (strncmp(prev_wd, "\\dv", strlen("\\dv")) == 0) + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL); + + /* must be at end of \d list */ + else if (strncmp(prev_wd, "\\d", strlen("\\d")) == 0) + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_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) + COMPLETE_WITH_LIST(sql_commands); + else if (strcmp(prev_wd, "\\password") == 0) + COMPLETE_WITH_QUERY(Query_for_list_of_roles); + else if (strcmp(prev_wd, "\\pset") == 0) + { + static const char *const my_list[] = + {"format", "border", "expanded", + "null", "fieldsep", "tuples_only", "title", "tableattr", + "linestyle", "pager", "recordsep", NULL}; + + 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 + ) + { + completion_charp = "\\"; + matches = completion_matches(text, complete_from_files); + } + + /* + * Finally, we look through the list of "things", such as TABLE, INDEX and + * check if that was the previous word. If so, execute the query to get a + * list of them. + */ + else + { + int i; + + for (i = 0; words_after_create[i].name; i++) + { + if (pg_strcasecmp(prev_wd, words_after_create[i].name) == 0) + { + if (words_after_create[i].query) + COMPLETE_WITH_QUERY(words_after_create[i].query); + else if (words_after_create[i].squery) + COMPLETE_WITH_SCHEMA_QUERY(*words_after_create[i].squery, + NULL); + break; + } + } + } + + /* + * If we still don't have anything to match we have to fabricate some sort + * of default list. If we were to just return NULL, readline automatically + * attempts filename completion, and that's usually no good. + */ + if (matches == NULL) + { + COMPLETE_WITH_CONST(""); +#ifdef HAVE_RL_COMPLETION_APPEND_CHARACTER + rl_completion_append_character = '\0'; +#endif + } + + /* free storage */ + { + int i; + + for (i = 0; i < (int)lengthof(previous_words); i++) + free(previous_words[i]); + } + + /* Return our Grand List O' Matches */ + return matches; +} /* * This creates a list of matching things, according to a query pointed to @@ -1036,7 +3395,7 @@ static char* _complete_from_query(int is_schema_query, const char* text, int sta result = NULL; return NULL; } -#endif + /* * This function returns in order one of a fixed, NULL pointer terminated list @@ -1096,7 +3455,6 @@ static char* complete_from_list(const char* text, int state) return NULL; } -#ifdef NOT_USED /* * 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 @@ -1126,7 +3484,7 @@ static char* complete_from_const(const char* text, int state) * 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) +static char** complete_from_variables(const char* text, const char* prefix, const char* suffix) { char** matches = NULL; int overhead = strlen(prefix) + strlen(suffix) + 1; @@ -1213,7 +3571,7 @@ static char* complete_from_files(const char* text, int state) return ret; } -#endif + /* HELPER FUNCTIONS */ /* @@ -1258,7 +3616,6 @@ static char* pg_strdup_keyword_case(const char* s, const char* ref) return ret; } -#ifdef NOT_USED /* * Execute a query and report any errors. This should be the preferred way of * talking to the database in this file. @@ -1272,9 +3629,7 @@ static PGresult* exec_query(const char* query) result = PQexec(pset.db, query); if (PQresultStatus(result) != PGRES_TUPLES_OK) { -#ifdef NOT_USED psql_error("tab completion query failed: %s\nQuery was:\n%s\n", PQerrorMessage(pset.db), query); -#endif PQclear(result); result = NULL; } @@ -1348,14 +3703,14 @@ static void get_previous_words(int point, char** previous_words, int nwords) /* make a copy of chars from start to end inclusive */ s = (char*)pg_malloc(end - start + 2); - rc = strncpy_s(s, end - start + 2, &buf[start], end - start + 2); + rc = strncpy_s(s, end - start + 2, &buf[start], end - start + 1); + s[end - start + 1] = '\0'; securec_check_c(rc, "\0", "\0"); } *previous_words++ = s; } } -#endif #ifdef NOT_USED