summaryrefslogtreecommitdiff
path: root/src/backend
diff options
context:
space:
mode:
authorTom Lane2020-09-18 20:46:26 +0000
committerTom Lane2020-09-18 20:46:36 +0000
commit06a7c3154f5bfad65549810cc84f0e3a77b408bf (patch)
treefdfecda402a05303ec6abc6d4437924ea26fe51c /src/backend
parent0811f766fd740018a72e222521553f8b22e7b3d6 (diff)
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
Diffstat (limited to 'src/backend')
-rw-r--r--src/backend/parser/check_keywords.pl92
-rw-r--r--src/backend/parser/gram.y440
-rw-r--r--src/backend/parser/scan.l2
-rw-r--r--src/backend/utils/adt/misc.c31
4 files changed, 526 insertions, 39 deletions
diff --git a/src/backend/parser/check_keywords.pl b/src/backend/parser/check_keywords.pl
index 702c97bba2a..3862db07278 100644
--- a/src/backend/parser/check_keywords.pl
+++ b/src/backend/parser/check_keywords.pl
@@ -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;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index b16ffb9bf7f..017940bdcd6 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -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
+ ;
+
%%
/*
diff --git a/src/backend/parser/scan.l b/src/backend/parser/scan.l
index b1ea0cb5384..4eab2980c99 100644
--- a/src/backend/parser/scan.l
+++ b/src/backend/parser/scan.l
@@ -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"
diff --git a/src/backend/utils/adt/misc.c b/src/backend/utils/adt/misc.c
index 37c23c9155a..b2bf9fa8cbc 100644
--- a/src/backend/utils/adt/misc.c
+++ b/src/backend/utils/adt/misc.c
@@ -416,12 +416,16 @@ pg_get_keywords(PG_FUNCTION_ARGS)
funcctx = SRF_FIRSTCALL_INIT();
oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
- tupdesc = CreateTemplateTupleDesc(3);
+ tupdesc = CreateTemplateTupleDesc(5);
TupleDescInitEntry(tupdesc, (AttrNumber) 1, "word",
TEXTOID, -1, 0);
TupleDescInitEntry(tupdesc, (AttrNumber) 2, "catcode",
CHAROID, -1, 0);
- TupleDescInitEntry(tupdesc, (AttrNumber) 3, "catdesc",
+ TupleDescInitEntry(tupdesc, (AttrNumber) 3, "barelabel",
+ BOOLOID, -1, 0);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 4, "catdesc",
+ TEXTOID, -1, 0);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 5, "baredesc",
TEXTOID, -1, 0);
funcctx->attinmeta = TupleDescGetAttInMetadata(tupdesc);
@@ -433,7 +437,7 @@ pg_get_keywords(PG_FUNCTION_ARGS)
if (funcctx->call_cntr < ScanKeywords.num_keywords)
{
- char *values[3];
+ char *values[5];
HeapTuple tuple;
/* cast-away-const is ugly but alternatives aren't much better */
@@ -445,26 +449,37 @@ pg_get_keywords(PG_FUNCTION_ARGS)
{
case UNRESERVED_KEYWORD:
values[1] = "U";
- values[2] = _("unreserved");
+ values[3] = _("unreserved");
break;
case COL_NAME_KEYWORD:
values[1] = "C";
- values[2] = _("unreserved (cannot be function or type name)");
+ values[3] = _("unreserved (cannot be function or type name)");
break;
case TYPE_FUNC_NAME_KEYWORD:
values[1] = "T";
- values[2] = _("reserved (can be function or type name)");
+ values[3] = _("reserved (can be function or type name)");
break;
case RESERVED_KEYWORD:
values[1] = "R";
- values[2] = _("reserved");
+ values[3] = _("reserved");
break;
default: /* shouldn't be possible */
values[1] = NULL;
- values[2] = NULL;
+ values[3] = NULL;
break;
}
+ if (ScanKeywordBareLabel[funcctx->call_cntr])
+ {
+ values[2] = "true";
+ values[4] = _("can be bare label");
+ }
+ else
+ {
+ values[2] = "false";
+ values[4] = _("requires AS");
+ }
+
tuple = BuildTupleFromCStrings(funcctx->attinmeta, values);
SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple));