From 934280c3db133aa08cc4e37282bc33f753b927cd Mon Sep 17 00:00:00 2001 From: Samuel Colvin Date: Thu, 22 Aug 2024 16:07:33 +0100 Subject: [PATCH 1/3] fix interval precedence for interval expressions --- examples/cli.rs | 2 +- src/dialect/mod.rs | 2 ++ src/dialect/postgresql.rs | 3 +++ src/parser/mod.rs | 2 +- tests/sqlparser_common.rs | 4 +++- tests/sqlparser_postgres.rs | 42 +++++++++++++++++++++++++++++++++++++ 6 files changed, 52 insertions(+), 3 deletions(-) diff --git a/examples/cli.rs b/examples/cli.rs index 72f963b1e..40bd447d3 100644 --- a/examples/cli.rs +++ b/examples/cli.rs @@ -31,7 +31,7 @@ Usage: $ cargo run --example cli FILENAME.sql [--dialectname] To print the parse results as JSON: -$ cargo run --feature json_example --example cli FILENAME.sql [--dialectname] +$ cargo run --features json_example --example cli FILENAME.sql [--dialectname] "#, ); diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs index df3022adc..44a23b169 100644 --- a/src/dialect/mod.rs +++ b/src/dialect/mod.rs @@ -482,6 +482,7 @@ pub trait Dialect: Debug + Any { Precedence::UnaryNot => 15, Precedence::And => 10, Precedence::Or => 5, + Precedence::Interval => 4, } } @@ -522,6 +523,7 @@ pub enum Precedence { UnaryNot, And, Or, + Interval, } impl dyn Dialect { diff --git a/src/dialect/postgresql.rs b/src/dialect/postgresql.rs index c25a80f67..22444a9ad 100644 --- a/src/dialect/postgresql.rs +++ b/src/dialect/postgresql.rs @@ -21,6 +21,8 @@ use crate::tokenizer::Token; #[derive(Debug)] pub struct PostgreSqlDialect {} +// matches +const INTERVAL_COLON_PREC: u8 = 150; const DOUBLE_COLON_PREC: u8 = 140; const BRACKET_PREC: u8 = 130; const COLLATE_PREC: u8 = 120; @@ -136,6 +138,7 @@ impl Dialect for PostgreSqlDialect { fn prec_value(&self, prec: Precedence) -> u8 { match prec { + Precedence::Interval => INTERVAL_COLON_PREC, Precedence::DoubleColon => DOUBLE_COLON_PREC, Precedence::AtTz => AT_TZ_PREC, Precedence::MulDivModOp => MUL_DIV_MOD_OP_PREC, diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 2632d807a..7266d8609 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -897,7 +897,7 @@ impl<'a> Parser<'a> { } pub fn parse_interval_expr(&mut self) -> Result { - let precedence = self.dialect.prec_unknown(); + let precedence = self.dialect.prec_value(Precedence::Interval); let mut expr = self.parse_prefix()?; loop { diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 517e978b4..51c1c1e78 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -4954,8 +4954,10 @@ fn parse_interval() { expr_from_projection(only(&select.projection)), ); + let sql = "SELECT INTERVAL 1 + 1 DAY"; - let select = verified_only_select(sql); + let all_except_pg = all_dialects_except(|d| d.is::()); + let select = all_except_pg.verified_only_select(sql); assert_eq!( &Expr::Interval(Interval { value: Box::new(Expr::BinaryOp { diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index 2f9fe86c9..913138aea 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -4949,6 +4949,48 @@ fn test_unicode_string_literal() { } } +#[test] +fn interval_precedence_gt() { + let expr = pg().verified_expr("INTERVAL '1 second' > x"); + assert_eq!(expr, Expr::BinaryOp { + left: Box::new(Expr::Interval( + Interval { + value: Box::new(Expr::Value(Value::SingleQuotedString("1 second".to_string()))), + leading_field: None, + leading_precision: None, + last_field: None, + fractional_seconds_precision: None, + }, + )), + op: BinaryOperator::Gt, + right: Box::new(Expr::Identifier( + Ident { + value: "x".to_string(), + quote_style: None, + }, + )), + }) +} + +#[test] +fn interval_precedence_double_colon() { + let expr = pg().verified_expr("INTERVAL '1 second'::TEXT"); + assert_eq!(expr, Expr::Cast { + kind: CastKind::DoubleColon, + expr: Box::new(Expr::Interval( + Interval { + value: Box::new(Expr::Value(Value::SingleQuotedString("1 second".to_string()))), + leading_field: None, + leading_precision: None, + last_field: None, + fractional_seconds_precision: None, + }, + )), + data_type: DataType::Text, + format: None, + }) +} + fn check_arrow_precedence(sql: &str, arrow_operator: BinaryOperator) { assert_eq!( pg().verified_expr(sql), From 32b108d78a84b8165f35a0adf4a743fa0535bfd0 Mon Sep 17 00:00:00 2001 From: Samuel Colvin Date: Thu, 22 Aug 2024 16:15:20 +0100 Subject: [PATCH 2/3] revert default behavior for other dialects --- src/dialect/mod.rs | 2 +- tests/sqlparser_common.rs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs index 44a23b169..986689a05 100644 --- a/src/dialect/mod.rs +++ b/src/dialect/mod.rs @@ -482,7 +482,7 @@ pub trait Dialect: Debug + Any { Precedence::UnaryNot => 15, Precedence::And => 10, Precedence::Or => 5, - Precedence::Interval => 4, + Precedence::Interval => self.prec_unknown(), } } diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 51c1c1e78..6a794d70e 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -4954,7 +4954,6 @@ fn parse_interval() { expr_from_projection(only(&select.projection)), ); - let sql = "SELECT INTERVAL 1 + 1 DAY"; let all_except_pg = all_dialects_except(|d| d.is::()); let select = all_except_pg.verified_only_select(sql); From 25ed151e5c825c4689a86dd124c0119fe375793c Mon Sep 17 00:00:00 2001 From: Samuel Colvin Date: Thu, 22 Aug 2024 16:20:12 +0100 Subject: [PATCH 3/3] fmt --- tests/sqlparser_postgres.rs | 48 ++++++++++++++++++++----------------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index 913138aea..1c297f7f1 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -4952,43 +4952,47 @@ fn test_unicode_string_literal() { #[test] fn interval_precedence_gt() { let expr = pg().verified_expr("INTERVAL '1 second' > x"); - assert_eq!(expr, Expr::BinaryOp { - left: Box::new(Expr::Interval( - Interval { - value: Box::new(Expr::Value(Value::SingleQuotedString("1 second".to_string()))), + assert_eq!( + expr, + Expr::BinaryOp { + left: Box::new(Expr::Interval(Interval { + value: Box::new(Expr::Value(Value::SingleQuotedString( + "1 second".to_string() + ))), leading_field: None, leading_precision: None, last_field: None, fractional_seconds_precision: None, - }, - )), - op: BinaryOperator::Gt, - right: Box::new(Expr::Identifier( - Ident { + },)), + op: BinaryOperator::Gt, + right: Box::new(Expr::Identifier(Ident { value: "x".to_string(), quote_style: None, - }, - )), - }) + })), + } + ) } #[test] fn interval_precedence_double_colon() { let expr = pg().verified_expr("INTERVAL '1 second'::TEXT"); - assert_eq!(expr, Expr::Cast { - kind: CastKind::DoubleColon, - expr: Box::new(Expr::Interval( - Interval { - value: Box::new(Expr::Value(Value::SingleQuotedString("1 second".to_string()))), + assert_eq!( + expr, + Expr::Cast { + kind: CastKind::DoubleColon, + expr: Box::new(Expr::Interval(Interval { + value: Box::new(Expr::Value(Value::SingleQuotedString( + "1 second".to_string() + ))), leading_field: None, leading_precision: None, last_field: None, fractional_seconds_precision: None, - }, - )), - data_type: DataType::Text, - format: None, - }) + })), + data_type: DataType::Text, + format: None, + } + ) } fn check_arrow_precedence(sql: &str, arrow_operator: BinaryOperator) {