Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Restrain JSON_TABLE table function parsing to MySqlDialect and AnsiDialect #1123

Closed
wants to merge 19 commits into from
Closed
20 changes: 20 additions & 0 deletions src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down Expand Up @@ -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::*;

Expand Down Expand Up @@ -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<Keyword> {
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 {
Expand Down
43 changes: 40 additions & 3 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be removed.

// reset index and return immediately
self.index = index;
return false;
Expand Down Expand Up @@ -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()?;
Expand All @@ -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<Ident> = if dialect_of!(self is MySqlDialect | GenericDialect)
&& self.parse_keyword(Keyword::PARTITION)
{
Expand Down
29 changes: 29 additions & 0 deletions tests/sqlparser_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8407,3 +8407,32 @@
p.parse_statements().unwrap();
let _ = p.into_tokens();
}

#[test]
fn parse_json_table_function_err() {
let unsupported_dialects =
all_dialects_except(|d| d.is::<AnsiDialect>() || d.is::<MySqlDialect>());

// 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()
);
}
Loading