1
0
mirror of https://github.com/postgres/postgres.git synced 2025-06-16 06:01:02 +03:00

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
1ed6b8956 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
This commit is contained in:
Tom Lane
2020-09-18 16:46:26 -04:00
parent 0811f766fd
commit 06a7c3154f
14 changed files with 1055 additions and 520 deletions

View File

@ -21,6 +21,28 @@ sub error
return;
}
# Check alphabetical order of a set of keyword symbols
# (note these are NOT the actual keyword strings)
sub check_alphabetical_order
{
my ($listname, $list) = @_;
my $prevkword = '';
foreach my $kword (@$list)
{
# Some symbols have a _P suffix. Remove it for the comparison.
my $bare_kword = $kword;
$bare_kword =~ s/_P$//;
if ($bare_kword le $prevkword)
{
error
"'$bare_kword' after '$prevkword' in $listname list is misplaced";
}
$prevkword = $bare_kword;
}
return;
}
$, = ' '; # set output field separator
$\ = "\n"; # set output record separator
@ -33,9 +55,11 @@ $keyword_categories{'reserved_keyword'} = 'RESERVED_KEYWORD';
open(my $gram, '<', $gram_filename) || die("Could not open : $gram_filename");
my $kcat;
my $in_bare_labels;
my $comment;
my @arr;
my %keywords;
my @bare_label_keywords;
line: while (my $S = <$gram>)
{
@ -51,7 +75,7 @@ line: while (my $S = <$gram>)
$s = '[/][*]', $S =~ s#$s# /* #g;
$s = '[*][/]', $S =~ s#$s# */ #g;
if (!($kcat))
if (!($kcat) && !($in_bare_labels))
{
# Is this the beginning of a keyword list?
@ -63,6 +87,10 @@ line: while (my $S = <$gram>)
next line;
}
}
# Is this the beginning of the bare_label_keyword list?
$in_bare_labels = 1 if ($S =~ m/^bare_label_keyword:/);
next line;
}
@ -97,7 +125,8 @@ line: while (my $S = <$gram>)
{
# end of keyword list
$kcat = '';
undef $kcat;
undef $in_bare_labels;
next;
}
@ -107,31 +136,21 @@ line: while (my $S = <$gram>)
}
# Put this keyword into the right list
push @{ $keywords{$kcat} }, $arr[$fieldIndexer];
if ($in_bare_labels)
{
push @bare_label_keywords, $arr[$fieldIndexer];
}
else
{
push @{ $keywords{$kcat} }, $arr[$fieldIndexer];
}
}
}
close $gram;
# Check that each keyword list is in alphabetical order (just for neatnik-ism)
my ($prevkword, $bare_kword);
foreach my $kcat (keys %keyword_categories)
{
$prevkword = '';
foreach my $kword (@{ $keywords{$kcat} })
{
# Some keyword have a _P suffix. Remove it for the comparison.
$bare_kword = $kword;
$bare_kword =~ s/_P$//;
if ($bare_kword le $prevkword)
{
error
"'$bare_kword' after '$prevkword' in $kcat list is misplaced";
}
$prevkword = $bare_kword;
}
}
check_alphabetical_order($_, $keywords{$_}) for (keys %keyword_categories);
check_alphabetical_order('bare_label_keyword', \@bare_label_keywords);
# Transform the keyword lists into hashes.
# kwhashes is a hash of hashes, keyed by keyword category id,
@ -147,6 +166,7 @@ while (my ($kcat, $kcat_id) = each(%keyword_categories))
$kwhashes{$kcat_id} = $hash;
}
my %bare_label_keywords = map { $_ => 1 } @bare_label_keywords;
# Now read in kwlist.h
@ -160,11 +180,12 @@ kwlist_line: while (<$kwlist>)
{
my ($line) = $_;
if ($line =~ /^PG_KEYWORD\(\"(.*)\", (.*), (.*)\)/)
if ($line =~ /^PG_KEYWORD\(\"(.*)\", (.*), (.*), (.*)\)/)
{
my ($kwstring) = $1;
my ($kwname) = $2;
my ($kwcat_id) = $3;
my ($collabel) = $4;
# Check that the list is in alphabetical order (critical!)
if ($kwstring le $prevkwstring)
@ -197,7 +218,7 @@ kwlist_line: while (<$kwlist>)
"keyword name '$kwname' doesn't match keyword string '$kwstring'";
}
# Check that the keyword is present in the grammar
# Check that the keyword is present in the right category list
%kwhash = %{ $kwhashes{$kwcat_id} };
if (!(%kwhash))
@ -219,6 +240,29 @@ kwlist_line: while (<$kwlist>)
delete $kwhashes{$kwcat_id}->{$kwname};
}
}
# Check that the keyword's collabel property matches gram.y
if ($collabel eq 'BARE_LABEL')
{
unless ($bare_label_keywords{$kwname})
{
error
"'$kwname' is marked as BARE_LABEL in kwlist.h, but it is missing from gram.y's bare_label_keyword rule";
}
}
elsif ($collabel eq 'AS_LABEL')
{
if ($bare_label_keywords{$kwname})
{
error
"'$kwname' is marked as AS_LABEL in kwlist.h, but it is listed in gram.y's bare_label_keyword rule";
}
}
else
{
error
"'$collabel' not recognized in kwlist.h. Expected either 'BARE_LABEL' or 'AS_LABEL'";
}
}
}
close $kwlist;

View File

@ -540,14 +540,16 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <str> Sconst comment_text notify_payload
%type <str> RoleId opt_boolean_or_string
%type <list> var_list
%type <str> ColId ColLabel var_name type_function_name param_name
%type <str> ColId ColLabel BareColLabel
%type <str> NonReservedWord NonReservedWord_or_Sconst
%type <str> var_name type_function_name param_name
%type <str> createdb_opt_name
%type <node> var_value zone_value
%type <rolespec> auth_ident RoleSpec opt_granted_by
%type <keyword> unreserved_keyword type_func_name_keyword
%type <keyword> col_name_keyword reserved_keyword
%type <keyword> bare_label_keyword
%type <node> TableConstraint TableLikeClause
%type <ival> TableLikeOptionList TableLikeOption
@ -14658,11 +14660,7 @@ target_el: a_expr AS ColLabel
$$->val = (Node *)$1;
$$->location = @1;
}
/*
* We support omitting AS only for column labels that aren't
* any known keyword.
*/
| a_expr IDENT
| a_expr BareColLabel
{
$$ = makeNode(ResTarget);
$$->name = $2;
@ -15011,6 +15009,13 @@ ColLabel: IDENT { $$ = $1; }
| reserved_keyword { $$ = pstrdup($1); }
;
/* Bare column label --- names that can be column labels without writing "AS".
* This classification is orthogonal to the other keyword categories.
*/
BareColLabel: IDENT { $$ = $1; }
| bare_label_keyword { $$ = pstrdup($1); }
;
/*
* Keyword category lists. Generally, every keyword present in
@ -15515,6 +15520,429 @@ reserved_keyword:
| WITH
;
/*
* While all keywords can be used as column labels when preceded by AS,
* not all of them can be used as a "bare" column label without AS.
* Those that can be used as a bare label must be listed here,
* in addition to appearing in one of the category lists above.
*
* Always add a new keyword to this list if possible. Mark it BARE_LABEL
* in kwlist.h if it is included here, or AS_LABEL if it is not.
*/
bare_label_keyword:
ABORT_P
| ABSOLUTE_P
| ACCESS
| ACTION
| ADD_P
| ADMIN
| AFTER
| AGGREGATE
| ALL
| ALSO
| ALTER
| ALWAYS
| ANALYSE
| ANALYZE
| AND
| ANY
| ASC
| ASSERTION
| ASSIGNMENT
| ASYMMETRIC
| AT
| ATTACH
| ATTRIBUTE
| AUTHORIZATION
| BACKWARD
| BEFORE
| BEGIN_P
| BETWEEN
| BIGINT
| BINARY
| BIT
| BOOLEAN_P
| BOTH
| BY
| CACHE
| CALL
| CALLED
| CASCADE
| CASCADED
| CASE
| CAST
| CATALOG_P
| CHAIN
| CHARACTERISTICS
| CHECK
| CHECKPOINT
| CLASS
| CLOSE
| CLUSTER
| COALESCE
| COLLATE
| COLLATION
| COLUMN
| COLUMNS
| COMMENT
| COMMENTS
| COMMIT
| COMMITTED
| CONCURRENTLY
| CONFIGURATION
| CONFLICT
| CONNECTION
| CONSTRAINT
| CONSTRAINTS
| CONTENT_P
| CONTINUE_P
| CONVERSION_P
| COPY
| COST
| CROSS
| CSV
| CUBE
| CURRENT_P
| CURRENT_CATALOG
| CURRENT_DATE
| CURRENT_ROLE
| CURRENT_SCHEMA
| CURRENT_TIME
| CURRENT_TIMESTAMP
| CURRENT_USER
| CURSOR
| CYCLE
| DATA_P
| DATABASE
| DEALLOCATE
| DEC
| DECIMAL_P
| DECLARE
| DEFAULT
| DEFAULTS
| DEFERRABLE
| DEFERRED
| DEFINER
| DELETE_P
| DELIMITER
| DELIMITERS
| DEPENDS
| DESC
| DETACH
| DICTIONARY
| DISABLE_P
| DISCARD
| DISTINCT
| DO
| DOCUMENT_P
| DOMAIN_P
| DOUBLE_P
| DROP
| EACH
| ELSE
| ENABLE_P
| ENCODING
| ENCRYPTED
| END_P
| ENUM_P
| ESCAPE
| EVENT
| EXCLUDE
| EXCLUDING
| EXCLUSIVE
| EXECUTE
| EXISTS
| EXPLAIN
| EXPRESSION
| EXTENSION
| EXTERNAL
| EXTRACT
| FALSE_P
| FAMILY
| FIRST_P
| FLOAT_P
| FOLLOWING
| FORCE
| FOREIGN
| FORWARD
| FREEZE
| FULL
| FUNCTION
| FUNCTIONS
| GENERATED
| GLOBAL
| GRANTED
| GREATEST
| GROUPING
| GROUPS
| HANDLER
| HEADER_P
| HOLD
| IDENTITY_P
| IF_P
| ILIKE
| IMMEDIATE
| IMMUTABLE
| IMPLICIT_P
| IMPORT_P
| IN_P
| INCLUDE
| INCLUDING
| INCREMENT
| INDEX
| INDEXES
| INHERIT
| INHERITS
| INITIALLY
| INLINE_P
| INNER_P
| INOUT
| INPUT_P
| INSENSITIVE
| INSERT
| INSTEAD
| INT_P
| INTEGER
| INTERVAL
| INVOKER
| IS
| ISOLATION
| JOIN
| KEY
| LABEL
| LANGUAGE
| LARGE_P
| LAST_P
| LATERAL_P
| LEADING
| LEAKPROOF
| LEAST
| LEFT
| LEVEL
| LIKE
| LISTEN
| LOAD
| LOCAL
| LOCALTIME
| LOCALTIMESTAMP
| LOCATION
| LOCK_P
| LOCKED
| LOGGED
| MAPPING
| MATCH
| MATERIALIZED
| MAXVALUE
| METHOD
| MINVALUE
| MODE
| MOVE
| NAME_P
| NAMES
| NATIONAL
| NATURAL
| NCHAR
| NEW
| NEXT
| NFC
| NFD
| NFKC
| NFKD
| NO
| NONE
| NORMALIZE
| NORMALIZED
| NOT
| NOTHING
| NOTIFY
| NOWAIT
| NULL_P
| NULLIF
| NULLS_P
| NUMERIC
| OBJECT_P
| OF
| OFF
| OIDS
| OLD
| ONLY
| OPERATOR
| OPTION
| OPTIONS
| OR
| ORDINALITY
| OTHERS
| OUT_P
| OUTER_P
| OVERLAY
| OVERRIDING
| OWNED
| OWNER
| PARALLEL
| PARSER
| PARTIAL
| PARTITION
| PASSING
| PASSWORD
| PLACING
| PLANS
| POLICY
| POSITION
| PRECEDING
| PREPARE
| PREPARED
| PRESERVE
| PRIMARY
| PRIOR
| PRIVILEGES
| PROCEDURAL
| PROCEDURE
| PROCEDURES
| PROGRAM
| PUBLICATION
| QUOTE
| RANGE
| READ
| REAL
| REASSIGN
| RECHECK
| RECURSIVE
| REF
| REFERENCES
| REFERENCING
| REFRESH
| REINDEX
| RELATIVE_P
| RELEASE
| RENAME
| REPEATABLE
| REPLACE
| REPLICA
| RESET
| RESTART
| RESTRICT
| RETURNS
| REVOKE
| RIGHT
| ROLE
| ROLLBACK
| ROLLUP
| ROUTINE
| ROUTINES
| ROW
| ROWS
| RULE
| SAVEPOINT
| SCHEMA
| SCHEMAS
| SCROLL
| SEARCH
| SECURITY
| SELECT
| SEQUENCE
| SEQUENCES
| SERIALIZABLE
| SERVER
| SESSION
| SESSION_USER
| SET
| SETOF
| SETS
| SHARE
| SHOW
| SIMILAR
| SIMPLE
| SKIP
| SMALLINT
| SNAPSHOT
| SOME
| SQL_P
| STABLE
| STANDALONE_P
| START
| STATEMENT
| STATISTICS
| STDIN
| STDOUT
| STORAGE
| STORED
| STRICT_P
| STRIP_P
| SUBSCRIPTION
| SUBSTRING
| SUPPORT
| SYMMETRIC
| SYSID
| SYSTEM_P
| TABLE
| TABLES
| TABLESAMPLE
| TABLESPACE
| TEMP
| TEMPLATE
| TEMPORARY
| TEXT_P
| THEN
| TIES
| TIME
| TIMESTAMP
| TRAILING
| TRANSACTION
| TRANSFORM
| TREAT
| TRIGGER
| TRIM
| TRUE_P
| TRUNCATE
| TRUSTED
| TYPE_P
| TYPES_P
| UESCAPE
| UNBOUNDED
| UNCOMMITTED
| UNENCRYPTED
| UNIQUE
| UNKNOWN
| UNLISTEN
| UNLOGGED
| UNTIL
| UPDATE
| USER
| USING
| VACUUM
| VALID
| VALIDATE
| VALIDATOR
| VALUE_P
| VALUES
| VARCHAR
| VARIADIC
| VERBOSE
| VERSION_P
| VIEW
| VIEWS
| VOLATILE
| WHEN
| WHITESPACE_P
| WORK
| WRAPPER
| WRITE
| XML_P
| XMLATTRIBUTES
| XMLCONCAT
| XMLELEMENT
| XMLEXISTS
| XMLFOREST
| XMLNAMESPACES
| XMLPARSE
| XMLPI
| XMLROOT
| XMLSERIALIZE
| XMLTABLE
| YES_P
| ZONE
;
%%
/*

View File

@ -73,7 +73,7 @@ bool standard_conforming_strings = true;
* callers need to pass it to scanner_init, if they are using the
* standard keyword list ScanKeywords.
*/
#define PG_KEYWORD(kwname, value, category) value,
#define PG_KEYWORD(kwname, value, category, collabel) value,
const uint16 ScanKeywordTokens[] = {
#include "parser/kwlist.h"