X-Git-Url: https://granicus.if.org/sourcecode?a=blobdiff_plain;f=src%2Fbin%2Fpsql%2Ftab-complete.c;h=b529b70e2c282265a63e1af18d0e91cb929bdb3c;hb=0239800893ef4901e3c085e06534934a485d3bf0;hp=cd448b9d0f15333ff6c1b79f8efbd4e0ca0f09ac;hpb=d0066b9fa4e76a3d83a86701649bf5acfc7c6f69;p=postgresql diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c index cd448b9d0f..b529b70e2c 100644 --- a/src/bin/psql/tab-complete.c +++ b/src/bin/psql/tab-complete.c @@ -1,9 +1,9 @@ /* * psql - the PostgreSQL interactive terminal * - * Copyright (c) 2000-2004, PostgreSQL Global Development Group + * Copyright (c) 2000-2010, PostgreSQL Global Development Group * - * $PostgreSQL: pgsql/src/bin/psql/tab-complete.c,v 1.117 2004/11/02 16:10:05 petere Exp $ + * $PostgreSQL: pgsql/src/bin/psql/tab-complete.c,v 1.189 2010/01/02 16:58:00 momjian Exp $ */ /*---------------------------------------------------------------------- @@ -53,7 +53,7 @@ #include "pqexpbuffer.h" #include "common.h" #include "settings.h" - +#include "stringutils.h" #ifdef HAVE_RL_FILENAME_COMPLETION_FUNCTION #define filename_completion_function rl_filename_completion_function @@ -83,30 +83,28 @@ typedef struct SchemaQuery const char *catname; /* - * Selection condition --- only rows meeting this condition are - * candidates to display. If catname mentions multiple tables, - * include the necessary join condition here. For example, "c.relkind - * = 'r'". Write NULL (not an empty string) if not needed. + * Selection condition --- only rows meeting this condition are candidates + * to display. If catname mentions multiple tables, include the necessary + * join condition here. For example, "c.relkind = 'r'". Write NULL (not + * an empty string) if not needed. */ const char *selcondition; /* * Visibility condition --- which rows are visible without schema - * qualification? For example, - * "pg_catalog.pg_table_is_visible(c.oid)". + * qualification? For example, "pg_catalog.pg_table_is_visible(c.oid)". */ const char *viscondition; /* - * Namespace --- name of field to join to pg_namespace.oid. For - * example, "c.relnamespace". + * Namespace --- name of field to join to pg_namespace.oid. For example, + * "c.relnamespace". */ const char *namespace; /* - * Result --- the appropriately-quoted name to return, in the case of - * an unqualified name. For example, - * "pg_catalog.quote_ident(c.relname)". + * Result --- the appropriately-quoted name to return, in the case of an + * unqualified name. For example, "pg_catalog.quote_ident(c.relname)". */ const char *result; @@ -128,29 +126,69 @@ static int completion_max_records; * the completion callback functions. Ugly but there is no better way. */ static const char *completion_charp; /* to pass a string */ -static const char *const * completion_charpp; /* to pass a list of - * strings */ +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 */ -/* A couple of macros to ease typing. You can use these to complete the given - string with - 1) The results from a query you pass it. (Perhaps one of those below?) - 2) The results from a schema query you pass it. - 3) The items from a null-pointer-terminated list. - 4) A string constant - 5) The list of attributes to the given table. -*/ +/* + * A few macros to ease typing. You can use these to complete the given + * string with + * 1) The results from a query you pass it. (Perhaps one of those below?) + * 2) The results from a schema query you pass it. + * 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). + */ #define COMPLETE_WITH_QUERY(query) \ -do { completion_charp = query; matches = completion_matches(text, complete_from_query); } while(0) -#define COMPLETE_WITH_SCHEMA_QUERY(query,addon) \ -do { completion_squery = &(query); completion_charp = addon; matches = completion_matches(text, complete_from_schema_query); } while(0) +do { \ + completion_charp = query; \ + matches = completion_matches(text, complete_from_query); \ +} while (0) + +#define COMPLETE_WITH_SCHEMA_QUERY(query, addon) \ +do { \ + completion_squery = &(query); \ + completion_charp = addon; \ + matches = completion_matches(text, complete_from_schema_query); \ +} while (0) + #define COMPLETE_WITH_LIST(list) \ -do { completion_charpp = list; matches = completion_matches(text, complete_from_list); } while(0) +do { \ + completion_charpp = list; \ + matches = completion_matches(text, complete_from_list); \ +} while (0) + #define COMPLETE_WITH_CONST(string) \ -do { completion_charp = string; matches = completion_matches(text, complete_from_const); } while(0) -#define COMPLETE_WITH_ATTR(table) \ -do {completion_charp = Query_for_list_of_attributes; completion_info_charp = table; matches = completion_matches(text, complete_from_query); } while(0) +do { \ + completion_charp = string; \ + matches = completion_matches(text, complete_from_const); \ +} while (0) + +#define COMPLETE_WITH_ATTR(relation, addon) \ +do { \ + char *_completion_schema; \ + char *_completion_table; \ +\ + _completion_schema = strtokx(relation, " \t\n\r", ".", "\"", 0, \ + false, false, pset.encoding); \ + (void) strtokx(NULL, " \t\n\r", ".", "\"", 0, \ + false, false, pset.encoding); \ + _completion_table = strtokx(NULL, " \t\n\r", ".", "\"", 0, \ + false, false, pset.encoding); \ + if (_completion_table == NULL) \ + { \ + completion_charp = Query_for_list_of_attributes addon; \ + completion_info_charp = relation; \ + } \ + else \ + { \ + completion_charp = Query_for_list_of_attributes_with_schema addon; \ + completion_info_charp = _completion_table; \ + completion_info_charp2 = _completion_schema; \ + } \ + matches = completion_matches(text, complete_from_query); \ +} while (0) /* * Assembly instructions for schema queries @@ -312,11 +350,12 @@ static const SchemaQuery Query_for_list_of_views = { /* * Queries to get lists of names of various kinds of things, possibly * restricted to names matching a partially entered name. In these queries, - * %s will be replaced by the text entered so far (suitably escaped to - * become a SQL literal string). %d will be replaced by the length of the - * string (in unescaped form). A second %s, if present, will be replaced - * by a suitably-escaped version of the string provided in - * completion_info_charp. + * the first %s will be replaced by the text entered so far (suitably escaped + * to become a SQL literal string). %d will be replaced by the length of the + * string (in unescaped form). A second and third %s, if present, will be + * replaced by a suitably-escaped version of the string provided in + * completion_info_charp. A fourth and fifth %s are similarly replaced by + * completion_info_charp2. * * Beware that the allowed sequences of %s and %d are determined by * _complete_from_query(). @@ -329,9 +368,27 @@ static const SchemaQuery Query_for_list_of_views = { " AND a.attnum > 0 "\ " AND NOT a.attisdropped "\ " AND substring(pg_catalog.quote_ident(attname),1,%d)='%s' "\ -" AND pg_catalog.quote_ident(relname)='%s' "\ +" AND (pg_catalog.quote_ident(relname)='%s' "\ +" OR '\"' || relname || '\"'='%s') "\ " AND pg_catalog.pg_table_is_visible(c.oid)" +#define Query_for_list_of_attributes_with_schema \ +"SELECT pg_catalog.quote_ident(attname) "\ +" FROM pg_catalog.pg_attribute a, pg_catalog.pg_class c, pg_catalog.pg_namespace n "\ +" WHERE c.oid = a.attrelid "\ +" AND n.oid = c.relnamespace "\ +" AND a.attnum > 0 "\ +" AND NOT a.attisdropped "\ +" AND substring(pg_catalog.quote_ident(attname),1,%d)='%s' "\ +" AND (pg_catalog.quote_ident(relname)='%s' "\ +" OR '\"' || relname || '\"' ='%s') "\ +" AND (pg_catalog.quote_ident(nspname)='%s' "\ +" OR '\"' || nspname || '\"' ='%s') " + +#define Query_for_list_of_template_databases \ +"SELECT pg_catalog.quote_ident(datname) FROM pg_catalog.pg_database "\ +" WHERE substring(pg_catalog.quote_ident(datname),1,%d)='%s' and datistemplate IS TRUE" + #define Query_for_list_of_databases \ "SELECT pg_catalog.quote_ident(datname) FROM pg_catalog.pg_database "\ " WHERE substring(pg_catalog.quote_ident(datname),1,%d)='%s'" @@ -355,25 +412,42 @@ static const SchemaQuery Query_for_list_of_views = { "SELECT pg_catalog.quote_ident(nspname) FROM pg_catalog.pg_namespace "\ " WHERE substring(pg_catalog.quote_ident(nspname),1,%d)='%s'" -#define Query_for_list_of_system_relations \ -"SELECT pg_catalog.quote_ident(relname) "\ -" FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n "\ -" WHERE c.relkind IN ('r', 'v', 's', 'S') "\ -" AND substring(pg_catalog.quote_ident(relname),1,%d)='%s' "\ -" AND c.relnamespace = n.oid "\ -" AND n.nspname = 'pg_catalog'" - -#define Query_for_list_of_users \ -" SELECT pg_catalog.quote_ident(usename) "\ -" FROM pg_catalog.pg_user "\ -" WHERE substring(pg_catalog.quote_ident(usename),1,%d)='%s'" +#define Query_for_list_of_set_vars \ +"SELECT name FROM "\ +" (SELECT pg_catalog.lower(name) AS name FROM pg_catalog.pg_settings "\ +" WHERE context IN ('user', 'superuser') "\ +" UNION ALL SELECT 'constraints' "\ +" UNION ALL SELECT 'transaction' "\ +" UNION ALL SELECT 'session' "\ +" UNION ALL SELECT 'role' "\ +" UNION ALL SELECT 'tablespace' "\ +" UNION ALL SELECT 'all') ss "\ +" WHERE substring(name,1,%d)='%s'" + +#define Query_for_list_of_show_vars \ +"SELECT name FROM "\ +" (SELECT pg_catalog.lower(name) AS name FROM pg_catalog.pg_settings "\ +" UNION ALL SELECT 'session authorization' "\ +" UNION ALL SELECT 'all') ss "\ +" WHERE substring(name,1,%d)='%s'" + +#define Query_for_list_of_roles \ +" SELECT pg_catalog.quote_ident(rolname) "\ +" FROM pg_catalog.pg_roles "\ +" WHERE substring(pg_catalog.quote_ident(rolname),1,%d)='%s'" + +#define Query_for_list_of_grant_roles \ +" SELECT pg_catalog.quote_ident(rolname) "\ +" FROM pg_catalog.pg_roles "\ +" WHERE substring(pg_catalog.quote_ident(rolname),1,%d)='%s'"\ +" UNION ALL SELECT 'PUBLIC'" /* the silly-looking length condition is just to eat up the current word */ #define Query_for_table_owning_index \ "SELECT pg_catalog.quote_ident(c1.relname) "\ " FROM pg_catalog.pg_class c1, pg_catalog.pg_class c2, pg_catalog.pg_index i"\ " WHERE c1.oid=i.indrelid and i.indexrelid=c2.oid"\ -" and (%d = length('%s'))"\ +" and (%d = pg_catalog.length('%s'))"\ " and pg_catalog.quote_ident(c2.relname)='%s'"\ " and pg_catalog.pg_table_is_visible(c2.oid)" @@ -382,7 +456,7 @@ static const SchemaQuery Query_for_list_of_views = { "SELECT pg_catalog.quote_ident(c2.relname) "\ " FROM pg_catalog.pg_class c1, pg_catalog.pg_class c2, pg_catalog.pg_index i"\ " WHERE c1.oid=i.indrelid and i.indexrelid=c2.oid"\ -" and (%d = length('%s'))"\ +" and (%d = pg_catalog.length('%s'))"\ " and pg_catalog.quote_ident(c1.relname)='%s'"\ " and pg_catalog.pg_table_is_visible(c2.oid)" @@ -390,11 +464,42 @@ static const SchemaQuery Query_for_list_of_views = { #define Query_for_list_of_tables_for_trigger \ "SELECT pg_catalog.quote_ident(relname) "\ " FROM pg_catalog.pg_class"\ -" WHERE (%d = length('%s'))"\ +" WHERE (%d = pg_catalog.length('%s'))"\ " AND oid IN "\ " (SELECT tgrelid FROM pg_catalog.pg_trigger "\ " WHERE pg_catalog.quote_ident(tgname)='%s')" +#define Query_for_list_of_ts_configurations \ +"SELECT pg_catalog.quote_ident(cfgname) FROM pg_catalog.pg_ts_config "\ +" WHERE substring(pg_catalog.quote_ident(cfgname),1,%d)='%s'" + +#define Query_for_list_of_ts_dictionaries \ +"SELECT pg_catalog.quote_ident(dictname) FROM pg_catalog.pg_ts_dict "\ +" WHERE substring(pg_catalog.quote_ident(dictname),1,%d)='%s'" + +#define Query_for_list_of_ts_parsers \ +"SELECT pg_catalog.quote_ident(prsname) FROM pg_catalog.pg_ts_parser "\ +" WHERE substring(pg_catalog.quote_ident(prsname),1,%d)='%s'" + +#define Query_for_list_of_ts_templates \ +"SELECT pg_catalog.quote_ident(tmplname) FROM pg_catalog.pg_ts_template "\ +" WHERE substring(pg_catalog.quote_ident(tmplname),1,%d)='%s'" + +#define Query_for_list_of_fdws \ +" SELECT pg_catalog.quote_ident(fdwname) "\ +" FROM pg_catalog.pg_foreign_data_wrapper "\ +" WHERE substring(pg_catalog.quote_ident(fdwname),1,%d)='%s'" + +#define Query_for_list_of_servers \ +" SELECT pg_catalog.quote_ident(srvname) "\ +" FROM pg_catalog.pg_foreign_server "\ +" WHERE substring(pg_catalog.quote_ident(srvname),1,%d)='%s'" + +#define Query_for_list_of_user_mappings \ +" SELECT pg_catalog.quote_ident(usename) "\ +" FROM pg_catalog.pg_user_mappings "\ +" WHERE substring(pg_catalog.quote_ident(usename),1,%d)='%s'" + /* * This is a list of all "things" in Pgsql, which can show up after CREATE or * DROP; and there is also a query to get a list of them. @@ -405,39 +510,56 @@ typedef struct const char *name; const char *query; /* simple query, or NULL */ const SchemaQuery *squery; /* schema query, or NULL */ + const bool noshow; /* NULL or true if this word should not show + * up after CREATE or DROP */ } pgsql_thing_t; static const pgsql_thing_t words_after_create[] = { {"AGGREGATE", NULL, &Query_for_list_of_aggregates}, - {"CAST", NULL, NULL}, /* Casts have complex structures for - * names, so skip it */ + {"CAST", NULL, NULL}, /* Casts have complex structures for names, so + * skip it */ + + /* + * CREATE CONSTRAINT TRIGGER is not supported here because it is designed + * to be used only by pg_dump. + */ + {"CONFIGURATION", Query_for_list_of_ts_configurations, NULL, true}, {"CONVERSION", "SELECT pg_catalog.quote_ident(conname) FROM pg_catalog.pg_conversion WHERE substring(pg_catalog.quote_ident(conname),1,%d)='%s'"}, {"DATABASE", Query_for_list_of_databases}, + {"DICTIONARY", Query_for_list_of_ts_dictionaries, NULL, true}, {"DOMAIN", NULL, &Query_for_list_of_domains}, + {"FOREIGN DATA WRAPPER", NULL, NULL}, {"FUNCTION", NULL, &Query_for_list_of_functions}, - {"GROUP", "SELECT pg_catalog.quote_ident(groname) FROM pg_catalog.pg_group WHERE substring(pg_catalog.quote_ident(groname),1,%d)='%s'"}, + {"GROUP", Query_for_list_of_roles}, {"LANGUAGE", Query_for_list_of_languages}, {"INDEX", NULL, &Query_for_list_of_indexes}, - {"OPERATOR", NULL, NULL}, /* Querying for this is probably not such - * a good idea. */ + {"OPERATOR", NULL, NULL}, /* Querying for this is probably not such a + * good idea. */ + {"PARSER", Query_for_list_of_ts_parsers, NULL, true}, + {"ROLE", Query_for_list_of_roles}, {"RULE", "SELECT pg_catalog.quote_ident(rulename) FROM pg_catalog.pg_rules WHERE substring(pg_catalog.quote_ident(rulename),1,%d)='%s'"}, {"SCHEMA", Query_for_list_of_schemas}, {"SEQUENCE", NULL, &Query_for_list_of_sequences}, + {"SERVER", Query_for_list_of_servers}, {"TABLE", NULL, &Query_for_list_of_tables}, {"TABLESPACE", Query_for_list_of_tablespaces}, {"TEMP", NULL, NULL}, /* for CREATE TEMP TABLE ... */ + {"TEMPLATE", Query_for_list_of_ts_templates, NULL, true}, + {"TEXT SEARCH", NULL, NULL}, {"TRIGGER", "SELECT pg_catalog.quote_ident(tgname) FROM pg_catalog.pg_trigger WHERE substring(pg_catalog.quote_ident(tgname),1,%d)='%s'"}, {"TYPE", NULL, &Query_for_list_of_datatypes}, {"UNIQUE", NULL, NULL}, /* for CREATE UNIQUE INDEX ... */ - {"USER", Query_for_list_of_users}, + {"USER", Query_for_list_of_roles}, + {"USER MAPPING FOR", NULL, NULL}, {"VIEW", NULL, &Query_for_list_of_views}, - {NULL, NULL, NULL} /* end of list */ + {NULL, NULL, NULL, false} /* end of list */ }; /* Forward declaration of functions */ static char **psql_completion(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); static char *complete_from_schema_query(const char *text, int state); static char *_complete_from_query(int is_schema_query, @@ -449,6 +571,8 @@ static PGresult *exec_query(const char *query); static char *previous_word(int point, int skip); +static int find_open_parenthesis(int end); + #if 0 static char *quote_file_name(char *text, int match_type, char *quote_pointer); static char *dequote_file_name(char *text, char quote_char); @@ -467,8 +591,8 @@ initialize_readline(void) completion_max_records = 1000; /* - * There is a variable rl_completion_query_items for this but - * apparently it's not defined everywhere. + * There is a variable rl_completion_query_items for this but apparently + * it's not defined everywhere. */ } @@ -492,128 +616,25 @@ psql_completion(char *text, int start, int end) *prev5_wd; static const char *const sql_commands[] = { - "ABORT", "ALTER", "ANALYZE", "BEGIN", "CHECKPOINT", "CLOSE", "CLUSTER", "COMMENT", - "COMMIT", "COPY", "CREATE", "DEALLOCATE", "DECLARE", "DELETE", "DROP", "END", "EXECUTE", - "EXPLAIN", "FETCH", "GRANT", "INSERT", "LISTEN", "LOAD", "LOCK", "MOVE", "NOTIFY", - "PREPARE", "REINDEX", "RELEASE", "RESET", "REVOKE", "ROLLBACK", "SAVEPOINT", - "SELECT", "SET", "SHOW", "START", "TRUNCATE", "UNLISTEN", "UPDATE", "VACUUM", NULL - }; - - static const char *const pgsql_variables[] = { - /* these SET arguments are known in gram.y */ - "CONSTRAINTS", - "NAMES", - "SESSION", - "TRANSACTION", - - /* - * the rest should match USERSET and possibly SUSET entries in - * backend/utils/misc/guc.c. - */ - "add_missing_from", - "australian_timezones", - "client_encoding", - "client_min_messages", - "commit_delay", - "commit_siblings", - "cpu_index_tuple_cost", - "cpu_operator_cost", - "cpu_tuple_cost", - "DateStyle", - "deadlock_timeout", - "debug_pretty_print", - "debug_print_parse", - "debug_print_plan", - "debug_print_rewritten", - "default_statistics_target", - "default_transaction_isolation", - "default_transaction_read_only", - "default_with_oids", - "dynamic_library_path", - "effective_cache_size", - "enable_hashagg", - "enable_hashjoin", - "enable_indexscan", - "enable_mergejoin", - "enable_nestloop", - "enable_seqscan", - "enable_sort", - "enable_tidscan", - "explain_pretty_print", - "extra_float_digits", - "from_collapse_limit", - "fsync", - "geqo", - "geqo_effort", - "geqo_generations", - "geqo_pool_size", - "geqo_selection_bias", - "geqo_threshold", - "join_collapse_limit", - "krb_server_keyfile", - "lc_messages", - "lc_monetary", - "lc_numeric", - "lc_time", - "log_destination", - "log_duration", - "log_error_verbosity", - "log_executor_stats", - "log_min_duration_statement", - "log_min_error_statement", - "log_min_messages", - "log_parser_stats", - "log_planner_stats", - "log_statement", - "log_statement_stats", - "maintenance_work_mem", - "max_connections", - "max_files_per_process", - "max_fsm_pages", - "max_fsm_relations", - "max_locks_per_transaction", - "max_stack_depth", - "password_encryption", - "port", - "random_page_cost", - "regex_flavor", - "search_path", - "shared_buffers", - "seed", - "server_encoding", - "sql_inheritance", - "ssl", - "statement_timeout", - "stats_block_level", - "stats_command_string", - "stats_reset_on_server_start", - "stats_row_level", - "stats_start_collector", - "superuser_reserved_connections", - "syslog_facility", - "syslog_ident", - "TimeZone", - "trace_notify", - "transform_null_equals", - "unix_socket_directory", - "unix_socket_group", - "unix_socket_permissions", - "wal_buffers", - "wal_debug", - "wal_sync_method", - "work_mem", - NULL + "ABORT", "ALTER", "ANALYZE", "BEGIN", "CHECKPOINT", "CLOSE", "CLUSTER", + "COMMENT", "COMMIT", "COPY", "CREATE", "DEALLOCATE", "DECLARE", + "DELETE FROM", "DISCARD", "DROP", "END", "EXECUTE", "EXPLAIN", "FETCH", + "GRANT", "INSERT", "LISTEN", "LOAD", "LOCK", "MOVE", "NOTIFY", "PREPARE", + "REASSIGN", "REINDEX", "RELEASE", "RESET", "REVOKE", "ROLLBACK", + "SAVEPOINT", "SELECT", "SET", "SHOW", "START", "TABLE", "TRUNCATE", "UNLISTEN", + "UPDATE", "VACUUM", "VALUES", "WITH", NULL }; static const char *const backslash_commands[] = { "\\a", "\\connect", "\\C", "\\cd", "\\copy", "\\copyright", - "\\d", "\\da", "\\db", "\\dc", "\\dC", "\\dd", "\\dD", "\\df", - "\\dg", "\\di", "\\dl", "\\dn", "\\do", "\\dp", "\\ds", "\\dS", - "\\dt", "\\dT", "\\dv", "\\du", - "\\e", "\\echo", "\\encoding", + "\\d", "\\da", "\\db", "\\dc", "\\dC", "\\dd", "\\dD", "\\des", "\\deu", "\\dew", "\\df", + "\\dF", "\\dFd", "\\dFp", "\\dFt", "\\dg", "\\di", "\\dl", + "\\dn", "\\do", "\\dp", "\\drds", "\\ds", "\\dS", "\\dt", "\\dT", "\\dv", "\\du", + "\\e", "\\echo", "\\ef", "\\encoding", "\\f", "\\g", "\\h", "\\help", "\\H", "\\i", "\\l", "\\lo_import", "\\lo_export", "\\lo_list", "\\lo_unlink", - "\\o", "\\p", "\\pset", "\\q", "\\qecho", "\\r", "\\set", "\\t", "\\T", + "\\o", "\\p", "\\password", "\\prompt", "\\pset", "\\q", "\\qecho", "\\r", + "\\set", "\\t", "\\T", "\\timing", "\\unset", "\\x", "\\w", "\\z", "\\!", NULL }; @@ -627,12 +648,12 @@ psql_completion(char *text, int start, int end) 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 four - * words. According to those we'll make some smart decisions on what - * the user is probably intending to type. TODO: Use strtokx() to do - * this. + * Scan the input line before our current position for the last five + * words. According to those we'll make some smart decisions on what the + * user is probably intending to type. TODO: Use strtokx() to do this. */ prev_wd = previous_word(start, 0); prev2_wd = previous_word(start, 1); @@ -648,37 +669,47 @@ psql_completion(char *text, int start, int end) else if (!prev_wd) COMPLETE_WITH_LIST(sql_commands); -/* CREATE or DROP but not ALTER (TABLE|DOMAIN|GROUP) sth DROP */ - /* complete with something you can create or drop */ - else if (pg_strcasecmp(prev_wd, "CREATE") == 0 || - (pg_strcasecmp(prev_wd, "DROP") == 0 && - pg_strcasecmp(prev3_wd, "TABLE") != 0 && - pg_strcasecmp(prev3_wd, "DOMAIN") != 0 && - pg_strcasecmp(prev3_wd, "GROUP") != 0)) +/* CREATE */ + /* complete with something you can create */ + else if (pg_strcasecmp(prev_wd, "CREATE") == 0) matches = completion_matches(text, create_command_generator); +/* DROP, but watch out for DROP embedded in other commands */ + /* complete with something you can drop */ + else if (pg_strcasecmp(prev_wd, "DROP") == 0 && + pg_strcasecmp(prev2_wd, "DROP") == 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 + * 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", "CONVERSION", "DATABASE", "DOMAIN", "FUNCTION", - "GROUP", "INDEX", "LANGUAGE", "OPERATOR", "SCHEMA", "SEQUENCE", "TABLE", - "TABLESPACE", "TRIGGER", "TYPE", "USER", NULL}; + {"AGGREGATE", "CONVERSION", "DATABASE", "DOMAIN", "FOREIGN DATA WRAPPER", "FUNCTION", + "GROUP", "INDEX", "LANGUAGE", "LARGE OBJECT", "OPERATOR", "ROLE", "SCHEMA", "SERVER", "SEQUENCE", "TABLE", + "TABLESPACE", "TEXT SEARCH", "TRIGGER", "TYPE", "USER", "USER MAPPING FOR", "VIEW", NULL}; COMPLETE_WITH_LIST(list_ALTER); } - - /* ALTER AGGREGATE,CONVERSION,FUNCTION,SCHEMA */ + /* ALTER AGGREGATE,FUNCTION */ else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 && (pg_strcasecmp(prev2_wd, "AGGREGATE") == 0 || - pg_strcasecmp(prev2_wd, "CONVERSION") == 0 || - pg_strcasecmp(prev2_wd, "FUNCTION") == 0 || + pg_strcasecmp(prev2_wd, "FUNCTION") == 0)) + { + static const char *const list_ALTERAGG[] = + {"OWNER TO", "RENAME TO", "SET SCHEMA", NULL}; + + COMPLETE_WITH_LIST(list_ALTERAGG); + } + + /* ALTER CONVERSION,SCHEMA */ + else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 && + (pg_strcasecmp(prev2_wd, "CONVERSION") == 0 || pg_strcasecmp(prev2_wd, "SCHEMA") == 0)) { static const char *const list_ALTERGEN[] = @@ -692,17 +723,29 @@ psql_completion(char *text, int start, int end) pg_strcasecmp(prev2_wd, "DATABASE") == 0) { static const char *const list_ALTERDATABASE[] = - {"RESET", "SET", "OWNER TO", "RENAME TO", NULL}; + {"RESET", "SET", "OWNER TO", "RENAME TO", "CONNECTION LIMIT", NULL}; COMPLETE_WITH_LIST(list_ALTERDATABASE); } + /* 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[] = + {"VALIDATOR", "OPTIONS", "OWNER TO", NULL}; + + COMPLETE_WITH_LIST(list_ALTER_FDW); + } + /* ALTER INDEX */ else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 && pg_strcasecmp(prev2_wd, "INDEX") == 0) { static const char *const list_ALTERINDEX[] = - {"SET TABLESPACE", "OWNER TO", "RENAME TO", NULL}; + {"OWNER TO", "RENAME TO", "SET", "RESET", NULL}; COMPLETE_WITH_LIST(list_ALTERINDEX); } @@ -710,19 +753,46 @@ psql_completion(char *text, int start, int end) /* ALTER LANGUAGE */ else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 && pg_strcasecmp(prev2_wd, "LANGUAGE") == 0) - COMPLETE_WITH_CONST("RENAME TO"); + { + 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 */ + /* ALTER USER,ROLE */ else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 && - pg_strcasecmp(prev2_wd, "USER") == 0) + !(pg_strcasecmp(prev2_wd, "USER") == 0 && pg_strcasecmp(prev_wd, "MAPPING") == 0) && + (pg_strcasecmp(prev2_wd, "USER") == 0 || + pg_strcasecmp(prev2_wd, "ROLE") == 0)) { static const char *const list_ALTERUSER[] = {"ENCRYPTED", "UNENCRYPTED", "CREATEDB", "NOCREATEDB", "CREATEUSER", - "NOCREATEUSER", "VALID UNTIL", "RENAME TO", "SET", "RESET", NULL}; + "NOCREATEUSER", "CREATEROLE", "NOCREATEROLE", "INHERIT", "NOINHERIT", + "LOGIN", "NOLOGIN", "CONNECTION LIMIT", "VALID UNTIL", "RENAME TO", + "SUPERUSER", "NOSUPERUSER", "SET", "RESET", NULL}; COMPLETE_WITH_LIST(list_ALTERUSER); } + /* 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 DOMAIN */ else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 && pg_strcasecmp(prev2_wd, "DOMAIN") == 0) @@ -748,7 +818,7 @@ psql_completion(char *text, int start, int end) pg_strcasecmp(prev_wd, "SET") == 0) { static const char *const list_ALTERDOMAIN3[] = - {"DEFAULT", "NOT NULL", NULL}; + {"DEFAULT", "NOT NULL", "SCHEMA", NULL}; COMPLETE_WITH_LIST(list_ALTERDOMAIN3); } @@ -756,20 +826,38 @@ psql_completion(char *text, int start, int end) else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 && pg_strcasecmp(prev2_wd, "SEQUENCE") == 0) { - static const char *const list_ALTERSCHEMA[] = - {"INCREMENT", "MINVALUE", "MAXVALUE", "RESTART", "NO", "CACHE", "CYCLE", NULL}; + static const char *const list_ALTERSEQUENCE[] = + {"INCREMENT", "MINVALUE", "MAXVALUE", "RESTART", "NO", "CACHE", "CYCLE", + "SET SCHEMA", "OWNED BY", "RENAME TO", NULL}; - COMPLETE_WITH_LIST(list_ALTERSCHEMA); + 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_ALTERSCHEMA2[] = - {"MINVALUE", "MAXVALUE", "CYCLE", NULL}; + 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_ALTERSCHEMA2); + 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[] = {"RENAME TO", NULL}; + + COMPLETE_WITH_LIST(list_ALTERVIEW); } /* ALTER TRIGGER , add ON */ else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 && @@ -804,20 +892,68 @@ psql_completion(char *text, int start, int end) pg_strcasecmp(prev2_wd, "TABLE") == 0) { static const char *const list_ALTER2[] = - {"ADD", "ALTER", "CLUSTER ON", "DROP", "RENAME", "OWNER TO", - "SET", NULL}; + {"ADD", "ALTER", "CLUSTER ON", "DISABLE", "DROP", "ENABLE", "INHERIT", + "NO INHERIT", "RENAME", "RESET", "OWNER TO", "SET", 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); + } + /* If we have TABLE 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)) - COMPLETE_WITH_ATTR(prev2_wd); + COMPLETE_WITH_ATTR(prev2_wd, " UNION SELECT 'COLUMN'"); + + /* + * 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(prev2_wd, "RENAME") == 0 && + pg_strcasecmp(prev_wd, "TO") != 0) + COMPLETE_WITH_CONST("TO"); + + /* ALTER TABLE xxx RENAME COLUMN yyy */ + else if (pg_strcasecmp(prev5_wd, "TABLE") == 0 && + pg_strcasecmp(prev3_wd, "RENAME") == 0 && + pg_strcasecmp(prev2_wd, "COLUMN") == 0 && + pg_strcasecmp(prev_wd, "TO") != 0) COMPLETE_WITH_CONST("TO"); /* If we have TABLE DROP, provide COLUMN or CONSTRAINT */ @@ -833,20 +969,51 @@ psql_completion(char *text, int start, int end) 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); + 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)) { - /* DROP ... does not work well yet */ - static const char *const list_COLUMNALTER[] = - {"TYPE", "SET DEFAULT", "DROP DEFAULT", "SET NOT NULL", - "DROP NOT NULL", "SET STATISTICS", "SET STORAGE", NULL}; + static const char *const list_COLUMNALTER[] = + {"TYPE", "SET", "DROP", NULL}; COMPLETE_WITH_LIST(list_COLUMNALTER); } + 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); + } + 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, "STATISTICS") == 0) + { + static const char *const list_COLUMNSETSTATS[] = + {"DISTINCT", NULL}; + + COMPLETE_WITH_LIST(list_COLUMNSETSTATS); + } + 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"); @@ -857,12 +1024,12 @@ psql_completion(char *text, int start, int end) completion_info_charp = prev3_wd; COMPLETE_WITH_QUERY(Query_for_index_of_table); } - /* If we have TABLE SET, provide WITHOUT or TABLESPACE */ + /* 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", NULL}; + {"WITHOUT", "TABLESPACE", "SCHEMA", NULL}; COMPLETE_WITH_LIST(list_TABLESET); } @@ -890,10 +1057,54 @@ psql_completion(char *text, int start, int end) COMPLETE_WITH_LIST(list_ALTERTSPC); } - /* complete ALTER TYPE with OWNER TO */ + /* 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)) + COMPLETE_WITH_CONST("RENAME TO"); + + else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 && + pg_strcasecmp(prev4_wd, "TEXT") == 0 && + pg_strcasecmp(prev3_wd, "SEARCH") == 0 && + pg_strcasecmp(prev2_wd, "DICTIONARY") == 0) + { + static const char *const list_ALTERTEXTSEARCH2[] = + {"OWNER TO", "RENAME TO", NULL}; + + 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, "CONFIGURATION") == 0) + { + static const char *const list_ALTERTEXTSEARCH3[] = + {"ADD MAPPING FOR", "ALTER MAPPING", "DROP MAPPING FOR", "OWNER TO", "RENAME TO", NULL}; + + COMPLETE_WITH_LIST(list_ALTERTEXTSEARCH3); + } + + /* complete ALTER TYPE with OWNER TO, SET SCHEMA */ else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 && pg_strcasecmp(prev2_wd, "TYPE") == 0) - COMPLETE_WITH_CONST("OWNER TO"); + { + static const char *const list_ALTERTYPE[] = + {"OWNER TO", "SET SCHEMA", NULL}; + + COMPLETE_WITH_LIST(list_ALTERTYPE); + } /* complete ALTER GROUP */ else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 && pg_strcasecmp(prev2_wd, "GROUP") == 0) @@ -914,20 +1125,11 @@ psql_completion(char *text, int start, int end) (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_users); - -/* 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); - /* If we have ANALYZE , complete with semicolon. */ - else if (pg_strcasecmp(prev2_wd, "ANALYZE") == 0) - COMPLETE_WITH_CONST(";"); + COMPLETE_WITH_QUERY(Query_for_list_of_roles); -/* BEGIN, END, COMMIT, ABORT */ +/* BEGIN, END, ABORT */ else if (pg_strcasecmp(prev_wd, "BEGIN") == 0 || pg_strcasecmp(prev_wd, "END") == 0 || - pg_strcasecmp(prev_wd, "COMMIT") == 0 || pg_strcasecmp(prev_wd, "ABORT") == 0) { static const char *const list_TRANS[] = @@ -935,6 +1137,14 @@ psql_completion(char *text, int start, int end) 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"); @@ -942,33 +1152,33 @@ psql_completion(char *text, int start, int end) else if (pg_strcasecmp(prev_wd, "ROLLBACK") == 0) { static const char *const list_TRANS[] = - {"WORK", "TRANSACTION", "TO SAVEPOINT", NULL}; + {"WORK", "TRANSACTION", "TO SAVEPOINT", "PREPARED", NULL}; COMPLETE_WITH_LIST(list_TRANS); } /* CLUSTER */ /* - * If the previous word is CLUSTER and not without produce list of - * indexes. + * 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_indexes, NULL); - /* If we have CLUSTER , then add "ON" */ + 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("ON"); + { + COMPLETE_WITH_CONST("USING"); + } /* - * If we have CLUSTER ON, then add the correct tablename as - * well. + * If we have CLUSTER USING, then add the index as well. */ else if (pg_strcasecmp(prev3_wd, "CLUSTER") == 0 && - pg_strcasecmp(prev_wd, "ON") == 0) + pg_strcasecmp(prev_wd, "USING") == 0) { completion_info_charp = prev2_wd; - COMPLETE_WITH_QUERY(Query_for_table_owning_index); + COMPLETE_WITH_QUERY(Query_for_index_of_table); } /* COMMENT */ @@ -980,12 +1190,26 @@ psql_completion(char *text, int start, int end) static const char *const list_COMMENT[] = {"CAST", "CONVERSION", "DATABASE", "INDEX", "LANGUAGE", "RULE", "SCHEMA", "SEQUENCE", "TABLE", "TYPE", "VIEW", "COLUMN", "AGGREGATE", "FUNCTION", - "OPERATOR", "TRIGGER", "CONSTRAINT", "DOMAIN", NULL}; + "OPERATOR", "TRIGGER", "CONSTRAINT", "DOMAIN", "LARGE OBJECT", + "TABLESPACE", "TEXT SEARCH", "ROLE", NULL}; COMPLETE_WITH_LIST(list_COMMENT); } else if (pg_strcasecmp(prev4_wd, "COMMENT") == 0 && - pg_strcasecmp(prev3_wd, "ON") == 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, "ON") == 0 && + pg_strcasecmp(prev4_wd, "TEXT") == 0 && + pg_strcasecmp(prev3_wd, "SEARCH") == 0)) COMPLETE_WITH_CONST("IS"); /* COPY */ @@ -1003,12 +1227,12 @@ psql_completion(char *text, int start, int end) 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}; + { + static const char *const list_FROMTO[] = + {"FROM", "TO", NULL}; - COMPLETE_WITH_LIST(list_FROMTO); - } + 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 || @@ -1023,25 +1247,48 @@ psql_completion(char *text, int start, int end) 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", "DELIMETER", "NULL", "CSV", NULL}; + { + static const char *const list_COPY[] = + {"BINARY", "OIDS", "DELIMITER", "NULL", "CSV", NULL}; - COMPLETE_WITH_LIST(list_COPY); - } + COMPLETE_WITH_LIST(list_COPY); + } /* Handle COPY|BINARY FROM|TO filename CSV */ - else if (pg_strcasecmp(prev_wd, "CSV") == 0 && + 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[] = - {"QUOTE", "ESCAPE", "FORCE QUOTE", NULL}; + { + static const char *const list_CSV[] = + {"HEADER", "QUOTE", "ESCAPE", "FORCE QUOTE", NULL}; - COMPLETE_WITH_LIST(list_CSV); - } + 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 INDEX */ + /* CREATE FOREIGN DATA WRAPPER */ + else if (pg_strcasecmp(prev5_wd, "CREATE") == 0 && + pg_strcasecmp(prev4_wd, "FOREIGN") == 0 && + pg_strcasecmp(prev3_wd, "DATA") == 0 && + pg_strcasecmp(prev2_wd, "WRAPPER") == 0) + COMPLETE_WITH_CONST("VALIDATOR"); + + /* CREATE INDEX */ /* First off we complete CREATE UNIQUE with "INDEX" */ else if (pg_strcasecmp(prev2_wd, "CREATE") == 0 && pg_strcasecmp(prev_wd, "UNIQUE") == 0) @@ -1057,21 +1304,30 @@ psql_completion(char *text, int start, int end) COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL); /* - * Complete INDEX ON
with a list of table columns - * (which should really be in parens) + * 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(prev2_wd, "ON") == 0) - COMPLETE_WITH_ATTR(prev_wd); + { + if (find_open_parenthesis(end)) + COMPLETE_WITH_ATTR(prev_wd, ""); + else + COMPLETE_WITH_CONST("("); + } + else if (pg_strcasecmp(prev5_wd, "INDEX") == 0 && + pg_strcasecmp(prev3_wd, "ON") == 0 && + pg_strcasecmp(prev_wd, "(") == 0) + COMPLETE_WITH_ATTR(prev2_wd, ""); /* same if you put in USING */ else if (pg_strcasecmp(prev4_wd, "ON") == 0 && pg_strcasecmp(prev2_wd, "USING") == 0) - COMPLETE_WITH_ATTR(prev3_wd); + COMPLETE_WITH_ATTR(prev3_wd, ""); /* Complete USING with an index method */ else if (pg_strcasecmp(prev_wd, "USING") == 0) { static const char *const index_mth[] = - {"BTREE", "RTREE", "HASH", "GIST", NULL}; + {"BTREE", "HASH", "GIN", "GIST", NULL}; COMPLETE_WITH_LIST(index_mth); } @@ -1099,8 +1355,8 @@ psql_completion(char *text, int start, int end) /* Complete "AS ON " with a "TO" */ else if (pg_strcasecmp(prev3_wd, "AS") == 0 && pg_strcasecmp(prev2_wd, "ON") == 0 && - (toupper((unsigned char) prev_wd[4]) == 'T' || - toupper((unsigned char) prev_wd[5]) == 'T')) + (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 && @@ -1108,11 +1364,27 @@ psql_completion(char *text, int start, int end) pg_strcasecmp(prev_wd, "TO") == 0) COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL); +/* CREATE SERVER */ + 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 with "TABLE" */ + /* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */ else if (pg_strcasecmp(prev2_wd, "CREATE") == 0 && - pg_strcasecmp(prev_wd, "TEMP") == 0) - COMPLETE_WITH_CONST("TABLE"); + (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); + } /* CREATE TABLESPACE */ else if (pg_strcasecmp(prev3_wd, "CREATE") == 0 && @@ -1131,11 +1403,83 @@ psql_completion(char *text, int start, int end) 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 */ - /* is on the agenda . . . */ + /* 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", NULL}; + + COMPLETE_WITH_LIST(list_CREATETRIGGER); + } + /* 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)) + { + static const char *const list_CREATETRIGGER2[] = + {"ON", "OR", NULL}; + + COMPLETE_WITH_LIST(list_CREATETRIGGER2); + } + +/* 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", "NOINHERIT", "NOLOGIN", "NOCREATEDB", + "NOCREATEROLE", "NOCREATEUSER", "NOSUPERUSER", "ROLE", "SUPERUSER", "SYSID", + "UNENCRYPTED", NULL}; + + COMPLETE_WITH_LIST(list_CREATEROLE); + } + + /* + * 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" */ + /* Complete CREATE VIEW with AS */ else if (pg_strcasecmp(prev3_wd, "CREATE") == 0 && pg_strcasecmp(prev2_wd, "VIEW") == 0) COMPLETE_WITH_CONST("AS"); @@ -1145,6 +1489,24 @@ psql_completion(char *text, int start, int end) 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); + } + + 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 */ /* @@ -1161,21 +1523,146 @@ psql_completion(char *text, int start, int end) else if (pg_strcasecmp(prev2_wd, "DELETE") == 0 && pg_strcasecmp(prev_wd, "FROM") == 0) COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL); - /* Complete DELETE FROM
with "WHERE" (perhaps a safe idea?) */ + /* Complete DELETE FROM
*/ else if (pg_strcasecmp(prev3_wd, "DELETE") == 0 && pg_strcasecmp(prev2_wd, "FROM") == 0) - COMPLETE_WITH_CONST("WHERE"); + { + 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); + } + +/* 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, "CONVERSION") == 0 || + pg_strcasecmp(prev2_wd, "DOMAIN") == 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)) + { + if (find_open_parenthesis(end)) + { + static const char func_args_query[] = "select pg_catalog.oidvectortypes(proargtypes)||')' from pg_proc where proname='%s'"; + char *tmp_buf = malloc(strlen(func_args_query) + strlen(prev_wd)); + + sprintf(tmp_buf, func_args_query, prev_wd); + COMPLETE_WITH_QUERY(tmp_buf); + free(tmp_buf); + } + else + { + COMPLETE_WITH_CONST("("); + } + } + else + { + static const char *const list_DROPCR[] = + {"CASCADE", "RESTRICT", NULL}; + + COMPLETE_WITH_LIST(list_DROPCR); + } + } + else if (pg_strcasecmp(prev4_wd, "DROP") == 0 && + pg_strcasecmp(prev3_wd, "FUNCTION") == 0 && + pg_strcasecmp(prev_wd, "(") == 0) + { + static const char func_args_query[] = "select pg_catalog.oidvectortypes(proargtypes)||')' from pg_proc where proname='%s'"; + char *tmp_buf = malloc(strlen(func_args_query) + strlen(prev2_wd)); + + sprintf(tmp_buf, func_args_query, 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); + } /* EXPLAIN */ /* - * Complete EXPLAIN [VERBOSE] (which you'd have to type yourself) with - * the list of SQL commands + * Complete EXPLAIN [ANALYZE] [VERBOSE] with list of EXPLAIN-able commands */ - else if (pg_strcasecmp(prev_wd, "EXPLAIN") == 0 || - (pg_strcasecmp(prev2_wd, "EXPLAIN") == 0 && + 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)) - COMPLETE_WITH_LIST(sql_commands); + { + 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 */ @@ -1183,7 +1670,7 @@ psql_completion(char *text, int start, int end) pg_strcasecmp(prev_wd, "MOVE") == 0) { static const char *const list_FETCH1[] = - {"ABSOLUT", "BACKWARD", "FORWARD", "RELATIVE", NULL}; + {"ABSOLUTE", "BACKWARD", "FORWARD", "RELATIVE", NULL}; COMPLETE_WITH_LIST(list_FETCH1); } @@ -1198,26 +1685,36 @@ psql_completion(char *text, int start, int end) } /* - * Complete FETCH with "FROM" or "TO". (Is there a - * difference? If not, remove one.) + * 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_FROMTO[] = - {"FROM", "TO", NULL}; + static const char *const list_FROMIN[] = + {"FROM", "IN", NULL}; - COMPLETE_WITH_LIST(list_FROMTO); + 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); + /* GRANT && REVOKE*/ /* Complete GRANT/REVOKE with a list of privileges */ else if (pg_strcasecmp(prev_wd, "GRANT") == 0 || pg_strcasecmp(prev_wd, "REVOKE") == 0) { static const char *const list_privileg[] = - {"SELECT", "INSERT", "UPDATE", "DELETE", "RULE", "REFERENCES", - "TRIGGER", "CREATE", "TEMPORARY", "EXECUTE", "USAGE", "ALL", NULL}; + {"SELECT", "INSERT", "UPDATE", "DELETE", "TRUNCATE", "REFERENCES", + "TRIGGER", "CREATE", "CONNECT", "TEMPORARY", "EXECUTE", "USAGE", + "ALL", NULL}; COMPLETE_WITH_LIST(list_privileg); } @@ -1227,23 +1724,26 @@ psql_completion(char *text, int start, int end) COMPLETE_WITH_CONST("ON"); /* - * Complete GRANT/REVOKE ON with a list of tables, views, - * sequences, and indexes + * 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 + * 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 + * 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_tsv, " UNION SELECT 'DATABASE'" + " 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'"); @@ -1262,15 +1762,24 @@ psql_completion(char *text, int start, int end) COMPLETE_WITH_QUERY(Query_for_list_of_schemas); else if (pg_strcasecmp(prev_wd, "TABLESPACE") == 0) COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces); - else + else if (pg_strcasecmp(prev4_wd, "GRANT") == 0) COMPLETE_WITH_CONST("TO"); + else + COMPLETE_WITH_CONST("FROM"); } - /* - * TODO: to complete with user name we need prev5_wd -- wait for a - * more general solution there same for GRANT ON { DATABASE | - * FUNCTION | LANGUAGE | SCHEMA | TABLESPACE } xxx TO - */ + /* Complete "GRANT/REVOKE * ON * TO/FROM" with username, GROUP, or PUBLIC */ + else if (pg_strcasecmp(prev3_wd, "ON") == 0 && + ((pg_strcasecmp(prev5_wd, "GRANT") == 0 && + pg_strcasecmp(prev_wd, "TO") == 0) || + (pg_strcasecmp(prev5_wd, "REVOKE") == 0 && + pg_strcasecmp(prev_wd, "FROM") == 0))) + 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" */ @@ -1284,27 +1793,31 @@ psql_completion(char *text, int start, int end) else if (rl_line_buffer[start - 1] == '(' && pg_strcasecmp(prev3_wd, "INSERT") == 0 && pg_strcasecmp(prev2_wd, "INTO") == 0) - COMPLETE_WITH_ATTR(prev_wd); + COMPLETE_WITH_ATTR(prev_wd, ""); /* - * Complete INSERT INTO
with "VALUES" or "SELECT" or "DEFAULT - * VALUES" + * Complete INSERT INTO
with "VALUES" or "SELECT" or "TABLE" or + * "DEFAULT VALUES" */ else if (pg_strcasecmp(prev3_wd, "INSERT") == 0 && pg_strcasecmp(prev2_wd, "INTO") == 0) { static const char *const list_INSERT[] = - {"DEFAULT VALUES", "SELECT", "VALUES", NULL}; + {"DEFAULT VALUES", "SELECT", "TABLE", "VALUES", NULL}; COMPLETE_WITH_LIST(list_INSERT); } - /* Complete INSERT INTO
(attribs) with "VALUES" or "SELECT" */ + + /* + * 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", "VALUES", NULL}; + {"SELECT", "TABLE", "VALUES", NULL}; COMPLETE_WITH_LIST(list_INSERT); } @@ -1316,10 +1829,12 @@ psql_completion(char *text, int start, int end) /* LOCK */ /* Complete LOCK [TABLE] with a list of tables */ - else if (pg_strcasecmp(prev_wd, "LOCK") == 0 || - (pg_strcasecmp(prev_wd, "TABLE") == 0 && - pg_strcasecmp(prev2_wd, "LOCK") == 0)) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL); + 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 */ @@ -1349,15 +1864,60 @@ psql_completion(char *text, int start, int end) /* NOTIFY */ else if (pg_strcasecmp(prev_wd, "NOTIFY") == 0) COMPLETE_WITH_QUERY("SELECT pg_catalog.quote_ident(relname) FROM pg_catalog.pg_listener WHERE substring(pg_catalog.quote_ident(relname),1,%d)='%s'"); -/* OWNER TO - complete with available users*/ + +/* 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_users); + 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); + } + +/* 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", "DATABASE", "INDEX", NULL}; + {"TABLE", "INDEX", "SYSTEM", "DATABASE", NULL}; COMPLETE_WITH_LIST(list_REINDEX); } @@ -1365,10 +1925,11 @@ psql_completion(char *text, int start, int end) { if (pg_strcasecmp(prev_wd, "TABLE") == 0) COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL); - else if (pg_strcasecmp(prev_wd, "DATABASE") == 0) - COMPLETE_WITH_QUERY(Query_for_list_of_databases); 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); } /* SELECT */ @@ -1378,9 +1939,10 @@ psql_completion(char *text, int start, int end) /* 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 || - pg_strcasecmp(prev_wd, "SHOW") == 0) - COMPLETE_WITH_LIST(pgsql_variables); + 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) @@ -1396,7 +1958,7 @@ psql_completion(char *text, int start, int end) && pg_strcasecmp(prev_wd, "TRANSACTION") == 0)) { static const char *const my_list[] = - {"ISOLATION", "READ", NULL}; + {"ISOLATION LEVEL", "READ", NULL}; COMPLETE_WITH_LIST(my_list); } @@ -1462,6 +2024,10 @@ psql_completion(char *text, int start, int end) 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) @@ -1475,7 +2041,11 @@ psql_completion(char *text, int start, int end) 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_users); + 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 && @@ -1484,7 +2054,7 @@ psql_completion(char *text, int start, int end) 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)) + (pg_strcasecmp(prev_wd, "TO") == 0 || strcmp(prev_wd, "=") == 0)) { if (pg_strcasecmp(prev2_wd, "DateStyle") == 0) { @@ -1496,6 +2066,13 @@ psql_completion(char *text, int start, int end) COMPLETE_WITH_LIST(my_list); } + else if (pg_strcasecmp(prev2_wd, "IntervalStyle") == 0) + { + static const char *const my_list[] = + {"postgres", "postgres_verbose", "sql_standard", "iso_8601", NULL}; + + COMPLETE_WITH_LIST(my_list); + } else if (pg_strcasecmp(prev2_wd, "GEQO") == 0) { static const char *const my_list[] = @@ -1533,29 +2110,99 @@ psql_completion(char *text, int start, int end) 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. + * 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); + 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); + 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 */ +/* + * 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, "ANALYZE") == 0 || - pg_strcasecmp(prev_wd, "VERBOSE") == 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] */ + else if (pg_strcasecmp(prev_wd, "WITH") == 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); /* 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); + COMPLETE_WITH_ATTR(prev2_wd, ""); /* ... FROM ... */ /* TODO: also include SRF ? */ @@ -1569,62 +2216,86 @@ psql_completion(char *text, int start, int end) /* TODO: \dc \dd \dl */ else if (strcmp(prev_wd, "\\connect") == 0 || strcmp(prev_wd, "\\c") == 0) COMPLETE_WITH_QUERY(Query_for_list_of_databases); - else if (strcmp(prev_wd, "\\d") == 0 || strcmp(prev_wd, "\\d+") == 0) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tisv, NULL); - else if (strcmp(prev_wd, "\\da") == 0) + + else if (strncmp(prev_wd, "\\da", strlen("\\da")) == 0) COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_aggregates, NULL); - else if (strcmp(prev_wd, "\\db") == 0) + else if (strncmp(prev_wd, "\\db", strlen("\\db")) == 0) COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces); - else if (strcmp(prev_wd, "\\dD") == 0) + else if (strncmp(prev_wd, "\\dD", strlen("\\dD")) == 0) COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains, NULL); - else if (strcmp(prev_wd, "\\df") == 0 || strcmp(prev_wd, "\\df+") == 0) + else if (strncmp(prev_wd, "\\des", strlen("\\des")) == 0) + COMPLETE_WITH_QUERY(Query_for_list_of_servers); + else if (strncmp(prev_wd, "\\deu", strlen("\\deu")) == 0) + COMPLETE_WITH_QUERY(Query_for_list_of_user_mappings); + else if (strncmp(prev_wd, "\\dew", strlen("\\dew")) == 0) + COMPLETE_WITH_QUERY(Query_for_list_of_fdws); + + else if (strncmp(prev_wd, "\\df", strlen("\\df")) == 0) COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL); - else if (strcmp(prev_wd, "\\di") == 0 || strcmp(prev_wd, "\\di+") == 0) + 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 (strcmp(prev_wd, "\\dn") == 0) + else if (strncmp(prev_wd, "\\dn", strlen("\\dn")) == 0) COMPLETE_WITH_QUERY(Query_for_list_of_schemas); - else if (strcmp(prev_wd, "\\dp") == 0 || strcmp(prev_wd, "\\z") == 0) + else if (strncmp(prev_wd, "\\dp", strlen("\\dp")) == 0) COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsv, NULL); - else if (strcmp(prev_wd, "\\ds") == 0 || strcmp(prev_wd, "\\ds+") == 0) + else if (strncmp(prev_wd, "\\ds", strlen("\\ds")) == 0) COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences, NULL); - else if (strcmp(prev_wd, "\\dS") == 0 || strcmp(prev_wd, "\\dS+") == 0) - COMPLETE_WITH_QUERY(Query_for_list_of_system_relations); - else if (strcmp(prev_wd, "\\dt") == 0 || strcmp(prev_wd, "\\dt+") == 0) + else if (strncmp(prev_wd, "\\dt", strlen("\\dt")) == 0) COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL); - else if (strcmp(prev_wd, "\\dT") == 0 || strcmp(prev_wd, "\\dT+") == 0) + else if (strncmp(prev_wd, "\\dT", strlen("\\dT")) == 0) COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL); - else if (strcmp(prev_wd, "\\du") == 0) - COMPLETE_WITH_QUERY(Query_for_list_of_users); - else if (strcmp(prev_wd, "\\dv") == 0 || strcmp(prev_wd, "\\dv+") == 0) + else if (strncmp(prev_wd, "\\du", strlen("\\du")) == 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_tisv, 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", "pager", - "recordsep", NULL}; + "null", "fieldsep", "tuples_only", "title", "tableattr", + "linestyle", "pager", "recordsep", NULL}; COMPLETE_WITH_LIST(my_list); } else if (strcmp(prev_wd, "\\cd") == 0 || - strcmp(prev_wd, "\\e") == 0 || strcmp(prev_wd, "\\edit") == 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, "\\o") == 0 || strcmp(prev_wd, "\\out") == 0 || + strcmp(prev_wd, "\\i") == 0 || strcmp(prev_wd, "\\include") == 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 ) matches = completion_matches(text, filename_completion_function); /* - * 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. + * 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 { @@ -1645,10 +2316,9 @@ psql_completion(char *text, int start, int end) } /* - * 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 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) { @@ -1663,6 +2333,7 @@ psql_completion(char *text, int start, int end) free(prev2_wd); free(prev3_wd); free(prev4_wd); + free(prev5_wd); /* Return our Grand List O' Matches */ return matches; @@ -1683,7 +2354,7 @@ psql_completion(char *text, int start, int end) something of that sort. */ -/* This one gives you one from a list of things you can put after CREATE or DROP +/* This one gives you one from a list of things you can put after CREATE as defined above. */ static char * @@ -1702,13 +2373,59 @@ create_command_generator(const char *text, int state) /* find something that matches */ while ((name = words_after_create[list_index++].name)) - if (pg_strncasecmp(name, text, string_length) == 0) + { + if ((pg_strncasecmp(name, text, string_length) == 0) && !words_after_create[list_index - 1].noshow) return pg_strdup(name); - + } /* if nothing matches, return NULL */ return NULL; } +/* + * This function gives you a list of things you can put after a DROP command. + * Very similar to create_command_generator, but has an additional entry for + * OWNED BY. (We do it this way in order not to duplicate the + * words_after_create list.) + */ +static char * +drop_command_generator(const char *text, int state) +{ + static int list_index, + string_length; + const char *name; + + if (state == 0) + { + /* If this is the first time for this completion, init some values */ + list_index = 0; + string_length = strlen(text); + + /* + * DROP can be followed by "OWNED BY", which is not found in the list + * for CREATE matches, so make it the first state. (We do not make it + * the last state because it would be more difficult to detect when we + * have to return NULL instead.) + * + * Make sure we advance to the next state. + */ + list_index++; + if (pg_strncasecmp("OWNED", text, string_length) == 0) + return pg_strdup("OWNED"); + } + + /* + * In subsequent attempts, try to complete with the same items we use for + * CREATE + */ + while ((name = words_after_create[list_index++ - 1].name)) + { + if ((pg_strncasecmp(name, text, string_length) == 0) && (!words_after_create[list_index - 2].noshow)) + return pg_strdup(name); + } + + /* if nothing matches, return NULL */ + return NULL; +} /* The following two functions are wrappers for _complete_from_query */ @@ -1730,8 +2447,9 @@ complete_from_schema_query(const char *text, int state) The query can be one of two kinds: - A simple query which must contain a %d and a %s, which will be replaced by the string length of the text and the text itself. The query may also - have another %s in it, which will be replaced by the value of - completion_info_charp. + have up to four more %s in it; the first two such will be replaced by the + value of completion_info_charp, the next two by the value of + completion_info_charp2. or: - A schema query used for completion of both schema and relation names; these are more complex and must contain in the following order: @@ -1752,14 +2470,15 @@ _complete_from_query(int is_schema_query, const char *text, int state) static PGresult *result = NULL; /* - * If this is the first time for this completion, we fetch a list of - * our "things" from the backend. + * If this is the first time for this completion, we fetch a list of our + * "things" from the backend. */ if (state == 0) { PQExpBufferData query_buffer; char *e_text; char *e_info_charp; + char *e_info_charp2; list_index = 0; string_length = strlen(text); @@ -1769,13 +2488,8 @@ _complete_from_query(int is_schema_query, const char *text, int state) result = NULL; /* Set up suitably-escaped copies of textual inputs */ - if (text) - { - e_text = pg_malloc(strlen(text) *2 + 1); - PQescapeString(e_text, text, strlen(text)); - } - else - e_text = NULL; + e_text = pg_malloc(string_length * 2 + 1); + PQescapeString(e_text, text, string_length); if (completion_info_charp) { @@ -1789,6 +2503,18 @@ _complete_from_query(int is_schema_query, const char *text, int state) else e_info_charp = NULL; + if (completion_info_charp2) + { + size_t charp_len; + + charp_len = strlen(completion_info_charp2); + e_info_charp2 = pg_malloc(charp_len * 2 + 1); + PQescapeString(e_info_charp2, completion_info_charp2, + charp_len); + } + else + e_info_charp2 = NULL; + initPQExpBuffer(&query_buffer); if (is_schema_query) @@ -1806,50 +2532,49 @@ _complete_from_query(int is_schema_query, const char *text, int state) if (completion_squery->selcondition) appendPQExpBuffer(&query_buffer, "%s AND ", completion_squery->selcondition); - appendPQExpBuffer(&query_buffer, "%s AND ", - completion_squery->viscondition); appendPQExpBuffer(&query_buffer, "substring(%s,1,%d)='%s'", completion_squery->result, string_length, e_text); + appendPQExpBuffer(&query_buffer, " AND %s", + completion_squery->viscondition); /* - * When fetching relation names, suppress system catalogs - * unless the input-so-far begins with "pg_". This is a - * compromise between not offering system catalogs for - * completion at all, and having them swamp the result when - * the input is just "p". + * When fetching relation names, suppress system catalogs unless + * the input-so-far begins with "pg_". This is a compromise + * between not offering system catalogs for completion at all, and + * having them swamp the result when the input is just "p". */ if (strcmp(completion_squery->catname, "pg_catalog.pg_class c") == 0 && strncmp(text, "pg_", 3) !=0) { appendPQExpBuffer(&query_buffer, - " AND c.relnamespace <> (SELECT oid FROM" - " pg_catalog.pg_namespace WHERE nspname = 'pg_catalog')"); + " AND c.relnamespace <> (SELECT oid FROM" + " pg_catalog.pg_namespace WHERE nspname = 'pg_catalog')"); } /* - * Add in matching schema names, but only if there is more - * than one potential match among schema names. + * Add in matching schema names, but only if there is more than + * one potential match among schema names. */ appendPQExpBuffer(&query_buffer, "\nUNION\n" - "SELECT pg_catalog.quote_ident(n.nspname) || '.' " + "SELECT pg_catalog.quote_ident(n.nspname) || '.' " "FROM pg_catalog.pg_namespace n " "WHERE substring(pg_catalog.quote_ident(n.nspname) || '.',1,%d)='%s'", string_length, e_text); appendPQExpBuffer(&query_buffer, " AND (SELECT pg_catalog.count(*)" " FROM pg_catalog.pg_namespace" - " WHERE substring(pg_catalog.quote_ident(nspname) || '.',1,%d) =" + " WHERE substring(pg_catalog.quote_ident(nspname) || '.',1,%d) =" " substring('%s',1,pg_catalog.length(pg_catalog.quote_ident(nspname))+1)) > 1", string_length, e_text); /* - * Add in matching qualified names, but only if there is - * exactly one schema matching the input-so-far. + * Add in matching qualified names, but only if there is exactly + * one schema matching the input-so-far. */ appendPQExpBuffer(&query_buffer, "\nUNION\n" - "SELECT pg_catalog.quote_ident(n.nspname) || '.' || %s " + "SELECT pg_catalog.quote_ident(n.nspname) || '.' || %s " "FROM %s, pg_catalog.pg_namespace n " "WHERE %s = n.oid AND ", qualresult, @@ -1867,13 +2592,13 @@ _complete_from_query(int is_schema_query, const char *text, int state) * speed up the query */ appendPQExpBuffer(&query_buffer, - " AND substring(pg_catalog.quote_ident(n.nspname) || '.',1,%d) =" + " AND substring(pg_catalog.quote_ident(n.nspname) || '.',1,%d) =" " substring('%s',1,pg_catalog.length(pg_catalog.quote_ident(n.nspname))+1)", string_length, e_text); appendPQExpBuffer(&query_buffer, " AND (SELECT pg_catalog.count(*)" " FROM pg_catalog.pg_namespace" - " WHERE substring(pg_catalog.quote_ident(nspname) || '.',1,%d) =" + " WHERE substring(pg_catalog.quote_ident(nspname) || '.',1,%d) =" " substring('%s',1,pg_catalog.length(pg_catalog.quote_ident(nspname))+1)) = 1", string_length, e_text); @@ -1885,7 +2610,9 @@ _complete_from_query(int is_schema_query, const char *text, int state) { /* completion_charp is an sprintf-style format string */ appendPQExpBuffer(&query_buffer, completion_charp, - string_length, e_text, e_info_charp); + string_length, e_text, + e_info_charp, e_info_charp, + e_info_charp2, e_info_charp2); } /* Limit the number of records in the result */ @@ -1895,11 +2622,11 @@ _complete_from_query(int is_schema_query, const char *text, int state) result = exec_query(query_buffer.data); termPQExpBuffer(&query_buffer); - - if (e_text) - free(e_text); + free(e_text); if (e_info_charp) free(e_info_charp); + if (e_info_charp2) + free(e_info_charp2); } /* Find something that matches */ @@ -1960,15 +2687,15 @@ complete_from_list(const char *text, int state) } /* - * No matches found. If we're not case insensitive already, lets - * switch to being case insensitive and try again + * No matches found. If we're not case insensitive already, lets switch to + * being case insensitive and try again */ if (casesensitive && matches == 0) { casesensitive = false; list_index = 0; state++; - return (complete_from_list(text, state)); + return complete_from_list(text, state); } /* If no more matches, return null. */ @@ -2014,11 +2741,11 @@ exec_query(const char *query) result = PQexec(pset.db, query); - if (result != NULL && PQresultStatus(result) != PGRES_TUPLES_OK) + if (PQresultStatus(result) != PGRES_TUPLES_OK) { #if 0 - psql_error("tab completion: %s failed - %s\n", - query, PQresStatus(PQresultStatus(result))); + psql_error("tab completion query failed: %s\nQuery was:\n%s\n", + PQerrorMessage(pset.db), query); #endif PQclear(result); result = NULL; @@ -2029,9 +2756,11 @@ exec_query(const char *query) -/* Return the word (space delimited) before point. Set skip > 0 to skip that - many words; e.g. skip=1 finds the word before the previous one. -*/ +/* + * Return the word (space delimited) before point. Set skip > 0 to + * skip that many words; e.g. skip=1 finds the word before the + * previous one. Return value is NULL or a malloc'ed string. + */ static char * previous_word(int point, int skip) { @@ -2057,16 +2786,16 @@ previous_word(int point, int skip) } /* - * If no end found we return null, because there is no word before - * the point + * If no end found we return null, because there is no word before the + * point */ if (end == -1) return NULL; /* - * Otherwise we now look for the start. The start is either the - * last character before any space going backwards from the end, - * or it's simply character 0 + * Otherwise we now look for the start. The start is either the last + * character before any space going backwards from the end, or it's + * simply character 0 */ for (start = end; start > 0; start--) { @@ -2081,15 +2810,37 @@ previous_word(int point, int skip) /* make a copy */ s = pg_malloc(end - start + 2); - - strncpy(s, &rl_line_buffer[start], end - start + 1); - s[end - start + 1] = '\0'; + strlcpy(s, &rl_line_buffer[start], end - start + 2); return s; } +/* Find the parenthesis after the last word */ +static int +find_open_parenthesis(int end) +{ + int i = end - 1; + + while ((rl_line_buffer[i] != ' ') && (i >= 0)) + { + if (rl_line_buffer[i] == '(') + return 1; + i--; + } + while ((rl_line_buffer[i] == ' ') && (i >= 0)) + { + i--; + } + if (rl_line_buffer[i] == '(') + { + return 1; + } + return 0; + +} + #if 0 /* @@ -2128,8 +2879,7 @@ dequote_file_name(char *text, char quote_char) length = strlen(text); s = pg_malloc(length - 2 + 1); - strncpy(s, text +1, length - 2); - s[length] = '\0'; + strlcpy(s, text +1, length - 2 + 1); return s; }