1405 lines
51 KiB
C++
1405 lines
51 KiB
C++
/*
|
|
* psql - the PostgreSQL interactive terminal
|
|
*
|
|
* Copyright (c) 2000-2012, PostgreSQL Global Development Group
|
|
*
|
|
* src/bin/psql/tab-complete.c
|
|
*/
|
|
|
|
/* ----------------------------------------------------------------------
|
|
* This file implements a somewhat more sophisticated readline "TAB
|
|
* completion" in psql. It is not intended to be AI, to replace
|
|
* learning SQL, or to relieve you from thinking about what you're
|
|
* doing. Also it does not always give you all the syntactically legal
|
|
* completions, only those that are the most common or the ones that
|
|
* the programmer felt most like implementing.
|
|
*
|
|
* CAVEAT: Tab completion causes queries to be sent to the backend.
|
|
* The number of tuples returned gets limited, in most default
|
|
* installations to 1000, but if you still don't like this prospect,
|
|
* you can turn off tab completion in your ~/.inputrc (or else
|
|
* ${INPUTRC}) file so:
|
|
*
|
|
* $if psql
|
|
* set disable-completion on
|
|
* $endif
|
|
*
|
|
* See `man 3 readline' or `info readline' for the full details. Also,
|
|
* hence the
|
|
*
|
|
* BUGS:
|
|
*
|
|
* - If you split your queries across lines, this whole thing gets
|
|
* confused. (To fix this, one would have to read psql's query
|
|
* buffer rather than readline's line buffer, which would require
|
|
* some major revisions of things.)
|
|
*
|
|
* - Table or attribute names with spaces in it may confuse it.
|
|
*
|
|
* - Quotes, parenthesis, and other funny characters are not handled
|
|
* all that gracefully.
|
|
* ----------------------------------------------------------------------
|
|
*/
|
|
#include "settings.h"
|
|
#include "postgres_fe.h"
|
|
#include "tab-complete.h"
|
|
#include "input.h"
|
|
|
|
/* If we don't have this, we might as well forget about the whole thing: */
|
|
#ifdef USE_READLINE
|
|
|
|
#include <ctype.h>
|
|
#include "libpq/libpq-fe.h"
|
|
#include "libpq/pqexpbuffer.h"
|
|
#include "common.h"
|
|
#include "stringutils.h"
|
|
|
|
#ifndef WIN32
|
|
#include "libpq/libpq-int.h"
|
|
#endif
|
|
|
|
#define filename_completion_function rl_filename_completion_function
|
|
|
|
#define completion_matches rl_completion_matches
|
|
|
|
/* word break characters */
|
|
#define WORD_BREAKS "\t\n@$><=;|&{() "
|
|
|
|
/*
|
|
* 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;
|
|
|
|
/* Store maximum number of records we want from database queries
|
|
* (implemented via SELECT ... LIMIT xx).
|
|
*/
|
|
static int completion_max_records;
|
|
|
|
/*
|
|
* 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* const* completion_charpp; /* to pass a list of strings */
|
|
#ifdef NOT_USED
|
|
static const char* completion_charp; /* to pass a string */
|
|
static const char* completion_info_charp; /* to pass a second string */
|
|
static const char* completion_info_charp2; /* to pass a third string */
|
|
static const SchemaQuery* completion_squery; /* to pass a SchemaQuery */
|
|
#endif
|
|
static bool completion_case_sensitive; /* completion is case sensitive */
|
|
|
|
/*
|
|
* A few macros to ease typing. You can use these to complete the given
|
|
* string with
|
|
* 1) The results from a query you pass it. (Perhaps one of those below?)
|
|
* 2) The results from a schema query you pass it.
|
|
* 3) The items from a null-pointer-terminated list.
|
|
* 4) A string constant.
|
|
* 5) The list of attributes of the given table (possibly schema-qualified).
|
|
*/
|
|
#define COMPLETE_WITH_QUERY(query) \
|
|
do { \
|
|
completion_charp = query; \
|
|
matches = completion_matches(text, complete_from_query); \
|
|
} while (0)
|
|
|
|
#define COMPLETE_WITH_SCHEMA_QUERY(query, addon) \
|
|
do { \
|
|
completion_squery = &(query); \
|
|
completion_charp = addon; \
|
|
matches = completion_matches(text, complete_from_schema_query); \
|
|
} while (0)
|
|
|
|
#define COMPLETE_WITH_LIST_CS(list) \
|
|
do { \
|
|
completion_charpp = list; \
|
|
completion_case_sensitive = true; \
|
|
matches = completion_matches(text, complete_from_list); \
|
|
} while (0)
|
|
|
|
#define COMPLETE_WITH_LIST(list) \
|
|
do { \
|
|
completion_charpp = list; \
|
|
completion_case_sensitive = false; \
|
|
matches = completion_matches(text, complete_from_list); \
|
|
} while (0)
|
|
|
|
#define COMPLETE_WITH_CONST(string) \
|
|
do { \
|
|
completion_charp = string; \
|
|
completion_case_sensitive = false; \
|
|
matches = completion_matches(text, complete_from_const); \
|
|
} while (0)
|
|
|
|
#define COMPLETE_WITH_ATTR(relation, addon) \
|
|
do { \
|
|
char* _completion_schema; \
|
|
char* _completion_table; \
|
|
_completion_schema = strtokx(relation, " \t\n\r", ".", "\"", 0, false, false, pset.encoding); \
|
|
(void)strtokx(NULL, " \t\n\r", ".", "\"", 0, false, false, pset.encoding); \
|
|
_completion_table = strtokx(NULL, " \t\n\r", ".", "\"", 0, false, false, pset.encoding); \
|
|
if (_completion_table == NULL) { \
|
|
completion_charp = Query_for_list_of_attributes addon; \
|
|
completion_info_charp = relation; \
|
|
} else { \
|
|
completion_charp = Query_for_list_of_attributes_with_schema addon; \
|
|
completion_info_charp = _completion_table; \
|
|
completion_info_charp2 = _completion_schema; \
|
|
} \
|
|
matches = completion_matches(text, complete_from_query); \
|
|
} while (0)
|
|
|
|
/*
|
|
* Assembly instructions for schema queries
|
|
*/
|
|
static const SchemaQuery Query_for_list_of_aggregates = {
|
|
/* catname */
|
|
"pg_catalog.pg_proc p",
|
|
/* selcondition */
|
|
"p.prokind",
|
|
/* 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)"};
|
|
|
|
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};
|
|
|
|
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};
|
|
|
|
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};
|
|
|
|
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_foreign_tables = {
|
|
/* catname */
|
|
"pg_catalog.pg_class c",
|
|
/* selcondition */
|
|
"c.relkind IN ('f')",
|
|
/* 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};
|
|
|
|
/* The bit masks for the following three functions come from
|
|
* src/include/catalog/pg_trigger.h.
|
|
*/
|
|
static const SchemaQuery Query_for_list_of_insertables = {
|
|
/* catname */
|
|
"pg_catalog.pg_class c",
|
|
/* selcondition */
|
|
"(c.relkind = 'r' OR (c.relkind = 'v' AND c.relhastriggers AND EXISTS "
|
|
"(SELECT 1 FROM pg_catalog.pg_trigger t WHERE t.tgrelid = c.oid AND t.tgtype & (1 << 2) <> 0)))",
|
|
/* 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_deletables = {
|
|
/* catname */
|
|
"pg_catalog.pg_class c",
|
|
/* selcondition */
|
|
"(c.relkind = 'r' OR (c.relkind = 'v' AND c.relhastriggers AND EXISTS "
|
|
"(SELECT 1 FROM pg_catalog.pg_trigger t WHERE t.tgrelid = c.oid AND t.tgtype & (1 << 3) <> 0)))",
|
|
/* 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_updatables = {
|
|
/* catname */
|
|
"pg_catalog.pg_class c",
|
|
/* selcondition */
|
|
"(c.relkind = 'r' OR (c.relkind = 'v' AND c.relhastriggers AND EXISTS "
|
|
"(SELECT 1 FROM pg_catalog.pg_trigger t WHERE t.tgrelid = c.oid AND t.tgtype & (1 << 4) <> 0)))",
|
|
/* 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_relations = {
|
|
/* catname */
|
|
"pg_catalog.pg_class c",
|
|
/* selcondition */
|
|
NULL,
|
|
/* 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_tsvmf = {
|
|
/* catname */
|
|
"pg_catalog.pg_class c",
|
|
/* selcondition */
|
|
"c.relkind IN ('r', 'S', 'v', 'm', 'f')",
|
|
/* 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_tmf = {
|
|
/* catname */
|
|
"pg_catalog.pg_class c",
|
|
/* selcondition */
|
|
"c.relkind IN ('r', 'm', 'f')",
|
|
/* 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_tm = {
|
|
/* catname */
|
|
"pg_catalog.pg_class c",
|
|
/* selcondition */
|
|
"c.relkind IN ('r', 'm')",
|
|
/* 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};
|
|
|
|
static const SchemaQuery Query_for_list_of_matviews = {
|
|
/* catname */
|
|
"pg_catalog.pg_class c",
|
|
/* selcondition */
|
|
"c.relkind IN ('m')",
|
|
/* viscondition */
|
|
"pg_catalog.pg_table_is_visible(c.oid)",
|
|
/* namespace */
|
|
"c.relnamespace",
|
|
/* result */
|
|
"pg_catalog.quote_ident(c.relname)",
|
|
/* qualresult */
|
|
NULL
|
|
};
|
|
|
|
/*
|
|
* Queries to get lists of names of various kinds of things, possibly
|
|
* restricted to names matching a partially entered name. In these queries,
|
|
* the first %s will be replaced by the text entered so far (suitably escaped
|
|
* to become a SQL literal string). %d will be replaced by the length of the
|
|
* string (in unescaped form). A second and third %s, if present, will be
|
|
* replaced by a suitably-escaped version of the string provided in
|
|
* completion_info_charp. A fourth and fifth %s are similarly replaced by
|
|
* completion_info_charp2.
|
|
*
|
|
* Beware that the allowed sequences of %s and %d are determined by
|
|
* _complete_from_query().
|
|
*/
|
|
#define Query_for_list_of_attributes \
|
|
"SELECT pg_catalog.quote_ident(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 substring(pg_catalog.quote_ident(attname),1,%d)='%s' " \
|
|
" AND (pg_catalog.quote_ident(relname)='%s' " \
|
|
" OR '\"' || relname || '\"'='%s') " \
|
|
" AND pg_catalog.pg_table_is_visible(c.oid)"
|
|
|
|
#define Query_for_list_of_attributes_with_schema \
|
|
"SELECT pg_catalog.quote_ident(attname) " \
|
|
" FROM pg_catalog.pg_attribute a, pg_catalog.pg_class c, pg_catalog.pg_namespace n " \
|
|
" WHERE c.oid = a.attrelid " \
|
|
" AND n.oid = c.relnamespace " \
|
|
" AND a.attnum > 0 " \
|
|
" AND NOT a.attisdropped " \
|
|
" AND substring(pg_catalog.quote_ident(attname),1,%d)='%s' " \
|
|
" AND (pg_catalog.quote_ident(relname)='%s' " \
|
|
" OR '\"' || relname || '\"' ='%s') " \
|
|
" AND (pg_catalog.quote_ident(nspname)='%s' " \
|
|
" OR '\"' || nspname || '\"' ='%s') "
|
|
|
|
#define Query_for_list_of_template_databases \
|
|
"SELECT pg_catalog.quote_ident(datname) FROM pg_catalog.pg_database " \
|
|
" WHERE substring(pg_catalog.quote_ident(datname),1,%d)='%s' AND datistemplate"
|
|
|
|
#define Query_for_list_of_databases \
|
|
"SELECT pg_catalog.quote_ident(datname) FROM pg_catalog.pg_database " \
|
|
" WHERE substring(pg_catalog.quote_ident(datname),1,%d)='%s'"
|
|
|
|
#define Query_for_list_of_tablespaces \
|
|
"SELECT pg_catalog.quote_ident(spcname) FROM pg_catalog.pg_tablespace " \
|
|
" WHERE substring(pg_catalog.quote_ident(spcname),1,%d)='%s'"
|
|
|
|
#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_languages \
|
|
"SELECT pg_catalog.quote_ident(lanname) " \
|
|
" FROM pg_catalog.pg_language " \
|
|
" WHERE lanname != 'internal' " \
|
|
" AND substring(pg_catalog.quote_ident(lanname),1,%d)='%s'"
|
|
|
|
#define Query_for_list_of_schemas \
|
|
"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_set_vars \
|
|
"SELECT name FROM " \
|
|
" (SELECT pg_catalog.lower(name) AS name FROM pg_catalog.pg_settings " \
|
|
" WHERE context IN ('user', 'superuser') " \
|
|
" UNION ALL SELECT 'constraints' " \
|
|
" UNION ALL SELECT 'transaction' " \
|
|
" UNION ALL SELECT 'session' " \
|
|
" UNION ALL SELECT 'role' " \
|
|
" UNION ALL SELECT 'tablespace' " \
|
|
" UNION ALL SELECT 'all') ss " \
|
|
" WHERE substring(name,1,%d)='%s'"
|
|
|
|
#define Query_for_list_of_show_vars \
|
|
"SELECT name FROM " \
|
|
" (SELECT pg_catalog.lower(name) AS name FROM pg_catalog.pg_settings " \
|
|
" UNION ALL SELECT 'session authorization' " \
|
|
" UNION ALL SELECT 'all') ss " \
|
|
" WHERE substring(name,1,%d)='%s'"
|
|
|
|
#define Query_for_list_of_roles \
|
|
" SELECT pg_catalog.quote_ident(rolname) " \
|
|
" FROM pg_catalog.pg_roles " \
|
|
" WHERE substring(pg_catalog.quote_ident(rolname),1,%d)='%s'"
|
|
|
|
#define Query_for_list_of_grant_roles \
|
|
" SELECT pg_catalog.quote_ident(rolname) " \
|
|
" FROM pg_catalog.pg_roles " \
|
|
" WHERE substring(pg_catalog.quote_ident(rolname),1,%d)='%s'" \
|
|
" UNION ALL SELECT 'PUBLIC'"
|
|
|
|
/* the silly-looking length condition is just to eat up the current word */
|
|
#define Query_for_table_owning_index \
|
|
"SELECT pg_catalog.quote_ident(c1.relname) " \
|
|
" FROM pg_catalog.pg_class c1, pg_catalog.pg_class c2, pg_catalog.pg_index i" \
|
|
" WHERE c1.oid=i.indrelid and i.indexrelid=c2.oid" \
|
|
" and (%d = pg_catalog.length('%s'))" \
|
|
" and pg_catalog.quote_ident(c2.relname)='%s'" \
|
|
" and pg_catalog.pg_table_is_visible(c2.oid)"
|
|
|
|
/* the silly-looking length condition is just to eat up the current word */
|
|
#define Query_for_index_of_table \
|
|
"SELECT pg_catalog.quote_ident(c2.relname) " \
|
|
" FROM pg_catalog.pg_class c1, pg_catalog.pg_class c2, pg_catalog.pg_index i" \
|
|
" WHERE c1.oid=i.indrelid and i.indexrelid=c2.oid" \
|
|
" and (%d = pg_catalog.length('%s'))" \
|
|
" and pg_catalog.quote_ident(c1.relname)='%s'" \
|
|
" and pg_catalog.pg_table_is_visible(c2.oid)"
|
|
|
|
/* the silly-looking length condition is just to eat up the current word */
|
|
#define Query_for_list_of_tables_for_trigger \
|
|
"SELECT pg_catalog.quote_ident(relname) " \
|
|
" FROM pg_catalog.pg_class" \
|
|
" WHERE (%d = pg_catalog.length('%s'))" \
|
|
" AND oid IN " \
|
|
" (SELECT tgrelid FROM pg_catalog.pg_trigger " \
|
|
" WHERE pg_catalog.quote_ident(tgname)='%s')"
|
|
|
|
#define Query_for_list_of_ts_configurations \
|
|
"SELECT pg_catalog.quote_ident(cfgname) FROM pg_catalog.pg_ts_config " \
|
|
" WHERE substring(pg_catalog.quote_ident(cfgname),1,%d)='%s'"
|
|
|
|
#define Query_for_list_of_ts_dictionaries \
|
|
"SELECT pg_catalog.quote_ident(dictname) FROM pg_catalog.pg_ts_dict " \
|
|
" WHERE substring(pg_catalog.quote_ident(dictname),1,%d)='%s'"
|
|
|
|
#define Query_for_list_of_ts_parsers \
|
|
"SELECT pg_catalog.quote_ident(prsname) FROM pg_catalog.pg_ts_parser " \
|
|
" WHERE substring(pg_catalog.quote_ident(prsname),1,%d)='%s'"
|
|
|
|
#define Query_for_list_of_ts_templates \
|
|
"SELECT pg_catalog.quote_ident(tmplname) FROM pg_catalog.pg_ts_template " \
|
|
" WHERE substring(pg_catalog.quote_ident(tmplname),1,%d)='%s'"
|
|
|
|
#define Query_for_list_of_fdws \
|
|
" SELECT pg_catalog.quote_ident(fdwname) " \
|
|
" FROM pg_catalog.pg_foreign_data_wrapper " \
|
|
" WHERE substring(pg_catalog.quote_ident(fdwname),1,%d)='%s'"
|
|
|
|
#define Query_for_list_of_servers \
|
|
" SELECT pg_catalog.quote_ident(srvname) " \
|
|
" FROM pg_catalog.pg_foreign_server " \
|
|
" WHERE substring(pg_catalog.quote_ident(srvname),1,%d)='%s'"
|
|
|
|
#define Query_for_list_of_user_mappings \
|
|
" SELECT pg_catalog.quote_ident(usename) " \
|
|
" FROM pg_catalog.pg_user_mappings " \
|
|
" WHERE substring(pg_catalog.quote_ident(usename),1,%d)='%s'"
|
|
|
|
#define Query_for_list_of_access_methods \
|
|
" SELECT pg_catalog.quote_ident(amname) " \
|
|
" FROM pg_catalog.pg_am " \
|
|
" WHERE substring(pg_catalog.quote_ident(amname),1,%d)='%s'"
|
|
|
|
#define Query_for_list_of_arguments \
|
|
" SELECT pg_catalog.oidvectortypes(proargtypes)||')' " \
|
|
" FROM pg_catalog.pg_proc " \
|
|
" WHERE proname='%s'"
|
|
|
|
#define Query_for_list_of_extensions \
|
|
" SELECT pg_catalog.quote_ident(extname) " \
|
|
" FROM pg_catalog.pg_extension " \
|
|
" WHERE substring(pg_catalog.quote_ident(extname),1,%d)='%s'"
|
|
|
|
#define Query_for_list_of_available_extensions \
|
|
" SELECT pg_catalog.quote_ident(name) " \
|
|
" FROM pg_catalog.pg_available_extensions " \
|
|
" WHERE substring(pg_catalog.quote_ident(name),1,%d)='%s' AND installed_version IS NULL"
|
|
|
|
#define Query_for_list_of_prepared_statements \
|
|
" SELECT pg_catalog.quote_ident(name) " \
|
|
" FROM pg_catalog.pg_prepared_statements " \
|
|
" WHERE substring(pg_catalog.quote_ident(name),1,%d)='%s'"
|
|
|
|
#ifdef PGXC
|
|
#define Query_for_list_of_available_nodenames \
|
|
" SELECT NODE_NAME " \
|
|
" FROM PGXC_NODE"
|
|
#define Query_for_list_of_available_coordinators \
|
|
" SELECT NODE_NAME " \
|
|
" FROM PGXC_NODE" \
|
|
" WHERE NODE_TYPE = 'C'"
|
|
#define Query_for_list_of_available_datanodes \
|
|
" SELECT NODE_NAME " \
|
|
" FROM PGXC_NODE" \
|
|
" WHERE NODE_TYPE = 'D'"
|
|
#define Query_for_list_of_available_nodegroup_names \
|
|
" SELECT GROUP_NAME " \
|
|
" FROM PGXC_GROUP"
|
|
#endif
|
|
|
|
/*
|
|
* 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 {
|
|
const char* name;
|
|
const char* query; /* simple query, or NULL */
|
|
const SchemaQuery* squery; /* schema query, or NULL */
|
|
const bits32 flags; /* visibility flags, see below */
|
|
} pgsql_thing_t;
|
|
|
|
#define THING_NO_CREATE (1 << 0) /* should not show up after CREATE */
|
|
#define THING_NO_DROP (1 << 1) /* should not show up after DROP */
|
|
#define THING_NO_SHOW (THING_NO_CREATE | THING_NO_DROP)
|
|
|
|
static const pgsql_thing_t words_after_create[] = {
|
|
{"AGGREGATE", NULL, &Query_for_list_of_aggregates, 0},
|
|
#ifdef PGXC
|
|
{"BARRIER", NULL, NULL, 0}, /* Comes barrier name next, so skip it */
|
|
#endif
|
|
{"CAST", NULL, NULL, 0}, /* Casts have complex structures for names, so
|
|
* skip it */
|
|
{"COLLATION",
|
|
"SELECT pg_catalog.quote_ident(collname) FROM pg_catalog.pg_collation WHERE collencoding IN (-1, "
|
|
"pg_catalog.pg_char_to_encoding(pg_catalog.getdatabaseencoding())) AND "
|
|
"substring(pg_catalog.quote_ident(collname),1,%d)='%s'",
|
|
NULL,
|
|
0},
|
|
|
|
/*
|
|
* CREATE CONSTRAINT TRIGGER is not supported here because it is designed
|
|
* to be used only by pg_dump.
|
|
*/
|
|
{"CONFIGURATION", Query_for_list_of_ts_configurations, NULL, THING_NO_SHOW},
|
|
{"CONVERSION",
|
|
"SELECT pg_catalog.quote_ident(conname) FROM pg_catalog.pg_conversion WHERE "
|
|
"substring(pg_catalog.quote_ident(conname),1,%d)='%s'",
|
|
NULL,
|
|
0},
|
|
{"DATABASE", Query_for_list_of_databases, NULL, 0},
|
|
{"DICTIONARY", Query_for_list_of_ts_dictionaries, NULL, THING_NO_SHOW},
|
|
{"DOMAIN", NULL, &Query_for_list_of_domains, 0},
|
|
{"EXTENSION", Query_for_list_of_extensions, NULL, 0},
|
|
#ifndef PGXC
|
|
{"FOREIGN DATA WRAPPER", NULL, NULL, 0},
|
|
{"FOREIGN TABLE", NULL, NULL, 0},
|
|
#endif
|
|
{"FUNCTION", NULL, &Query_for_list_of_functions, 0},
|
|
{"GROUP", Query_for_list_of_roles, NULL, 0},
|
|
{"LANGUAGE", Query_for_list_of_languages, NULL, 0},
|
|
{"INDEX", NULL, &Query_for_list_of_indexes, 0},
|
|
{"MATERIALIZED VIEW", NULL, NULL},
|
|
#ifdef PGXC
|
|
{"NODE", Query_for_list_of_available_nodenames, NULL, 0},
|
|
{"NODE GROUP", Query_for_list_of_available_nodegroup_names, NULL, 0},
|
|
#endif
|
|
{"OPERATOR", NULL, NULL, 0}, /* Querying for this is probably not such a
|
|
* good idea. */
|
|
{"OWNED", NULL, NULL, THING_NO_CREATE}, /* for DROP OWNED BY ... */
|
|
{"PARSER", Query_for_list_of_ts_parsers, NULL, THING_NO_SHOW},
|
|
{"ROLE", Query_for_list_of_roles, NULL, 0},
|
|
{"RULE",
|
|
"SELECT pg_catalog.quote_ident(rulename) FROM pg_catalog.pg_rules WHERE "
|
|
"substring(pg_catalog.quote_ident(rulename),1,%d)='%s'",
|
|
NULL,
|
|
0},
|
|
{"SCHEMA", Query_for_list_of_schemas, NULL, 0},
|
|
{"SEQUENCE", NULL, &Query_for_list_of_sequences, 0},
|
|
#ifndef PGXC
|
|
{"SERVER", Query_for_list_of_servers, NULL, 0},
|
|
#endif
|
|
{"TABLE", NULL, &Query_for_list_of_tables, 0},
|
|
{"TABLESPACE", Query_for_list_of_tablespaces, NULL, 0},
|
|
{"TEMP", NULL, NULL, THING_NO_DROP}, /* for CREATE TEMP TABLE ... */
|
|
{"TEMPLATE", Query_for_list_of_ts_templates, NULL, THING_NO_SHOW},
|
|
{"TEXT SEARCH", NULL, NULL, 0},
|
|
{"TRIGGER",
|
|
"SELECT pg_catalog.quote_ident(tgname) FROM pg_catalog.pg_trigger WHERE "
|
|
"substring(pg_catalog.quote_ident(tgname),1,%d)='%s'",
|
|
NULL,
|
|
0},
|
|
{"TYPE", NULL, &Query_for_list_of_datatypes, 0},
|
|
{"UNIQUE", NULL, NULL, THING_NO_DROP}, /* for CREATE UNIQUE INDEX ... */
|
|
{"UNLOGGED", NULL, NULL, THING_NO_DROP}, /* for CREATE UNLOGGED TABLE
|
|
* ... */
|
|
{"USER", Query_for_list_of_roles, NULL, 0},
|
|
#ifndef PGXC
|
|
{"USER MAPPING FOR", NULL, NULL, 0},
|
|
#endif
|
|
{"VIEW", NULL, &Query_for_list_of_views, 0},
|
|
{NULL, NULL, NULL, 0} /* end of list */
|
|
};
|
|
|
|
#ifdef NOT_USED
|
|
/* Forward declaration of functions */
|
|
/*psql_completion is deleted because it's too complex and not be used at all. */
|
|
static char* create_command_generator(const char* text, int state);
|
|
static char* drop_command_generator(const char* text, int state);
|
|
static char* complete_from_query(const char* text, int state);
|
|
static char* complete_from_schema_query(const char* text, int state);
|
|
static char* _complete_from_query(int is_schema_query, const char* text, int state);
|
|
static char* complete_from_list(const char* text, int state);
|
|
static char* complete_from_const(const char* text, int state);
|
|
static char** complete_from_variables(char* text, const char* prefix, const char* suffix);
|
|
static char* complete_from_files(const char* text, int state);
|
|
#endif
|
|
|
|
static char* pg_strdup_keyword_case(const char* s, const char* ref);
|
|
|
|
#ifdef NOT_USED
|
|
static PGresult* exec_query(const char* query);
|
|
static void get_previous_words(int point, char** previous_words, int nwords);
|
|
|
|
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 = (char*)pset.progname;
|
|
|
|
/* psql_completion is deleted because it's too complex and not be used at all. */
|
|
rl_attempted_completion_function = NULL;
|
|
|
|
rl_basic_word_break_characters = WORD_BREAKS;
|
|
|
|
completion_max_records = 1000;
|
|
|
|
/*
|
|
* There is a variable rl_completion_query_items for this but apparently
|
|
* it's not defined everywhere.
|
|
*/
|
|
}
|
|
|
|
#ifdef NOT_USED
|
|
/*
|
|
* GENERATOR FUNCTIONS
|
|
*
|
|
* These functions do all the actual work of completing the input. They get
|
|
* passed the text so far and the count how many times they have been called
|
|
* so far with the same text.
|
|
* If you read the above carefully, you'll see that these don't get called
|
|
* directly but through the readline interface.
|
|
* The return value is expected to be the full completion of the text, going
|
|
* through a list each time, or NULL if there are no more matches. The string
|
|
* will be free()'d by readline, so you must run it through strdup() or
|
|
* something of that sort.
|
|
*/
|
|
/*
|
|
* Common routine for create_command_generator and drop_command_generator.
|
|
* Entries that have 'excluded' flags are not returned.
|
|
*/
|
|
static char* create_or_drop_command_generator(const char* text, int state, bits32 excluded)
|
|
{
|
|
static int list_index, string_length;
|
|
const char* name = NULL;
|
|
|
|
/* If this is the first time for this completion, init some values */
|
|
if (state == 0) {
|
|
list_index = 0;
|
|
string_length = strlen(text);
|
|
}
|
|
|
|
/* find something that matches */
|
|
while ((name = words_after_create[list_index++].name)) {
|
|
if ((pg_strncasecmp(name, text, string_length) == 0) && !(words_after_create[list_index - 1].flags & excluded))
|
|
return pg_strdup_keyword_case(name, text);
|
|
}
|
|
/* if nothing matches, return NULL */
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* This one gives you one from a list of things you can put after CREATE
|
|
* as defined above.
|
|
*/
|
|
static char* create_command_generator(const char* text, int state)
|
|
{
|
|
return create_or_drop_command_generator(text, state, THING_NO_CREATE);
|
|
}
|
|
|
|
/*
|
|
* This function gives you a list of things you can put after a DROP command.
|
|
*/
|
|
static char* drop_command_generator(const char* text, int state)
|
|
{
|
|
return create_or_drop_command_generator(text, state, THING_NO_DROP);
|
|
}
|
|
|
|
/* The following two functions are wrappers for _complete_from_query */
|
|
static char* complete_from_query(const char* text, int state)
|
|
{
|
|
return _complete_from_query(0, text, state);
|
|
}
|
|
|
|
static char* complete_from_schema_query(const char* text, int state)
|
|
{
|
|
return _complete_from_query(1, text, state);
|
|
}
|
|
|
|
/*
|
|
* This creates a list of matching things, according to a query pointed to
|
|
* by completion_charp.
|
|
* The query can be one of two kinds:
|
|
*
|
|
* 1. A simple query which must contain a %d and a %s, which will be replaced
|
|
* by the string length of the text and the text itself. The query may also
|
|
* have up to four more %s in it; the first two such will be replaced by the
|
|
* value of completion_info_charp, the next two by the value of
|
|
* completion_info_charp2.
|
|
*
|
|
* 2. 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.
|
|
*
|
|
* It is assumed that strings should be escaped to become SQL literals
|
|
* (that is, what is in the query is actually ... '%s' ...)
|
|
*
|
|
* See top of file for examples of both kinds of query.
|
|
*/
|
|
static char* _complete_from_query(int is_schema_query, const char* text, int state)
|
|
{
|
|
static int list_index, string_length;
|
|
static PGresult* result = NULL;
|
|
|
|
/*
|
|
* If this is the first time for this completion, we fetch a list of our
|
|
* "things" from the backend.
|
|
*/
|
|
if (state == 0) {
|
|
PQExpBufferData query_buffer;
|
|
char* e_text = NULL;
|
|
char* e_info_charp = NULL;
|
|
char* e_info_charp2 = NULL;
|
|
|
|
list_index = 0;
|
|
string_length = strlen(text);
|
|
|
|
/* Free any prior result */
|
|
PQclear(result);
|
|
result = NULL;
|
|
|
|
/* Set up suitably-escaped copies of textual inputs */
|
|
e_text = (char*)pg_malloc(string_length * 2 + 1);
|
|
PQescapeString(e_text, text, string_length);
|
|
|
|
if (NULL != completion_info_charp) {
|
|
size_t charp_len;
|
|
|
|
charp_len = strlen(completion_info_charp);
|
|
e_info_charp = (char*)pg_malloc(charp_len * 2 + 1);
|
|
PQescapeString(e_info_charp, completion_info_charp, charp_len);
|
|
} else
|
|
e_info_charp = NULL;
|
|
|
|
if (NULL != completion_info_charp2) {
|
|
size_t charp_len;
|
|
|
|
charp_len = strlen(completion_info_charp2);
|
|
e_info_charp2 = (char*)pg_malloc(charp_len * 2 + 1);
|
|
PQescapeString(e_info_charp2, completion_info_charp2, charp_len);
|
|
} else
|
|
e_info_charp2 = NULL;
|
|
|
|
initPQExpBuffer(&query_buffer);
|
|
|
|
if (is_schema_query) {
|
|
/* 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 (NULL != completion_squery->selcondition)
|
|
appendPQExpBuffer(&query_buffer, "%s AND ", completion_squery->selcondition);
|
|
appendPQExpBuffer(
|
|
&query_buffer, "substring(%s,1,%d)='%s'", completion_squery->result, string_length, e_text);
|
|
appendPQExpBuffer(&query_buffer, " AND %s", completion_squery->viscondition);
|
|
|
|
/*
|
|
* When fetching relation names, suppress system catalogs unless
|
|
* the input-so-far begins with "pg_". This is a compromise
|
|
* between not offering system catalogs for completion at all, and
|
|
* having them swamp the result when the input is just "p".
|
|
*/
|
|
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 != NULL)
|
|
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 (NULL != completion_charp)
|
|
appendPQExpBuffer(&query_buffer, "\n%s", completion_charp);
|
|
} else {
|
|
/* completion_charp is an sprintf-style format string */
|
|
appendPQExpBuffer(&query_buffer,
|
|
completion_charp,
|
|
string_length,
|
|
e_text,
|
|
e_info_charp,
|
|
e_info_charp,
|
|
e_info_charp2,
|
|
e_info_charp2);
|
|
}
|
|
|
|
/* Limit the number of records in the result */
|
|
appendPQExpBuffer(&query_buffer, "\nLIMIT %d", completion_max_records);
|
|
|
|
result = exec_query(query_buffer.data);
|
|
|
|
termPQExpBuffer(&query_buffer);
|
|
free(e_text);
|
|
e_text = NULL;
|
|
if (e_info_charp != NULL) {
|
|
free(e_info_charp);
|
|
e_info_charp = NULL;
|
|
}
|
|
if (e_info_charp2 != NULL) {
|
|
free(e_info_charp2);
|
|
e_info_charp2 = NULL;
|
|
}
|
|
}
|
|
|
|
/* Find something that matches */
|
|
if (result && PQresultStatus(result) == PGRES_TUPLES_OK) {
|
|
const char* item = NULL;
|
|
|
|
while (list_index < PQntuples(result) && (item = PQgetvalue(result, list_index++, 0)))
|
|
if (pg_strncasecmp(text, item, string_length) == 0)
|
|
return pg_strdup(item);
|
|
}
|
|
|
|
/* If nothing matches, free the db structure and return null */
|
|
PQclear(result);
|
|
result = NULL;
|
|
return NULL;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* This function returns in order one of a fixed, NULL pointer terminated list
|
|
* of strings (if matching). This can be used if there are only a fixed number
|
|
* SQL words that can appear at certain spot.
|
|
*/
|
|
static char* complete_from_list(const char* text, int state)
|
|
{
|
|
static int string_length, list_index, matches;
|
|
static bool casesensitive = false;
|
|
const char* item = NULL;
|
|
|
|
/* need to have a list */
|
|
psql_assert(completion_charpp);
|
|
|
|
/* Initialization */
|
|
if (state == 0) {
|
|
list_index = 0;
|
|
string_length = strlen(text);
|
|
casesensitive = completion_case_sensitive;
|
|
matches = 0;
|
|
}
|
|
|
|
while ((item = completion_charpp[list_index++])) {
|
|
/* First pass is case sensitive */
|
|
if (casesensitive && strncmp(text, item, string_length) == 0) {
|
|
matches++;
|
|
return pg_strdup(item);
|
|
}
|
|
|
|
/* Second pass is case insensitive, don't bother counting matches */
|
|
if (!casesensitive && pg_strncasecmp(text, item, string_length) == 0) {
|
|
if (completion_case_sensitive)
|
|
return pg_strdup(item);
|
|
else
|
|
|
|
/*
|
|
* If case insensitive matching was requested initially,
|
|
* adjust the case according to setting.
|
|
*/
|
|
return pg_strdup_keyword_case(item, text);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* No matches found. If we're not case insensitive already, lets switch to
|
|
* being case insensitive and try again
|
|
*/
|
|
if (casesensitive && matches == 0) {
|
|
casesensitive = false;
|
|
list_index = 0;
|
|
state++;
|
|
return complete_from_list(text, state);
|
|
}
|
|
|
|
/* If no more matches, return null. */
|
|
return NULL;
|
|
}
|
|
|
|
#ifdef NOT_USED
|
|
/*
|
|
* This function returns one fixed string the first time even if it doesn't
|
|
* match what's there, and nothing the second time. This should be used if
|
|
* there is only one possibility that can appear at a certain spot, so
|
|
* misspellings will be overwritten. The string to be passed must be in
|
|
* completion_charp.
|
|
*/
|
|
static char* complete_from_const(const char* text, int state)
|
|
{
|
|
psql_assert(completion_charp);
|
|
if (state == 0) {
|
|
if (completion_case_sensitive)
|
|
return pg_strdup(completion_charp);
|
|
else
|
|
|
|
/*
|
|
* If case insensitive matching was requested initially, adjust
|
|
* the case according to setting.
|
|
*/
|
|
return pg_strdup_keyword_case(completion_charp, text);
|
|
} else
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* This function supports completion with the name of a psql variable.
|
|
* The variable names can be prefixed and suffixed with additional text
|
|
* to support quoting usages.
|
|
*/
|
|
static char** complete_from_variables(char* text, const char* prefix, const char* suffix)
|
|
{
|
|
char** matches = NULL;
|
|
int overhead = strlen(prefix) + strlen(suffix) + 1;
|
|
char** varnames = NULL;
|
|
int nvars = 0;
|
|
int maxvars = 100;
|
|
int i;
|
|
struct _variable* ptr = NULL;
|
|
int rc;
|
|
size_t sz = 0;
|
|
|
|
varnames = (char**)pg_malloc((maxvars + 1) * sizeof(char*));
|
|
|
|
ptr = (pset.vars != NULL) ? pset.vars->next : NULL;
|
|
for (;ptr != NULL; ptr = ptr->next) {
|
|
char* buffer = NULL;
|
|
|
|
if (nvars >= maxvars) {
|
|
maxvars *= 2;
|
|
varnames = (char**)pg_realloc(varnames, (maxvars + 1) * sizeof(char*));
|
|
if (varnames == NULL) {
|
|
psql_error("out of memory\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
}
|
|
|
|
sz = strlen(ptr->name) + overhead;
|
|
buffer = (char*)pg_malloc(sz);
|
|
rc = sprintf_s(buffer, sz, "%s%s%s", prefix, ptr->name, suffix);
|
|
check_sprintf_s(rc);
|
|
varnames[nvars++] = buffer;
|
|
}
|
|
|
|
varnames[nvars] = NULL;
|
|
COMPLETE_WITH_LIST_CS((const char* const*)varnames);
|
|
|
|
for (i = 0; i < nvars; i++) {
|
|
free(varnames[i]);
|
|
varnames[i] = NULL;
|
|
}
|
|
free(varnames);
|
|
varnames = NULL;
|
|
|
|
return matches;
|
|
}
|
|
|
|
/*
|
|
* This function wraps rl_filename_completion_function() to strip quotes from
|
|
* the input before searching for matches and to quote any matches for which
|
|
* the consuming command will require it.
|
|
*/
|
|
static char* complete_from_files(const char* text, int state)
|
|
{
|
|
static const char* unquoted_text = NULL;
|
|
char* unquoted_match = NULL;
|
|
char* ret = NULL;
|
|
|
|
if (state == 0) {
|
|
/* Initialization: stash the unquoted input. */
|
|
unquoted_text = strtokx(text, "", NULL, "'", *completion_charp, false, true, pset.encoding);
|
|
/* expect a NULL return for the empty string only */
|
|
if (NULL == unquoted_text) {
|
|
psql_assert(!*text);
|
|
unquoted_text = text;
|
|
}
|
|
}
|
|
|
|
unquoted_match = filename_completion_function(unquoted_text, state);
|
|
if (unquoted_match != NULL) {
|
|
/*
|
|
* Caller sets completion_charp to a zero- or one-character string
|
|
* containing the escape character. This is necessary since \copy has
|
|
* no escape character, but every other backslash command recognizes
|
|
* "\" as an escape character. Since we have only two callers, don't
|
|
* bother providing a macro to simplify this.
|
|
*/
|
|
ret = quote_if_needed(unquoted_match, " \t\r\n\"`", '\'', *completion_charp, pset.encoding);
|
|
if (ret != NULL) {
|
|
free(unquoted_match);
|
|
unquoted_match = NULL;
|
|
} else
|
|
ret = unquoted_match;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
/* HELPER FUNCTIONS */
|
|
/*
|
|
* Make a pg_strdup copy of s and convert the case according to
|
|
* COMP_KEYWORD_CASE variable, using ref as the text that was already entered.
|
|
*/
|
|
static char* pg_strdup_keyword_case(const char* s, const char* ref)
|
|
{
|
|
char *ret = NULL;
|
|
char *p = NULL;
|
|
unsigned char first = ref[0];
|
|
int tocase;
|
|
const char* varval = NULL;
|
|
|
|
varval = GetVariable(pset.vars, "COMP_KEYWORD_CASE");
|
|
if (varval == NULL)
|
|
tocase = 0;
|
|
else if (strcmp(varval, "lower") == 0)
|
|
tocase = -2;
|
|
else if (strcmp(varval, "preserve-lower") == 0)
|
|
tocase = -1;
|
|
else if (strcmp(varval, "preserve-upper") == 0)
|
|
tocase = +1;
|
|
else if (strcmp(varval, "upper") == 0)
|
|
tocase = +2;
|
|
else
|
|
tocase = 0;
|
|
|
|
/* default */
|
|
if (tocase == 0)
|
|
tocase = +1;
|
|
|
|
ret = pg_strdup(s);
|
|
|
|
if (tocase == -2 || ((tocase == -1 || tocase == +1) && islower(first)) || (tocase == -1 && !isalpha(first)))
|
|
for (p = ret; *p; p++)
|
|
*p = pg_tolower((unsigned char)*p);
|
|
else
|
|
for (p = ret; *p; p++)
|
|
*p = pg_toupper((unsigned char)*p);
|
|
|
|
return ret;
|
|
}
|
|
|
|
#ifdef NOT_USED
|
|
/*
|
|
* 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(const char* query)
|
|
{
|
|
PGresult* result = NULL;
|
|
|
|
if (query == NULL || pset.db == NULL || PQstatus(pset.db) != CONNECTION_OK)
|
|
return NULL;
|
|
|
|
result = PQexec(pset.db, query);
|
|
if (PQresultStatus(result) != PGRES_TUPLES_OK) {
|
|
#ifdef NOT_USED
|
|
psql_error("tab completion query failed: %s\nQuery was:\n%s\n", PQerrorMessage(pset.db), query);
|
|
#endif
|
|
PQclear(result);
|
|
result = NULL;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* Return the nwords word(s) before point. Words are returned right to left,
|
|
* that is, previous_words[0] gets the last word before point.
|
|
* If we run out of words, remaining array elements are set to empty strings.
|
|
* Each array element is filled with a malloc'd string.
|
|
*/
|
|
static void get_previous_words(int point, char** previous_words, int nwords)
|
|
{
|
|
const char* buf = rl_line_buffer; /* alias */
|
|
int i;
|
|
errno_t rc = EOK;
|
|
|
|
/* first we look for a non-word char before the current point */
|
|
for (i = point - 1; i >= 0; i--)
|
|
if (strchr(WORD_BREAKS, buf[i]))
|
|
break;
|
|
point = i;
|
|
|
|
while (nwords-- > 0) {
|
|
int start, end;
|
|
char* s = NULL;
|
|
|
|
/* now find the first non-space which then constitutes the end */
|
|
end = -1;
|
|
for (i = point; i >= 0; i--) {
|
|
if (!isspace((unsigned char)buf[i])) {
|
|
end = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* If no end found we return an empty string, because there is no word
|
|
* before the point
|
|
*/
|
|
if (end < 0) {
|
|
point = end;
|
|
s = pg_strdup("");
|
|
} else {
|
|
/*
|
|
* Otherwise we now look for the start. The start is either the
|
|
* last character before any word-break character going backwards
|
|
* from the end, or it's simply character 0. We also handle open
|
|
* quotes and parentheses.
|
|
*/
|
|
bool inquotes = false;
|
|
int parentheses = 0;
|
|
|
|
for (start = end; start > 0; start--) {
|
|
if (buf[start] == '"')
|
|
inquotes = !inquotes;
|
|
if (!inquotes) {
|
|
if (buf[start] == ')')
|
|
parentheses++;
|
|
else if (buf[start] == '(') {
|
|
if (--parentheses <= 0)
|
|
break;
|
|
} else if (parentheses == 0 && strchr(WORD_BREAKS, buf[start - 1]))
|
|
break;
|
|
}
|
|
}
|
|
|
|
point = start - 1;
|
|
|
|
/* make a copy of chars from start to end inclusive */
|
|
s = (char*)pg_malloc(end - start + 2);
|
|
rc = strncpy_s(s, end - start + 2, &buf[start], end - start + 2);
|
|
securec_check_c(rc, "\0", "\0");
|
|
}
|
|
|
|
*previous_words++ = s;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifdef NOT_USED
|
|
|
|
/*
|
|
* Surround a string with single quotes. This works for both SQL and
|
|
* psql internal. Currently disabled because it is reported not to
|
|
* cooperate with certain versions of readline.
|
|
*/
|
|
static char* quote_file_name(char* text, int match_type, char* quote_pointer)
|
|
{
|
|
char* s = NULL;
|
|
size_t length;
|
|
int rc;
|
|
|
|
(void)quote_pointer; /* not used */
|
|
|
|
length = strlen(text) + (match_type == SINGLE_MATCH ? 3 : 2);
|
|
s = pg_malloc(length);
|
|
s[0] = '\'';
|
|
rc = strcpy_s(s + 1, length - 1, text);
|
|
check_strcpy_s(rc);
|
|
if (match_type == SINGLE_MATCH)
|
|
s[length - 2] = '\'';
|
|
s[length - 1] = '\0';
|
|
return s;
|
|
}
|
|
|
|
static char* dequote_file_name(char* text, char quote_char)
|
|
{
|
|
char* s = NULL;
|
|
size_t length;
|
|
errno_t rc = EOK;
|
|
|
|
if (!quote_char)
|
|
return pg_strdup(text);
|
|
|
|
length = strlen(text);
|
|
s = pg_malloc(length - 2 + 1);
|
|
rc = strncpy_s(s, length - 2 + 1, text + 1, length - 2 + 1);
|
|
securec_check_c(rc, "\0", "\0");
|
|
|
|
return s;
|
|
}
|
|
#endif /* NOT_USED */
|
|
|
|
#endif /* USE_READLINE */
|