diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 35210526a..4f99dd55d 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -18,8 +18,8 @@ use alloc::{ vec::Vec, }; -use core::fmt::{self, Display}; use core::borrow::Borrow; +use core::fmt::{self, Display}; use std::ops::Deref; #[cfg(feature = "serde")] @@ -48,7 +48,6 @@ pub use self::value::{ escape_quoted_string, DateTimeField, DollarQuotedString, TrimWhereField, Value, }; - use crate::ast::helpers::stmt_data_loading::{ DataLoadingOptions, StageLoadSelectItem, StageParamsObject, }; @@ -176,14 +175,13 @@ impl fmt::Display for ObjectName { } } - #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] pub struct EngineSpec { pub name: String, pub options: Option>, - } +} impl fmt::Display for EngineSpec { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { @@ -584,12 +582,14 @@ pub enum Expr { /// ```sql /// TRIM([BOTH | LEADING | TRAILING] [ FROM] ) /// TRIM() + /// TRIM(, [, characters]) -- Snowflake /// ``` Trim { expr: Box, // ([BOTH | LEADING | TRAILING] trim_where: Option, trim_what: Option>, + trim_characters: Option>, }, /// ```sql /// OVERLAY( PLACING FROM [ FOR ] @@ -831,27 +831,39 @@ impl fmt::Display for Expr { write!(f, "{op}{expr}") } } - Expr::Cast { expr, data_type, format } => { + Expr::Cast { + expr, + data_type, + format, + } => { if let Some(format) = format { write!(f, "CAST({expr} AS {data_type} FORMAT {format})") } else { write!(f, "CAST({expr} AS {data_type})") } - }, - Expr::TryCast { expr, data_type, format } => { + } + Expr::TryCast { + expr, + data_type, + format, + } => { if let Some(format) = format { write!(f, "TRY_CAST({expr} AS {data_type} FORMAT {format})") } else { write!(f, "TRY_CAST({expr} AS {data_type})") } - }, - Expr::SafeCast { expr, data_type, format } => { + } + Expr::SafeCast { + expr, + data_type, + format, + } => { if let Some(format) = format { write!(f, "SAFE_CAST({expr} AS {data_type} FORMAT {format})") } else { write!(f, "SAFE_CAST({expr} AS {data_type})") } - }, + } Expr::Extract { field, expr } => write!(f, "EXTRACT({field} FROM {expr})"), Expr::Ceil { expr, field } => { if field == &DateTimeField::NoDateTime { @@ -984,6 +996,7 @@ impl fmt::Display for Expr { expr, trim_where, trim_what, + trim_characters, } => { write!(f, "TRIM(")?; if let Some(ident) = trim_where { @@ -993,6 +1006,9 @@ impl fmt::Display for Expr { write!(f, "{trim_char} FROM {expr}")?; } else { write!(f, "{expr}")?; + if let Some(characters) = trim_characters { + write!(f, ", {}", display_comma_separated(characters))?; + } } write!(f, ")") @@ -2232,7 +2248,7 @@ impl fmt::Display for Statement { cluster_by, destination_table, columns_with_types, - late_binding + late_binding, } => { write!( f, @@ -2259,7 +2275,7 @@ impl fmt::Display for Statement { write!(f, " CLUSTER BY ({})", display_comma_separated(cluster_by))?; } write!(f, " AS {query}")?; - if *late_binding{ + if *late_binding { write!(f, " WITH NO SCHEMA BINDING")?; } Ok(()) diff --git a/src/parser.rs b/src/parser.rs index 17c197801..e53a870d7 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1086,7 +1086,8 @@ impl<'a> Parser<'a> { self.expect_keyword(Keyword::AS)?; let data_type = self.parse_data_type()?; - let format = if dialect_of!(self is BigQueryDialect) && self.parse_keyword(Keyword::FORMAT) { + let format = if dialect_of!(self is BigQueryDialect) && self.parse_keyword(Keyword::FORMAT) + { Some(self.parse_value()?) } else { None @@ -1107,7 +1108,8 @@ impl<'a> Parser<'a> { self.expect_keyword(Keyword::AS)?; let data_type = self.parse_data_type()?; - let format = if dialect_of!(self is BigQueryDialect) && self.parse_keyword(Keyword::FORMAT) { + let format = if dialect_of!(self is BigQueryDialect) && self.parse_keyword(Keyword::FORMAT) + { Some(self.parse_value()?) } else { None @@ -1128,7 +1130,8 @@ impl<'a> Parser<'a> { self.expect_keyword(Keyword::AS)?; let data_type = self.parse_data_type()?; - let format = if dialect_of!(self is BigQueryDialect) && self.parse_keyword(Keyword::FORMAT) { + let format = if dialect_of!(self is BigQueryDialect) && self.parse_keyword(Keyword::FORMAT) + { Some(self.parse_value()?) } else { None @@ -1254,10 +1257,12 @@ impl<'a> Parser<'a> { /// ```sql /// TRIM ([WHERE] ['text' FROM] 'text') /// TRIM ('text') + /// TRIM(, [, characters]) -- Snowflake /// ``` pub fn parse_trim_expr(&mut self) -> Result { self.expect_token(&Token::LParen)?; let mut trim_where = None; + if let Token::Word(word) = self.peek_token().token { if [Keyword::BOTH, Keyword::LEADING, Keyword::TRAILING] .iter() @@ -1266,6 +1271,7 @@ impl<'a> Parser<'a> { trim_where = Some(self.parse_trim_where()?); } } + let expr = self.parse_expr()?; if self.parse_keyword(Keyword::FROM) { let trim_what = Box::new(expr); @@ -1275,6 +1281,16 @@ impl<'a> Parser<'a> { expr: Box::new(expr), trim_where, trim_what: Some(trim_what), + trim_characters: None, + }) + } else if self.consume_token(&Token::Comma) && dialect_of!(self is SnowflakeDialect) { + let characters = self.parse_comma_separated(Parser::parse_expr)?; + self.expect_token(&Token::RParen)?; + Ok(Expr::Trim { + expr: Box::new(expr), + trim_where: None, + trim_what: None, + trim_characters: Some(characters), }) } else { self.expect_token(&Token::RParen)?; @@ -1282,6 +1298,7 @@ impl<'a> Parser<'a> { expr: Box::new(expr), trim_where, trim_what: None, + trim_characters: None, }) } } @@ -1932,7 +1949,11 @@ impl<'a> Parser<'a> { }) } - pub fn parse_clickhouse_array_index(&mut self, expr: Expr, ind: String) -> Result { + pub fn parse_clickhouse_array_index( + &mut self, + expr: Expr, + ind: String, + ) -> Result { let without_dot = ind[1..].to_string(); let index = Expr::Value(Value::Number(without_dot, false)); Ok(Expr::ArrayIndex { @@ -2859,8 +2880,10 @@ impl<'a> Parser<'a> { // ANSI SQL and Postgres support RECURSIVE here, but we don't support it either. let name = self.parse_object_name()?; - - let destination_table = if dialect_of!(self is ClickHouseDialect) && materialized && self.parse_keyword(Keyword::TO) { + let destination_table = if dialect_of!(self is ClickHouseDialect) + && materialized + && self.parse_keyword(Keyword::TO) + { Some(self.parse_object_name()?) } else { None @@ -2870,7 +2893,10 @@ impl<'a> Parser<'a> { let (cols, _) = self.parse_columns()?; (vec![], cols) } else { - (self.parse_parenthesized_column_list(Optional, false)?, vec![]) + ( + self.parse_parenthesized_column_list(Optional, false)?, + vec![], + ) }; let with_options = self.parse_options(Keyword::WITH)?; @@ -2886,7 +2912,13 @@ impl<'a> Parser<'a> { let query = Box::new(self.parse_query()?); // Optional `WITH [ CASCADED | LOCAL ] CHECK OPTION` is widely supported here. - let late_binding = dialect_of!(self is RedshiftSqlDialect) && self.parse_keywords(&[Keyword::WITH, Keyword::NO, Keyword::SCHEMA, Keyword::BINDING]); + let late_binding = dialect_of!(self is RedshiftSqlDialect) + && self.parse_keywords(&[ + Keyword::WITH, + Keyword::NO, + Keyword::SCHEMA, + Keyword::BINDING, + ]); Ok(Statement::CreateView { name, @@ -2898,7 +2930,7 @@ impl<'a> Parser<'a> { cluster_by, destination_table, columns_with_types, - late_binding + late_binding, }) } @@ -3489,7 +3521,10 @@ impl<'a> Parser<'a> { } else { None }; - Some(EngineSpec{name: engine_name, options: engine_options}) + Some(EngineSpec { + name: engine_name, + options: engine_options, + }) } else { None }; @@ -4533,10 +4568,9 @@ impl<'a> Parser<'a> { Ok(DataType::Custom(type_name, modifiers)) } else { Ok(DataType::Custom(type_name, vec![])) - } + }; } - let next_token = self.next_token(); let mut data = match next_token.token { Token::Word(w) => match w.keyword { @@ -4789,9 +4823,13 @@ impl<'a> Parser<'a> { // character. When it sees such a , your DBMS will // ignore the and treat the multiple strings as // a single ." - Token::SingleQuotedString(s) => Ok(Some(Ident::with_quote('\'', s).spanning(next_token.span))), + Token::SingleQuotedString(s) => { + Ok(Some(Ident::with_quote('\'', s).spanning(next_token.span))) + } // Support for MySql dialect double quoted string, `AS "HOUR"` for example - Token::DoubleQuotedString(s) => Ok(Some(Ident::with_quote('\"', s).spanning(next_token.span))), + Token::DoubleQuotedString(s) => { + Ok(Some(Ident::with_quote('\"', s).spanning(next_token.span))) + } _ => { if after_as { return self.expected("an identifier after AS", next_token); @@ -5085,13 +5123,11 @@ impl<'a> Parser<'a> { loop { let next_token = self.next_token(); match next_token.token { - Token::EOF => { - return parser_err!(format!("unbalanced parens: {}", modifier)) - } + Token::EOF => return parser_err!(format!("unbalanced parens: {}", modifier)), Token::LParen => { modifier.push_str(&next_token.to_string()); depth += 1; - }, + } Token::RParen => { depth -= 1; if depth == 0 { @@ -5103,7 +5139,7 @@ impl<'a> Parser<'a> { } } if depth != 0 { - return parser_err!(format!("unbalanced parens: {}", modifier)) + return parser_err!(format!("unbalanced parens: {}", modifier)); } Ok(Some(vec![modifier])) } else { @@ -5278,11 +5314,12 @@ impl<'a> Parser<'a> { } } - let limit_by = if dialect_of!(self is ClickHouseDialect) && self.parse_keyword(Keyword::BY) { - self.parse_comma_separated(Parser::parse_expr)? - } else { - vec![] - }; + let limit_by = + if dialect_of!(self is ClickHouseDialect) && self.parse_keyword(Keyword::BY) { + self.parse_comma_separated(Parser::parse_expr)? + } else { + vec![] + }; let fetch = if self.parse_keyword(Keyword::FETCH) { Some(self.parse_fetch()?) @@ -5989,7 +6026,12 @@ impl<'a> Parser<'a> { self.expect_token(&Token::LParen)?; let args = self.parse_optional_args()?; let alias = self.parse_optional_table_alias(keywords::RESERVED_FOR_TABLE_ALIAS)?; - Ok(TableFactor::Function { lateral: true, name, args, alias }) + Ok(TableFactor::Function { + lateral: true, + name, + args, + alias, + }) } } else if self.parse_keyword(Keyword::TABLE) { // parse table function (SELECT * FROM TABLE () [ AS ]) @@ -6168,17 +6210,12 @@ impl<'a> Parser<'a> { } } - pub fn parse_array_join_table_factor( - &mut self - ) -> Result { + pub fn parse_array_join_table_factor(&mut self) -> Result { { let expr = self.parse_expr()?; let alias = self.parse_optional_table_alias(keywords::RESERVED_FOR_TABLE_ALIAS)?; - Ok(TableFactor::FieldAccessor { - expr, - alias, - }) + Ok(TableFactor::FieldAccessor { expr, alias }) } } @@ -6613,7 +6650,7 @@ impl<'a> Parser<'a> { Some(NullTreatment::IGNORE) } else if self.parse_keywords(&[Keyword::RESPECT, Keyword::NULLS]) { Some(NullTreatment::RESPECT) - }else { + } else { None }; self.expect_token(&Token::RParen)?; @@ -6652,10 +6689,8 @@ impl<'a> Parser<'a> { alias, } .spanning(self.span_from_index(start_span)), - None => { - SelectItem::UnnamedExpr(expr_with_location) - .spanning(self.span_from_index(start_span)) - } + None => SelectItem::UnnamedExpr(expr_with_location) + .spanning(self.span_from_index(start_span)), }) } WildcardExpr::QualifiedWildcard(prefix) => Ok(SelectItem::QualifiedWildcard( @@ -7362,7 +7397,7 @@ impl<'a> Parser<'a> { } let start_span = start_token.span; - let mut idx = self.index.max(start_index).min(self.tokens.len()-1); + let mut idx = self.index.max(start_index).min(self.tokens.len() - 1); loop { if idx <= start_index || idx >= self.tokens.len() { break; diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 4dc758e74..73d509d59 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -190,7 +190,9 @@ fn parse_update() { let sql = "UPDATE t SET a = 1 extrabadstuff"; let res = parse_sql_statements(sql); assert_eq!( - ParserError::ParserError("Expected end of statement, found: extrabadstuff\nNear ` t SET a = 1`".to_string()), + ParserError::ParserError( + "Expected end of statement, found: extrabadstuff\nNear ` t SET a = 1`".to_string() + ), res.unwrap_err() ); } @@ -236,12 +238,14 @@ fn parse_update_set_from() { distinct: None, top: None, projection: vec![ - SelectItem::UnnamedExpr(Expr::Identifier( - Ident::new("name").empty_span() - ).empty_span()).empty_span(), - SelectItem::UnnamedExpr(Expr::Identifier( - Ident::new("id").empty_span() - ).empty_span()).empty_span(), + SelectItem::UnnamedExpr( + Expr::Identifier(Ident::new("name").empty_span()).empty_span() + ) + .empty_span(), + SelectItem::UnnamedExpr( + Expr::Identifier(Ident::new("id").empty_span()).empty_span() + ) + .empty_span(), ], into: None, from: vec![TableWithJoins { @@ -360,9 +364,12 @@ fn parse_select_with_table_alias() { assert_eq!( select.projection, vec![ - SelectItem::UnnamedExpr(Expr::Identifier(Ident::new("a").empty_span()).empty_span(),).empty_span(), - SelectItem::UnnamedExpr(Expr::Identifier(Ident::new("b").empty_span()).empty_span(),).empty_span(), - SelectItem::UnnamedExpr(Expr::Identifier(Ident::new("c").empty_span()).empty_span(),).empty_span(), + SelectItem::UnnamedExpr(Expr::Identifier(Ident::new("a").empty_span()).empty_span(),) + .empty_span(), + SelectItem::UnnamedExpr(Expr::Identifier(Ident::new("b").empty_span()).empty_span(),) + .empty_span(), + SelectItem::UnnamedExpr(Expr::Identifier(Ident::new("c").empty_span()).empty_span(),) + .empty_span(), ] ); assert_eq!( @@ -638,7 +645,8 @@ fn parse_select_distinct() { let select = verified_only_select(sql); assert!(select.distinct.is_some()); assert_eq!( - &SelectItem::UnnamedExpr(Expr::Identifier(Ident::new("name").empty_span()).empty_span()).empty_span(), + &SelectItem::UnnamedExpr(Expr::Identifier(Ident::new("name").empty_span()).empty_span()) + .empty_span(), only(&select.projection) ); } @@ -649,11 +657,13 @@ fn parse_select_distinct_two_fields() { let select = verified_only_select(sql); assert!(select.distinct.is_some()); assert_eq!( - &SelectItem::UnnamedExpr(Expr::Identifier(Ident::new("name").empty_span()).empty_span()).empty_span(), + &SelectItem::UnnamedExpr(Expr::Identifier(Ident::new("name").empty_span()).empty_span()) + .empty_span(), &select.projection[0] ); assert_eq!( - &SelectItem::UnnamedExpr(Expr::Identifier(Ident::new("id").empty_span()).empty_span()).empty_span(), + &SelectItem::UnnamedExpr(Expr::Identifier(Ident::new("id").empty_span()).empty_span()) + .empty_span(), &select.projection[1] ); } @@ -663,10 +673,14 @@ fn parse_select_distinct_tuple() { let sql = "SELECT DISTINCT (name, id) FROM customer"; let select = verified_only_select(sql); assert_eq!( - &vec![SelectItem::UnnamedExpr(Expr::Tuple(vec![ - Expr::Identifier(Ident::new("name").empty_span()), - Expr::Identifier(Ident::new("id").empty_span()), - ]).empty_span()).empty_span()], + &vec![SelectItem::UnnamedExpr( + Expr::Tuple(vec![ + Expr::Identifier(Ident::new("name").empty_span()), + Expr::Identifier(Ident::new("id").empty_span()), + ]) + .empty_span() + ) + .empty_span()], &select.projection ); } @@ -676,7 +690,9 @@ fn parse_select_distinct_on() { let sql = "SELECT DISTINCT ON (album_id) name FROM track ORDER BY album_id, milliseconds"; let select = verified_only_select(sql); assert_eq!( - &Some(Distinct::On(vec![Expr::Identifier(Ident::new("album_id").empty_span())])), + &Some(Distinct::On(vec![Expr::Identifier( + Ident::new("album_id").empty_span() + )])), &select.distinct ); @@ -699,7 +715,9 @@ fn parse_select_distinct_on() { fn parse_select_distinct_missing_paren() { let result = parse_sql_statements("SELECT DISTINCT (name, id FROM customer"); assert_eq!( - ParserError::ParserError("Expected ), found: FROM\nNear `SELECT DISTINCT (name, id`".to_string()), + ParserError::ParserError( + "Expected ), found: FROM\nNear `SELECT DISTINCT (name, id`".to_string() + ), result.unwrap_err(), ); } @@ -743,7 +761,9 @@ fn parse_select_into() { let sql = "SELECT * INTO table0 asdf FROM table1"; let result = parse_sql_statements(sql); assert_eq!( - ParserError::ParserError("Expected end of statement, found: asdf\nNear `SELECT * INTO table0`".to_string()), + ParserError::ParserError( + "Expected end of statement, found: asdf\nNear `SELECT * INTO table0`".to_string() + ), result.unwrap_err() ) } @@ -763,7 +783,8 @@ fn parse_select_wildcard() { &SelectItem::QualifiedWildcard( ObjectName(vec![Ident::new("foo")]), WildcardAdditionalOptions::default() - ).empty_span(), + ) + .empty_span(), only(&select.projection) ); @@ -773,14 +794,17 @@ fn parse_select_wildcard() { &SelectItem::QualifiedWildcard( ObjectName(vec![Ident::new("myschema"), Ident::new("mytable"),]), WildcardAdditionalOptions::default(), - ).empty_span(), + ) + .empty_span(), only(&select.projection) ); let sql = "SELECT * + * FROM foo;"; let result = parse_sql_statements(sql); assert_eq!( - ParserError::ParserError("Expected end of statement, found: +\nNear `SELECT *`".to_string()), + ParserError::ParserError( + "Expected end of statement, found: +\nNear `SELECT *`".to_string() + ), result.unwrap_err(), ); } @@ -820,13 +844,17 @@ fn parse_count_wildcard() { fn test_eof_after_as() { let res = parse_sql_statements("SELECT foo AS"); assert_eq!( - ParserError::ParserError("Expected an identifier after AS, found: EOF\nNear `SELECT foo AS`".to_string()), + ParserError::ParserError( + "Expected an identifier after AS, found: EOF\nNear `SELECT foo AS`".to_string() + ), res.unwrap_err() ); let res = parse_sql_statements("SELECT 1 FROM foo AS"); assert_eq!( - ParserError::ParserError("Expected an identifier after AS, found: EOF\nNear `SELECT 1 FROM foo AS`".to_string()), + ParserError::ParserError( + "Expected an identifier after AS, found: EOF\nNear `SELECT 1 FROM foo AS`".to_string() + ), res.unwrap_err() ); } @@ -902,7 +930,9 @@ fn parse_not() { fn parse_invalid_infix_not() { let res = parse_sql_statements("SELECT c FROM t WHERE c NOT ("); assert_eq!( - ParserError::ParserError("Expected end of statement, found: NOT\nNear ` c FROM t WHERE c`".to_string()), + ParserError::ParserError( + "Expected end of statement, found: NOT\nNear ` c FROM t WHERE c`".to_string() + ), res.unwrap_err(), ); } @@ -990,11 +1020,13 @@ fn parse_exponent_in_select() -> Result<(), ParserError> { SelectItem::ExprWithAlias { expr: Expr::Value(number("1e3")).empty_span(), alias: Ident::new("a").empty_span() - }.empty_span(), + } + .empty_span(), SelectItem::ExprWithAlias { expr: Expr::Value(number("1")).empty_span(), alias: Ident::new("e").empty_span() - }.empty_span(), + } + .empty_span(), SelectItem::UnnamedExpr(Expr::Value(number("0.5e2")).empty_span()).empty_span(), ], &select.projection @@ -1026,13 +1058,16 @@ fn parse_escaped_single_quote_string_predicate() { WHERE salary <> 'Jim''s salary'"; let ast = verified_only_select(sql); assert_eq!( - Some(Expr::BinaryOp { - left: Box::new(Expr::Identifier(Ident::new("salary").empty_span())), - op: NotEq, - right: Box::new(Expr::Value(Value::SingleQuotedString( - "Jim's salary".to_string() - ))), - }.empty_span()), + Some( + Expr::BinaryOp { + left: Box::new(Expr::Identifier(Ident::new("salary").empty_span())), + op: NotEq, + right: Box::new(Expr::Value(Value::SingleQuotedString( + "Jim's salary".to_string() + ))), + } + .empty_span() + ), ast.selection, ); } @@ -1236,12 +1271,15 @@ fn parse_null_like() { negated: false, pattern: Box::new(Expr::Value(Value::Null)), escape_char: None, - }.empty_span(), + } + .empty_span(), alias: Ident { value: "col_null".to_owned(), quote_style: None, - }.empty_span(), - }.empty_span(), + } + .empty_span(), + } + .empty_span(), select.projection[0] ); assert_eq!( @@ -1251,12 +1289,15 @@ fn parse_null_like() { negated: false, pattern: Box::new(Expr::Identifier(Ident::new("column1").empty_span())), escape_char: None, - }.empty_span(), + } + .empty_span(), alias: Ident { value: "null_col".to_owned(), quote_style: None, - }.empty_span(), - }.empty_span(), + } + .empty_span(), + } + .empty_span(), select.projection[1] ); } @@ -1275,7 +1316,8 @@ fn parse_ilike() { negated, pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), escape_char: None, - }.empty_span(), + } + .empty_span(), select.selection.unwrap() ); @@ -1291,7 +1333,8 @@ fn parse_ilike() { negated, pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), escape_char: Some('^'), - }.empty_span(), + } + .empty_span(), select.selection.unwrap() ); @@ -1308,7 +1351,8 @@ fn parse_ilike() { negated, pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), escape_char: None, - })).empty_span(), + })) + .empty_span(), select.selection.unwrap() ); } @@ -1332,7 +1376,8 @@ fn parse_in_list() { Expr::Value(Value::SingleQuotedString("MED".to_string())), ], negated, - }.empty_span(), + } + .empty_span(), select.selection.unwrap() ); } @@ -1349,7 +1394,8 @@ fn parse_in_subquery() { expr: Box::new(Expr::Identifier(Ident::new("segment").empty_span())), subquery: Box::new(verified_query("SELECT segm FROM bar")), negated: false, - }.empty_span(), + } + .empty_span(), select.selection.unwrap() ); } @@ -1367,7 +1413,8 @@ fn parse_in_unnest() { expr: Box::new(Expr::Identifier(Ident::new("segment").empty_span())), array_expr: Box::new(verified_expr("expr")), negated, - }.empty_span(), + } + .empty_span(), select.selection.unwrap() ); } @@ -1381,7 +1428,9 @@ fn parse_in_error() { let sql = "SELECT * FROM customers WHERE segment in segment"; let res = parse_sql_statements(sql); assert_eq!( - ParserError::ParserError("Expected (, found: segment\nNear ` FROM customers WHERE segment in`".to_string()), + ParserError::ParserError( + "Expected (, found: segment\nNear ` FROM customers WHERE segment in`".to_string() + ), res.unwrap_err() ); } @@ -1392,11 +1441,15 @@ fn parse_string_agg() { let select = verified_only_select(sql); assert_eq!( - SelectItem::UnnamedExpr(Expr::BinaryOp { - left: Box::new(Expr::Identifier(Ident::new("a").empty_span())), - op: BinaryOperator::StringConcat, - right: Box::new(Expr::Identifier(Ident::new("b").empty_span())), - }.empty_span()).empty_span(), + SelectItem::UnnamedExpr( + Expr::BinaryOp { + left: Box::new(Expr::Identifier(Ident::new("a").empty_span())), + op: BinaryOperator::StringConcat, + right: Box::new(Expr::Identifier(Ident::new("b").empty_span())), + } + .empty_span() + ) + .empty_span(), select.projection[0] ); } @@ -1424,11 +1477,15 @@ fn parse_bitwise_ops() { for (str_op, op, dialects) in bitwise_ops { let select = dialects.verified_only_select(&format!("SELECT a {} b", &str_op)); assert_eq!( - SelectItem::UnnamedExpr(Expr::BinaryOp { - left: Box::new(Expr::Identifier(Ident::new("a").empty_span())), - op: op.clone(), - right: Box::new(Expr::Identifier(Ident::new("b").empty_span())), - }.empty_span()).empty_span(), + SelectItem::UnnamedExpr( + Expr::BinaryOp { + left: Box::new(Expr::Identifier(Ident::new("a").empty_span())), + op: op.clone(), + right: Box::new(Expr::Identifier(Ident::new("b").empty_span())), + } + .empty_span() + ) + .empty_span(), select.projection[0] ); } @@ -1438,13 +1495,17 @@ fn parse_bitwise_ops() { fn parse_binary_any() { let select = verified_only_select("SELECT a = ANY(b)"); assert_eq!( - SelectItem::UnnamedExpr(Expr::BinaryOp { - left: Box::new(Expr::Identifier(Ident::new("a").empty_span())), - op: BinaryOperator::Eq, - right: Box::new(Expr::AnyOp(Box::new(Expr::Identifier( - Ident::new("b").empty_span() - )))), - }.empty_span()).empty_span(), + SelectItem::UnnamedExpr( + Expr::BinaryOp { + left: Box::new(Expr::Identifier(Ident::new("a").empty_span())), + op: BinaryOperator::Eq, + right: Box::new(Expr::AnyOp(Box::new(Expr::Identifier( + Ident::new("b").empty_span() + )))), + } + .empty_span() + ) + .empty_span(), select.projection[0] ); } @@ -1453,13 +1514,17 @@ fn parse_binary_any() { fn parse_binary_all() { let select = verified_only_select("SELECT a = ALL(b)"); assert_eq!( - SelectItem::UnnamedExpr(Expr::BinaryOp { - left: Box::new(Expr::Identifier(Ident::new("a").empty_span())), - op: BinaryOperator::Eq, - right: Box::new(Expr::AllOp(Box::new(Expr::Identifier( - Ident::new("b").empty_span() - )))), - }.empty_span()).empty_span(), + SelectItem::UnnamedExpr( + Expr::BinaryOp { + left: Box::new(Expr::Identifier(Ident::new("a").empty_span())), + op: BinaryOperator::Eq, + right: Box::new(Expr::AllOp(Box::new(Expr::Identifier( + Ident::new("b").empty_span() + )))), + } + .empty_span() + ) + .empty_span(), select.projection[0] ); } @@ -1469,35 +1534,51 @@ fn parse_logical_xor() { let sql = "SELECT true XOR true, false XOR false, true XOR false, false XOR true"; let select = verified_only_select(sql); assert_eq!( - SelectItem::UnnamedExpr(Expr::BinaryOp { - left: Box::new(Expr::Value(Value::Boolean(true))), - op: BinaryOperator::Xor, - right: Box::new(Expr::Value(Value::Boolean(true))), - }.empty_span()).empty_span(), + SelectItem::UnnamedExpr( + Expr::BinaryOp { + left: Box::new(Expr::Value(Value::Boolean(true))), + op: BinaryOperator::Xor, + right: Box::new(Expr::Value(Value::Boolean(true))), + } + .empty_span() + ) + .empty_span(), select.projection[0] ); assert_eq!( - SelectItem::UnnamedExpr(Expr::BinaryOp { - left: Box::new(Expr::Value(Value::Boolean(false))), - op: BinaryOperator::Xor, - right: Box::new(Expr::Value(Value::Boolean(false))), - }.empty_span()).empty_span(), + SelectItem::UnnamedExpr( + Expr::BinaryOp { + left: Box::new(Expr::Value(Value::Boolean(false))), + op: BinaryOperator::Xor, + right: Box::new(Expr::Value(Value::Boolean(false))), + } + .empty_span() + ) + .empty_span(), select.projection[1] ); assert_eq!( - SelectItem::UnnamedExpr(Expr::BinaryOp { - left: Box::new(Expr::Value(Value::Boolean(true))), - op: BinaryOperator::Xor, - right: Box::new(Expr::Value(Value::Boolean(false))), - }.empty_span()).empty_span(), + SelectItem::UnnamedExpr( + Expr::BinaryOp { + left: Box::new(Expr::Value(Value::Boolean(true))), + op: BinaryOperator::Xor, + right: Box::new(Expr::Value(Value::Boolean(false))), + } + .empty_span() + ) + .empty_span(), select.projection[2] ); assert_eq!( - SelectItem::UnnamedExpr(Expr::BinaryOp { - left: Box::new(Expr::Value(Value::Boolean(false))), - op: BinaryOperator::Xor, - right: Box::new(Expr::Value(Value::Boolean(true))), - }.empty_span()).empty_span(), + SelectItem::UnnamedExpr( + Expr::BinaryOp { + left: Box::new(Expr::Value(Value::Boolean(false))), + op: BinaryOperator::Xor, + right: Box::new(Expr::Value(Value::Boolean(true))), + } + .empty_span() + ) + .empty_span(), select.projection[3] ); } @@ -1516,7 +1597,8 @@ fn parse_between() { low: Box::new(Expr::Value(number("25"))), high: Box::new(Expr::Value(number("32"))), negated, - }.empty_span(), + } + .empty_span(), select.selection.unwrap() ); } @@ -1543,7 +1625,8 @@ fn parse_between_with_expr() { right: Box::new(Expr::Value(number("4"))), }), negated: false, - })).empty_span(), + })) + .empty_span(), select.selection.unwrap() ); @@ -1567,7 +1650,8 @@ fn parse_between_with_expr() { high: Box::new(Expr::Value(number("2"))), negated: false, }), - }.empty_span(), + } + .empty_span(), select.selection.unwrap(), ) } @@ -1578,16 +1662,21 @@ fn parse_tuples() { let select = verified_only_select(sql); assert_eq!( vec![ - SelectItem::UnnamedExpr(Expr::Tuple(vec![ - Expr::Value(number("1")), - Expr::Value(number("2")), - ]).empty_span()).empty_span(), - SelectItem::UnnamedExpr(Expr::Nested(Box::new(Expr::Value(number("1")))).empty_span()).empty_span(), - SelectItem::UnnamedExpr(Expr::Tuple(vec![ - Expr::Value(Value::SingleQuotedString("foo".into())), - Expr::Value(number("3")), - Expr::Identifier(Ident::new("baz").empty_span()), - ]).empty_span()).empty_span(), + SelectItem::UnnamedExpr( + Expr::Tuple(vec![Expr::Value(number("1")), Expr::Value(number("2")),]).empty_span() + ) + .empty_span(), + SelectItem::UnnamedExpr(Expr::Nested(Box::new(Expr::Value(number("1")))).empty_span()) + .empty_span(), + SelectItem::UnnamedExpr( + Expr::Tuple(vec![ + Expr::Value(Value::SingleQuotedString("foo".into())), + Expr::Value(number("3")), + Expr::Identifier(Ident::new("baz").empty_span()), + ]) + .empty_span() + ) + .empty_span(), ], select.projection ); @@ -1965,7 +2054,9 @@ fn parse_extract() { let res = parse_sql_statements("SELECT EXTRACT(JIFFY FROM d)"); assert_eq!( - ParserError::ParserError("Expected date/time field, found: JIFFY\nNear `SELECT EXTRACT(JIFFY`".to_string()), + ParserError::ParserError( + "Expected date/time field, found: JIFFY\nNear `SELECT EXTRACT(JIFFY`".to_string() + ), res.unwrap_err() ); } @@ -2003,7 +2094,9 @@ fn parse_ceil_datetime() { let res = parse_sql_statements("SELECT CEIL(d TO JIFFY) FROM df"); assert_eq!( - ParserError::ParserError("Expected date/time field, found: JIFFY\nNear `SELECT CEIL(d TO JIFFY`".to_string()), + ParserError::ParserError( + "Expected date/time field, found: JIFFY\nNear `SELECT CEIL(d TO JIFFY`".to_string() + ), res.unwrap_err() ); } @@ -2029,7 +2122,9 @@ fn parse_floor_datetime() { let res = parse_sql_statements("SELECT FLOOR(d TO JIFFY) FROM df"); assert_eq!( - ParserError::ParserError("Expected date/time field, found: JIFFY\nNear `SELECT FLOOR(d TO JIFFY`".to_string()), + ParserError::ParserError( + "Expected date/time field, found: JIFFY\nNear `SELECT FLOOR(d TO JIFFY`".to_string() + ), res.unwrap_err() ); } @@ -2377,7 +2472,9 @@ fn parse_create_table_hive_array() { assert_eq!( dialects.parse_sql_statements(sql).unwrap_err(), - ParserError::ParserError("Expected >, found: )\nNear `name int, val array, found: )\nNear `name int, val array {} @@ -6547,19 +6749,25 @@ fn parse_offset_and_limit() { // Can't repeat OFFSET / LIMIT let res = parse_sql_statements("SELECT foo FROM bar OFFSET 2 OFFSET 2"); assert_eq!( - ParserError::ParserError("Expected end of statement, found: OFFSET\nNear ` foo FROM bar OFFSET 2`".to_string()), + ParserError::ParserError( + "Expected end of statement, found: OFFSET\nNear ` foo FROM bar OFFSET 2`".to_string() + ), res.unwrap_err() ); let res = parse_sql_statements("SELECT foo FROM bar LIMIT 2 LIMIT 2"); assert_eq!( - ParserError::ParserError("Expected end of statement, found: LIMIT\nNear ` foo FROM bar LIMIT 2`".to_string()), + ParserError::ParserError( + "Expected end of statement, found: LIMIT\nNear ` foo FROM bar LIMIT 2`".to_string() + ), res.unwrap_err() ); let res = parse_sql_statements("SELECT foo FROM bar OFFSET 2 LIMIT 2 OFFSET 2"); assert_eq!( - ParserError::ParserError("Expected end of statement, found: OFFSET\nNear ` bar OFFSET 2 LIMIT 2`".to_string()), + ParserError::ParserError( + "Expected end of statement, found: OFFSET\nNear ` bar OFFSET 2 LIMIT 2`".to_string() + ), res.unwrap_err() ); } @@ -6682,7 +6890,9 @@ fn parse_position_negative() { let sql = "SELECT POSITION(foo IN) from bar"; let res = parse_sql_statements(sql); assert_eq!( - ParserError::ParserError("Expected an expression:, found: )\nNear `SELECT POSITION(foo IN)`".to_string()), + ParserError::ParserError( + "Expected an expression:, found: )\nNear `SELECT POSITION(foo IN)`".to_string() + ), res.unwrap_err() ); } @@ -6964,19 +7174,25 @@ fn parse_cache_table() { let res = parse_sql_statements("CACHE 'table_name'"); assert_eq!( - ParserError::ParserError("Expected a `TABLE` keyword, found: 'table_name'\nNear `CACHE `".to_string()), + ParserError::ParserError( + "Expected a `TABLE` keyword, found: 'table_name'\nNear `CACHE `".to_string() + ), res.unwrap_err() ); let res = parse_sql_statements("CACHE 'table_name' OPTIONS('K1'='V1')"); assert_eq!( - ParserError::ParserError("Expected a `TABLE` keyword, found: OPTIONS\nNear `CACHE 'table_name'`".to_string()), + ParserError::ParserError( + "Expected a `TABLE` keyword, found: OPTIONS\nNear `CACHE 'table_name'`".to_string() + ), res.unwrap_err() ); let res = parse_sql_statements("CACHE flag 'table_name' OPTIONS('K1'='V1')"); assert_eq!( - ParserError::ParserError("Expected a `TABLE` keyword, found: 'table_name'\nNear `CACHE flag`".to_string()), + ParserError::ParserError( + "Expected a `TABLE` keyword, found: 'table_name'\nNear `CACHE flag`".to_string() + ), res.unwrap_err() ); } @@ -7001,19 +7217,25 @@ fn parse_uncache_table() { let res = parse_sql_statements("UNCACHE TABLE 'table_name' foo"); assert_eq!( - ParserError::ParserError("Expected an `EOF`, found: foo\nNear `UNCACHE TABLE 'table_name'`".to_string()), + ParserError::ParserError( + "Expected an `EOF`, found: foo\nNear `UNCACHE TABLE 'table_name'`".to_string() + ), res.unwrap_err() ); let res = parse_sql_statements("UNCACHE 'table_name' foo"); assert_eq!( - ParserError::ParserError("Expected a `TABLE` keyword, found: 'table_name'\nNear `UNCACHE`".to_string()), + ParserError::ParserError( + "Expected a `TABLE` keyword, found: 'table_name'\nNear `UNCACHE`".to_string() + ), res.unwrap_err() ); let res = parse_sql_statements("UNCACHE IF EXISTS 'table_name' foo"); assert_eq!( - ParserError::ParserError("Expected a `TABLE` keyword, found: IF\nNear `UNCACHE`".to_string()), + ParserError::ParserError( + "Expected a `TABLE` keyword, found: IF\nNear `UNCACHE`".to_string() + ), res.unwrap_err() ); } @@ -7127,7 +7349,8 @@ fn parse_pivot_table() { name: Ident { value: "p".to_string(), quote_style: None - }.empty_span(), + } + .empty_span(), columns: vec![Ident::new("c"), Ident::new("d")], }), } diff --git a/tests/sqlparser_snowflake.rs b/tests/sqlparser_snowflake.rs index 7be43cb59..379ad8a4b 100644 --- a/tests/sqlparser_snowflake.rs +++ b/tests/sqlparser_snowflake.rs @@ -186,33 +186,45 @@ fn parse_json_using_colon() { let sql = "SELECT a:b FROM t"; let select = snowflake().verified_only_select(sql); assert_eq!( - SelectItem::UnnamedExpr(Expr::JsonAccess { - left: Box::new(Expr::Identifier(Ident::new("a").empty_span())), - operator: JsonOperator::Colon, - right: Box::new(Expr::Value(Value::UnQuotedString("b".to_string()))), - }.empty_span()).empty_span(), + SelectItem::UnnamedExpr( + Expr::JsonAccess { + left: Box::new(Expr::Identifier(Ident::new("a").empty_span())), + operator: JsonOperator::Colon, + right: Box::new(Expr::Value(Value::UnQuotedString("b".to_string()))), + } + .empty_span() + ) + .empty_span(), select.projection[0] ); let sql = "SELECT a:type FROM t"; let select = snowflake().verified_only_select(sql); assert_eq!( - SelectItem::UnnamedExpr(Expr::JsonAccess { - left: Box::new(Expr::Identifier(Ident::new("a").empty_span())), - operator: JsonOperator::Colon, - right: Box::new(Expr::Value(Value::UnQuotedString("type".to_string()))), - }.empty_span()).empty_span(), + SelectItem::UnnamedExpr( + Expr::JsonAccess { + left: Box::new(Expr::Identifier(Ident::new("a").empty_span())), + operator: JsonOperator::Colon, + right: Box::new(Expr::Value(Value::UnQuotedString("type".to_string()))), + } + .empty_span() + ) + .empty_span(), select.projection[0] ); let sql = "SELECT a:location FROM t"; let select = snowflake().verified_only_select(sql); assert_eq!( - SelectItem::UnnamedExpr(Expr::JsonAccess { - left: Box::new(Expr::Identifier(Ident::new("a").empty_span())), - operator: JsonOperator::Colon, - right: Box::new(Expr::Value(Value::UnQuotedString("location".to_string()))), - }.empty_span()).empty_span(), + SelectItem::UnnamedExpr( + Expr::JsonAccess { + left: Box::new(Expr::Identifier(Ident::new("a").empty_span())), + operator: JsonOperator::Colon, + right: Box::new(Expr::Value(Value::UnQuotedString("location".to_string()))), + } + .empty_span() + ) + .empty_span(), select.projection[0] ); @@ -234,7 +246,10 @@ fn parse_delimited_identifiers() { with_hints, } => { assert_eq!(vec![Ident::with_quote('"', "a table")], name.0); - assert_eq!(Ident::with_quote('"', "alias").empty_span(), alias.unwrap().name); + assert_eq!( + Ident::with_quote('"', "alias").empty_span(), + alias.unwrap().name + ); assert!(args.is_none()); assert!(with_hints.is_empty()); } @@ -291,7 +306,8 @@ fn parse_like() { negated, pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), escape_char: None, - }.empty_span(), + } + .empty_span(), select.selection.unwrap() ); @@ -307,7 +323,8 @@ fn parse_like() { negated, pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), escape_char: Some('\\'), - }.empty_span(), + } + .empty_span(), select.selection.unwrap() ); @@ -324,7 +341,8 @@ fn parse_like() { negated, pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), escape_char: None, - })).empty_span(), + })) + .empty_span(), select.selection.unwrap() ); } @@ -346,7 +364,8 @@ fn parse_similar_to() { negated, pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), escape_char: None, - }.empty_span(), + } + .empty_span(), select.selection.unwrap() ); @@ -362,7 +381,8 @@ fn parse_similar_to() { negated, pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), escape_char: Some('\\'), - }.empty_span(), + } + .empty_span(), select.selection.unwrap() ); @@ -378,7 +398,8 @@ fn parse_similar_to() { negated, pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), escape_char: Some('\\'), - })).empty_span(), + })) + .empty_span(), select.selection.unwrap() ); } @@ -425,7 +446,8 @@ fn test_select_wildcard_with_exclude() { let expected = SelectItem::Wildcard(WildcardAdditionalOptions { opt_exclude: Some(ExcludeSelectItem::Multiple(vec![Ident::new("col_a")])), ..Default::default() - }).empty_span(); + }) + .empty_span(); assert_eq!(expected, select.projection[0]); let select = snowflake_and_generic() @@ -436,7 +458,8 @@ fn test_select_wildcard_with_exclude() { opt_exclude: Some(ExcludeSelectItem::Single(Ident::new("department_id"))), ..Default::default() }, - ).empty_span(); + ) + .empty_span(); assert_eq!(expected, select.projection[0]); let select = snowflake_and_generic() @@ -447,7 +470,8 @@ fn test_select_wildcard_with_exclude() { Ident::new("employee_id"), ])), ..Default::default() - }).empty_span(); + }) + .empty_span(); assert_eq!(expected, select.projection[0]); } @@ -461,7 +485,8 @@ fn test_select_wildcard_with_rename() { alias: Ident::new("col_b"), })), ..Default::default() - }).empty_span(); + }) + .empty_span(); assert_eq!(expected, select.projection[0]); let select = snowflake_and_generic().verified_only_select( @@ -482,7 +507,8 @@ fn test_select_wildcard_with_rename() { ])), ..Default::default() }, - ).empty_span(); + ) + .empty_span(); assert_eq!(expected, select.projection[0]); } @@ -497,7 +523,8 @@ fn test_select_wildcard_with_exclude_and_rename() { alias: Ident::new("col_b"), })), ..Default::default() - }).empty_span(); + }) + .empty_span(); assert_eq!(expected, select.projection[0]); // rename cannot precede exclude @@ -1053,3 +1080,27 @@ fn test_snowflake_stage_object_names() { } } } + +#[test] +fn test_snowflake_trim() { + let real_sql = r#"SELECT customer_id, TRIM(sub_items.value:item_price_id, '"', "a") AS item_price_id FROM SPX_DATAMART.MODELS_staging.stg_chargebee_subscriptions"#; + assert_eq!(snowflake().verified_stmt(real_sql).to_string(), real_sql); + + let sql_only_select = "SELECT TRIM('xyz', 'a')"; + let select = snowflake().verified_only_select(sql_only_select); + assert_eq!( + &Expr::Trim { + expr: Box::new(Expr::Value(Value::SingleQuotedString("xyz".to_owned()))), + trim_where: None, + trim_what: None, + trim_characters: Some(vec![Expr::Value(Value::SingleQuotedString("a".to_owned()))]), + }, + expr_from_projection(only(&select.projection)) + ); + + let error_sql = "SELECT TRIM('xyz' 'a')"; + assert_eq!( + ParserError::ParserError("Expected ), found: 'a'\nNear `SELECT TRIM('xyz'`".to_owned()), + snowflake().parse_sql_statements(error_sql).unwrap_err() + ); +}