From: Tom Lane <tgl@sss.pgh.pa.us>
To: tanghy.fnst@fujitsu.com <tanghy.fnst@fujitsu.com>
Cc: Peter Eisentraut <peter.eisentraut@enterprisedb.com>
Cc: Japin Li <japinli@hotmail.com>
Cc: Kyotaro Horiguchi <horikyota.ntt@gmail.com>
Cc: Laurenz Albe <laurenz.albe@cybertec.at>
Cc: smithpb2250@gmail.com <smithpb2250@gmail.com>
Cc: david.zhang@highgo.ca <david.zhang@highgo.ca>
Cc: pgsql-hackers@lists.postgresql.org <pgsql-hackers@lists.postgresql.org>
Subject: Re: Support tab completion for upper character inputs in psql
Date: Tue, 25 Jan 2022 18:11:27 -0500
Message-ID: <2304294.1643152287@sss.pgh.pa.us> (raw)
In-Reply-To: <OS0PR01MB6113DB08D8CEA4E214DA37BBFB5F9@OS0PR01MB6113.jpnprd01.prod.outlook.com>
References: <OS0PR01MB611303B647967B6949B759F1FB4E9@OS0PR01MB6113.jpnprd01.prod.outlook.com>
<20210423.115812.900808736988307020.horikyota.ntt@gmail.com>
<OS0PR01MB6113CA04E06D5BF221BC4FE2FB429@OS0PR01MB6113.jpnprd01.prod.outlook.com>
<OS0PR01MB61136721363BD08576725F22FB089@OS0PR01MB6113.jpnprd01.prod.outlook.com>
<80b093ae-88bd-8d8a-68b9-b535a3ab11be@enterprisedb.com>
<OS0PR01MB61137C7B2B09C0431415B9DBFBD69@OS0PR01MB6113.jpnprd01.prod.outlook.com>
<54bd7839-f463-ef03-f2d2-ccb8184b9b27@enterprisedb.com>
<1068530.1641480996@sss.pgh.pa.us>
<TYCPR01MB6128BD8EA1529F4270114C18FB4D9@TYCPR01MB6128.jpnprd01.prod.outlook.com>
<MEYP282MB1669EAF2183E67EAF86F7F61B64D9@MEYP282MB1669.AUSP282.PROD.OUTLOOK.COM>
<TYCPR01MB6128D4933189A74AAD326F98FB4D9@TYCPR01MB6128.jpnprd01.prod.outlook.com>
<c7fb9ad1-0b77-eda5-0055-02ee472e3a4a@enterprisedb.co! m>
<3547066.1642272686@sss.pgh.pa.us>
<OS0PR01MB6113C130461FB8A9DBA0DAEBFB5A9@OS0PR01MB6113.jpnprd01.prod.outlook.com>
<17d022ce-2543-b4a3-2b55-fc04! ae87a220@enterprisedb.com>
<OS0PR01MB6113DB08D8CEA4E214DA37BBFB5F9@OS0PR01MB6113.jpnprd01.prod.outlook.com>
I spent some time contemplating my navel about the concerns I raised
upthread about double-quoted identifiers. I concluded that the reason
things don't work well in that area is that we're trying to get all the
work done by applying quote_ident() on the backend side and then
ignoring quoting considerations in tab-complete itself. That sort of
works, but not terribly well. The currently proposed patch is sticking
a toe into the water of dealing with quoting/downcasing in tab-complete,
but we need to go a lot further. I propose that we ought to drop the
use of quote_ident() in the tab completion queries altogether, instead
having the backend return names as-is, and doing all the dequoting and
requoting work in tab-complete.
Attached is a very-much-WIP patch along these lines. I make no
pretense that it's complete; no doubt some of the individual
queries are broken or don't return quite the results we want.
But it seems to act the way I think it should for relation names.
One thing I'm particularly unsure what to do with is the queries
for type names, which want to match against the output of
format_type, which'll already have applied quote_ident. We can
probably hack something up there, but I ran out of time to mess
with that for today.
Anyway, I wanted to post this just to see what people think of
going in this direction.
regards, tom lane
PS: I omitted the proposed regression test changes here.
Many of them are not at all portable --- different versions
of readline/libedit will produce different control character
sequences for backspacing, for example. I got a lot of
failures when I tried to use those tests with this patch;
I've not run down which ones are test portability problems,
which are due to intentional behavior changes in this patch,
and which are due to breakage I've not fixed yet.
Attachments:rethink-tab-complete-quote-handling-wip.patch (text/x-diff, 62.5K) downloaddiff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.cindex 502b5c5751..2dadf7d945 100644--- a/src/bin/psql/tab-complete.c+++ b/src/bin/psql/tab-complete.c@@ -47,6 +47,7 @@
#include "catalog/pg_class_d.h"
#include "common.h"
#include "libpq-fe.h"
+#include "mb/pg_wchar.h"
#include "pqexpbuffer.h"
#include "settings.h"
#include "stringutils.h"
@@ -148,8 +149,8 @@ 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 (unquoted) name to return, in the case of an unqualified+ * name. For example, "c.relname".
*/
const char *result;
@@ -315,7 +316,7 @@ do { \
completion_info_charp = _completion_type; \
completion_info_charp2 = _completion_schema; \
} \
- matches = rl_completion_matches(text, complete_from_query); \+ matches = rl_completion_matches(text, complete_from_query_verbatim); \
} while (0)
#define COMPLETE_WITH_FUNCTION_ARG(function) \
@@ -357,14 +358,14 @@ 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",
}
};
@@ -378,7 +379,7 @@ static const SchemaQuery Query_for_list_of_datatypes = {
.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)",+ .qualresult = "t.typname",
};
static const SchemaQuery Query_for_list_of_composite_datatypes = {
@@ -390,7 +391,7 @@ static const SchemaQuery Query_for_list_of_composite_datatypes = {
.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)",+ .qualresult = "t.typname",
};
static const SchemaQuery Query_for_list_of_domains = {
@@ -398,7 +399,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 +410,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 +427,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 +439,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 +447,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 +455,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 +465,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 +473,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 +481,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 +489,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 +499,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 +507,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 +516,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 +526,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 +546,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 +561,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 +573,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 +589,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 +601,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 +618,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 +626,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 +641,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 +660,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 +677,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 +699,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 +708,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 +718,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 +729,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,14 +754,14 @@ 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 "\
@@ -789,14 +790,14 @@ static const SchemaQuery Query_for_list_of_collations = {
" 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) "\+" SELECT rolname "\
" FROM pg_catalog.pg_roles "\
-" WHERE substring(pg_catalog.quote_ident(rolname),1,%d)='%s'"\+" WHERE substring(rolname,1,%d)='%s'"\
" UNION ALL SELECT 'PUBLIC'"\
" UNION ALL SELECT 'CURRENT_ROLE'"\
" UNION ALL SELECT 'CURRENT_USER'"\
@@ -804,11 +805,11 @@ static const SchemaQuery Query_for_list_of_collations = {
/* 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 c1.relname='%s'"\
" and pg_catalog.pg_table_is_visible(c2.oid)"
#define Query_for_unique_index_of_table \
@@ -817,124 +818,124 @@ Query_for_index_of_table \
/* 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'"\+" 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) "\+"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'"\+" and c1.relname='%s'"\
" and pg_catalog.pg_table_is_visible(c1.oid)" \
" 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 "
/* 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'"\+" 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'))"\
" 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'"\+" 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'))"\
" 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'"\+" 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'))"\
" 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 */
@@ -942,7 +943,7 @@ Query_for_index_of_table \
"SELECT pg_catalog.oidvectortypes(proargtypes)||')' "\
" FROM pg_catalog.pg_proc "\
" WHERE (%d = pg_catalog.length('%s'))"\
-" AND (pg_catalog.quote_ident(proname)='%s'"\+" AND (proname='%s'"\
" OR '\"' || proname || '\"'='%s') "\
" AND (pg_catalog.pg_function_is_visible(pg_proc.oid))"
@@ -952,68 +953,68 @@ Query_for_index_of_table \
" FROM pg_catalog.pg_proc p, pg_catalog.pg_namespace n "\
" WHERE (%d = pg_catalog.length('%s'))"\
" AND n.oid = p.pronamespace "\
-" AND (pg_catalog.quote_ident(proname)='%s' "\+" 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 */
#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'"+" AND name='%s'"
/* the silly-looking length condition is just to eat up the current word */
#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'"+" 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'))"\
" 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 " \
@@ -1022,18 +1023,18 @@ Query_for_index_of_table \
/* 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 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 +1044,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"
},
@@ -1092,7 +1093,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, 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'"},+ {"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},
@@ -1117,7 +1118,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, 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'"},+ {"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},
@@ -1133,7 +1134,7 @@ static const pgsql_thing_t words_after_create[] = {
* 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"},+ {"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 ... */
@@ -1195,11 +1196,13 @@ static char *create_command_generator(const char *text, int state);
static char *drop_command_generator(const char *text, int state);
static char *alter_command_generator(const char *text, int state);
static char *complete_from_query(const char *text, int state);
+static char *complete_from_query_verbatim(const char *text, int state);
static char *complete_from_versioned_query(const char *text, int state);
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,
+ 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 +1215,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);
@@ -1651,7 +1660,7 @@ 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'");+ "UNION SELECT 'ALL IN TABLESPACE', NULL, true");
/* ALTER something */
else if (Matches("ALTER"))
@@ -1812,7 +1821,7 @@ 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'");+ "UNION SELECT 'ALL IN TABLESPACE', NULL, true");
/* ALTER INDEX <name> */
else if (Matches("ALTER", "INDEX", MatchAny))
COMPLETE_WITH("ALTER COLUMN", "OWNER TO", "RENAME TO", "SET",
@@ -1879,7 +1888,7 @@ 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'");+ "UNION SELECT 'ALL IN TABLESPACE', NULL, true");
/* ALTER USER,ROLE <name> */
else if (Matches("ALTER", "USER|ROLE", MatchAny) &&
@@ -2164,13 +2173,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, NULL);
/* 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, NULL);
/* ALTER TABLE xxx DISABLE */
else if (Matches("ALTER", "TABLE", MatchAny, "DISABLE"))
COMPLETE_WITH("ROW LEVEL SECURITY", "RULE", "TRIGGER");
@@ -2298,7 +2307,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, NULL);
/* Limited completion support for partition bound specification */
else if (TailMatches("ATTACH", "PARTITION", MatchAny))
COMPLETE_WITH("FOR VALUES", "DEFAULT");
@@ -2386,7 +2395,7 @@ psql_completion(const char *text, int start, int end)
*/
else if (Matches("ANALYZE"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_analyzables,
- " UNION SELECT 'VERBOSE'");+ " UNION SELECT 'VERBOSE', NULL, true");
else if (HeadMatches("ANALYZE", "(*") &&
!HeadMatches("ANALYZE", "(*)"))
{
@@ -2434,7 +2443,8 @@ psql_completion(const char *text, int start, int end)
" UNION SELECT 'ALL'");
/* CLUSTER */
else if (Matches("CLUSTER"))
- COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_clusterables, "UNION SELECT 'VERBOSE'");+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_clusterables,+ "UNION SELECT 'VERBOSE', NULL, true");
else if (Matches("CLUSTER", "VERBOSE") ||
Matches("CLUSTER", "(*)"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_clusterables, NULL);
@@ -2558,7 +2568,7 @@ psql_completion(const char *text, int start, int end)
*/
else if (Matches("COPY|\\copy"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
- " UNION ALL SELECT '('");+ " UNION ALL SELECT '(', NULL, true");
/* Complete COPY ( with legal query commands */
else if (Matches("COPY|\\copy", "("))
COMPLETE_WITH("SELECT", "TABLE", "VALUES", "INSERT INTO", "UPDATE", "DELETE FROM", "WITH");
@@ -2686,8 +2696,8 @@ psql_completion(const char *text, int start, int end)
*/
else if (TailMatches("CREATE|UNIQUE", "INDEX"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
- " UNION SELECT 'ON'"- " UNION SELECT 'CONCURRENTLY'");+ " UNION SELECT 'ON', NULL, true"+ " UNION SELECT 'CONCURRENTLY', NULL, true");
/*
* Complete ... INDEX|CONCURRENTLY [<name>] ON with a list of relations
@@ -2703,7 +2713,7 @@ psql_completion(const char *text, int start, int end)
*/
else if (TailMatches("CREATE|UNIQUE", "INDEX", "CONCURRENTLY"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
- " UNION SELECT 'ON'");+ " UNION SELECT 'ON', NULL, true");
/* Complete CREATE|UNIQUE INDEX [CONCURRENTLY] <sth> with "ON" */
else if (TailMatches("CREATE|UNIQUE", "INDEX", MatchAny) ||
TailMatches("CREATE|UNIQUE", "INDEX", "CONCURRENTLY", MatchAny))
@@ -2912,7 +2922,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, NULL);
/* Limited completion support for partition bound specification */
else if (TailMatches("PARTITION", "OF", MatchAny))
COMPLETE_WITH("FOR VALUES", "DEFAULT");
@@ -3359,7 +3369,7 @@ 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'");+ " UNION SELECT 'CONCURRENTLY', NULL, true");
else if (Matches("DROP", "INDEX", "CONCURRENTLY"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
else if (Matches("DROP", "INDEX", MatchAny))
@@ -3618,25 +3628,25 @@ psql_completion(const char *text, int start, int end)
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'");+ " UNION SELECT 'ALL FUNCTIONS IN SCHEMA', NULL, true"+ " UNION SELECT 'ALL PROCEDURES IN SCHEMA', NULL, true"+ " UNION SELECT 'ALL ROUTINES IN SCHEMA', NULL, true"+ " UNION SELECT 'ALL SEQUENCES IN SCHEMA', NULL, true"+ " UNION SELECT 'ALL TABLES IN SCHEMA', NULL, true"+ " UNION SELECT 'DATABASE', NULL, true"+ " UNION SELECT 'DOMAIN', NULL, true"+ " UNION SELECT 'FOREIGN DATA WRAPPER', NULL, true"+ " UNION SELECT 'FOREIGN SERVER', NULL, true"+ " UNION SELECT 'FUNCTION', NULL, true"+ " UNION SELECT 'LANGUAGE', NULL, true"+ " UNION SELECT 'LARGE OBJECT', NULL, true"+ " UNION SELECT 'PROCEDURE', NULL, true"+ " UNION SELECT 'ROUTINE', NULL, true"+ " UNION SELECT 'SCHEMA', NULL, true"+ " UNION SELECT 'SEQUENCE', NULL, true"+ " UNION SELECT 'TABLE', NULL, true"+ " UNION SELECT 'TABLESPACE', NULL, true"+ " UNION SELECT 'TYPE', NULL, true");
}
else if (TailMatches("GRANT|REVOKE", MatchAny, "ON", "ALL"))
COMPLETE_WITH("FUNCTIONS IN SCHEMA",
@@ -3789,11 +3799,11 @@ psql_completion(const char *text, int start, int end)
/* 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'");+ " UNION SELECT 'TABLE', NULL, true"+ " UNION SELECT 'ONLY', NULL, true");
else if (Matches("LOCK", "TABLE"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables,
- " UNION SELECT 'ONLY'");+ " UNION SELECT 'ONLY', NULL, true");
else if (Matches("LOCK", "TABLE", "ONLY") || Matches("LOCK", "ONLY"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
/* For the following, handle the case of a single table only for now */
@@ -3831,7 +3841,7 @@ psql_completion(const char *text, int start, int end)
/* NOTIFY --- can be inside EXPLAIN, RULE, etc */
else if (TailMatches("NOTIFY"))
- COMPLETE_WITH_QUERY("SELECT pg_catalog.quote_ident(channel) FROM pg_catalog.pg_listening_channels() AS channel WHERE substring(pg_catalog.quote_ident(channel),1,%d)='%s'");+ COMPLETE_WITH_QUERY("SELECT channel FROM pg_catalog.pg_listening_channels() AS channel WHERE substring(channel,1,%d)='%s'");
/* OPTIONS */
else if (TailMatches("OPTIONS"))
@@ -3875,7 +3885,7 @@ psql_completion(const char *text, int start, int end)
COMPLETE_WITH("VIEW");
else if (Matches("REFRESH", "MATERIALIZED", "VIEW"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews,
- " UNION SELECT 'CONCURRENTLY'");+ " UNION SELECT 'CONCURRENTLY', NULL, true");
else if (Matches("REFRESH", "MATERIALIZED", "VIEW", "CONCURRENTLY"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_matviews, NULL);
else if (Matches("REFRESH", "MATERIALIZED", "VIEW", MatchAny))
@@ -3898,15 +3908,15 @@ psql_completion(const char *text, int start, int end)
else if (Matches("REINDEX", "TABLE") ||
Matches("REINDEX", "(*)", "TABLE"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexables,
- " UNION SELECT 'CONCURRENTLY'");+ " UNION SELECT 'CONCURRENTLY', NULL, true");
else if (Matches("REINDEX", "INDEX") ||
Matches("REINDEX", "(*)", "INDEX"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
- " UNION SELECT 'CONCURRENTLY'");+ " UNION SELECT 'CONCURRENTLY', NULL, true");
else if (Matches("REINDEX", "SCHEMA") ||
Matches("REINDEX", "(*)", "SCHEMA"))
COMPLETE_WITH_QUERY(Query_for_list_of_schemas
- " UNION SELECT 'CONCURRENTLY'");+ " UNION SELECT 'CONCURRENTLY', NULL, true");
else if (Matches("REINDEX", "SYSTEM|DATABASE") ||
Matches("REINDEX", "(*)", "SYSTEM|DATABASE"))
COMPLETE_WITH_QUERY(Query_for_list_of_databases
@@ -3997,7 +4007,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(Query_for_list_of_constraints_with_schema,+ "UNION SELECT 'ALL', NULL, true");
/* Complete SET CONSTRAINTS <foo> with DEFERRED|IMMEDIATE */
else if (Matches("SET", "CONSTRAINTS", MatchAny))
COMPLETE_WITH("DEFERRED", "IMMEDIATE");
@@ -4091,11 +4102,11 @@ 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'");+ " UNION SELECT 'TABLE', NULL, true"+ " UNION SELECT 'ONLY', NULL, true");
else if (Matches("TRUNCATE", "TABLE"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_truncatables,
- " UNION SELECT 'ONLY'");+ " UNION SELECT 'ONLY', NULL, true");
else if (HeadMatches("TRUNCATE") && TailMatches("ONLY"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_truncatables, NULL);
else if (Matches("TRUNCATE", MatchAny) ||
@@ -4107,7 +4118,7 @@ psql_completion(const char *text, int start, int end)
/* UNLISTEN */
else if (Matches("UNLISTEN"))
- COMPLETE_WITH_QUERY("SELECT pg_catalog.quote_ident(channel) FROM pg_catalog.pg_listening_channels() AS channel WHERE substring(pg_catalog.quote_ident(channel),1,%d)='%s' UNION SELECT '*'");+ COMPLETE_WITH_QUERY("SELECT channel FROM pg_catalog.pg_listening_channels() AS channel WHERE substring(channel,1,%d)='%s' UNION SELECT '*'");
/* UPDATE --- can be inside EXPLAIN, RULE, etc */
/* If prev. word is UPDATE suggest a list of tables */
@@ -4145,25 +4156,25 @@ psql_completion(const char *text, int start, int end)
*/
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'");+ " UNION SELECT 'FULL', NULL, true"+ " UNION SELECT 'FREEZE', NULL, true"+ " UNION SELECT 'ANALYZE', NULL, true"+ " UNION SELECT 'VERBOSE', NULL, true");
else if (Matches("VACUUM", "FULL"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_vacuumables,
- " UNION SELECT 'FREEZE'"- " UNION SELECT 'ANALYZE'"- " UNION SELECT 'VERBOSE'");+ " UNION SELECT 'FREEZE', NULL, true"+ " UNION SELECT 'ANALYZE', NULL, true"+ " UNION SELECT 'VERBOSE', NULL, true");
else if (Matches("VACUUM", "FREEZE") ||
Matches("VACUUM", "FULL", "FREEZE"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_vacuumables,
- " UNION SELECT 'VERBOSE'"- " UNION SELECT 'ANALYZE'");+ " UNION SELECT 'VERBOSE', NULL, true"+ " UNION SELECT 'ANALYZE', NULL, true");
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'");+ " UNION SELECT 'ANALYZE', NULL, true");
else if (HeadMatches("VACUUM", "(*") &&
!HeadMatches("VACUUM", "(*)"))
{
@@ -4551,7 +4562,14 @@ 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, false, text, state);+}++static char *+complete_from_query_verbatim(const char *text, int state)+{+ /* query is assumed to work for any server version */+ return _complete_from_query(completion_charp, NULL, true, text, state);
}
static char *
@@ -4566,7 +4584,7 @@ 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, false, text, state);
}
static char *
@@ -4574,7 +4592,7 @@ 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);+ false, text, state);
}
static char *
@@ -4600,7 +4618,7 @@ complete_from_versioned_schema_query(const char *text, int state)
}
return _complete_from_query(vquery ? vquery->query : NULL,
- squery, text, state);+ squery, false, text, state);
}
@@ -4618,28 +4636,40 @@ complete_from_versioned_schema_query(const char *text, int state)
* completion_info_charp2.
*
* 2. A schema query used for completion of both schema and relation names.
- * These are more complex and must contain in the following order:- * %d %s %d %s %d %s %s %d %s- * where %d is the string length of the text and %s the text itself.+ * This is represented by a SchemaQuery object; see that typedef for details.+ *+ * In either case, the query can return up to three columns: an object name+ * column, a schema name column, and a boolean column which if TRUE indicates+ * that the object-name column should be returned verbatim. If the boolean+ * column is false or omitted, we will combine and appropriately quote the+ * schema and object names.
*
* 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.
+ * The simple query must return all three columns in this case.
*
* It is assumed that strings should be escaped to become SQL literals
* (that is, what is in the query is actually ... '%s' ...)
*
* See top of file for examples of both kinds of query.
*
+ * 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. This also changes the+ * default assumption about whether the results are verbatim.+ *
* "text" and "state" are supplied by readline.
*/
static char *
_complete_from_query(const char *simple_query,
const SchemaQuery *schema_query,
+ bool verbatim,
const char *text, int state)
{
- static int list_index,- byte_length;+ static int list_index;
static PGresult *result = NULL;
+ static bool schemaquoted;+ static bool objectquoted;
/*
* If this is the first time for this completion, we fetch a list of our
@@ -4648,31 +4678,56 @@ _complete_from_query(const char *simple_query,
if (state == 0)
{
PQExpBufferData query_buffer;
- char *e_text;+ char *schemaname;+ char *objectname;+ int schema_length = 0;+ 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);+ PQclear(result);+ result = NULL;++ if (verbatim)+ {+ objectname = pg_strdup(text);+ schemaname = NULL;+ }+ else+ {+ /* Parse text, splitting into schema and object name if needed */+ parse_identifier(text,+ &schemaname, &objectname,+ &schemaquoted, &objectquoted);+ }
/*
- * Count length as number of characters (not bytes), for passing to+ * Count lengths as number of characters (not bytes), for passing to
* substring
*/
- while (*pstr)+ if (schemaname)
{
- char_length++;- pstr += PQmblenBounded(pstr, pset.encoding);+ for (const char *p = schemaname;+ *p;+ p += PQmblenBounded(p, pset.encoding))+ schema_length++;
}
-- /* 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);
@@ -4694,8 +4749,14 @@ _complete_from_query(const char *simple_query,
if (qualresult == NULL)
qualresult = schema_query->result;
+ /*+ * We issue different queries depending on whether the input is+ * qualified or not.+ */+ 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, false FROM %s WHERE ",
schema_query->result,
schema_query->catname);
if (schema_query->selcondition)
@@ -4703,7 +4764,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,35 +4776,32 @@ _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 matching schema names, but only if there is more than- * one potential match among schema names.- */+ /* Add in schema names matching the input-so-far */
appendPQExpBuffer(&query_buffer, "\nUNION\n"
- "SELECT pg_catalog.quote_ident(n.nspname) || '.' "+ "SELECT NULL::pg_catalog.text, n.nspname, false "
"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);+ "WHERE substring(n.nspname,1,%d)='%s'",+ object_length, e_objectname);
/*
- * Add in matching qualified names, but only if there is exactly- * one schema matching the input-so-far.+ * Likewise, suppress system schemas unless the input-so-far+ * begins with "pg_".
*/
- appendPQExpBuffer(&query_buffer, "\nUNION\n"- "SELECT pg_catalog.quote_ident(n.nspname) || '.' || %s "+ if (strncmp(objectname, "pg_", 3) != 0)+ appendPQExpBufferStr(&query_buffer,+ " AND n.nspname NOT LIKE 'pg\\_%'");+ }+ else+ {+ /* Input is qualified, so produce only qualified names */+ appendPQExpBuffer(&query_buffer, "SELECT %s, n.nspname, false "
"FROM %s, pg_catalog.pg_namespace n "
"WHERE %s = n.oid AND ",
qualresult,
@@ -4752,24 +4810,12 @@ _complete_from_query(const char *simple_query,
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'",+ appendPQExpBuffer(&query_buffer, "substring(%s,1,%d)='%s' AND ",
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);+ object_length, e_objectname);+ appendPQExpBuffer(&query_buffer, "substring(n.nspname,1,%d)='%s'",+ schema_length, e_schemaname);+ }
/* If an addon query was provided, use it */
if (simple_query)
@@ -4780,7 +4826,7 @@ _complete_from_query(const char *simple_query,
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);
}
@@ -4792,25 +4838,49 @@ _complete_from_query(const char *simple_query,
result = exec_query(query_buffer.data);
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 */- if (result && PQresultStatus(result) == PGRES_TUPLES_OK)+ /* Return the next result, if there is one */+ if (result && PQresultStatus(result) == PGRES_TUPLES_OK &&+ list_index < PQntuples(result))
{
- const char *item;+ const char *item = NULL;+ const char *nsp = NULL;+ bool thisverbatim = verbatim;++ 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);+ if (PQnfields(result) > 2)+ thisverbatim = (PQgetvalue(result, list_index, 2)[0] == 't');+ list_index++;++ if (thisverbatim)+ return pg_strdup(item);- while (list_index < PQntuples(result) &&- (item = PQgetvalue(result, list_index++, 0)))- if (pg_strncasecmp(text, item, byte_length) == 0)- return pg_strdup(item);+ /*+ * Hack: if we're returning one single schema name, don't let Readline+ * add a space after it. Otherwise it'll stop being part of the+ * completion subject text, which is not what we want.+ */+#ifdef HAVE_RL_COMPLETION_APPEND_CHARACTER+ if (item == NULL && nsp != NULL && PQntuples(result) == 1)+ rl_completion_append_character = '\0';+#endif++ return requote_identifier(nsp, item, schemaquoted, objectquoted);
}
- /* If nothing matches, free the db structure and return null */+ /* If nothing (else) matches, free the db structure and return null */
PQclear(result);
result = NULL;
return NULL;
@@ -5144,6 +5214,199 @@ 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 (!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.+ */+static bool+identifier_needs_quotes(const char *ident)+{+ if (!((ident[0] >= 'a' && ident[0] <= 'z') || ident[0] == '_'))+ return true;+ if (strspn(ident, "abcdefghijklmnopqrstuvwxyz0123456789_") != strlen(ident))+ return true;++ /* XXX is it worth checking for SQL reserved words? */++ return false;+}++
/*
* Execute a query and report any errors. This should be the preferred way of
* talking to the database in this file.
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Reply to all the recipients using the --to and --cc options:
reply via email
To: pgsql-hackers@postgresql.org
Cc: tgl@sss.pgh.pa.us, tanghy.fnst@fujitsu.com, peter.eisentraut@enterprisedb.com, japinli@hotmail.com, horikyota.ntt@gmail.com, laurenz.albe@cybertec.at, smithpb2250@gmail.com, david.zhang@highgo.ca, pgsql-hackers@lists.postgresql.org
Subject: Re: Support tab completion for upper character inputs in psql
In-Reply-To: <2304294.1643152287@sss.pgh.pa.us>
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
This inbox is served by DDX for PostgreSQL; see mirroring instructions
for how to clone and mirror all data and code used for this inbox