diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 1999451d5..d0e321185 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -201,10 +201,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, @@ -333,6 +340,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 {}", from_part)?; + } + if let Some(from_part) = substring_for { + write!(f, " FOR {}", from_part)?; + } + + write!(f, ")") + } } } } diff --git a/src/parser.rs b/src/parser.rs index 7a0b23101..bee671f04 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -350,6 +350,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 { @@ -606,6 +607,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(Box::new), + substring_for: to_expr.map(Box::new), + }) + } + /// 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 ab4aa457b..f302245ee 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -2598,6 +2598,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..1fc35d99c 100644 --- a/tests/sqlparser_regression.rs +++ b/tests/sqlparser_regression.rs @@ -25,10 +25,9 @@ macro_rules! tpch_tests { #[test] fn $name() { let dialect = GenericDialect {}; - let res = Parser::parse_sql(&dialect, QUERIES[$value -1]); - // Ignore 6.sql and 22.sql - if $value != 6 && $value != 22 { + // Ignore 6.sql + if $value != 6 { assert!(res.is_ok()); } }