diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 1ab200a4adc..caabb06c537 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -318,9 +318,9 @@ EOF - Do not use Readline for line editing and do - not use the command history. - This can be useful to turn off tab expansion when cutting and pasting. + Do not use Readline for line editing and + do not use the command history (see + below). @@ -4562,21 +4562,47 @@ testdb=> \set PROMPT1 '%[%033[1;33;40m%]%n@%/%R%[%033[0m%]%# ' - + Command-Line Editing + + Readline + in psql + + + libedit + in psql + + - psql supports the Readline - library for convenient line editing and retrieval. The command - history is automatically saved when psql - exits and is reloaded when - psql starts up. Tab-completion is also - supported, although the completion logic makes no claim to be an - SQL parser. The queries generated by tab-completion - can also interfere with other SQL commands, e.g., SET - TRANSACTION ISOLATION LEVEL. - If for some reason you do not like the tab completion, you - can turn it off by putting this in a file named + psql uses + the Readline + or libedit library, if available, for + convenient line editing and retrieval. The command history is + automatically saved when psql exits and is + reloaded when psql starts up. Type + up-arrow or control-P to retrieve previous lines. + + + + You can also use tab completion to fill in partially-typed keywords + and SQL object names in many (by no means all) contexts. For example, + at the start of a command, typing ins and pressing + TAB will fill in insert into . Then, typing a few + characters of a table or schema name and pressing TAB will fill in the + unfinished name, or offer a menu of possible completions when there's + more than one. (Depending on the library in use, you may need to + press TAB more than once to get a menu.) + + + + Tab completion for SQL object names requires sending queries to the + server to find possible matches. In some contexts this can interfere + with other operations. For example, after BEGIN + it will be too late to issue SET TRANSACTION ISOLATION + LEVEL if a tab-completion query is issued in between. + If you do not want tab completion at all, you + can turn it off permanently by putting this in a file named .inputrc in your home directory: $if psql @@ -4587,6 +4613,16 @@ $endif Readline feature. Read its documentation for further details.) + + + The () command line + option can also be useful to disable use + of Readline for a single run + of psql. This prevents tab completion, + use or recording of command line history, and editing of multi-line + commands. It is particularly useful when you need to copy-and-paste + text that contains TAB characters. + diff --git a/src/bin/psql/t/010_tab_completion.pl b/src/bin/psql/t/010_tab_completion.pl index d3d1bd650e7..c4f6552ac97 100644 --- a/src/bin/psql/t/010_tab_completion.pl +++ b/src/bin/psql/t/010_tab_completion.pl @@ -40,10 +40,11 @@ $node->start; # set up a few database objects $node->safe_psql('postgres', - "CREATE TABLE tab1 (f1 int, f2 text);\n" + "CREATE TABLE tab1 (f1 int primary key, f2 text);\n" . "CREATE TABLE mytab123 (f1 int, f2 text);\n" . "CREATE TABLE mytab246 (f1 int, f2 text);\n" - . "CREATE TYPE enum1 AS ENUM ('foo', 'bar', 'baz');\n"); + . "CREATE TABLE \"mixedName\" (f1 int, f2 text);\n" + . "CREATE TYPE enum1 AS ENUM ('foo', 'bar', 'baz', 'BLACK');\n"); # Developers would not appreciate this test adding a bunch of junk to # their ~/.psql_history, so be sure to redirect history into a temp file. @@ -176,6 +177,38 @@ check_completion("2\t", qr/246 /, clear_query(); +# check handling of quoted names +check_completion( + "select * from \"my\t", + qr/select \* from "my\a?tab/, + "complete \"my to \"mytab when there are multiple choices"); + +check_completion( + "\t\t", + qr/"mytab123" +"mytab246"/, + "offer multiple quoted table choices"); + +check_completion("2\t", qr/246" /, + "finish completion of one of multiple quoted table choices"); + +clear_query(); + +# check handling of mixed-case names +check_completion( + "select * from \"mi\t", + qr/"mixedName"/, + "complete a mixed-case name"); + +clear_query(); + +# check case folding +check_completion( + "select * from TAB\t", + qr/tab1 /, + "automatically fold case"); + +clear_query(); + # check case-sensitive keyword replacement # note: various versions of readline/libedit handle backspacing # differently, so just check that the replacement comes out correctly @@ -183,6 +216,48 @@ check_completion("\\DRD\t", qr/drds /, "complete \\DRD to \\drds"); clear_query(); +# check completion of a schema-qualified name +check_completion( + "select * from pub\t", + qr/public\./, + "complete schema when relevant"); + +check_completion( + "tab\t", + qr/tab1 /, + "complete schema-qualified name"); + +clear_query(); + +check_completion( + "select * from PUBLIC.t\t", + qr/public\.tab1 /, + "automatically fold case in schema-qualified name"); + +clear_query(); + +# check interpretation of referenced names +check_completion( + "alter table tab1 drop constraint \t", + qr/tab1_pkey /, + "complete index name for referenced table"); + +clear_query(); + +check_completion( + "alter table TAB1 drop constraint \t", + qr/tab1_pkey /, + "complete index name for referenced table, with downcasing"); + +clear_query(); + +check_completion( + "alter table public.\"tab1\" drop constraint \t", + qr/tab1_pkey /, + "complete index name for referenced table, with schema and quoting"); + +clear_query(); + # check filename completion check_completion( "\\lo_import tmp_check/some\t", @@ -234,6 +309,23 @@ check_completion( clear_line(); +# enum labels are case sensitive, so this should complete BLACK immediately +check_completion( + "ALTER TYPE enum1 RENAME VALUE 'B\t", + qr|BLACK|, + "enum labels are case sensitive"); + +clear_line(); + +# check completion of a keyword offered in addition to object names +# (that code path currently doesn't preserve case of what's typed) +check_completion( + "comment on constraint foo on dom\t", + qr|DOMAIN|, + "offer keyword in addition to query result"); + +clear_query(); + # send psql an explicit \q to shut it down, else pty won't close properly $timer->start(5); $in .= "\\q\n"; diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c index e09221d63db..b2ec50b4f27 100644 --- a/src/bin/psql/tab-complete.c +++ b/src/bin/psql/tab-complete.c @@ -46,7 +46,9 @@ #include "catalog/pg_am_d.h" #include "catalog/pg_class_d.h" #include "common.h" +#include "common/keywords.h" #include "libpq-fe.h" +#include "mb/pg_wchar.h" #include "pqexpbuffer.h" #include "settings.h" #include "stringutils.h" @@ -108,6 +110,12 @@ typedef struct VersionedQuery * time. So we put the components of each query into this struct and * assemble them with the common boilerplate in _complete_from_query(). * + * We also use this struct to define queries that use completion_ref_object, + * which is some object related to the one(s) we want to get the names of + * (for example, the table we want the indexes of). In that usage the + * objects we're completing might not have a schema of their own, but the + * reference object almost always does (passed in completion_ref_schema). + * * As with VersionedQuery, we can use an array of these if the query details * must vary across versions. */ @@ -121,8 +129,9 @@ typedef struct SchemaQuery int min_server_version; /* - * Name of catalog or catalogs to be queried, with alias, eg. - * "pg_catalog.pg_class c". Note that "pg_namespace n" will be added. + * Name of catalog or catalogs to be queried, with alias(es), eg. + * "pg_catalog.pg_class c". Note that "pg_namespace n" and/or + * "pg_namespace nr" will be added automatically when needed. */ const char *catname; @@ -138,26 +147,60 @@ typedef struct SchemaQuery /* * Visibility condition --- which rows are visible without schema * qualification? For example, "pg_catalog.pg_table_is_visible(c.oid)". + * NULL if not needed. */ const char *viscondition; /* - * Namespace --- name of field to join to pg_namespace.oid. For example, - * "c.relnamespace". + * Namespace --- name of field to join to pg_namespace.oid when there is + * schema qualification. For example, "c.relnamespace". NULL if we don't + * want to join to pg_namespace (then any schema part in the input word + * will be ignored). */ 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)". + * Result --- the base object name to return. For example, "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. + * In some cases, it's difficult to keep the query from returning the same + * object multiple times. Specify use_distinct to filter out duplicates. */ - const char *qualresult; + bool use_distinct; + + /* + * Additional literal strings (usually keywords) to be offered along with + * the query results. Provide a NULL-terminated array of constant + * strings, or NULL if none. + */ + const char *const *keywords; + + /* + * If this query uses completion_ref_object/completion_ref_schema, + * populate the remaining fields, else leave them NULL. When using this + * capability, catname must include the catalog that defines the + * completion_ref_object, and selcondition must include the join condition + * that connects it to the result's catalog. + * + * refname is the field that should be equated to completion_ref_object, + * for example "cr.relname". + */ + const char *refname; + + /* + * Visibility condition to use when completion_ref_schema is not set. For + * example, "pg_catalog.pg_table_is_visible(cr.oid)". NULL if not needed. + */ + const char *refviscondition; + + /* + * Name of field to join to pg_namespace.oid when completion_ref_schema is + * set. For example, "cr.relnamespace". NULL if we don't want to + * consider completion_ref_schema. + */ + const char *refnamespace; } SchemaQuery; @@ -174,11 +217,12 @@ static int completion_max_records; static char completion_last_char; /* last char of input word */ 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 char *completion_info_charp2; /* to pass a third string */ static const VersionedQuery *completion_vquery; /* to pass a VersionedQuery */ static const SchemaQuery *completion_squery; /* to pass a SchemaQuery */ +static char *completion_ref_object; /* name of reference object */ +static char *completion_ref_schema; /* schema name of reference object */ static bool completion_case_sensitive; /* completion is case sensitive */ +static bool completion_verbatim; /* completion is verbatim */ static bool completion_force_quote; /* true to force-quote filenames */ /* @@ -190,36 +234,95 @@ static bool completion_force_quote; /* true to force-quote filenames */ * We support both simple and versioned schema queries. * 3) The items from a null-pointer-terminated list (with or without * case-sensitive comparison); if the list is constant you can build it - * with COMPLETE_WITH() or COMPLETE_WITH_CS(). + * with COMPLETE_WITH() or COMPLETE_WITH_CS(). The QUERY_LIST and + * QUERY_PLUS forms combine such literal lists with a query result. * 4) The list of attributes of the given table (possibly schema-qualified). * 5) The list of arguments to the given function (possibly schema-qualified). */ #define COMPLETE_WITH_QUERY(query) \ + COMPLETE_WITH_QUERY_LIST(query, NULL) + +#define COMPLETE_WITH_QUERY_LIST(query, list) \ do { \ completion_charp = query; \ + completion_charpp = list; \ + completion_verbatim = false; \ + matches = rl_completion_matches(text, complete_from_query); \ +} while (0) + +#define COMPLETE_WITH_QUERY_PLUS(query, ...) \ +do { \ + static const char *const list[] = { __VA_ARGS__, NULL }; \ + COMPLETE_WITH_QUERY_LIST(query, list); \ +} while (0) + +#define COMPLETE_WITH_QUERY_VERBATIM(query) \ +do { \ + completion_charp = query; \ + completion_charpp = NULL; \ + completion_verbatim = true; \ matches = rl_completion_matches(text, complete_from_query); \ } while (0) #define COMPLETE_WITH_VERSIONED_QUERY(query) \ + COMPLETE_WITH_VERSIONED_QUERY_LIST(query, NULL) + +#define COMPLETE_WITH_VERSIONED_QUERY_LIST(query, list) \ do { \ completion_vquery = query; \ + completion_charpp = list; \ + completion_verbatim = false; \ matches = rl_completion_matches(text, complete_from_versioned_query); \ } while (0) -#define COMPLETE_WITH_SCHEMA_QUERY(query, addon) \ +#define COMPLETE_WITH_VERSIONED_QUERY_PLUS(query, ...) \ +do { \ + static const char *const list[] = { __VA_ARGS__, NULL }; \ + COMPLETE_WITH_VERSIONED_QUERY_LIST(query, list); \ +} while (0) + +#define COMPLETE_WITH_SCHEMA_QUERY(query) \ + COMPLETE_WITH_SCHEMA_QUERY_LIST(query, NULL) + +#define COMPLETE_WITH_SCHEMA_QUERY_LIST(query, list) \ do { \ completion_squery = &(query); \ - completion_charp = addon; \ + completion_charpp = list; \ + completion_verbatim = false; \ matches = rl_completion_matches(text, complete_from_schema_query); \ } while (0) -#define COMPLETE_WITH_VERSIONED_SCHEMA_QUERY(query, addon) \ +#define COMPLETE_WITH_SCHEMA_QUERY_PLUS(query, ...) \ +do { \ + static const char *const list[] = { __VA_ARGS__, NULL }; \ + COMPLETE_WITH_SCHEMA_QUERY_LIST(query, list); \ +} while (0) + +#define COMPLETE_WITH_SCHEMA_QUERY_VERBATIM(query) \ +do { \ + completion_squery = &(query); \ + completion_charpp = NULL; \ + completion_verbatim = true; \ + matches = rl_completion_matches(text, complete_from_schema_query); \ +} while (0) + +#define COMPLETE_WITH_VERSIONED_SCHEMA_QUERY(query) \ + COMPLETE_WITH_VERSIONED_SCHEMA_QUERY_LIST(query, NULL) + +#define COMPLETE_WITH_VERSIONED_SCHEMA_QUERY_LIST(query, list) \ do { \ completion_squery = query; \ - completion_vquery = addon; \ + completion_charpp = list; \ + completion_verbatim = false; \ matches = rl_completion_matches(text, complete_from_versioned_schema_query); \ } while (0) +#define COMPLETE_WITH_VERSIONED_SCHEMA_QUERY_PLUS(query, ...) \ +do { \ + static const char *const list[] = { __VA_ARGS__, NULL }; \ + COMPLETE_WITH_VERSIONED_SCHEMA_QUERY_LIST(query, list); \ +} while (0) + /* * Caution: COMPLETE_WITH_CONST is not for general-purpose use; you probably * want COMPLETE_WITH() with one element, instead. @@ -253,29 +356,22 @@ do { \ COMPLETE_WITH_LIST_CS(list); \ } while (0) -#define COMPLETE_WITH_ATTR(relation, addon) \ +#define COMPLETE_WITH_ATTR(relation) \ + COMPLETE_WITH_ATTR_LIST(relation, NULL) + +#define COMPLETE_WITH_ATTR_LIST(relation, list) \ 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 = rl_completion_matches(text, complete_from_query); \ + set_completion_reference(relation); \ + completion_squery = &(Query_for_list_of_attributes); \ + completion_charpp = list; \ + completion_verbatim = false; \ + matches = rl_completion_matches(text, complete_from_schema_query); \ +} while (0) + +#define COMPLETE_WITH_ATTR_PLUS(relation, ...) \ +do { \ + static const char *const list[] = { __VA_ARGS__, NULL }; \ + COMPLETE_WITH_ATTR_LIST(relation, list); \ } while (0) /* @@ -286,61 +382,24 @@ do { \ */ #define COMPLETE_WITH_ENUM_VALUE(type) \ do { \ - char *_completion_schema; \ - char *_completion_type; \ - bool use_quotes; \ -\ - _completion_schema = strtokx(type, " \t\n\r", ".", "\"", 0, \ - false, false, pset.encoding); \ - (void) strtokx(NULL, " \t\n\r", ".", "\"", 0, \ - false, false, pset.encoding); \ - _completion_type = strtokx(NULL, " \t\n\r", ".", "\"", 0, \ - false, false, pset.encoding); \ - use_quotes = (text[0] == '\'' || \ - start == 0 || rl_line_buffer[start - 1] != '\''); \ - if (_completion_type == NULL) \ - { \ - if (use_quotes) \ - completion_charp = Query_for_list_of_enum_values_quoted; \ - else \ - completion_charp = Query_for_list_of_enum_values_unquoted; \ - completion_info_charp = type; \ - } \ + set_completion_reference(type); \ + if (text[0] == '\'' || \ + start == 0 || rl_line_buffer[start - 1] != '\'') \ + completion_squery = &(Query_for_list_of_enum_values_quoted); \ else \ - { \ - if (use_quotes) \ - completion_charp = Query_for_list_of_enum_values_with_schema_quoted; \ - else \ - completion_charp = Query_for_list_of_enum_values_with_schema_unquoted; \ - completion_info_charp = _completion_type; \ - completion_info_charp2 = _completion_schema; \ - } \ - matches = rl_completion_matches(text, complete_from_query); \ + completion_squery = &(Query_for_list_of_enum_values_unquoted); \ + completion_charpp = NULL; \ + completion_verbatim = true; \ + matches = rl_completion_matches(text, complete_from_schema_query); \ } while (0) #define COMPLETE_WITH_FUNCTION_ARG(function) \ do { \ - char *_completion_schema; \ - char *_completion_function; \ -\ - _completion_schema = strtokx(function, " \t\n\r", ".", "\"", 0, \ - false, false, pset.encoding); \ - (void) strtokx(NULL, " \t\n\r", ".", "\"", 0, \ - false, false, pset.encoding); \ - _completion_function = strtokx(NULL, " \t\n\r", ".", "\"", 0, \ - false, false, pset.encoding); \ - if (_completion_function == NULL) \ - { \ - completion_charp = Query_for_list_of_arguments; \ - completion_info_charp = function; \ - } \ - else \ - { \ - completion_charp = Query_for_list_of_arguments_with_schema; \ - completion_info_charp = _completion_function; \ - completion_info_charp2 = _completion_schema; \ - } \ - matches = rl_completion_matches(text, complete_from_query); \ + set_completion_reference(function); \ + completion_squery = &(Query_for_list_of_arguments); \ + completion_charpp = NULL; \ + completion_verbatim = true; \ + matches = rl_completion_matches(text, complete_from_schema_query); \ } while (0) /* @@ -350,6 +409,51 @@ do { \ * unnecessary bloat in the completions generated. */ +static const SchemaQuery Query_for_constraint_of_table = { + .catname = "pg_catalog.pg_constraint con, pg_catalog.pg_class c1", + .selcondition = "con.conrelid=c1.oid", + .result = "con.conname", + .refname = "c1.relname", + .refviscondition = "pg_catalog.pg_table_is_visible(c1.oid)", + .refnamespace = "c1.relnamespace", +}; + +static const SchemaQuery Query_for_constraint_of_table_not_validated = { + .catname = "pg_catalog.pg_constraint con, pg_catalog.pg_class c1", + .selcondition = "con.conrelid=c1.oid and not con.convalidated", + .result = "con.conname", + .refname = "c1.relname", + .refviscondition = "pg_catalog.pg_table_is_visible(c1.oid)", + .refnamespace = "c1.relnamespace", +}; + +static const SchemaQuery Query_for_constraint_of_type = { + .catname = "pg_catalog.pg_constraint con, pg_catalog.pg_type t", + .selcondition = "con.contypid=t.oid", + .result = "con.conname", + .refname = "t.typname", + .refviscondition = "pg_catalog.pg_type_is_visible(t.oid)", + .refnamespace = "t.typnamespace", +}; + +static const SchemaQuery Query_for_index_of_table = { + .catname = "pg_catalog.pg_class c1, pg_catalog.pg_class c2, pg_catalog.pg_index i", + .selcondition = "c1.oid=i.indrelid and i.indexrelid=c2.oid", + .result = "c2.relname", + .refname = "c1.relname", + .refviscondition = "pg_catalog.pg_table_is_visible(c1.oid)", + .refnamespace = "c1.relnamespace", +}; + +static const SchemaQuery Query_for_unique_index_of_table = { + .catname = "pg_catalog.pg_class c1, pg_catalog.pg_class c2, pg_catalog.pg_index i", + .selcondition = "c1.oid=i.indrelid and i.indexrelid=c2.oid and i.indisunique", + .result = "c2.relname", + .refname = "c1.relname", + .refviscondition = "pg_catalog.pg_table_is_visible(c1.oid)", + .refnamespace = "c1.relnamespace", +}; + static const SchemaQuery Query_for_list_of_aggregates[] = { { .min_server_version = 110000, @@ -357,17 +461,71 @@ static const SchemaQuery Query_for_list_of_aggregates[] = { .selcondition = "p.prokind = 'a'", .viscondition = "pg_catalog.pg_function_is_visible(p.oid)", .namespace = "p.pronamespace", - .result = "pg_catalog.quote_ident(p.proname)", + .result = "p.proname", }, { .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)", + .result = "p.proname", } }; +static const SchemaQuery Query_for_list_of_arguments = { + .catname = "pg_catalog.pg_proc p", + .result = "pg_catalog.oidvectortypes(p.proargtypes)||')'", + .refname = "p.proname", + .refviscondition = "pg_catalog.pg_function_is_visible(p.oid)", + .refnamespace = "p.pronamespace", +}; + +static const SchemaQuery Query_for_list_of_attributes = { + .catname = "pg_catalog.pg_attribute a, pg_catalog.pg_class c", + .selcondition = "c.oid = a.attrelid and a.attnum > 0 and not a.attisdropped", + .result = "a.attname", + .refname = "c.relname", + .refviscondition = "pg_catalog.pg_table_is_visible(c.oid)", + .refnamespace = "c.relnamespace", +}; + +static const SchemaQuery Query_for_list_of_attribute_numbers = { + .catname = "pg_catalog.pg_attribute a, pg_catalog.pg_class c", + .selcondition = "c.oid = a.attrelid and a.attnum > 0 and not a.attisdropped", + .result = "a.attnum::pg_catalog.text", + .refname = "c.relname", + .refviscondition = "pg_catalog.pg_table_is_visible(c.oid)", + .refnamespace = "c.relnamespace", +}; + +static const char *const Keywords_for_list_of_datatypes[] = { + "bigint", + "boolean", + "character", + "double precision", + "integer", + "real", + "smallint", + + /* + * Note: currently there's no value in offering the following multiword + * type names, because tab completion cannot succeed for them: we can't + * disambiguate until somewhere in the second word, at which point we + * won't have the first word as context. ("double precision" does work, + * as long as no other type name begins with "double".) Leave them out to + * encourage users to use the PG-specific aliases, which we can complete. + */ +#ifdef NOT_USED + "bit varying", + "character varying", + "time with time zone", + "time without time zone", + "timestamp with time zone", + "timestamp without time zone", +#endif + NULL +}; + static const SchemaQuery Query_for_list_of_datatypes = { .catname = "pg_catalog.pg_type t", /* selcondition --- ignore table rowtypes and array types */ @@ -377,8 +535,8 @@ static const SchemaQuery Query_for_list_of_datatypes = { "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)", + .result = "t.typname", + .keywords = Keywords_for_list_of_datatypes, }; static const SchemaQuery Query_for_list_of_composite_datatypes = { @@ -389,8 +547,7 @@ static const SchemaQuery Query_for_list_of_composite_datatypes = { "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)", + .result = "t.typname", }; static const SchemaQuery Query_for_list_of_domains = { @@ -398,7 +555,25 @@ static const SchemaQuery Query_for_list_of_domains = { .selcondition = "t.typtype = 'd'", .viscondition = "pg_catalog.pg_type_is_visible(t.oid)", .namespace = "t.typnamespace", - .result = "pg_catalog.quote_ident(t.typname)", + .result = "t.typname", +}; + +static const SchemaQuery Query_for_list_of_enum_values_quoted = { + .catname = "pg_catalog.pg_enum e, pg_catalog.pg_type t", + .selcondition = "t.oid = e.enumtypid", + .result = "pg_catalog.quote_literal(enumlabel)", + .refname = "t.typname", + .refviscondition = "pg_catalog.pg_type_is_visible(t.oid)", + .refnamespace = "t.typnamespace", +}; + +static const SchemaQuery Query_for_list_of_enum_values_unquoted = { + .catname = "pg_catalog.pg_enum e, pg_catalog.pg_type t", + .selcondition = "t.oid = e.enumtypid", + .result = "e.enumlabel", + .refname = "t.typname", + .refviscondition = "pg_catalog.pg_type_is_visible(t.oid)", + .refnamespace = "t.typnamespace", }; /* Note: this intentionally accepts aggregates as well as plain functions */ @@ -409,13 +584,13 @@ static const SchemaQuery Query_for_list_of_functions[] = { .selcondition = "p.prokind != 'p'", .viscondition = "pg_catalog.pg_function_is_visible(p.oid)", .namespace = "p.pronamespace", - .result = "pg_catalog.quote_ident(p.proname)", + .result = "p.proname", }, { .catname = "pg_catalog.pg_proc p", .viscondition = "pg_catalog.pg_function_is_visible(p.oid)", .namespace = "p.pronamespace", - .result = "pg_catalog.quote_ident(p.proname)", + .result = "p.proname", } }; @@ -426,7 +601,7 @@ static const SchemaQuery Query_for_list_of_procedures[] = { .selcondition = "p.prokind = 'p'", .viscondition = "pg_catalog.pg_function_is_visible(p.oid)", .namespace = "p.pronamespace", - .result = "pg_catalog.quote_ident(p.proname)", + .result = "p.proname", }, { /* not supported in older versions */ @@ -438,7 +613,7 @@ static const SchemaQuery Query_for_list_of_routines = { .catname = "pg_catalog.pg_proc p", .viscondition = "pg_catalog.pg_function_is_visible(p.oid)", .namespace = "p.pronamespace", - .result = "pg_catalog.quote_ident(p.proname)", + .result = "p.proname", }; static const SchemaQuery Query_for_list_of_sequences = { @@ -446,7 +621,7 @@ static const SchemaQuery Query_for_list_of_sequences = { .selcondition = "c.relkind IN (" CppAsString2(RELKIND_SEQUENCE) ")", .viscondition = "pg_catalog.pg_table_is_visible(c.oid)", .namespace = "c.relnamespace", - .result = "pg_catalog.quote_ident(c.relname)", + .result = "c.relname", }; static const SchemaQuery Query_for_list_of_foreign_tables = { @@ -454,7 +629,7 @@ static const SchemaQuery Query_for_list_of_foreign_tables = { .selcondition = "c.relkind IN (" CppAsString2(RELKIND_FOREIGN_TABLE) ")", .viscondition = "pg_catalog.pg_table_is_visible(c.oid)", .namespace = "c.relnamespace", - .result = "pg_catalog.quote_ident(c.relname)", + .result = "c.relname", }; static const SchemaQuery Query_for_list_of_tables = { @@ -464,7 +639,7 @@ static const SchemaQuery Query_for_list_of_tables = { CppAsString2(RELKIND_PARTITIONED_TABLE) ")", .viscondition = "pg_catalog.pg_table_is_visible(c.oid)", .namespace = "c.relnamespace", - .result = "pg_catalog.quote_ident(c.relname)", + .result = "c.relname", }; static const SchemaQuery Query_for_list_of_partitioned_tables = { @@ -472,7 +647,77 @@ static const SchemaQuery Query_for_list_of_partitioned_tables = { .selcondition = "c.relkind IN (" CppAsString2(RELKIND_PARTITIONED_TABLE) ")", .viscondition = "pg_catalog.pg_table_is_visible(c.oid)", .namespace = "c.relnamespace", - .result = "pg_catalog.quote_ident(c.relname)", + .result = "c.relname", +}; + +static const SchemaQuery Query_for_list_of_tables_for_constraint = { + .catname = "pg_catalog.pg_class c, pg_catalog.pg_constraint con", + .selcondition = "c.oid=con.conrelid and c.relkind IN (" + CppAsString2(RELKIND_RELATION) ", " + CppAsString2(RELKIND_PARTITIONED_TABLE) ")", + .viscondition = "pg_catalog.pg_table_is_visible(c.oid)", + .namespace = "c.relnamespace", + .result = "c.relname", + .use_distinct = true, + .refname = "con.conname", +}; + +static const SchemaQuery Query_for_list_of_tables_for_policy = { + .catname = "pg_catalog.pg_class c, pg_catalog.pg_policy p", + .selcondition = "c.oid=p.polrelid", + .viscondition = "pg_catalog.pg_table_is_visible(c.oid)", + .namespace = "c.relnamespace", + .result = "c.relname", + .use_distinct = true, + .refname = "p.polname", +}; + +static const SchemaQuery Query_for_list_of_tables_for_rule = { + .catname = "pg_catalog.pg_class c, pg_catalog.pg_rewrite r", + .selcondition = "c.oid=r.ev_class", + .viscondition = "pg_catalog.pg_table_is_visible(c.oid)", + .namespace = "c.relnamespace", + .result = "c.relname", + .use_distinct = true, + .refname = "r.rulename", +}; + +static const SchemaQuery Query_for_list_of_tables_for_trigger = { + .catname = "pg_catalog.pg_class c, pg_catalog.pg_trigger t", + .selcondition = "c.oid=t.tgrelid", + .viscondition = "pg_catalog.pg_table_is_visible(c.oid)", + .namespace = "c.relnamespace", + .result = "c.relname", + .use_distinct = true, + .refname = "t.tgname", +}; + +static const SchemaQuery Query_for_list_of_ts_configurations = { + .catname = "pg_catalog.pg_ts_config c", + .viscondition = "pg_catalog.pg_ts_config_is_visible(c.oid)", + .namespace = "c.cfgnamespace", + .result = "c.cfgname", +}; + +static const SchemaQuery Query_for_list_of_ts_dictionaries = { + .catname = "pg_catalog.pg_ts_dict d", + .viscondition = "pg_catalog.pg_ts_dict_is_visible(d.oid)", + .namespace = "d.dictnamespace", + .result = "d.dictname", +}; + +static const SchemaQuery Query_for_list_of_ts_parsers = { + .catname = "pg_catalog.pg_ts_parser p", + .viscondition = "pg_catalog.pg_ts_parser_is_visible(p.oid)", + .namespace = "p.prsnamespace", + .result = "p.prsname", +}; + +static const SchemaQuery Query_for_list_of_ts_templates = { + .catname = "pg_catalog.pg_ts_template t", + .viscondition = "pg_catalog.pg_ts_template_is_visible(t.oid)", + .namespace = "t.tmplnamespace", + .result = "t.tmplname", }; static const SchemaQuery Query_for_list_of_views = { @@ -480,7 +725,7 @@ static const SchemaQuery Query_for_list_of_views = { .selcondition = "c.relkind IN (" CppAsString2(RELKIND_VIEW) ")", .viscondition = "pg_catalog.pg_table_is_visible(c.oid)", .namespace = "c.relnamespace", - .result = "pg_catalog.quote_ident(c.relname)", + .result = "c.relname", }; static const SchemaQuery Query_for_list_of_matviews = { @@ -488,7 +733,7 @@ static const SchemaQuery Query_for_list_of_matviews = { .selcondition = "c.relkind IN (" CppAsString2(RELKIND_MATVIEW) ")", .viscondition = "pg_catalog.pg_table_is_visible(c.oid)", .namespace = "c.relnamespace", - .result = "pg_catalog.quote_ident(c.relname)", + .result = "c.relname", }; static const SchemaQuery Query_for_list_of_indexes = { @@ -498,7 +743,7 @@ static const SchemaQuery Query_for_list_of_indexes = { CppAsString2(RELKIND_PARTITIONED_INDEX) ")", .viscondition = "pg_catalog.pg_table_is_visible(c.oid)", .namespace = "c.relnamespace", - .result = "pg_catalog.quote_ident(c.relname)", + .result = "c.relname", }; static const SchemaQuery Query_for_list_of_partitioned_indexes = { @@ -506,7 +751,7 @@ static const SchemaQuery Query_for_list_of_partitioned_indexes = { .selcondition = "c.relkind = " CppAsString2(RELKIND_PARTITIONED_INDEX), .viscondition = "pg_catalog.pg_table_is_visible(c.oid)", .namespace = "c.relnamespace", - .result = "pg_catalog.quote_ident(c.relname)", + .result = "c.relname", }; @@ -515,7 +760,7 @@ static const SchemaQuery Query_for_list_of_relations = { .catname = "pg_catalog.pg_class c", .viscondition = "pg_catalog.pg_table_is_visible(c.oid)", .namespace = "c.relnamespace", - .result = "pg_catalog.quote_ident(c.relname)", + .result = "c.relname", }; /* partitioned relations */ @@ -525,14 +770,14 @@ static const SchemaQuery Query_for_list_of_partitioned_relations = { ", " CppAsString2(RELKIND_PARTITIONED_INDEX) ")", .viscondition = "pg_catalog.pg_table_is_visible(c.oid)", .namespace = "c.relnamespace", - .result = "pg_catalog.quote_ident(c.relname)", + .result = "c.relname", }; static const SchemaQuery Query_for_list_of_operator_families = { .catname = "pg_catalog.pg_opfamily c", .viscondition = "pg_catalog.pg_opfamily_is_visible(c.oid)", .namespace = "c.opfnamespace", - .result = "pg_catalog.quote_ident(c.opfname)", + .result = "c.opfname", }; /* Relations supporting INSERT, UPDATE or DELETE */ @@ -545,7 +790,7 @@ static const SchemaQuery Query_for_list_of_updatables = { CppAsString2(RELKIND_PARTITIONED_TABLE) ")", .viscondition = "pg_catalog.pg_table_is_visible(c.oid)", .namespace = "c.relnamespace", - .result = "pg_catalog.quote_ident(c.relname)", + .result = "c.relname", }; /* Relations supporting SELECT */ @@ -560,7 +805,7 @@ static const SchemaQuery Query_for_list_of_selectables = { CppAsString2(RELKIND_PARTITIONED_TABLE) ")", .viscondition = "pg_catalog.pg_table_is_visible(c.oid)", .namespace = "c.relnamespace", - .result = "pg_catalog.quote_ident(c.relname)", + .result = "c.relname", }; /* Relations supporting TRUNCATE */ @@ -572,7 +817,7 @@ static const SchemaQuery Query_for_list_of_truncatables = { CppAsString2(RELKIND_PARTITIONED_TABLE) ")", .viscondition = "pg_catalog.pg_table_is_visible(c.oid)", .namespace = "c.relnamespace", - .result = "pg_catalog.quote_ident(c.relname)", + .result = "c.relname", }; /* Relations supporting GRANT are currently same as those supporting SELECT */ @@ -588,7 +833,7 @@ static const SchemaQuery Query_for_list_of_analyzables = { CppAsString2(RELKIND_FOREIGN_TABLE) ")", .viscondition = "pg_catalog.pg_table_is_visible(c.oid)", .namespace = "c.relnamespace", - .result = "pg_catalog.quote_ident(c.relname)", + .result = "c.relname", }; /* Relations supporting index creation */ @@ -600,7 +845,7 @@ static const SchemaQuery Query_for_list_of_indexables = { CppAsString2(RELKIND_MATVIEW) ")", .viscondition = "pg_catalog.pg_table_is_visible(c.oid)", .namespace = "c.relnamespace", - .result = "pg_catalog.quote_ident(c.relname)", + .result = "c.relname", }; /* @@ -617,22 +862,21 @@ static const SchemaQuery Query_for_list_of_clusterables = { CppAsString2(RELKIND_MATVIEW) ")", .viscondition = "pg_catalog.pg_table_is_visible(c.oid)", .namespace = "c.relnamespace", - .result = "pg_catalog.quote_ident(c.relname)", + .result = "c.relname", }; static const SchemaQuery Query_for_list_of_constraints_with_schema = { .catname = "pg_catalog.pg_constraint c", .selcondition = "c.conrelid <> 0", - .viscondition = "true", /* there is no pg_constraint_is_visible */ .namespace = "c.connamespace", - .result = "pg_catalog.quote_ident(c.conname)", + .result = "c.conname", }; static const SchemaQuery Query_for_list_of_statistics = { .catname = "pg_catalog.pg_statistic_ext s", .viscondition = "pg_catalog.pg_statistics_obj_is_visible(s.oid)", .namespace = "s.stxnamespace", - .result = "pg_catalog.quote_ident(s.stxname)", + .result = "s.stxname", }; static const SchemaQuery Query_for_list_of_collations = { @@ -640,400 +884,217 @@ static const SchemaQuery Query_for_list_of_collations = { .selcondition = "c.collencoding IN (-1, pg_catalog.pg_char_to_encoding(pg_catalog.getdatabaseencoding()))", .viscondition = "pg_catalog.pg_collation_is_visible(c.oid)", .namespace = "c.collnamespace", - .result = "pg_catalog.quote_ident(c.collname)", + .result = "c.collname", +}; + +static const SchemaQuery Query_for_partition_of_table = { + .catname = "pg_catalog.pg_class c1, pg_catalog.pg_class c2, pg_catalog.pg_inherits i", + .selcondition = "c1.oid=i.inhparent and i.inhrelid=c2.oid and c2.relispartition", + .viscondition = "pg_catalog.pg_table_is_visible(c2.oid)", + .namespace = "c2.relnamespace", + .result = "c2.relname", + .refname = "c1.relname", + .refviscondition = "pg_catalog.pg_table_is_visible(c1.oid)", + .refnamespace = "c1.relnamespace", +}; + +static const SchemaQuery Query_for_rule_of_table = { + .catname = "pg_catalog.pg_rewrite r, pg_catalog.pg_class c1", + .selcondition = "r.ev_class=c1.oid", + .result = "r.rulename", + .refname = "c1.relname", + .refviscondition = "pg_catalog.pg_table_is_visible(c1.oid)", + .refnamespace = "c1.relnamespace", +}; + +static const SchemaQuery Query_for_trigger_of_table = { + .catname = "pg_catalog.pg_trigger t, pg_catalog.pg_class c1", + .selcondition = "t.tgrelid=c1.oid and not t.tgisinternal", + .result = "t.tgname", + .refname = "c1.relname", + .refviscondition = "pg_catalog.pg_table_is_visible(c1.oid)", + .refnamespace = "c1.relnamespace", }; /* * 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. + * restricted to names matching a partially entered name. Don't use + * this method where the user might wish to enter a schema-qualified + * name; make a SchemaQuery instead. * - * Beware that the allowed sequences of %s and %d are determined by - * _complete_from_query(). + * In these queries, there must be a restriction clause of the form + * output LIKE '%s' + * where "output" is the same string that the query returns. The %s + * will be replaced by a LIKE pattern to match the already-typed text. + * + * There can be a second '%s', which will be replaced by a suitably-escaped + * version of the string provided in completion_ref_object. If there is a + * third '%s', it will be replaced by a suitably-escaped version of the string + * provided in completion_ref_schema. NOTE: using completion_ref_object + * that way is usually the wrong thing, and using completion_ref_schema + * that way is always the wrong thing. Make a SchemaQuery instead. */ -#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_attribute_numbers \ -"SELECT attnum "\ -" 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(attnum::pg_catalog.text,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_enum_values_quoted \ -"SELECT pg_catalog.quote_literal(enumlabel) "\ -" FROM pg_catalog.pg_enum e, pg_catalog.pg_type t "\ -" WHERE t.oid = e.enumtypid "\ -" AND substring(pg_catalog.quote_literal(enumlabel),1,%d)='%s' "\ -" AND (pg_catalog.quote_ident(typname)='%s' "\ -" OR '\"' || typname || '\"'='%s') "\ -" AND pg_catalog.pg_type_is_visible(t.oid)" - -#define Query_for_list_of_enum_values_unquoted \ -"SELECT enumlabel "\ -" FROM pg_catalog.pg_enum e, pg_catalog.pg_type t "\ -" WHERE t.oid = e.enumtypid "\ -" AND substring(enumlabel,1,%d)='%s' "\ -" AND (pg_catalog.quote_ident(typname)='%s' "\ -" OR '\"' || typname || '\"'='%s') "\ -" AND pg_catalog.pg_type_is_visible(t.oid)" - -#define Query_for_list_of_enum_values_with_schema_quoted \ -"SELECT pg_catalog.quote_literal(enumlabel) "\ -" FROM pg_catalog.pg_enum e, pg_catalog.pg_type t, pg_catalog.pg_namespace n "\ -" WHERE t.oid = e.enumtypid "\ -" AND n.oid = t.typnamespace "\ -" AND substring(pg_catalog.quote_literal(enumlabel),1,%d)='%s' "\ -" AND (pg_catalog.quote_ident(typname)='%s' "\ -" OR '\"' || typname || '\"'='%s') "\ -" AND (pg_catalog.quote_ident(nspname)='%s' "\ -" OR '\"' || nspname || '\"' ='%s') " - -#define Query_for_list_of_enum_values_with_schema_unquoted \ -"SELECT enumlabel "\ -" FROM pg_catalog.pg_enum e, pg_catalog.pg_type t, pg_catalog.pg_namespace n "\ -" WHERE t.oid = e.enumtypid "\ -" AND n.oid = t.typnamespace "\ -" AND substring(enumlabel,1,%d)='%s' "\ -" AND (pg_catalog.quote_ident(typname)='%s' "\ -" OR '\"' || typname || '\"'='%s') "\ -" AND (pg_catalog.quote_ident(nspname)='%s' "\ -" OR '\"' || nspname || '\"' ='%s') " - #define Query_for_list_of_template_databases \ -"SELECT pg_catalog.quote_ident(d.datname) "\ +"SELECT d.datname "\ " FROM pg_catalog.pg_database d "\ -" WHERE substring(pg_catalog.quote_ident(d.datname),1,%d)='%s' "\ +" WHERE d.datname LIKE '%s' "\ " AND (d.datistemplate OR pg_catalog.pg_has_role(d.datdba, 'USAGE'))" #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'" +"SELECT datname FROM pg_catalog.pg_database "\ +" WHERE datname LIKE '%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'" +"SELECT spcname FROM pg_catalog.pg_tablespace "\ +" WHERE spcname LIKE '%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')" +" WHERE pg_catalog.pg_encoding_to_char(conforencoding) LIKE UPPER('%s')" #define Query_for_list_of_languages \ -"SELECT pg_catalog.quote_ident(lanname) "\ +"SELECT lanname "\ " FROM pg_catalog.pg_language "\ " WHERE lanname != 'internal' "\ -" AND substring(pg_catalog.quote_ident(lanname),1,%d)='%s'" +" AND lanname LIKE '%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'" +"SELECT nspname FROM pg_catalog.pg_namespace "\ +" WHERE nspname LIKE '%s'" #define Query_for_list_of_alter_system_set_vars \ "SELECT name FROM "\ " (SELECT pg_catalog.lower(name) AS name FROM pg_catalog.pg_settings "\ " WHERE context != 'internal' "\ -" UNION ALL SELECT 'all') ss "\ -" WHERE substring(name,1,%d)='%s'" +" ) ss "\ +" WHERE name LIKE '%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'" +" ) ss "\ +" WHERE name LIKE '%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'" +" ) ss "\ +" WHERE name LIKE '%s'" #define Query_for_list_of_roles \ -" SELECT pg_catalog.quote_ident(rolname) "\ +" SELECT rolname "\ " FROM pg_catalog.pg_roles "\ -" WHERE substring(pg_catalog.quote_ident(rolname),1,%d)='%s'" +" WHERE rolname LIKE '%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'"\ -" UNION ALL SELECT 'CURRENT_ROLE'"\ -" UNION ALL SELECT 'CURRENT_USER'"\ -" UNION ALL SELECT 'SESSION_USER'" - -/* 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)" - -#define Query_for_unique_index_of_table \ -Query_for_index_of_table \ -" and i.indisunique" - -/* the silly-looking length condition is just to eat up the current word */ -#define Query_for_constraint_of_table \ -"SELECT pg_catalog.quote_ident(conname) "\ -" FROM pg_catalog.pg_class c1, pg_catalog.pg_constraint con "\ -" WHERE c1.oid=conrelid and (%d = pg_catalog.length('%s'))"\ -" and pg_catalog.quote_ident(c1.relname)='%s'"\ -" and pg_catalog.pg_table_is_visible(c1.oid)" - -/* the silly-looking length condition is just to eat up the current word */ -#define Query_for_constraint_of_table_not_validated \ -"SELECT pg_catalog.quote_ident(conname) "\ -" FROM pg_catalog.pg_class c1, pg_catalog.pg_constraint con "\ -" WHERE c1.oid=conrelid and (%d = pg_catalog.length('%s'))"\ -" and pg_catalog.quote_ident(c1.relname)='%s'"\ -" and pg_catalog.pg_table_is_visible(c1.oid)" \ -" and not con.convalidated" +/* add these to Query_for_list_of_roles in GRANT contexts */ +#define Keywords_for_list_of_grant_roles \ +"PUBLIC", "CURRENT_ROLE", "CURRENT_USER", "SESSION_USER" #define Query_for_all_table_constraints \ -"SELECT pg_catalog.quote_ident(conname) "\ +"SELECT conname "\ " FROM pg_catalog.pg_constraint c "\ -" WHERE c.conrelid <> 0 " - -/* the silly-looking length condition is just to eat up the current word */ -#define Query_for_constraint_of_type \ -"SELECT pg_catalog.quote_ident(conname) "\ -" FROM pg_catalog.pg_type t, pg_catalog.pg_constraint con "\ -" WHERE t.oid=contypid and (%d = pg_catalog.length('%s'))"\ -" and pg_catalog.quote_ident(t.typname)='%s'"\ -" and pg_catalog.pg_type_is_visible(t.oid)" - -/* the silly-looking length condition is just to eat up the current word */ -#define Query_for_list_of_tables_for_constraint \ -"SELECT pg_catalog.quote_ident(relname) "\ -" FROM pg_catalog.pg_class"\ -" WHERE (%d = pg_catalog.length('%s'))"\ -" AND oid IN "\ -" (SELECT conrelid FROM pg_catalog.pg_constraint "\ -" WHERE pg_catalog.quote_ident(conname)='%s')" - -/* the silly-looking length condition is just to eat up the current word */ -#define Query_for_rule_of_table \ -"SELECT pg_catalog.quote_ident(rulename) "\ -" FROM pg_catalog.pg_class c1, pg_catalog.pg_rewrite "\ -" WHERE c1.oid=ev_class and (%d = pg_catalog.length('%s'))"\ -" and pg_catalog.quote_ident(c1.relname)='%s'"\ -" and pg_catalog.pg_table_is_visible(c1.oid)" - -/* the silly-looking length condition is just to eat up the current word */ -#define Query_for_list_of_tables_for_rule \ -"SELECT pg_catalog.quote_ident(relname) "\ -" FROM pg_catalog.pg_class"\ -" WHERE (%d = pg_catalog.length('%s'))"\ -" AND oid IN "\ -" (SELECT ev_class FROM pg_catalog.pg_rewrite "\ -" WHERE pg_catalog.quote_ident(rulename)='%s')" - -/* the silly-looking length condition is just to eat up the current word */ -#define Query_for_trigger_of_table \ -"SELECT pg_catalog.quote_ident(tgname) "\ -" FROM pg_catalog.pg_class c1, pg_catalog.pg_trigger "\ -" WHERE c1.oid=tgrelid and (%d = pg_catalog.length('%s'))"\ -" and pg_catalog.quote_ident(c1.relname)='%s'"\ -" and pg_catalog.pg_table_is_visible(c1.oid)"\ -" and not tgisinternal" - -/* 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'" +" WHERE c.conrelid <> 0 "\ +" and conname LIKE '%s'" #define Query_for_list_of_fdws \ -" SELECT pg_catalog.quote_ident(fdwname) "\ +" SELECT fdwname "\ " FROM pg_catalog.pg_foreign_data_wrapper "\ -" WHERE substring(pg_catalog.quote_ident(fdwname),1,%d)='%s'" +" WHERE fdwname LIKE '%s'" #define Query_for_list_of_servers \ -" SELECT pg_catalog.quote_ident(srvname) "\ +" SELECT srvname "\ " FROM pg_catalog.pg_foreign_server "\ -" WHERE substring(pg_catalog.quote_ident(srvname),1,%d)='%s'" +" WHERE srvname LIKE '%s'" #define Query_for_list_of_user_mappings \ -" SELECT pg_catalog.quote_ident(usename) "\ +" SELECT usename "\ " FROM pg_catalog.pg_user_mappings "\ -" WHERE substring(pg_catalog.quote_ident(usename),1,%d)='%s'" +" WHERE usename LIKE '%s'" #define Query_for_list_of_access_methods \ -" SELECT pg_catalog.quote_ident(amname) "\ +" SELECT amname "\ " FROM pg_catalog.pg_am "\ -" WHERE substring(pg_catalog.quote_ident(amname),1,%d)='%s'" +" WHERE amname LIKE '%s'" #define Query_for_list_of_index_access_methods \ -" SELECT pg_catalog.quote_ident(amname) "\ +" SELECT amname "\ " FROM pg_catalog.pg_am "\ -" WHERE substring(pg_catalog.quote_ident(amname),1,%d)='%s' AND "\ +" WHERE amname LIKE '%s' AND "\ " amtype=" CppAsString2(AMTYPE_INDEX) #define Query_for_list_of_table_access_methods \ -" SELECT pg_catalog.quote_ident(amname) "\ +" SELECT amname "\ " FROM pg_catalog.pg_am "\ -" WHERE substring(pg_catalog.quote_ident(amname),1,%d)='%s' AND "\ +" WHERE amname LIKE '%s' AND "\ " amtype=" CppAsString2(AMTYPE_TABLE) -/* the silly-looking length condition is just to eat up the current word */ -#define Query_for_list_of_arguments \ -"SELECT pg_catalog.oidvectortypes(proargtypes)||')' "\ -" FROM pg_catalog.pg_proc "\ -" WHERE (%d = pg_catalog.length('%s'))"\ -" AND (pg_catalog.quote_ident(proname)='%s'"\ -" OR '\"' || proname || '\"'='%s') "\ -" AND (pg_catalog.pg_function_is_visible(pg_proc.oid))" - -/* the silly-looking length condition is just to eat up the current word */ -#define Query_for_list_of_arguments_with_schema \ -"SELECT pg_catalog.oidvectortypes(proargtypes)||')' "\ -" FROM pg_catalog.pg_proc p, pg_catalog.pg_namespace n "\ -" WHERE (%d = pg_catalog.length('%s'))"\ -" AND n.oid = p.pronamespace "\ -" AND (pg_catalog.quote_ident(proname)='%s' "\ -" OR '\"' || proname || '\"' ='%s') "\ -" AND (pg_catalog.quote_ident(nspname)='%s' "\ -" OR '\"' || nspname || '\"' ='%s') " - #define Query_for_list_of_extensions \ -" SELECT pg_catalog.quote_ident(extname) "\ +" SELECT extname "\ " FROM pg_catalog.pg_extension "\ -" WHERE substring(pg_catalog.quote_ident(extname),1,%d)='%s'" +" WHERE extname LIKE '%s'" #define Query_for_list_of_available_extensions \ -" SELECT pg_catalog.quote_ident(name) "\ +" SELECT name "\ " FROM pg_catalog.pg_available_extensions "\ -" WHERE substring(pg_catalog.quote_ident(name),1,%d)='%s' AND installed_version IS NULL" +" WHERE name LIKE '%s' AND installed_version IS NULL" -/* the silly-looking length condition is just to eat up the current word */ +/* the result of this query is not an identifier, so use VERBATIM */ #define Query_for_list_of_available_extension_versions \ -" SELECT pg_catalog.quote_ident(version) "\ +" SELECT version "\ " FROM pg_catalog.pg_available_extension_versions "\ -" WHERE (%d = pg_catalog.length('%s'))"\ -" AND pg_catalog.quote_ident(name)='%s'" +" WHERE version LIKE '%s'"\ +" AND name='%s'" -/* the silly-looking length condition is just to eat up the current word */ +/* the result of this query is not an identifier, so use VERBATIM */ #define Query_for_list_of_available_extension_versions_with_TO \ -" SELECT 'TO ' || pg_catalog.quote_ident(version) "\ +" SELECT 'TO ' || version "\ " FROM pg_catalog.pg_available_extension_versions "\ -" WHERE (%d = pg_catalog.length('%s'))"\ -" AND pg_catalog.quote_ident(name)='%s'" +" WHERE ('TO ' || version) LIKE '%s'"\ +" AND name='%s'" #define Query_for_list_of_prepared_statements \ -" SELECT pg_catalog.quote_ident(name) "\ +" SELECT name "\ " FROM pg_catalog.pg_prepared_statements "\ -" WHERE substring(pg_catalog.quote_ident(name),1,%d)='%s'" +" WHERE name LIKE '%s'" #define Query_for_list_of_event_triggers \ -" SELECT pg_catalog.quote_ident(evtname) "\ +" SELECT evtname "\ " FROM pg_catalog.pg_event_trigger "\ -" WHERE substring(pg_catalog.quote_ident(evtname),1,%d)='%s'" +" WHERE evtname LIKE '%s'" #define Query_for_list_of_tablesample_methods \ -" SELECT pg_catalog.quote_ident(proname) "\ +" SELECT proname "\ " FROM pg_catalog.pg_proc "\ " WHERE prorettype = 'pg_catalog.tsm_handler'::pg_catalog.regtype AND "\ " proargtypes[0] = 'pg_catalog.internal'::pg_catalog.regtype AND "\ -" substring(pg_catalog.quote_ident(proname),1,%d)='%s'" +" proname LIKE '%s'" #define Query_for_list_of_policies \ -" SELECT pg_catalog.quote_ident(polname) "\ +" SELECT polname "\ " FROM pg_catalog.pg_policy "\ -" WHERE substring(pg_catalog.quote_ident(polname),1,%d)='%s'" +" WHERE polname LIKE '%s'" -#define Query_for_list_of_tables_for_policy \ -"SELECT pg_catalog.quote_ident(relname) "\ -" FROM pg_catalog.pg_class"\ -" WHERE (%d = pg_catalog.length('%s'))"\ -" AND oid IN "\ -" (SELECT polrelid FROM pg_catalog.pg_policy "\ -" WHERE pg_catalog.quote_ident(polname)='%s')" - -#define Query_for_enum \ -" SELECT name FROM ( "\ -" SELECT pg_catalog.quote_ident(pg_catalog.unnest(enumvals)) AS name "\ +#define Query_for_values_of_enum_GUC \ +" SELECT val FROM ( "\ +" SELECT name, pg_catalog.unnest(enumvals) AS val "\ " FROM pg_catalog.pg_settings "\ -" WHERE pg_catalog.lower(name)=pg_catalog.lower('%s') "\ -" UNION ALL " \ -" SELECT 'DEFAULT' ) ss "\ -" WHERE pg_catalog.substring(name,1,%%d)='%%s'" +" ) ss "\ +" WHERE val LIKE '%s'"\ +" and pg_catalog.lower(name)=pg_catalog.lower('%s')" -/* the silly-looking length condition is just to eat up the current word */ -#define Query_for_partition_of_table \ -"SELECT pg_catalog.quote_ident(c2.relname) "\ -" FROM pg_catalog.pg_class c1, pg_catalog.pg_class c2, pg_catalog.pg_inherits i"\ -" WHERE c1.oid=i.inhparent and i.inhrelid=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)"\ -" and c2.relispartition = 'true'" +#define Query_for_list_of_channels \ +" SELECT channel "\ +" FROM pg_catalog.pg_listening_channels() AS channel "\ +" WHERE channel LIKE '%s'" #define Query_for_list_of_cursors \ -" SELECT pg_catalog.quote_ident(name) "\ +" SELECT name "\ " FROM pg_catalog.pg_cursors "\ -" WHERE substring(pg_catalog.quote_ident(name),1,%d)='%s'" +" WHERE name LIKE '%s'" /* * These object types were introduced later than our support cutoff of @@ -1043,18 +1104,18 @@ Query_for_index_of_table \ static const VersionedQuery Query_for_list_of_publications[] = { {100000, - " SELECT pg_catalog.quote_ident(pubname) " + " SELECT pubname " " FROM pg_catalog.pg_publication " - " WHERE substring(pg_catalog.quote_ident(pubname),1,%d)='%s'" + " WHERE pubname LIKE '%s'" }, {0, NULL} }; static const VersionedQuery Query_for_list_of_subscriptions[] = { {100000, - " SELECT pg_catalog.quote_ident(s.subname) " + " SELECT s.subname " " FROM pg_catalog.pg_subscription s, pg_catalog.pg_database d " - " WHERE substring(pg_catalog.quote_ident(s.subname),1,%d)='%s' " + " WHERE s.subname LIKE '%s' " " AND d.datname = pg_catalog.current_database() " " AND s.subdbid = d.oid" }, @@ -1069,9 +1130,11 @@ static const VersionedQuery Query_for_list_of_subscriptions[] = { typedef struct { const char *name; + /* Provide at most one of these three types of query: */ const char *query; /* simple query, or NULL */ const VersionedQuery *vquery; /* versioned query, or NULL */ const SchemaQuery *squery; /* schema query, or NULL */ + const char *const *keywords; /* keywords to be offered as well */ const bits32 flags; /* visibility flags, see below */ } pgsql_thing_t; @@ -1080,8 +1143,14 @@ typedef struct #define THING_NO_ALTER (1 << 2) /* should not show up after ALTER */ #define THING_NO_SHOW (THING_NO_CREATE | THING_NO_DROP | THING_NO_ALTER) +/* When we have DROP USER etc, also offer MAPPING FOR */ +static const char *const Keywords_for_user_thing[] = { + "MAPPING FOR", + NULL +}; + static const pgsql_thing_t words_after_create[] = { - {"ACCESS METHOD", NULL, NULL, NULL, THING_NO_ALTER}, + {"ACCESS METHOD", NULL, NULL, NULL, NULL, THING_NO_ALTER}, {"AGGREGATE", NULL, NULL, Query_for_list_of_aggregates}, {"CAST", NULL, NULL, NULL}, /* Casts have complex structures for names, so * skip it */ @@ -1091,11 +1160,11 @@ static const pgsql_thing_t words_after_create[] = { * 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, 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'"}, + {"CONFIGURATION", NULL, NULL, &Query_for_list_of_ts_configurations, NULL, THING_NO_SHOW}, + {"CONVERSION", "SELECT conname FROM pg_catalog.pg_conversion WHERE conname LIKE '%s'"}, {"DATABASE", Query_for_list_of_databases}, - {"DEFAULT PRIVILEGES", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP}, - {"DICTIONARY", Query_for_list_of_ts_dictionaries, NULL, NULL, THING_NO_SHOW}, + {"DEFAULT PRIVILEGES", NULL, NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP}, + {"DICTIONARY", NULL, NULL, &Query_for_list_of_ts_dictionaries, NULL, THING_NO_SHOW}, {"DOMAIN", NULL, NULL, &Query_for_list_of_domains}, {"EVENT TRIGGER", NULL, NULL, NULL}, {"EXTENSION", Query_for_list_of_extensions}, @@ -1105,41 +1174,41 @@ static const pgsql_thing_t words_after_create[] = { {"GROUP", Query_for_list_of_roles}, {"INDEX", NULL, NULL, &Query_for_list_of_indexes}, {"LANGUAGE", Query_for_list_of_languages}, - {"LARGE OBJECT", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP}, + {"LARGE OBJECT", NULL, NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP}, {"MATERIALIZED VIEW", NULL, NULL, &Query_for_list_of_matviews}, {"OPERATOR", NULL, NULL, NULL}, /* Querying for this is probably not such * a good idea. */ - {"OR REPLACE", NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, - {"OWNED", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_ALTER}, /* for DROP OWNED BY ... */ - {"PARSER", Query_for_list_of_ts_parsers, NULL, NULL, THING_NO_SHOW}, + {"OR REPLACE", NULL, NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, + {"OWNED", NULL, NULL, NULL, NULL, THING_NO_CREATE | THING_NO_ALTER}, /* for DROP OWNED BY ... */ + {"PARSER", NULL, NULL, &Query_for_list_of_ts_parsers, NULL, THING_NO_SHOW}, {"POLICY", NULL, NULL, NULL}, {"PROCEDURE", NULL, NULL, Query_for_list_of_procedures}, {"PUBLICATION", NULL, Query_for_list_of_publications}, {"ROLE", Query_for_list_of_roles}, - {"ROUTINE", NULL, NULL, &Query_for_list_of_routines, THING_NO_CREATE}, - {"RULE", "SELECT pg_catalog.quote_ident(rulename) FROM pg_catalog.pg_rules WHERE substring(pg_catalog.quote_ident(rulename),1,%d)='%s'"}, + {"ROUTINE", NULL, NULL, &Query_for_list_of_routines, NULL, THING_NO_CREATE}, + {"RULE", "SELECT rulename FROM pg_catalog.pg_rules WHERE rulename LIKE '%s'"}, {"SCHEMA", Query_for_list_of_schemas}, {"SEQUENCE", NULL, NULL, &Query_for_list_of_sequences}, {"SERVER", Query_for_list_of_servers}, {"STATISTICS", NULL, NULL, &Query_for_list_of_statistics}, {"SUBSCRIPTION", NULL, Query_for_list_of_subscriptions}, - {"SYSTEM", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP}, + {"SYSTEM", NULL, NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP}, {"TABLE", NULL, NULL, &Query_for_list_of_tables}, {"TABLESPACE", Query_for_list_of_tablespaces}, - {"TEMP", NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE TEMP TABLE - * ... */ - {"TEMPLATE", Query_for_list_of_ts_templates, NULL, NULL, THING_NO_SHOW}, - {"TEMPORARY", NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE TEMPORARY - * TABLE ... */ + {"TEMP", NULL, NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE TEMP TABLE + * ... */ + {"TEMPLATE", NULL, NULL, &Query_for_list_of_ts_templates, NULL, THING_NO_SHOW}, + {"TEMPORARY", NULL, NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE TEMPORARY + * TABLE ... */ {"TEXT SEARCH", NULL, NULL, NULL}, - {"TRANSFORM", NULL, NULL, NULL, THING_NO_ALTER}, - {"TRIGGER", "SELECT pg_catalog.quote_ident(tgname) FROM pg_catalog.pg_trigger WHERE substring(pg_catalog.quote_ident(tgname),1,%d)='%s' AND NOT tgisinternal"}, + {"TRANSFORM", NULL, NULL, NULL, NULL, THING_NO_ALTER}, + {"TRIGGER", "SELECT tgname FROM pg_catalog.pg_trigger WHERE tgname LIKE '%s' AND NOT tgisinternal"}, {"TYPE", NULL, NULL, &Query_for_list_of_datatypes}, - {"UNIQUE", NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE UNIQUE - * INDEX ... */ - {"UNLOGGED", NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE UNLOGGED - * TABLE ... */ - {"USER", Query_for_list_of_roles " UNION SELECT 'MAPPING FOR'"}, + {"UNIQUE", NULL, NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE UNIQUE + * INDEX ... */ + {"UNLOGGED", NULL, NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE UNLOGGED + * TABLE ... */ + {"USER", Query_for_list_of_roles, NULL, NULL, Keywords_for_user_thing}, {"USER MAPPING FOR", NULL, NULL, NULL}, {"VIEW", NULL, NULL, &Query_for_list_of_views}, {NULL} /* end of list */ @@ -1200,7 +1269,10 @@ static char *complete_from_schema_query(const char *text, int state); static char *complete_from_versioned_schema_query(const char *text, int state); static char *_complete_from_query(const char *simple_query, const SchemaQuery *schema_query, + const char *const *keywords, + bool verbatim, const char *text, int state); +static void set_completion_reference(const char *word); static char *complete_from_list(const char *text, int state); static char *complete_from_const(const char *text, int state); static void append_variable_names(char ***varnames, int *nvars, @@ -1212,6 +1284,13 @@ static char *complete_from_files(const char *text, int state); static char *pg_strdup_keyword_case(const char *s, const char *ref); static char *escape_string(const char *text); +static char *make_like_pattern(const char *word); +static void parse_identifier(const char *ident, + char **schemaname, char **objectname, + bool *schemaquoted, bool *objectquoted); +static char *requote_identifier(const char *schemaname, const char *objectname, + bool quote_schema, bool quote_object); +static bool identifier_needs_quotes(const char *ident); static PGresult *exec_query(const char *query); static char **get_previous_words(int point, char **buffer, int *nwords); @@ -1600,8 +1679,10 @@ psql_completion(const char *text, int start, int end) /* Clear a few things. */ completion_charp = NULL; completion_charpp = NULL; - completion_info_charp = NULL; - completion_info_charp2 = NULL; + completion_vquery = NULL; + completion_squery = NULL; + completion_ref_object = NULL; + completion_ref_schema = NULL; /* * Scan the input line to extract the words before our current position. @@ -1650,8 +1731,8 @@ psql_completion(const char *text, int start, int end) /* ALTER TABLE */ else if (Matches("ALTER", "TABLE")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, - "UNION SELECT 'ALL IN TABLESPACE'"); + COMPLETE_WITH_SCHEMA_QUERY_PLUS(Query_for_list_of_tables, + "ALL IN TABLESPACE"); /* ALTER something */ else if (Matches("ALTER")) @@ -1695,7 +1776,7 @@ psql_completion(const char *text, int start, int end) else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD|SET", "TABLE") || (HeadMatches("ALTER", "PUBLICATION", MatchAny, "ADD|SET", "TABLE") && ends_with(prev_wd, ','))) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables); else if (HeadMatches("ALTER", "PUBLICATION", MatchAny, "ADD|SET", "TABLE")) COMPLETE_WITH(","); /* ALTER PUBLICATION DROP */ @@ -1705,11 +1786,9 @@ psql_completion(const char *text, int start, int end) else if (Matches("ALTER", "PUBLICATION", MatchAny, "SET")) COMPLETE_WITH("(", "ALL TABLES IN SCHEMA", "TABLE"); else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD|DROP|SET", "ALL", "TABLES", "IN", "SCHEMA")) - COMPLETE_WITH_QUERY(Query_for_list_of_schemas - " AND nspname != 'pg_catalog' " - " AND nspname not like 'pg\\_toast%%' " - " AND nspname not like 'pg\\_temp%%' " - " UNION SELECT 'CURRENT_SCHEMA'"); + COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_schemas + " AND nspname NOT LIKE E'pg\\\\_%'", + "CURRENT_SCHEMA"); /* ALTER PUBLICATION SET ( */ else if (HeadMatches("ALTER", "PUBLICATION", MatchAny) && TailMatches("SET", "(")) COMPLETE_WITH("publish", "publish_via_partition_root"); @@ -1787,15 +1866,15 @@ psql_completion(const char *text, int start, int end) /* ALTER EXTENSION UPDATE */ else if (Matches("ALTER", "EXTENSION", MatchAny, "UPDATE")) { - completion_info_charp = prev2_wd; - COMPLETE_WITH_QUERY(Query_for_list_of_available_extension_versions_with_TO); + set_completion_reference(prev2_wd); + COMPLETE_WITH_QUERY_VERBATIM(Query_for_list_of_available_extension_versions_with_TO); } /* ALTER EXTENSION UPDATE TO */ else if (Matches("ALTER", "EXTENSION", MatchAny, "UPDATE", "TO")) { - completion_info_charp = prev3_wd; - COMPLETE_WITH_QUERY(Query_for_list_of_available_extension_versions); + set_completion_reference(prev3_wd); + COMPLETE_WITH_QUERY_VERBATIM(Query_for_list_of_available_extension_versions); } /* ALTER FOREIGN */ @@ -1817,8 +1896,8 @@ psql_completion(const char *text, int start, int end) /* ALTER INDEX */ else if (Matches("ALTER", "INDEX")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, - "UNION SELECT 'ALL IN TABLESPACE'"); + COMPLETE_WITH_SCHEMA_QUERY_PLUS(Query_for_list_of_indexes, + "ALL IN TABLESPACE"); /* ALTER INDEX */ else if (Matches("ALTER", "INDEX", MatchAny)) COMPLETE_WITH("ALTER COLUMN", "OWNER TO", "RENAME TO", "SET", @@ -1827,15 +1906,15 @@ psql_completion(const char *text, int start, int end) else if (Matches("ALTER", "INDEX", MatchAny, "ATTACH")) COMPLETE_WITH("PARTITION"); else if (Matches("ALTER", "INDEX", MatchAny, "ATTACH", "PARTITION")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes); /* ALTER INDEX ALTER */ else if (Matches("ALTER", "INDEX", MatchAny, "ALTER")) COMPLETE_WITH("COLUMN"); /* ALTER INDEX ALTER COLUMN */ else if (Matches("ALTER", "INDEX", MatchAny, "ALTER", "COLUMN")) { - completion_info_charp = prev3_wd; - COMPLETE_WITH_QUERY(Query_for_list_of_attribute_numbers); + set_completion_reference(prev3_wd); + COMPLETE_WITH_SCHEMA_QUERY_VERBATIM(Query_for_list_of_attribute_numbers); } /* ALTER INDEX ALTER COLUMN */ else if (Matches("ALTER", "INDEX", MatchAny, "ALTER", "COLUMN", MatchAny)) @@ -1884,8 +1963,8 @@ psql_completion(const char *text, int start, int end) /* ALTER MATERIALIZED VIEW */ else if (Matches("ALTER", "MATERIALIZED", "VIEW")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, - "UNION SELECT 'ALL IN TABLESPACE'"); + COMPLETE_WITH_SCHEMA_QUERY_PLUS(Query_for_list_of_matviews, + "ALL IN TABLESPACE"); /* ALTER USER,ROLE */ else if (Matches("ALTER", "USER|ROLE", MatchAny) && @@ -1945,8 +2024,8 @@ psql_completion(const char *text, int start, int end) /* ALTER DOMAIN DROP|RENAME|VALIDATE CONSTRAINT */ else if (Matches("ALTER", "DOMAIN", MatchAny, "DROP|RENAME|VALIDATE", "CONSTRAINT")) { - completion_info_charp = prev3_wd; - COMPLETE_WITH_QUERY(Query_for_constraint_of_type); + set_completion_reference(prev3_wd); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_constraint_of_type); } /* ALTER DOMAIN RENAME */ else if (Matches("ALTER", "DOMAIN", MatchAny, "RENAME")) @@ -1979,7 +2058,8 @@ psql_completion(const char *text, int start, int end) else if (Matches("ALTER", "SYSTEM")) COMPLETE_WITH("SET", "RESET"); else if (Matches("ALTER", "SYSTEM", "SET|RESET")) - COMPLETE_WITH_QUERY(Query_for_list_of_alter_system_set_vars); + COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_alter_system_set_vars, + "all"); else if (Matches("ALTER", "SYSTEM", "SET", MatchAny)) COMPLETE_WITH("TO"); /* ALTER VIEW */ @@ -1988,9 +2068,9 @@ psql_completion(const char *text, int start, int end) "SET SCHEMA"); /* ALTER VIEW xxx RENAME */ else if (Matches("ALTER", "VIEW", MatchAny, "RENAME")) - COMPLETE_WITH_ATTR(prev2_wd, " UNION SELECT 'COLUMN' UNION SELECT 'TO'"); + COMPLETE_WITH_ATTR_PLUS(prev2_wd, "COLUMN", "TO"); else if (Matches("ALTER", "VIEW", MatchAny, "ALTER|RENAME", "COLUMN")) - COMPLETE_WITH_ATTR(prev3_wd, ""); + COMPLETE_WITH_ATTR(prev3_wd); /* ALTER VIEW xxx ALTER [ COLUMN ] yyy */ else if (Matches("ALTER", "VIEW", MatchAny, "ALTER", MatchAny) || Matches("ALTER", "VIEW", MatchAny, "ALTER", "COLUMN", MatchAny)) @@ -2009,9 +2089,9 @@ psql_completion(const char *text, int start, int end) "RESET (", "SET"); /* ALTER MATERIALIZED VIEW xxx RENAME */ else if (Matches("ALTER", "MATERIALIZED", "VIEW", MatchAny, "RENAME")) - COMPLETE_WITH_ATTR(prev2_wd, " UNION SELECT 'COLUMN' UNION SELECT 'TO'"); + COMPLETE_WITH_ATTR_PLUS(prev2_wd, "COLUMN", "TO"); else if (Matches("ALTER", "MATERIALIZED", "VIEW", MatchAny, "ALTER|RENAME", "COLUMN")) - COMPLETE_WITH_ATTR(prev3_wd, ""); + COMPLETE_WITH_ATTR(prev3_wd); /* ALTER MATERIALIZED VIEW xxx RENAME yyy */ else if (Matches("ALTER", "MATERIALIZED", "VIEW", MatchAny, "RENAME", MatchAnyExcept("TO"))) COMPLETE_WITH("TO"); @@ -2030,15 +2110,16 @@ psql_completion(const char *text, int start, int end) /* ALTER POLICY ON */ else if (Matches("ALTER", "POLICY", MatchAny, "ON")) { - completion_info_charp = prev2_wd; - COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_policy); + set_completion_reference(prev2_wd); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables_for_policy); } /* ALTER POLICY ON
- show options */ else if (Matches("ALTER", "POLICY", MatchAny, "ON", MatchAny)) COMPLETE_WITH("RENAME TO", "TO", "USING (", "WITH CHECK ("); /* ALTER POLICY ON
TO */ else if (Matches("ALTER", "POLICY", MatchAny, "ON", MatchAny, "TO")) - COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles); + COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_roles, + Keywords_for_list_of_grant_roles); /* ALTER POLICY ON
USING ( */ else if (Matches("ALTER", "POLICY", MatchAny, "ON", MatchAny, "USING")) COMPLETE_WITH("("); @@ -2053,8 +2134,8 @@ psql_completion(const char *text, int start, int end) /* If we have ALTER RULE ON, then add the correct tablename */ else if (Matches("ALTER", "RULE", MatchAny, "ON")) { - completion_info_charp = prev2_wd; - COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_rule); + set_completion_reference(prev2_wd); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables_for_rule); } /* ALTER RULE ON */ @@ -2069,17 +2150,11 @@ psql_completion(const char *text, int start, int end) else if (Matches("ALTER", "TRIGGER", MatchAny)) COMPLETE_WITH("ON"); - else if (Matches("ALTER", "TRIGGER", MatchAny, MatchAny)) - { - completion_info_charp = prev2_wd; - COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger); - } - - /* - * If we have ALTER TRIGGER ON, then add the correct tablename - */ else if (Matches("ALTER", "TRIGGER", MatchAny, "ON")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL); + { + set_completion_reference(prev2_wd); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables_for_trigger); + } /* ALTER TRIGGER ON */ else if (Matches("ALTER", "TRIGGER", MatchAny, "ON", MatchAny)) @@ -2106,7 +2181,7 @@ psql_completion(const char *text, int start, int end) else if (Matches("ALTER", "TABLE", MatchAny, "ADD", "COLUMN", MatchAny) || (Matches("ALTER", "TABLE", MatchAny, "ADD", MatchAny) && !Matches("ALTER", "TABLE", MatchAny, "ADD", "COLUMN|CONSTRAINT|CHECK|UNIQUE|PRIMARY|EXCLUDE|FOREIGN"))) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes); /* ALTER TABLE xxx ADD CONSTRAINT yyy */ else if (Matches("ALTER", "TABLE", MatchAny, "ADD", "CONSTRAINT", MatchAny)) COMPLETE_WITH("CHECK", "UNIQUE", "PRIMARY KEY", "EXCLUDE", "FOREIGN KEY"); @@ -2119,28 +2194,28 @@ psql_completion(const char *text, int start, int end) /* ALTER TABLE xxx ADD PRIMARY KEY USING INDEX */ else if (Matches("ALTER", "TABLE", MatchAny, "ADD", "PRIMARY", "KEY", "USING", "INDEX")) { - completion_info_charp = prev6_wd; - COMPLETE_WITH_QUERY(Query_for_unique_index_of_table); + set_completion_reference(prev6_wd); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_unique_index_of_table); } /* ALTER TABLE xxx ADD UNIQUE USING INDEX */ else if (Matches("ALTER", "TABLE", MatchAny, "ADD", "UNIQUE", "USING", "INDEX")) { - completion_info_charp = prev5_wd; - COMPLETE_WITH_QUERY(Query_for_unique_index_of_table); + set_completion_reference(prev5_wd); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_unique_index_of_table); } /* ALTER TABLE xxx ADD CONSTRAINT yyy PRIMARY KEY USING INDEX */ else if (Matches("ALTER", "TABLE", MatchAny, "ADD", "CONSTRAINT", MatchAny, "PRIMARY", "KEY", "USING", "INDEX")) { - completion_info_charp = prev8_wd; - COMPLETE_WITH_QUERY(Query_for_unique_index_of_table); + set_completion_reference(prev8_wd); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_unique_index_of_table); } /* ALTER TABLE xxx ADD CONSTRAINT yyy UNIQUE USING INDEX */ else if (Matches("ALTER", "TABLE", MatchAny, "ADD", "CONSTRAINT", MatchAny, "UNIQUE", "USING", "INDEX")) { - completion_info_charp = prev7_wd; - COMPLETE_WITH_QUERY(Query_for_unique_index_of_table); + set_completion_reference(prev7_wd); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_unique_index_of_table); } /* ALTER TABLE xxx ENABLE */ else if (Matches("ALTER", "TABLE", MatchAny, "ENABLE")) @@ -2150,56 +2225,56 @@ psql_completion(const char *text, int start, int end) COMPLETE_WITH("RULE", "TRIGGER"); else if (Matches("ALTER", "TABLE", MatchAny, "ENABLE", "RULE")) { - completion_info_charp = prev3_wd; - COMPLETE_WITH_QUERY(Query_for_rule_of_table); + set_completion_reference(prev3_wd); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_rule_of_table); } else if (Matches("ALTER", "TABLE", MatchAny, "ENABLE", MatchAny, "RULE")) { - completion_info_charp = prev4_wd; - COMPLETE_WITH_QUERY(Query_for_rule_of_table); + set_completion_reference(prev4_wd); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_rule_of_table); } else if (Matches("ALTER", "TABLE", MatchAny, "ENABLE", "TRIGGER")) { - completion_info_charp = prev3_wd; - COMPLETE_WITH_QUERY(Query_for_trigger_of_table); + set_completion_reference(prev3_wd); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_trigger_of_table); } else if (Matches("ALTER", "TABLE", MatchAny, "ENABLE", MatchAny, "TRIGGER")) { - completion_info_charp = prev4_wd; - COMPLETE_WITH_QUERY(Query_for_trigger_of_table); + set_completion_reference(prev4_wd); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_trigger_of_table); } /* ALTER TABLE xxx INHERIT */ else if (Matches("ALTER", "TABLE", MatchAny, "INHERIT")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, ""); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables); /* ALTER TABLE xxx NO */ else if (Matches("ALTER", "TABLE", MatchAny, "NO")) COMPLETE_WITH("FORCE ROW LEVEL SECURITY", "INHERIT"); /* ALTER TABLE xxx NO INHERIT */ else if (Matches("ALTER", "TABLE", MatchAny, "NO", "INHERIT")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, ""); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables); /* ALTER TABLE xxx DISABLE */ else if (Matches("ALTER", "TABLE", MatchAny, "DISABLE")) COMPLETE_WITH("ROW LEVEL SECURITY", "RULE", "TRIGGER"); else if (Matches("ALTER", "TABLE", MatchAny, "DISABLE", "RULE")) { - completion_info_charp = prev3_wd; - COMPLETE_WITH_QUERY(Query_for_rule_of_table); + set_completion_reference(prev3_wd); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_rule_of_table); } else if (Matches("ALTER", "TABLE", MatchAny, "DISABLE", "TRIGGER")) { - completion_info_charp = prev3_wd; - COMPLETE_WITH_QUERY(Query_for_trigger_of_table); + set_completion_reference(prev3_wd); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_trigger_of_table); } /* ALTER TABLE xxx ALTER */ else if (Matches("ALTER", "TABLE", MatchAny, "ALTER")) - COMPLETE_WITH_ATTR(prev2_wd, " UNION SELECT 'COLUMN' UNION SELECT 'CONSTRAINT'"); + COMPLETE_WITH_ATTR_PLUS(prev2_wd, "COLUMN", "CONSTRAINT"); /* ALTER TABLE xxx RENAME */ else if (Matches("ALTER", "TABLE", MatchAny, "RENAME")) - COMPLETE_WITH_ATTR(prev2_wd, " UNION SELECT 'COLUMN' UNION SELECT 'CONSTRAINT' UNION SELECT 'TO'"); + COMPLETE_WITH_ATTR_PLUS(prev2_wd, "COLUMN", "CONSTRAINT", "TO"); else if (Matches("ALTER", "TABLE", MatchAny, "ALTER|RENAME", "COLUMN")) - COMPLETE_WITH_ATTR(prev3_wd, ""); + COMPLETE_WITH_ATTR(prev3_wd); /* ALTER TABLE xxx RENAME yyy */ else if (Matches("ALTER", "TABLE", MatchAny, "RENAME", MatchAnyExcept("CONSTRAINT|TO"))) @@ -2214,18 +2289,18 @@ psql_completion(const char *text, int start, int end) COMPLETE_WITH("COLUMN", "CONSTRAINT"); /* If we have ALTER TABLE DROP COLUMN, provide list of columns */ else if (Matches("ALTER", "TABLE", MatchAny, "DROP", "COLUMN")) - COMPLETE_WITH_ATTR(prev3_wd, ""); + COMPLETE_WITH_ATTR(prev3_wd); /* ALTER TABLE ALTER|DROP|RENAME CONSTRAINT */ else if (Matches("ALTER", "TABLE", MatchAny, "ALTER|DROP|RENAME", "CONSTRAINT")) { - completion_info_charp = prev3_wd; - COMPLETE_WITH_QUERY(Query_for_constraint_of_table); + set_completion_reference(prev3_wd); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_constraint_of_table); } /* ALTER TABLE VALIDATE CONSTRAINT */ else if (Matches("ALTER", "TABLE", MatchAny, "VALIDATE", "CONSTRAINT")) { - completion_info_charp = prev3_wd; - COMPLETE_WITH_QUERY(Query_for_constraint_of_table_not_validated); + set_completion_reference(prev3_wd); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_constraint_of_table_not_validated); } /* ALTER TABLE ALTER [COLUMN] */ else if (Matches("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny) || @@ -2257,8 +2332,8 @@ psql_completion(const char *text, int start, int end) COMPLETE_WITH("ON"); else if (Matches("ALTER", "TABLE", MatchAny, "CLUSTER", "ON")) { - completion_info_charp = prev3_wd; - COMPLETE_WITH_QUERY(Query_for_index_of_table); + set_completion_reference(prev3_wd); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_index_of_table); } /* If we have ALTER TABLE SET, provide list of attributes and '(' */ else if (Matches("ALTER", "TABLE", MatchAny, "SET")) @@ -2289,8 +2364,8 @@ psql_completion(const char *text, int start, int end) COMPLETE_WITH_LIST(table_storage_parameters); else if (Matches("ALTER", "TABLE", MatchAny, "REPLICA", "IDENTITY", "USING", "INDEX")) { - completion_info_charp = prev5_wd; - COMPLETE_WITH_QUERY(Query_for_index_of_table); + set_completion_reference(prev5_wd); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_index_of_table); } else if (Matches("ALTER", "TABLE", MatchAny, "REPLICA", "IDENTITY", "USING")) COMPLETE_WITH("INDEX"); @@ -2304,7 +2379,7 @@ psql_completion(const char *text, int start, int end) * tables. */ else if (Matches("ALTER", "TABLE", MatchAny, "ATTACH", "PARTITION")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, ""); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables); /* Limited completion support for partition bound specification */ else if (TailMatches("ATTACH", "PARTITION", MatchAny)) COMPLETE_WITH("FOR VALUES", "DEFAULT"); @@ -2317,8 +2392,8 @@ psql_completion(const char *text, int start, int end) */ else if (Matches("ALTER", "TABLE", MatchAny, "DETACH", "PARTITION")) { - completion_info_charp = prev3_wd; - COMPLETE_WITH_QUERY(Query_for_partition_of_table); + set_completion_reference(prev3_wd); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_partition_of_table); } else if (Matches("ALTER", "TABLE", MatchAny, "DETACH", "PARTITION", MatchAny)) COMPLETE_WITH("CONCURRENTLY", "FINALIZE"); @@ -2366,7 +2441,7 @@ psql_completion(const char *text, int start, int end) * of attributes */ else if (Matches("ALTER", "TYPE", MatchAny, "ALTER|DROP|RENAME", "ATTRIBUTE")) - COMPLETE_WITH_ATTR(prev3_wd, ""); + COMPLETE_WITH_ATTR(prev3_wd); /* ALTER TYPE ALTER ATTRIBUTE */ else if (Matches("ALTER", "TYPE", MatchAny, "ALTER", "ATTRIBUTE", MatchAny)) COMPLETE_WITH("TYPE"); @@ -2391,8 +2466,8 @@ psql_completion(const char *text, int start, int end) * ANALYZE [ VERBOSE ] [ table_and_columns [, ...] ] */ else if (Matches("ANALYZE")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_analyzables, - " UNION SELECT 'VERBOSE'"); + COMPLETE_WITH_SCHEMA_QUERY_PLUS(Query_for_list_of_analyzables, + "VERBOSE"); else if (HeadMatches("ANALYZE", "(*") && !HeadMatches("ANALYZE", "(*)")) { @@ -2408,9 +2483,9 @@ psql_completion(const char *text, int start, int end) } else if (HeadMatches("ANALYZE") && TailMatches("(")) /* "ANALYZE (" should be caught above, so assume we want columns */ - COMPLETE_WITH_ATTR(prev2_wd, ""); + COMPLETE_WITH_ATTR(prev2_wd); else if (HeadMatches("ANALYZE")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_analyzables, NULL); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_analyzables); /* BEGIN */ else if (Matches("BEGIN")) @@ -2431,19 +2506,20 @@ psql_completion(const char *text, int start, int end) COMPLETE_WITH("CHAIN"); /* CALL */ else if (Matches("CALL")) - COMPLETE_WITH_VERSIONED_SCHEMA_QUERY(Query_for_list_of_procedures, NULL); + COMPLETE_WITH_VERSIONED_SCHEMA_QUERY(Query_for_list_of_procedures); else if (Matches("CALL", MatchAny)) COMPLETE_WITH("("); /* CLOSE */ else if (Matches("CLOSE")) - COMPLETE_WITH_QUERY(Query_for_list_of_cursors - " UNION SELECT 'ALL'"); + COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_cursors, + "ALL"); /* CLUSTER */ else if (Matches("CLUSTER")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_clusterables, "UNION SELECT 'VERBOSE'"); + COMPLETE_WITH_SCHEMA_QUERY_PLUS(Query_for_list_of_clusterables, + "VERBOSE"); else if (Matches("CLUSTER", "VERBOSE") || Matches("CLUSTER", "(*)")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_clusterables, NULL); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_clusterables); /* If we have CLUSTER , then add "USING" */ else if (Matches("CLUSTER", MatchAnyExcept("VERBOSE|ON|(|(*)"))) COMPLETE_WITH("USING"); @@ -2454,8 +2530,8 @@ psql_completion(const char *text, int start, int end) else if (Matches("CLUSTER", MatchAny, "USING") || Matches("CLUSTER", "VERBOSE|(*)", MatchAny, "USING")) { - completion_info_charp = prev2_wd; - COMPLETE_WITH_QUERY(Query_for_index_of_table); + set_completion_reference(prev2_wd); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_index_of_table); } else if (HeadMatches("CLUSTER", "(*") && !HeadMatches("CLUSTER", "(*)")) @@ -2492,28 +2568,28 @@ psql_completion(const char *text, int start, int end) COMPLETE_WITH("ON"); else if (Matches("COMMENT", "ON", "CONSTRAINT", MatchAny, "ON")) { - completion_info_charp = prev2_wd; - COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_constraint - " UNION SELECT 'DOMAIN'"); + set_completion_reference(prev2_wd); + COMPLETE_WITH_SCHEMA_QUERY_PLUS(Query_for_list_of_tables_for_constraint, + "DOMAIN"); } else if (Matches("COMMENT", "ON", "CONSTRAINT", MatchAny, "ON", "DOMAIN")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains, NULL); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains); else if (Matches("COMMENT", "ON", "EVENT", "TRIGGER")) COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers); else if (Matches("COMMENT", "ON", "FOREIGN")) COMPLETE_WITH("DATA WRAPPER", "TABLE"); else if (Matches("COMMENT", "ON", "FOREIGN", "TABLE")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, NULL); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables); else if (Matches("COMMENT", "ON", "MATERIALIZED", "VIEW")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews); else if (Matches("COMMENT", "ON", "POLICY")) COMPLETE_WITH_QUERY(Query_for_list_of_policies); else if (Matches("COMMENT", "ON", "POLICY", MatchAny)) COMPLETE_WITH("ON"); else if (Matches("COMMENT", "ON", "POLICY", MatchAny, "ON")) { - completion_info_charp = prev2_wd; - COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_policy); + set_completion_reference(prev2_wd); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables_for_policy); } else if (Matches("COMMENT", "ON", "PROCEDURAL", "LANGUAGE")) COMPLETE_WITH_QUERY(Query_for_list_of_languages); @@ -2521,34 +2597,34 @@ psql_completion(const char *text, int start, int end) COMPLETE_WITH("ON"); else if (Matches("COMMENT", "ON", "RULE", MatchAny, "ON")) { - completion_info_charp = prev2_wd; - COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_rule); + set_completion_reference(prev2_wd); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables_for_rule); } else if (Matches("COMMENT", "ON", "TEXT", "SEARCH")) COMPLETE_WITH("CONFIGURATION", "DICTIONARY", "PARSER", "TEMPLATE"); else if (Matches("COMMENT", "ON", "TEXT", "SEARCH", "CONFIGURATION")) - COMPLETE_WITH_QUERY(Query_for_list_of_ts_configurations); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_ts_configurations); else if (Matches("COMMENT", "ON", "TEXT", "SEARCH", "DICTIONARY")) - COMPLETE_WITH_QUERY(Query_for_list_of_ts_dictionaries); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_ts_dictionaries); else if (Matches("COMMENT", "ON", "TEXT", "SEARCH", "PARSER")) - COMPLETE_WITH_QUERY(Query_for_list_of_ts_parsers); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_ts_parsers); else if (Matches("COMMENT", "ON", "TEXT", "SEARCH", "TEMPLATE")) - COMPLETE_WITH_QUERY(Query_for_list_of_ts_templates); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_ts_templates); else if (Matches("COMMENT", "ON", "TRANSFORM", "FOR")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes); else if (Matches("COMMENT", "ON", "TRANSFORM", "FOR", MatchAny)) COMPLETE_WITH("LANGUAGE"); else if (Matches("COMMENT", "ON", "TRANSFORM", "FOR", MatchAny, "LANGUAGE")) { - completion_info_charp = prev2_wd; + set_completion_reference(prev2_wd); COMPLETE_WITH_QUERY(Query_for_list_of_languages); } else if (Matches("COMMENT", "ON", "TRIGGER", MatchAny)) COMPLETE_WITH("ON"); else if (Matches("COMMENT", "ON", "TRIGGER", MatchAny, "ON")) { - completion_info_charp = prev2_wd; - COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger); + set_completion_reference(prev2_wd); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables_for_trigger); } else if (Matches("COMMENT", "ON", MatchAny, MatchAnyExcept("IS")) || Matches("COMMENT", "ON", MatchAny, MatchAny, MatchAnyExcept("IS")) || @@ -2563,8 +2639,7 @@ psql_completion(const char *text, int start, int end) * backslash command). */ else if (Matches("COPY|\\copy")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, - " UNION ALL SELECT '('"); + COMPLETE_WITH_SCHEMA_QUERY_PLUS(Query_for_list_of_tables, "("); /* Complete COPY ( with legal query commands */ else if (Matches("COPY|\\copy", "(")) COMPLETE_WITH("SELECT", "TABLE", "VALUES", "INSERT INTO", "UPDATE", "DELETE FROM", "WITH"); @@ -2622,7 +2697,7 @@ psql_completion(const char *text, int start, int end) else if (Matches("CREATE", "COLLATION", MatchAny)) COMPLETE_WITH("(", "FROM"); else if (Matches("CREATE", "COLLATION", MatchAny, "FROM")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_collations, NULL); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_collations); else if (HeadMatches("CREATE", "COLLATION", MatchAny, "(*")) { if (TailMatches("(|*,")) @@ -2648,12 +2723,12 @@ psql_completion(const char *text, int start, int end) else if (Matches("CREATE", "DOMAIN", MatchAny)) COMPLETE_WITH("AS"); else if (Matches("CREATE", "DOMAIN", MatchAny, "AS")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes); else if (Matches("CREATE", "DOMAIN", MatchAny, "AS", MatchAny)) COMPLETE_WITH("COLLATE", "DEFAULT", "CONSTRAINT", "NOT NULL", "NULL", "CHECK ("); else if (Matches("CREATE", "DOMAIN", MatchAny, "COLLATE")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_collations, NULL); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_collations); /* CREATE EXTENSION */ /* Complete with available extensions rather than installed ones. */ @@ -2665,8 +2740,8 @@ psql_completion(const char *text, int start, int end) /* CREATE EXTENSION VERSION */ else if (Matches("CREATE", "EXTENSION", MatchAny, "VERSION")) { - completion_info_charp = prev2_wd; - COMPLETE_WITH_QUERY(Query_for_list_of_available_extension_versions); + set_completion_reference(prev2_wd); + COMPLETE_WITH_QUERY_VERBATIM(Query_for_list_of_available_extension_versions); } /* CREATE FOREIGN */ @@ -2691,9 +2766,8 @@ psql_completion(const char *text, int start, int end) * existing indexes */ else if (TailMatches("CREATE|UNIQUE", "INDEX")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, - " UNION SELECT 'ON'" - " UNION SELECT 'CONCURRENTLY'"); + COMPLETE_WITH_SCHEMA_QUERY_PLUS(Query_for_list_of_indexes, + "ON", "CONCURRENTLY"); /* * Complete ... INDEX|CONCURRENTLY [] ON with a list of relations @@ -2701,15 +2775,15 @@ psql_completion(const char *text, int start, int end) */ else if (TailMatches("INDEX|CONCURRENTLY", MatchAny, "ON") || TailMatches("INDEX|CONCURRENTLY", "ON")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexables, NULL); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexables); /* * Complete CREATE|UNIQUE INDEX CONCURRENTLY with "ON" and existing * indexes */ else if (TailMatches("CREATE|UNIQUE", "INDEX", "CONCURRENTLY")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, - " UNION SELECT 'ON'"); + COMPLETE_WITH_SCHEMA_QUERY_PLUS(Query_for_list_of_indexes, + "ON"); /* Complete CREATE|UNIQUE INDEX [CONCURRENTLY] with "ON" */ else if (TailMatches("CREATE|UNIQUE", "INDEX", MatchAny) || TailMatches("CREATE|UNIQUE", "INDEX", "CONCURRENTLY", MatchAny)) @@ -2724,10 +2798,10 @@ psql_completion(const char *text, int start, int end) COMPLETE_WITH("(", "USING"); else if (TailMatches("INDEX", MatchAny, "ON", MatchAny, "(") || TailMatches("INDEX|CONCURRENTLY", "ON", MatchAny, "(")) - COMPLETE_WITH_ATTR(prev2_wd, ""); + COMPLETE_WITH_ATTR(prev2_wd); /* same if you put in USING */ else if (TailMatches("ON", MatchAny, "USING", MatchAny, "(")) - COMPLETE_WITH_ATTR(prev4_wd, ""); + COMPLETE_WITH_ATTR(prev4_wd); /* Complete USING with an index method */ else if (TailMatches("INDEX", MatchAny, MatchAny, "ON", MatchAny, "USING") || TailMatches("INDEX", MatchAny, "ON", MatchAny, "USING") || @@ -2748,7 +2822,7 @@ psql_completion(const char *text, int start, int end) COMPLETE_WITH("ON"); /* Complete "CREATE POLICY ON
" */ else if (Matches("CREATE", "POLICY", MatchAny, "ON")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables); /* Complete "CREATE POLICY ON
AS|FOR|TO|USING|WITH CHECK" */ else if (Matches("CREATE", "POLICY", MatchAny, "ON", MatchAny)) COMPLETE_WITH("AS", "FOR", "TO", "USING (", "WITH CHECK ("); @@ -2776,7 +2850,8 @@ psql_completion(const char *text, int start, int end) COMPLETE_WITH("TO", "USING (", "WITH CHECK ("); /* Complete "CREATE POLICY ON
TO " */ else if (Matches("CREATE", "POLICY", MatchAny, "ON", MatchAny, "TO")) - COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles); + COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_roles, + Keywords_for_list_of_grant_roles); /* Complete "CREATE POLICY ON
USING (" */ else if (Matches("CREATE", "POLICY", MatchAny, "ON", MatchAny, "USING")) COMPLETE_WITH("("); @@ -2814,7 +2889,8 @@ psql_completion(const char *text, int start, int end) * " */ else if (Matches("CREATE", "POLICY", MatchAny, "ON", MatchAny, "AS", MatchAny, "TO")) - COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles); + COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_roles, + Keywords_for_list_of_grant_roles); /* * Complete "CREATE POLICY ON
AS PERMISSIVE|RESTRICTIVE @@ -2837,18 +2913,16 @@ psql_completion(const char *text, int start, int end) COMPLETE_WITH("WITH ("); /* Complete "CREATE PUBLICATION FOR TABLE" with "
, ..." */ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "TABLE")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables); /* * Complete "CREATE PUBLICATION FOR ALL TABLES IN SCHEMA , * ..." */ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES", "IN", "SCHEMA")) - COMPLETE_WITH_QUERY(Query_for_list_of_schemas - " AND nspname != 'pg_catalog' " - " AND nspname not like 'pg\\_toast%%' " - " AND nspname not like 'pg\\_temp%%' " - " UNION SELECT 'CURRENT_SCHEMA' "); + COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_schemas + " AND nspname NOT LIKE E'pg\\\\_%'", + "CURRENT_SCHEMA"); else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES", "IN", "SCHEMA", MatchAny) && (!ends_with(prev_wd, ','))) COMPLETE_WITH("WITH ("); /* Complete "CREATE PUBLICATION [...] WITH" */ @@ -2877,7 +2951,7 @@ psql_completion(const char *text, int start, int end) COMPLETE_WITH("TO"); /* Complete "AS ON TO" with a table name */ else if (TailMatches("AS", "ON", "SELECT|UPDATE|INSERT|DELETE", "TO")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables); /* CREATE SEQUENCE --- is allowed inside CREATE SCHEMA, so use TailMatches */ else if (TailMatches("CREATE", "SEQUENCE", MatchAny) || @@ -2904,7 +2978,7 @@ psql_completion(const char *text, int start, int end) COMPLETE_WITH("ON"); else if (HeadMatches("CREATE", "STATISTICS", MatchAny) && TailMatches("FROM")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables); /* CREATE TABLE --- is allowed inside CREATE SCHEMA, so use TailMatches */ /* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */ @@ -2918,7 +2992,7 @@ psql_completion(const char *text, int start, int end) COMPLETE_WITH("RANGE (", "LIST (", "HASH ("); /* If we have xxx PARTITION OF, provide a list of partitioned tables */ else if (TailMatches("PARTITION", "OF")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_partitioned_tables, ""); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_partitioned_tables); /* Limited completion support for partition bound specification */ else if (TailMatches("PARTITION", "OF", MatchAny)) COMPLETE_WITH("FOR VALUES", "DEFAULT"); @@ -2929,7 +3003,7 @@ psql_completion(const char *text, int start, int end) /* Complete CREATE TABLE OF with list of composite types */ else if (TailMatches("CREATE", "TABLE", MatchAny, "OF") || TailMatches("CREATE", "TEMP|TEMPORARY|UNLOGGED", "TABLE", MatchAny, "OF")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_composite_datatypes, NULL); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_composite_datatypes); /* Complete CREATE TABLE name (...) with supported options */ else if (TailMatches("CREATE", "TABLE", MatchAny, "(*)") || TailMatches("CREATE", "UNLOGGED", "TABLE", MatchAny, "(*)")) @@ -2968,14 +3042,14 @@ psql_completion(const char *text, int start, int end) COMPLETE_WITH("FOR"); else if (Matches("CREATE", "TRANSFORM", "FOR") || Matches("CREATE", "OR", "REPLACE", "TRANSFORM", "FOR")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes); else if (Matches("CREATE", "TRANSFORM", "FOR", MatchAny) || Matches("CREATE", "OR", "REPLACE", "TRANSFORM", "FOR", MatchAny)) COMPLETE_WITH("LANGUAGE"); else if (Matches("CREATE", "TRANSFORM", "FOR", MatchAny, "LANGUAGE") || Matches("CREATE", "OR", "REPLACE", "TRANSFORM", "FOR", MatchAny, "LANGUAGE")) { - completion_info_charp = prev2_wd; + set_completion_reference(prev2_wd); COMPLETE_WITH_QUERY(Query_for_list_of_languages); } @@ -3036,7 +3110,7 @@ psql_completion(const char *text, int start, int end) */ else if (TailMatches("CREATE", "TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny, "ON") || TailMatches("CREATE", "OR", "REPLACE", "TRIGGER", MatchAny, "BEFORE|AFTER", MatchAny, "ON")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables); /* * Complete CREATE [ OR REPLACE ] TRIGGER ... INSTEAD OF event ON with a @@ -3044,7 +3118,7 @@ psql_completion(const char *text, int start, int end) */ else if (TailMatches("CREATE", "TRIGGER", MatchAny, "INSTEAD", "OF", MatchAny, "ON") || TailMatches("CREATE", "OR", "REPLACE", "TRIGGER", MatchAny, "INSTEAD", "OF", MatchAny, "ON")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views); else if ((HeadMatches("CREATE", "TRIGGER") || HeadMatches("CREATE", "OR", "REPLACE", "TRIGGER")) && TailMatches("ON", MatchAny)) @@ -3149,7 +3223,7 @@ psql_completion(const char *text, int start, int end) else if ((HeadMatches("CREATE", "TRIGGER") || HeadMatches("CREATE", "OR", "REPLACE", "TRIGGER")) && TailMatches("EXECUTE", "FUNCTION|PROCEDURE")) - COMPLETE_WITH_VERSIONED_SCHEMA_QUERY(Query_for_list_of_functions, NULL); + COMPLETE_WITH_VERSIONED_SCHEMA_QUERY(Query_for_list_of_functions); /* CREATE ROLE,USER,GROUP */ else if (Matches("CREATE", "ROLE|GROUP|USER", MatchAny) && @@ -3185,7 +3259,7 @@ psql_completion(const char *text, int start, int end) else if (HeadMatches("CREATE", "TYPE", MatchAny, "AS", "(")) { if (TailMatches("(|*,", MatchAny)) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes); else if (TailMatches("(|*,", MatchAny, MatchAnyExcept("*)"))) COMPLETE_WITH("COLLATE", ",", ")"); } @@ -3269,12 +3343,12 @@ psql_completion(const char *text, int start, int end) } else if (HeadMatches("CREATE", "EVENT", "TRIGGER") && TailMatches("EXECUTE", "FUNCTION|PROCEDURE")) - COMPLETE_WITH_VERSIONED_SCHEMA_QUERY(Query_for_list_of_functions, NULL); + COMPLETE_WITH_VERSIONED_SCHEMA_QUERY(Query_for_list_of_functions); /* DEALLOCATE */ else if (Matches("DEALLOCATE")) - COMPLETE_WITH_QUERY(Query_for_list_of_prepared_statements - " UNION SELECT 'ALL'"); + COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_prepared_statements, + "ALL"); /* DECLARE */ @@ -3322,7 +3396,7 @@ psql_completion(const char *text, int start, int end) COMPLETE_WITH("FROM"); /* Complete DELETE FROM with a list of tables */ else if (TailMatches("DELETE", "FROM")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, NULL); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables); /* Complete DELETE FROM
*/ else if (TailMatches("DELETE", "FROM", MatchAny)) COMPLETE_WITH("USING", "WHERE"); @@ -3364,10 +3438,10 @@ psql_completion(const char *text, int start, int end) /* DROP INDEX */ else if (Matches("DROP", "INDEX")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, - " UNION SELECT 'CONCURRENTLY'"); + COMPLETE_WITH_SCHEMA_QUERY_PLUS(Query_for_list_of_indexes, + "CONCURRENTLY"); else if (Matches("DROP", "INDEX", "CONCURRENTLY")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes); else if (Matches("DROP", "INDEX", MatchAny)) COMPLETE_WITH("CASCADE", "RESTRICT"); else if (Matches("DROP", "INDEX", "CONCURRENTLY", MatchAny)) @@ -3377,7 +3451,7 @@ psql_completion(const char *text, int start, int end) else if (Matches("DROP", "MATERIALIZED")) COMPLETE_WITH("VIEW"); else if (Matches("DROP", "MATERIALIZED", "VIEW")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews); else if (Matches("DROP", "MATERIALIZED", "VIEW", MatchAny)) COMPLETE_WITH("CASCADE", "RESTRICT"); @@ -3398,8 +3472,8 @@ psql_completion(const char *text, int start, int end) COMPLETE_WITH("ON"); else if (Matches("DROP", "TRIGGER", MatchAny, "ON")) { - completion_info_charp = prev2_wd; - COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger); + set_completion_reference(prev2_wd); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables_for_trigger); } else if (Matches("DROP", "TRIGGER", MatchAny, "ON", MatchAny)) COMPLETE_WITH("CASCADE", "RESTRICT"); @@ -3425,8 +3499,8 @@ psql_completion(const char *text, int start, int end) /* DROP POLICY ON
*/ else if (Matches("DROP", "POLICY", MatchAny, "ON")) { - completion_info_charp = prev2_wd; - COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_policy); + set_completion_reference(prev2_wd); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables_for_policy); } else if (Matches("DROP", "POLICY", MatchAny, "ON", MatchAny)) COMPLETE_WITH("CASCADE", "RESTRICT"); @@ -3436,8 +3510,8 @@ psql_completion(const char *text, int start, int end) COMPLETE_WITH("ON"); else if (Matches("DROP", "RULE", MatchAny, "ON")) { - completion_info_charp = prev2_wd; - COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_rule); + set_completion_reference(prev2_wd); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables_for_rule); } else if (Matches("DROP", "RULE", MatchAny, "ON", MatchAny)) COMPLETE_WITH("CASCADE", "RESTRICT"); @@ -3446,12 +3520,12 @@ psql_completion(const char *text, int start, int end) else if (Matches("DROP", "TRANSFORM")) COMPLETE_WITH("FOR"); else if (Matches("DROP", "TRANSFORM", "FOR")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes); else if (Matches("DROP", "TRANSFORM", "FOR", MatchAny)) COMPLETE_WITH("LANGUAGE"); else if (Matches("DROP", "TRANSFORM", "FOR", MatchAny, "LANGUAGE")) { - completion_info_charp = prev2_wd; + set_completion_reference(prev2_wd); COMPLETE_WITH_QUERY(Query_for_list_of_languages); } else if (Matches("DROP", "TRANSFORM", "FOR", MatchAny, "LANGUAGE", MatchAny)) @@ -3500,28 +3574,28 @@ psql_completion(const char *text, int start, int end) * NEXT, PRIOR, FIRST, LAST, FROM, IN, and a list of cursors */ else if (Matches("FETCH|MOVE")) - COMPLETE_WITH_QUERY(Query_for_list_of_cursors - " UNION SELECT 'ABSOLUTE'" - " UNION SELECT 'BACKWARD'" - " UNION SELECT 'FORWARD'" - " UNION SELECT 'RELATIVE'" - " UNION SELECT 'ALL'" - " UNION SELECT 'NEXT'" - " UNION SELECT 'PRIOR'" - " UNION SELECT 'FIRST'" - " UNION SELECT 'LAST'" - " UNION SELECT 'FROM'" - " UNION SELECT 'IN'"); + COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_cursors, + "ABSOLUTE", + "BACKWARD", + "FORWARD", + "RELATIVE", + "ALL", + "NEXT", + "PRIOR", + "FIRST", + "LAST", + "FROM", + "IN"); /* * Complete FETCH BACKWARD or FORWARD with one of ALL, FROM, IN, and a * list of cursors */ else if (Matches("FETCH|MOVE", "BACKWARD|FORWARD")) - COMPLETE_WITH_QUERY(Query_for_list_of_cursors - " UNION SELECT 'ALL'" - " UNION SELECT 'FROM'" - " UNION SELECT 'IN'"); + COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_cursors, + "ALL", + "FROM", + "IN"); /* * Complete FETCH with "FROM" or "IN". These are equivalent, @@ -3531,9 +3605,9 @@ psql_completion(const char *text, int start, int end) else if (Matches("FETCH|MOVE", "ABSOLUTE|BACKWARD|FORWARD|RELATIVE", MatchAnyExcept("FROM|IN")) || Matches("FETCH|MOVE", "ALL|NEXT|PRIOR|FIRST|LAST")) - COMPLETE_WITH_QUERY(Query_for_list_of_cursors - " UNION SELECT 'FROM'" - " UNION SELECT 'IN'"); + COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_cursors, + "FROM", + "IN"); /* Complete FETCH "FROM" or "IN" with a list of cursors */ else if (HeadMatches("FETCH|MOVE") && TailMatches("FROM|IN")) @@ -3552,7 +3626,7 @@ psql_completion(const char *text, int start, int end) /* FOREIGN TABLE */ else if (TailMatches("FOREIGN", "TABLE") && !TailMatches("CREATE", MatchAny, MatchAny)) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, NULL); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables); /* FOREIGN SERVER */ else if (TailMatches("FOREIGN", "SERVER")) @@ -3574,20 +3648,20 @@ psql_completion(const char *text, int start, int end) "DELETE", "TRUNCATE", "REFERENCES", "TRIGGER", "EXECUTE", "USAGE", "ALL"); else - COMPLETE_WITH_QUERY(Query_for_list_of_roles - " UNION SELECT 'SELECT'" - " UNION SELECT 'INSERT'" - " UNION SELECT 'UPDATE'" - " UNION SELECT 'DELETE'" - " UNION SELECT 'TRUNCATE'" - " UNION SELECT 'REFERENCES'" - " UNION SELECT 'TRIGGER'" - " UNION SELECT 'CREATE'" - " UNION SELECT 'CONNECT'" - " UNION SELECT 'TEMPORARY'" - " UNION SELECT 'EXECUTE'" - " UNION SELECT 'USAGE'" - " UNION SELECT 'ALL'"); + COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_roles, + "SELECT", + "INSERT", + "UPDATE", + "DELETE", + "TRUNCATE", + "REFERENCES", + "TRIGGER", + "CREATE", + "CONNECT", + "TEMPORARY", + "EXECUTE", + "USAGE", + "ALL"); } /* @@ -3607,9 +3681,6 @@ psql_completion(const char *text, int start, int end) /* * Complete GRANT/REVOKE ON with a list of appropriate relations. * - * Keywords like DATABASE, FUNCTION, LANGUAGE and SCHEMA added to query - * result via UNION; seems to work intuitively. - * * Note: GRANT/REVOKE can get quite complex; tab-completion as implemented * here will only work if the privilege list contains exactly one * privilege. @@ -3623,26 +3694,26 @@ psql_completion(const char *text, int start, int end) if (HeadMatches("ALTER", "DEFAULT", "PRIVILEGES")) COMPLETE_WITH("TABLES", "SEQUENCES", "FUNCTIONS", "PROCEDURES", "ROUTINES", "TYPES", "SCHEMAS"); else - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_grantables, - " UNION SELECT 'ALL FUNCTIONS IN SCHEMA'" - " UNION SELECT 'ALL PROCEDURES IN SCHEMA'" - " UNION SELECT 'ALL ROUTINES IN SCHEMA'" - " UNION SELECT 'ALL SEQUENCES IN SCHEMA'" - " UNION SELECT 'ALL TABLES IN SCHEMA'" - " UNION SELECT 'DATABASE'" - " UNION SELECT 'DOMAIN'" - " UNION SELECT 'FOREIGN DATA WRAPPER'" - " UNION SELECT 'FOREIGN SERVER'" - " UNION SELECT 'FUNCTION'" - " UNION SELECT 'LANGUAGE'" - " UNION SELECT 'LARGE OBJECT'" - " UNION SELECT 'PROCEDURE'" - " UNION SELECT 'ROUTINE'" - " UNION SELECT 'SCHEMA'" - " UNION SELECT 'SEQUENCE'" - " UNION SELECT 'TABLE'" - " UNION SELECT 'TABLESPACE'" - " UNION SELECT 'TYPE'"); + COMPLETE_WITH_SCHEMA_QUERY_PLUS(Query_for_list_of_grantables, + "ALL FUNCTIONS IN SCHEMA", + "ALL PROCEDURES IN SCHEMA", + "ALL ROUTINES IN SCHEMA", + "ALL SEQUENCES IN SCHEMA", + "ALL TABLES IN SCHEMA", + "DATABASE", + "DOMAIN", + "FOREIGN DATA WRAPPER", + "FOREIGN SERVER", + "FUNCTION", + "LANGUAGE", + "LARGE OBJECT", + "PROCEDURE", + "ROUTINE", + "SCHEMA", + "SEQUENCE", + "TABLE", + "TABLESPACE", + "TYPE"); } else if (TailMatches("GRANT|REVOKE", MatchAny, "ON", "ALL")) COMPLETE_WITH("FUNCTIONS IN SCHEMA", @@ -3664,25 +3735,25 @@ psql_completion(const char *text, int start, int end) if (TailMatches("DATABASE")) COMPLETE_WITH_QUERY(Query_for_list_of_databases); else if (TailMatches("DOMAIN")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains, NULL); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains); else if (TailMatches("FUNCTION")) - COMPLETE_WITH_VERSIONED_SCHEMA_QUERY(Query_for_list_of_functions, NULL); + COMPLETE_WITH_VERSIONED_SCHEMA_QUERY(Query_for_list_of_functions); else if (TailMatches("LANGUAGE")) COMPLETE_WITH_QUERY(Query_for_list_of_languages); else if (TailMatches("PROCEDURE")) - COMPLETE_WITH_VERSIONED_SCHEMA_QUERY(Query_for_list_of_procedures, NULL); + COMPLETE_WITH_VERSIONED_SCHEMA_QUERY(Query_for_list_of_procedures); else if (TailMatches("ROUTINE")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_routines, NULL); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_routines); else if (TailMatches("SCHEMA")) COMPLETE_WITH_QUERY(Query_for_list_of_schemas); else if (TailMatches("SEQUENCE")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences, NULL); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences); else if (TailMatches("TABLE")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_grantables, NULL); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_grantables); else if (TailMatches("TABLESPACE")) COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces); else if (TailMatches("TYPE")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes); else if (TailMatches("GRANT", MatchAny, MatchAny, MatchAny)) COMPLETE_WITH("TO"); else @@ -3695,10 +3766,12 @@ psql_completion(const char *text, int start, int end) */ else if ((HeadMatches("GRANT") && TailMatches("TO")) || (HeadMatches("REVOKE") && TailMatches("FROM"))) - COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles); + COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_roles, + Keywords_for_list_of_grant_roles); /* Complete "ALTER DEFAULT PRIVILEGES ... GRANT/REVOKE ... TO/FROM */ else if (HeadMatches("ALTER", "DEFAULT", "PRIVILEGES") && TailMatches("TO|FROM")) - COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles); + COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_roles, + Keywords_for_list_of_grant_roles); /* Complete "GRANT/REVOKE ... ON * *" with TO/FROM */ else if (HeadMatches("GRANT") && TailMatches("ON", MatchAny, MatchAny)) COMPLETE_WITH("TO"); @@ -3759,10 +3832,10 @@ psql_completion(const char *text, int start, int end) COMPLETE_WITH("INTO"); /* Complete INSERT INTO with table names */ else if (TailMatches("INSERT", "INTO")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, NULL); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables); /* Complete "INSERT INTO
(" with attribute names */ else if (TailMatches("INSERT", "INTO", MatchAny, "(")) - COMPLETE_WITH_ATTR(prev2_wd, ""); + COMPLETE_WITH_ATTR(prev2_wd); /* * Complete INSERT INTO
with "(" or "VALUES" or "SELECT" or @@ -3794,14 +3867,13 @@ psql_completion(const char *text, int start, int end) /* LOCK */ /* Complete LOCK [TABLE] [ONLY] with a list of tables */ else if (Matches("LOCK")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, - " UNION SELECT 'TABLE'" - " UNION SELECT 'ONLY'"); + COMPLETE_WITH_SCHEMA_QUERY_PLUS(Query_for_list_of_tables, + "TABLE", "ONLY"); else if (Matches("LOCK", "TABLE")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, - " UNION SELECT 'ONLY'"); + COMPLETE_WITH_SCHEMA_QUERY_PLUS(Query_for_list_of_tables, + "ONLY"); else if (Matches("LOCK", "TABLE", "ONLY") || Matches("LOCK", "ONLY")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables); /* For the following, handle the case of a single table only for now */ /* Complete LOCK [TABLE] [ONLY]
with IN or NOWAIT */ @@ -3837,7 +3909,7 @@ psql_completion(const char *text, int start, int end) /* NOTIFY --- can be inside EXPLAIN, RULE, etc */ else if (TailMatches("NOTIFY")) - COMPLETE_WITH_QUERY("SELECT pg_catalog.quote_ident(channel) FROM pg_catalog.pg_listening_channels() AS channel WHERE substring(pg_catalog.quote_ident(channel),1,%d)='%s'"); + COMPLETE_WITH_QUERY(Query_for_list_of_channels); /* OPTIONS */ else if (TailMatches("OPTIONS")) @@ -3851,7 +3923,7 @@ psql_completion(const char *text, int start, int end) else if (TailMatches("FROM", MatchAny, "ORDER")) COMPLETE_WITH("BY"); else if (TailMatches("FROM", MatchAny, "ORDER", "BY")) - COMPLETE_WITH_ATTR(prev3_wd, ""); + COMPLETE_WITH_ATTR(prev3_wd); /* PREPARE xx AS */ else if (Matches("PREPARE", MatchAny, "AS")) @@ -3880,10 +3952,10 @@ psql_completion(const char *text, int start, int end) else if (Matches("REFRESH", "MATERIALIZED")) COMPLETE_WITH("VIEW"); else if (Matches("REFRESH", "MATERIALIZED", "VIEW")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, - " UNION SELECT 'CONCURRENTLY'"); + COMPLETE_WITH_SCHEMA_QUERY_PLUS(Query_for_list_of_matviews, + "CONCURRENTLY"); else if (Matches("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews); else if (Matches("REFRESH", "MATERIALIZED", "VIEW", MatchAny)) COMPLETE_WITH("WITH"); else if (Matches("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY", MatchAny)) @@ -3903,26 +3975,26 @@ psql_completion(const char *text, int start, int end) COMPLETE_WITH("TABLE", "INDEX", "SYSTEM", "SCHEMA", "DATABASE"); else if (Matches("REINDEX", "TABLE") || Matches("REINDEX", "(*)", "TABLE")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexables, - " UNION SELECT 'CONCURRENTLY'"); + COMPLETE_WITH_SCHEMA_QUERY_PLUS(Query_for_list_of_indexables, + "CONCURRENTLY"); else if (Matches("REINDEX", "INDEX") || Matches("REINDEX", "(*)", "INDEX")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, - " UNION SELECT 'CONCURRENTLY'"); + COMPLETE_WITH_SCHEMA_QUERY_PLUS(Query_for_list_of_indexes, + "CONCURRENTLY"); else if (Matches("REINDEX", "SCHEMA") || Matches("REINDEX", "(*)", "SCHEMA")) - COMPLETE_WITH_QUERY(Query_for_list_of_schemas - " UNION SELECT 'CONCURRENTLY'"); + COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_schemas, + "CONCURRENTLY"); else if (Matches("REINDEX", "SYSTEM|DATABASE") || Matches("REINDEX", "(*)", "SYSTEM|DATABASE")) - COMPLETE_WITH_QUERY(Query_for_list_of_databases - " UNION SELECT 'CONCURRENTLY'"); + COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_databases, + "CONCURRENTLY"); else if (Matches("REINDEX", "TABLE", "CONCURRENTLY") || Matches("REINDEX", "(*)", "TABLE", "CONCURRENTLY")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexables, NULL); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexables); else if (Matches("REINDEX", "INDEX", "CONCURRENTLY") || Matches("REINDEX", "(*)", "INDEX", "CONCURRENTLY")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes); else if (Matches("REINDEX", "SCHEMA", "CONCURRENTLY") || Matches("REINDEX", "(*)", "SCHEMA", "CONCURRENTLY")) COMPLETE_WITH_QUERY(Query_for_list_of_schemas); @@ -3966,9 +4038,17 @@ psql_completion(const char *text, int start, int end) /* SET, RESET, SHOW */ /* Complete with a variable name */ else if (TailMatches("SET|RESET") && !TailMatches("UPDATE", MatchAny, "SET")) - COMPLETE_WITH_QUERY(Query_for_list_of_set_vars); + COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_set_vars, + "constraints", + "transaction", + "session", + "role", + "tablespace", + "all"); else if (Matches("SHOW")) - COMPLETE_WITH_QUERY(Query_for_list_of_show_vars); + COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_show_vars, + "session authorization", + "all"); /* Complete "SET TRANSACTION" */ else if (Matches("SET", "TRANSACTION")) COMPLETE_WITH("SNAPSHOT", "ISOLATION LEVEL", "READ", "DEFERRABLE", "NOT DEFERRABLE"); @@ -4003,7 +4083,8 @@ psql_completion(const char *text, int start, int end) COMPLETE_WITH("ONLY", "WRITE"); /* SET CONSTRAINTS */ else if (Matches("SET", "CONSTRAINTS")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_constraints_with_schema, "UNION SELECT 'ALL'"); + COMPLETE_WITH_SCHEMA_QUERY_PLUS(Query_for_list_of_constraints_with_schema, + "ALL"); /* Complete SET CONSTRAINTS with DEFERRED|IMMEDIATE */ else if (Matches("SET", "CONSTRAINTS", MatchAny)) COMPLETE_WITH("DEFERRED", "IMMEDIATE"); @@ -4015,7 +4096,8 @@ psql_completion(const char *text, int start, int end) COMPLETE_WITH("AUTHORIZATION", "CHARACTERISTICS AS TRANSACTION"); /* Complete SET SESSION AUTHORIZATION with username */ else if (Matches("SET", "SESSION", "AUTHORIZATION")) - COMPLETE_WITH_QUERY(Query_for_list_of_roles " UNION SELECT 'DEFAULT'"); + COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_roles, + "DEFAULT"); /* Complete RESET SESSION with AUTHORIZATION */ else if (Matches("RESET", "SESSION")) COMPLETE_WITH("AUTHORIZATION"); @@ -4045,10 +4127,13 @@ psql_completion(const char *text, int start, int end) "US", "European", "NonEuropean", "DEFAULT"); else if (TailMatches("search_path", "TO|=")) - COMPLETE_WITH_QUERY(Query_for_list_of_schemas - " AND nspname not like 'pg\\_toast%%' " - " AND nspname not like 'pg\\_temp%%' " - " UNION SELECT 'DEFAULT' "); + { + /* Here, we want to allow pg_catalog, so use narrower exclusion */ + COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_schemas + " AND nspname NOT LIKE E'pg\\\\_toast%%'" + " AND nspname NOT LIKE E'pg\\\\_temp%%'", + "DEFAULT"); + } else { /* generic, type based, GUC support */ @@ -4063,11 +4148,9 @@ psql_completion(const char *text, int start, int end) { if (strcmp(guctype, "enum") == 0) { - char querybuf[1024]; - - snprintf(querybuf, sizeof(querybuf), - Query_for_enum, prev2_wd); - COMPLETE_WITH_QUERY(querybuf); + set_completion_reference(prev2_wd); + COMPLETE_WITH_QUERY_PLUS(Query_for_values_of_enum_GUC, + "DEFAULT"); } else if (strcmp(guctype, "bool") == 0) COMPLETE_WITH("on", "off", "true", "false", "yes", "no", @@ -4086,7 +4169,7 @@ psql_completion(const char *text, int start, int end) /* TABLE, but not TABLE embedded in other commands */ else if (Matches("TABLE")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_selectables, NULL); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_selectables); /* TABLESAMPLE */ else if (TailMatches("TABLESAMPLE")) @@ -4096,14 +4179,13 @@ psql_completion(const char *text, int start, int end) /* TRUNCATE */ else if (Matches("TRUNCATE")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_truncatables, - " UNION SELECT 'TABLE'" - " UNION SELECT 'ONLY'"); + COMPLETE_WITH_SCHEMA_QUERY_PLUS(Query_for_list_of_truncatables, + "TABLE", "ONLY"); else if (Matches("TRUNCATE", "TABLE")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_truncatables, - " UNION SELECT 'ONLY'"); + COMPLETE_WITH_SCHEMA_QUERY_PLUS(Query_for_list_of_truncatables, + "ONLY"); else if (HeadMatches("TRUNCATE") && TailMatches("ONLY")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_truncatables, NULL); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_truncatables); else if (Matches("TRUNCATE", MatchAny) || Matches("TRUNCATE", "TABLE|ONLY", MatchAny) || Matches("TRUNCATE", "TABLE", "ONLY", MatchAny)) @@ -4113,18 +4195,18 @@ psql_completion(const char *text, int start, int end) /* UNLISTEN */ else if (Matches("UNLISTEN")) - COMPLETE_WITH_QUERY("SELECT pg_catalog.quote_ident(channel) FROM pg_catalog.pg_listening_channels() AS channel WHERE substring(pg_catalog.quote_ident(channel),1,%d)='%s' UNION SELECT '*'"); + COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_channels, "*"); /* UPDATE --- can be inside EXPLAIN, RULE, etc */ /* If prev. word is UPDATE suggest a list of tables */ else if (TailMatches("UPDATE")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables, NULL); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_updatables); /* Complete UPDATE
with "SET" */ else if (TailMatches("UPDATE", MatchAny)) COMPLETE_WITH("SET"); /* Complete UPDATE
SET with list of attributes */ else if (TailMatches("UPDATE", MatchAny, "SET")) - COMPLETE_WITH_ATTR(prev2_wd, ""); + COMPLETE_WITH_ATTR(prev2_wd); /* UPDATE
SET = */ else if (TailMatches("UPDATE", MatchAny, "SET", MatchAnyExcept("*="))) COMPLETE_WITH("="); @@ -4133,11 +4215,11 @@ psql_completion(const char *text, int start, int end) else if (Matches("ALTER|CREATE|DROP", "USER", "MAPPING")) COMPLETE_WITH("FOR"); else if (Matches("CREATE", "USER", "MAPPING", "FOR")) - COMPLETE_WITH_QUERY(Query_for_list_of_roles - " UNION SELECT 'CURRENT_ROLE'" - " UNION SELECT 'CURRENT_USER'" - " UNION SELECT 'PUBLIC'" - " UNION SELECT 'USER'"); + COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_roles, + "CURRENT_ROLE", + "CURRENT_USER", + "PUBLIC", + "USER"); else if (Matches("ALTER|DROP", "USER", "MAPPING", "FOR")) COMPLETE_WITH_QUERY(Query_for_list_of_user_mappings); else if (Matches("CREATE|ALTER|DROP", "USER", "MAPPING", "FOR", MatchAny)) @@ -4150,26 +4232,26 @@ psql_completion(const char *text, int start, int end) * VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ table_and_columns [, ...] ] */ else if (Matches("VACUUM")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_vacuumables, - " UNION SELECT 'FULL'" - " UNION SELECT 'FREEZE'" - " UNION SELECT 'ANALYZE'" - " UNION SELECT 'VERBOSE'"); + COMPLETE_WITH_SCHEMA_QUERY_PLUS(Query_for_list_of_vacuumables, + "FULL", + "FREEZE", + "ANALYZE", + "VERBOSE"); else if (Matches("VACUUM", "FULL")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_vacuumables, - " UNION SELECT 'FREEZE'" - " UNION SELECT 'ANALYZE'" - " UNION SELECT 'VERBOSE'"); + COMPLETE_WITH_SCHEMA_QUERY_PLUS(Query_for_list_of_vacuumables, + "FREEZE", + "ANALYZE", + "VERBOSE"); else if (Matches("VACUUM", "FREEZE") || Matches("VACUUM", "FULL", "FREEZE")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_vacuumables, - " UNION SELECT 'VERBOSE'" - " UNION SELECT 'ANALYZE'"); + COMPLETE_WITH_SCHEMA_QUERY_PLUS(Query_for_list_of_vacuumables, + "VERBOSE", + "ANALYZE"); else if (Matches("VACUUM", "VERBOSE") || Matches("VACUUM", "FULL|FREEZE", "VERBOSE") || Matches("VACUUM", "FULL", "FREEZE", "VERBOSE")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_vacuumables, - " UNION SELECT 'ANALYZE'"); + COMPLETE_WITH_SCHEMA_QUERY_PLUS(Query_for_list_of_vacuumables, + "ANALYZE"); else if (HeadMatches("VACUUM", "(*") && !HeadMatches("VACUUM", "(*)")) { @@ -4190,9 +4272,9 @@ psql_completion(const char *text, int start, int end) } else if (HeadMatches("VACUUM") && TailMatches("(")) /* "VACUUM (" should be caught above, so assume we want columns */ - COMPLETE_WITH_ATTR(prev2_wd, ""); + COMPLETE_WITH_ATTR(prev2_wd); else if (HeadMatches("VACUUM")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_vacuumables, NULL); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_vacuumables); /* WITH [RECURSIVE] */ @@ -4206,16 +4288,16 @@ psql_completion(const char *text, int start, int end) /* WHERE */ /* Simple case of the word before the where being the table name */ else if (TailMatches(MatchAny, "WHERE")) - COMPLETE_WITH_ATTR(prev2_wd, ""); + COMPLETE_WITH_ATTR(prev2_wd); /* ... FROM ... */ /* TODO: also include SRF ? */ else if (TailMatches("FROM") && !Matches("COPY|\\copy", MatchAny, "FROM")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_selectables, NULL); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_selectables); /* ... JOIN ... */ else if (TailMatches("JOIN")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_selectables, NULL); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_selectables); /* Backslash commands */ /* TODO: \dc \dd \dl */ @@ -4232,19 +4314,19 @@ psql_completion(const char *text, int start, int end) COMPLETE_WITH_QUERY(Query_for_list_of_roles); } else if (TailMatchesCS("\\da*")) - COMPLETE_WITH_VERSIONED_SCHEMA_QUERY(Query_for_list_of_aggregates, NULL); + COMPLETE_WITH_VERSIONED_SCHEMA_QUERY(Query_for_list_of_aggregates); else if (TailMatchesCS("\\dAc*", MatchAny) || TailMatchesCS("\\dAf*", MatchAny)) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes); else if (TailMatchesCS("\\dAo*", MatchAny) || TailMatchesCS("\\dAp*", MatchAny)) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_operator_families, NULL); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_operator_families); else if (TailMatchesCS("\\dA*")) COMPLETE_WITH_QUERY(Query_for_list_of_access_methods); else if (TailMatchesCS("\\db*")) COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces); else if (TailMatchesCS("\\dD*")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains, NULL); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_domains); else if (TailMatchesCS("\\des*")) COMPLETE_WITH_QUERY(Query_for_list_of_servers); else if (TailMatchesCS("\\deu*")) @@ -4252,69 +4334,69 @@ psql_completion(const char *text, int start, int end) else if (TailMatchesCS("\\dew*")) COMPLETE_WITH_QUERY(Query_for_list_of_fdws); else if (TailMatchesCS("\\df*")) - COMPLETE_WITH_VERSIONED_SCHEMA_QUERY(Query_for_list_of_functions, NULL); + COMPLETE_WITH_VERSIONED_SCHEMA_QUERY(Query_for_list_of_functions); else if (HeadMatchesCS("\\df*")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes); else if (TailMatchesCS("\\dFd*")) - COMPLETE_WITH_QUERY(Query_for_list_of_ts_dictionaries); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_ts_dictionaries); else if (TailMatchesCS("\\dFp*")) - COMPLETE_WITH_QUERY(Query_for_list_of_ts_parsers); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_ts_parsers); else if (TailMatchesCS("\\dFt*")) - COMPLETE_WITH_QUERY(Query_for_list_of_ts_templates); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_ts_templates); /* must be at end of \dF alternatives: */ else if (TailMatchesCS("\\dF*")) - COMPLETE_WITH_QUERY(Query_for_list_of_ts_configurations); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_ts_configurations); else if (TailMatchesCS("\\di*")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes); else if (TailMatchesCS("\\dL*")) COMPLETE_WITH_QUERY(Query_for_list_of_languages); else if (TailMatchesCS("\\dn*")) COMPLETE_WITH_QUERY(Query_for_list_of_schemas); /* no support for completing operators, but we can complete types: */ else if (HeadMatchesCS("\\do*", MatchAny)) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes); else if (TailMatchesCS("\\dp") || TailMatchesCS("\\z")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_grantables, NULL); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_grantables); else if (TailMatchesCS("\\dPi*")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_partitioned_indexes, NULL); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_partitioned_indexes); else if (TailMatchesCS("\\dPt*")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_partitioned_tables, NULL); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_partitioned_tables); else if (TailMatchesCS("\\dP*")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_partitioned_relations, NULL); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_partitioned_relations); else if (TailMatchesCS("\\ds*")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences, NULL); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences); else if (TailMatchesCS("\\dt*")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables); else if (TailMatchesCS("\\dT*")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes); else if (TailMatchesCS("\\du*") || TailMatchesCS("\\dg*")) COMPLETE_WITH_QUERY(Query_for_list_of_roles); else if (TailMatchesCS("\\dv*")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views); else if (TailMatchesCS("\\dx*")) COMPLETE_WITH_QUERY(Query_for_list_of_extensions); else if (TailMatchesCS("\\dX*")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_statistics, NULL); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_statistics); else if (TailMatchesCS("\\dm*")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews); else if (TailMatchesCS("\\dE*")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables, NULL); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_foreign_tables); else if (TailMatchesCS("\\dy*")) COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers); /* must be at end of \d alternatives: */ else if (TailMatchesCS("\\d*")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_relations, NULL); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_relations); else if (TailMatchesCS("\\ef")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_routines, NULL); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_routines); else if (TailMatchesCS("\\ev")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views); else if (TailMatchesCS("\\encoding")) - COMPLETE_WITH_QUERY(Query_for_list_of_encodings); + COMPLETE_WITH_QUERY_VERBATIM(Query_for_list_of_encodings); else if (TailMatchesCS("\\h|\\help")) COMPLETE_WITH_LIST(sql_commands); else if (TailMatchesCS("\\h|\\help", MatchAny)) @@ -4413,9 +4495,9 @@ psql_completion(const char *text, int start, int end) COMPLETE_WITH_CS("default", "verbose", "terse", "sqlstate"); } else if (TailMatchesCS("\\sf*")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_routines, NULL); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_routines); else if (TailMatchesCS("\\sv*")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views); else if (TailMatchesCS("\\cd|\\e|\\edit|\\g|\\gx|\\i|\\include|" "\\ir|\\include_relative|\\o|\\out|" "\\s|\\w|\\write|\\lo_import")) @@ -4432,19 +4514,21 @@ psql_completion(const char *text, int start, int end) */ else { - int i; + const pgsql_thing_t *wac; - for (i = 0; words_after_create[i].name; i++) + for (wac = words_after_create; wac->name != NULL; wac++) { - if (pg_strcasecmp(prev_wd, words_after_create[i].name) == 0) + if (pg_strcasecmp(prev_wd, wac->name) == 0) { - if (words_after_create[i].query) - COMPLETE_WITH_QUERY(words_after_create[i].query); - else if (words_after_create[i].vquery) - COMPLETE_WITH_VERSIONED_QUERY(words_after_create[i].vquery); - else if (words_after_create[i].squery) - COMPLETE_WITH_VERSIONED_SCHEMA_QUERY(words_after_create[i].squery, - NULL); + if (wac->query) + COMPLETE_WITH_QUERY_LIST(wac->query, + wac->keywords); + else if (wac->vquery) + COMPLETE_WITH_VERSIONED_QUERY_LIST(wac->vquery, + wac->keywords); + else if (wac->squery) + COMPLETE_WITH_VERSIONED_SCHEMA_QUERY_LIST(wac->squery, + wac->keywords); break; } } @@ -4471,6 +4555,12 @@ psql_completion(const char *text, int start, int end) free(previous_words); free(words_buffer); free(text_copy); + if (completion_ref_object) + free(completion_ref_object); + completion_ref_object = NULL; + if (completion_ref_schema) + free(completion_ref_schema); + completion_ref_schema = NULL; /* Return our Grand List O' Matches */ return matches; @@ -4557,7 +4647,8 @@ static char * complete_from_query(const char *text, int state) { /* query is assumed to work for any server version */ - return _complete_from_query(completion_charp, NULL, text, state); + return _complete_from_query(completion_charp, NULL, completion_charpp, + completion_verbatim, text, state); } static char * @@ -4572,22 +4663,22 @@ complete_from_versioned_query(const char *text, int state) if (vquery->query == NULL) return NULL; - return _complete_from_query(vquery->query, NULL, text, state); + return _complete_from_query(vquery->query, NULL, completion_charpp, + completion_verbatim, text, state); } static char * complete_from_schema_query(const char *text, int state) { /* query is assumed to work for any server version */ - return _complete_from_query(completion_charp, completion_squery, - text, state); + return _complete_from_query(NULL, completion_squery, completion_charpp, + completion_verbatim, text, state); } static char * complete_from_versioned_schema_query(const char *text, int state) { const SchemaQuery *squery = completion_squery; - const VersionedQuery *vquery = completion_vquery; /* Find appropriate array element */ while (pset.sversion < squery->min_server_version) @@ -4596,17 +4687,8 @@ complete_from_versioned_schema_query(const char *text, int state) if (squery->catname == NULL) return NULL; - /* Likewise for the add-on text, if any */ - if (vquery) - { - while (pset.sversion < vquery->min_server_version) - vquery++; - if (vquery->query == NULL) - return NULL; - } - - return _complete_from_query(vquery ? vquery->query : NULL, - squery, text, state); + return _complete_from_query(NULL, squery, completion_charpp, + completion_verbatim, text, state); } @@ -4617,35 +4699,54 @@ complete_from_versioned_schema_query(const char *text, int state) * * 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. + * 1. A simple query, which must contain a restriction clause of the form + * output LIKE '%s' + * where "output" is the same string that the query returns. The %s + * will be replaced by a LIKE pattern to match the already-typed text. + * There can be a second '%s', which will be replaced by a suitably-escaped + * version of the string provided in completion_ref_object. If there is a + * third '%s', it will be replaced by a suitably-escaped version of the string + * provided in completion_ref_schema. Those strings should be set up + * by calling set_completion_reference(). + * Simple queries should return a single column of matches. If "verbatim" + * is true, the matches are returned as-is; otherwise, they are taken to + * be SQL identifiers and quoted if necessary. * * 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. - * - * If both simple_query and schema_query are non-NULL, then we construct - * a schema query and append the (uninterpreted) string simple_query to it. - * - * It is assumed that strings should be escaped to become SQL literals - * (that is, what is in the query is actually ... '%s' ...) + * This is represented by a SchemaQuery object; see that typedef for details. * * See top of file for examples of both kinds of query. * - * "text" and "state" are supplied by readline. + * In addition to the query itself, we accept a null-terminated array of + * literal keywords, which will be returned if they match the input-so-far + * (case insensitively). (These are in addition to keywords specified + * within the schema_query, if any.) + * + * If "verbatim" is true, then we use the given text as-is to match the + * query results; otherwise we parse it as a possibly-qualified identifier, + * and reconstruct suitable quoting afterward. + * + * "text" and "state" are supplied by Readline. "text" is the word we are + * trying to complete. "state" is zero on first call, nonzero later. + * + * readline will call this repeatedly with the same text and varying + * state. On each call, we are supposed to return a malloc'd string + * that is a candidate completion. Return NULL when done. */ static char * _complete_from_query(const char *simple_query, const SchemaQuery *schema_query, + const char *const *keywords, + bool verbatim, const char *text, int state) { static int list_index, - byte_length; + num_schema_only, + num_other; static PGresult *result = NULL; + static bool non_empty_object; + static bool schemaquoted; + static bool objectquoted; /* * If this is the first time for this completion, we fetch a list of our @@ -4654,175 +4755,341 @@ _complete_from_query(const char *simple_query, if (state == 0) { PQExpBufferData query_buffer; - char *e_text; - char *e_info_charp; - char *e_info_charp2; - const char *pstr = text; - int char_length = 0; + char *schemaname; + char *objectname; + char *e_object_like; + char *e_schemaname; + char *e_ref_object; + char *e_ref_schema; + /* Reset static state, ensuring no memory leaks */ list_index = 0; - byte_length = strlen(text); - - /* - * Count length as number of characters (not bytes), for passing to - * substring - */ - while (*pstr) - { - char_length++; - pstr += PQmblenBounded(pstr, pset.encoding); - } - - /* Free any prior result */ + num_schema_only = 0; + num_other = 0; PQclear(result); result = NULL; - /* Set up suitably-escaped copies of textual inputs */ - e_text = escape_string(text); - - if (completion_info_charp) - e_info_charp = escape_string(completion_info_charp); + /* Parse text, splitting into schema and object name if needed */ + if (verbatim) + { + objectname = pg_strdup(text); + schemaname = NULL; + } else - e_info_charp = NULL; + { + parse_identifier(text, + &schemaname, &objectname, + &schemaquoted, &objectquoted); + } - if (completion_info_charp2) - e_info_charp2 = escape_string(completion_info_charp2); + /* Remember whether the user has typed anything in the object part */ + non_empty_object = (*objectname != '\0'); + + /* + * Convert objectname to a LIKE prefix pattern (e.g. 'foo%'), and set + * up suitably-escaped copies of all the strings we need. + */ + e_object_like = make_like_pattern(objectname); + + if (schemaname) + e_schemaname = escape_string(schemaname); else - e_info_charp2 = NULL; + e_schemaname = NULL; + + if (completion_ref_object) + e_ref_object = escape_string(completion_ref_object); + else + e_ref_object = NULL; + + if (completion_ref_schema) + e_ref_schema = escape_string(completion_ref_schema); + else + e_ref_schema = NULL; initPQExpBuffer(&query_buffer); if (schema_query) { - /* schema_query gives us the pieces to assemble */ - const char *qualresult = schema_query->qualresult; - - if (qualresult == NULL) - qualresult = schema_query->result; - - /* Get unqualified names matching the input-so-far */ - appendPQExpBuffer(&query_buffer, "SELECT %s FROM %s WHERE ", - schema_query->result, - schema_query->catname); - if (schema_query->selcondition) - appendPQExpBuffer(&query_buffer, "%s AND ", - schema_query->selcondition); - appendPQExpBuffer(&query_buffer, "substring(%s,1,%d)='%s'", - schema_query->result, - char_length, e_text); - appendPQExpBuffer(&query_buffer, " AND %s", - schema_query->viscondition); + Assert(simple_query == NULL); /* - * 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". + * We issue different queries depending on whether the input is + * already qualified or not. schema_query gives us the pieces to + * assemble. */ - if (strcmp(schema_query->catname, - "pg_catalog.pg_class c") == 0 && - strncmp(text, "pg_", 3) != 0) + if (schemaname == NULL || schema_query->namespace == NULL) { - appendPQExpBufferStr(&query_buffer, - " AND c.relnamespace <> (SELECT oid FROM" - " pg_catalog.pg_namespace WHERE nspname = 'pg_catalog')"); + /* Get unqualified names matching the input-so-far */ + appendPQExpBufferStr(&query_buffer, "SELECT "); + if (schema_query->use_distinct) + appendPQExpBufferStr(&query_buffer, "DISTINCT "); + appendPQExpBuffer(&query_buffer, + "%s, NULL::pg_catalog.text FROM %s", + schema_query->result, + schema_query->catname); + if (schema_query->refnamespace && completion_ref_schema) + appendPQExpBufferStr(&query_buffer, + ", pg_catalog.pg_namespace nr"); + appendPQExpBufferStr(&query_buffer, " WHERE "); + if (schema_query->selcondition) + appendPQExpBuffer(&query_buffer, "%s AND ", + schema_query->selcondition); + appendPQExpBuffer(&query_buffer, "(%s) LIKE '%s'", + schema_query->result, + e_object_like); + if (schema_query->viscondition) + appendPQExpBuffer(&query_buffer, " AND %s", + schema_query->viscondition); + if (schema_query->refname) + { + Assert(completion_ref_object); + appendPQExpBuffer(&query_buffer, " AND %s = '%s'", + schema_query->refname, e_ref_object); + if (schema_query->refnamespace && completion_ref_schema) + appendPQExpBuffer(&query_buffer, + " AND %s = nr.oid AND nr.nspname = '%s'", + schema_query->refnamespace, + e_ref_schema); + else if (schema_query->refviscondition) + appendPQExpBuffer(&query_buffer, + " AND %s", + schema_query->refviscondition); + } + + /* + * 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(schema_query->catname, + "pg_catalog.pg_class c") == 0 && + strncmp(objectname, "pg_", 3) != 0) + { + appendPQExpBufferStr(&query_buffer, + " AND c.relnamespace <> (SELECT oid FROM" + " pg_catalog.pg_namespace WHERE nspname = 'pg_catalog')"); + } + + /* + * If the target object type can be schema-qualified, add in + * schema names matching the input-so-far. + */ + if (schema_query->namespace) + { + appendPQExpBuffer(&query_buffer, "\nUNION ALL\n" + "SELECT NULL::pg_catalog.text, n.nspname " + "FROM pg_catalog.pg_namespace n " + "WHERE n.nspname LIKE '%s'", + e_object_like); + + /* + * Likewise, suppress system schemas unless the + * input-so-far begins with "pg_". + */ + if (strncmp(objectname, "pg_", 3) != 0) + appendPQExpBufferStr(&query_buffer, + " AND n.nspname NOT LIKE E'pg\\\\_%'"); + + /* + * Since we're matching these schema names to the object + * name, handle their quoting using the object name's + * quoting state. + */ + schemaquoted = objectquoted; + } + } + else + { + /* Input is qualified, so produce only qualified names */ + appendPQExpBufferStr(&query_buffer, "SELECT "); + if (schema_query->use_distinct) + appendPQExpBufferStr(&query_buffer, "DISTINCT "); + appendPQExpBuffer(&query_buffer, "%s, n.nspname " + "FROM %s, pg_catalog.pg_namespace n", + schema_query->result, + schema_query->catname); + if (schema_query->refnamespace && completion_ref_schema) + appendPQExpBufferStr(&query_buffer, + ", pg_catalog.pg_namespace nr"); + appendPQExpBuffer(&query_buffer, " WHERE %s = n.oid AND ", + schema_query->namespace); + if (schema_query->selcondition) + appendPQExpBuffer(&query_buffer, "%s AND ", + schema_query->selcondition); + appendPQExpBuffer(&query_buffer, "(%s) LIKE '%s' AND ", + schema_query->result, + e_object_like); + appendPQExpBuffer(&query_buffer, "n.nspname = '%s'", + e_schemaname); + if (schema_query->refname) + { + Assert(completion_ref_object); + appendPQExpBuffer(&query_buffer, " AND %s = '%s'", + schema_query->refname, e_ref_object); + if (schema_query->refnamespace && completion_ref_schema) + appendPQExpBuffer(&query_buffer, + " AND %s = nr.oid AND nr.nspname = '%s'", + schema_query->refnamespace, + e_ref_schema); + else if (schema_query->refviscondition) + appendPQExpBuffer(&query_buffer, + " AND %s", + schema_query->refviscondition); + } } - - /* - * 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'", - char_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", - char_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, - schema_query->catname, - schema_query->namespace); - if (schema_query->selcondition) - appendPQExpBuffer(&query_buffer, "%s AND ", - schema_query->selcondition); - appendPQExpBuffer(&query_buffer, "substring(pg_catalog.quote_ident(n.nspname) || '.' || %s,1,%d)='%s'", - qualresult, - char_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)", - char_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", - char_length, e_text); - - /* If an addon query was provided, use it */ - if (simple_query) - appendPQExpBuffer(&query_buffer, "\n%s", simple_query); } else { Assert(simple_query); /* simple_query is an sprintf-style format string */ appendPQExpBuffer(&query_buffer, simple_query, - char_length, e_text, - e_info_charp, e_info_charp, - e_info_charp2, e_info_charp2); + e_object_like, + e_ref_object, e_ref_schema); } /* Limit the number of records in the result */ appendPQExpBuffer(&query_buffer, "\nLIMIT %d", completion_max_records); + /* Finally, we can issue the query */ result = exec_query(query_buffer.data); + /* Clean up */ termPQExpBuffer(&query_buffer); - free(e_text); - if (e_info_charp) - free(e_info_charp); - if (e_info_charp2) - free(e_info_charp2); + free(e_object_like); + if (e_schemaname) + free(e_schemaname); + if (e_ref_object) + free(e_ref_object); + if (e_ref_schema) + free(e_ref_schema); } - /* Find something that matches */ + /* Return the next result, if any, but not if the query failed */ if (result && PQresultStatus(result) == PGRES_TUPLES_OK) { - const char *item; + int nskip; - while (list_index < PQntuples(result) && - (item = PQgetvalue(result, list_index++, 0))) - if (pg_strncasecmp(text, item, byte_length) == 0) + while (list_index < PQntuples(result)) + { + const char *item = NULL; + const char *nsp = NULL; + + if (!PQgetisnull(result, list_index, 0)) + item = PQgetvalue(result, list_index, 0); + if (PQnfields(result) > 1 && + !PQgetisnull(result, list_index, 1)) + nsp = PQgetvalue(result, list_index, 1); + list_index++; + + /* In verbatim mode, we return all the items as-is */ + if (verbatim) return pg_strdup(item); + + /* + * In normal mode, a name requiring quoting will be returned only + * if the input was empty or quoted. Otherwise the user might see + * completion inserting a quote she didn't type, which is + * surprising. This restriction also dodges some odd behaviors of + * some versions of readline/libedit. + */ + if (non_empty_object) + { + if (item && !objectquoted && identifier_needs_quotes(item)) + continue; + if (nsp && !schemaquoted && identifier_needs_quotes(nsp)) + continue; + } + + /* Count schema-only results for hack below */ + if (item == NULL && nsp != NULL) + num_schema_only++; + else + num_other++; + + return requote_identifier(nsp, item, schemaquoted, objectquoted); + } + + /* + * When the query result is exhausted, check for hard-wired keywords. + * These will only be returned if they match the input-so-far, + * ignoring case. + */ + nskip = list_index - PQntuples(result); + if (schema_query && schema_query->keywords) + { + const char *const *itemp = schema_query->keywords; + + while (*itemp) + { + const char *item = *itemp++; + + if (nskip-- > 0) + continue; + list_index++; + if (pg_strncasecmp(text, item, strlen(text)) == 0) + { + num_other++; + return pg_strdup(item); + } + } + } + if (keywords) + { + const char *const *itemp = keywords; + + while (*itemp) + { + const char *item = *itemp++; + + if (nskip-- > 0) + continue; + list_index++; + if (pg_strncasecmp(text, item, strlen(text)) == 0) + { + num_other++; + return pg_strdup(item); + } + } + } } - /* If nothing matches, free the db structure and return null */ + /* + * Hack: if we returned only bare schema names, don't let Readline add a + * space afterwards. Otherwise the schema will stop being part of the + * completion subject text, which is not what we want. + */ +#ifdef HAVE_RL_COMPLETION_APPEND_CHARACTER + if (num_schema_only > 0 && num_other == 0) + rl_completion_append_character = '\0'; +#endif + + /* No more matches, so free the result structure and return null */ PQclear(result); result = NULL; return NULL; } +/* + * Set up completion_ref_object and completion_ref_schema + * by parsing the given word. These variables can then be + * used in a query passed to _complete_from_query. + */ +static void +set_completion_reference(const char *word) +{ + bool schemaquoted, + objectquoted; + + parse_identifier(word, + &completion_ref_schema, &completion_ref_object, + &schemaquoted, &objectquoted); +} + + /* * 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 @@ -5151,8 +5418,272 @@ escape_string(const char *text) /* - * Execute a query and report any errors. This should be the preferred way of - * talking to the database in this file. + * make_like_pattern - Convert argument to a LIKE prefix pattern. + * + * We escape _ and % in the given text by backslashing, append a % to + * represent "any subsequent characters", and then pass the string through + * escape_string() so it's ready to insert in a query. The result needs + * to be freed. + */ +static char * +make_like_pattern(const char *word) +{ + char *result; + char *buffer = pg_malloc(strlen(word) * 2 + 2); + char *bptr = buffer; + + while (*word) + { + if (*word == '_' || *word == '%') + *bptr++ = '\\'; + if (IS_HIGHBIT_SET(*word)) + { + /* + * Transfer multibyte characters without further processing, to + * avoid getting confused in unsafe client encodings. + */ + int chlen = PQmblenBounded(word, pset.encoding); + + while (chlen-- > 0) + *bptr++ = *word++; + } + else + *bptr++ = *word++; + } + *bptr++ = '%'; + *bptr = '\0'; + + result = escape_string(buffer); + free(buffer); + return result; +} + + +/* + * parse_identifier - Parse a possibly-schema-qualified SQL identifier. + * + * This involves splitting off the schema name if present, de-quoting, + * and downcasing any unquoted text. We are a bit laxer than the backend + * in that we allow just portions of a name to be quoted --- that's because + * psql metacommands have traditionally behaved that way. + * + * Outputs are a malloc'd schema name (NULL if none), malloc'd object name, + * and booleans telling whether any part of the schema and object name was + * double-quoted. + */ +static void +parse_identifier(const char *ident, + char **schemaname, char **objectname, + bool *schemaquoted, bool *objectquoted) +{ + size_t buflen = strlen(ident) + 1; + bool enc_is_single_byte = (pg_encoding_max_length(pset.encoding) == 1); + char *sname; + char *oname; + char *optr; + bool inquotes; + + /* Initialize, making a certainly-large-enough output buffer */ + sname = NULL; + oname = pg_malloc(buflen); + *schemaquoted = *objectquoted = false; + /* Scan */ + optr = oname; + inquotes = false; + while (*ident) + { + unsigned char ch = (unsigned char) *ident++; + + if (ch == '"') + { + if (inquotes && *ident == '"') + { + /* two quote marks within a quoted identifier = emit quote */ + *optr++ = '"'; + ident++; + } + else + { + inquotes = !inquotes; + *objectquoted = true; + } + } + else if (ch == '.' && !inquotes) + { + /* Found a schema name, transfer it to sname / *schemaquoted */ + *optr = '\0'; + free(sname); /* drop any catalog name */ + sname = oname; + oname = pg_malloc(buflen); + optr = oname; + *schemaquoted = *objectquoted; + *objectquoted = false; + } + else if (!enc_is_single_byte && IS_HIGHBIT_SET(ch)) + { + /* + * Transfer multibyte characters without further processing. They + * wouldn't be affected by our downcasing rule anyway, and this + * avoids possibly doing the wrong thing in unsafe client + * encodings. + */ + int chlen = PQmblenBounded(ident - 1, pset.encoding); + + *optr++ = (char) ch; + while (--chlen > 0) + *optr++ = *ident++; + } + else + { + if (!inquotes) + { + /* + * This downcasing transformation should match the backend's + * downcase_identifier() as best we can. We do not know the + * backend's locale, though, so it's necessarily approximate. + * We assume that psql is operating in the same locale and + * encoding as the backend. + */ + if (ch >= 'A' && ch <= 'Z') + ch += 'a' - 'A'; + else if (enc_is_single_byte && IS_HIGHBIT_SET(ch) && isupper(ch)) + ch = tolower(ch); + } + *optr++ = (char) ch; + } + } + + *optr = '\0'; + *schemaname = sname; + *objectname = oname; +} + + +/* + * requote_identifier - Reconstruct a possibly-schema-qualified SQL identifier. + * + * Build a malloc'd string containing the identifier, with quoting applied + * as necessary. This is more or less the inverse of parse_identifier; + * in particular, if an input component was quoted, we'll quote the output + * even when that isn't strictly required. + * + * Unlike parse_identifier, we handle the case where a schema and no + * object name is provided, producing just "schema.". + */ +static char * +requote_identifier(const char *schemaname, const char *objectname, + bool quote_schema, bool quote_object) +{ + char *result; + size_t buflen = 1; /* count the trailing \0 */ + char *ptr; + + /* + * We could use PQescapeIdentifier for some of this, but not all, and it + * adds more notational cruft than it seems worth. + */ + if (schemaname) + { + buflen += strlen(schemaname) + 1; /* +1 for the dot */ + if (!quote_schema) + quote_schema = identifier_needs_quotes(schemaname); + if (quote_schema) + { + buflen += 2; /* account for quote marks */ + for (const char *p = schemaname; *p; p++) + { + if (*p == '"') + buflen++; + } + } + } + if (objectname) + { + buflen += strlen(objectname); + if (!quote_object) + quote_object = identifier_needs_quotes(objectname); + if (quote_object) + { + buflen += 2; /* account for quote marks */ + for (const char *p = objectname; *p; p++) + { + if (*p == '"') + buflen++; + } + } + } + result = pg_malloc(buflen); + ptr = result; + if (schemaname) + { + if (quote_schema) + *ptr++ = '"'; + for (const char *p = schemaname; *p; p++) + { + *ptr++ = *p; + if (*p == '"') + *ptr++ = '"'; + } + if (quote_schema) + *ptr++ = '"'; + *ptr++ = '.'; + } + if (objectname) + { + if (quote_object) + *ptr++ = '"'; + for (const char *p = objectname; *p; p++) + { + *ptr++ = *p; + if (*p == '"') + *ptr++ = '"'; + } + if (quote_object) + *ptr++ = '"'; + } + *ptr = '\0'; + return result; +} + + +/* + * Detect whether an identifier must be double-quoted. + * + * Note we'll quote anything that's not ASCII; the backend's quote_ident() + * does the same. Perhaps this could be relaxed in future. + */ +static bool +identifier_needs_quotes(const char *ident) +{ + int kwnum; + + /* Check syntax. */ + if (!((ident[0] >= 'a' && ident[0] <= 'z') || ident[0] == '_')) + return true; + if (strspn(ident, "abcdefghijklmnopqrstuvwxyz0123456789_") != strlen(ident)) + return true; + + /* + * Check for keyword. We quote keywords except for unreserved ones. + * + * It is possible that our keyword list doesn't quite agree with the + * server's, but this should be close enough for tab-completion purposes. + * + * Note: ScanKeywordLookup() does case-insensitive comparison, but that's + * fine, since we already know we have all-lower-case. + */ + kwnum = ScanKeywordLookup(ident, &ScanKeywords); + + if (kwnum >= 0 && ScanKeywordCategories[kwnum] != UNRESERVED_KEYWORD) + return true; + + return false; +} + + +/* + * Execute a query, returning NULL if there was any error. + * This should be the preferred way of talking to the database in this file. */ static PGresult * exec_query(const char *query) @@ -5166,6 +5697,11 @@ exec_query(const char *query) if (PQresultStatus(result) != PGRES_TUPLES_OK) { + /* + * Printing an error while the user is typing would be quite annoying, + * so we don't. This does complicate debugging of this code; but you + * can look in the server log instead. + */ #ifdef NOT_USED pg_log_error("tab completion query failed: %s\nQuery was:\n%s", PQerrorMessage(pset.db), query);