diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c index bcd01b7516557e6f9c010703dcb5acd568b6860f..d4ceb9a3e9e9be6142043732bc0ebced66a4a0c4 100644 --- a/src/bin/psql/tab-complete.c +++ b/src/bin/psql/tab-complete.c @@ -3,7 +3,7 @@ * * Copyright (c) 2000-2003, PostgreSQL Global Development Group * - * $Header: /cvsroot/pgsql/src/bin/psql/tab-complete.c,v 1.90 2003/10/28 23:35:52 tgl Exp $ + * $Header: /cvsroot/pgsql/src/bin/psql/tab-complete.c,v 1.91 2003/10/30 21:37:38 tgl Exp $ */ /*---------------------------------------------------------------------- @@ -43,7 +43,6 @@ #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: */ @@ -53,12 +52,12 @@ #ifdef USE_ASSERT_CHECKING #include <assert.h> #endif - #include "libpq-fe.h" - +#include "pqexpbuffer.h" #include "common.h" #include "settings.h" + #ifdef HAVE_RL_FILENAME_COMPLETION_FUNCTION #define filename_completion_function rl_filename_completion_function #else @@ -70,58 +69,239 @@ extern char *filename_completion_function(); #define completion_matches rl_completion_matches #endif -#define BUF_SIZE 2048 -#define ERROR_QUERY_TOO_LONG /* empty */ +/* + * This struct is used to define "schema queries", which are custom-built + * to obtain possibly-schema-qualified names of database objects. There is + * enough similarity in the structure that we don't want to repeat it each + * time. So we put the components of each query into this struct and + * assemble them with the common boilerplate in _complete_from_query(). + */ +typedef struct SchemaQuery +{ + /* + * Name of catalog or catalogs to be queried, with alias, eg. + * "pg_catalog.pg_class c". Note that "pg_namespace n" will be added. + */ + 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. + */ + const char *selcondition; + /* + * Visibility condition --- which rows are visible without schema + * 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". + */ + 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)". + */ + const char *result; + /* + * In some cases a different result must be used for qualified names. + * Enter that here, or write NULL if result can be used. + */ + const char *qualresult; +} SchemaQuery; -/* 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 *complete_from_query(const char *text, int state); -static char *complete_from_schema_query(const char *text, int state); -static char *_complete_from_query(int is_schema_query, - const char *text, int state); -static char *complete_from_const(const char *text, int state); -static char *complete_from_list(const char *text, int state); -static PGresult *exec_query(char *query); +/* Store maximum number of records we want from database queries + * (implemented via SELECT ... LIMIT xx). + */ +static int completion_max_records; -static char *previous_word(int point, int skip); +/* + * Communication variables set by COMPLETE_WITH_FOO macros and then used by + * the completion callback functions. Ugly but there is no better way. + */ +static const char *completion_charp; /* to pass a string */ +static const char * const *completion_charpp; /* to pass a list of strings */ +static const char *completion_info_charp; /* to pass a second string */ +static const SchemaQuery *completion_squery; /* to pass a SchemaQuery */ -#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); -#endif +/* 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. +*/ +#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) +#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) -/* 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. */ -static char *completion_charp; /* if you need to pass a string */ -static char **completion_charpp; /* if you need to pass a list of strings */ -static char *completion_info_charp; /* if you need to pass another - * string */ +/* + * Assembly instructions for schema queries + */ -/* Store how many records from a database query we want to return at most -(implemented via SELECT ... LIMIT xx). */ -static int completion_max_records; +static const SchemaQuery Query_for_list_of_aggregates = { + /* catname */ + "pg_catalog.pg_proc p", + /* selcondition */ + "p.proisagg", + /* viscondition */ + "pg_catalog.pg_function_is_visible(p.oid)", + /* namespace */ + "p.pronamespace", + /* result */ + "pg_catalog.quote_ident(p.proname)", + /* qualresult */ + NULL +}; +static const SchemaQuery Query_for_list_of_datatypes = { + /* catname */ + "pg_catalog.pg_type t", + /* selcondition --- ignore table rowtypes and array types */ + "(t.typrelid = 0 " + " OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid)) " + "AND t.typname !~ '^_'", + /* viscondition */ + "pg_catalog.pg_type_is_visible(t.oid)", + /* namespace */ + "t.typnamespace", + /* result */ + "pg_catalog.format_type(t.oid, NULL)", + /* qualresult */ + "pg_catalog.quote_ident(t.typname)" +}; -/* Initialize the readline library for our purposes. */ -void -initialize_readline(void) -{ - rl_readline_name = pset.progname; - rl_attempted_completion_function = (void *) psql_completion; +static const SchemaQuery Query_for_list_of_domains = { + /* catname */ + "pg_catalog.pg_type t", + /* selcondition */ + "t.typtype = 'd'", + /* viscondition */ + "pg_catalog.pg_type_is_visible(t.oid)", + /* namespace */ + "t.typnamespace", + /* result */ + "pg_catalog.quote_ident(t.typname)", + /* qualresult */ + NULL +}; - rl_basic_word_break_characters = "\t\n@$><=;|&{( "; +static const SchemaQuery Query_for_list_of_functions = { + /* catname */ + "pg_catalog.pg_proc p", + /* selcondition */ + NULL, + /* viscondition */ + "pg_catalog.pg_function_is_visible(p.oid)", + /* namespace */ + "p.pronamespace", + /* result */ + "pg_catalog.quote_ident(p.proname)", + /* qualresult */ + NULL +}; - completion_max_records = 1000; +static const SchemaQuery Query_for_list_of_indexes = { + /* catname */ + "pg_catalog.pg_class c", + /* selcondition */ + "c.relkind IN ('i')", + /* viscondition */ + "pg_catalog.pg_table_is_visible(c.oid)", + /* namespace */ + "c.relnamespace", + /* result */ + "pg_catalog.quote_ident(c.relname)", + /* qualresult */ + NULL +}; - /* - * There is a variable rl_completion_query_items for this but - * apparently it's not defined everywhere. - */ -} +static const SchemaQuery Query_for_list_of_sequences = { + /* catname */ + "pg_catalog.pg_class c", + /* selcondition */ + "c.relkind IN ('S')", + /* viscondition */ + "pg_catalog.pg_table_is_visible(c.oid)", + /* namespace */ + "c.relnamespace", + /* result */ + "pg_catalog.quote_ident(c.relname)", + /* qualresult */ + NULL +}; + +static const SchemaQuery Query_for_list_of_tables = { + /* catname */ + "pg_catalog.pg_class c", + /* selcondition */ + "c.relkind IN ('r')", + /* viscondition */ + "pg_catalog.pg_table_is_visible(c.oid)", + /* namespace */ + "c.relnamespace", + /* result */ + "pg_catalog.quote_ident(c.relname)", + /* qualresult */ + NULL +}; + +static const SchemaQuery Query_for_list_of_tisv = { + /* catname */ + "pg_catalog.pg_class c", + /* selcondition */ + "c.relkind IN ('r', 'i', 'S', 'v')", + /* viscondition */ + "pg_catalog.pg_table_is_visible(c.oid)", + /* namespace */ + "c.relnamespace", + /* result */ + "pg_catalog.quote_ident(c.relname)", + /* qualresult */ + NULL +}; + +static const SchemaQuery Query_for_list_of_tsv = { + /* catname */ + "pg_catalog.pg_class c", + /* selcondition */ + "c.relkind IN ('r', 'S', 'v')", + /* viscondition */ + "pg_catalog.pg_table_is_visible(c.oid)", + /* namespace */ + "c.relnamespace", + /* result */ + "pg_catalog.quote_ident(c.relname)", + /* qualresult */ + NULL +}; + +static const SchemaQuery Query_for_list_of_views = { + /* catname */ + "pg_catalog.pg_class c", + /* selcondition */ + "c.relkind IN ('v')", + /* viscondition */ + "pg_catalog.pg_table_is_visible(c.oid)", + /* namespace */ + "c.relnamespace", + /* result */ + "pg_catalog.quote_ident(c.relname)", + /* qualresult */ + NULL +}; /* @@ -129,32 +309,14 @@ initialize_readline(void) * 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). Beware that the allowed sequences of %s and - * %d are determined by _complete_from_query(). + * 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. + * + * Beware that the allowed sequences of %s and %d are determined by + * _complete_from_query(). */ -#define Query_for_list_of_aggregates \ -" SELECT pg_catalog.quote_ident(proname) " \ -" FROM pg_catalog.pg_proc p" \ -" WHERE proisagg " \ -" AND substring(pg_catalog.quote_ident(proname),1,%d)='%s'" \ -" AND pg_catalog.pg_function_is_visible(p.oid) "\ -" UNION" \ -" SELECT pg_catalog.quote_ident(nspname) || '.'" \ -" FROM pg_catalog.pg_namespace" \ -" WHERE substring(pg_catalog.quote_ident(nspname),1,%d)='%s'" \ -" UNION" \ -" SELECT pg_catalog.quote_ident(nspname) || '.' || pg_catalog.quote_ident(proname)" \ -" FROM pg_catalog.pg_proc p, pg_catalog.pg_namespace n" \ -" WHERE proisagg " \ -" AND substring(pg_catalog.quote_ident(nspname) || '.' || pg_catalog.quote_ident(proname),1,%d)='%s'" \ -" AND pronamespace = n.oid" \ -" AND ('%s' ~ '\\\\.' "\ -" OR (SELECT TRUE "\ -" FROM pg_catalog.pg_namespace "\ -" WHERE substring(pg_catalog.quote_ident(nspname),1,%d)='%s' "\ -" HAVING COUNT(nspname)=1))" - #define Query_for_list_of_attributes \ "SELECT pg_catalog.quote_ident(attname) "\ " FROM pg_catalog.pg_attribute a, pg_catalog.pg_class c "\ @@ -169,104 +331,11 @@ initialize_readline(void) "SELECT pg_catalog.quote_ident(datname) FROM pg_catalog.pg_database "\ " WHERE substring(pg_catalog.quote_ident(datname),1,%d)='%s'" -#define Query_for_list_of_datatypes \ -" SELECT pg_catalog.format_type(t.oid, NULL) "\ -" FROM pg_catalog.pg_type t "\ -" WHERE (t.typrelid = 0 "\ -" OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid)) "\ -" AND t.typname !~ '^_' "\ -" AND substring(pg_catalog.format_type(t.oid, NULL),1,%d)='%s' "\ -" AND pg_catalog.pg_type_is_visible(t.oid) "\ -" UNION "\ -" SELECT pg_catalog.quote_ident(nspname) || '.'"\ -" FROM pg_catalog.pg_namespace "\ -" WHERE substring(pg_catalog.quote_ident(nspname),1,%d)='%s' "\ -" UNION "\ -" SELECT pg_catalog.quote_ident(nspname) || '.' || pg_catalog.format_type(t.oid, NULL)"\ -" FROM pg_catalog.pg_type t, pg_catalog.pg_namespace n "\ -" WHERE(t.typrelid = 0 "\ -" OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid)) "\ -" AND t.typname !~ '^_' "\ -" AND substring(pg_catalog.quote_ident(nspname) || '.' || pg_catalog.format_type(t.oid, NULL),1,%d)='%s' "\ -" AND typnamespace = n.oid "\ -" AND ('%s' ~ '\\\\.' "\ -" OR (SELECT TRUE "\ -" FROM pg_catalog.pg_namespace "\ -" WHERE substring(pg_catalog.quote_ident(nspname),1,%d)='%s' "\ -" HAVING COUNT(nspname)=1))" - -#define Query_for_list_of_domains \ -" SELECT pg_catalog.quote_ident(typname) "\ -" FROM pg_catalog.pg_type t "\ -" WHERE typtype = 'd' "\ -" AND substring(pg_catalog.quote_ident(typname),1,%d)='%s' "\ -" AND pg_catalog.pg_type_is_visible(t.oid) "\ -" UNION" \ -" SELECT pg_catalog.quote_ident(nspname) || '.'"\ -" FROM pg_catalog.pg_namespace "\ -" WHERE substring(pg_catalog.quote_ident(nspname),1,%d)='%s' "\ -" UNION "\ -" SELECT pg_catalog.quote_ident(nspname) || '.' || pg_catalog.quote_ident(typname)"\ -" FROM pg_catalog.pg_type t, pg_catalog.pg_namespace n "\ -" WHERE typtype = 'd' "\ -" AND substring(pg_catalog.quote_ident(nspname) || '.' || pg_catalog.quote_ident(typname),1,%d)='%s' "\ -" AND typnamespace = n.oid "\ -" AND ('%s' ~ '\\\\.' "\ -" OR (SELECT TRUE "\ -" FROM pg_catalog.pg_namespace "\ -" WHERE substring(pg_catalog.quote_ident(nspname),1,%d)='%s' "\ -" HAVING COUNT(nspname)=1))" - #define Query_for_list_of_encodings \ " SELECT DISTINCT pg_catalog.pg_encoding_to_char(conforencoding) "\ " FROM pg_catalog.pg_conversion "\ " WHERE substring(pg_catalog.pg_encoding_to_char(conforencoding),1,%d)=UPPER('%s')" -#define Query_for_list_of_functions \ -" SELECT pg_catalog.quote_ident(proname) "\ -" FROM pg_catalog.pg_proc p "\ -" WHERE substring(pg_catalog.quote_ident(proname),1,%d)='%s'"\ -" AND pg_catalog.pg_function_is_visible(p.oid) "\ -" UNION "\ -" SELECT pg_catalog.quote_ident(nspname) || '.' "\ -" FROM pg_catalog.pg_namespace "\ -" WHERE substring(pg_catalog.quote_ident(nspname),1,%d)='%s' "\ -" UNION "\ -" SELECT pg_catalog.quote_ident(nspname) || '.' || pg_catalog.quote_ident(proname) "\ -" FROM pg_catalog.pg_proc p, pg_catalog.pg_namespace n "\ -" WHERE substring(pg_catalog.quote_ident(nspname) || '.' || pg_catalog.quote_ident(proname),1,%d)='%s' "\ -" AND pronamespace = n.oid "\ -" AND ('%s' ~ '\\\\.' "\ -" OR (SELECT TRUE "\ -" FROM pg_catalog.pg_namespace "\ -" WHERE substring(pg_catalog.quote_ident(nspname),1,%d)='%s' "\ -" HAVING COUNT(nspname)=1))" - -#define Query_for_list_of_indexes \ -" SELECT pg_catalog.quote_ident(relname) "\ -" FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n "\ -" WHERE relkind='i' "\ -" AND substring(pg_catalog.quote_ident(relname),1,%d)='%s' "\ -" AND pg_catalog.pg_table_is_visible(c.oid) "\ -" AND relnamespace = n.oid "\ -" AND n.nspname NOT IN ('pg_catalog', 'pg_toast') "\ -" UNION "\ -" SELECT pg_catalog.quote_ident(nspname) || '.' "\ -" FROM pg_catalog.pg_namespace "\ -" WHERE substring(pg_catalog.quote_ident(nspname),1,%d)='%s' "\ -" UNION "\ -" SELECT pg_catalog.quote_ident(nspname) || '.' || pg_catalog.quote_ident(relname) "\ -" FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n "\ -" WHERE relkind='i' "\ -" AND substring(pg_catalog.quote_ident(nspname) || '.' || pg_catalog.quote_ident(relname),1,%d)='%s' "\ -" AND relnamespace = n.oid "\ -" AND ('%s' ~ '\\\\.' "\ -" OR (SELECT TRUE "\ -" FROM pg_catalog.pg_namespace "\ -" WHERE substring(pg_catalog.quote_ident(nspname),1,%d)='%s' "\ -" HAVING COUNT(nspname)=1))" - - #define Query_for_list_of_languages \ "SELECT pg_catalog.quote_ident(lanname) "\ " FROM pg_language "\ @@ -277,135 +346,14 @@ initialize_readline(void) "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_sequences \ -" SELECT pg_catalog.quote_ident(relname) "\ -" FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n "\ -" WHERE relkind='S' "\ -" AND substring(pg_catalog.quote_ident(relname),1,%d)='%s' "\ -" AND pg_catalog.pg_table_is_visible(c.oid) "\ -" AND relnamespace = n.oid "\ -" AND n.nspname NOT IN ('pg_catalog', 'pg_toast') "\ -" UNION "\ -" SELECT pg_catalog.quote_ident(nspname) || '.' "\ -" FROM pg_catalog.pg_namespace "\ -" WHERE substring(pg_catalog.quote_ident(nspname),1,%d)='%s' "\ -" UNION "\ -" SELECT pg_catalog.quote_ident(nspname) || '.' || pg_catalog.quote_ident(relname) "\ -" FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n "\ -" WHERE relkind='S' "\ -" AND substring(pg_catalog.quote_ident(nspname) || '.' || pg_catalog.quote_ident(relname),1,%d)='%s' "\ -" AND relnamespace = n.oid "\ -" AND ('%s' ~ '\\\\.' "\ -" OR (SELECT TRUE "\ -" FROM pg_catalog.pg_namespace "\ -" WHERE substring(pg_catalog.quote_ident(nspname),1,%d)='%s' "\ -" HAVING COUNT(nspname)=1))" - #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='r' OR c.relkind='v' OR c.relkind='s' OR c.relkind='S') "\ +" WHERE c.relkind IN ('r', 'v', 's', 'S') "\ " AND substring(pg_catalog.quote_ident(relname),1,%d)='%s' "\ -" AND pg_catalog.pg_table_is_visible(c.oid)"\ -" AND relnamespace = n.oid "\ +" AND c.relnamespace = n.oid "\ " AND n.nspname = 'pg_catalog'" -#define Query_for_list_of_tables \ -" SELECT pg_catalog.quote_ident(relname) "\ -" FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n "\ -" WHERE relkind='r' "\ -" AND substring(pg_catalog.quote_ident(relname),1,%d)='%s' "\ -" AND pg_catalog.pg_table_is_visible(c.oid) "\ -" AND relnamespace = n.oid "\ -" AND n.nspname NOT IN ('pg_catalog', 'pg_toast') "\ -" UNION "\ -" SELECT pg_catalog.quote_ident(nspname) || '.' "\ -" FROM pg_catalog.pg_namespace "\ -" WHERE substring(pg_catalog.quote_ident(nspname),1,%d)='%s' "\ -" UNION "\ -" SELECT pg_catalog.quote_ident(nspname) || '.' || pg_catalog.quote_ident(relname) "\ -" FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n "\ -" WHERE relkind='r' "\ -" AND substring(pg_catalog.quote_ident(nspname) || '.' || pg_catalog.quote_ident(relname),1,%d)='%s' "\ -" AND relnamespace = n.oid "\ -" AND ('%s' ~ '\\\\.' "\ -" OR (SELECT TRUE "\ -" FROM pg_catalog.pg_namespace "\ -" WHERE substring(pg_catalog.quote_ident(nspname),1,%d)='%s' "\ -" HAVING COUNT(nspname)=1))" - -#define Query_for_list_of_tisv \ -" SELECT pg_catalog.quote_ident(relname) "\ -" FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n "\ -" WHERE (relkind='r' OR relkind='i' OR relkind='S' OR relkind='v') "\ -" AND substring(pg_catalog.quote_ident(relname),1,%d)='%s' "\ -" AND pg_catalog.pg_table_is_visible(c.oid) "\ -" AND relnamespace = n.oid "\ -" AND n.nspname NOT IN ('pg_catalog', 'pg_toast') "\ -" UNION "\ -" SELECT pg_catalog.quote_ident(nspname) || '.' "\ -" FROM pg_catalog.pg_namespace "\ -" WHERE substring(pg_catalog.quote_ident(nspname),1,%d)='%s' "\ -" UNION "\ -" SELECT pg_catalog.quote_ident(nspname) || '.' || pg_catalog.quote_ident(relname) "\ -" FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n "\ -" WHERE (relkind='r' OR relkind='i' OR relkind='S' OR relkind='v') "\ -" AND substring(pg_catalog.quote_ident(nspname) || '.' || pg_catalog.quote_ident(relname),1,%d)='%s' "\ -" AND relnamespace = n.oid "\ -" AND ('%s' ~ '\\\\.' "\ -" OR (SELECT TRUE "\ -" FROM pg_catalog.pg_namespace "\ -" WHERE substring(pg_catalog.quote_ident(nspname),1,%d)='%s' "\ -" HAVING COUNT(nspname)=1))" - -#define Query_for_list_of_tsv \ -" SELECT pg_catalog.quote_ident(relname) "\ -" FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n "\ -" WHERE (relkind='r' OR relkind='S' OR relkind='v') "\ -" AND substring(pg_catalog.quote_ident(relname),1,%d)='%s' "\ -" AND pg_catalog.pg_table_is_visible(c.oid) "\ -" AND relnamespace = n.oid "\ -" AND n.nspname NOT IN ('pg_catalog', 'pg_toast') "\ -" UNION "\ -" SELECT pg_catalog.quote_ident(nspname) || '.' "\ -" FROM pg_catalog.pg_namespace "\ -" WHERE substring(pg_catalog.quote_ident(nspname),1,%d)='%s' "\ -" UNION "\ -" SELECT pg_catalog.quote_ident(nspname) || '.' || pg_catalog.quote_ident(relname) "\ -" FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n "\ -" WHERE (relkind='r' OR relkind='S' OR relkind='v') "\ -" AND substring(pg_catalog.quote_ident(nspname) || '.' || pg_catalog.quote_ident(relname),1,%d)='%s' "\ -" AND relnamespace = n.oid "\ -" AND ('%s' ~ '\\\\.' "\ -" OR (SELECT TRUE "\ -" FROM pg_catalog.pg_namespace "\ -" WHERE substring(pg_catalog.quote_ident(nspname),1,%d)='%s' "\ -" HAVING COUNT(nspname)=1))" - -#define Query_for_list_of_views \ -" SELECT pg_catalog.quote_ident(relname) "\ -" FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n "\ -" WHERE relkind='v'"\ -" AND substring(pg_catalog.quote_ident(relname),1,%d)='%s' "\ -" AND pg_catalog.pg_table_is_visible(c.oid) "\ -" AND relnamespace = n.oid "\ -" AND n.nspname NOT IN ('pg_catalog', 'pg_toast') "\ -" UNION "\ -" SELECT pg_catalog.quote_ident(nspname) || '.' "\ -" FROM pg_catalog.pg_namespace "\ -" WHERE substring(pg_catalog.quote_ident(nspname),1,%d)='%s' "\ -" UNION "\ -" SELECT pg_catalog.quote_ident(nspname) || '.' || pg_catalog.quote_ident(relname) "\ -" FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n "\ -" WHERE relkind='v' "\ -" AND substring(pg_catalog.quote_ident(nspname) || '.' || pg_catalog.quote_ident(relname),1,%d)='%s' "\ -" AND relnamespace = n.oid "\ -" AND ('%s' ~ '\\\\.' "\ -" OR (SELECT TRUE "\ -" FROM pg_catalog.pg_namespace "\ -" WHERE substring(pg_catalog.quote_ident(nspname),1,%d)='%s' "\ -" HAVING COUNT(nspname)=1))" - #define Query_for_list_of_users \ " SELECT pg_catalog.quote_ident(usename) "\ " FROM pg_catalog.pg_user "\ @@ -420,65 +368,81 @@ initialize_readline(void) " and pg_catalog.quote_ident(c2.relname)='%s'"\ " and pg_catalog.pg_table_is_visible(c2.oid)" -/* 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. -*/ - -#define WITH_SCHEMA 1 -#define NO_SCHEMA 0 +/* + * 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. + */ typedef struct { - char *name; - int with_schema; - char *query; + const char *name; + const char *query; /* simple query, or NULL */ + const SchemaQuery *squery; /* schema query, or NULL */ } pgsql_thing_t; -pgsql_thing_t words_after_create[] = { - {"AGGREGATE", WITH_SCHEMA, Query_for_list_of_aggregates}, - {"CAST", NO_SCHEMA, NULL}, /* Casts have complex structures for - * namees, so skip it */ - {"CONVERSION", NO_SCHEMA, "SELECT pg_catalog.quote_ident(conname) FROM pg_catalog.pg_conversion WHERE substring(pg_catalog.quote_ident(conname),1,%d)='%s'"}, - {"DATABASE", NO_SCHEMA, Query_for_list_of_databases}, - {"DOMAIN", WITH_SCHEMA, Query_for_list_of_domains}, - {"FUNCTION", WITH_SCHEMA, Query_for_list_of_functions}, - {"GROUP", NO_SCHEMA, "SELECT pg_catalog.quote_ident(groname) FROM pg_catalog.pg_group WHERE substring(pg_catalog.quote_ident(groname),1,%d)='%s'"}, - {"LANGUAGE", NO_SCHEMA, Query_for_list_of_languages}, - {"INDEX", WITH_SCHEMA, Query_for_list_of_indexes}, - {"OPERATOR", NO_SCHEMA, NULL}, /* Querying for this is probably - * not such a good idea. */ - {"RULE", NO_SCHEMA, "SELECT pg_catalog.quote_ident(rulename) FROM pg_catalog.pg_rules WHERE substring(pg_catalog.quote_ident(rulename),1,%d)='%s'"}, - {"SCHEMA", NO_SCHEMA, Query_for_list_of_schemas}, - {"SEQUENCE", WITH_SCHEMA, Query_for_list_of_sequences}, - {"TABLE", WITH_SCHEMA, Query_for_list_of_tables}, - {"TEMP", NO_SCHEMA, NULL}, /* for CREATE TEMP TABLE ... */ - {"TRIGGER", NO_SCHEMA, "SELECT pg_catalog.quote_ident(tgname) FROM pg_catalog.pg_trigger WHERE substring(pg_catalog.quote_ident(tgname),1,%d)='%s'"}, - {"TYPE", WITH_SCHEMA, Query_for_list_of_datatypes}, - {"UNIQUE", NO_SCHEMA, NULL}, /* for CREATE UNIQUE INDEX ... */ - {"USER", NO_SCHEMA, Query_for_list_of_users}, - {"VIEW", WITH_SCHEMA, Query_for_list_of_views}, - {NULL, NO_SCHEMA, NULL} /* end of list */ +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 */ + {"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}, + {"DOMAIN", NULL, &Query_for_list_of_domains}, + {"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'"}, + {"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. */ + {"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}, + {"TABLE", NULL, &Query_for_list_of_tables}, + {"TEMP", NULL, NULL}, /* for CREATE TEMP TABLE ... */ + {"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}, + {"VIEW", NULL, &Query_for_list_of_views}, + {NULL, NULL, NULL} /* end of list */ }; -/* 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 above?) - 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. -*/ -#define COMPLETE_WITH_QUERY(query) \ -do { completion_charp = query; matches = completion_matches(text, complete_from_query); } while(0) -#define COMPLETE_WITH_SCHEMA_QUERY(query) \ -do { completion_charp = query; 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) -#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) +/* 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 *complete_from_query(const char *text, int state); +static char *complete_from_schema_query(const char *text, int state); +static char *_complete_from_query(int is_schema_query, + const char *text, int state); +static char *complete_from_const(const char *text, int state); +static char *complete_from_list(const char *text, int state); + +static PGresult *exec_query(const char *query); + +static char *previous_word(int point, int skip); + +#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); +#endif + + +/* 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 = 1000; + + /* + * There is a variable rl_completion_query_items for this but + * apparently it's not defined everywhere. + */ +} /* The completion function. Acc. to readline spec this gets passed the text @@ -498,7 +462,7 @@ psql_completion(char *text, int start, int end) *prev3_wd, *prev4_wd; - static char *sql_commands[] = { + static const char * const sql_commands[] = { "ABORT", "ALTER", "ANALYZE", "BEGIN", "CHECKPOINT", "CLOSE", "CLUSTER", "COMMENT", "COMMIT", "COPY", "CREATE", "DEALLOCATE", "DECLARE", "DELETE", "DROP", "EXECUTE", "EXPLAIN", "FETCH", "GRANT", "INSERT", "LISTEN", "LOAD", "LOCK", "MOVE", "NOTIFY", @@ -506,7 +470,7 @@ psql_completion(char *text, int start, int end) "TRUNCATE", "UNLISTEN", "UPDATE", "VACUUM", NULL }; - static char *pgsql_variables[] = { + static const char * const pgsql_variables[] = { /* these SET arguments are known in gram.y */ "CONSTRAINTS", "NAMES", @@ -612,7 +576,7 @@ psql_completion(char *text, int start, int end) NULL }; - static char *backslash_commands[] = { + static const char * const backslash_commands[] = { "\\a", "\\connect", "\\C", "\\cd", "\\copy", "\\copyright", "\\d", "\\da", "\\dc", "\\dC", "\\dd", "\\dD", "\\df", "\\di", "\\dl", "\\dn", "\\do", "\\dp", "\\ds", "\\dS", "\\dt", "\\dT", @@ -670,8 +634,8 @@ psql_completion(char *text, int start, int end) else if (strcasecmp(prev_wd, "ALTER") == 0 && strcasecmp(prev3_wd, "TABLE") != 0) { - char *list_ALTER[] = {"DATABASE", "GROUP", "SCHEMA", "TABLE", - "TRIGGER", "USER", NULL}; + static const char *const list_ALTER[] = + {"DATABASE", "GROUP", "SCHEMA", "TABLE", "TRIGGER", "USER", NULL}; COMPLETE_WITH_LIST(list_ALTER); } @@ -680,7 +644,8 @@ psql_completion(char *text, int start, int end) else if (strcasecmp(prev3_wd, "ALTER") == 0 && strcasecmp(prev2_wd, "DATABASE") == 0) { - char *list_ALTERDATABASE[] = {"RESET", "SET", NULL}; + static const char *const list_ALTERDATABASE[] = + {"RESET", "SET", NULL}; COMPLETE_WITH_LIST(list_ALTERDATABASE); } @@ -695,7 +660,7 @@ psql_completion(char *text, int start, int end) else if (strcasecmp(prev4_wd, "ALTER") == 0 && strcasecmp(prev3_wd, "TRIGGER") == 0 && strcasecmp(prev_wd, "ON") == 0) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL); /* * If we detect ALTER TABLE <name>, suggest either ADD, DROP, ALTER, @@ -704,8 +669,8 @@ psql_completion(char *text, int start, int end) else if (strcasecmp(prev3_wd, "ALTER") == 0 && strcasecmp(prev2_wd, "TABLE") == 0) { - char *list_ALTER2[] = {"ADD", "ALTER", "DROP", "RENAME", - "OWNER TO", NULL}; + static const char *const list_ALTER2[] = + {"ADD", "ALTER", "DROP", "RENAME", "OWNER TO", NULL}; COMPLETE_WITH_LIST(list_ALTER2); } @@ -719,7 +684,8 @@ psql_completion(char *text, int start, int end) else if (strcasecmp(prev3_wd, "TABLE") == 0 && strcasecmp(prev_wd, "DROP") == 0) { - char *list_TABLEDROP[] = {"COLUMN", "CONSTRAINT", NULL}; + static const char *const list_TABLEDROP[] = + {"COLUMN", "CONSTRAINT", NULL}; COMPLETE_WITH_LIST(list_TABLEDROP); } @@ -733,7 +699,8 @@ psql_completion(char *text, int start, int end) else if (strcasecmp(prev3_wd, "ALTER") == 0 && strcasecmp(prev2_wd, "GROUP") == 0) { - char *list_ALTERGROUP[] = {"ADD", "DROP", NULL}; + static const char *const list_ALTERGROUP[] = + {"ADD", "DROP", NULL}; COMPLETE_WITH_LIST(list_ALTERGROUP); } @@ -753,7 +720,7 @@ psql_completion(char *text, int start, int end) /* ANALYZE */ /* If the previous word is ANALYZE, produce list of tables. */ else if (strcasecmp(prev_wd, "ANALYZE") == 0) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL); /* If we have ANALYZE <table>, complete with semicolon. */ else if (strcasecmp(prev2_wd, "ANALYZE") == 0) COMPLETE_WITH_CONST(";"); @@ -761,7 +728,7 @@ psql_completion(char *text, int start, int end) /* CLUSTER */ /* If the previous word is CLUSTER, produce list of indexes. */ else if (strcasecmp(prev_wd, "CLUSTER") == 0) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL); /* If we have CLUSTER <sth>, then add "ON" */ else if (strcasecmp(prev2_wd, "CLUSTER") == 0) COMPLETE_WITH_CONST("ON"); @@ -783,10 +750,10 @@ psql_completion(char *text, int start, int end) else if (strcasecmp(prev2_wd, "COMMENT") == 0 && strcasecmp(prev_wd, "ON") == 0) { - char *list_COMMENT[] = + static const char *const list_COMMENT[] = {"DATABASE", "INDEX", "RULE", "SCHEMA", "SEQUENCE", "TABLE", - "TYPE", "VIEW", "COLUMN", "AGGREGATE", "FUNCTION", "OPERATOR", - "TRIGGER", "CONSTRAINT", "DOMAIN", NULL}; + "TYPE", "VIEW", "COLUMN", "AGGREGATE", "FUNCTION", "OPERATOR", + "TRIGGER", "CONSTRAINT", "DOMAIN", NULL}; COMPLETE_WITH_LIST(list_COMMENT); } @@ -804,13 +771,14 @@ psql_completion(char *text, int start, int end) strcasecmp(prev_wd, "\\copy") == 0 || (strcasecmp(prev2_wd, "COPY") == 0 && strcasecmp(prev_wd, "BINARY") == 0)) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL); /* 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}; + static const char *const list_FROMTO[] = + {"FROM", "TO", NULL}; COMPLETE_WITH_LIST(list_FROMTO); } @@ -828,7 +796,7 @@ psql_completion(char *text, int start, int end) /* Complete ... INDEX <name> ON with a list of tables */ else if (strcasecmp(prev3_wd, "INDEX") == 0 && strcasecmp(prev_wd, "ON") == 0) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL); /* * Complete INDEX <name> ON <table> with a list of table columns @@ -844,7 +812,8 @@ psql_completion(char *text, int start, int end) /* Complete USING with an index method */ else if (strcasecmp(prev_wd, "USING") == 0) { - char *index_mth[] = {"BTREE", "RTREE", "HASH", "GIST", NULL}; + static const char *const index_mth[] = + {"BTREE", "RTREE", "HASH", "GIST", NULL}; COMPLETE_WITH_LIST(index_mth); } @@ -864,8 +833,8 @@ psql_completion(char *text, int start, int end) strcasecmp(prev2_wd, "AS") == 0 && strcasecmp(prev_wd, "ON") == 0) { - char *rule_events[] = {"SELECT", "UPDATE", "INSERT", - "DELETE", NULL}; + static const char *const rule_events[] = + {"SELECT", "UPDATE", "INSERT", "DELETE", NULL}; COMPLETE_WITH_LIST(rule_events); } @@ -879,7 +848,7 @@ psql_completion(char *text, int start, int end) else if (strcasecmp(prev4_wd, "AS") == 0 && strcasecmp(prev3_wd, "ON") == 0 && strcasecmp(prev_wd, "TO") == 0) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL); /* CREATE TABLE */ /* Complete CREATE TEMP with "TABLE" */ @@ -916,7 +885,7 @@ psql_completion(char *text, int start, int end) /* Complete DELETE FROM with a list of tables */ else if (strcasecmp(prev2_wd, "DELETE") == 0 && strcasecmp(prev_wd, "FROM") == 0) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL); /* Complete DELETE FROM <table> with "WHERE" (perhaps a safe idea?) */ else if (strcasecmp(prev3_wd, "DELETE") == 0 && strcasecmp(prev2_wd, "FROM") == 0) @@ -938,7 +907,8 @@ psql_completion(char *text, int start, int end) else if (strcasecmp(prev_wd, "FETCH") == 0 || strcasecmp(prev_wd, "MOVE") == 0) { - char *list_FETCH1[] = {"FORWARD", "BACKWARD", "RELATIVE", NULL}; + static const char * const list_FETCH1[] = + {"FORWARD", "BACKWARD", "RELATIVE", NULL}; COMPLETE_WITH_LIST(list_FETCH1); } @@ -946,7 +916,8 @@ psql_completion(char *text, int start, int end) else if (strcasecmp(prev2_wd, "FETCH") == 0 || strcasecmp(prev2_wd, "MOVE") == 0) { - char *list_FETCH2[] = {"ALL", "NEXT", "PRIOR", NULL}; + static const char * const list_FETCH2[] = + {"ALL", "NEXT", "PRIOR", NULL}; COMPLETE_WITH_LIST(list_FETCH2); } @@ -958,7 +929,8 @@ psql_completion(char *text, int start, int end) else if (strcasecmp(prev3_wd, "FETCH") == 0 || strcasecmp(prev3_wd, "MOVE") == 0) { - char *list_FROMTO[] = {"FROM", "TO", NULL}; + static const char * const list_FROMTO[] = + {"FROM", "TO", NULL}; COMPLETE_WITH_LIST(list_FROMTO); } @@ -968,7 +940,9 @@ psql_completion(char *text, int start, int end) else if (strcasecmp(prev_wd, "GRANT") == 0 || strcasecmp(prev_wd, "REVOKE") == 0) { - char *list_privileg[] = {"SELECT", "INSERT", "UPDATE", "DELETE", "RULE", "REFERENCES", "TRIGGER", "CREATE", "TEMPORARY", "EXECUTE", "USAGE", "ALL", NULL}; + static const char * const list_privileg[] = + {"SELECT", "INSERT", "UPDATE", "DELETE", "RULE", "REFERENCES", + "TRIGGER", "CREATE", "TEMPORARY", "EXECUTE", "USAGE", "ALL", NULL}; COMPLETE_WITH_LIST(list_privileg); } @@ -991,7 +965,7 @@ psql_completion(char *text, int start, int end) else if ((strcasecmp(prev3_wd, "GRANT") == 0 || strcasecmp(prev3_wd, "REVOKE") == 0) && strcasecmp(prev_wd, "ON") == 0) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsv + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsv, " UNION SELECT 'DATABASE'" " UNION SELECT 'FUNCTION'" " UNION SELECT 'LANGUAGE'" @@ -1005,7 +979,7 @@ psql_completion(char *text, int start, int end) if (strcasecmp(prev_wd, "DATABASE") == 0) COMPLETE_WITH_QUERY(Query_for_list_of_databases); else if (strcasecmp(prev_wd, "FUNCTION") == 0) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL); else if (strcasecmp(prev_wd, "LANGUAGE") == 0) COMPLETE_WITH_QUERY(Query_for_list_of_languages); else if (strcasecmp(prev_wd, "SCHEMA") == 0) @@ -1027,7 +1001,7 @@ psql_completion(char *text, int start, int end) /* Complete INSERT INTO with table names */ else if (strcasecmp(prev2_wd, "INSERT") == 0 && strcasecmp(prev_wd, "INTO") == 0) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL); /* Complete "INSERT INTO <table> (" with attribute names */ else if (rl_line_buffer[start - 1] == '(' && strcasecmp(prev3_wd, "INSERT") == 0 && @@ -1041,7 +1015,8 @@ psql_completion(char *text, int start, int end) else if (strcasecmp(prev3_wd, "INSERT") == 0 && strcasecmp(prev2_wd, "INTO") == 0) { - char *list_INSERT[] = {"DEFAULT VALUES", "SELECT", "VALUES", NULL}; + static const char * const list_INSERT[] = + {"DEFAULT VALUES", "SELECT", "VALUES", NULL}; COMPLETE_WITH_LIST(list_INSERT); } @@ -1050,7 +1025,8 @@ psql_completion(char *text, int start, int end) strcasecmp(prev3_wd, "INTO") == 0 && prev_wd[strlen(prev_wd) - 1] == ')') { - char *list_INSERT[] = {"SELECT", "VALUES", NULL}; + static const char * const list_INSERT[] = + {"SELECT", "VALUES", NULL}; COMPLETE_WITH_LIST(list_INSERT); } @@ -1065,7 +1041,7 @@ psql_completion(char *text, int start, int end) else if (strcasecmp(prev_wd, "LOCK") == 0 || (strcasecmp(prev_wd, "TABLE") == 0 && strcasecmp(prev2_wd, "LOCK") == 0)) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL); /* For the following, handle the case of a single table only for now */ @@ -1082,11 +1058,12 @@ psql_completion(char *text, int start, int end) (strcasecmp(prev3_wd, "TABLE") == 0 && strcasecmp(prev4_wd, "LOCK") == 0))) { - char *lock_modes[] = {"ACCESS SHARE MODE", - "ROW SHARE MODE", "ROW EXCLUSIVE MODE", - "SHARE UPDATE EXCLUSIVE MODE", "SHARE MODE", - "SHARE ROW EXCLUSIVE MODE", - "EXCLUSIVE MODE", "ACCESS EXCLUSIVE MODE", NULL}; + static const char * const lock_modes[] = + {"ACCESS SHARE MODE", + "ROW SHARE MODE", "ROW EXCLUSIVE MODE", + "SHARE UPDATE EXCLUSIVE MODE", "SHARE MODE", + "SHARE ROW EXCLUSIVE MODE", + "EXCLUSIVE MODE", "ACCESS EXCLUSIVE MODE", NULL}; COMPLETE_WITH_LIST(lock_modes); } @@ -1098,18 +1075,19 @@ psql_completion(char *text, int start, int end) /* REINDEX */ else if (strcasecmp(prev_wd, "REINDEX") == 0) { - char *list_REINDEX[] = {"TABLE", "DATABASE", "INDEX", NULL}; + static const char * const 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_SCHEMA_QUERY(Query_for_list_of_tables); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL); else if (strcasecmp(prev_wd, "DATABASE") == 0) COMPLETE_WITH_QUERY(Query_for_list_of_databases); else if (strcasecmp(prev_wd, "INDEX") == 0) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL); } /* SELECT */ @@ -1130,7 +1108,8 @@ psql_completion(char *text, int start, int end) strcasecmp(prev2_wd, "AS") == 0 && strcasecmp(prev_wd, "TRANSACTION") == 0)) { - char *my_list[] = {"ISOLATION", "READ", NULL}; + static const char * const my_list[] = + {"ISOLATION", "READ", NULL}; COMPLETE_WITH_LIST(my_list); } @@ -1144,7 +1123,8 @@ psql_completion(char *text, int start, int end) strcasecmp(prev2_wd, "ISOLATION") == 0 && strcasecmp(prev_wd, "LEVEL") == 0) { - char *my_list[] = {"READ", "SERIALIZABLE", NULL}; + static const char * const my_list[] = + {"READ", "SERIALIZABLE", NULL}; COMPLETE_WITH_LIST(my_list); } @@ -1158,7 +1138,8 @@ psql_completion(char *text, int start, int end) strcasecmp(prev2_wd, "TRANSACTION") == 0 && strcasecmp(prev_wd, "READ") == 0) { - char *my_list[] = {"ONLY", "WRITE", NULL}; + static const char * const my_list[] = + {"ONLY", "WRITE", NULL}; COMPLETE_WITH_LIST(my_list); } @@ -1166,7 +1147,8 @@ psql_completion(char *text, int start, int end) else if (strcasecmp(prev3_wd, "SET") == 0 && strcasecmp(prev2_wd, "CONSTRAINTS") == 0) { - char *constraint_list[] = {"DEFERRED", "IMMEDIATE", NULL}; + static const char * const constraint_list[] = + {"DEFERRED", "IMMEDIATE", NULL}; COMPLETE_WITH_LIST(constraint_list); } @@ -1174,9 +1156,8 @@ psql_completion(char *text, int start, int end) else if (strcasecmp(prev2_wd, "SET") == 0 && strcasecmp(prev_wd, "SESSION") == 0) { - char *my_list[] = {"AUTHORIZATION", - "CHARACTERISTICS AS TRANSACTION", - NULL}; + static const char * const my_list[] = + {"AUTHORIZATION", "CHARACTERISTICS AS TRANSACTION", NULL}; COMPLETE_WITH_LIST(my_list); } @@ -1195,22 +1176,25 @@ psql_completion(char *text, int start, int end) { if (strcasecmp(prev2_wd, "DateStyle") == 0) { - char *my_list[] = {"ISO", "SQL", "Postgres", "German", - "YMD", "DMY", "MDY", - "US", "European", "NonEuropean", - "DEFAULT", NULL}; + static const char * const my_list[] = + {"ISO", "SQL", "Postgres", "German", + "YMD", "DMY", "MDY", + "US", "European", "NonEuropean", + "DEFAULT", NULL}; COMPLETE_WITH_LIST(my_list); } else if (strcasecmp(prev2_wd, "GEQO") == 0) { - char *my_list[] = {"ON", "OFF", "DEFAULT", NULL}; + static const char * const my_list[] = + {"ON", "OFF", "DEFAULT", NULL}; COMPLETE_WITH_LIST(my_list); } else { - char *my_list[] = {"DEFAULT", NULL}; + static const char * const my_list[] = + {"DEFAULT", NULL}; COMPLETE_WITH_LIST(my_list); } @@ -1218,7 +1202,7 @@ psql_completion(char *text, int start, int end) /* TRUNCATE */ else if (strcasecmp(prev_wd, "TRUNCATE") == 0) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL); /* UNLISTEN */ else if (strcasecmp(prev_wd, "UNLISTEN") == 0) @@ -1227,7 +1211,7 @@ psql_completion(char *text, int start, int end) /* UPDATE */ /* If prev. word is UPDATE suggest a list of tables */ else if (strcasecmp(prev_wd, "UPDATE") == 0) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL); /* Complete UPDATE <table> with "SET" */ else if (strcasecmp(prev2_wd, "UPDATE") == 0) COMPLETE_WITH_CONST("SET"); @@ -1242,7 +1226,7 @@ psql_completion(char *text, int start, int end) /* VACUUM */ else if (strcasecmp(prev_wd, "VACUUM") == 0) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, " UNION SELECT 'FULL'" " UNION SELECT 'ANALYZE'" " UNION SELECT 'VERBOSE'"); @@ -1250,7 +1234,7 @@ psql_completion(char *text, int start, int end) (strcasecmp(prev_wd, "FULL") == 0 || strcasecmp(prev_wd, "ANALYZE") == 0 || strcasecmp(prev_wd, "VERBOSE") == 0)) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL); /* WHERE */ /* Simple case of the word before the where being the table name */ @@ -1260,7 +1244,7 @@ psql_completion(char *text, int start, int end) /* ... FROM ... */ /* TODO: also include SRF ? */ else if (strcasecmp(prev_wd, "FROM") == 0) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsv); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsv, NULL); /* Backslash commands */ @@ -1268,40 +1252,41 @@ psql_completion(char *text, int start, int end) 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); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tisv, NULL); else if (strcmp(prev_wd, "\\da") == 0) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_aggregates); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_aggregates, NULL); else if (strcmp(prev_wd, "\\dD") == 0) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains, NULL); else if (strcmp(prev_wd, "\\df") == 0 || strcmp(prev_wd, "\\df+") == 0) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions, NULL); else if (strcmp(prev_wd, "\\di") == 0 || strcmp(prev_wd, "\\di+") == 0) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL); else if (strcmp(prev_wd, "\\dn") == 0) COMPLETE_WITH_QUERY(Query_for_list_of_schemas); else if (strcmp(prev_wd, "\\dp") == 0 || strcmp(prev_wd, "\\z") == 0) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsv); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsv, NULL); else if (strcmp(prev_wd, "\\ds") == 0 || strcmp(prev_wd, "\\ds+") == 0) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences); + 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) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL); else if (strcmp(prev_wd, "\\dT") == 0 || strcmp(prev_wd, "\\dT+") == 0) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes); + 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) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, 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, "\\pset") == 0) { - char *my_list[] = {"format", "border", "expanded", - "null", "fieldsep", "tuples_only", "title", "tableattr", "pager", - "recordsep", NULL}; + static const char * const my_list[] = + {"format", "border", "expanded", + "null", "fieldsep", "tuples_only", "title", "tableattr", "pager", + "recordsep", NULL}; COMPLETE_WITH_LIST(my_list); } @@ -1326,14 +1311,17 @@ psql_completion(char *text, int start, int end) int i; for (i = 0; words_after_create[i].name; i++) + { if (strcasecmp(prev_wd, words_after_create[i].name) == 0) { - if (words_after_create[i].with_schema == WITH_SCHEMA) - COMPLETE_WITH_SCHEMA_QUERY(words_after_create[i].query); - else + if (words_after_create[i].query) COMPLETE_WITH_QUERY(words_after_create[i].query); + else if (words_after_create[i].squery) + COMPLETE_WITH_SCHEMA_QUERY(*words_after_create[i].squery, + NULL); break; } + } } @@ -1385,7 +1373,7 @@ create_command_generator(const char *text, int state) { static int list_index, string_length; - char *name; + const char *name; /* If this is the first time for this completion, init some values */ if (state == 0) @@ -1451,7 +1439,7 @@ _complete_from_query(int is_schema_query, const char *text, int state) */ if (state == 0) { - char query_buffer[BUF_SIZE]; + PQExpBufferData query_buffer; char *e_text; char *e_info_charp; @@ -1462,10 +1450,6 @@ _complete_from_query(int is_schema_query, const char *text, int state) PQclear(result); result = NULL; - /* Need to have a query */ - if (completion_charp == NULL) - return NULL; - /* Set up suitably-escaped copies of textual inputs */ if (text) { @@ -1493,28 +1477,107 @@ _complete_from_query(int is_schema_query, const char *text, int state) else e_info_charp = NULL; + initPQExpBuffer(&query_buffer); + if (is_schema_query) { - if (snprintf(query_buffer, BUF_SIZE, completion_charp, - string_length, e_text, - string_length, e_text, - string_length, e_text, - e_text, - string_length, e_text, - string_length, e_text) == -1) - ERROR_QUERY_TOO_LONG; - else - result = exec_query(query_buffer); + /* completion_squery gives us the pieces to assemble */ + const char *qualresult = completion_squery->qualresult; + + if (qualresult == NULL) + qualresult = completion_squery->result; + + /* Get unqualified names matching the input-so-far */ + appendPQExpBuffer(&query_buffer, "SELECT %s FROM %s WHERE ", + completion_squery->result, + completion_squery->catname); + 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); + /* + * 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')"); + } + + /* + * 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) || '.' " + "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) =" + " 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. + */ + appendPQExpBuffer(&query_buffer, "\nUNION\n" + "SELECT pg_catalog.quote_ident(n.nspname) || '.' || %s " + "FROM %s, pg_catalog.pg_namespace n " + "WHERE %s = n.oid AND ", + qualresult, + completion_squery->catname, + completion_squery->namespace); + if (completion_squery->selcondition) + appendPQExpBuffer(&query_buffer, "%s AND ", + completion_squery->selcondition); + appendPQExpBuffer(&query_buffer, "substring(pg_catalog.quote_ident(n.nspname) || '.' || %s,1,%d)='%s'", + qualresult, + string_length, e_text); + /* This condition exploits the single-matching-schema rule to speed up the query */ + appendPQExpBuffer(&query_buffer, + " 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) =" + " substring('%s',1,pg_catalog.length(pg_catalog.quote_ident(nspname))+1)) = 1", + string_length, e_text); + + /* If an addon query was provided, use it */ + if (completion_charp) + appendPQExpBuffer(&query_buffer, "\n%s", completion_charp); } else { - if (snprintf(query_buffer, BUF_SIZE, completion_charp, - string_length, e_text, e_info_charp) == -1) - ERROR_QUERY_TOO_LONG; - else - result = exec_query(query_buffer); + /* completion_charp is an sprintf-style format string */ + appendPQExpBuffer(&query_buffer, completion_charp, + string_length, e_text, e_info_charp); } + /* Limit the number of records in the result */ + appendPQExpBuffer(&query_buffer, "\nLIMIT %d", + completion_max_records); + + result = exec_query(query_buffer.data); + + termPQExpBuffer(&query_buffer); + if (e_text) free(e_text); if (e_info_charp) @@ -1550,7 +1613,7 @@ complete_from_list(const char *text, int state) list_index, matches; static bool casesensitive; - char *item; + const char *item; /* need to have a list */ #ifdef USE_ASSERT_CHECKING @@ -1623,37 +1686,25 @@ complete_from_const(const char *text, int state) /* 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. -*/ +/* + * Execute a query and report any errors. This should be the preferred way of + * talking to the database in this file. + */ static PGresult * -exec_query(char *query) +exec_query(const 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_buffer); + result = PQexec(pset.db, query); if (result != NULL && PQresultStatus(result) != PGRES_TUPLES_OK) { #if 0 psql_error("tab completion: %s failed - %s\n", - query_buffer, PQresStatus(PQresultStatus(result))); + query, PQresStatus(PQresultStatus(result))); #endif PQclear(result); result = NULL;