mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-09-09 15:40:32 +00:00
Support table aliases without AS
(7/8)
...as in `FROM foo bar WHERE bar.x > 1`. To avoid ambiguity as to whether a token is an alias or a keyword, we maintain a blacklist of keywords, that can follow a "table factor", to prevent parsing them as an alias. This "context-specific reserved keyword" approach lets us accept more SQL that's valid in some dialects, than a list of globally reserved keywords. Also some dialects (e.g. Oracle) apparently don't reserve some keywords (like JOIN), while presumably they won't accept them as an alias (`FROM foo JOIN` meaning `FROM foo AS JOIN`).
This commit is contained in:
parent
536fa6e428
commit
76ec175d20
3 changed files with 36 additions and 11 deletions
|
@ -1,14 +1,16 @@
|
|||
///! This module defines a list of constants for every keyword that
|
||||
///! This module defines
|
||||
/// 1) a list of constants for every keyword that
|
||||
/// can appear in SQLWord::keyword:
|
||||
/// pub const KEYWORD = "KEYWORD"
|
||||
/// and an `ALL_KEYWORDS` array with every keyword in it.
|
||||
/// 2) an `ALL_KEYWORDS` array with every keyword in it
|
||||
/// This is not a list of *reserved* keywords: some of these can be
|
||||
/// parsed as identifiers if the parser decides so. This means that
|
||||
/// new keywords can be added here without affecting the parse result.
|
||||
///
|
||||
/// This is not a list of *reserved* keywords: some of these can be
|
||||
/// parsed as identifiers if the parser decides so. This means that
|
||||
/// new keywords can be added here without affecting the parse result.
|
||||
///
|
||||
/// As a matter of fact, most of these keywords are not used at all
|
||||
/// and could be removed.
|
||||
/// As a matter of fact, most of these keywords are not used at all
|
||||
/// and could be removed.
|
||||
/// 3) a `RESERVED_FOR_TABLE_ALIAS` array with keywords reserved in a
|
||||
/// "table alias" context.
|
||||
|
||||
macro_rules! keyword {
|
||||
($($ident:ident),*) => {
|
||||
|
@ -707,3 +709,12 @@ pub const ALL_KEYWORDS: &'static [&'static str] = &[
|
|||
ZONE,
|
||||
END_EXEC,
|
||||
];
|
||||
|
||||
/// These keywords can't be used as a table alias, so that `FROM table_name alias`
|
||||
/// can be parsed unambiguously without looking ahead.
|
||||
pub const RESERVED_FOR_TABLE_ALIAS: &'static [&'static str] = &[
|
||||
WHERE, GROUP, ON, // keyword is 'reserved' in most dialects
|
||||
JOIN, INNER, CROSS, FULL, LEFT, RIGHT, // not reserved in Oracle
|
||||
NATURAL, USING, // not reserved in Oracle & MSSQL
|
||||
// UNION, EXCEPT, INTERSECT, ORDER // TODO add these with tests.
|
||||
];
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
|
||||
//! SQL Parser
|
||||
|
||||
use super::dialect::keywords;
|
||||
use super::dialect::Dialect;
|
||||
use super::sqlast::*;
|
||||
use super::sqltokenizer::*;
|
||||
|
@ -950,13 +951,18 @@ impl Parser {
|
|||
/// `SELECT ... FROM t1 foo, t2 bar`, `SELECT ... FROM (...) AS bar`
|
||||
pub fn parse_optional_alias(
|
||||
&mut self,
|
||||
reserved_kwds: &[&str],
|
||||
) -> Result<Option<SQLIdent>, ParserError> {
|
||||
let after_as = self.parse_keyword("AS");
|
||||
let maybe_alias = self.next_token();
|
||||
match maybe_alias {
|
||||
// Accept any identifier after `AS` (though many dialects have restrictions on
|
||||
// keywords that may appear here).
|
||||
Some(Token::SQLWord(ref w)) if after_as =>
|
||||
// keywords that may appear here). If there's no `AS`: don't parse keywords,
|
||||
// which may start a construct allowed in this position, to be parsed as aliases.
|
||||
// (For example, in `FROM t1 JOIN` the `JOIN` will always be parsed as a keyword,
|
||||
// not an alias.)
|
||||
Some(Token::SQLWord(ref w))
|
||||
if after_as || !reserved_kwds.contains(&w.keyword.as_str()) =>
|
||||
{
|
||||
// have to clone here until #![feature(bind_by_move_pattern_guards)] is enabled by default
|
||||
Ok(Some(w.value.clone()))
|
||||
|
@ -1157,7 +1163,7 @@ impl Parser {
|
|||
} else {
|
||||
self.parse_compound_identifier(&Token::Period)?
|
||||
};
|
||||
let alias = self.parse_optional_alias()?;
|
||||
let alias = self.parse_optional_alias(keywords::RESERVED_FOR_TABLE_ALIAS)?;
|
||||
Ok(ASTNode::TableFactor {
|
||||
relation: Box::new(relation),
|
||||
alias,
|
||||
|
|
|
@ -601,6 +601,10 @@ fn parse_joins_on() {
|
|||
JoinOperator::Inner
|
||||
)]
|
||||
);
|
||||
parses_to(
|
||||
"SELECT * FROM t1 JOIN t2 foo ON c1 = c2",
|
||||
"SELECT * FROM t1 JOIN t2 AS foo ON c1 = c2",
|
||||
);
|
||||
// Test parsing of different join operators
|
||||
assert_eq!(
|
||||
joins_from(verified("SELECT * FROM t1 JOIN t2 ON c1 = c2")),
|
||||
|
@ -644,6 +648,10 @@ fn parse_joins_using() {
|
|||
JoinOperator::Inner
|
||||
)]
|
||||
);
|
||||
parses_to(
|
||||
"SELECT * FROM t1 JOIN t2 foo USING(c1)",
|
||||
"SELECT * FROM t1 JOIN t2 AS foo USING(c1)",
|
||||
);
|
||||
// Test parsing of different join operators
|
||||
assert_eq!(
|
||||
joins_from(verified("SELECT * FROM t1 JOIN t2 USING(c1)")),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue