*
* 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 $
*/
/*----------------------------------------------------------------------
#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_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
#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
+};
/*
* 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 "\
"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 "\
"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 "\
" 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
*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",
"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",
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",
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);
}
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);
}
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,
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);
}
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);
}
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);
}
/* 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(";");
/* 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");
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);
}
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);
}
/* 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
/* 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);
}
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);
}
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" */
/* 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)
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);
}
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);
}
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);
}
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);
}
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'"
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)
/* 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 &&
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);
}
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);
}
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 */
(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);
}
/* 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 */
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);
}
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);
}
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);
}
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);
}
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);
}
{
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);
}
/* 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)
/* 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");
/* 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'");
(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 */
/* ... 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 */
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);
}
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;
}
+ }
}
{
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)
*/
if (state == 0)
{
- char query_buffer[BUF_SIZE];
+ PQExpBufferData query_buffer;
char *e_text;
char *e_info_charp;
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)
{
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)
list_index,
matches;
static bool casesensitive;
- char *item;
+ const char *item;
/* need to have a list */
#ifdef USE_ASSERT_CHECKING
/* 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;