From 57083a0df19fb759cdff77e569875abaa7f55bd7 Mon Sep 17 00:00:00 2001 From: Sarah Yurick <53962159+sarahyurick@users.noreply.github.com> Date: Tue, 22 Nov 2022 04:45:47 -0800 Subject: [PATCH] Fix interval parsing logic and precedence (#705) * initial fix * add comma * add test * style * add more tests * codestyle fix --- src/parser.rs | 32 ++++++++++- tests/sqlparser_common.rs | 115 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 146 insertions(+), 1 deletion(-) diff --git a/src/parser.rs b/src/parser.rs index f91223c2f..a0b141bc1 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -363,6 +363,36 @@ impl<'a> Parser<'a> { Ok(expr) } + pub fn parse_interval_expr(&mut self) -> Result { + let precedence = 0; + let mut expr = self.parse_prefix()?; + + loop { + let next_precedence = self.get_next_interval_precedence()?; + + if precedence >= next_precedence { + break; + } + + expr = self.parse_infix(expr, next_precedence)?; + } + + Ok(expr) + } + + /// Get the precedence of the next token + /// With AND, OR, and XOR + pub fn get_next_interval_precedence(&self) -> Result { + let token = self.peek_token(); + + match token { + Token::Word(w) if w.keyword == Keyword::AND => Ok(0), + Token::Word(w) if w.keyword == Keyword::OR => Ok(0), + Token::Word(w) if w.keyword == Keyword::XOR => Ok(0), + _ => self.get_next_precedence(), + } + } + pub fn parse_assert(&mut self) -> Result { let condition = self.parse_expr()?; let message = if self.parse_keyword(Keyword::AS) { @@ -1200,7 +1230,7 @@ impl<'a> Parser<'a> { // The first token in an interval is a string literal which specifies // the duration of the interval. - let value = self.parse_expr()?; + let value = self.parse_interval_expr()?; // Following the string literal is a qualifier which indicates the units // of the duration specified in the string literal. diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 83a7a4ca6..ec4c2f257 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -3237,6 +3237,121 @@ fn parse_interval() { ); } +#[test] +fn parse_interval_and_or_xor() { + let sql = "SELECT col FROM test \ + WHERE d3_date > d1_date + INTERVAL '5 days' \ + AND d2_date > d1_date + INTERVAL '3 days'"; + + let actual_ast = Parser::parse_sql(&GenericDialect {}, sql).unwrap(); + + let expected_ast = vec![Statement::Query(Box::new(Query { + with: None, + body: Box::new(SetExpr::Select(Box::new(Select { + distinct: false, + top: None, + projection: vec![UnnamedExpr(Expr::Identifier(Ident { + value: "col".to_string(), + quote_style: None, + }))], + into: None, + from: vec![TableWithJoins { + relation: TableFactor::Table { + name: ObjectName(vec![Ident { + value: "test".to_string(), + quote_style: None, + }]), + alias: None, + args: None, + with_hints: vec![], + }, + joins: vec![], + }], + lateral_views: vec![], + selection: Some(Expr::BinaryOp { + left: Box::new(Expr::BinaryOp { + left: Box::new(Expr::Identifier(Ident { + value: "d3_date".to_string(), + quote_style: None, + })), + op: BinaryOperator::Gt, + right: Box::new(Expr::BinaryOp { + left: Box::new(Expr::Identifier(Ident { + value: "d1_date".to_string(), + quote_style: None, + })), + op: BinaryOperator::Plus, + right: Box::new(Expr::Interval { + value: Box::new(Expr::Value(Value::SingleQuotedString( + "5 days".to_string(), + ))), + leading_field: None, + leading_precision: None, + last_field: None, + fractional_seconds_precision: None, + }), + }), + }), + op: BinaryOperator::And, + right: Box::new(Expr::BinaryOp { + left: Box::new(Expr::Identifier(Ident { + value: "d2_date".to_string(), + quote_style: None, + })), + op: BinaryOperator::Gt, + right: Box::new(Expr::BinaryOp { + left: Box::new(Expr::Identifier(Ident { + value: "d1_date".to_string(), + quote_style: None, + })), + op: BinaryOperator::Plus, + right: Box::new(Expr::Interval { + value: Box::new(Expr::Value(Value::SingleQuotedString( + "3 days".to_string(), + ))), + leading_field: None, + leading_precision: None, + last_field: None, + fractional_seconds_precision: None, + }), + }), + }), + }), + group_by: vec![], + cluster_by: vec![], + distribute_by: vec![], + sort_by: vec![], + having: None, + qualify: None, + }))), + order_by: vec![], + limit: None, + offset: None, + fetch: None, + lock: None, + }))]; + + assert_eq!(actual_ast, expected_ast); + + verified_stmt( + "SELECT col FROM test \ + WHERE d3_date > d1_date + INTERVAL '5 days' \ + AND d2_date > d1_date + INTERVAL '3 days'", + ); + + verified_stmt( + "SELECT col FROM test \ + WHERE d3_date > d1_date + INTERVAL '5 days' \ + OR d2_date > d1_date + INTERVAL '3 days'", + ); + + verified_stmt( + "SELECT col FROM test \ + WHERE d3_date > d1_date + INTERVAL '5 days' \ + XOR d2_date > d1_date + INTERVAL '3 days'", + ); +} + #[test] fn parse_at_timezone() { let zero = Expr::Value(number("0"));