mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-09-07 14:40:31 +00:00
Stricter parsing for subqueries (3/4)
This makes the parser more strict when handling SELECTs nested somewhere in the main statement: 1) instead of accepting SELECT anywhere in the expression where an operand was expected, we only accept it inside parens. (I've added a test for the currently supported syntax, <scalar subquery> in ANSI SQL terms) 2) instead of accepting any expression in the derived table context: `FROM ( ... )` - we only look for a SELECT subquery there. Due to #1, I had to swith the 'ansi' test from invoking the expression parser to the statement parser.
This commit is contained in:
parent
82dc581639
commit
215820ef66
4 changed files with 48 additions and 21 deletions
|
@ -77,8 +77,9 @@ pub enum ASTNode {
|
||||||
relation: Box<ASTNode>, // SQLNested or SQLCompoundIdentifier
|
relation: Box<ASTNode>, // SQLNested or SQLCompoundIdentifier
|
||||||
alias: Option<SQLIdent>,
|
alias: Option<SQLIdent>,
|
||||||
},
|
},
|
||||||
/// SELECT
|
/// A parenthesized subquery `(SELECT ...)`, used in expression like
|
||||||
SQLSelect(SQLSelect),
|
/// `SELECT (subquery) AS x` or `WHERE (subquery) = x`
|
||||||
|
SQLSubquery(SQLSelect),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToString for ASTNode {
|
impl ToString for ASTNode {
|
||||||
|
@ -139,7 +140,7 @@ impl ToString for ASTNode {
|
||||||
relation.to_string()
|
relation.to_string()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ASTNode::SQLSelect(s) => s.to_string(),
|
ASTNode::SQLSubquery(s) => format!("({})", s.to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -158,7 +158,6 @@ impl Parser {
|
||||||
match self.next_token() {
|
match self.next_token() {
|
||||||
Some(t) => match t {
|
Some(t) => match t {
|
||||||
Token::SQLWord(w) => match w.keyword.as_ref() {
|
Token::SQLWord(w) => match w.keyword.as_ref() {
|
||||||
"SELECT" => Ok(ASTNode::SQLSelect(self.parse_select()?)),
|
|
||||||
"TRUE" | "FALSE" | "NULL" => {
|
"TRUE" | "FALSE" | "NULL" => {
|
||||||
self.prev_token();
|
self.prev_token();
|
||||||
self.parse_sql_value()
|
self.parse_sql_value()
|
||||||
|
@ -197,9 +196,13 @@ impl Parser {
|
||||||
self.parse_sql_value()
|
self.parse_sql_value()
|
||||||
}
|
}
|
||||||
Token::LParen => {
|
Token::LParen => {
|
||||||
let expr = self.parse_expr()?;
|
let expr = if self.parse_keyword("SELECT") {
|
||||||
|
ASTNode::SQLSubquery(self.parse_select()?)
|
||||||
|
} else {
|
||||||
|
ASTNode::SQLNested(Box::new(self.parse_expr()?))
|
||||||
|
};
|
||||||
self.expect_token(&Token::RParen)?;
|
self.expect_token(&Token::RParen)?;
|
||||||
Ok(ASTNode::SQLNested(Box::new(expr)))
|
Ok(expr)
|
||||||
}
|
}
|
||||||
_ => parser_err!(format!(
|
_ => parser_err!(format!(
|
||||||
"Prefix parser expected a keyword but found {:?}",
|
"Prefix parser expected a keyword but found {:?}",
|
||||||
|
@ -1184,8 +1187,10 @@ impl Parser {
|
||||||
/// A table name or a parenthesized subquery, followed by optional `[AS] alias`
|
/// A table name or a parenthesized subquery, followed by optional `[AS] alias`
|
||||||
pub fn parse_table_factor(&mut self) -> Result<ASTNode, ParserError> {
|
pub fn parse_table_factor(&mut self) -> Result<ASTNode, ParserError> {
|
||||||
let relation = if self.consume_token(&Token::LParen) {
|
let relation = if self.consume_token(&Token::LParen) {
|
||||||
self.prev_token();
|
self.expect_keyword("SELECT")?;
|
||||||
self.parse_subexpr(0)? /* TBD (3) */
|
let subquery = self.parse_select()?;
|
||||||
|
self.expect_token(&Token::RParen)?;
|
||||||
|
ASTNode::SQLSubquery(subquery)
|
||||||
} else {
|
} else {
|
||||||
self.parse_compound_identifier(&Token::Period)?
|
self.parse_compound_identifier(&Token::Period)?
|
||||||
};
|
};
|
||||||
|
|
|
@ -4,25 +4,16 @@ extern crate sqlparser;
|
||||||
use sqlparser::dialect::AnsiSqlDialect;
|
use sqlparser::dialect::AnsiSqlDialect;
|
||||||
use sqlparser::sqlast::*;
|
use sqlparser::sqlast::*;
|
||||||
use sqlparser::sqlparser::*;
|
use sqlparser::sqlparser::*;
|
||||||
use sqlparser::sqltokenizer::*;
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_simple_select() {
|
fn parse_simple_select() {
|
||||||
let sql = String::from("SELECT id, fname, lname FROM customer WHERE id = 1");
|
let sql = String::from("SELECT id, fname, lname FROM customer WHERE id = 1");
|
||||||
let ast = parse_sql_expr(&sql);
|
let ast = Parser::parse_sql(&AnsiSqlDialect {}, sql).unwrap();
|
||||||
match ast {
|
assert_eq!(1, ast.len());
|
||||||
ASTNode::SQLSelect(SQLSelect { projection, .. }) => {
|
match ast.first().unwrap() {
|
||||||
|
SQLStatement::SQLSelect(SQLSelect { projection, .. }) => {
|
||||||
assert_eq!(3, projection.len());
|
assert_eq!(3, projection.len());
|
||||||
}
|
}
|
||||||
_ => assert!(false),
|
_ => assert!(false),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_sql_expr(sql: &str) -> ASTNode {
|
|
||||||
let dialect = AnsiSqlDialect {};
|
|
||||||
let mut tokenizer = Tokenizer::new(&dialect, &sql);
|
|
||||||
let tokens = tokenizer.tokenize().unwrap();
|
|
||||||
let mut parser = Parser::new(tokens);
|
|
||||||
let ast = parser.parse_expr().unwrap();
|
|
||||||
ast
|
|
||||||
}
|
|
||||||
|
|
|
@ -664,6 +664,13 @@ fn parse_join_syntax_variants() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_derived_tables() {
|
||||||
|
let sql = "SELECT a.x, b.y FROM (SELECT x FROM foo) AS a CROSS JOIN (SELECT y FROM bar) AS b";
|
||||||
|
let _ = verified_only_select(sql);
|
||||||
|
//TODO: add assertions
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_multiple_statements() {
|
fn parse_multiple_statements() {
|
||||||
fn test_with(sql1: &str, sql2_kw: &str, sql2_rest: &str) {
|
fn test_with(sql1: &str, sql2_kw: &str, sql2_rest: &str) {
|
||||||
|
@ -695,6 +702,29 @@ fn parse_multiple_statements() {
|
||||||
assert_eq!(0, res.unwrap().len());
|
assert_eq!(0, res.unwrap().len());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_scalar_subqueries() {
|
||||||
|
use self::ASTNode::*;
|
||||||
|
let sql = "(SELECT 1) + (SELECT 2)";
|
||||||
|
match verified_expr(sql) {
|
||||||
|
SQLBinaryExpr {
|
||||||
|
op: SQLOperator::Plus, ..
|
||||||
|
//left: box SQLSubquery { .. },
|
||||||
|
//right: box SQLSubquery { .. },
|
||||||
|
} => assert!(true),
|
||||||
|
_ => assert!(false),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_invalid_subquery_without_parens() {
|
||||||
|
let res = parse_sql_statements("SELECT SELECT 1 FROM bar WHERE 1=1 FROM baz");
|
||||||
|
assert_eq!(
|
||||||
|
ParserError::ParserError("Expected end of statement, found: 1".to_string()),
|
||||||
|
res.unwrap_err()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
fn only<'a, T>(v: &'a Vec<T>) -> &'a T {
|
fn only<'a, T>(v: &'a Vec<T>) -> &'a T {
|
||||||
assert_eq!(1, v.len());
|
assert_eq!(1, v.len());
|
||||||
v.first().unwrap()
|
v.first().unwrap()
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue