diff --git a/src/ast/ddl.rs b/src/ast/ddl.rs index 847ee71a3..2a822393b 100644 --- a/src/ast/ddl.rs +++ b/src/ast/ddl.rs @@ -254,6 +254,7 @@ impl fmt::Display for ColumnOption { } } +#[allow(clippy::needless_lifetimes)] fn display_constraint_name<'a>(name: &'a Option) -> impl fmt::Display + 'a { struct ConstraintName<'a>(&'a Option); impl<'a> fmt::Display for ConstraintName<'a> { diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 27dfe0782..c562c6100 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -164,7 +164,7 @@ pub enum Expr { /// `IS [NOT] { NULL | FALSE | TRUE | UNKNOWN }` expression Is { expr: Box, - check: &'static str, + check: IsCheck, negated: bool, }, InList { @@ -466,9 +466,31 @@ impl fmt::Display for Expr { } } +/// An enum for Is Expr +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum IsCheck { + NULL, + FALSE, + TRUE, + UNKNOWN +} + +impl fmt::Display for IsCheck{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + IsCheck::NULL => write!(f, "NULL"), + IsCheck::FALSE => write!(f, "FALSE"), + IsCheck::TRUE => write!(f, "TRUE"), + IsCheck::UNKNOWN => write!(f, "UNKNOWN"), + } + } +} + /// A window specification, either inline or named /// https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax#window_clause #[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum WindowSpec { Inline(InlineWindowSpec), Named(Ident), @@ -500,15 +522,14 @@ impl fmt::Display for InlineWindowSpec { write!(f, "ORDER BY {}", display_comma_separated(&self.order_by))?; } if let Some(window_frame) = &self.window_frame { + f.write_str(delim)?; if let Some(end_bound) = &window_frame.end_bound { - f.write_str(delim)?; write!( f, "{} BETWEEN {} AND {}", window_frame.units, window_frame.start_bound, end_bound )?; } else { - f.write_str(delim)?; write!(f, "{} {}", window_frame.units, window_frame.start_bound)?; } } @@ -1069,7 +1090,6 @@ pub struct Function { // https://cloud.google.com/bigquery/docs/reference/standard-sql/functions-and-operators#array_agg /// Some(true) for IGNORE NULLS, Some(false) for RESPECT NULLS pub ignore_respect_nulls: Option, - /// Some(true) for ASC, Some(false) for DESC pub order_by: Vec, pub limit: Option>, // for snowflake - this goes outside of the parens @@ -1334,6 +1354,7 @@ impl fmt::Display for TransactionIsolationLevel { #[derive(Debug, Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[allow(clippy::large_enum_variant)] pub enum ShowStatementFilter { Like(String), Where(Expr), diff --git a/src/ast/query.rs b/src/ast/query.rs index a6c34bfae..f587419b0 100644 --- a/src/ast/query.rs +++ b/src/ast/query.rs @@ -468,6 +468,8 @@ impl fmt::Display for Join { _ => "", } } + + #[allow(clippy::needless_lifetimes)] fn suffix<'a>(constraint: &'a JoinConstraint) -> impl fmt::Display + 'a { struct Suffix<'a>(&'a JoinConstraint); impl<'a> fmt::Display for Suffix<'a> { diff --git a/src/ast/value.rs b/src/ast/value.rs index 510b1ec0c..8a1017d95 100644 --- a/src/ast/value.rs +++ b/src/ast/value.rs @@ -146,7 +146,7 @@ pub enum DateTimeField { Second, Epoch, // https://cloud.google.com/bigquery/docs/reference/standard-sql/functions-and-operators#extract_2 - Other(&'static str), + Other(String), Literal(String), } @@ -169,7 +169,7 @@ impl fmt::Display for DateTimeField { DateTimeField::Minute => "MINUTE", DateTimeField::Second => "SECOND", DateTimeField::Epoch => "EPOCH", - DateTimeField::Other(s) => return write!(f, "{}", s), + DateTimeField::Other(ref s) => return write!(f, "{}", s), DateTimeField::Literal(ref s) => return write!(f, "'{}'", s), }) } diff --git a/src/dialect/ansi.rs b/src/dialect/ansi.rs index ca01fb751..1015ca2d3 100644 --- a/src/dialect/ansi.rs +++ b/src/dialect/ansi.rs @@ -17,13 +17,13 @@ pub struct AnsiDialect {} impl Dialect for AnsiDialect { fn is_identifier_start(&self, ch: char) -> bool { - (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') + ('a'..='z').contains(&ch) || ('A'..='Z').contains(&ch) } fn is_identifier_part(&self, ch: char) -> bool { - (ch >= 'a' && ch <= 'z') - || (ch >= 'A' && ch <= 'Z') - || (ch >= '0' && ch <= '9') + ('a'..='z').contains(&ch) + || ('A'..='Z').contains(&ch) + || ('0'..='9').contains(&ch) || ch == '_' } } diff --git a/src/dialect/bigquery.rs b/src/dialect/bigquery.rs index c80e3459f..f4be12490 100644 --- a/src/dialect/bigquery.rs +++ b/src/dialect/bigquery.rs @@ -18,13 +18,13 @@ pub struct BigQueryDialect; impl Dialect for BigQueryDialect { // see https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#identifiers fn is_identifier_start(&self, ch: char) -> bool { - (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '_' + ('a'..='z').contains(&ch) || ('A'..='Z').contains(&ch) || ch == '_' } fn is_identifier_part(&self, ch: char) -> bool { - (ch >= 'a' && ch <= 'z') - || (ch >= 'A' && ch <= 'Z') - || (ch >= '0' && ch <= '9') + ('a'..='z').contains(&ch) + || ('A'..='Z').contains(&ch) + || ('0'..='9').contains(&ch) || ch == '_' } } diff --git a/src/dialect/generic.rs b/src/dialect/generic.rs index 104d3a9a3..d779d2576 100644 --- a/src/dialect/generic.rs +++ b/src/dialect/generic.rs @@ -17,13 +17,13 @@ pub struct GenericDialect; impl Dialect for GenericDialect { fn is_identifier_start(&self, ch: char) -> bool { - (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '_' || ch == '#' || ch == '@' + ('a'..='z').contains(&ch) || ('A'..='Z').contains(&ch) || ch == '_' || ch == '#' || ch == '@' } fn is_identifier_part(&self, ch: char) -> bool { - (ch >= 'a' && ch <= 'z') - || (ch >= 'A' && ch <= 'Z') - || (ch >= '0' && ch <= '9') + ('a'..='z').contains(&ch) + || ('A'..='Z').contains(&ch) + || ('0'..='9').contains(&ch) || ch == '@' || ch == '$' || ch == '#' diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs index bc5eb0f48..34532ab49 100644 --- a/src/dialect/mod.rs +++ b/src/dialect/mod.rs @@ -85,20 +85,11 @@ mod tests { dialect: ansi_dialect, }; - assert_eq!( - dialect_of!(generic_holder is GenericDialect | AnsiDialect), - true - ); - assert_eq!(dialect_of!(generic_holder is AnsiDialect), false); + assert!(dialect_of!(generic_holder is GenericDialect | AnsiDialect)); + assert!(!dialect_of!(generic_holder is AnsiDialect)); - assert_eq!(dialect_of!(ansi_holder is AnsiDialect), true); - assert_eq!( - dialect_of!(ansi_holder is GenericDialect | AnsiDialect), - true - ); - assert_eq!( - dialect_of!(ansi_holder is GenericDialect | MsSqlDialect), - false - ); + assert!(dialect_of!(ansi_holder is AnsiDialect)); + assert!(dialect_of!(ansi_holder is GenericDialect | AnsiDialect)); + assert!(!dialect_of!(ansi_holder is GenericDialect | MsSqlDialect)); } } diff --git a/src/dialect/mssql.rs b/src/dialect/mssql.rs index c613a1502..cb5c6daa8 100644 --- a/src/dialect/mssql.rs +++ b/src/dialect/mssql.rs @@ -23,13 +23,13 @@ impl Dialect for MsSqlDialect { fn is_identifier_start(&self, ch: char) -> bool { // See https://docs.microsoft.com/en-us/sql/relational-databases/databases/database-identifiers?view=sql-server-2017#rules-for-regular-identifiers // We don't support non-latin "letters" currently. - (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '_' || ch == '#' || ch == '@' + ('a'..='z').contains(&ch) || ('A'..='Z').contains(&ch) || ch == '_' || ch == '#' || ch == '@' } fn is_identifier_part(&self, ch: char) -> bool { - (ch >= 'a' && ch <= 'z') - || (ch >= 'A' && ch <= 'Z') - || (ch >= '0' && ch <= '9') + ('a'..='z').contains(&ch) + || ('A'..='Z').contains(&ch) + || ('0'..='9').contains(&ch) || ch == '@' || ch == '$' || ch == '#' diff --git a/src/dialect/mysql.rs b/src/dialect/mysql.rs index a4aaafe6b..6581195b8 100644 --- a/src/dialect/mysql.rs +++ b/src/dialect/mysql.rs @@ -20,15 +20,15 @@ impl Dialect for MySqlDialect { // See https://dev.mysql.com/doc/refman/8.0/en/identifiers.html. // We don't yet support identifiers beginning with numbers, as that // makes it hard to distinguish numeric literals. - (ch >= 'a' && ch <= 'z') - || (ch >= 'A' && ch <= 'Z') + ('a'..='z').contains(&ch) + || ('A'..='Z').contains(&ch) || ch == '_' || ch == '$' - || (ch >= '\u{0080}' && ch <= '\u{ffff}') + || ('\u{0080}'..='\u{ffff}').contains(&ch) } fn is_identifier_part(&self, ch: char) -> bool { - self.is_identifier_start(ch) || (ch >= '0' && ch <= '9') + self.is_identifier_start(ch) || ('0'..='9').contains(&ch) } fn is_delimited_identifier_start(&self, ch: char) -> bool { diff --git a/src/dialect/postgresql.rs b/src/dialect/postgresql.rs index 1c11d8a37..0c2eb99f0 100644 --- a/src/dialect/postgresql.rs +++ b/src/dialect/postgresql.rs @@ -20,13 +20,13 @@ impl Dialect for PostgreSqlDialect { // See https://www.postgresql.org/docs/11/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS // We don't yet support identifiers beginning with "letters with // diacritical marks and non-Latin letters" - (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '_' + ('a'..='z').contains(&ch) || ('A'..='Z').contains(&ch) || ch == '_' } fn is_identifier_part(&self, ch: char) -> bool { - (ch >= 'a' && ch <= 'z') - || (ch >= 'A' && ch <= 'Z') - || (ch >= '0' && ch <= '9') + ('a'..='z').contains(&ch) + || ('A'..='Z').contains(&ch) + || ('0'..='9').contains(&ch) || ch == '$' || ch == '_' } diff --git a/src/dialect/snowflake.rs b/src/dialect/snowflake.rs index 017f42dd5..9453b493a 100644 --- a/src/dialect/snowflake.rs +++ b/src/dialect/snowflake.rs @@ -21,13 +21,13 @@ impl Dialect for SnowflakeDialect { // querying stages: // https://docs.snowflake.com/en/user-guide/querying-stage.html#query-syntax-and-parameters fn is_identifier_start(&self, ch: char) -> bool { - (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '_' || ch == '$' + ('a'..='z').contains(&ch) || ('A'..='Z').contains(&ch) || ch == '_' || ch == '$' } fn is_identifier_part(&self, ch: char) -> bool { - (ch >= 'a' && ch <= 'z') - || (ch >= 'A' && ch <= 'Z') - || (ch >= '0' && ch <= '9') + ('a'..='z').contains(&ch) + || ('A'..='Z').contains(&ch) + || ('0'..='9').contains(&ch) || ch == '$' || ch == '_' } diff --git a/src/dialect/sqlite.rs b/src/dialect/sqlite.rs index 16ec66ac2..4ce2f834b 100644 --- a/src/dialect/sqlite.rs +++ b/src/dialect/sqlite.rs @@ -25,14 +25,14 @@ impl Dialect for SQLiteDialect { fn is_identifier_start(&self, ch: char) -> bool { // See https://www.sqlite.org/draft/tokenreq.html - (ch >= 'a' && ch <= 'z') - || (ch >= 'A' && ch <= 'Z') + ('a'..='z').contains(&ch) + || ('A'..='Z').contains(&ch) || ch == '_' || ch == '$' - || (ch >= '\u{007f}' && ch <= '\u{ffff}') + || ('\u{007f}'..='\u{ffff}').contains(&ch) } fn is_identifier_part(&self, ch: char) -> bool { - self.is_identifier_start(ch) || (ch >= '0' && ch <= '9') + self.is_identifier_start(ch) || ('0'..='9').contains(&ch) } } diff --git a/src/parser.rs b/src/parser.rs index 1c5baee34..3c8d6d21a 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -27,6 +27,9 @@ pub enum ParserError { ParserError(String, String), } +pub type WildcardExcept = Vec; +pub type WildcardReplace = Vec<(Expr, Ident)>; + // Use `Parser::expected` instead, if possible macro_rules! parser_err { ($parser:expr, $MSG:expr) => { @@ -110,7 +113,7 @@ impl<'a> Parser<'a> { /// Parse a SQL statement and produce an Abstract Syntax Tree (AST) pub fn parse_sql(dialect: &dyn Dialect, sql: &str) -> Result, ParserError> { - let mut tokenizer = Tokenizer::new(dialect, &sql); + let mut tokenizer = Tokenizer::new(dialect, sql); let tokens = tokenizer.tokenize()?; let mut parser = Parser::new(tokens, dialect); let mut stmts = Vec::new(); @@ -275,7 +278,7 @@ impl<'a> Parser<'a> { _ => self.parse_ident(w.to_ident()), }, // End of Token::Word Token::BacktickQuotedString(w) => self.parse_ident(Ident { - value: w.clone(), + value: w, quote_style: Some('`'), }), Token::DoubleQuotedString(s) => Ok(Expr::Value(Value::DoubleQuotedString(s))), @@ -429,7 +432,7 @@ impl<'a> Parser<'a> { let ident = self.parse_identifier()?; self.expect_keyword(Keyword::AS)?; let spec = self.parse_window_spec()?; - return Ok((ident, spec)); + Ok((ident, spec)) } pub fn parse_window_spec(&mut self) -> Result { @@ -708,10 +711,10 @@ impl<'a> Parser<'a> { | Keyword::WEEKOFYEAR | Keyword::WOY | Keyword::WY => Ok(DateTimeField::Week(None)), - Keyword::ISOWEEK => Ok(DateTimeField::Other("ISOWEEK")), - Keyword::ISOYEAR => Ok(DateTimeField::Other("ISOYEAR")), - Keyword::MICROSECOND => Ok(DateTimeField::Other("MICROSECOND")), - Keyword::MILLISECOND => Ok(DateTimeField::Other("MILLISECOND")), + Keyword::ISOWEEK => Ok(DateTimeField::Other("ISOWEEK".to_owned())), + Keyword::ISOYEAR => Ok(DateTimeField::Other("ISOYEAR".to_owned())), + Keyword::MICROSECOND => Ok(DateTimeField::Other("MICROSECOND".to_owned())), + Keyword::MILLISECOND => Ok(DateTimeField::Other("MILLISECOND".to_owned())), Keyword::WEEKISO | Keyword::WEEK_ISO | Keyword::WEEKOFYEARISO @@ -729,15 +732,15 @@ impl<'a> Parser<'a> { Keyword::DAYOFYEAR | Keyword::YEARDAY | Keyword::DOY | Keyword::DY => { Ok(DateTimeField::DayOfYear) } - Keyword::DATE => Ok(DateTimeField::Other("DATE")), - Keyword::DATETIME => Ok(DateTimeField::Other("DATETIME")), + Keyword::DATE => Ok(DateTimeField::Other("DATE".to_owned())), + Keyword::DATETIME => Ok(DateTimeField::Other("DATETIME".to_owned())), Keyword::HOUR => Ok(DateTimeField::Hour), Keyword::MINUTE => Ok(DateTimeField::Minute), Keyword::SECOND => Ok(DateTimeField::Second), Keyword::EPOCH => Ok(DateTimeField::Epoch), _ => self.expected("date/time field", Token::Word(w))?, }, - Token::SingleQuotedString(w) => Ok(DateTimeField::Literal(w.clone())), + Token::SingleQuotedString(w) => Ok(DateTimeField::Literal(w)), unexpected => self.expected("date/time field", unexpected), } } @@ -938,10 +941,10 @@ impl<'a> Parser<'a> { Keyword::IS => { let negated = self.parse_keyword(Keyword::NOT); let check = match self.next_token() { - Token::Word(w) if w.keyword == Keyword::NULL => "NULL", - Token::Word(w) if w.keyword == Keyword::FALSE => "FALSE", - Token::Word(w) if w.keyword == Keyword::TRUE => "TRUE", - Token::Word(w) if w.keyword == Keyword::UNKNOWN => "UNKNOWN", + Token::Word(w) if w.keyword == Keyword::NULL => IsCheck::NULL, + Token::Word(w) if w.keyword == Keyword::FALSE => IsCheck::FALSE, + Token::Word(w) if w.keyword == Keyword::TRUE => IsCheck::TRUE, + Token::Word(w) if w.keyword == Keyword::UNKNOWN => IsCheck::UNKNOWN, unexpected => { return self.expected("NULL, FALSE, TRUE, or UNKNOWN", unexpected) } @@ -2593,14 +2596,11 @@ impl<'a> Parser<'a> { // followed by some joins or (B) another level of nesting. let mut table_and_joins = self.parse_table_and_joins()?; - if !table_and_joins.joins.is_empty() { - self.expect_token(&Token::RParen)?; - Ok(TableFactor::NestedJoin(Box::new(table_and_joins))) // (A) - } else if let TableFactor::NestedJoin(_) = &table_and_joins.relation { + if !table_and_joins.joins.is_empty() || matches!(&table_and_joins.relation, TableFactor::NestedJoin(_)) { // (B): `table_and_joins` (what we found inside the parentheses) // is a nested join `(foo JOIN bar)`, not followed by other joins. self.expect_token(&Token::RParen)?; - Ok(TableFactor::NestedJoin(Box::new(table_and_joins))) + Ok(TableFactor::NestedJoin(Box::new(table_and_joins))) // (A) } else if dialect_of!(self is SnowflakeDialect | GenericDialect) { // Dialect-specific behavior: Snowflake diverges from the // standard and from most of the other implementations by @@ -2840,13 +2840,13 @@ impl<'a> Parser<'a> { args_res: FunctionArgsRes, ) -> Result, ParserError> { if args_res.ignore_respect_nulls.is_some() { - return parser_err!(self, format!("Unexpected IGNORE|RESPECT NULLS clause")); - } else if args_res.order_by.len() > 0 { - return parser_err!(self, format!("Unexpected ORDER BY clause")); + return parser_err!(self, "Unexpected IGNORE|RESPECT NULLS clause".to_string()); + } else if !args_res.order_by.is_empty() { + return parser_err!(self, "Unexpected ORDER BY clause".to_string()); } else if args_res.limit.is_some() { - return parser_err!(self, format!("Unexpected LIMIT clause")); + return parser_err!(self, "Unexpected LIMIT clause".to_string()); } - return Ok(args_res.args); + Ok(args_res.args) } pub fn parse_optional_args(&mut self) -> Result { @@ -2899,7 +2899,7 @@ impl<'a> Parser<'a> { pub fn parse_wildcard_modifiers( &mut self, - ) -> Result<(Vec, Vec<(Expr, Ident)>), ParserError> { + ) -> Result<(WildcardExcept, WildcardReplace), ParserError> { let except = if self.parse_keyword(Keyword::EXCEPT) { self.expect_token(&Token::LParen)?; let aliases = self.parse_comma_separated(Parser::parse_identifier)?; diff --git a/src/test_utils.rs b/src/test_utils.rs index 2fcacffa9..03d1c7b40 100644 --- a/src/test_utils.rs +++ b/src/test_utils.rs @@ -64,7 +64,7 @@ impl TestedDialects { } pub fn parse_sql_statements(&self, sql: &str) -> Result, ParserError> { - self.one_of_identical_results(|dialect| Parser::parse_sql(dialect, &sql)) + self.one_of_identical_results(|dialect| Parser::parse_sql(dialect, sql)) // To fail the `ensure_multiple_dialects_are_tested` test: // Parser::parse_sql(&**self.dialects.first().unwrap(), sql) } @@ -75,11 +75,11 @@ impl TestedDialects { /// tree as parsing `canonical`, and that serializing it back to string /// results in the `canonical` representation. pub fn one_statement_parses_to(&self, sql: &str, canonical: &str) -> Statement { - let mut statements = self.parse_sql_statements(&sql).unwrap(); + let mut statements = self.parse_sql_statements(sql).unwrap(); assert_eq!(statements.len(), 1); if !canonical.is_empty() && sql != canonical { - assert_eq!(self.parse_sql_statements(&canonical).unwrap(), statements); + assert_eq!(self.parse_sql_statements(canonical).unwrap(), statements); } let only_statement = statements.pop().unwrap(); diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index b189bd0e8..a4f05d760 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -92,7 +92,7 @@ fn parse_insert_invalid() { let sql = "INSERT public.customer (id, name, active) VALUES (1, 2, 3)"; let res = parse_sql_statements(sql); assert_eq!( - ParserError::ParserError("Expected INTO, found: public".to_string()), + ParserError::ParserError("".to_string(), "Expected INTO, found: public".to_string()), res.unwrap_err() ); } @@ -135,14 +135,14 @@ fn parse_update() { let sql = "UPDATE t WHERE 1"; let res = parse_sql_statements(sql); assert_eq!( - ParserError::ParserError("Expected SET, found: WHERE".to_string()), + ParserError::ParserError("".to_string(), "Expected SET, found: WHERE".to_string()), res.unwrap_err() ); let sql = "UPDATE t SET a = 1 extrabadstuff"; let res = parse_sql_statements(sql); assert_eq!( - ParserError::ParserError("Expected end of statement, found: extrabadstuff".to_string()), + ParserError::ParserError("".to_string(), "Expected end of statement, found: extrabadstuff".to_string()), res.unwrap_err() ); } @@ -212,7 +212,7 @@ fn parse_top_level() { fn parse_simple_select() { let sql = "SELECT id, fname, lname FROM customer WHERE id = 1 LIMIT 5"; let select = verified_only_select(sql); - assert_eq!(false, select.distinct); + assert!(!select.distinct); assert_eq!(3, select.projection.len()); let select = verified_query(sql); assert_eq!(Some(Expr::Value(number("5"))), select.limit); @@ -232,7 +232,7 @@ fn parse_limit_is_not_an_alias() { fn parse_select_distinct() { let sql = "SELECT DISTINCT name FROM customer"; let select = verified_only_select(sql); - assert_eq!(true, select.distinct); + assert!(select.distinct); assert_eq!( &SelectItem::UnnamedExpr(Expr::Identifier(Ident::new("name"))), only(&select.projection) @@ -248,7 +248,7 @@ fn parse_select_all() { fn parse_select_all_distinct() { let result = parse_sql_statements("SELECT ALL DISTINCT name FROM customer"); assert_eq!( - ParserError::ParserError("Cannot specify both ALL and DISTINCT".to_string()), + ParserError::ParserError("".to_string(), "Cannot specify both ALL and DISTINCT".to_string()), result.unwrap_err(), ); } @@ -257,22 +257,22 @@ fn parse_select_all_distinct() { fn parse_select_wildcard() { let sql = "SELECT * FROM foo"; let select = verified_only_select(sql); - assert_eq!(&SelectItem::Wildcard, only(&select.projection)); + assert_eq!(&SelectItem::Wildcard{prefix: None, except: vec![], replace: vec![]}, only(&select.projection)); let sql = "SELECT foo.* FROM foo"; let select = verified_only_select(sql); assert_eq!( - &SelectItem::QualifiedWildcard(ObjectName(vec![Ident::new("foo")])), + &SelectItem::Wildcard{prefix: Some(ObjectName(vec![Ident::new("foo")])), except: vec![], replace: vec![]}, only(&select.projection) ); let sql = "SELECT myschema.mytable.* FROM myschema.mytable"; let select = verified_only_select(sql); assert_eq!( - &SelectItem::QualifiedWildcard(ObjectName(vec![ + &SelectItem::Wildcard{ prefix: Some(ObjectName(vec![ Ident::new("myschema"), Ident::new("mytable"), - ])), + ])), except: vec![], replace: vec![] }, only(&select.projection) ); } @@ -303,20 +303,20 @@ fn parse_column_aliases() { } // alias without AS is parsed correctly: - one_statement_parses_to("SELECT a.col + 1 newname FROM foo AS a", &sql); + one_statement_parses_to("SELECT a.col + 1 newname FROM foo AS a", sql); } #[test] fn test_eof_after_as() { let res = parse_sql_statements("SELECT foo AS"); assert_eq!( - ParserError::ParserError("Expected an identifier after AS, found: EOF".to_string()), + ParserError::ParserError("".to_string(), "Expected an identifier after AS, found: EOF".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".to_string()), + ParserError::ParserError("".to_string(), "Expected an identifier after AS, found: EOF".to_string()), res.unwrap_err() ); } @@ -331,6 +331,11 @@ fn parse_select_count_wildcard() { args: vec![FunctionArg::Unnamed(Expr::Wildcard)], over: None, distinct: false, + ignore_respect_nulls: None, + limit: None, + order_by: vec![], + outer_ignore_respect_nulls: None, + within_group: vec![], }), expr_from_projection(only(&select.projection)) ); @@ -349,6 +354,11 @@ fn parse_select_count_distinct() { })], over: None, distinct: true, + ignore_respect_nulls: None, + limit: None, + order_by: vec![], + outer_ignore_respect_nulls: None, + within_group: vec![], }), expr_from_projection(only(&select.projection)) ); @@ -361,7 +371,7 @@ fn parse_select_count_distinct() { let sql = "SELECT COUNT(ALL DISTINCT + x) FROM customer"; let res = parse_sql_statements(sql); assert_eq!( - ParserError::ParserError("Cannot specify both ALL and DISTINCT".to_string()), + ParserError::ParserError("".to_string(), "Cannot specify both ALL and DISTINCT".to_string()), res.unwrap_err() ); } @@ -377,7 +387,7 @@ 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".to_string()), + ParserError::ParserError("".to_string(), "Expected end of statement, found: NOT".to_string()), res.unwrap_err(), ); } @@ -524,7 +534,7 @@ fn parse_is_null() { use self::Expr::*; let sql = "a IS NULL"; assert_eq!( - IsNull(Box::new(Identifier(Ident::new("a")))), + Expr::Is{expr: Box::new(Identifier(Ident::new("a"))), check: IsCheck::NULL, negated: false}, verified_expr(sql) ); } @@ -534,7 +544,7 @@ fn parse_is_not_null() { use self::Expr::*; let sql = "a IS NOT NULL"; assert_eq!( - IsNotNull(Box::new(Identifier(Ident::new("a")))), + Expr::Is{expr: (Box::new(Identifier(Ident::new("a")))), check: IsCheck::NULL, negated: true}, verified_expr(sql) ); } @@ -578,7 +588,7 @@ fn parse_not_precedence() { op: UnaryOperator::Not, expr: Box::new(Expr::BinaryOp { left: Box::new(Expr::Value(Value::SingleQuotedString("a".into()))), - op: BinaryOperator::NotLike, + op: BinaryOperator::NotRlike, right: Box::new(Expr::Value(Value::SingleQuotedString("b".into()))), }), }, @@ -611,9 +621,9 @@ fn parse_like() { Expr::BinaryOp { left: Box::new(Expr::Identifier(Ident::new("name"))), op: if negated { - BinaryOperator::NotLike + BinaryOperator::NotRlike } else { - BinaryOperator::Like + BinaryOperator::Rlike }, right: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), }, @@ -628,15 +638,15 @@ fn parse_like() { ); let select = verified_only_select(sql); assert_eq!( - Expr::IsNull(Box::new(Expr::BinaryOp { + Expr::Is{expr: Box::new(Expr::BinaryOp { left: Box::new(Expr::Identifier(Ident::new("name"))), op: if negated { - BinaryOperator::NotLike + BinaryOperator::NotRlike } else { - BinaryOperator::Like + BinaryOperator::Rlike }, right: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), - })), + }), check: IsCheck::NULL, negated: false,}, select.selection.unwrap() ); } @@ -746,7 +756,7 @@ fn parse_between_with_expr() { let sql = "SELECT * FROM t WHERE 1 BETWEEN 1 + 2 AND 3 + 4 IS NULL"; let select = verified_only_select(sql); assert_eq!( - Expr::IsNull(Box::new(Expr::Between { + Expr::Is{expr: Box::new(Expr::Between { expr: Box::new(Expr::Value(number("1"))), low: Box::new(Expr::BinaryOp { left: Box::new(Expr::Value(number("1"))), @@ -759,7 +769,7 @@ fn parse_between_with_expr() { right: Box::new(Expr::Value(number("4"))), }), negated: false, - })), + }), check: IsCheck::NULL, negated: false}, select.selection.unwrap() ); @@ -888,7 +898,12 @@ fn parse_select_having() { name: ObjectName(vec![Ident::new("COUNT")]), args: vec![FunctionArg::Unnamed(Expr::Wildcard)], over: None, - distinct: false + distinct: false, + ignore_respect_nulls: None, + limit: None, + order_by: vec![], + outer_ignore_respect_nulls: None, + within_group: vec![], })), op: BinaryOperator::Gt, right: Box::new(Expr::Value(number("1"))) @@ -916,7 +931,8 @@ fn parse_cast() { assert_eq!( &Expr::Cast { expr: Box::new(Expr::Identifier(Ident::new("id"))), - data_type: DataType::BigInt + data_type: DataType::BigInt, + try_cast: false, }, expr_from_projection(only(&select.projection)) ); @@ -960,7 +976,7 @@ fn parse_extract() { let res = parse_sql_statements("SELECT EXTRACT(MILLISECOND FROM d)"); assert_eq!( - ParserError::ParserError("Expected date/time field, found: MILLISECOND".to_string()), + ParserError::ParserError("".to_string(), "Expected date/time field, found: MILLISECOND".to_string()), res.unwrap_err() ); } @@ -1190,12 +1206,9 @@ fn parse_assert_message() { match ast { Statement::Assert { condition: _condition, - message: Some(message), + message: Some(Expr::Value(Value::SingleQuotedString(s))), } => { - match message { - Expr::Value(Value::SingleQuotedString(s)) => assert_eq!(s, "No rows in my_table"), - _ => unreachable!(), - }; + assert_eq!(s, "No rows in my_table") } _ => unreachable!(), } @@ -1566,8 +1579,8 @@ fn parse_alter_table_drop_column() { } => { assert_eq!("tab", name.to_string()); assert_eq!("is_active", column_name.to_string()); - assert_eq!(true, if_exists); - assert_eq!(true, cascade); + assert!(if_exists); + assert!(cascade); } _ => unreachable!(), } @@ -1578,13 +1591,13 @@ fn parse_alter_table_drop_column() { fn parse_bad_constraint() { let res = parse_sql_statements("ALTER TABLE tab ADD"); assert_eq!( - ParserError::ParserError("Expected identifier, found: EOF".to_string()), + ParserError::ParserError("".to_string(), "Expected identifier, found: EOF".to_string()), res.unwrap_err() ); let res = parse_sql_statements("CREATE TABLE tab (foo int,"); assert_eq!( - ParserError::ParserError( + ParserError::ParserError("".to_string(), "Expected column name or constraint definition, found: EOF".to_string() ), res.unwrap_err() @@ -1601,6 +1614,11 @@ fn parse_scalar_function_in_projection() { args: vec![FunctionArg::Unnamed(Expr::Identifier(Ident::new("id")))], over: None, distinct: false, + ignore_respect_nulls: None, + limit: None, + order_by: vec![], + outer_ignore_respect_nulls: None, + within_group: vec![], }), expr_from_projection(only(&select.projection)) ); @@ -1626,6 +1644,11 @@ fn parse_named_argument_function() { ], over: None, distinct: false, + ignore_respect_nulls: None, + limit: None, + order_by: vec![], + outer_ignore_respect_nulls: None, + within_group: vec![], }), expr_from_projection(only(&select.projection)) ); @@ -1649,7 +1672,7 @@ fn parse_window_functions() { &Expr::Function(Function { name: ObjectName(vec![Ident::new("row_number")]), args: vec![], - over: Some(WindowSpec { + over: Some(WindowSpec::Inline(InlineWindowSpec { partition_by: vec![], order_by: vec![OrderByExpr { expr: Expr::Identifier(Ident::new("dt")), @@ -1657,8 +1680,13 @@ fn parse_window_functions() { nulls_first: None, }], window_frame: None, - }), + })), distinct: false, + ignore_respect_nulls: None, + limit: None, + order_by: vec![], + outer_ignore_respect_nulls: None, + within_group: vec![], }), expr_from_projection(&select.projection[0]) ); @@ -1759,6 +1787,7 @@ fn parse_literal_interval() { leading_precision: None, last_field: Some(DateTimeField::Month), fractional_seconds_precision: None, + value_quoting: None, }), expr_from_projection(only(&select.projection)), ); @@ -1772,6 +1801,7 @@ fn parse_literal_interval() { leading_precision: Some(5), last_field: Some(DateTimeField::Second), fractional_seconds_precision: Some(5), + value_quoting: None, }), expr_from_projection(only(&select.projection)), ); @@ -1785,6 +1815,7 @@ fn parse_literal_interval() { leading_precision: Some(5), last_field: None, fractional_seconds_precision: Some(4), + value_quoting: None, }), expr_from_projection(only(&select.projection)), ); @@ -1798,6 +1829,7 @@ fn parse_literal_interval() { leading_precision: None, last_field: None, fractional_seconds_precision: None, + value_quoting: None, }), expr_from_projection(only(&select.projection)), ); @@ -1811,6 +1843,7 @@ fn parse_literal_interval() { leading_precision: Some(1), last_field: None, fractional_seconds_precision: None, + value_quoting: None, }), expr_from_projection(only(&select.projection)), ); @@ -1824,19 +1857,20 @@ fn parse_literal_interval() { leading_precision: None, last_field: None, fractional_seconds_precision: None, + value_quoting: None, }), expr_from_projection(only(&select.projection)), ); let result = parse_sql_statements("SELECT INTERVAL '1' SECOND TO SECOND"); assert_eq!( - ParserError::ParserError("Expected end of statement, found: SECOND".to_string()), + ParserError::ParserError("".to_string(), "Expected end of statement, found: SECOND".to_string()), result.unwrap_err(), ); let result = parse_sql_statements("SELECT INTERVAL '10' HOUR (1) TO HOUR (2)"); assert_eq!( - ParserError::ParserError("Expected end of statement, found: (".to_string()), + ParserError::ParserError("".to_string(), "Expected end of statement, found: (".to_string()), result.unwrap_err(), ); @@ -1886,6 +1920,11 @@ fn parse_table_function() { ))], over: None, distinct: false, + ignore_respect_nulls: None, + limit: None, + order_by: vec![], + outer_ignore_respect_nulls: None, + within_group: vec![], }); assert_eq!(expr, expected_expr); assert_eq!(alias, table_alias("a")) @@ -1895,13 +1934,13 @@ fn parse_table_function() { let res = parse_sql_statements("SELECT * FROM TABLE '1' AS a"); assert_eq!( - ParserError::ParserError("Expected (, found: \'1\'".to_string()), + ParserError::ParserError("".to_string(), "Expected (, found: \'1\'".to_string()), res.unwrap_err() ); let res = parse_sql_statements("SELECT * FROM TABLE (FUN(a) AS a"); assert_eq!( - ParserError::ParserError("Expected ), found: AS".to_string()), + ParserError::ParserError("".to_string(), "Expected ), found: AS".to_string()), res.unwrap_err() ); } @@ -1942,6 +1981,11 @@ fn parse_delimited_identifiers() { args: vec![], over: None, distinct: false, + ignore_respect_nulls: None, + limit: None, + order_by: vec![], + outer_ignore_respect_nulls: None, + within_group: vec![], }), expr_from_projection(&select.projection[1]), ); @@ -1965,17 +2009,17 @@ fn parse_parens() { let sql = "(a + b) - (c + d)"; assert_eq!( BinaryOp { - left: Box::new(Nested(Box::new(BinaryOp { + left: Box::new(Nested(vec![BinaryOp { left: Box::new(Identifier(Ident::new("a"))), op: Plus, right: Box::new(Identifier(Ident::new("b"))) - }))), + }])), op: Minus, - right: Box::new(Nested(Box::new(BinaryOp { + right: Box::new(Nested(vec![BinaryOp { left: Box::new(Identifier(Ident::new("c"))), op: Plus, right: Box::new(Identifier(Ident::new("d"))) - }))) + }])) }, verified_expr(sql) ); @@ -1985,13 +2029,13 @@ fn parse_parens() { fn parse_searched_case_expr() { let sql = "SELECT CASE WHEN bar IS NULL THEN 'null' WHEN bar = 0 THEN '=0' WHEN bar >= 0 THEN '>=0' ELSE '<0' END FROM foo"; use self::BinaryOperator::*; - use self::Expr::{BinaryOp, Case, Identifier, IsNull}; + use self::Expr::{BinaryOp, Case, Identifier}; let select = verified_only_select(sql); assert_eq!( &Case { operand: None, conditions: vec![ - IsNull(Box::new(Identifier(Ident::new("bar")))), + Expr::Is{expr: Box::new(Identifier(Ident::new("bar"))), check: IsCheck::NULL, negated: false}, BinaryOp { left: Box::new(Identifier(Ident::new("bar"))), op: Eq, @@ -2264,7 +2308,7 @@ fn parse_natural_join() { let sql = "SELECT * FROM t1 natural"; assert_eq!( - ParserError::ParserError("Expected a join type after NATURAL, found: EOF".to_string()), + ParserError::ParserError("".to_string(), "Expected a join type after NATURAL, found: EOF".to_string()), parse_sql_statements(sql).unwrap_err(), ); } @@ -2330,7 +2374,7 @@ fn parse_join_syntax_variants() { let res = parse_sql_statements("SELECT * FROM a OUTER JOIN b ON 1"); assert_eq!( - ParserError::ParserError("Expected APPLY, found: JOIN".to_string()), + ParserError::ParserError("".to_string(), "Expected APPLY, found: JOIN".to_string()), res.unwrap_err() ); } @@ -2519,7 +2563,7 @@ fn parse_multiple_statements() { let res = parse_sql_statements(&(sql1.to_owned() + ";" + sql2_kw + sql2_rest)); assert_eq!( vec![ - one_statement_parses_to(&sql1, ""), + one_statement_parses_to(sql1, ""), one_statement_parses_to(&(sql2_kw.to_owned() + sql2_rest), ""), ], res.unwrap() @@ -2529,7 +2573,7 @@ fn parse_multiple_statements() { // Check that forgetting the semicolon results in an error: let res = parse_sql_statements(&(sql1.to_owned() + " " + sql2_kw + sql2_rest)); assert_eq!( - ParserError::ParserError("Expected end of statement, found: ".to_string() + sql2_kw), + ParserError::ParserError("".to_string(), "Expected end of statement, found: ".to_string() + sql2_kw), res.unwrap_err() ); } @@ -2586,7 +2630,7 @@ fn parse_exists_subquery() { let res = parse_sql_statements("SELECT EXISTS ("); assert_eq!( - ParserError::ParserError( + ParserError::ParserError("".to_string(), "Expected SELECT, VALUES, or a subquery in the query body, found: EOF".to_string() ), res.unwrap_err(), @@ -2594,7 +2638,7 @@ fn parse_exists_subquery() { let res = parse_sql_statements("SELECT EXISTS (NULL)"); assert_eq!( - ParserError::ParserError( + ParserError::ParserError("".to_string(), "Expected SELECT, VALUES, or a subquery in the query body, found: NULL".to_string() ), res.unwrap_err(), @@ -2752,13 +2796,13 @@ fn parse_drop_table() { names, cascade, } => { - assert_eq!(false, if_exists); + assert!(!if_exists); assert_eq!(ObjectType::Table, object_type); assert_eq!( vec!["foo"], names.iter().map(ToString::to_string).collect::>() ); - assert_eq!(false, cascade); + assert!(!cascade); } _ => unreachable!(), } @@ -2771,26 +2815,26 @@ fn parse_drop_table() { names, cascade, } => { - assert_eq!(true, if_exists); + assert!(if_exists); assert_eq!(ObjectType::Table, object_type); assert_eq!( vec!["foo", "bar"], names.iter().map(ToString::to_string).collect::>() ); - assert_eq!(true, cascade); + assert!(cascade); } _ => unreachable!(), } let sql = "DROP TABLE"; assert_eq!( - ParserError::ParserError("Expected identifier, found: EOF".to_string()), + ParserError::ParserError("".to_string(), "Expected identifier, found: EOF".to_string()), parse_sql_statements(sql).unwrap_err(), ); let sql = "DROP TABLE IF EXISTS foo, bar CASCADE RESTRICT"; assert_eq!( - ParserError::ParserError("Cannot specify both CASCADE and RESTRICT in DROP".to_string()), + ParserError::ParserError("".to_string(), "Cannot specify both CASCADE and RESTRICT in DROP".to_string()), parse_sql_statements(sql).unwrap_err(), ); } @@ -2816,7 +2860,7 @@ fn parse_drop_view() { fn parse_invalid_subquery_without_parens() { let res = parse_sql_statements("SELECT SELECT 1 FROM bar WHERE 1=1 FROM baz"); assert_eq!( - ParserError::ParserError("Expected end of statement, found: 1".to_string()), + ParserError::ParserError("".to_string(), "Expected end of statement, found: 1".to_string()), res.unwrap_err() ); } @@ -3030,7 +3074,7 @@ fn lateral_derived() { let sql = "SELECT * FROM customer LEFT JOIN LATERAL generate_series(1, customer.id)"; let res = parse_sql_statements(sql); assert_eq!( - ParserError::ParserError( + ParserError::ParserError("".to_string(), "Expected subquery after LATERAL, found: generate_series".to_string() ), res.unwrap_err() @@ -3039,7 +3083,7 @@ fn lateral_derived() { let sql = "SELECT * FROM a LEFT JOIN LATERAL (b CROSS JOIN c)"; let res = parse_sql_statements(sql); assert_eq!( - ParserError::ParserError( + ParserError::ParserError("".to_string(), "Expected SELECT, VALUES, or a subquery in the query body, found: b".to_string() ), res.unwrap_err() @@ -3100,19 +3144,19 @@ fn parse_start_transaction() { let res = parse_sql_statements("START TRANSACTION ISOLATION LEVEL BAD"); assert_eq!( - ParserError::ParserError("Expected isolation level, found: BAD".to_string()), + ParserError::ParserError("".to_string(), "Expected isolation level, found: BAD".to_string()), res.unwrap_err() ); let res = parse_sql_statements("START TRANSACTION BAD"); assert_eq!( - ParserError::ParserError("Expected end of statement, found: BAD".to_string()), + ParserError::ParserError("".to_string(), "Expected end of statement, found: BAD".to_string()), res.unwrap_err() ); let res = parse_sql_statements("START TRANSACTION READ ONLY,"); assert_eq!( - ParserError::ParserError("Expected transaction mode, found: EOF".to_string()), + ParserError::ParserError("".to_string(), "Expected transaction mode, found: EOF".to_string()), res.unwrap_err() ); } @@ -3212,8 +3256,8 @@ fn parse_create_index() { assert_eq!("idx_name", name.to_string()); assert_eq!("test", table_name.to_string()); assert_eq!(indexed_columns, columns); - assert_eq!(true, unique); - assert_eq!(true, if_not_exists) + assert!(unique); + assert!(if_not_exists) } _ => unreachable!(), } diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index 20f186100..f0891bbd7 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -280,25 +280,25 @@ fn parse_create_table_if_not_exists() { fn parse_bad_if_not_exists() { let res = pg().parse_sql_statements("CREATE TABLE NOT EXISTS uk_cities ()"); assert_eq!( - ParserError::ParserError("Expected end of statement, found: EXISTS".to_string()), + ParserError::ParserError("".to_string(), "Expected end of statement, found: EXISTS".to_string()), res.unwrap_err() ); let res = pg().parse_sql_statements("CREATE TABLE IF EXISTS uk_cities ()"); assert_eq!( - ParserError::ParserError("Expected end of statement, found: EXISTS".to_string()), + ParserError::ParserError("".to_string(), "Expected end of statement, found: EXISTS".to_string()), res.unwrap_err() ); let res = pg().parse_sql_statements("CREATE TABLE IF uk_cities ()"); assert_eq!( - ParserError::ParserError("Expected end of statement, found: uk_cities".to_string()), + ParserError::ParserError("".to_string(), "Expected end of statement, found: uk_cities".to_string()), res.unwrap_err() ); let res = pg().parse_sql_statements("CREATE TABLE IF NOT uk_cities ()"); assert_eq!( - ParserError::ParserError("Expected end of statement, found: NOT".to_string()), + ParserError::ParserError("".to_string(), "Expected end of statement, found: NOT".to_string()), res.unwrap_err() ); } @@ -414,21 +414,21 @@ fn parse_set() { assert_eq!( pg_and_generic().parse_sql_statements("SET"), - Err(ParserError::ParserError( + Err(ParserError::ParserError("".to_string(), "Expected identifier, found: EOF".to_string() )), ); assert_eq!( pg_and_generic().parse_sql_statements("SET a b"), - Err(ParserError::ParserError( + Err(ParserError::ParserError("".to_string(), "Expected equals sign or TO, found: b".to_string() )), ); assert_eq!( pg_and_generic().parse_sql_statements("SET a ="), - Err(ParserError::ParserError( + Err(ParserError::ParserError("".to_string(), "Expected variable value, found: EOF".to_string() )), ); diff --git a/tests/sqlparser_snowflake.rs b/tests/sqlparser_snowflake.rs index 1b1aaec9b..19b1d8ae5 100644 --- a/tests/sqlparser_snowflake.rs +++ b/tests/sqlparser_snowflake.rs @@ -38,7 +38,7 @@ fn test_snowflake_create_table() { fn test_snowflake_single_line_tokenize() { let sql = "CREATE TABLE# this is a comment \ntable_1"; let dialect = SnowflakeDialect {}; - let mut tokenizer = Tokenizer::new(&dialect, &sql); + let mut tokenizer = Tokenizer::new(&dialect, sql); let tokens = tokenizer.tokenize().unwrap(); let expected = vec![ @@ -55,7 +55,7 @@ fn test_snowflake_single_line_tokenize() { assert_eq!(expected, tokens); let sql = "CREATE TABLE// this is a comment \ntable_1"; - let mut tokenizer = Tokenizer::new(&dialect, &sql); + let mut tokenizer = Tokenizer::new(&dialect, sql); let tokens = tokenizer.tokenize().unwrap(); let expected = vec![ @@ -133,13 +133,13 @@ fn test_single_table_in_parenthesis_with_alias() { let res = snowflake_and_generic().parse_sql_statements("SELECT * FROM (a NATURAL JOIN b) c"); assert_eq!( - ParserError::ParserError("Expected end of statement, found: c".to_string()), + ParserError::ParserError("".to_string(), "Expected end of statement, found: c".to_string()), res.unwrap_err() ); let res = snowflake().parse_sql_statements("SELECT * FROM (a b) c"); assert_eq!( - ParserError::ParserError("duplicate alias b".to_string()), + ParserError::ParserError("".to_string(), "duplicate alias b".to_string()), res.unwrap_err() ); }