Re: Support tab completion for upper character inputs in psql - Mailing list pgsql-hackers
From | Tom Lane |
---|---|
Subject | Re: Support tab completion for upper character inputs in psql |
Date | |
Msg-id | 2805502.1643405157@sss.pgh.pa.us Whole thread Raw |
In response to | Re: Support tab completion for upper character inputs in psql (Tom Lane <tgl@sss.pgh.pa.us>) |
Responses |
Re: Support tab completion for upper character inputs in psql
|
List | pgsql-hackers |
I wrote: > It's certainly arguable that the first case is right as-is and we > shouldn't change it. I think that could be handled by tweaking my > patch so that it wouldn't offer completions that start with a quote > unless the input word does. That would also cause I<TAB> to complete > immediately to id, which is arguably fine. Here's a patch series that does it like that. I have to admit that after playing with it, this is probably better. There's less magic-looking behavior involved, and it lets me drop an ugly hack I had to work around a case where Readline didn't want to play along. 0001 also cleans up one oversight in the previous version, which is to beware of multibyte characters in parse_identifier(). I'm not sure there is any actual hazard there, since we weren't looking for backslashes, but it's better to be sure. I added the keyword handling I'd left out before, too. 0002-0004 are largely as before. I've also added 0005, which changes the prefix-matching clauses in the SQL queries from "substring(foo,1,%d)='%s'" to "foo LIKE '%s'". This simplifies reading the queries a little bit, but the real reason to do it is that the planner can optimize the catalog searches a lot better. It knows a lot about LIKE prefix queries and exactly nothing about substring(). For example, DROP TYPE foo<TAB> now produces a query like this: explain SELECT t.typname, NULL::pg_catalog.text FROM pg_catalog.pg_type t WHERE (t.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid)) AND t.typname !~ '^_' AND (t.typname) LIKE 'foo%' AND pg_catalog.pg_type_is_visible(t.oid); QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------ Index Scan using pg_type_typname_nsp_index on pg_type t (cost=0.28..16.63 rows=1 width=96) Index Cond: ((typname >= 'foo'::text) AND (typname < 'fop'::text)) Filter: ((typname !~ '^_'::text) AND (typname ~~ 'foo%'::text) AND pg_type_is_visible(oid) AND ((typrelid = '0'::oid)OR (SubPlan 1))) SubPlan 1 -> Index Scan using pg_class_oid_index on pg_class c (cost=0.28..8.30 rows=1 width=1) Index Cond: (oid = t.typrelid) (6 rows) where before you got a seqscan: explain SELECT pg_catalog.format_type(t.oid, NULL) FROM pg_catalog.pg_type t WHERE (t.typrelid = 0 OR (SELECT c.relkind= 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid)) AND t.typname !~ '^_' AND substring(pg_catalog.format_type(t.oid,NULL),1,3)='foo' AND pg_catalog.pg_type_is_visible(t.oid); QUERY PLAN ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Seq Scan on pg_type t (cost=0.00..16691.86 rows=1 width=32) Filter: ((typname !~ '^_'::text) AND ("substring"(format_type(oid, NULL::integer), 1, 3) = 'foo'::text) AND pg_type_is_visible(oid)AND ((typrelid = '0'::oid) OR (SubPlan 1))) SubPlan 1 -> Index Scan using pg_class_oid_index on pg_class c (cost=0.28..8.30 rows=1 width=1) Index Cond: (oid = t.typrelid) (5 rows) Again, while these queries only have to run at human speed, that doesn't mean it's okay to be wasteful. I seem to recall hearing complaints that they are noticeably slow in installations with many thousand tables, too. This should help. regards, tom lane diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c index 4c62e7b1b4..16999dd9a2 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" @@ -148,16 +150,16 @@ typedef struct SchemaQuery 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. + * 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 *qualresult; + const char *const *keywords; } SchemaQuery; @@ -179,6 +181,7 @@ 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 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 +193,87 @@ 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_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,7 +307,10 @@ 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; \ @@ -266,18 +323,26 @@ do { \ false, false, pset.encoding); \ if (_completion_table == NULL) \ { \ - completion_charp = Query_for_list_of_attributes addon; \ + completion_charp = Query_for_list_of_attributes; \ completion_info_charp = relation; \ } \ else \ { \ - completion_charp = Query_for_list_of_attributes_with_schema addon; \ + completion_charp = Query_for_list_of_attributes_with_schema; \ completion_info_charp = _completion_table; \ completion_info_charp2 = _completion_schema; \ } \ + completion_charpp = list; \ + completion_verbatim = false; \ matches = rl_completion_matches(text, complete_from_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) + /* * libedit will typically include the literal's leading single quote in * "text", while readline will not. Adapt our offered strings to fit. @@ -315,6 +380,8 @@ do { \ completion_info_charp = _completion_type; \ completion_info_charp2 = _completion_schema; \ } \ + completion_charpp = NULL; \ + completion_verbatim = true; \ matches = rl_completion_matches(text, complete_from_query); \ } while (0) @@ -340,6 +407,8 @@ do { \ completion_info_charp = _completion_function; \ completion_info_charp2 = _completion_schema; \ } \ + completion_charpp = NULL; \ + completion_verbatim = true; \ matches = rl_completion_matches(text, complete_from_query); \ } while (0) @@ -357,17 +426,45 @@ 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 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 +474,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 +486,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 +494,7 @@ 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", }; /* Note: this intentionally accepts aggregates as well as plain functions */ @@ -409,13 +505,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 +522,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 +534,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 +542,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 +550,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 +560,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 +568,7 @@ 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_views = { @@ -480,7 +576,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 +584,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 +594,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 +602,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 +611,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 +621,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 +641,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 +656,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 +668,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 +684,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 +696,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,7 +713,7 @@ 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 = { @@ -625,14 +721,14 @@ static const SchemaQuery Query_for_list_of_constraints_with_schema = { .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,7 +736,7 @@ 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", }; @@ -659,13 +755,13 @@ static const SchemaQuery Query_for_list_of_collations = { */ #define Query_for_list_of_attributes \ -"SELECT pg_catalog.quote_ident(attname) "\ +"SELECT 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' "\ +" AND substring(attname,1,%d)='%s' "\ +" AND (relname='%s' "\ " OR '\"' || relname || '\"'='%s') "\ " AND pg_catalog.pg_table_is_visible(c.oid)" @@ -676,21 +772,21 @@ static const SchemaQuery Query_for_list_of_collations = { " AND a.attnum > 0 "\ " AND NOT a.attisdropped "\ " AND substring(attnum::pg_catalog.text,1,%d)='%s' "\ -" AND (pg_catalog.quote_ident(relname)='%s' "\ +" AND (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) "\ +"SELECT 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' "\ +" AND substring(attname,1,%d)='%s' "\ +" AND (relname='%s' "\ " OR '\"' || relname || '\"' ='%s') "\ -" AND (pg_catalog.quote_ident(nspname)='%s' "\ +" AND (nspname='%s' "\ " OR '\"' || nspname || '\"' ='%s') " #define Query_for_list_of_enum_values_quoted \ @@ -698,7 +794,7 @@ static const SchemaQuery Query_for_list_of_collations = { " 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' "\ +" AND (typname='%s' "\ " OR '\"' || typname || '\"'='%s') "\ " AND pg_catalog.pg_type_is_visible(t.oid)" @@ -707,7 +803,7 @@ static const SchemaQuery Query_for_list_of_collations = { " 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' "\ +" AND (typname='%s' "\ " OR '\"' || typname || '\"'='%s') "\ " AND pg_catalog.pg_type_is_visible(t.oid)" @@ -717,9 +813,9 @@ static const SchemaQuery Query_for_list_of_collations = { " 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' "\ +" AND (typname='%s' "\ " OR '\"' || typname || '\"'='%s') "\ -" AND (pg_catalog.quote_ident(nspname)='%s' "\ +" AND (nspname='%s' "\ " OR '\"' || nspname || '\"' ='%s') " #define Query_for_list_of_enum_values_with_schema_unquoted \ @@ -728,24 +824,24 @@ static const SchemaQuery Query_for_list_of_collations = { " WHERE t.oid = e.enumtypid "\ " AND n.oid = t.typnamespace "\ " AND substring(enumlabel,1,%d)='%s' "\ -" AND (pg_catalog.quote_ident(typname)='%s' "\ +" AND (typname='%s' "\ " OR '\"' || typname || '\"'='%s') "\ -" AND (pg_catalog.quote_ident(nspname)='%s' "\ +" AND (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 substring(d.datname,1,%d)='%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 substring(datname,1,%d)='%s'" #define Query_for_list_of_tablespaces \ -"SELECT pg_catalog.quote_ident(spcname) FROM pg_catalog.pg_tablespace "\ -" WHERE substring(pg_catalog.quote_ident(spcname),1,%d)='%s'" +"SELECT spcname FROM pg_catalog.pg_tablespace "\ +" WHERE substring(spcname,1,%d)='%s'" #define Query_for_list_of_encodings \ " SELECT DISTINCT pg_catalog.pg_encoding_to_char(conforencoding) "\ @@ -753,287 +849,260 @@ static const SchemaQuery Query_for_list_of_collations = { " WHERE substring(pg_catalog.pg_encoding_to_char(conforencoding),1,%d)=UPPER('%s')" #define Query_for_list_of_languages \ -"SELECT pg_catalog.quote_ident(lanname) "\ +"SELECT lanname "\ " FROM pg_catalog.pg_language "\ " WHERE lanname != 'internal' "\ -" AND substring(pg_catalog.quote_ident(lanname),1,%d)='%s'" +" AND substring(lanname,1,%d)='%s'" #define Query_for_list_of_schemas \ -"SELECT pg_catalog.quote_ident(nspname) FROM pg_catalog.pg_namespace "\ -" WHERE substring(pg_catalog.quote_ident(nspname),1,%d)='%s'" +"SELECT nspname FROM pg_catalog.pg_namespace "\ +" WHERE substring(nspname,1,%d)='%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 "\ +" ) ss "\ " WHERE substring(name,1,%d)='%s'" #define Query_for_list_of_set_vars \ "SELECT name FROM "\ " (SELECT pg_catalog.lower(name) AS name FROM pg_catalog.pg_settings "\ " WHERE context IN ('user', 'superuser') "\ -" UNION ALL SELECT 'constraints' "\ -" UNION ALL SELECT 'transaction' "\ -" UNION ALL SELECT 'session' "\ -" UNION ALL SELECT 'role' "\ -" UNION ALL SELECT 'tablespace' "\ -" UNION ALL SELECT 'all') ss "\ +" ) ss "\ " WHERE substring(name,1,%d)='%s'" #define Query_for_list_of_show_vars \ "SELECT name FROM "\ " (SELECT pg_catalog.lower(name) AS name FROM pg_catalog.pg_settings "\ -" UNION ALL SELECT 'session authorization' "\ -" UNION ALL SELECT 'all') ss "\ +" ) ss "\ " WHERE substring(name,1,%d)='%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 substring(rolname,1,%d)='%s'" -#define Query_for_list_of_grant_roles \ -" SELECT pg_catalog.quote_ident(rolname) "\ -" FROM pg_catalog.pg_roles "\ -" WHERE substring(pg_catalog.quote_ident(rolname),1,%d)='%s'"\ -" UNION ALL SELECT 'PUBLIC'"\ -" UNION ALL SELECT 'CURRENT_ROLE'"\ -" UNION ALL SELECT 'CURRENT_USER'"\ -" UNION ALL SELECT 'SESSION_USER'" +/* 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" -/* 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) "\ +"SELECT 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 substring(c2.relname,1,%d)='%s'"\ +" and 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) "\ +"SELECT 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'"\ +" WHERE c1.oid=conrelid and substring(conname,1,%d)='%s'"\ +" and 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)" \ +Query_for_constraint_of_table \ " and not con.convalidated" #define Query_for_all_table_constraints \ -"SELECT pg_catalog.quote_ident(conname) "\ +"SELECT conname "\ " FROM pg_catalog.pg_constraint c "\ -" WHERE c.conrelid <> 0 " +" WHERE c.conrelid <> 0 "\ +" and substring(conname,1,%d)='%s'" -/* 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) "\ +"SELECT 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'"\ +" WHERE t.oid=contypid and substring(conname,1,%d)='%s'"\ +" and 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) "\ +"SELECT relname "\ " FROM pg_catalog.pg_class"\ -" WHERE (%d = pg_catalog.length('%s'))"\ +" WHERE substring(relname,1,%d)='%s'"\ " AND oid IN "\ " (SELECT conrelid FROM pg_catalog.pg_constraint "\ -" WHERE pg_catalog.quote_ident(conname)='%s')" +" WHERE 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) "\ +"SELECT 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'"\ +" WHERE c1.oid=ev_class and substring(rulename,1,%d)='%s'"\ +" and 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) "\ +"SELECT relname "\ " FROM pg_catalog.pg_class"\ -" WHERE (%d = pg_catalog.length('%s'))"\ +" WHERE substring(relname,1,%d)='%s'"\ " AND oid IN "\ " (SELECT ev_class FROM pg_catalog.pg_rewrite "\ -" WHERE pg_catalog.quote_ident(rulename)='%s')" +" WHERE 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) "\ +"SELECT 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'"\ +" WHERE c1.oid=tgrelid and substring(tgname,1,%d)='%s'"\ +" and 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) "\ +"SELECT relname "\ " FROM pg_catalog.pg_class"\ -" WHERE (%d = pg_catalog.length('%s'))"\ +" WHERE substring(relname,1,%d)='%s'"\ " AND oid IN "\ " (SELECT tgrelid FROM pg_catalog.pg_trigger "\ -" WHERE pg_catalog.quote_ident(tgname)='%s')" +" WHERE 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'" +"SELECT cfgname FROM pg_catalog.pg_ts_config "\ +" WHERE substring(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'" +"SELECT dictname FROM pg_catalog.pg_ts_dict "\ +" WHERE substring(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'" +"SELECT prsname FROM pg_catalog.pg_ts_parser "\ +" WHERE substring(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'" +"SELECT tmplname FROM pg_catalog.pg_ts_template "\ +" WHERE substring(tmplname,1,%d)='%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 substring(fdwname,1,%d)='%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 substring(srvname,1,%d)='%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 substring(usename,1,%d)='%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 substring(amname,1,%d)='%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 substring(amname,1,%d)='%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 substring(amname,1,%d)='%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'"\ +" WHERE substring(pg_catalog.oidvectortypes(proargtypes)||')',1,%d)='%s'"\ +" AND (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'))"\ +" WHERE substring(pg_catalog.oidvectortypes(proargtypes)||')',1,%d)='%s'"\ " AND n.oid = p.pronamespace "\ -" AND (pg_catalog.quote_ident(proname)='%s' "\ +" AND (proname='%s' "\ " OR '\"' || proname || '\"' ='%s') "\ -" AND (pg_catalog.quote_ident(nspname)='%s' "\ +" AND (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 substring(extname,1,%d)='%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 substring(name,1,%d)='%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 substring(version,1,%d)='%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 substring('TO ' || version,1,%d)='%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 substring(name,1,%d)='%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 substring(evtname,1,%d)='%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'" +" substring(proname,1,%d)='%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 substring(polname,1,%d)='%s'" #define Query_for_list_of_tables_for_policy \ -"SELECT pg_catalog.quote_ident(relname) "\ +"SELECT relname "\ " FROM pg_catalog.pg_class"\ -" WHERE (%d = pg_catalog.length('%s'))"\ +" WHERE substring(relname,1,%d)='%s'"\ " AND oid IN "\ " (SELECT polrelid FROM pg_catalog.pg_policy "\ -" WHERE pg_catalog.quote_ident(polname)='%s')" +" WHERE polname='%s')" #define Query_for_enum \ " SELECT name FROM ( "\ -" SELECT pg_catalog.quote_ident(pg_catalog.unnest(enumvals)) AS name "\ +" SELECT pg_catalog.unnest(enumvals) AS name "\ " FROM pg_catalog.pg_settings "\ " WHERE pg_catalog.lower(name)=pg_catalog.lower('%s') "\ -" UNION ALL " \ -" SELECT 'DEFAULT' ) ss "\ +" ) ss "\ " WHERE pg_catalog.substring(name,1,%%d)='%%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) "\ +"SELECT 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 substring(c2.relname,1,%d)='%s'"\ +" and c1.relname='%s'"\ " and pg_catalog.pg_table_is_visible(c2.oid)"\ " and c2.relispartition = 'true'" #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 substring(name,1,%d)='%s'" /* * These object types were introduced later than our support cutoff of @@ -1043,18 +1112,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 substring(pubname,1,%d)='%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 substring(s.subname,1,%d)='%s' " " AND d.datname = pg_catalog.current_database() " " AND s.subdbid = d.oid" }, @@ -1069,9 +1138,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 +1151,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 +1168,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", Query_for_list_of_ts_configurations, NULL, NULL, NULL, THING_NO_SHOW}, + {"CONVERSION", "SELECT conname FROM pg_catalog.pg_conversion WHERE substring(conname,1,%d)='%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", Query_for_list_of_ts_dictionaries, NULL, NULL, 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 +1182,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", Query_for_list_of_ts_parsers, NULL, NULL, 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 substring(rulename,1,%d)='%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", Query_for_list_of_ts_templates, NULL, NULL, 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 substring(tgname,1,%d)='%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,6 +1277,8 @@ 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 char *complete_from_list(const char *text, int state); static char *complete_from_const(const char *text, int state); @@ -1212,6 +1291,12 @@ 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 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); @@ -1650,8 +1735,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")) @@ -1699,11 +1784,11 @@ 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 != 'pg_catalog' " + " AND nspname not like 'pg\\_toast%%' " + " AND nspname not like 'pg\\_temp%%' ", + "CURRENT_SCHEMA"); /* ALTER PUBLICATION <name> SET ( */ else if (HeadMatches("ALTER", "PUBLICATION", MatchAny) && TailMatches("SET", "(")) COMPLETE_WITH("publish", "publish_via_partition_root"); @@ -1782,14 +1867,14 @@ psql_completion(const char *text, int start, int end) else if (Matches("ALTER", "EXTENSION", MatchAny, "UPDATE")) { completion_info_charp = prev2_wd; - COMPLETE_WITH_QUERY(Query_for_list_of_available_extension_versions_with_TO); + COMPLETE_WITH_QUERY_VERBATIM(Query_for_list_of_available_extension_versions_with_TO); } /* ALTER EXTENSION <name> 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); + COMPLETE_WITH_QUERY_VERBATIM(Query_for_list_of_available_extension_versions); } /* ALTER FOREIGN */ @@ -1811,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 <name> */ else if (Matches("ALTER", "INDEX", MatchAny)) COMPLETE_WITH("ALTER COLUMN", "OWNER TO", "RENAME TO", "SET", @@ -1821,7 +1906,7 @@ 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 <name> ALTER */ else if (Matches("ALTER", "INDEX", MatchAny, "ALTER")) COMPLETE_WITH("COLUMN"); @@ -1878,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 <name> */ else if (Matches("ALTER", "USER|ROLE", MatchAny) && @@ -1973,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 <name> */ @@ -1982,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)) @@ -2003,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"); @@ -2032,7 +2118,8 @@ psql_completion(const char *text, int start, int end) COMPLETE_WITH("RENAME TO", "TO", "USING (", "WITH CHECK ("); /* ALTER POLICY <name> ON <table> TO <role> */ 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 <name> ON <table> USING ( */ else if (Matches("ALTER", "POLICY", MatchAny, "ON", MatchAny, "USING")) COMPLETE_WITH("("); @@ -2073,7 +2160,7 @@ psql_completion(const char *text, int start, int end) * If we have ALTER TRIGGER <sth> ON, then add the correct tablename */ else if (Matches("ALTER", "TRIGGER", MatchAny, "ON")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables); /* ALTER TRIGGER <name> ON <name> */ else if (Matches("ALTER", "TRIGGER", MatchAny, "ON", MatchAny)) @@ -2100,7 +2187,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"); @@ -2164,13 +2251,13 @@ psql_completion(const char *text, int start, int end) } /* 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"); @@ -2187,13 +2274,13 @@ psql_completion(const char *text, int start, int end) /* 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"))) @@ -2208,7 +2295,7 @@ psql_completion(const char *text, int start, int end) COMPLETE_WITH("COLUMN", "CONSTRAINT"); /* If we have ALTER TABLE <sth> 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 <sth> ALTER|DROP|RENAME CONSTRAINT <constraint> */ else if (Matches("ALTER", "TABLE", MatchAny, "ALTER|DROP|RENAME", "CONSTRAINT")) { @@ -2298,7 +2385,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"); @@ -2360,7 +2447,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 <foo> */ else if (Matches("ALTER", "TYPE", MatchAny, "ALTER", "ATTRIBUTE", MatchAny)) COMPLETE_WITH("TYPE"); @@ -2385,8 +2472,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", "(*)")) { @@ -2402,9 +2489,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")) @@ -2425,19 +2512,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 <sth>, then add "USING" */ else if (Matches("CLUSTER", MatchAnyExcept("VERBOSE|ON|(|(*)"))) COMPLETE_WITH("USING"); @@ -2487,19 +2575,19 @@ psql_completion(const char *text, int start, int end) 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'"); + COMPLETE_WITH_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)) @@ -2529,7 +2617,7 @@ psql_completion(const char *text, int start, int end) else if (Matches("COMMENT", "ON", "TEXT", "SEARCH", "TEMPLATE")) COMPLETE_WITH_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")) @@ -2557,8 +2645,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"); @@ -2616,7 +2703,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("(|*,")) @@ -2642,12 +2729,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. */ @@ -2660,7 +2747,7 @@ psql_completion(const char *text, int start, int end) else if (Matches("CREATE", "EXTENSION", MatchAny, "VERSION")) { completion_info_charp = prev2_wd; - COMPLETE_WITH_QUERY(Query_for_list_of_available_extension_versions); + COMPLETE_WITH_QUERY_VERBATIM(Query_for_list_of_available_extension_versions); } /* CREATE FOREIGN */ @@ -2685,9 +2772,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 [<name>] ON with a list of relations @@ -2695,15 +2781,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] <sth> with "ON" */ else if (TailMatches("CREATE|UNIQUE", "INDEX", MatchAny) || TailMatches("CREATE|UNIQUE", "INDEX", "CONCURRENTLY", MatchAny)) @@ -2718,10 +2804,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") || @@ -2742,7 +2828,7 @@ psql_completion(const char *text, int start, int end) COMPLETE_WITH("ON"); /* Complete "CREATE POLICY <name> ON <table>" */ 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 <name> ON <table> AS|FOR|TO|USING|WITH CHECK" */ else if (Matches("CREATE", "POLICY", MatchAny, "ON", MatchAny)) COMPLETE_WITH("AS", "FOR", "TO", "USING (", "WITH CHECK ("); @@ -2770,7 +2856,8 @@ psql_completion(const char *text, int start, int end) COMPLETE_WITH("TO", "USING (", "WITH CHECK ("); /* Complete "CREATE POLICY <name> ON <table> TO <role>" */ 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 <name> ON <table> USING (" */ else if (Matches("CREATE", "POLICY", MatchAny, "ON", MatchAny, "USING")) COMPLETE_WITH("("); @@ -2808,7 +2895,8 @@ psql_completion(const char *text, int start, int end) * <role>" */ 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 <name> ON <table> AS PERMISSIVE|RESTRICTIVE @@ -2831,18 +2919,18 @@ psql_completion(const char *text, int start, int end) COMPLETE_WITH("WITH ("); /* Complete "CREATE PUBLICATION <name> FOR TABLE" with "<table>, ..." */ 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 <name> FOR ALL TABLES IN SCHEMA <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 != 'pg_catalog' " + " AND nspname not like 'pg\\_toast%%' " + " AND nspname not like 'pg\\_temp%%' ", + "CURRENT_SCHEMA"); else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES", "IN", "SCHEMA", MatchAny) && (!ends_with(prev_wd,','))) COMPLETE_WITH("WITH ("); /* Complete "CREATE PUBLICATION <name> [...] WITH" */ @@ -2871,7 +2959,7 @@ psql_completion(const char *text, int start, int end) COMPLETE_WITH("TO"); /* Complete "AS ON <sth> 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) || @@ -2898,7 +2986,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 */ @@ -2912,7 +3000,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"); @@ -2923,7 +3011,7 @@ psql_completion(const char *text, int start, int end) /* Complete CREATE TABLE <name> 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, "(*)")) @@ -2962,7 +3050,7 @@ 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"); @@ -3030,7 +3118,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 @@ -3038,7 +3126,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)) @@ -3143,7 +3231,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 <name> */ else if (Matches("CREATE", "ROLE|GROUP|USER", MatchAny) && @@ -3179,7 +3267,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", ",", ")"); } @@ -3263,12 +3351,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 */ @@ -3316,7 +3404,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 <table> */ else if (TailMatches("DELETE", "FROM", MatchAny)) COMPLETE_WITH("USING", "WHERE"); @@ -3358,10 +3446,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)) @@ -3371,7 +3459,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"); @@ -3440,7 +3528,7 @@ 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")) @@ -3494,28 +3582,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 <direction> with "FROM" or "IN". These are equivalent, @@ -3525,9 +3613,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 <direction> "FROM" or "IN" with a list of cursors */ else if (HeadMatches("FETCH|MOVE") && TailMatches("FROM|IN")) @@ -3546,7 +3634,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")) @@ -3568,20 +3656,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"); } /* @@ -3601,9 +3689,6 @@ psql_completion(const char *text, int start, int end) /* * Complete GRANT/REVOKE <sth> 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. @@ -3617,26 +3702,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", @@ -3658,25 +3743,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 @@ -3689,10 +3774,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"); @@ -3753,10 +3840,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 <table> (" with attribute names */ else if (TailMatches("INSERT", "INTO", MatchAny, "(")) - COMPLETE_WITH_ATTR(prev2_wd, ""); + COMPLETE_WITH_ATTR(prev2_wd); /* * Complete INSERT INTO <table> with "(" or "VALUES" or "SELECT" or @@ -3788,14 +3875,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] <table> with IN or NOWAIT */ @@ -3831,7 +3917,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 WHEREsubstring(pg_catalog.quote_ident(channel),1,%d)='%s'"); + COMPLETE_WITH_QUERY("SELECT channel FROM pg_catalog.pg_listening_channels() AS channel WHERE substring(channel,1,%d)='%s'"); /* OPTIONS */ else if (TailMatches("OPTIONS")) @@ -3845,7 +3931,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")) @@ -3874,10 +3960,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)) @@ -3897,26 +3983,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); @@ -3960,9 +4046,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"); @@ -3997,7 +4091,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 <foo> with DEFERRED|IMMEDIATE */ else if (Matches("SET", "CONSTRAINTS", MatchAny)) COMPLETE_WITH("DEFERRED", "IMMEDIATE"); @@ -4009,7 +4104,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"); @@ -4039,10 +4135,10 @@ 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' "); + COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_schemas + " AND nspname not like 'pg\\_toast%%' " + " AND nspname not like 'pg\\_temp%%' ", + "DEFAULT"); else { /* generic, type based, GUC support */ @@ -4061,7 +4157,7 @@ psql_completion(const char *text, int start, int end) snprintf(querybuf, sizeof(querybuf), Query_for_enum, prev2_wd); - COMPLETE_WITH_QUERY(querybuf); + COMPLETE_WITH_QUERY_PLUS(querybuf, "DEFAULT"); } else if (strcmp(guctype, "bool") == 0) COMPLETE_WITH("on", "off", "true", "false", "yes", "no", @@ -4080,7 +4176,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")) @@ -4090,14 +4186,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)) @@ -4107,18 +4202,20 @@ 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 WHEREsubstring(pg_catalog.quote_ident(channel),1,%d)='%s' UNION SELECT '*'"); + COMPLETE_WITH_QUERY_PLUS("SELECT channel FROM pg_catalog.pg_listening_channels() AS channel" + " WHERE substring(channel,1,%d)='%s'", + "*"); /* 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 <table> with "SET" */ else if (TailMatches("UPDATE", MatchAny)) COMPLETE_WITH("SET"); /* Complete UPDATE <table> SET with list of attributes */ else if (TailMatches("UPDATE", MatchAny, "SET")) - COMPLETE_WITH_ATTR(prev2_wd, ""); + COMPLETE_WITH_ATTR(prev2_wd); /* UPDATE <table> SET <attr> = */ else if (TailMatches("UPDATE", MatchAny, "SET", MatchAnyExcept("*="))) COMPLETE_WITH("="); @@ -4127,11 +4224,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)) @@ -4144,26 +4241,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", "(*)")) { @@ -4184,9 +4281,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] */ @@ -4200,16 +4297,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 */ @@ -4226,19 +4323,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*")) @@ -4246,9 +4343,9 @@ 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); @@ -4261,51 +4358,51 @@ psql_completion(const char *text, int start, int end) COMPLETE_WITH_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); @@ -4407,9 +4504,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")) @@ -4426,19 +4523,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; } } @@ -4551,7 +4650,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 * @@ -4566,22 +4666,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) @@ -4590,17 +4690,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); } @@ -4611,35 +4702,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 + * substring(OUTPUT,1,%d)='%s' + * where "OUTPUT" is the same string that the query returns. The %d and %s + * will be replaced by the string length of the text and the text itself, + * causing the results to be limited to those matching the already-typed text. + * 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. (These strings will be escaped to + * become SQL literals, so what is actually in the query should be '%s'.) + * 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. + * This is represented by a SchemaQuery object; see that typedef for details. + * + * See top of file for examples of both kinds of query. * - * 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. + * 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.) * - * It is assumed that strings should be escaped to become SQL literals - * (that is, what is in the query is actually ... '%s' ...) + * 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. * - * See top of file for examples of both kinds of query. + * "text" and "state" are supplied by Readline. "text" is the word we are + * trying to complete. "state" is zero on first call, nonzero later. * - * "text" and "state" are supplied by readline. + * 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 @@ -4648,31 +4758,53 @@ _complete_from_query(const char *simple_query, if (state == 0) { PQExpBufferData query_buffer; - char *e_text; + char *schemaname; + char *objectname; + int object_length = 0; + char *e_schemaname; + char *e_objectname; char *e_info_charp; char *e_info_charp2; - const char *pstr = text; - int char_length = 0; + /* Reset static state, ensuring no memory leaks */ list_index = 0; - byte_length = strlen(text); + num_schema_only = 0; + num_other = 0; + PQclear(result); + result = NULL; + + /* Parse text, splitting into schema and object name if needed */ + if (verbatim) + { + objectname = pg_strdup(text); + schemaname = NULL; + } + else + { + parse_identifier(text, + &schemaname, &objectname, + &schemaquoted, &objectquoted); + } + + /* Remember whether the user has typed anything in the object part */ + non_empty_object = (*objectname != '\0'); /* * 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 */ - PQclear(result); - result = NULL; + for (const char *p = objectname; + *p; + p += PQmblenBounded(p, pset.encoding)) + object_length++; /* Set up suitably-escaped copies of textual inputs */ - e_text = escape_string(text); + if (schemaname) + e_schemaname = escape_string(schemaname); + else + e_schemaname = NULL; + + e_objectname = escape_string(objectname); if (completion_info_charp) e_info_charp = escape_string(completion_info_charp); @@ -4688,14 +4820,17 @@ _complete_from_query(const char *simple_query, if (schema_query) { - /* schema_query gives us the pieces to assemble */ - const char *qualresult = schema_query->qualresult; - - if (qualresult == NULL) - qualresult = schema_query->result; + Assert(simple_query == NULL); + /* + * We issue different queries depending on whether the input is + * already qualified or not. schema_query gives us the pieces to + * assemble. + */ + if (schemaname == NULL) + { /* Get unqualified names matching the input-so-far */ - appendPQExpBuffer(&query_buffer, "SELECT %s FROM %s WHERE ", + appendPQExpBuffer(&query_buffer, "SELECT %s, NULL::pg_catalog.text FROM %s WHERE ", schema_query->result, schema_query->catname); if (schema_query->selcondition) @@ -4703,7 +4838,7 @@ _complete_from_query(const char *simple_query, schema_query->selcondition); appendPQExpBuffer(&query_buffer, "substring(%s,1,%d)='%s'", schema_query->result, - char_length, e_text); + object_length, e_objectname); appendPQExpBuffer(&query_buffer, " AND %s", schema_query->viscondition); @@ -4715,72 +4850,59 @@ _complete_from_query(const char *simple_query, */ if (strcmp(schema_query->catname, "pg_catalog.pg_class c") == 0 && - strncmp(text, "pg_", 3) != 0) + strncmp(objectname, "pg_", 3) != 0) { appendPQExpBufferStr(&query_buffer, " AND c.relnamespace <> (SELECT oid FROM" " pg_catalog.pg_namespace WHERE nspname = 'pg_catalog')"); } + /* Add in schema names matching the input-so-far */ + appendPQExpBuffer(&query_buffer, "\nUNION ALL\n" + "SELECT NULL::pg_catalog.text, n.nspname " + "FROM pg_catalog.pg_namespace n " + "WHERE substring(n.nspname,1,%d)='%s'", + object_length, e_objectname); + /* - * Add in matching schema names, but only if there is more than - * one potential match among schema names. + * Likewise, suppress system schemas unless the input-so-far + * begins with "pg_". */ - 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); + if (strncmp(objectname, "pg_", 3) != 0) + appendPQExpBufferStr(&query_buffer, + " AND n.nspname NOT LIKE 'pg\\_%'"); /* - * Add in matching qualified names, but only if there is exactly - * one schema matching the input-so-far. + * Since we're matching these schema names to the object name, + * handle their quoting using the object name's quoting state. */ - appendPQExpBuffer(&query_buffer, "\nUNION\n" - "SELECT pg_catalog.quote_ident(n.nspname) || '.' || %s " + schemaquoted = objectquoted; + } + else + { + /* Input is qualified, so produce only qualified names */ + appendPQExpBuffer(&query_buffer, "SELECT %s, n.nspname " "FROM %s, pg_catalog.pg_namespace n " "WHERE %s = n.oid AND ", - qualresult, + schema_query->result, 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); + appendPQExpBuffer(&query_buffer, "substring(%s,1,%d)='%s' AND ", + schema_query->result, + object_length, e_objectname); + appendPQExpBuffer(&query_buffer, "n.nspname = '%s'", + e_schemaname); + } } else { Assert(simple_query); /* simple_query is an sprintf-style format string */ appendPQExpBuffer(&query_buffer, simple_query, - char_length, e_text, + object_length, e_objectname, e_info_charp, e_info_charp, e_info_charp2, e_info_charp2); } @@ -4789,28 +4911,120 @@ _complete_from_query(const char *simple_query, 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_schemaname) + free(e_schemaname); + free(e_objectname); if (e_info_charp) free(e_info_charp); if (e_info_charp2) free(e_info_charp2); } - /* 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; @@ -5144,6 +5358,228 @@ escape_string(const char *text) } +/* + * 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 and report any errors. This should be the preferred way of * talking to the database in this file. diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c index 16999dd9a2..5658ec3759 100644 --- a/src/bin/psql/tab-complete.c +++ b/src/bin/psql/tab-complete.c @@ -110,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_info_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 + * related object almost always does (passed in completion_info_schema). + * * As with VersionedQuery, we can use an array of these if the query details * must vary across versions. */ @@ -123,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 ni" will be added automatically when needed. */ const char *catname; @@ -140,12 +147,15 @@ 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; @@ -154,12 +164,43 @@ typedef struct SchemaQuery */ const char *result; + /* + * In some cases, it's difficult to keep the query from returning the same + * object multiple times. Specify use_distinct to filter out duplicates. + */ + 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_info_object/completion_info_schema, + * populate the remaining fields, else leave them NULL. When using this + * capability, catname must include the catalog that defines the + * completion_info_object, and selcondition must include the join + * condition that connects it to the result's catalog. + * + * infoname is the field that should be equated to completion_info_object, + * for example "ci.relname". + */ + const char *infoname; + + /* + * Visibility condition to use when completion_info_schema is not set. For + * example, "pg_catalog.pg_table_is_visible(ci.oid)". NULL if not needed. + */ + const char *infoviscondition; + + /* + * Name of field to join to pg_namespace.oid when completion_info_schema + * is set. For example, "ci.relnamespace". NULL if we don't want to + * consider completion_info_schema. + */ + const char *infonamespace; } SchemaQuery; @@ -176,10 +217,10 @@ 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_info_object; /* name of a related object */ +static char *completion_info_schema; /* schema name of a related 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 */ @@ -257,6 +298,14 @@ do { \ 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) @@ -312,29 +361,11 @@ do { \ #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; \ - completion_info_charp = relation; \ - } \ - else \ - { \ - completion_charp = Query_for_list_of_attributes_with_schema; \ - completion_info_charp = _completion_table; \ - completion_info_charp2 = _completion_schema; \ - } \ + setup_completion_info(relation); \ + completion_squery = &(Query_for_list_of_attributes); \ completion_charpp = list; \ completion_verbatim = false; \ - matches = rl_completion_matches(text, complete_from_query); \ + matches = rl_completion_matches(text, complete_from_schema_query); \ } while (0) #define COMPLETE_WITH_ATTR_PLUS(relation, ...) \ @@ -351,65 +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; \ - } \ + setup_completion_info(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; \ - } \ + completion_squery = &(Query_for_list_of_enum_values_unquoted); \ completion_charpp = NULL; \ completion_verbatim = true; \ - matches = rl_completion_matches(text, complete_from_query); \ + 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; \ - } \ + setup_completion_info(function); \ + completion_squery = &(Query_for_list_of_arguments); \ completion_charpp = NULL; \ completion_verbatim = true; \ - matches = rl_completion_matches(text, complete_from_query); \ + matches = rl_completion_matches(text, complete_from_schema_query); \ } while (0) /* @@ -419,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", + .infoname = "c1.relname", + .infoviscondition = "pg_catalog.pg_table_is_visible(c1.oid)", + .infonamespace = "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", + .infoname = "c1.relname", + .infoviscondition = "pg_catalog.pg_table_is_visible(c1.oid)", + .infonamespace = "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", + .infoname = "t.typname", + .infoviscondition = "pg_catalog.pg_type_is_visible(t.oid)", + .infonamespace = "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", + .infoname = "c1.relname", + .infoviscondition = "pg_catalog.pg_table_is_visible(c1.oid)", + .infonamespace = "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", + .infoname = "c1.relname", + .infoviscondition = "pg_catalog.pg_table_is_visible(c1.oid)", + .infonamespace = "c1.relnamespace", +}; + static const SchemaQuery Query_for_list_of_aggregates[] = { { .min_server_version = 110000, @@ -437,6 +472,32 @@ static const SchemaQuery Query_for_list_of_aggregates[] = { } }; +static const SchemaQuery Query_for_list_of_arguments = { + .catname = "pg_catalog.pg_proc p", + .result = "pg_catalog.oidvectortypes(p.proargtypes)||')'", + .infoname = "p.proname", + .infoviscondition = "pg_catalog.pg_function_is_visible(p.oid)", + .infonamespace = "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", + .infoname = "c.relname", + .infoviscondition = "pg_catalog.pg_table_is_visible(c.oid)", + .infonamespace = "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", + .infoname = "c.relname", + .infoviscondition = "pg_catalog.pg_table_is_visible(c.oid)", + .infonamespace = "c.relnamespace", +}; + static const char *const Keywords_for_list_of_datatypes[] = { "bigint", "boolean", @@ -497,6 +558,24 @@ static const SchemaQuery Query_for_list_of_domains = { .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)", + .infoname = "t.typname", + .infoviscondition = "pg_catalog.pg_type_is_visible(t.oid)", + .infonamespace = "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", + .infoname = "t.typname", + .infoviscondition = "pg_catalog.pg_type_is_visible(t.oid)", + .infonamespace = "t.typnamespace", +}; + /* Note: this intentionally accepts aggregates as well as plain functions */ static const SchemaQuery Query_for_list_of_functions[] = { { @@ -571,6 +650,48 @@ static const SchemaQuery Query_for_list_of_partitioned_tables = { .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, + .infoname = "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, + .infoname = "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, + .infoname = "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, + .infoname = "t.tgname", +}; + static const SchemaQuery Query_for_list_of_views = { .catname = "pg_catalog.pg_class c", .selcondition = "c.relkind IN (" CppAsString2(RELKIND_VIEW) ")", @@ -719,7 +840,6 @@ static const SchemaQuery Query_for_list_of_clusterables = { 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 = "c.conname", }; @@ -739,96 +859,56 @@ static const SchemaQuery Query_for_list_of_collations = { .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", + .infoname = "c1.relname", + .infoviscondition = "pg_catalog.pg_table_is_visible(c1.oid)", + .infonamespace = "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", + .infoname = "c1.relname", + .infoviscondition = "pg_catalog.pg_table_is_visible(c1.oid)", + .infonamespace = "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", + .infoname = "c1.relname", + .infoviscondition = "pg_catalog.pg_table_is_visible(c1.oid)", + .infonamespace = "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. + * + * In these queries, there must be a restriction clause of the form + * substring(OUTPUT,1,%d)='%s' + * where "OUTPUT" is the same string that the query returns. The %d and %s + * will be replaced by the string length of the text and the text itself, + * causing the results to be limited to those matching the already-typed text. * - * Beware that the allowed sequences of %s and %d are determined by - * _complete_from_query(). + * There can be a second '%s', which will be replaced by a suitably-escaped + * version of the string provided in completion_info_object. If there is a + * third '%s', it will be replaced by a suitably-escaped version of the string + * provided in completion_info_schema. NOTE: using completion_info_object + * that way is usually the wrong thing, and using completion_info_schema + * that way is always the wrong thing. Make a SchemaQuery instead. */ -#define Query_for_list_of_attributes \ -"SELECT 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(attname,1,%d)='%s' "\ -" AND (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 (relname='%s' "\ -" OR '\"' || relname || '\"'='%s') "\ -" AND pg_catalog.pg_table_is_visible(c.oid)" - -#define Query_for_list_of_attributes_with_schema \ -"SELECT 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(attname,1,%d)='%s' "\ -" AND (relname='%s' "\ -" OR '\"' || relname || '\"' ='%s') "\ -" AND (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 (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 (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 (typname='%s' "\ -" OR '\"' || typname || '\"'='%s') "\ -" AND (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 (typname='%s' "\ -" OR '\"' || typname || '\"'='%s') "\ -" AND (nspname='%s' "\ -" OR '\"' || nspname || '\"' ='%s') " - #define Query_for_list_of_template_databases \ "SELECT d.datname "\ " FROM pg_catalog.pg_database d "\ @@ -887,81 +967,12 @@ static const SchemaQuery Query_for_list_of_collations = { #define Keywords_for_list_of_grant_roles \ "PUBLIC", "CURRENT_ROLE", "CURRENT_USER", "SESSION_USER" -#define Query_for_index_of_table \ -"SELECT 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 substring(c2.relname,1,%d)='%s'"\ -" and 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" - -#define Query_for_constraint_of_table \ -"SELECT conname "\ -" FROM pg_catalog.pg_class c1, pg_catalog.pg_constraint con "\ -" WHERE c1.oid=conrelid and substring(conname,1,%d)='%s'"\ -" and c1.relname='%s'"\ -" and pg_catalog.pg_table_is_visible(c1.oid)" - -#define Query_for_constraint_of_table_not_validated \ -Query_for_constraint_of_table \ -" and not con.convalidated" - #define Query_for_all_table_constraints \ "SELECT conname "\ " FROM pg_catalog.pg_constraint c "\ " WHERE c.conrelid <> 0 "\ " and substring(conname,1,%d)='%s'" -#define Query_for_constraint_of_type \ -"SELECT conname "\ -" FROM pg_catalog.pg_type t, pg_catalog.pg_constraint con "\ -" WHERE t.oid=contypid and substring(conname,1,%d)='%s'"\ -" and t.typname='%s'"\ -" and pg_catalog.pg_type_is_visible(t.oid)" - -#define Query_for_list_of_tables_for_constraint \ -"SELECT relname "\ -" FROM pg_catalog.pg_class"\ -" WHERE substring(relname,1,%d)='%s'"\ -" AND oid IN "\ -" (SELECT conrelid FROM pg_catalog.pg_constraint "\ -" WHERE conname='%s')" - -#define Query_for_rule_of_table \ -"SELECT rulename "\ -" FROM pg_catalog.pg_class c1, pg_catalog.pg_rewrite "\ -" WHERE c1.oid=ev_class and substring(rulename,1,%d)='%s'"\ -" and c1.relname='%s'"\ -" and pg_catalog.pg_table_is_visible(c1.oid)" - -#define Query_for_list_of_tables_for_rule \ -"SELECT relname "\ -" FROM pg_catalog.pg_class"\ -" WHERE substring(relname,1,%d)='%s'"\ -" AND oid IN "\ -" (SELECT ev_class FROM pg_catalog.pg_rewrite "\ -" WHERE rulename='%s')" - -#define Query_for_trigger_of_table \ -"SELECT tgname "\ -" FROM pg_catalog.pg_class c1, pg_catalog.pg_trigger "\ -" WHERE c1.oid=tgrelid and substring(tgname,1,%d)='%s'"\ -" and c1.relname='%s'"\ -" and pg_catalog.pg_table_is_visible(c1.oid)"\ -" and not tgisinternal" - -#define Query_for_list_of_tables_for_trigger \ -"SELECT relname "\ -" FROM pg_catalog.pg_class"\ -" WHERE substring(relname,1,%d)='%s'"\ -" AND oid IN "\ -" (SELECT tgrelid FROM pg_catalog.pg_trigger "\ -" WHERE tgname='%s')" - #define Query_for_list_of_ts_configurations \ "SELECT cfgname FROM pg_catalog.pg_ts_config "\ " WHERE substring(cfgname,1,%d)='%s'" @@ -1010,24 +1021,6 @@ Query_for_constraint_of_table \ " WHERE substring(amname,1,%d)='%s' AND "\ " amtype=" CppAsString2(AMTYPE_TABLE) -#define Query_for_list_of_arguments \ -"SELECT pg_catalog.oidvectortypes(proargtypes)||')' "\ -" FROM pg_catalog.pg_proc "\ -" WHERE substring(pg_catalog.oidvectortypes(proargtypes)||')',1,%d)='%s'"\ -" AND (proname='%s'"\ -" OR '\"' || proname || '\"'='%s') "\ -" AND (pg_catalog.pg_function_is_visible(pg_proc.oid))" - -#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 substring(pg_catalog.oidvectortypes(proargtypes)||')',1,%d)='%s'"\ -" AND n.oid = p.pronamespace "\ -" AND (proname='%s' "\ -" OR '\"' || proname || '\"' ='%s') "\ -" AND (nspname='%s' "\ -" OR '\"' || nspname || '\"' ='%s') " - #define Query_for_list_of_extensions \ " SELECT extname "\ " FROM pg_catalog.pg_extension "\ @@ -1074,30 +1067,18 @@ Query_for_constraint_of_table \ " FROM pg_catalog.pg_policy "\ " WHERE substring(polname,1,%d)='%s'" -#define Query_for_list_of_tables_for_policy \ -"SELECT relname "\ -" FROM pg_catalog.pg_class"\ -" WHERE substring(relname,1,%d)='%s'"\ -" AND oid IN "\ -" (SELECT polrelid FROM pg_catalog.pg_policy "\ -" WHERE polname='%s')" - -#define Query_for_enum \ -" SELECT name FROM ( "\ -" SELECT 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') "\ " ) ss "\ -" WHERE pg_catalog.substring(name,1,%%d)='%%s'" +" WHERE substring(val,1,%d)='%s'"\ +" and pg_catalog.lower(name)=pg_catalog.lower('%s')" -#define Query_for_partition_of_table \ -"SELECT 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 substring(c2.relname,1,%d)='%s'"\ -" and 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 substring(channel,1,%d)='%s'" #define Query_for_list_of_cursors \ " SELECT name "\ @@ -1280,6 +1261,7 @@ static char *_complete_from_query(const char *simple_query, const char *const *keywords, bool verbatim, const char *text, int state); +static void setup_completion_info(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, @@ -1685,8 +1667,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_info_object = NULL; + completion_info_schema = NULL; /* * Scan the input line to extract the words before our current position. @@ -1866,14 +1850,14 @@ psql_completion(const char *text, int start, int end) /* ALTER EXTENSION <name> UPDATE */ else if (Matches("ALTER", "EXTENSION", MatchAny, "UPDATE")) { - completion_info_charp = prev2_wd; + setup_completion_info(prev2_wd); COMPLETE_WITH_QUERY_VERBATIM(Query_for_list_of_available_extension_versions_with_TO); } /* ALTER EXTENSION <name> UPDATE TO */ else if (Matches("ALTER", "EXTENSION", MatchAny, "UPDATE", "TO")) { - completion_info_charp = prev3_wd; + setup_completion_info(prev3_wd); COMPLETE_WITH_QUERY_VERBATIM(Query_for_list_of_available_extension_versions); } @@ -1913,8 +1897,8 @@ psql_completion(const char *text, int start, int end) /* ALTER INDEX <name> ALTER COLUMN */ else if (Matches("ALTER", "INDEX", MatchAny, "ALTER", "COLUMN")) { - completion_info_charp = prev3_wd; - COMPLETE_WITH_QUERY(Query_for_list_of_attribute_numbers); + setup_completion_info(prev3_wd); + COMPLETE_WITH_SCHEMA_QUERY_VERBATIM(Query_for_list_of_attribute_numbers); } /* ALTER INDEX <name> ALTER COLUMN <colnum> */ else if (Matches("ALTER", "INDEX", MatchAny, "ALTER", "COLUMN", MatchAny)) @@ -2024,8 +2008,8 @@ psql_completion(const char *text, int start, int end) /* ALTER DOMAIN <sth> 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); + setup_completion_info(prev3_wd); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_constraint_of_type); } /* ALTER DOMAIN <sth> RENAME */ else if (Matches("ALTER", "DOMAIN", MatchAny, "RENAME")) @@ -2110,8 +2094,8 @@ psql_completion(const char *text, int start, int end) /* ALTER POLICY <name> ON <table> */ else if (Matches("ALTER", "POLICY", MatchAny, "ON")) { - completion_info_charp = prev2_wd; - COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_policy); + setup_completion_info(prev2_wd); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables_for_policy); } /* ALTER POLICY <name> ON <table> - show options */ else if (Matches("ALTER", "POLICY", MatchAny, "ON", MatchAny)) @@ -2134,8 +2118,8 @@ psql_completion(const char *text, int start, int end) /* If we have ALTER RULE <name> 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); + setup_completion_info(prev2_wd); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables_for_rule); } /* ALTER RULE <name> ON <name> */ @@ -2150,18 +2134,12 @@ 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)) + else if (Matches("ALTER", "TRIGGER", MatchAny, "ON")) { - completion_info_charp = prev2_wd; - COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_trigger); + setup_completion_info(prev2_wd); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables_for_trigger); } - /* - * If we have ALTER TRIGGER <sth> ON, then add the correct tablename - */ - else if (Matches("ALTER", "TRIGGER", MatchAny, "ON")) - COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables); - /* ALTER TRIGGER <name> ON <name> */ else if (Matches("ALTER", "TRIGGER", MatchAny, "ON", MatchAny)) COMPLETE_WITH("RENAME TO", "DEPENDS ON EXTENSION", @@ -2200,28 +2178,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); + setup_completion_info(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); + setup_completion_info(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); + setup_completion_info(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); + setup_completion_info(prev7_wd); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_unique_index_of_table); } /* ALTER TABLE xxx ENABLE */ else if (Matches("ALTER", "TABLE", MatchAny, "ENABLE")) @@ -2231,23 +2209,23 @@ 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); + setup_completion_info(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); + setup_completion_info(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); + setup_completion_info(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); + setup_completion_info(prev4_wd); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_trigger_of_table); } /* ALTER TABLE xxx INHERIT */ else if (Matches("ALTER", "TABLE", MatchAny, "INHERIT")) @@ -2263,13 +2241,13 @@ psql_completion(const char *text, int start, int end) 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); + setup_completion_info(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); + setup_completion_info(prev3_wd); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_trigger_of_table); } /* ALTER TABLE xxx ALTER */ @@ -2299,14 +2277,14 @@ psql_completion(const char *text, int start, int end) /* ALTER TABLE <sth> ALTER|DROP|RENAME CONSTRAINT <constraint> */ else if (Matches("ALTER", "TABLE", MatchAny, "ALTER|DROP|RENAME", "CONSTRAINT")) { - completion_info_charp = prev3_wd; - COMPLETE_WITH_QUERY(Query_for_constraint_of_table); + setup_completion_info(prev3_wd); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_constraint_of_table); } /* ALTER TABLE <sth> VALIDATE CONSTRAINT <non-validated constraint> */ else if (Matches("ALTER", "TABLE", MatchAny, "VALIDATE", "CONSTRAINT")) { - completion_info_charp = prev3_wd; - COMPLETE_WITH_QUERY(Query_for_constraint_of_table_not_validated); + setup_completion_info(prev3_wd); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_constraint_of_table_not_validated); } /* ALTER TABLE ALTER [COLUMN] <foo> */ else if (Matches("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny) || @@ -2338,8 +2316,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); + setup_completion_info(prev3_wd); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_index_of_table); } /* If we have ALTER TABLE <sth> SET, provide list of attributes and '(' */ else if (Matches("ALTER", "TABLE", MatchAny, "SET")) @@ -2370,8 +2348,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); + setup_completion_info(prev5_wd); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_index_of_table); } else if (Matches("ALTER", "TABLE", MatchAny, "REPLICA", "IDENTITY", "USING")) COMPLETE_WITH("INDEX"); @@ -2398,8 +2376,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); + setup_completion_info(prev3_wd); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_partition_of_table); } else if (Matches("ALTER", "TABLE", MatchAny, "DETACH", "PARTITION", MatchAny)) COMPLETE_WITH("CONCURRENTLY", "FINALIZE"); @@ -2536,8 +2514,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); + setup_completion_info(prev2_wd); + COMPLETE_WITH_SCHEMA_QUERY(Query_for_index_of_table); } else if (HeadMatches("CLUSTER", "(*") && !HeadMatches("CLUSTER", "(*)")) @@ -2574,9 +2552,9 @@ 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_PLUS(Query_for_list_of_tables_for_constraint, - "DOMAIN"); + setup_completion_info(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); @@ -2594,8 +2572,8 @@ psql_completion(const char *text, int start, int end) 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); + setup_completion_info(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); @@ -2603,8 +2581,8 @@ 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); + setup_completion_info(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"); @@ -2622,15 +2600,15 @@ psql_completion(const char *text, int start, int end) COMPLETE_WITH("LANGUAGE"); else if (Matches("COMMENT", "ON", "TRANSFORM", "FOR", MatchAny, "LANGUAGE")) { - completion_info_charp = prev2_wd; + setup_completion_info(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); + setup_completion_info(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")) || @@ -2746,7 +2724,7 @@ psql_completion(const char *text, int start, int end) /* CREATE EXTENSION <name> VERSION */ else if (Matches("CREATE", "EXTENSION", MatchAny, "VERSION")) { - completion_info_charp = prev2_wd; + setup_completion_info(prev2_wd); COMPLETE_WITH_QUERY_VERBATIM(Query_for_list_of_available_extension_versions); } @@ -3057,7 +3035,7 @@ psql_completion(const char *text, int start, int end) else if (Matches("CREATE", "TRANSFORM", "FOR", MatchAny, "LANGUAGE") || Matches("CREATE", "OR", "REPLACE", "TRANSFORM", "FOR", MatchAny, "LANGUAGE")) { - completion_info_charp = prev2_wd; + setup_completion_info(prev2_wd); COMPLETE_WITH_QUERY(Query_for_list_of_languages); } @@ -3480,8 +3458,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); + setup_completion_info(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"); @@ -3507,8 +3485,8 @@ psql_completion(const char *text, int start, int end) /* DROP POLICY <name> ON <table> */ else if (Matches("DROP", "POLICY", MatchAny, "ON")) { - completion_info_charp = prev2_wd; - COMPLETE_WITH_QUERY(Query_for_list_of_tables_for_policy); + setup_completion_info(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"); @@ -3518,8 +3496,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); + setup_completion_info(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"); @@ -3533,7 +3511,7 @@ psql_completion(const char *text, int start, int end) COMPLETE_WITH("LANGUAGE"); else if (Matches("DROP", "TRANSFORM", "FOR", MatchAny, "LANGUAGE")) { - completion_info_charp = prev2_wd; + setup_completion_info(prev2_wd); COMPLETE_WITH_QUERY(Query_for_list_of_languages); } else if (Matches("DROP", "TRANSFORM", "FOR", MatchAny, "LANGUAGE", MatchAny)) @@ -3917,7 +3895,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 channel FROM pg_catalog.pg_listening_channels() AS channel WHERE substring(channel,1,%d)='%s'"); + COMPLETE_WITH_QUERY(Query_for_list_of_channels); /* OPTIONS */ else if (TailMatches("OPTIONS")) @@ -4153,11 +4131,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_PLUS(querybuf, "DEFAULT"); + setup_completion_info(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", @@ -4202,9 +4178,7 @@ psql_completion(const char *text, int start, int end) /* UNLISTEN */ else if (Matches("UNLISTEN")) - COMPLETE_WITH_QUERY_PLUS("SELECT channel FROM pg_catalog.pg_listening_channels() AS channel" - " WHERE substring(channel,1,%d)='%s'", - "*"); + 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 */ @@ -4405,7 +4379,7 @@ psql_completion(const char *text, int start, int end) 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)) @@ -4564,6 +4538,12 @@ psql_completion(const char *text, int start, int end) free(previous_words); free(words_buffer); free(text_copy); + if (completion_info_object) + free(completion_info_object); + completion_info_object = NULL; + if (completion_info_schema) + free(completion_info_schema); + completion_info_schema = NULL; /* Return our Grand List O' Matches */ return matches; @@ -4707,10 +4687,11 @@ complete_from_versioned_schema_query(const char *text, int state) * where "OUTPUT" is the same string that the query returns. The %d and %s * will be replaced by the string length of the text and the text itself, * causing the results to be limited to those matching the already-typed text. - * 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. (These strings will be escaped to - * become SQL literals, so what is actually in the query should be '%s'.) + * There can be a second '%s', which will be replaced by a suitably-escaped + * version of the string provided in completion_info_object. If there is a + * third '%s', it will be replaced by a suitably-escaped version of the string + * provided in completion_info_schema. Those strings should be set up + * by calling setup_completion_info(). * 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. @@ -4763,8 +4744,8 @@ _complete_from_query(const char *simple_query, int object_length = 0; char *e_schemaname; char *e_objectname; - char *e_info_charp; - char *e_info_charp2; + char *e_info_object; + char *e_info_schema; /* Reset static state, ensuring no memory leaks */ list_index = 0; @@ -4806,15 +4787,15 @@ _complete_from_query(const char *simple_query, e_objectname = escape_string(objectname); - if (completion_info_charp) - e_info_charp = escape_string(completion_info_charp); + if (completion_info_object) + e_info_object = escape_string(completion_info_object); else - e_info_charp = NULL; + e_info_object = NULL; - if (completion_info_charp2) - e_info_charp2 = escape_string(completion_info_charp2); + if (completion_info_schema) + e_info_schema = escape_string(completion_info_schema); else - e_info_charp2 = NULL; + e_info_schema = NULL; initPQExpBuffer(&query_buffer); @@ -4827,20 +4808,44 @@ _complete_from_query(const char *simple_query, * already qualified or not. schema_query gives us the pieces to * assemble. */ - if (schemaname == NULL) + if (schemaname == NULL || schema_query->namespace == NULL) { /* Get unqualified names matching the input-so-far */ - appendPQExpBuffer(&query_buffer, "SELECT %s, NULL::pg_catalog.text FROM %s WHERE ", + 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->infonamespace && completion_info_schema) + appendPQExpBufferStr(&query_buffer, + ", pg_catalog.pg_namespace ni"); + appendPQExpBufferStr(&query_buffer, " WHERE "); if (schema_query->selcondition) appendPQExpBuffer(&query_buffer, "%s AND ", schema_query->selcondition); appendPQExpBuffer(&query_buffer, "substring(%s,1,%d)='%s'", schema_query->result, object_length, e_objectname); + if (schema_query->viscondition) appendPQExpBuffer(&query_buffer, " AND %s", schema_query->viscondition); + if (schema_query->infoname) + { + Assert(completion_info_object); + appendPQExpBuffer(&query_buffer, " AND %s = '%s'", + schema_query->infoname, e_info_object); + if (schema_query->infonamespace && completion_info_schema) + appendPQExpBuffer(&query_buffer, + " AND %s = ni.oid AND ni.nspname = '%s'", + schema_query->infonamespace, + e_info_schema); + else if (schema_query->infoviscondition) + appendPQExpBuffer(&query_buffer, + " AND %s", + schema_query->infoviscondition); + } /* * When fetching relation names, suppress system catalogs unless @@ -4857,7 +4862,12 @@ _complete_from_query(const char *simple_query, " pg_catalog.pg_namespace WHERE nspname = 'pg_catalog')"); } - /* Add in schema names matching the input-so-far */ + /* + * 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 " @@ -4878,14 +4888,21 @@ _complete_from_query(const char *simple_query, */ schemaquoted = objectquoted; } + } else { /* Input is qualified, so produce only qualified names */ - appendPQExpBuffer(&query_buffer, "SELECT %s, n.nspname " - "FROM %s, pg_catalog.pg_namespace n " - "WHERE %s = n.oid AND ", + 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, + schema_query->catname); + if (schema_query->infonamespace && completion_info_schema) + appendPQExpBufferStr(&query_buffer, + ", pg_catalog.pg_namespace ni"); + appendPQExpBuffer(&query_buffer, " WHERE %s = n.oid AND ", schema_query->namespace); if (schema_query->selcondition) appendPQExpBuffer(&query_buffer, "%s AND ", @@ -4895,6 +4912,21 @@ _complete_from_query(const char *simple_query, object_length, e_objectname); appendPQExpBuffer(&query_buffer, "n.nspname = '%s'", e_schemaname); + if (schema_query->infoname) + { + Assert(completion_info_object); + appendPQExpBuffer(&query_buffer, " AND %s = '%s'", + schema_query->infoname, e_info_object); + if (schema_query->infonamespace && completion_info_schema) + appendPQExpBuffer(&query_buffer, + " AND %s = ni.oid AND ni.nspname = '%s'", + schema_query->infonamespace, + e_info_schema); + else if (schema_query->infoviscondition) + appendPQExpBuffer(&query_buffer, + " AND %s", + schema_query->infoviscondition); + } } } else @@ -4903,8 +4935,7 @@ _complete_from_query(const char *simple_query, /* simple_query is an sprintf-style format string */ appendPQExpBuffer(&query_buffer, simple_query, object_length, e_objectname, - e_info_charp, e_info_charp, - e_info_charp2, e_info_charp2); + e_info_object, e_info_schema); } /* Limit the number of records in the result */ @@ -4919,10 +4950,10 @@ _complete_from_query(const char *simple_query, if (e_schemaname) free(e_schemaname); free(e_objectname); - if (e_info_charp) - free(e_info_charp); - if (e_info_charp2) - free(e_info_charp2); + if (e_info_object) + free(e_info_object); + if (e_info_schema) + free(e_info_schema); } /* Return the next result, if any, but not if the query failed */ @@ -5031,6 +5062,23 @@ _complete_from_query(const char *simple_query, } +/* + * Set up completion_info_object and completion_info_schema + * by parsing the given word. These variables can then be + * used in a query passed to _complete_from_query. + */ +static void +setup_completion_info(const char *word) +{ + bool schemaquoted, + objectquoted; + + parse_identifier(word, + &completion_info_schema, &completion_info_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 diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c index 5658ec3759..01441f9bf4 100644 --- a/src/bin/psql/tab-complete.c +++ b/src/bin/psql/tab-complete.c @@ -4810,123 +4810,125 @@ _complete_from_query(const char *simple_query, */ if (schemaname == NULL || schema_query->namespace == NULL) { - /* 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->infonamespace && completion_info_schema) - appendPQExpBufferStr(&query_buffer, - ", pg_catalog.pg_namespace ni"); - appendPQExpBufferStr(&query_buffer, " WHERE "); - if (schema_query->selcondition) - appendPQExpBuffer(&query_buffer, "%s AND ", - schema_query->selcondition); - appendPQExpBuffer(&query_buffer, "substring(%s,1,%d)='%s'", - schema_query->result, - object_length, e_objectname); - if (schema_query->viscondition) - appendPQExpBuffer(&query_buffer, " AND %s", - schema_query->viscondition); - if (schema_query->infoname) - { - Assert(completion_info_object); - appendPQExpBuffer(&query_buffer, " AND %s = '%s'", - schema_query->infoname, e_info_object); + /* 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->infonamespace && completion_info_schema) - appendPQExpBuffer(&query_buffer, - " AND %s = ni.oid AND ni.nspname = '%s'", - schema_query->infonamespace, - e_info_schema); - else if (schema_query->infoviscondition) - appendPQExpBuffer(&query_buffer, - " AND %s", - schema_query->infoviscondition); - } - - /* - * 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 substring(n.nspname,1,%d)='%s'", - object_length, e_objectname); + appendPQExpBufferStr(&query_buffer, + ", pg_catalog.pg_namespace ni"); + appendPQExpBufferStr(&query_buffer, " WHERE "); + if (schema_query->selcondition) + appendPQExpBuffer(&query_buffer, "%s AND ", + schema_query->selcondition); + appendPQExpBuffer(&query_buffer, "substring(%s,1,%d)='%s'", + schema_query->result, + object_length, e_objectname); + if (schema_query->viscondition) + appendPQExpBuffer(&query_buffer, " AND %s", + schema_query->viscondition); + if (schema_query->infoname) + { + Assert(completion_info_object); + appendPQExpBuffer(&query_buffer, " AND %s = '%s'", + schema_query->infoname, e_info_object); + if (schema_query->infonamespace && completion_info_schema) + appendPQExpBuffer(&query_buffer, + " AND %s = ni.oid AND ni.nspname = '%s'", + schema_query->infonamespace, + e_info_schema); + else if (schema_query->infoviscondition) + appendPQExpBuffer(&query_buffer, + " AND %s", + schema_query->infoviscondition); + } - /* - * 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 'pg\\_%'"); + /* + * 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')"); + } - /* - * Since we're matching these schema names to the object name, - * handle their quoting using the object name's quoting state. - */ - schemaquoted = objectquoted; - } + /* + * 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 substring(n.nspname,1,%d)='%s'", + object_length, e_objectname); + + /* + * 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 '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->infonamespace && completion_info_schema) - appendPQExpBufferStr(&query_buffer, - ", pg_catalog.pg_namespace ni"); - 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, "substring(%s,1,%d)='%s' AND ", - schema_query->result, - object_length, e_objectname); - appendPQExpBuffer(&query_buffer, "n.nspname = '%s'", - e_schemaname); - if (schema_query->infoname) - { - Assert(completion_info_object); - appendPQExpBuffer(&query_buffer, " AND %s = '%s'", - schema_query->infoname, e_info_object); + /* 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->infonamespace && completion_info_schema) - appendPQExpBuffer(&query_buffer, - " AND %s = ni.oid AND ni.nspname = '%s'", - schema_query->infonamespace, - e_info_schema); - else if (schema_query->infoviscondition) - appendPQExpBuffer(&query_buffer, - " AND %s", - schema_query->infoviscondition); - } + appendPQExpBufferStr(&query_buffer, + ", pg_catalog.pg_namespace ni"); + 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, "substring(%s,1,%d)='%s' AND ", + schema_query->result, + object_length, e_objectname); + appendPQExpBuffer(&query_buffer, "n.nspname = '%s'", + e_schemaname); + if (schema_query->infoname) + { + Assert(completion_info_object); + appendPQExpBuffer(&query_buffer, " AND %s = '%s'", + schema_query->infoname, e_info_object); + if (schema_query->infonamespace && completion_info_schema) + appendPQExpBuffer(&query_buffer, + " AND %s = ni.oid AND ni.nspname = '%s'", + schema_query->infonamespace, + e_info_schema); + else if (schema_query->infoviscondition) + appendPQExpBuffer(&query_buffer, + " AND %s", + schema_query->infoviscondition); + } } } else diff --git a/src/bin/psql/t/010_tab_completion.pl b/src/bin/psql/t/010_tab_completion.pl index d3d1bd650e..8fda7c7f40 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<tab> 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<tab> 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,14 @@ 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(); + # 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 01441f9bf4..f7efa4b947 100644 --- a/src/bin/psql/tab-complete.c +++ b/src/bin/psql/tab-complete.c @@ -896,10 +896,9 @@ static const SchemaQuery Query_for_trigger_of_table = { * name; make a SchemaQuery instead. * * In these queries, there must be a restriction clause of the form - * substring(OUTPUT,1,%d)='%s' - * where "OUTPUT" is the same string that the query returns. The %d and %s - * will be replaced by the string length of the text and the text itself, - * causing the results to be limited to those matching the already-typed text. + * 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_info_object. If there is a @@ -912,56 +911,56 @@ static const SchemaQuery Query_for_trigger_of_table = { #define Query_for_list_of_template_databases \ "SELECT d.datname "\ " FROM pg_catalog.pg_database d "\ -" WHERE substring(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 datname FROM pg_catalog.pg_database "\ -" WHERE substring(datname,1,%d)='%s'" +" WHERE datname LIKE '%s'" #define Query_for_list_of_tablespaces \ "SELECT spcname FROM pg_catalog.pg_tablespace "\ -" WHERE substring(spcname,1,%d)='%s'" +" 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 lanname "\ " FROM pg_catalog.pg_language "\ " WHERE lanname != 'internal' "\ -" AND substring(lanname,1,%d)='%s'" +" AND lanname LIKE '%s'" #define Query_for_list_of_schemas \ "SELECT nspname FROM pg_catalog.pg_namespace "\ -" WHERE substring(nspname,1,%d)='%s'" +" 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' "\ " ) ss "\ -" WHERE substring(name,1,%d)='%s'" +" 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') "\ " ) ss "\ -" WHERE substring(name,1,%d)='%s'" +" 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 "\ " ) ss "\ -" WHERE substring(name,1,%d)='%s'" +" WHERE name LIKE '%s'" #define Query_for_list_of_roles \ " SELECT rolname "\ " FROM pg_catalog.pg_roles "\ -" WHERE substring(rolname,1,%d)='%s'" +" WHERE rolname LIKE '%s'" /* add these to Query_for_list_of_roles in GRANT contexts */ #define Keywords_for_list_of_grant_roles \ @@ -971,119 +970,119 @@ static const SchemaQuery Query_for_trigger_of_table = { "SELECT conname "\ " FROM pg_catalog.pg_constraint c "\ " WHERE c.conrelid <> 0 "\ -" and substring(conname,1,%d)='%s'" +" and conname LIKE '%s'" #define Query_for_list_of_ts_configurations \ "SELECT cfgname FROM pg_catalog.pg_ts_config "\ -" WHERE substring(cfgname,1,%d)='%s'" +" WHERE cfgname LIKE '%s'" #define Query_for_list_of_ts_dictionaries \ "SELECT dictname FROM pg_catalog.pg_ts_dict "\ -" WHERE substring(dictname,1,%d)='%s'" +" WHERE dictname LIKE '%s'" #define Query_for_list_of_ts_parsers \ "SELECT prsname FROM pg_catalog.pg_ts_parser "\ -" WHERE substring(prsname,1,%d)='%s'" +" WHERE prsname LIKE '%s'" #define Query_for_list_of_ts_templates \ "SELECT tmplname FROM pg_catalog.pg_ts_template "\ -" WHERE substring(tmplname,1,%d)='%s'" +" WHERE tmplname LIKE '%s'" #define Query_for_list_of_fdws \ " SELECT fdwname "\ " FROM pg_catalog.pg_foreign_data_wrapper "\ -" WHERE substring(fdwname,1,%d)='%s'" +" WHERE fdwname LIKE '%s'" #define Query_for_list_of_servers \ " SELECT srvname "\ " FROM pg_catalog.pg_foreign_server "\ -" WHERE substring(srvname,1,%d)='%s'" +" WHERE srvname LIKE '%s'" #define Query_for_list_of_user_mappings \ " SELECT usename "\ " FROM pg_catalog.pg_user_mappings "\ -" WHERE substring(usename,1,%d)='%s'" +" WHERE usename LIKE '%s'" #define Query_for_list_of_access_methods \ " SELECT amname "\ " FROM pg_catalog.pg_am "\ -" WHERE substring(amname,1,%d)='%s'" +" WHERE amname LIKE '%s'" #define Query_for_list_of_index_access_methods \ " SELECT amname "\ " FROM pg_catalog.pg_am "\ -" WHERE substring(amname,1,%d)='%s' AND "\ +" WHERE amname LIKE '%s' AND "\ " amtype=" CppAsString2(AMTYPE_INDEX) #define Query_for_list_of_table_access_methods \ " SELECT amname "\ " FROM pg_catalog.pg_am "\ -" WHERE substring(amname,1,%d)='%s' AND "\ +" WHERE amname LIKE '%s' AND "\ " amtype=" CppAsString2(AMTYPE_TABLE) #define Query_for_list_of_extensions \ " SELECT extname "\ " FROM pg_catalog.pg_extension "\ -" WHERE substring(extname,1,%d)='%s'" +" WHERE extname LIKE '%s'" #define Query_for_list_of_available_extensions \ " SELECT name "\ " FROM pg_catalog.pg_available_extensions "\ -" WHERE substring(name,1,%d)='%s' AND installed_version IS NULL" +" WHERE name LIKE '%s' AND installed_version IS NULL" /* the result of this query is not an identifier, so use VERBATIM */ #define Query_for_list_of_available_extension_versions \ " SELECT version "\ " FROM pg_catalog.pg_available_extension_versions "\ -" WHERE substring(version,1,%d)='%s'"\ +" WHERE version LIKE '%s'"\ " AND name='%s'" /* the result of this query is not an identifier, so use VERBATIM */ #define Query_for_list_of_available_extension_versions_with_TO \ " SELECT 'TO ' || version "\ " FROM pg_catalog.pg_available_extension_versions "\ -" WHERE substring('TO ' || version,1,%d)='%s'"\ +" WHERE ('TO ' || version) LIKE '%s'"\ " AND name='%s'" #define Query_for_list_of_prepared_statements \ " SELECT name "\ " FROM pg_catalog.pg_prepared_statements "\ -" WHERE substring(name,1,%d)='%s'" +" WHERE name LIKE '%s'" #define Query_for_list_of_event_triggers \ " SELECT evtname "\ " FROM pg_catalog.pg_event_trigger "\ -" WHERE substring(evtname,1,%d)='%s'" +" WHERE evtname LIKE '%s'" #define Query_for_list_of_tablesample_methods \ " 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(proname,1,%d)='%s'" +" proname LIKE '%s'" #define Query_for_list_of_policies \ " SELECT polname "\ " FROM pg_catalog.pg_policy "\ -" WHERE substring(polname,1,%d)='%s'" +" WHERE polname LIKE '%s'" #define Query_for_values_of_enum_GUC \ " SELECT val FROM ( "\ " SELECT name, pg_catalog.unnest(enumvals) AS val "\ " FROM pg_catalog.pg_settings "\ " ) ss "\ -" WHERE substring(val,1,%d)='%s'"\ +" WHERE val LIKE '%s'"\ " and pg_catalog.lower(name)=pg_catalog.lower('%s')" #define Query_for_list_of_channels \ " SELECT channel "\ " FROM pg_catalog.pg_listening_channels() AS channel "\ -" WHERE substring(channel,1,%d)='%s'" +" WHERE channel LIKE '%s'" #define Query_for_list_of_cursors \ " SELECT name "\ " FROM pg_catalog.pg_cursors "\ -" WHERE substring(name,1,%d)='%s'" +" WHERE name LIKE '%s'" /* * These object types were introduced later than our support cutoff of @@ -1095,7 +1094,7 @@ static const VersionedQuery Query_for_list_of_publications[] = { {100000, " SELECT pubname " " FROM pg_catalog.pg_publication " - " WHERE substring(pubname,1,%d)='%s'" + " WHERE pubname LIKE '%s'" }, {0, NULL} }; @@ -1104,7 +1103,7 @@ static const VersionedQuery Query_for_list_of_subscriptions[] = { {100000, " SELECT s.subname " " FROM pg_catalog.pg_subscription s, pg_catalog.pg_database d " - " WHERE substring(s.subname,1,%d)='%s' " + " WHERE s.subname LIKE '%s' " " AND d.datname = pg_catalog.current_database() " " AND s.subdbid = d.oid" }, @@ -1150,7 +1149,7 @@ static const pgsql_thing_t words_after_create[] = { * to be used only by pg_dump. */ {"CONFIGURATION", Query_for_list_of_ts_configurations, NULL, NULL, NULL, THING_NO_SHOW}, - {"CONVERSION", "SELECT conname FROM pg_catalog.pg_conversion WHERE substring(conname,1,%d)='%s'"}, + {"CONVERSION", "SELECT conname FROM pg_catalog.pg_conversion WHERE conname LIKE '%s'"}, {"DATABASE", Query_for_list_of_databases}, {"DEFAULT PRIVILEGES", NULL, NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP}, {"DICTIONARY", Query_for_list_of_ts_dictionaries, NULL, NULL, NULL, THING_NO_SHOW}, @@ -1175,7 +1174,7 @@ static const pgsql_thing_t words_after_create[] = { {"PUBLICATION", NULL, Query_for_list_of_publications}, {"ROLE", Query_for_list_of_roles}, {"ROUTINE", NULL, NULL, &Query_for_list_of_routines, NULL, THING_NO_CREATE}, - {"RULE", "SELECT rulename FROM pg_catalog.pg_rules WHERE substring(rulename,1,%d)='%s'"}, + {"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}, @@ -1191,7 +1190,7 @@ static const pgsql_thing_t words_after_create[] = { * TABLE ... */ {"TEXT SEARCH", NULL, NULL, NULL}, {"TRANSFORM", NULL, NULL, NULL, NULL, THING_NO_ALTER}, - {"TRIGGER", "SELECT tgname FROM pg_catalog.pg_trigger WHERE substring(tgname,1,%d)='%s' AND NOT tgisinternal"}, + {"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, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE UNIQUE * INDEX ... */ @@ -1273,6 +1272,7 @@ 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); @@ -1769,9 +1769,7 @@ psql_completion(const char *text, int start, int end) COMPLETE_WITH("(", "ALL TABLES IN SCHEMA", "TABLE"); else if (Matches("ALTER", "PUBLICATION", MatchAny, "ADD|DROP|SET", "ALL", "TABLES", "IN", "SCHEMA")) COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_schemas - " AND nspname != 'pg_catalog' " - " AND nspname not like 'pg\\_toast%%' " - " AND nspname not like 'pg\\_temp%%' ", + " AND nspname NOT LIKE E'pg\\\\_%'", "CURRENT_SCHEMA"); /* ALTER PUBLICATION <name> SET ( */ else if (HeadMatches("ALTER", "PUBLICATION", MatchAny) && TailMatches("SET", "(")) @@ -2905,9 +2903,7 @@ psql_completion(const char *text, int start, int end) */ else if (Matches("CREATE", "PUBLICATION", MatchAny, "FOR", "ALL", "TABLES", "IN", "SCHEMA")) COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_schemas - " AND nspname != 'pg_catalog' " - " AND nspname not like 'pg\\_toast%%' " - " AND nspname not like 'pg\\_temp%%' ", + " 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 ("); @@ -4113,10 +4109,13 @@ psql_completion(const char *text, int start, int end) "US", "European", "NonEuropean", "DEFAULT"); else if (TailMatches("search_path", "TO|=")) + { + /* Here, we want to allow pg_catalog, so use narrower exclusion */ COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_schemas - " AND nspname not like 'pg\\_toast%%' " - " AND nspname not like 'pg\\_temp%%' ", + " AND nspname NOT LIKE E'pg\\\\_toast%%'" + " AND nspname NOT LIKE E'pg\\\\_temp%%'", "DEFAULT"); + } else { /* generic, type based, GUC support */ @@ -4683,10 +4682,9 @@ 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 restriction clause of the form - * substring(OUTPUT,1,%d)='%s' - * where "OUTPUT" is the same string that the query returns. The %d and %s - * will be replaced by the string length of the text and the text itself, - * causing the results to be limited to those matching the already-typed text. + * 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_info_object. If there is a * third '%s', it will be replaced by a suitably-escaped version of the string @@ -4741,9 +4739,8 @@ _complete_from_query(const char *simple_query, PQExpBufferData query_buffer; char *schemaname; char *objectname; - int object_length = 0; + char *e_object_like; char *e_schemaname; - char *e_objectname; char *e_info_object; char *e_info_schema; @@ -4771,22 +4768,16 @@ _complete_from_query(const char *simple_query, non_empty_object = (*objectname != '\0'); /* - * Count length as number of characters (not bytes), for passing to - * substring + * Convert objectname to a LIKE prefix pattern (e.g. 'foo%'), and set + * up suitably-escaped copies of all the strings we need. */ - for (const char *p = objectname; - *p; - p += PQmblenBounded(p, pset.encoding)) - object_length++; + e_object_like = make_like_pattern(objectname); - /* Set up suitably-escaped copies of textual inputs */ if (schemaname) e_schemaname = escape_string(schemaname); else e_schemaname = NULL; - e_objectname = escape_string(objectname); - if (completion_info_object) e_info_object = escape_string(completion_info_object); else @@ -4825,9 +4816,9 @@ _complete_from_query(const char *simple_query, if (schema_query->selcondition) appendPQExpBuffer(&query_buffer, "%s AND ", schema_query->selcondition); - appendPQExpBuffer(&query_buffer, "substring(%s,1,%d)='%s'", + appendPQExpBuffer(&query_buffer, "(%s) LIKE '%s'", schema_query->result, - object_length, e_objectname); + e_object_like); if (schema_query->viscondition) appendPQExpBuffer(&query_buffer, " AND %s", schema_query->viscondition); @@ -4872,8 +4863,8 @@ _complete_from_query(const char *simple_query, appendPQExpBuffer(&query_buffer, "\nUNION ALL\n" "SELECT NULL::pg_catalog.text, n.nspname " "FROM pg_catalog.pg_namespace n " - "WHERE substring(n.nspname,1,%d)='%s'", - object_length, e_objectname); + "WHERE n.nspname LIKE '%s'", + e_object_like); /* * Likewise, suppress system schemas unless the @@ -4881,7 +4872,7 @@ _complete_from_query(const char *simple_query, */ if (strncmp(objectname, "pg_", 3) != 0) appendPQExpBufferStr(&query_buffer, - " AND n.nspname NOT LIKE 'pg\\_%'"); + " AND n.nspname NOT LIKE E'pg\\\\_%'"); /* * Since we're matching these schema names to the object @@ -4909,9 +4900,9 @@ _complete_from_query(const char *simple_query, if (schema_query->selcondition) appendPQExpBuffer(&query_buffer, "%s AND ", schema_query->selcondition); - appendPQExpBuffer(&query_buffer, "substring(%s,1,%d)='%s' AND ", + appendPQExpBuffer(&query_buffer, "(%s) LIKE '%s' AND ", schema_query->result, - object_length, e_objectname); + e_object_like); appendPQExpBuffer(&query_buffer, "n.nspname = '%s'", e_schemaname); if (schema_query->infoname) @@ -4936,7 +4927,7 @@ _complete_from_query(const char *simple_query, Assert(simple_query); /* simple_query is an sprintf-style format string */ appendPQExpBuffer(&query_buffer, simple_query, - object_length, e_objectname, + e_object_like, e_info_object, e_info_schema); } @@ -4949,9 +4940,9 @@ _complete_from_query(const char *simple_query, /* Clean up */ termPQExpBuffer(&query_buffer); + free(e_object_like); if (e_schemaname) free(e_schemaname); - free(e_objectname); if (e_info_object) free(e_info_object); if (e_info_schema) @@ -5408,6 +5399,48 @@ escape_string(const char *text) } +/* + * 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. *
pgsql-hackers by date: