diff options
Diffstat (limited to 'src/bin/psql/tab-complete.c')
-rw-r--r-- | src/bin/psql/tab-complete.c | 1119 |
1 files changed, 0 insertions, 1119 deletions
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c deleted file mode 100644 index 521742ea1dd..00000000000 --- a/src/bin/psql/tab-complete.c +++ /dev/null @@ -1,1119 +0,0 @@ -/* - * psql - the PostgreSQL interactive terminal - * - * Copyright 2000 by PostgreSQL Global Development Group - * - * $Header: /cvsroot/pgsql/src/bin/psql/tab-complete.c,v 1.50 2002/06/16 00:09:12 momjian Exp $ - */ - -/*---------------------------------------------------------------------- - * This file implements a somewhat more sophisticated readline "TAB - * completion" in psql. It is not intended to be AI, to replace - * learning SQL, or to relieve you from thinking about what you're - * doing. Also it does not always give you all the syntactically legal - * completions, only those that are the most common or the ones that - * the programmer felt most like implementing. - * - * CAVEAT: Tab completion causes queries to be sent to the backend. - * The number tuples returned gets limited, in most default - * installations to 101, but if you still don't like this prospect, - * you can turn off tab completion in your ~/.inputrc (or else - * ${INPUTRC}) file so: - * - * $if psql - * set disable-completion on - * $endif - * - * See `man 3 readline' or `info readline' for the full details. Also, - * hence the - * - * BUGS: - * - * - If you split your queries across lines, this whole things gets - * confused. (To fix this, one would have to read psql's query - * buffer rather than readline's line buffer, which would require - * some major revisions of things.) - * - * - Table or attribute names with spaces in it will equally confuse - * it. - * - * - Quotes, parenthesis, and other funny characters are not handled - * all that gracefully. - *---------------------------------------------------------------------- - */ - -#include "postgres_fe.h" -#include "tab-complete.h" - -#include "input.h" - -/* If we don't have this, we might as well forget about the whole thing: */ -#ifdef USE_READLINE - -#include <ctype.h> -#ifdef USE_ASSERT_CHECKING -#include <assert.h> -#endif - -#include "libpq-fe.h" - -#include "common.h" -#include "settings.h" - -#ifdef HAVE_RL_FILENAME_COMPLETION_FUNCTION -#define filename_completion_function rl_filename_completion_function -#else -/* missing in some header files */ -extern char *filename_completion_function(); -#endif - -#ifdef HAVE_RL_COMPLETION_MATCHES -#define completion_matches(x, y) rl_completion_matches((x), ((rl_compentry_func_t *)(y))) -#endif - -#define BUF_SIZE 2048 -#define ERROR_QUERY_TOO_LONG /* empty */ - - -/* Forward declaration of functions */ -static char **psql_completion(char *text, int start, int end); -static char *create_command_generator(char *text, int state); -static char *complete_from_query(char *text, int state); -static char *complete_from_const(char *text, int state); -static char *complete_from_list(char *text, int state); - -static PGresult *exec_query(char *query); -char *quote_file_name(char *text, int match_type, char *quote_pointer); - -/*static char * dequote_file_name(char *text, char quote_char);*/ -static char *previous_word(int point, int skip); - -/* These variables are used to pass information into the completion functions. - Realizing that this is the cardinal sin of programming, I don't see a better - way. */ -char *completion_charp; /* if you need to pass a string */ -char **completion_charpp; /* if you need to pass a list of strings */ -char *completion_info_charp; /* if you need to pass another - * string */ - -/* Store how many records from a database query we want to return at most -(implemented via SELECT ... LIMIT xx). */ -static int completion_max_records; - - -/* Initialize the readline library for our purposes. */ -void -initialize_readline(void) -{ - rl_readline_name = pset.progname; - rl_attempted_completion_function = (void *) psql_completion; - - rl_basic_word_break_characters = "\t\n@$><=;|&{( "; - - completion_max_records = 100; - - /* - * There is a variable rl_completion_query_items for this but - * apparently it's not defined everywhere. - */ -} - - -/* 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. - The %s will be replaced by the text entered so far, the %d by its length. - If you change the order here or insert things, make sure to also adjust the - referencing macros below. -*/ -typedef struct -{ - char *name; - char *query; -} pgsql_thing_t; - -pgsql_thing_t words_after_create[] = { - {"AGGREGATE", "SELECT distinct proname FROM pg_proc WHERE proisagg AND substr(proname,1,%d)='%s'"}, - {"DATABASE", "SELECT datname FROM pg_database WHERE substr(datname,1,%d)='%s'"}, - {"FUNCTION", "SELECT distinct proname FROM pg_proc WHERE substr(proname,1,%d)='%s'"}, - {"GROUP", "SELECT groname FROM pg_group WHERE substr(groname,1,%d)='%s'"}, - {"INDEX", "SELECT relname FROM pg_class WHERE relkind='i' and substr(relname,1,%d)='%s'"}, - {"OPERATOR", NULL}, /* Querying for this is probably not such - * a good idea. */ - {"RULE", "SELECT rulename FROM pg_rules WHERE substr(rulename,1,%d)='%s'"}, - {"SEQUENCE", "SELECT relname FROM pg_class WHERE relkind='S' and substr(relname,1,%d)='%s'"}, - {"TABLE", "SELECT relname FROM pg_class WHERE (relkind='r' or relkind='v') and substr(relname,1,%d)='%s'"}, - {"TEMP", NULL}, /* for CREATE TEMP TABLE ... */ - {"TRIGGER", "SELECT tgname FROM pg_trigger WHERE substr(tgname,1,%d)='%s'"}, - {"TYPE", "SELECT typname FROM pg_type WHERE substr(typname,1,%d)='%s'"}, - {"UNIQUE", NULL}, /* for CREATE UNIQUE INDEX ... */ - {"USER", "SELECT usename FROM pg_user WHERE substr(usename,1,%d)='%s'"}, - {"VIEW", "SELECT viewname FROM pg_views WHERE substr(viewname,1,%d)='%s'"}, - {NULL, NULL} /* end of list */ -}; - - -/* The query to get a list of tables and a list of indexes, which are used at - various places. */ -#define Query_for_list_of_tables words_after_create[8].query -#define Query_for_list_of_indexes words_after_create[4].query -#define Query_for_list_of_databases words_after_create[1].query -#define Query_for_list_of_attributes "SELECT a.attname FROM pg_attribute a, pg_class c WHERE c.oid = a.attrelid and a.attnum>0 and substr(a.attname,1,%d)='%s' and c.relname='%s'" -#define Query_for_list_of_users words_after_create[13].query - -/* 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 right above?) - 2) The items from a null-pointer-terminated list. - 3) A string constant - 4) The list of attributes to the given table. -*/ -#define COMPLETE_WITH_QUERY(query) \ -do { completion_charp = query; matches = completion_matches(text, complete_from_query); } while(0) -#define COMPLETE_WITH_LIST(list) \ -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) - - -/* The completion function. Acc. to readline spec this gets passed the text - entered to far and its start and end in the readline buffer. The return value - is some partially obscure list format that can be generated by the readline - libraries completion_matches() function, so we don't have to worry about it. -*/ -static char ** -psql_completion(char *text, int start, int end) -{ - /* This is the variable we'll return. */ - char **matches = NULL; - - /* These are going to contain some scannage of the input line. */ - char *prev_wd, - *prev2_wd, - *prev3_wd, - *prev4_wd; - - static char *sql_commands[] = { - "ABORT", "ALTER", "ANALYZE", "BEGIN", "CLOSE", "CLUSTER", "COMMENT", "COMMIT", "COPY", - "CREATE", "DECLARE", "DELETE", "DROP", "EXPLAIN", "FETCH", "GRANT", - "INSERT", "LISTEN", "LOAD", "LOCK", "MOVE", "NOTIFY", "RESET", - "REVOKE", "ROLLBACK", "SELECT", "SET", "SHOW", "TRUNCATE", "UNLISTEN", "UPDATE", - "VACUUM", NULL - }; - - static char *pgsql_variables[] = { - /* these SET arguments are known in gram.y */ - "CONSTRAINTS", - "NAMES", - "SESSION", - "TRANSACTION ISOLATION LEVEL", - /* these are treated in backend/commands/variable.c */ - "DateStyle", - "TimeZone", - "client_encoding", - "server_encoding", - "seed", - - /* - * the rest should match USERSET entries in - * backend/utils/misc/guc.c - */ - "enable_seqscan", - "enable_indexscan", - "enable_tidscan", - "enable_sort", - "enable_nestloop", - "enable_mergejoin", - "enable_hashjoin", - "geqo", - "fsync", - "server_min_messages", - "client_min_messages", - "debug_assertions", - "debug_print_query", - "debug_print_parse", - "debug_print_rewritten", - "debug_print_plan", - "debug_pretty_print", - "show_parser_stats", - "show_planner_stats", - "show_executor_stats", - "show_query_stats", - "trace_notify", - "explain_pretty_print", - "sql_inheritance", - "australian_timezones", - "password_encryption", - "transform_null_equals", - - "geqo_threshold", - "geqo_pool_size", - "geqo_effort", - "geqo_generations", - "geqo_random_seed", - "sort_mem", - "vacuum_mem", - "max_expr_depth", - "commit_delay", - "commit_siblings", - - "effective_cache_size", - "random_page_cost", - "cpu_tuple_cost", - "cpu_index_tuple_cost", - "cpu_operator_cost", - "geqo_selection_bias", - - "default_transaction_isolation", - "search_path", - - NULL - }; - - static char *backslash_commands[] = { - "\\connect", "\\copy", "\\d", "\\da", "\\dd", "\\df", "\\di", - "\\dl", "\\do", "\\dp", "\\ds", "\\dS", "\\dt", "\\dT", "\\dv", - "\\e", "\\echo", - "\\encoding", "\\g", "\\h", "\\i", "\\l", - "\\lo_import", "\\lo_export", "\\lo_list", "\\lo_unlink", - "\\o", "\\p", "\\pset", "\\q", "\\qecho", "\\r", "\\set", "\\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; - - /* - * 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. - */ - prev_wd = previous_word(start, 0); - prev2_wd = previous_word(start, 1); - prev3_wd = previous_word(start, 2); - prev4_wd = previous_word(start, 3); - - /* If a backslash command was started, continue */ - if (text[0] == '\\') - COMPLETE_WITH_LIST(backslash_commands); - - /* If no previous word, suggest one of the basic sql commands */ - else if (!prev_wd) - COMPLETE_WITH_LIST(sql_commands); - -/* CREATE or DROP */ - /* complete with something you can create or drop */ - else if (strcasecmp(prev_wd, "CREATE") == 0 || strcasecmp(prev_wd, "DROP") == 0) - matches = completion_matches(text, create_command_generator); - -/* ALTER */ - /* complete with what you can alter (TABLE, GROUP, USER) */ - else if (strcasecmp(prev_wd, "ALTER") == 0) - { - char *list_ALTER[] = {"GROUP", "TABLE", "USER", NULL}; - - COMPLETE_WITH_LIST(list_ALTER); - } - - /* - * If we detect ALTER TABLE <name>, suggest either ADD, ALTER, or - * RENAME - */ - else if (strcasecmp(prev3_wd, "ALTER") == 0 && strcasecmp(prev2_wd, "TABLE") == 0) - { - char *list_ALTER2[] = {"ADD", "ALTER", "RENAME", NULL}; - - COMPLETE_WITH_LIST(list_ALTER2); - } - /* If we have TABLE <sth> ALTER|RENAME, provide list of columns */ - else if (strcasecmp(prev3_wd, "TABLE") == 0 && - (strcasecmp(prev_wd, "ALTER") == 0 || strcasecmp(prev_wd, "RENAME") == 0)) - COMPLETE_WITH_ATTR(prev2_wd); - - /* complete ALTER GROUP <foo> with ADD or DROP */ - else if (strcasecmp(prev3_wd, "ALTER") == 0 && strcasecmp(prev2_wd, "GROUP") == 0) - { - char *list_ALTERGROUP[] = {"ADD", "DROP", NULL}; - - COMPLETE_WITH_LIST(list_ALTERGROUP); - } - /* complete ALTER GROUP <foo> ADD|DROP with USER */ - else if (strcasecmp(prev4_wd, "ALTER") == 0 && strcasecmp(prev3_wd, "GROUP") == 0 - && (strcasecmp(prev_wd, "ADD") == 0 || strcasecmp(prev_wd, "DROP") == 0)) - COMPLETE_WITH_CONST("USER"); - /* complete {ALTER} GROUP <foo> ADD|DROP USER with a user name */ - else if (strcasecmp(prev4_wd, "GROUP") == 0 - && (strcasecmp(prev2_wd, "ADD") == 0 || strcasecmp(prev2_wd, "DROP") == 0) - && strcasecmp(prev_wd, "USER") == 0) - COMPLETE_WITH_QUERY(Query_for_list_of_users); - -/* CLUSTER */ - /* If the previous word is CLUSTER, produce list of indexes. */ - else if (strcasecmp(prev_wd, "CLUSTER") == 0) - COMPLETE_WITH_QUERY(Query_for_list_of_indexes); - /* If we have CLUSTER <sth>, then add "ON" */ - else if (strcasecmp(prev2_wd, "CLUSTER") == 0) - COMPLETE_WITH_CONST("ON"); - - /* - * If we have CLUSTER <sth> ON, then add the correct tablename as - * well. - */ - else if (strcasecmp(prev3_wd, "CLUSTER") == 0 && strcasecmp(prev_wd, "ON") == 0) - { - char query_buffer[BUF_SIZE]; /* Some room to build - * queries. */ - - if (snprintf(query_buffer, BUF_SIZE, - "SELECT c1.relname FROM pg_class c1, pg_class c2, pg_index i WHERE c1.oid=i.indrelid and i.indexrelid=c2.oid and c2.relname='%s'", - prev2_wd) == -1) - ERROR_QUERY_TOO_LONG; - else - COMPLETE_WITH_QUERY(query_buffer); - } - -/* COMMENT */ - else if (strcasecmp(prev_wd, "COMMENT") == 0) - COMPLETE_WITH_CONST("ON"); - else if (strcasecmp(prev2_wd, "COMMENT") == 0 && strcasecmp(prev_wd, "ON") == 0) - { - char *list_COMMENT[] = - {"DATABASE", "INDEX", "RULE", "SEQUENCE", "TABLE", "TYPE", "VIEW", - "COLUMN", "AGGREGATE", "FUNCTION", "OPERATOR", "TRIGGER", NULL}; - - COMPLETE_WITH_LIST(list_COMMENT); - } - else if (strcasecmp(prev4_wd, "COMMENT") == 0 && strcasecmp(prev3_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 (strcasecmp(prev_wd, "COPY") == 0 || - strcasecmp(prev_wd, "\\copy") == 0 || - (strcasecmp(prev2_wd, "COPY") == 0 && strcasecmp(prev_wd, "BINARY") == 0)) - COMPLETE_WITH_QUERY(Query_for_list_of_tables); - /* If we have COPY|BINARY <sth>, complete it with "TO" or "FROM" */ - else if (strcasecmp(prev2_wd, "COPY") == 0 || - strcasecmp(prev2_wd, "\\copy") == 0 || - strcasecmp(prev2_wd, "BINARY") == 0) - { - char *list_FROMTO[] = {"FROM", "TO", NULL}; - - COMPLETE_WITH_LIST(list_FROMTO); - } - -/* CREATE INDEX */ - /* First off we complete CREATE UNIQUE with "INDEX" */ - else if (strcasecmp(prev2_wd, "CREATE") == 0 && strcasecmp(prev_wd, "UNIQUE") == 0) - COMPLETE_WITH_CONST("INDEX"); - /* If we have CREATE|UNIQUE INDEX <sth>, then add "ON" */ - else if (strcasecmp(prev2_wd, "INDEX") == 0 && - (strcasecmp(prev3_wd, "CREATE") == 0 || strcasecmp(prev3_wd, "UNIQUE") == 0)) - COMPLETE_WITH_CONST("ON"); - /* Complete ... INDEX <name> ON with a list of tables */ - else if ((strcasecmp(prev3_wd, "INDEX") == 0 && strcasecmp(prev_wd, "ON") == 0) || (0)) - COMPLETE_WITH_QUERY(Query_for_list_of_tables); - - /* - * Complete INDEX <name> ON <table> with a list of table columns - * (which should really be in parens) - */ - else if ((strcasecmp(prev4_wd, "INDEX") == 0 && strcasecmp(prev2_wd, "ON") == 0)) - COMPLETE_WITH_ATTR(prev_wd); - /* same if you put in USING */ - else if ((strcasecmp(prev4_wd, "ON") == 0 && strcasecmp(prev2_wd, "USING") == 0)) - COMPLETE_WITH_ATTR(prev3_wd); - /* Complete USING with an index method */ - else if (strcasecmp(prev_wd, "USING") == 0) - { - char *index_mth[] = {"BTREE", "RTREE", "HASH", NULL}; - - COMPLETE_WITH_LIST(index_mth); - } - -/* CREATE RULE */ - /* Complete "CREATE RULE <sth>" with "AS" */ - else if (strcasecmp(prev3_wd, "CREATE") == 0 && strcasecmp(prev2_wd, "RULE") == 0) - COMPLETE_WITH_CONST("AS"); - /* Complete "CREATE RULE <sth> AS with "ON" */ - else if (strcasecmp(prev4_wd, "CREATE") == 0 && - strcasecmp(prev3_wd, "RULE") == 0 && - strcasecmp(prev_wd, "AS") == 0) - COMPLETE_WITH_CONST("ON"); - /* Complete "RULE * AS ON" with SELECT|UPDATE|DELETE|INSERT */ - else if (strcasecmp(prev4_wd, "RULE") == 0 && - strcasecmp(prev2_wd, "AS") == 0 && - strcasecmp(prev_wd, "ON") == 0) - { - char *rule_events[] = {"SELECT", "UPDATE", "INSERT", "DELETE", NULL}; - - COMPLETE_WITH_LIST(rule_events); - } - /* Complete "AS ON <sth with a 'T' :)>" with a "TO" */ - else if (strcasecmp(prev3_wd, "AS") == 0 && - strcasecmp(prev2_wd, "ON") == 0 && - (toupper((unsigned char) prev_wd[4]) == 'T' || - toupper((unsigned char) prev_wd[5]) == 'T')) - COMPLETE_WITH_CONST("TO"); - /* Complete "AS ON <sth> TO" with a table name */ - else if (strcasecmp(prev4_wd, "AS") == 0 && - strcasecmp(prev3_wd, "ON") == 0 && - strcasecmp(prev_wd, "TO") == 0) - COMPLETE_WITH_QUERY(Query_for_list_of_tables); - -/* CREATE TABLE */ - /* Complete CREATE TEMP with "TABLE" */ - else if (strcasecmp(prev2_wd, "CREATE") == 0 && strcasecmp(prev_wd, "TEMP") == 0) - COMPLETE_WITH_CONST("TABLE"); - -/* CREATE TRIGGER */ - /* is on the agenda . . . */ - -/* CREATE VIEW */ - /* Complete "CREATE VIEW <name>" with "AS" */ - else if (strcasecmp(prev3_wd, "CREATE") == 0 && strcasecmp(prev2_wd, "VIEW") == 0) - COMPLETE_WITH_CONST("AS"); - /* Complete "CREATE VIEW <sth> AS with "SELECT" */ - else if (strcasecmp(prev4_wd, "CREATE") == 0 && - strcasecmp(prev3_wd, "VIEW") == 0 && - strcasecmp(prev_wd, "AS") == 0) - COMPLETE_WITH_CONST("SELECT"); - -/* 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 (strcasecmp(prev_wd, "DELETE") == 0 && - !(strcasecmp(prev2_wd, "ON") == 0 || - strcasecmp(prev2_wd, "GRANT") == 0 || - strcasecmp(prev2_wd, "BEFORE") == 0 || - strcasecmp(prev2_wd, "AFTER") == 0)) - COMPLETE_WITH_CONST("FROM"); - /* Complete DELETE FROM with a list of tables */ - else if (strcasecmp(prev2_wd, "DELETE") == 0 && strcasecmp(prev_wd, "FROM") == 0) - COMPLETE_WITH_QUERY(Query_for_list_of_tables); - /* Complete DELETE FROM <table> with "WHERE" (perhaps a safe idea?) */ - else if (strcasecmp(prev3_wd, "DELETE") == 0 && strcasecmp(prev2_wd, "FROM") == 0) - COMPLETE_WITH_CONST("WHERE"); - -/* EXPLAIN */ - - /* - * Complete EXPLAIN [VERBOSE] (which you'd have to type yourself) with - * the list of SQL commands - */ - else if (strcasecmp(prev_wd, "EXPLAIN") == 0 || - (strcasecmp(prev2_wd, "EXPLAIN") == 0 && strcasecmp(prev_wd, "VERBOSE") == 0)) - COMPLETE_WITH_LIST(sql_commands); - -/* FETCH && MOVE */ - /* Complete FETCH with one of FORWARD, BACKWARD, RELATIVE */ - else if (strcasecmp(prev_wd, "FETCH") == 0 || strcasecmp(prev_wd, "MOVE") == 0) - { - char *list_FETCH1[] = {"FORWARD", "BACKWARD", "RELATIVE", NULL}; - - COMPLETE_WITH_LIST(list_FETCH1); - } - /* Complete FETCH <sth> with one of ALL, NEXT, PRIOR */ - else if (strcasecmp(prev2_wd, "FETCH") == 0 || strcasecmp(prev2_wd, "MOVE") == 0) - { - char *list_FETCH2[] = {"ALL", "NEXT", "PRIOR", NULL}; - - COMPLETE_WITH_LIST(list_FETCH2); - } - - /* - * Complete FETCH <sth1> <sth2> with "FROM" or "TO". (Is there a - * difference? If not, remove one.) - */ - else if (strcasecmp(prev3_wd, "FETCH") == 0 || strcasecmp(prev3_wd, "MOVE") == 0) - { - char *list_FROMTO[] = {"FROM", "TO", NULL}; - - COMPLETE_WITH_LIST(list_FROMTO); - } - -/* GRANT && REVOKE*/ - /* Complete GRANT/REVOKE with a list of privileges */ - else if (strcasecmp(prev_wd, "GRANT") == 0 || strcasecmp(prev_wd, "REVOKE") == 0) - { - char *list_privileg[] = {"SELECT", "INSERT", "UPDATE", "DELETE", "RULE", "ALL", NULL}; - - COMPLETE_WITH_LIST(list_privileg); - } - /* Complete GRANT/REVOKE <sth> with "ON" */ - else if (strcasecmp(prev2_wd, "GRANT") == 0 || strcasecmp(prev2_wd, "REVOKE") == 0) - COMPLETE_WITH_CONST("ON"); - - /* - * Complete GRANT/REVOKE <sth> ON with a list of tables, views, - * sequences, and indexes - */ - else if ((strcasecmp(prev3_wd, "GRANT") == 0 || strcasecmp(prev3_wd, "REVOKE") == 0) && - strcasecmp(prev_wd, "ON") == 0) - COMPLETE_WITH_QUERY("SELECT relname FROM pg_class WHERE relkind in ('r','i','S','v') and substr(relname,1,%d)='%s'"); - /* Complete "GRANT * ON * " with "TO" */ - else if (strcasecmp(prev4_wd, "GRANT") == 0 && strcasecmp(prev2_wd, "ON") == 0) - COMPLETE_WITH_CONST("TO"); - /* Complete "REVOKE * ON * " with "FROM" */ - else if (strcasecmp(prev4_wd, "REVOKE") == 0 && strcasecmp(prev2_wd, "ON") == 0) - COMPLETE_WITH_CONST("FROM"); - - /* - * TODO: to complete with user name we need prev5_wd -- wait for a - * more general solution there - */ - -/* INSERT */ - /* Complete INSERT with "INTO" */ - else if (strcasecmp(prev_wd, "INSERT") == 0) - COMPLETE_WITH_CONST("INTO"); - /* Complete INSERT INTO with table names */ - else if (strcasecmp(prev2_wd, "INSERT") == 0 && strcasecmp(prev_wd, "INTO") == 0) - COMPLETE_WITH_QUERY(Query_for_list_of_tables); - - /* - * Complete INSERT INTO <table> with "VALUES" or "SELECT" or "DEFAULT - * VALUES" - */ - else if (strcasecmp(prev3_wd, "INSERT") == 0 && strcasecmp(prev2_wd, "INTO") == 0) - { - char *list_INSERT[] = {"DEFAULT VALUES", "SELECT", "VALUES", NULL}; - - COMPLETE_WITH_LIST(list_INSERT); - } - /* Insert an open parenthesis after "VALUES" */ - else if (strcasecmp(prev_wd, "VALUES") == 0 && strcasecmp(prev2_wd, "DEFAULT") != 0) - COMPLETE_WITH_CONST("("); - -/* LOCK */ - /* Complete with list of tables */ - else if (strcasecmp(prev_wd, "LOCK") == 0) - COMPLETE_WITH_QUERY(Query_for_list_of_tables); - /* (If you want more with LOCK, you better think about it yourself.) */ - -/* NOTIFY */ - else if (strcasecmp(prev_wd, "NOTIFY") == 0) - COMPLETE_WITH_QUERY("SELECT relname FROM pg_listener WHERE substr(relname,1,%d)='%s'"); - -/* REINDEX */ - else if (strcasecmp(prev_wd, "REINDEX") == 0) - { - char *list_REINDEX[] = {"TABLE", "DATABASE", "INDEX", NULL}; - - COMPLETE_WITH_LIST(list_REINDEX); - } - else if (strcasecmp(prev2_wd, "REINDEX") == 0) - { - if (strcasecmp(prev_wd, "TABLE") == 0) - COMPLETE_WITH_QUERY(Query_for_list_of_tables); - else if (strcasecmp(prev_wd, "DATABASE") == 0) - COMPLETE_WITH_QUERY(Query_for_list_of_databases); - else if (strcasecmp(prev_wd, "INDEX") == 0) - COMPLETE_WITH_QUERY(Query_for_list_of_indexes); - } - -/* SELECT */ - /* naah . . . */ - -/* SET, RESET, SHOW */ - /* Complete with a variable name */ - else if ((strcasecmp(prev_wd, "SET") == 0 && strcasecmp(prev3_wd, "UPDATE") != 0) || - strcasecmp(prev_wd, "RESET") == 0 || - strcasecmp(prev_wd, "SHOW") == 0) - COMPLETE_WITH_LIST(pgsql_variables); - /* Complete "SET TRANSACTION ISOLOLATION LEVEL" */ - else if (strcasecmp(prev2_wd, "SET") == 0 && strcasecmp(prev_wd, "TRANSACTION") == 0) - COMPLETE_WITH_CONST("ISOLATION"); - else if (strcasecmp(prev3_wd, "SET") == 0 && - strcasecmp(prev2_wd, "TRANSACTION") == 0 && - strcasecmp(prev_wd, "ISOLATION") == 0) - COMPLETE_WITH_CONST("LEVEL"); - else if ((strcasecmp(prev4_wd, "SET") == 0 || strcasecmp(prev4_wd, "AS") == 0) && - strcasecmp(prev3_wd, "TRANSACTION") == 0 && - strcasecmp(prev2_wd, "ISOLATION") == 0 && - strcasecmp(prev_wd, "LEVEL") == 0) - { - char *my_list[] = {"READ", "SERIALIZABLE", NULL}; - - COMPLETE_WITH_LIST(my_list); - } - else if (strcasecmp(prev4_wd, "TRANSACTION") == 0 && - strcasecmp(prev3_wd, "ISOLATION") == 0 && - strcasecmp(prev2_wd, "LEVEL") == 0 && - strcasecmp(prev_wd, "READ") == 0) - COMPLETE_WITH_CONST("COMMITTED"); - /* Complete SET CONSTRAINTS <foo> with DEFERRED|IMMEDIATE */ - else if (strcasecmp(prev3_wd, "SET") == 0 && strcasecmp(prev2_wd, "CONSTRAINTS") == 0) - { - char *constraint_list[] = {"DEFERRED", "IMMEDIATE", NULL}; - - COMPLETE_WITH_LIST(constraint_list); - } - /* Complete SET SESSION with AUTHORIZATION or CHARACTERISTICS... */ - else if (strcasecmp(prev2_wd, "SET") == 0 && strcasecmp(prev_wd, "SESSION") == 0) - { - char *my_list[] = {"AUTHORIZATION", - "CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL", - NULL}; - - COMPLETE_WITH_LIST(my_list); - } - /* Complete SET SESSION AUTHORIZATION with username */ - else if (strcasecmp(prev3_wd, "SET") == 0 - && strcasecmp(prev2_wd, "SESSION") == 0 - && strcasecmp(prev_wd, "AUTHORIZATION") == 0) - COMPLETE_WITH_QUERY(Query_for_list_of_users); - /* Complete SET <var> with "TO" */ - else if (strcasecmp(prev2_wd, "SET") == 0 && - strcasecmp(prev4_wd, "UPDATE") != 0) - COMPLETE_WITH_CONST("TO"); - /* Suggest possible variable values */ - else if (strcasecmp(prev3_wd, "SET") == 0 && - (strcasecmp(prev_wd, "TO") == 0 || strcmp(prev_wd, "=") == 0)) - { - if (strcasecmp(prev2_wd, "DateStyle") == 0) - { - char *my_list[] = {"'ISO'", "'SQL'", "'Postgres'", "'European'", "'NonEuropean'", "'German'", "DEFAULT", NULL}; - - COMPLETE_WITH_LIST(my_list); - } - else if (strcasecmp(prev2_wd, "GEQO") == 0) - { - char *my_list[] = {"ON", "OFF", "DEFAULT", NULL}; - - COMPLETE_WITH_LIST(my_list); - } - else - { - char *my_list[] = {"DEFAULT", NULL}; - - COMPLETE_WITH_LIST(my_list); - } - } - -/* TRUNCATE */ - else if (strcasecmp(prev_wd, "TRUNCATE") == 0) - COMPLETE_WITH_QUERY(Query_for_list_of_tables); - -/* UNLISTEN */ - else if (strcasecmp(prev_wd, "UNLISTEN") == 0) - COMPLETE_WITH_QUERY("SELECT relname FROM pg_listener WHERE substr(relname,1,%d)='%s' UNION SELECT '*'::text"); - -/* UPDATE */ - /* If prev. word is UPDATE suggest a list of tables */ - else if (strcasecmp(prev_wd, "UPDATE") == 0) - COMPLETE_WITH_QUERY(Query_for_list_of_tables); - /* Complete UPDATE <table> with "SET" */ - else if (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 (strcasecmp(prev_wd, "SET") == 0) - COMPLETE_WITH_ATTR(prev2_wd); - -/* VACUUM */ - else if (strcasecmp(prev_wd, "VACUUM") == 0) - COMPLETE_WITH_QUERY("SELECT relname FROM pg_class WHERE relkind='r' and substr(relname,1,%d)='%s' UNION SELECT 'FULL'::text UNION SELECT 'ANALYZE'::text"); - else if (strcasecmp(prev2_wd, "VACUUM") == 0 && (strcasecmp(prev_wd, "FULL") == 0 || strcasecmp(prev_wd, "ANALYZE") == 0)) - COMPLETE_WITH_QUERY(Query_for_list_of_tables); - -/* ... FROM ... */ - else if (strcasecmp(prev_wd, "FROM") == 0) - COMPLETE_WITH_QUERY(Query_for_list_of_tables); - - -/* Backslash commands */ - 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) - COMPLETE_WITH_QUERY(Query_for_list_of_tables); - else if (strcmp(prev_wd, "\\h") == 0 || strcmp(prev_wd, "\\help") == 0) - COMPLETE_WITH_LIST(sql_commands); - else if (strcmp(prev_wd, "\\pset") == 0) - { - char *my_list[] = {"format", "border", "expanded", "null", "fieldsep", - "tuples_only", "title", "tableattr", "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, "\\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, "\\s") == 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. - */ - else - { - int i; - - for (i = 0; words_after_create[i].name; i++) - if (strcasecmp(prev_wd, words_after_create[i].name) == 0) - { - COMPLETE_WITH_QUERY(words_after_create[i].query); - 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 */ - free(prev_wd); - free(prev2_wd); - free(prev3_wd); - free(prev4_wd); - - /* Return our Grand List O' Matches */ - return matches; -} - - - -/* GENERATOR FUNCTIONS - - These functions do all the actual work of completing the input. They get - passed the text so far and the count how many times they have been called so - far with the same text. - If you read the above carefully, you'll see that these don't get called - directly but through the readline interface. - The return value is expected to be the full completion of the text, going - through a list each time, or NULL if there are no more matches. The string - will be free()'d be readline, so you must run it through strdup() or - something of that sort. -*/ - -/* This one gives you one from a list of things you can put after CREATE or DROP - as defined above. -*/ -static char * -create_command_generator(char *text, int state) -{ - static int list_index, - string_length; - char *name; - - /* If this is the first time for this completion, init some values */ - if (state == 0) - { - list_index = 0; - string_length = strlen(text); - } - - /* find something that matches */ - while ((name = words_after_create[list_index++].name)) - if (strncasecmp(name, text, string_length) == 0) - return xstrdup(name); - - /* if nothing matches, return NULL */ - return NULL; -} - - -/* This creates a list of matching things, according to a query pointed to - by completion_charp. The query needs to have a %d and a %s in it, which will - be replaced by the string length of the text and the text itself. See some - example queries at the top. - The query may also have another %s in it, which will be replaced by the value - of completion_info_charp. - Ordinarily this would be used to get a list of matching tables or functions, - etc. -*/ -static char * -complete_from_query(char *text, int state) -{ - static int list_index, - string_length; - static PGresult *result = NULL; - char query_buffer[BUF_SIZE]; - const char *item; - - /* - * If this ist the first time for this completion, we fetch a list of - * our "things" from the backend. - */ - if (state == 0) - { - list_index = 0; - string_length = strlen(text); - - /* Need to have a query */ - if (completion_charp == NULL) - return NULL; - - if (snprintf(query_buffer, BUF_SIZE, completion_charp, string_length, text, completion_info_charp) == -1) - { - ERROR_QUERY_TOO_LONG; - return NULL; - } - - result = exec_query(query_buffer); - } - - /* Find something that matches */ - if (result && PQresultStatus(result) == PGRES_TUPLES_OK) - while (list_index < PQntuples(result) && (item = PQgetvalue(result, list_index++, 0))) - if (strncasecmp(text, item, string_length) == 0) - return xstrdup(item); - - /* If nothing matches, free the db structure and return null */ - PQclear(result); - result = NULL; - return NULL; -} - - -/* This function returns in order one of a fixed, NULL pointer terminated list - of strings (if matching). This can be used if there are only a fixed number - SQL words that can appear at certain spot. -*/ -static char * -complete_from_list(char *text, int state) -{ - static int string_length, - list_index; - char *item; - - /* need to have a list */ -#ifdef USE_ASSERT_CHECKING - assert(completion_charpp); -#endif - - /* Initialization */ - if (state == 0) - { - list_index = 0; - string_length = strlen(text); - } - - while ((item = completion_charpp[list_index++])) - if (strncasecmp(text, item, string_length) == 0) - return xstrdup(item); - - /* If no more matches, return null. */ - return NULL; -} - - -/* This function returns one fixed string the first time even if it doesn't - match what's there, and nothing the second time. This should be used if there - is only one possibility that can appear at a certain spot, so misspellings - will be overwritten. - The string to be passed must be in completion_charp. -*/ -static char * -complete_from_const(char *text, int state) -{ - (void) text; /* We don't care about what was entered - * already. */ - -#ifdef USE_ASSERT_CHECKING - assert(completion_charp); -#endif - if (state == 0) - return xstrdup(completion_charp); - else - return NULL; -} - - - -/* HELPER FUNCTIONS */ - - -/* Execute a query and report any errors. This should be the preferred way of - talking to the database in this file. - Note that the query passed in here must not have a semicolon at the end - because we need to append LIMIT xxx. -*/ -static PGresult * -exec_query(char *query) -{ - PGresult *result; - char query_buffer[BUF_SIZE]; - - if (query == NULL || !pset.db || PQstatus(pset.db) != CONNECTION_OK) - return NULL; -#ifdef USE_ASSERT_CHECKING - assert(query[strlen(query) - 1] != ';'); -#endif - - if (snprintf(query_buffer, BUF_SIZE, "%s LIMIT %d;", query, completion_max_records) == -1) - { - ERROR_QUERY_TOO_LONG; - return NULL; - } - - result = PQexec(pset.db, query); - - if (result != NULL && PQresultStatus(result) != PGRES_TUPLES_OK) - { -#if 0 - psql_error("tab completion: %s failed - %s\n", - query, PQresStatus(PQresultStatus(result))); -#endif - PQclear(result); - result = NULL; - } - - return result; -} - - - -/* 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. - TODO: Take account of quotes. (Right now, if you table names contain spaces - you're screwed.) -*/ -static char * -previous_word(int point, int skip) -{ - int i, - start = 0, - end = -1; - char *s; - - while (skip-- >= 0) - { - /* first we look for a space before the current word */ - for (i = point; i >= 0; i--) - if (rl_line_buffer[i] == ' ') - break; - - /* now find the first non-space which then constitutes the end */ - for (; i >= 0; i--) - if (rl_line_buffer[i] != ' ') - { - end = i; - break; - } - - /* - * If no end found we return null, because there is no word before - * the point - */ - if (end == -1) - return NULL; - - /* - * Otherwise we now look for the start. The start is either the - * last character before any space going backwards from the end, - * or it's simply character 0 - */ - for (start = end; start > 0; start--) - if (rl_line_buffer[start - 1] == ' ') - break; - - point = start; - } - - /* make a copy */ - s = (char *) malloc(end - start + 2); - if (!s) - { - psql_error("out of memory\n"); - if (!pset.cur_cmd_interactive) - exit(EXIT_FAILURE); - else - return NULL; - } - - strncpy(s, &rl_line_buffer[start], end - start + 1); - s[end - start + 1] = '\0'; - - return s; -} - - - -#if 0 - -/* - * Surround a string with single quotes. This works for both SQL and - * psql internal. Currently disable because it is reported not to - * cooperate with certain versions of readline. - */ -char * -quote_file_name(char *text, int match_type, char *quote_pointer) -{ - char *s; - size_t length; - - (void) quote_pointer; /* not used */ - - length = strlen(text) +(match_type == SINGLE_MATCH ? 3 : 2); - s = malloc(length); - s[0] = '\''; - strcpy(s + 1, text); - if (match_type == SINGLE_MATCH) - s[length - 2] = '\''; - s[length - 1] = '\0'; - return s; -} - - - -static char * -dequote_file_name(char *text, char quote_char) -{ - char *s; - size_t length; - - if (!quote_char) - return xstrdup(text); - - length = strlen(text); - s = malloc(length - 2 + 1); - strncpy(s, text +1, length - 2); - s[length] = '\0'; - - return s; -} -#endif /* 0 */ - -#endif /* USE_READLINE */ |