diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 371fe3d54..212b1378e 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -23,6 +23,7 @@ use core::fmt::{self, Display}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; +use sqlparser::keywords::{ALL_KEYWORDS, ALL_KEYWORDS_INDEX}; #[cfg(feature = "visitor")] use sqlparser_derive::{Visit, VisitMut}; @@ -54,6 +55,7 @@ pub use self::value::{ use crate::ast::helpers::stmt_data_loading::{ DataLoadingOptions, StageLoadSelectItem, StageParamsObject, }; +use crate::keywords::Keyword; #[cfg(feature = "visitor")] pub use visitor::*; @@ -141,6 +143,24 @@ impl Ident { quote_style: Some(quote), } } + + /// If this identifier is also a keyword, return the corresponding [`Keyword`]. + /// + /// For example even though `AVRO` is a keyword, it can also be used as an + /// identifier for a column, such as `SELECT avro FROM my_table`. + pub fn find_keyword(&self) -> Option { + ALL_KEYWORDS + .iter() + .enumerate() + .find_map(|(idx, &kw)| { + if kw.to_string().to_uppercase() == self.value.to_uppercase() { + Some(idx) + } else { + None + } + }) + .map(|idx| ALL_KEYWORDS_INDEX[idx]) + } } impl From<&str> for Ident { diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 9d79a32e3..25f3f46b6 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -2771,6 +2771,27 @@ impl<'a> Parser<'a> { } } + /// If the current token is the `expected` keyword followed by + /// specified tokens, consume them and returns true. + /// Otherwise, no tokens are consumed and returns false. + pub fn parse_keyword_with_tokens(&mut self, expected: Keyword, tokens: &[Token]) -> bool { + match self.peek_token().token { + Token::Word(w) if expected == w.keyword => { + for (idx, token) in tokens.iter().enumerate() { + if self.peek_nth_token(idx + 1).token != *token { + return false; + } + } + // consume all tokens + for _ in 0..(tokens.len() + 1) { + self.next_token(); + } + true + } + _ => false, + } + } + /// If the current and subsequent tokens exactly match the `keywords` /// sequence, consume them and returns true. Otherwise, no tokens are /// consumed and returns false @@ -2779,7 +2800,6 @@ impl<'a> Parser<'a> { let index = self.index; for &keyword in keywords { if !self.parse_keyword(keyword) { - // println!("parse_keywords aborting .. did not find {:?}", keyword); // reset index and return immediately self.index = index; return false; @@ -7506,8 +7526,9 @@ impl<'a> Parser<'a> { with_offset, with_offset_alias, }) - } else if self.parse_keyword(Keyword::JSON_TABLE) { - self.expect_token(&Token::LParen)?; + } else if dialect_of!(self is MySqlDialect | AnsiDialect) + && self.parse_keyword_with_tokens(Keyword::JSON_TABLE, &[Token::LParen]) + { let json_expr = self.parse_expr()?; self.expect_token(&Token::Comma)?; let json_path = self.parse_value()?; @@ -7524,8 +7545,24 @@ impl<'a> Parser<'a> { alias, }) } else { + let loc = self.peek_token().location; let name = self.parse_object_name(true)?; + // Prevents using keywords as identifiers for table factor in ANSI mode + if dialect_of!(self is AnsiDialect) { + for ident in &name.0 { + if ident.quote_style.is_none() && ident.find_keyword().is_some() { + return parser_err!( + format!( + "Cannot specify a keyword as identifier for table factor: {}", + ident.value + ), + loc + ); + } + } + } + let partitions: Vec = if dialect_of!(self is MySqlDialect | GenericDialect) && self.parse_keyword(Keyword::PARTITION) { diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 691ffb92d..f3fcffdb9 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -8407,3 +8407,32 @@ fn test_buffer_reuse() { p.parse_statements().unwrap(); let _ = p.into_tokens(); } + +#[test] +fn parse_json_table_function_err() { + let unsupported_dialects = + all_dialects_except(|d| d.is::() || d.is::()); + + // JSON_TABLE table function is not supported in the above dialects. + assert!(unsupported_dialects + .parse_sql_statements("SELECT * FROM JSON_TABLE('[[1, 2], [3, 4]]', '$[*]' COLUMNS(a INT PATH '$[0]', b INT PATH '$[1]')) AS t") + .is_err()); +} + +#[test] +fn parse_json_table_as_identifier() { + let ansi_dialect = TestedDialects { + dialects: vec![ + Box::new(AnsiDialect {}), + ], + options: None, + }; + + let parsed = ansi_dialect.parse_sql_statements("SELECT * FROM json_table"); + assert_eq!( + ParserError::ParserError( + "Cannot specify a keyword as identifier for table factor: json_table".to_string() + ), + parsed.unwrap_err() + ); +}