Commit a6699f61 authored by Bruce Momjian's avatar Bruce Momjian

Attached are two patches for psql's tab-completion.c.

The first cleans up a couple of minor errors and ommissions
and adds tab completion support to more slash commands, e.g.
\dv.

The second is an attempt to add tab completion for schemas
and fully qualified relation names (e.g. public.mytable ).
I think this covers the TODO-item:
"Allow psql to do table completion for SELECT * FROM schema_part and table
completion for SELECT * FROM schema_name."

This happens via union selects querying:
 - relation_name in current search path;
 - schema_name;
 - schema.relation_name
matching the current input string.

E.g:
  SELECT p[TAB]
will produce a list of all appropriate relation names in the current search
path which begin with 'p', and also all schema names which begin with 'p';
  \d pub[TAB]
will produce any relation names in the current search path and also
any schema names beginning with 'pub';
  \d public.[TAB]
will produce a list of all relations in the schema 'public';
  \d public.my[TAB]
produces all relation names beginning with 'my' in schema 'public'.

It seems to work for me; comments, suggestions, particularly regarding
the coding and queries, are very welcome.

Note that tables, indexes, views and sequences relations in the
'pg_catalog' namespace are excluded even though they are in
the current search path. I found not doing this produced annoying behaviour
when expanding names beginning with 'p'. People who work with system
tables a lot may not like this though; I can look for another solution
if necessary.

Ian Barwick
parent 9d6f5ee8
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
* *
* Copyright 2000-2002 by PostgreSQL Global Development Group * Copyright 2000-2002 by PostgreSQL Global Development Group
* *
* $Header: /cvsroot/pgsql/src/bin/psql/tab-complete.c,v 1.73 2003/02/06 20:25:33 tgl Exp $ * $Header: /cvsroot/pgsql/src/bin/psql/tab-complete.c,v 1.74 2003/03/27 16:45:01 momjian Exp $
*/ */
/*---------------------------------------------------------------------- /*----------------------------------------------------------------------
...@@ -78,6 +78,8 @@ extern char *filename_completion_function(); ...@@ -78,6 +78,8 @@ extern char *filename_completion_function();
static char **psql_completion(char *text, int start, int end); static char **psql_completion(char *text, int start, int end);
static char *create_command_generator(char *text, int state); static char *create_command_generator(char *text, int state);
static char *complete_from_query(char *text, int state); static char *complete_from_query(char *text, int state);
static char *complete_from_schema_query(char *text, int state);
static char *_complete_from_query(int is_schema_query, char *text, int state);
static char *complete_from_const(char *text, int state); static char *complete_from_const(char *text, int state);
static char *complete_from_list(char *text, int state); static char *complete_from_list(char *text, int state);
...@@ -124,43 +126,311 @@ initialize_readline(void) ...@@ -124,43 +126,311 @@ initialize_readline(void)
* the %s will be replaced by the text entered so far, the %d by its length. * the %s will be replaced by the text entered so far, the %d by its length.
*/ */
#define Query_for_list_of_tables "SELECT relname FROM pg_catalog.pg_class WHERE (relkind='r' or relkind='v') and substr(relname,1,%d)='%s' and pg_catalog.pg_table_is_visible(oid)" #define Query_for_list_of_aggregates \
#define Query_for_list_of_indexes "SELECT relname FROM pg_catalog.pg_class WHERE relkind='i' and substr(relname,1,%d)='%s' and pg_catalog.pg_table_is_visible(oid)" " SELECT DISTINCT proname " \
#define Query_for_list_of_databases "SELECT datname FROM pg_catalog.pg_database WHERE substr(datname,1,%d)='%s'" " FROM pg_catalog.pg_proc" \
#define Query_for_list_of_attributes "SELECT a.attname FROM pg_catalog.pg_attribute a, pg_catalog.pg_class c WHERE c.oid = a.attrelid and a.attnum>0 and not a.attisdropped and substr(a.attname,1,%d)='%s' and c.relname='%s' and pg_catalog.pg_table_is_visible(c.oid)" " WHERE proisagg " \
#define Query_for_list_of_users "SELECT usename FROM pg_catalog.pg_user WHERE substr(usename,1,%d)='%s'" " AND substr(proname,1,%d)='%s'" \
" UNION" \
" SELECT nspname || '.' AS relname" \
" FROM pg_catalog.pg_namespace" \
" WHERE substr(nspname,1,%d)='%s'" \
" UNION" \
" SELECT DISTINCT nspname || '.' || proname AS relname" \
" FROM pg_catalog.pg_proc p, pg_catalog.pg_namespace n" \
" WHERE proisagg " \
" AND substr(nspname || '.' || proname,1,%d)='%s'" \
" AND pronamespace = n.oid" \
" AND ('%s' ~ '^.*\\\\.' "\
" OR (SELECT TRUE "\
" FROM pg_catalog.pg_namespace "\
" WHERE substr(nspname,1,%d)='%s' "\
" HAVING COUNT(nspname)=1))"
#define Query_for_list_of_attributes \
"SELECT a.attname FROM pg_catalog.pg_attribute a, pg_catalog.pg_class c "\
" WHERE c.oid = a.attrelid "\
" AND a.attnum > 0 "\
" AND NOT a.attisdropped "\
" AND substr(a.attname,1,%d)='%s' "\
" AND c.relname='%s' "\
" AND pg_catalog.pg_table_is_visible(c.oid)"
#define Query_for_list_of_databases \
"SELECT datname FROM pg_catalog.pg_database "\
" WHERE substr(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 substr(pg_catalog.format_type(t.oid, NULL),1,%d)='%s' "\
" UNION "\
" SELECT nspname || '.' AS relname "\
" FROM pg_catalog.pg_namespace "\
" WHERE substr(nspname,1,%d)='%s' "\
" UNION "\
" SELECT nspname || '.' || pg_catalog.format_type(t.oid, NULL) AS relname "\
" 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 substr(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 substr(nspname,1,%d)='%s' "\
" HAVING COUNT(nspname)=1))"
#define Query_for_list_of_domains \
" SELECT typname "\
" FROM pg_catalog.pg_type t "\
" WHERE typtype = 'd' "\
" AND substr(typname,1,%d)='%s' "\
" UNION" \
" SELECT nspname || '.' "\
" FROM pg_catalog.pg_namespace "\
" WHERE substr(nspname,1,%d)='%s' "\
" UNION "\
" SELECT nspname || '.' || pg_catalog.format_type(t.oid, NULL) "\
" FROM pg_catalog.pg_type t, pg_catalog.pg_namespace n "\
" WHERE typtype = 'd' "\
" AND substr(nspname || '.' || typname,1,%d)='%s' "\
" AND typnamespace = n.oid "\
" AND ('%s' ~ '^.*\\\\.' "\
" OR (SELECT TRUE "\
" FROM pg_catalog.pg_namespace "\
" WHERE substr(nspname,1,%d)='%s' "\
" HAVING COUNT(nspname)=1))"
#define Query_for_list_of_functions \
" SELECT DISTINCT proname || '()' "\
" FROM pg_catalog.pg_proc p, pg_catalog.pg_namespace n "\
" WHERE substr(proname,1,%d)='%s'"\
" AND pg_catalog.pg_function_is_visible(p.oid) "\
" AND pronamespace = n.oid "\
" UNION "\
" SELECT nspname || '.' "\
" FROM pg_catalog.pg_namespace "\
" WHERE substr(nspname,1,%d)='%s' "\
" UNION "\
" SELECT nspname || '.' || proname "\
" FROM pg_catalog.pg_proc p, pg_catalog.pg_namespace n "\
" WHERE substr(nspname || '.' || proname,1,%d)='%s' "\
" AND pronamespace = n.oid "\
" AND ('%s' ~ '^.*\\\\.' "\
" OR (SELECT TRUE "\
" FROM pg_catalog.pg_namespace "\
" WHERE substr(nspname,1,%d)='%s' "\
" HAVING COUNT(nspname)=1))"
#define Query_for_list_of_indexes \
" SELECT relname "\
" FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n "\
" WHERE relkind='i' "\
" AND substr(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 nspname || '.' "\
" FROM pg_catalog.pg_namespace "\
" WHERE substr(nspname,1,%d)='%s' "\
" UNION "\
" SELECT nspname || '.' || relname "\
" FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n "\
" WHERE relkind='i' "\
" AND substr(nspname || '.' || relname,1,%d)='%s' "\
" AND relnamespace = n.oid "\
" AND ('%s' ~ '^.*\\\\.' "\
" OR (SELECT TRUE "\
" FROM pg_catalog.pg_namespace "\
" WHERE substr(nspname,1,%d)='%s' "\
" HAVING COUNT(nspname)=1))"
#define Query_for_list_of_languages \
"SELECT lanname "\
" FROM pg_language "\
" WHERE lanname != 'internal' "\
" AND substr(lanname,1,%d)='%s' "
#define Query_for_list_of_schemas \
"SELECT nspname FROM pg_catalog.pg_namespace "\
" WHERE substr(nspname,1,%d)='%s'"
#define Query_for_list_of_sequences \
" SELECT relname "\
" FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n "\
" WHERE relkind='S' "\
" AND substr(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 nspname || '.' "\
" FROM pg_catalog.pg_namespace "\
" WHERE substr(nspname,1,%d)='%s' "\
" UNION "\
" SELECT nspname || '.' || relname "\
" FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n "\
" WHERE relkind='S' "\
" AND substr(nspname || '.' || relname,1,%d)='%s' "\
" AND relnamespace = n.oid "\
" AND ('%s' ~ '^.*\\\\.' "\
" OR (SELECT TRUE "\
" FROM pg_catalog.pg_namespace "\
" WHERE substr(nspname,1,%d)='%s' "\
" HAVING COUNT(nspname)=1))"
#define Query_for_list_of_system_relations \
"SELECT c.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') "\
" AND substr(c.relname,1,%d)='%s' "\
" AND pg_catalog.pg_table_is_visible(c.oid)"\
" AND relnamespace = n.oid "\
" AND n.nspname = 'pg_catalog'"
#define Query_for_list_of_tables \
" SELECT relname "\
" FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n "\
" WHERE relkind='r' "\
" AND substr(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 nspname || '.' "\
" FROM pg_catalog.pg_namespace "\
" WHERE substr(nspname || '.',1,%d)='%s' "\
" UNION "\
" SELECT nspname || '.' || relname "\
" FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n "\
" WHERE relkind='r' "\
" AND substr(nspname || '.' || relname,1,%d)='%s' "\
" AND relnamespace = n.oid "\
" AND ('%s' ~ '^.*\\\\.' "\
" OR (SELECT TRUE "\
" FROM pg_catalog.pg_namespace n1 "\
" WHERE substr(nspname ||'.',1,%d)='%s' "\
" HAVING COUNT(nspname)=1))"
#define Query_for_list_of_tisv \
" SELECT 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 substr(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 nspname || '.' "\
" FROM pg_catalog.pg_namespace "\
" WHERE substr(nspname,1,%d)='%s' "\
" UNION "\
" SELECT nspname || '.' || 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 substr(nspname || '.' || relname,1,%d)='%s' "\
" AND relnamespace = n.oid "\
" AND ('%s' ~ '^.*\\\\.' "\
" OR (SELECT TRUE "\
" FROM pg_catalog.pg_namespace "\
" WHERE substr(nspname,1,%d)='%s' "\
" HAVING COUNT(nspname)=1))"
#define Query_for_list_of_tsv \
" SELECT relname "\
" FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n "\
" WHERE (relkind='r' OR relkind='S' OR relkind='v') "\
" AND substr(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 nspname || '.' "\
" FROM pg_catalog.pg_namespace "\
" WHERE substr(nspname,1,%d)='%s' "\
" UNION "\
" SELECT nspname || '.' || relname "\
" FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n "\
" WHERE (relkind='r' OR relkind='S' OR relkind='v') "\
" AND substr(nspname || '.' || relname,1,%d)='%s' "\
" AND relnamespace = n.oid "\
" AND ('%s' ~ '^.*\\\\.' "\
" OR (SELECT TRUE "\
" FROM pg_catalog.pg_namespace "\
" WHERE substr(nspname,1,%d)='%s' "\
" HAVING COUNT(nspname)=1))"
#define Query_for_list_of_views \
" SELECT relname "\
" FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n "\
" WHERE relkind='v'"\
" AND substr(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 nspname || '.' "\
" FROM pg_catalog.pg_namespace "\
" WHERE substr(nspname,1,%d)='%s' "\
" UNION "\
" SELECT nspname || '.' || relname "\
" FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n "\
" WHERE relkind='v' "\
" AND substr(nspname || '.' || relname,1,%d)='%s' "\
" AND relnamespace = n.oid "\
" AND ('%s' ~ '^.*\\\\.' "\
" OR (SELECT TRUE "\
" FROM pg_catalog.pg_namespace "\
" WHERE substr(nspname,1,%d)='%s' "\
" HAVING COUNT(nspname)=1))"
#define Query_for_list_of_users \
" SELECT usename "\
" FROM pg_catalog.pg_user "\
" WHERE substr(usename,1,%d)='%s'"
/* This is a list of all "things" in Pgsql, which can show up after CREATE or /* 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. DROP; and there is also a query to get a list of them.
*/ */
#define WITH_SCHEMA 1
#define NO_SCHEMA 0
typedef struct typedef struct
{ {
char *name; char *name;
int with_schema;
char *query; char *query;
} pgsql_thing_t; } pgsql_thing_t;
pgsql_thing_t words_after_create[] = { pgsql_thing_t words_after_create[] = {
{"AGGREGATE", "SELECT DISTINCT proname FROM pg_catalog.pg_proc WHERE proisagg AND substr(proname,1,%d)='%s'"}, {"AGGREGATE", WITH_SCHEMA, Query_for_list_of_aggregates},
{"CAST", NULL}, /* Casts have complex structures for namees, so skip it */ {"CAST", NULL, NULL}, /* Casts have complex structures for namees, so skip it */
{"CONVERSION", "SELECT conname FROM pg_catalog.pg_conversion WHERE substr(conname,1,%d)='%s'"}, {"CONVERSION", NO_SCHEMA, "SELECT conname FROM pg_catalog.pg_conversion WHERE substr(conname,1,%d)='%s'"},
{"DATABASE", Query_for_list_of_databases}, {"DATABASE", NO_SCHEMA, Query_for_list_of_databases},
{"DOMAIN", "SELECT typname FROM pg_catalog.pg_type WHERE typtype = 'd' AND substr(typname,1,%d)='%s'"}, {"DOMAIN", WITH_SCHEMA, Query_for_list_of_domains},
{"FUNCTION", "SELECT DISTINCT proname FROM pg_catalog.pg_proc WHERE substr(proname,1,%d)='%s'"}, {"FUNCTION", WITH_SCHEMA, Query_for_list_of_functions},
{"GROUP", "SELECT groname FROM pg_catalog.pg_group WHERE substr(groname,1,%d)='%s'"}, {"GROUP", NO_SCHEMA, "SELECT groname FROM pg_catalog.pg_group WHERE substr(groname,1,%d)='%s'"},
{"LANGUAGE", "SELECT lanname FROM pg_catalog.pg_language WHERE substr(lanname,1,%d)='%s'"}, {"LANGUAGE", NO_SCHEMA, Query_for_list_of_languages},
{"INDEX", Query_for_list_of_indexes}, {"INDEX", WITH_SCHEMA, Query_for_list_of_indexes},
{"OPERATOR", NULL}, /* Querying for this is probably not such {"OPERATOR", NULL, NULL}, /* Querying for this is probably not such
* a good idea. */ * a good idea. */
{"RULE", "SELECT rulename FROM pg_catalog.pg_rules WHERE substr(rulename,1,%d)='%s'"}, {"RULE", NO_SCHEMA, "SELECT rulename FROM pg_catalog.pg_rules WHERE substr(rulename,1,%d)='%s'"},
{"SCHEMA", "SELECT nspname FROM pg_catalog.pg_namespace WHERE substr(nspname,1,%d)='%s'"}, {"SCHEMA", NO_SCHEMA, Query_for_list_of_schemas},
{"SEQUENCE", "SELECT relname FROM pg_catalog.pg_class WHERE relkind='S' and substr(relname,1,%d)='%s'"}, {"SEQUENCE", WITH_SCHEMA, Query_for_list_of_sequences},
{"TABLE", Query_for_list_of_tables}, {"TABLE", WITH_SCHEMA, Query_for_list_of_tables},
{"TEMP", NULL}, /* for CREATE TEMP TABLE ... */ {"TEMP", NULL, NULL}, /* for CREATE TEMP TABLE ... */
{"TRIGGER", "SELECT tgname FROM pg_catalog.pg_trigger WHERE substr(tgname,1,%d)='%s'"}, {"TRIGGER", NO_SCHEMA, "SELECT tgname FROM pg_catalog.pg_trigger WHERE substr(tgname,1,%d)='%s'"},
{"TYPE", "SELECT typname FROM pg_catalog.pg_type WHERE substr(typname,1,%d)='%s'"}, {"TYPE", WITH_SCHEMA, Query_for_list_of_datatypes },
{"UNIQUE", NULL}, /* for CREATE UNIQUE INDEX ... */ {"UNIQUE", NULL, NULL}, /* for CREATE UNIQUE INDEX ... */
{"USER", Query_for_list_of_users}, {"USER", NO_SCHEMA, Query_for_list_of_users},
{"VIEW", "SELECT viewname FROM pg_catalog.pg_views WHERE substr(viewname,1,%d)='%s'"}, {"VIEW", WITH_SCHEMA, Query_for_list_of_views},
{NULL, NULL} /* end of list */ {NULL, NULL} /* end of list */
}; };
...@@ -168,12 +438,15 @@ pgsql_thing_t words_after_create[] = { ...@@ -168,12 +438,15 @@ pgsql_thing_t words_after_create[] = {
/* A couple of macros to ease typing. You can use these to complete the given /* A couple of macros to ease typing. You can use these to complete the given
string with string with
1) The results from a query you pass it. (Perhaps one of those above?) 1) The results from a query you pass it. (Perhaps one of those above?)
2) The items from a null-pointer-terminated list. 2) The results from a schema query you pass it.
3) A string constant 3) The items from a null-pointer-terminated list.
4) The list of attributes to the given table. 4) A string constant
5) The list of attributes to the given table.
*/ */
#define COMPLETE_WITH_QUERY(query) \ #define COMPLETE_WITH_QUERY(query) \
do { completion_charp = query; matches = completion_matches(text, complete_from_query); } while(0) 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) \ #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) \ #define COMPLETE_WITH_CONST(string) \
...@@ -314,8 +587,9 @@ psql_completion(char *text, int start, int end) ...@@ -314,8 +587,9 @@ psql_completion(char *text, int start, int end)
static char *backslash_commands[] = { static char *backslash_commands[] = {
"\\a", "\\connect", "\\C", "\\cd", "\\copy", "\\copyright", "\\a", "\\connect", "\\C", "\\cd", "\\copy", "\\copyright",
"\\d", "\\da", "\\dd", "\\dD", "\\df", "\\di", "\\dl", "\\do", "\\d", "\\da", "\\dc", "\\dC", "\\dd", "\\dD", "\\df", "\\di",
"\\dp", "\\ds", "\\dS", "\\dt", "\\dT", "\\dv","\\du", "\\dl", "\\dn", "\\do", "\\dp", "\\ds", "\\dS", "\\dt", "\\dT",
"\\dv", "\\du",
"\\e", "\\echo", "\\encoding", "\\e", "\\echo", "\\encoding",
"\\f", "\\g", "\\h", "\\help", "\\H", "\\i", "\\l", "\\f", "\\g", "\\h", "\\help", "\\H", "\\i", "\\l",
"\\lo_import", "\\lo_export", "\\lo_list", "\\lo_unlink", "\\lo_import", "\\lo_export", "\\lo_list", "\\lo_unlink",
...@@ -391,7 +665,7 @@ psql_completion(char *text, int start, int end) ...@@ -391,7 +665,7 @@ psql_completion(char *text, int start, int end)
else if (strcasecmp(prev4_wd, "ALTER") == 0 && else if (strcasecmp(prev4_wd, "ALTER") == 0 &&
strcasecmp(prev3_wd, "TRIGGER") == 0 && strcasecmp(prev3_wd, "TRIGGER") == 0 &&
strcasecmp(prev_wd, "ON") == 0) strcasecmp(prev_wd, "ON") == 0)
COMPLETE_WITH_QUERY(Query_for_list_of_tables); COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
/* /*
* If we detect ALTER TABLE <name>, suggest either ADD, DROP, ALTER, * If we detect ALTER TABLE <name>, suggest either ADD, DROP, ALTER,
...@@ -448,7 +722,7 @@ psql_completion(char *text, int start, int end) ...@@ -448,7 +722,7 @@ psql_completion(char *text, int start, int end)
/* ANALYZE */ /* ANALYZE */
/* If the previous word is ANALYZE, produce list of tables. */ /* If the previous word is ANALYZE, produce list of tables. */
else if (strcasecmp(prev_wd, "ANALYZE") == 0) else if (strcasecmp(prev_wd, "ANALYZE") == 0)
COMPLETE_WITH_QUERY(Query_for_list_of_tables); COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
/* If we have ANALYZE <table>, complete with semicolon. */ /* If we have ANALYZE <table>, complete with semicolon. */
else if (strcasecmp(prev2_wd, "ANALYZE") == 0) else if (strcasecmp(prev2_wd, "ANALYZE") == 0)
COMPLETE_WITH_CONST(";"); COMPLETE_WITH_CONST(";");
...@@ -456,7 +730,7 @@ psql_completion(char *text, int start, int end) ...@@ -456,7 +730,7 @@ psql_completion(char *text, int start, int end)
/* CLUSTER */ /* CLUSTER */
/* If the previous word is CLUSTER, produce list of indexes. */ /* If the previous word is CLUSTER, produce list of indexes. */
else if (strcasecmp(prev_wd, "CLUSTER") == 0) else if (strcasecmp(prev_wd, "CLUSTER") == 0)
COMPLETE_WITH_QUERY(Query_for_list_of_indexes); COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes);
/* If we have CLUSTER <sth>, then add "ON" */ /* If we have CLUSTER <sth>, then add "ON" */
else if (strcasecmp(prev2_wd, "CLUSTER") == 0) else if (strcasecmp(prev2_wd, "CLUSTER") == 0)
COMPLETE_WITH_CONST("ON"); COMPLETE_WITH_CONST("ON");
...@@ -506,7 +780,7 @@ psql_completion(char *text, int start, int end) ...@@ -506,7 +780,7 @@ psql_completion(char *text, int start, int end)
strcasecmp(prev_wd, "\\copy") == 0 || strcasecmp(prev_wd, "\\copy") == 0 ||
(strcasecmp(prev2_wd, "COPY") == 0 && (strcasecmp(prev2_wd, "COPY") == 0 &&
strcasecmp(prev_wd, "BINARY") == 0)) strcasecmp(prev_wd, "BINARY") == 0))
COMPLETE_WITH_QUERY(Query_for_list_of_tables); COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
/* If we have COPY|BINARY <sth>, complete it with "TO" or "FROM" */ /* If we have COPY|BINARY <sth>, complete it with "TO" or "FROM" */
else if (strcasecmp(prev2_wd, "COPY") == 0 || else if (strcasecmp(prev2_wd, "COPY") == 0 ||
strcasecmp(prev2_wd, "\\copy") == 0 || strcasecmp(prev2_wd, "\\copy") == 0 ||
...@@ -530,7 +804,7 @@ psql_completion(char *text, int start, int end) ...@@ -530,7 +804,7 @@ psql_completion(char *text, int start, int end)
/* Complete ... INDEX <name> ON with a list of tables */ /* Complete ... INDEX <name> ON with a list of tables */
else if (strcasecmp(prev3_wd, "INDEX") == 0 && else if (strcasecmp(prev3_wd, "INDEX") == 0 &&
strcasecmp(prev_wd, "ON") == 0) strcasecmp(prev_wd, "ON") == 0)
COMPLETE_WITH_QUERY(Query_for_list_of_tables); COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
/* /*
* Complete INDEX <name> ON <table> with a list of table columns * Complete INDEX <name> ON <table> with a list of table columns
...@@ -581,7 +855,7 @@ psql_completion(char *text, int start, int end) ...@@ -581,7 +855,7 @@ psql_completion(char *text, int start, int end)
else if (strcasecmp(prev4_wd, "AS") == 0 && else if (strcasecmp(prev4_wd, "AS") == 0 &&
strcasecmp(prev3_wd, "ON") == 0 && strcasecmp(prev3_wd, "ON") == 0 &&
strcasecmp(prev_wd, "TO") == 0) strcasecmp(prev_wd, "TO") == 0)
COMPLETE_WITH_QUERY(Query_for_list_of_tables); COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
/* CREATE TABLE */ /* CREATE TABLE */
/* Complete CREATE TEMP with "TABLE" */ /* Complete CREATE TEMP with "TABLE" */
...@@ -618,7 +892,7 @@ psql_completion(char *text, int start, int end) ...@@ -618,7 +892,7 @@ psql_completion(char *text, int start, int end)
/* Complete DELETE FROM with a list of tables */ /* Complete DELETE FROM with a list of tables */
else if (strcasecmp(prev2_wd, "DELETE") == 0 && else if (strcasecmp(prev2_wd, "DELETE") == 0 &&
strcasecmp(prev_wd, "FROM") == 0) strcasecmp(prev_wd, "FROM") == 0)
COMPLETE_WITH_QUERY(Query_for_list_of_tables); COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
/* Complete DELETE FROM <table> with "WHERE" (perhaps a safe idea?) */ /* Complete DELETE FROM <table> with "WHERE" (perhaps a safe idea?) */
else if (strcasecmp(prev3_wd, "DELETE") == 0 && else if (strcasecmp(prev3_wd, "DELETE") == 0 &&
strcasecmp(prev2_wd, "FROM") == 0) strcasecmp(prev2_wd, "FROM") == 0)
...@@ -683,26 +957,51 @@ psql_completion(char *text, int start, int end) ...@@ -683,26 +957,51 @@ psql_completion(char *text, int start, int end)
* Complete GRANT/REVOKE <sth> ON with a list of tables, views, * Complete GRANT/REVOKE <sth> ON with a list of tables, views,
* sequences, and indexes * sequences, and indexes
* *
* XXX should also offer DATABASE, FUNCTION, LANGUAGE, SCHEMA here * keywords DATABASE, FUNCTION, LANGUAGE, SCHEMA added to query result
* via UNION; seems to work intuitively
*
* Note: GRANT/REVOKE can get quite complex; tab-completion as implemented
* here will only work if the privilege list contains exactly one privilege
*/ */
else if ((strcasecmp(prev3_wd, "GRANT") == 0 || else if ((strcasecmp(prev3_wd, "GRANT") == 0 ||
strcasecmp(prev3_wd, "REVOKE") == 0) && strcasecmp(prev3_wd, "REVOKE") == 0) &&
strcasecmp(prev_wd, "ON") == 0) strcasecmp(prev_wd, "ON") == 0)
COMPLETE_WITH_QUERY("SELECT relname FROM pg_catalog.pg_class " COMPLETE_WITH_QUERY("SELECT relname FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n "
"WHERE relkind in ('r','i','S','v') AND " " WHERE relkind in ('r','S','v') "
"substr(relname,1,%d)='%s' AND pg_catalog.pg_table_is_visible(oid)"); " AND substr(relname,1,%d)='%s' "
/* Complete "GRANT * ON * " with "TO" */ " AND pg_catalog.pg_table_is_visible(c.oid) "
else if (strcasecmp(prev4_wd, "GRANT") == 0 && " AND relnamespace = n.oid "
strcasecmp(prev2_wd, "ON") == 0) " AND n.nspname NOT IN ('pg_catalog', 'pg_toast') "
COMPLETE_WITH_CONST("TO"); " UNION "
/* Complete "REVOKE * ON * " with "FROM" */ "SELECT 'DATABASE' AS relname "
else if (strcasecmp(prev4_wd, "REVOKE") == 0 && " UNION "
"SELECT 'FUNCTION' AS relname "
" UNION "
"SELECT 'LANGUAGE' AS relname "
" UNION "
"SELECT 'SCHEMA' AS relname ");
/* Complete "GRANT/REVOKE * ON * " with "TO" */
else if ((strcasecmp(prev4_wd, "GRANT") == 0 ||
strcasecmp(prev4_wd, "REVOKE") == 0) &&
strcasecmp(prev2_wd, "ON") == 0) strcasecmp(prev2_wd, "ON") == 0)
COMPLETE_WITH_CONST("FROM"); {
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);
else if(strcasecmp(prev_wd, "LANGUAGE") == 0)
COMPLETE_WITH_QUERY(Query_for_list_of_languages);
else if(strcasecmp(prev_wd, "SCHEMA") == 0)
COMPLETE_WITH_QUERY(Query_for_list_of_schemas);
else
COMPLETE_WITH_CONST("TO");
}
/* /*
* TODO: to complete with user name we need prev5_wd -- wait for a * TODO: to complete with user name we need prev5_wd -- wait for a
* more general solution there * more general solution there
* same for GRANT <sth> ON { DATABASE | FUNCTION | LANGUAGE | SCHEMA } xxx TO
*/ */
/* INSERT */ /* INSERT */
...@@ -712,7 +1011,7 @@ psql_completion(char *text, int start, int end) ...@@ -712,7 +1011,7 @@ psql_completion(char *text, int start, int end)
/* Complete INSERT INTO with table names */ /* Complete INSERT INTO with table names */
else if (strcasecmp(prev2_wd, "INSERT") == 0 && else if (strcasecmp(prev2_wd, "INSERT") == 0 &&
strcasecmp(prev_wd, "INTO") == 0) strcasecmp(prev_wd, "INTO") == 0)
COMPLETE_WITH_QUERY(Query_for_list_of_tables); COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
/* Complete "INSERT INTO <table> (" with attribute names */ /* Complete "INSERT INTO <table> (" with attribute names */
else if (rl_line_buffer[start - 1] == '(' && else if (rl_line_buffer[start - 1] == '(' &&
strcasecmp(prev3_wd, "INSERT") == 0 && strcasecmp(prev3_wd, "INSERT") == 0 &&
...@@ -749,8 +1048,8 @@ psql_completion(char *text, int start, int end) ...@@ -749,8 +1048,8 @@ psql_completion(char *text, int start, int end)
/* Complete LOCK [TABLE] with a list of tables */ /* Complete LOCK [TABLE] with a list of tables */
else if (strcasecmp(prev_wd, "LOCK") == 0 || else if (strcasecmp(prev_wd, "LOCK") == 0 ||
(strcasecmp(prev_wd, "TABLE") == 0 && (strcasecmp(prev_wd, "TABLE") == 0 &&
strcasecmp(prev2_wd, "LOCK"))) strcasecmp(prev2_wd, "LOCK") == 0))
COMPLETE_WITH_QUERY(Query_for_list_of_tables); COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
/* For the following, handle the case of a single table only for now */ /* For the following, handle the case of a single table only for now */
...@@ -765,7 +1064,7 @@ psql_completion(char *text, int start, int end) ...@@ -765,7 +1064,7 @@ psql_completion(char *text, int start, int end)
else if (strcasecmp(prev_wd, "IN") == 0 && else if (strcasecmp(prev_wd, "IN") == 0 &&
(strcasecmp(prev3_wd, "LOCK") == 0 || (strcasecmp(prev3_wd, "LOCK") == 0 ||
(strcasecmp(prev3_wd, "TABLE") == 0 && (strcasecmp(prev3_wd, "TABLE") == 0 &&
strcasecmp(prev3_wd, "LOCK")))) strcasecmp(prev4_wd, "LOCK") == 0)))
{ {
char *lock_modes[] = {"ACCESS SHARE MODE", char *lock_modes[] = {"ACCESS SHARE MODE",
"ROW SHARE MODE", "ROW EXCLUSIVE MODE", "ROW SHARE MODE", "ROW EXCLUSIVE MODE",
...@@ -790,11 +1089,11 @@ psql_completion(char *text, int start, int end) ...@@ -790,11 +1089,11 @@ psql_completion(char *text, int start, int end)
else if (strcasecmp(prev2_wd, "REINDEX") == 0) else if (strcasecmp(prev2_wd, "REINDEX") == 0)
{ {
if (strcasecmp(prev_wd, "TABLE") == 0) if (strcasecmp(prev_wd, "TABLE") == 0)
COMPLETE_WITH_QUERY(Query_for_list_of_tables); COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
else if (strcasecmp(prev_wd, "DATABASE") == 0) else if (strcasecmp(prev_wd, "DATABASE") == 0)
COMPLETE_WITH_QUERY(Query_for_list_of_databases); COMPLETE_WITH_QUERY(Query_for_list_of_databases);
else if (strcasecmp(prev_wd, "INDEX") == 0) else if (strcasecmp(prev_wd, "INDEX") == 0)
COMPLETE_WITH_QUERY(Query_for_list_of_indexes); COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes);
} }
/* SELECT */ /* SELECT */
...@@ -901,7 +1200,7 @@ psql_completion(char *text, int start, int end) ...@@ -901,7 +1200,7 @@ psql_completion(char *text, int start, int end)
/* TRUNCATE */ /* TRUNCATE */
else if (strcasecmp(prev_wd, "TRUNCATE") == 0) else if (strcasecmp(prev_wd, "TRUNCATE") == 0)
COMPLETE_WITH_QUERY(Query_for_list_of_tables); COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
/* UNLISTEN */ /* UNLISTEN */
else if (strcasecmp(prev_wd, "UNLISTEN") == 0) else if (strcasecmp(prev_wd, "UNLISTEN") == 0)
...@@ -910,7 +1209,7 @@ psql_completion(char *text, int start, int end) ...@@ -910,7 +1209,7 @@ psql_completion(char *text, int start, int end)
/* UPDATE */ /* UPDATE */
/* If prev. word is UPDATE suggest a list of tables */ /* If prev. word is UPDATE suggest a list of tables */
else if (strcasecmp(prev_wd, "UPDATE") == 0) else if (strcasecmp(prev_wd, "UPDATE") == 0)
COMPLETE_WITH_QUERY(Query_for_list_of_tables); COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
/* Complete UPDATE <table> with "SET" */ /* Complete UPDATE <table> with "SET" */
else if (strcasecmp(prev2_wd, "UPDATE") == 0) else if (strcasecmp(prev2_wd, "UPDATE") == 0)
COMPLETE_WITH_CONST("SET"); COMPLETE_WITH_CONST("SET");
...@@ -929,7 +1228,7 @@ psql_completion(char *text, int start, int end) ...@@ -929,7 +1228,7 @@ psql_completion(char *text, int start, int end)
else if (strcasecmp(prev2_wd, "VACUUM") == 0 && else if (strcasecmp(prev2_wd, "VACUUM") == 0 &&
(strcasecmp(prev_wd, "FULL") == 0 || (strcasecmp(prev_wd, "FULL") == 0 ||
strcasecmp(prev_wd, "ANALYZE") == 0)) strcasecmp(prev_wd, "ANALYZE") == 0))
COMPLETE_WITH_QUERY(Query_for_list_of_tables); COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables);
/* WHERE */ /* WHERE */
/* Simple case of the word before the where being the table name */ /* Simple case of the word before the where being the table name */
...@@ -937,15 +1236,41 @@ psql_completion(char *text, int start, int end) ...@@ -937,15 +1236,41 @@ psql_completion(char *text, int start, int end)
COMPLETE_WITH_ATTR(prev2_wd); COMPLETE_WITH_ATTR(prev2_wd);
/* ... FROM ... */ /* ... FROM ... */
/* TODO: also include SRF ? */
else if (strcasecmp(prev_wd, "FROM") == 0) else if (strcasecmp(prev_wd, "FROM") == 0)
COMPLETE_WITH_QUERY(Query_for_list_of_tables); COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsv);
/* Backslash commands */ /* Backslash commands */
/* TODO: \dc \dd \dl */
else if (strcmp(prev_wd, "\\connect") == 0 || strcmp(prev_wd, "\\c") == 0) else if (strcmp(prev_wd, "\\connect") == 0 || strcmp(prev_wd, "\\c") == 0)
COMPLETE_WITH_QUERY(Query_for_list_of_databases); COMPLETE_WITH_QUERY(Query_for_list_of_databases);
else if (strcmp(prev_wd, "\\d") == 0) else if (strcmp(prev_wd, "\\d") == 0 || strcmp(prev_wd, "\\d+") == 0)
COMPLETE_WITH_QUERY(Query_for_list_of_tables); COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tisv);
else if (strcmp(prev_wd, "\\da") == 0)
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_aggregates);
else if (strcmp(prev_wd, "\\dD") == 0)
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains);
else if (strcmp(prev_wd, "\\df") == 0 || strcmp(prev_wd, "\\df+") == 0)
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_functions);
else if (strcmp(prev_wd, "\\di") == 0 || strcmp(prev_wd, "\\di+") == 0)
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes);
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);
else if (strcmp(prev_wd, "\\ds") == 0 || strcmp(prev_wd, "\\ds+") == 0)
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences);
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);
else if (strcmp(prev_wd, "\\dT") == 0 || strcmp(prev_wd, "\\dT+") == 0)
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes);
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);
else if (strcmp(prev_wd, "\\h") == 0 || strcmp(prev_wd, "\\help") == 0) else if (strcmp(prev_wd, "\\h") == 0 || strcmp(prev_wd, "\\help") == 0)
COMPLETE_WITH_LIST(sql_commands); COMPLETE_WITH_LIST(sql_commands);
else if (strcmp(prev_wd, "\\pset") == 0) else if (strcmp(prev_wd, "\\pset") == 0)
...@@ -979,7 +1304,10 @@ psql_completion(char *text, int start, int end) ...@@ -979,7 +1304,10 @@ psql_completion(char *text, int start, int end)
for (i = 0; words_after_create[i].name; i++) for (i = 0; words_after_create[i].name; i++)
if (strcasecmp(prev_wd, words_after_create[i].name) == 0) if (strcasecmp(prev_wd, words_after_create[i].name) == 0)
{ {
COMPLETE_WITH_QUERY(words_after_create[i].query); if(words_after_create[i].with_schema == WITH_SCHEMA)
COMPLETE_WITH_SCHEMA_QUERY(words_after_create[i].query);
else
COMPLETE_WITH_QUERY(words_after_create[i].query);
break; break;
} }
} }
...@@ -1052,17 +1380,39 @@ create_command_generator(char *text, int state) ...@@ -1052,17 +1380,39 @@ create_command_generator(char *text, int state)
} }
/* The following two functions are wrappers for _complete_from_query */
static char *
complete_from_query(char *text, int state)
{
return _complete_from_query(0, text, state);
}
static char *
complete_from_schema_query(char *text, int state)
{
return _complete_from_query(1, text, state);
}
/* This creates a list of matching things, according to a query pointed to /* 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 by completion_charp.
be replaced by the string length of the text and the text itself. See some The query can be one of two kinds:
example queries at the top. - A simple query which must contain a %d and a %s, which will be replaced
The query may also have another %s in it, which will be replaced by the value by the string length of the text and the text itself. The query may also
of completion_info_charp. have another %s in it, which will be replaced by the value of
Ordinarily this would be used to get a list of matching tables or functions, completion_info_charp.
etc. or:
- A schema query used for completion of both schema and relation names;
these are more complex and must contain in the following order:
%d %s %d %s %d %s %s %d %s
where %d is the string length of the text and %s the text itself.
See top of file for examples of both kinds of query.
*/ */
static char * static char *
complete_from_query(char *text, int state) _complete_from_query(int is_schema_query, char *text, int state)
{ {
static int list_index, static int list_index,
string_length; string_length;
...@@ -1083,10 +1433,20 @@ complete_from_query(char *text, int state) ...@@ -1083,10 +1433,20 @@ complete_from_query(char *text, int state)
if (completion_charp == NULL) if (completion_charp == NULL)
return NULL; return NULL;
if (snprintf(query_buffer, BUF_SIZE, completion_charp, string_length, text, completion_info_charp) == -1) if(is_schema_query)
{ {
ERROR_QUERY_TOO_LONG; if (snprintf(query_buffer, BUF_SIZE, completion_charp, string_length, text, string_length, text, string_length, text, text, string_length, text,string_length,text) == -1)
return NULL; {
ERROR_QUERY_TOO_LONG;
return NULL;
}
}
else {
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); result = exec_query(query_buffer);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment