Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 06a7c31

Browse files
committed
Allow most keywords to be used as column labels without requiring AS.
Up to now, if you tried to omit "AS" before a column label in a SELECT list, it would only work if the column label was an IDENT, that is not any known keyword. This is rather unfriendly considering that we have so many keywords and are constantly growing more. In the wake of commit 1ed6b89 it's possible to improve matters quite a bit. We'd originally tried to make this work by having some of the existing keyword categories be allowed without AS, but that didn't work too well, because each category contains a few special cases that don't work without AS. Instead, invent an entirely orthogonal keyword property "can be bare column label", and mark all keywords that way for which we don't get shift/reduce errors by doing so. It turns out that of our 450 current keywords, all but 39 can be made bare column labels, improving the situation by over 90%. This number might move around a little depending on future grammar work, but it's a pretty nice improvement. Mark Dilger, based on work by myself and Robert Haas; review by John Naylor Discussion: https://postgr.es/m/38ca86db-42ab-9b48-2902-337a0d6b8311@2ndquadrant.com
1 parent 0811f76 commit 06a7c31

File tree

14 files changed

+1055
-520
lines changed

14 files changed

+1055
-520
lines changed

doc/src/sgml/func.sgml

+11-2
Original file line numberDiff line numberDiff line change
@@ -22173,7 +22173,9 @@ SELECT pg_type_is_visible('myschema.widget'::regtype);
2217322173
<returnvalue>setof record</returnvalue>
2217422174
( <parameter>word</parameter> <type>text</type>,
2217522175
<parameter>catcode</parameter> <type>"char"</type>,
22176-
<parameter>catdesc</parameter> <type>text</type> )
22176+
<parameter>barelabel</parameter> <type>boolean</type>,
22177+
<parameter>catdesc</parameter> <type>text</type>,
22178+
<parameter>baredesc</parameter> <type>text</type> )
2217722179
</para>
2217822180
<para>
2217922181
Returns a set of records describing the SQL keywords recognized by the
@@ -22183,8 +22185,15 @@ SELECT pg_type_is_visible('myschema.widget'::regtype);
2218322185
keyword, <literal>C</literal> for a keyword that can be a column
2218422186
name, <literal>T</literal> for a keyword that can be a type or
2218522187
function name, or <literal>R</literal> for a fully reserved keyword.
22188+
The <parameter>barelabel</parameter> column
22189+
contains <literal>true</literal> if the keyword can be used as
22190+
a <quote>bare</quote> column label in <command>SELECT</command> lists,
22191+
or <literal>false</literal> if it can only be used
22192+
after <literal>AS</literal>.
2218622193
The <parameter>catdesc</parameter> column contains a
22187-
possibly-localized string describing the category.
22194+
possibly-localized string describing the keyword's category.
22195+
The <parameter>baredesc</parameter> column contains a
22196+
possibly-localized string describing the keyword's column label status.
2218822197
</para></entry>
2218922198
</row>
2219022199

doc/src/sgml/generate-keywords-table.pl

+10-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#!/usr/bin/perl
22
#
3-
# Generate the keywords table file
3+
# Generate the keywords table for the documentation's SQL Key Words appendix
4+
#
45
# Copyright (c) 2019-2020, PostgreSQL Global Development Group
56

67
use strict;
@@ -11,8 +12,9 @@
1112
my $srcdir = $ARGV[0];
1213

1314
my %keywords;
15+
my %as_keywords;
1416

15-
# read SQL keywords
17+
# read SQL-spec keywords
1618

1719
foreach my $ver (@sql_versions)
1820
{
@@ -39,9 +41,10 @@
3941

4042
while (<$fh>)
4143
{
42-
if (/^PG_KEYWORD\("(\w+)", \w+, (\w+)_KEYWORD\)/)
44+
if (/^PG_KEYWORD\("(\w+)", \w+, (\w+)_KEYWORD\, (\w+)\)/)
4345
{
4446
$keywords{ uc $1 }{'pg'}{ lc $2 } = 1;
47+
$as_keywords{ uc $1 } = 1 if $3 eq 'AS_LABEL';
4548
}
4649
}
4750

@@ -107,6 +110,10 @@ END
107110
{
108111
print "reserved";
109112
}
113+
if ($as_keywords{$word})
114+
{
115+
print ", requires <literal>AS</literal>";
116+
}
110117
print "</entry>\n";
111118

112119
foreach my $ver (@sql_versions)

doc/src/sgml/keywords.sgml

+17-7
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,11 @@
3232
</para>
3333

3434
<para>
35-
In the <productname>PostgreSQL</productname> parser life is a bit
35+
In the <productname>PostgreSQL</productname> parser, life is a bit
3636
more complicated. There are several different classes of tokens
3737
ranging from those that can never be used as an identifier to those
38-
that have absolutely no special status in the parser as compared to
39-
an ordinary identifier. (The latter is usually the case for
38+
that have absolutely no special status in the parser, but are considered
39+
ordinary identifiers. (The latter is usually the case for
4040
functions specified by SQL.) Even reserved key words are not
4141
completely reserved in <productname>PostgreSQL</productname>, but
4242
can be used as column labels (for example, <literal>SELECT 55 AS
@@ -57,14 +57,24 @@
5757
<quote>reserved</quote> are those tokens that are not allowed as
5858
column or table names. Some reserved key words are
5959
allowable as names for functions or data types; this is also shown in the
60-
table. If not so marked, a reserved key word is only allowed as an
61-
<quote>AS</quote> column label name.
60+
table. If not so marked, a reserved key word is only allowed as a
61+
column label.
62+
A blank entry in this column means that the word is treated as an
63+
ordinary identifier by <productname>PostgreSQL</productname>.
64+
</para>
65+
66+
<para>
67+
Furthermore, while most key words can be used as <quote>bare</quote>
68+
column labels without writing <literal>AS</literal> before them (as
69+
described in <xref linkend="queries-column-labels"/>), there are a few
70+
that require a leading <literal>AS</literal> to avoid ambiguity. These
71+
are marked in the table as <quote>requires <literal>AS</literal></quote>.
6272
</para>
6373

6474
<para>
6575
As a general rule, if you get spurious parser errors for commands
66-
that contain any of the listed key words as an identifier you should
67-
try to quote the identifier to see if the problem goes away.
76+
that use any of the listed key words as an identifier, you should
77+
try quoting the identifier to see if the problem goes away.
6878
</para>
6979

7080
<para>

doc/src/sgml/queries.sgml

+15-11
Original file line numberDiff line numberDiff line change
@@ -1496,21 +1496,25 @@ SELECT a AS value, b + c AS sum FROM ...
14961496
</para>
14971497

14981498
<para>
1499-
The <literal>AS</literal> keyword is optional, but only if the new column
1500-
name does not match any
1501-
<productname>PostgreSQL</productname> keyword (see <xref
1502-
linkend="sql-keywords-appendix"/>). To avoid an accidental match to
1503-
a keyword, you can double-quote the column name. For example,
1504-
<literal>VALUE</literal> is a keyword, so this does not work:
1499+
The <literal>AS</literal> key word is usually optional, but in some
1500+
cases where the desired column name matches a
1501+
<productname>PostgreSQL</productname> key word, you must write
1502+
<literal>AS</literal> or double-quote the column name in order to
1503+
avoid ambiguity.
1504+
(<xref linkend="sql-keywords-appendix"/> shows which key words
1505+
require <literal>AS</literal> to be used as a column label.)
1506+
For example, <literal>FROM</literal> is one such key word, so this
1507+
does not work:
15051508
<programlisting>
1506-
SELECT a value, b + c AS sum FROM ...
1509+
SELECT a from, b + c AS sum FROM ...
15071510
</programlisting>
1508-
but this does:
1511+
but either of these do:
15091512
<programlisting>
1510-
SELECT a "value", b + c AS sum FROM ...
1513+
SELECT a AS from, b + c AS sum FROM ...
1514+
SELECT a "from", b + c AS sum FROM ...
15111515
</programlisting>
1512-
For protection against possible
1513-
future keyword additions, it is recommended that you always either
1516+
For greatest safety against possible
1517+
future key word additions, it is recommended that you always either
15141518
write <literal>AS</literal> or double-quote the output column name.
15151519
</para>
15161520

src/backend/parser/check_keywords.pl

+68-24
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,28 @@ sub error
2121
return;
2222
}
2323

24+
# Check alphabetical order of a set of keyword symbols
25+
# (note these are NOT the actual keyword strings)
26+
sub check_alphabetical_order
27+
{
28+
my ($listname, $list) = @_;
29+
my $prevkword = '';
30+
31+
foreach my $kword (@$list)
32+
{
33+
# Some symbols have a _P suffix. Remove it for the comparison.
34+
my $bare_kword = $kword;
35+
$bare_kword =~ s/_P$//;
36+
if ($bare_kword le $prevkword)
37+
{
38+
error
39+
"'$bare_kword' after '$prevkword' in $listname list is misplaced";
40+
}
41+
$prevkword = $bare_kword;
42+
}
43+
return;
44+
}
45+
2446
$, = ' '; # set output field separator
2547
$\ = "\n"; # set output record separator
2648

@@ -33,9 +55,11 @@ sub error
3355
open(my $gram, '<', $gram_filename) || die("Could not open : $gram_filename");
3456

3557
my $kcat;
58+
my $in_bare_labels;
3659
my $comment;
3760
my @arr;
3861
my %keywords;
62+
my @bare_label_keywords;
3963

4064
line: while (my $S = <$gram>)
4165
{
@@ -51,7 +75,7 @@ sub error
5175
$s = '[/][*]', $S =~ s#$s# /* #g;
5276
$s = '[*][/]', $S =~ s#$s# */ #g;
5377

54-
if (!($kcat))
78+
if (!($kcat) && !($in_bare_labels))
5579
{
5680

5781
# Is this the beginning of a keyword list?
@@ -63,6 +87,10 @@ sub error
6387
next line;
6488
}
6589
}
90+
91+
# Is this the beginning of the bare_label_keyword list?
92+
$in_bare_labels = 1 if ($S =~ m/^bare_label_keyword:/);
93+
6694
next line;
6795
}
6896

@@ -97,7 +125,8 @@ sub error
97125
{
98126

99127
# end of keyword list
100-
$kcat = '';
128+
undef $kcat;
129+
undef $in_bare_labels;
101130
next;
102131
}
103132

@@ -107,31 +136,21 @@ sub error
107136
}
108137

109138
# Put this keyword into the right list
110-
push @{ $keywords{$kcat} }, $arr[$fieldIndexer];
139+
if ($in_bare_labels)
140+
{
141+
push @bare_label_keywords, $arr[$fieldIndexer];
142+
}
143+
else
144+
{
145+
push @{ $keywords{$kcat} }, $arr[$fieldIndexer];
146+
}
111147
}
112148
}
113149
close $gram;
114150

115151
# Check that each keyword list is in alphabetical order (just for neatnik-ism)
116-
my ($prevkword, $bare_kword);
117-
foreach my $kcat (keys %keyword_categories)
118-
{
119-
$prevkword = '';
120-
121-
foreach my $kword (@{ $keywords{$kcat} })
122-
{
123-
124-
# Some keyword have a _P suffix. Remove it for the comparison.
125-
$bare_kword = $kword;
126-
$bare_kword =~ s/_P$//;
127-
if ($bare_kword le $prevkword)
128-
{
129-
error
130-
"'$bare_kword' after '$prevkword' in $kcat list is misplaced";
131-
}
132-
$prevkword = $bare_kword;
133-
}
134-
}
152+
check_alphabetical_order($_, $keywords{$_}) for (keys %keyword_categories);
153+
check_alphabetical_order('bare_label_keyword', \@bare_label_keywords);
135154

136155
# Transform the keyword lists into hashes.
137156
# kwhashes is a hash of hashes, keyed by keyword category id,
@@ -147,6 +166,7 @@ sub error
147166

148167
$kwhashes{$kcat_id} = $hash;
149168
}
169+
my %bare_label_keywords = map { $_ => 1 } @bare_label_keywords;
150170

151171
# Now read in kwlist.h
152172

@@ -160,11 +180,12 @@ sub error
160180
{
161181
my ($line) = $_;
162182

163-
if ($line =~ /^PG_KEYWORD\(\"(.*)\", (.*), (.*)\)/)
183+
if ($line =~ /^PG_KEYWORD\(\"(.*)\", (.*), (.*), (.*)\)/)
164184
{
165185
my ($kwstring) = $1;
166186
my ($kwname) = $2;
167187
my ($kwcat_id) = $3;
188+
my ($collabel) = $4;
168189

169190
# Check that the list is in alphabetical order (critical!)
170191
if ($kwstring le $prevkwstring)
@@ -197,7 +218,7 @@ sub error
197218
"keyword name '$kwname' doesn't match keyword string '$kwstring'";
198219
}
199220

200-
# Check that the keyword is present in the grammar
221+
# Check that the keyword is present in the right category list
201222
%kwhash = %{ $kwhashes{$kwcat_id} };
202223

203224
if (!(%kwhash))
@@ -219,6 +240,29 @@ sub error
219240
delete $kwhashes{$kwcat_id}->{$kwname};
220241
}
221242
}
243+
244+
# Check that the keyword's collabel property matches gram.y
245+
if ($collabel eq 'BARE_LABEL')
246+
{
247+
unless ($bare_label_keywords{$kwname})
248+
{
249+
error
250+
"'$kwname' is marked as BARE_LABEL in kwlist.h, but it is missing from gram.y's bare_label_keyword rule";
251+
}
252+
}
253+
elsif ($collabel eq 'AS_LABEL')
254+
{
255+
if ($bare_label_keywords{$kwname})
256+
{
257+
error
258+
"'$kwname' is marked as AS_LABEL in kwlist.h, but it is listed in gram.y's bare_label_keyword rule";
259+
}
260+
}
261+
else
262+
{
263+
error
264+
"'$collabel' not recognized in kwlist.h. Expected either 'BARE_LABEL' or 'AS_LABEL'";
265+
}
222266
}
223267
}
224268
close $kwlist;

0 commit comments

Comments
 (0)