From d579b471797286f3fb532f203b039626ed458d5a Mon Sep 17 00:00:00 2001 From: "Heres, Daniel" Date: Sun, 7 Feb 2021 12:51:17 +0100 Subject: [PATCH 1/3] Implement SUBSTRING(FROM FOR ) syntax --- src/ast/mod.rs | 55 +++++++++++++++++++++++++++++++++++ src/parser.rs | 22 ++++++++++++++ tests/sqlparser_common.rs | 17 +++++++++++ tests/sqlparser_regression.rs | 4 --- 4 files changed, 94 insertions(+), 4 deletions(-) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 4232ad022..e964eef40 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -197,10 +197,17 @@ pub enum Expr { expr: Box, data_type: DataType, }, + /// EXTRACT(DateTimeField FROM ) Extract { field: DateTimeField, expr: Box, }, + /// SUBSTRING( [FROM ] [FOR ]) + Substring { + expr: Box, + substring_from: Option, + substring_for: Option, + }, /// `expr COLLATE collation` Collate { expr: Box, @@ -321,6 +328,21 @@ impl fmt::Display for Expr { Expr::Exists(s) => write!(f, "EXISTS ({})", s), Expr::Subquery(s) => write!(f, "({})", s), Expr::ListAgg(listagg) => write!(f, "{}", listagg), + Expr::Substring { + expr, + substring_from, + substring_for, + } => { + write!(f, "SUBSTRING({}", expr)?; + if let Some(from_part) = substring_from { + write!(f, " {}", from_part)?; + } + if let Some(from_part) = substring_for { + write!(f, " {}", from_part)?; + } + + write!(f, ")") + } } } } @@ -426,6 +448,39 @@ impl fmt::Display for WindowFrameBound { } } +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +/// FROM part of SUBSTRING( [FROM ]) +pub enum SubstringFrom { + /// + FromExpr(Box), +} + +impl fmt::Display for SubstringFrom { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + SubstringFrom::FromExpr(expr) => { + write!(f, "FROM {}", expr) + } + } + } +} + +/// FROM part of SUBSTRING( [TO ]) +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum SubstringFor { + ForExpr(Box), +} + +impl fmt::Display for SubstringFor { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + SubstringFor::ForExpr(expr) => { + write!(f, "FOR {}", expr) + } + } + } +} + /// A top-level statement (SELECT, INSERT, CREATE, etc.) #[allow(clippy::large_enum_variant)] #[derive(Debug, Clone, PartialEq, Eq, Hash)] diff --git a/src/parser.rs b/src/parser.rs index 94afeb6e9..900063cdd 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -247,6 +247,7 @@ impl<'a> Parser<'a> { Keyword::CAST => self.parse_cast_expr(), Keyword::EXISTS => self.parse_exists_expr(), Keyword::EXTRACT => self.parse_extract_expr(), + Keyword::SUBSTRING => self.parse_substring_expr(), Keyword::INTERVAL => self.parse_literal_interval(), Keyword::LISTAGG => self.parse_listagg_expr(), Keyword::NOT => Ok(Expr::UnaryOp { @@ -502,6 +503,27 @@ impl<'a> Parser<'a> { }) } + pub fn parse_substring_expr(&mut self) -> Result { + // PARSE SUBSTRING (EXPR [FROM 1] [FOR 3]) + self.expect_token(&Token::LParen)?; + let expr = self.parse_expr()?; + let mut from_expr = None; + let mut to_expr = None; + if self.parse_keyword(Keyword::FROM) { + from_expr = Some(self.parse_expr()?); + } + if self.parse_keyword(Keyword::FOR) { + to_expr = Some(self.parse_expr()?); + } + self.expect_token(&Token::RParen)?; + + Ok(Expr::Substring { + expr: Box::new(expr), + substring_from: from_expr.map(|x| SubstringFrom::FromExpr(Box::new(x))), + substring_for: to_expr.map(|x| SubstringFor::ForExpr(Box::new(x))), + }) + } + /// Parse a SQL LISTAGG expression, e.g. `LISTAGG(...) WITHIN GROUP (ORDER BY ...)`. pub fn parse_listagg_expr(&mut self) -> Result { self.expect_token(&Token::LParen)?; diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index e7d78f950..16530072c 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -2609,6 +2609,23 @@ fn parse_scalar_subqueries() { ); } +#[test] +fn parse_substring() { + one_statement_parses_to("SELECT SUBSTRING('1')", "SELECT SUBSTRING('1')"); + + one_statement_parses_to( + "SELECT SUBSTRING('1' FROM 1)", + "SELECT SUBSTRING('1' FROM 1)", + ); + + one_statement_parses_to( + "SELECT SUBSTRING('1' FROM 1 FOR 3)", + "SELECT SUBSTRING('1' FROM 1 FOR 3)", + ); + + one_statement_parses_to("SELECT SUBSTRING('1' FOR 3)", "SELECT SUBSTRING('1' FOR 3)"); +} + #[test] fn parse_exists_subquery() { let expected_inner = verified_query("SELECT 1"); diff --git a/tests/sqlparser_regression.rs b/tests/sqlparser_regression.rs index bbf1b2977..a930872fb 100644 --- a/tests/sqlparser_regression.rs +++ b/tests/sqlparser_regression.rs @@ -27,10 +27,6 @@ macro_rules! tpch_tests { let dialect = GenericDialect {}; let res = Parser::parse_sql(&dialect, QUERIES[$value -1]); - // Ignore 6.sql and 22.sql - if $value != 6 && $value != 22 { - assert!(res.is_ok()); - } } )* } From 6f66c872d736cb706979355b3cf8e0231248cac4 Mon Sep 17 00:00:00 2001 From: "Heres, Daniel" Date: Sun, 7 Feb 2021 13:02:22 +0100 Subject: [PATCH 2/3] Simplify, clippy --- src/ast/mod.rs | 41 ++++------------------------------- src/parser.rs | 4 ++-- tests/sqlparser_regression.rs | 2 +- 3 files changed, 7 insertions(+), 40 deletions(-) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 2333ae19a..d0e321185 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -209,8 +209,8 @@ pub enum Expr { /// SUBSTRING( [FROM ] [FOR ]) Substring { expr: Box, - substring_from: Option, - substring_for: Option, + substring_from: Option>, + substring_for: Option>, }, /// `expr COLLATE collation` Collate { @@ -347,10 +347,10 @@ impl fmt::Display for Expr { } => { write!(f, "SUBSTRING({}", expr)?; if let Some(from_part) = substring_from { - write!(f, " {}", from_part)?; + write!(f, " FROM {}", from_part)?; } if let Some(from_part) = substring_for { - write!(f, " {}", from_part)?; + write!(f, " FOR {}", from_part)?; } write!(f, ")") @@ -460,39 +460,6 @@ impl fmt::Display for WindowFrameBound { } } -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -/// FROM part of SUBSTRING( [FROM ]) -pub enum SubstringFrom { - /// - FromExpr(Box), -} - -impl fmt::Display for SubstringFrom { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - SubstringFrom::FromExpr(expr) => { - write!(f, "FROM {}", expr) - } - } - } -} - -/// FROM part of SUBSTRING( [TO ]) -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum SubstringFor { - ForExpr(Box), -} - -impl fmt::Display for SubstringFor { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - SubstringFor::ForExpr(expr) => { - write!(f, "FOR {}", expr) - } - } - } -} - #[derive(Debug, Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum AddDropSync { diff --git a/src/parser.rs b/src/parser.rs index ef4afcd95..bee671f04 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -623,8 +623,8 @@ impl<'a> Parser<'a> { Ok(Expr::Substring { expr: Box::new(expr), - substring_from: from_expr.map(|x| SubstringFrom::FromExpr(Box::new(x))), - substring_for: to_expr.map(|x| SubstringFor::ForExpr(Box::new(x))), + substring_from: from_expr.map(Box::new), + substring_for: to_expr.map(Box::new), }) } diff --git a/tests/sqlparser_regression.rs b/tests/sqlparser_regression.rs index a930872fb..732bc1068 100644 --- a/tests/sqlparser_regression.rs +++ b/tests/sqlparser_regression.rs @@ -26,7 +26,7 @@ macro_rules! tpch_tests { fn $name() { let dialect = GenericDialect {}; - let res = Parser::parse_sql(&dialect, QUERIES[$value -1]); + let _res = Parser::parse_sql(&dialect, QUERIES[$value -1]); } )* } From 5677393e8e6add12f90101d24e96c000765e2677 Mon Sep 17 00:00:00 2001 From: "Heres, Daniel" Date: Sun, 7 Feb 2021 13:19:28 +0100 Subject: [PATCH 3/3] Fix test --- tests/sqlparser_regression.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/sqlparser_regression.rs b/tests/sqlparser_regression.rs index 732bc1068..1fc35d99c 100644 --- a/tests/sqlparser_regression.rs +++ b/tests/sqlparser_regression.rs @@ -25,8 +25,11 @@ macro_rules! tpch_tests { #[test] fn $name() { let dialect = GenericDialect {}; - - let _res = Parser::parse_sql(&dialect, QUERIES[$value -1]); + let res = Parser::parse_sql(&dialect, QUERIES[$value -1]); + // Ignore 6.sql + if $value != 6 { + assert!(res.is_ok()); + } } )* }