diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 407821d3..d5f929f1 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -219,6 +219,14 @@ pub enum Expr { substring_from: Option>, substring_for: Option>, }, + /// TRIM([BOTH | LEADING | TRAILING] [FROM ])\ + /// Or\ + /// TRIM() + Trim { + expr: Box, + // ([BOTH | LEADING | TRAILING], ) + trim_where: Option<(Box, Box)>, + }, /// `expr COLLATE collation` Collate { expr: Box, @@ -361,6 +369,16 @@ impl fmt::Display for Expr { write!(f, " FOR {}", from_part)?; } + write!(f, ")") + } + Expr::Trim { expr, trim_where } => { + write!(f, "TRIM(")?; + if let Some((ident, trim_char)) = trim_where { + write!(f, "{} {} FROM {}", ident, trim_char, expr)?; + } else { + write!(f, "{}", expr)?; + } + write!(f, ")") } } diff --git a/src/parser.rs b/src/parser.rs index b3129750..5bec257b 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -357,6 +357,7 @@ impl<'a> Parser<'a> { Keyword::EXISTS => self.parse_exists_expr(), Keyword::EXTRACT => self.parse_extract_expr(), Keyword::SUBSTRING => self.parse_substring_expr(), + Keyword::TRIM => self.parse_trim_expr(), Keyword::INTERVAL => self.parse_literal_interval(), Keyword::LISTAGG => self.parse_listagg_expr(), Keyword::NOT => Ok(Expr::UnaryOp { @@ -647,6 +648,31 @@ impl<'a> Parser<'a> { }) } + /// TRIM (WHERE 'text' FROM 'text')\ + /// TRIM ('text') + pub fn parse_trim_expr(&mut self) -> Result { + self.expect_token(&Token::LParen)?; + let mut where_expr = None; + if let Token::Word(word) = self.peek_token() { + if [Keyword::BOTH, Keyword::LEADING, Keyword::TRAILING] + .iter() + .any(|d| word.keyword == *d) + { + let ident = self.parse_identifier()?; + let sub_expr = self.parse_expr()?; + self.expect_keyword(Keyword::FROM)?; + where_expr = Some((ident, sub_expr)) + } + } + let expr = self.parse_expr()?; + self.expect_token(&Token::RParen)?; + + Ok(Expr::Trim { + expr: Box::new(expr), + trim_where: where_expr.map(|(ident, expr)| (Box::new(ident), Box::new(expr))), + }) + } + /// 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 eecf1dd5..4de9d5c6 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -2747,6 +2747,26 @@ fn parse_substring() { one_statement_parses_to("SELECT SUBSTRING('1' FOR 3)", "SELECT SUBSTRING('1' FOR 3)"); } +#[test] +fn parse_trim() { + one_statement_parses_to( + "SELECT TRIM(BOTH 'xyz' FROM 'xyzfooxyz')", + "SELECT TRIM(BOTH 'xyz' FROM 'xyzfooxyz')", + ); + + one_statement_parses_to( + "SELECT TRIM(LEADING 'xyz' FROM 'xyzfooxyz')", + "SELECT TRIM(LEADING 'xyz' FROM 'xyzfooxyz')", + ); + + one_statement_parses_to( + "SELECT TRIM(TRAILING 'xyz' FROM 'xyzfooxyz')", + "SELECT TRIM(TRAILING 'xyz' FROM 'xyzfooxyz')", + ); + + one_statement_parses_to("SELECT TRIM(' foo ')", "SELECT TRIM(' foo ')"); +} + #[test] fn parse_exists_subquery() { let expected_inner = verified_query("SELECT 1");