Skip to content

Commit

Permalink
Fix interval parsing logic and precedence (#705)
Browse files Browse the repository at this point in the history
* initial fix

* add comma

* add test

* style

* add more tests

* codestyle fix
  • Loading branch information
sarahyurick committed Nov 22, 2022
1 parent 4b1dc1a commit 57083a0
Show file tree
Hide file tree
Showing 2 changed files with 146 additions and 1 deletion.
32 changes: 31 additions & 1 deletion src/parser.rs
Expand Up @@ -363,6 +363,36 @@ impl<'a> Parser<'a> {
Ok(expr)
}

pub fn parse_interval_expr(&mut self) -> Result<Expr, ParserError> {
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<u8, ParserError> {
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<Statement, ParserError> {
let condition = self.parse_expr()?;
let message = if self.parse_keyword(Keyword::AS) {
Expand Down Expand Up @@ -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.
Expand Down
115 changes: 115 additions & 0 deletions tests/sqlparser_common.rs
Expand Up @@ -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"));
Expand Down

0 comments on commit 57083a0

Please sign in to comment.