diff --git a/src/keywords.rs b/src/keywords.rs index e59e49339..4b599f12a 100644 --- a/src/keywords.rs +++ b/src/keywords.rs @@ -20,7 +20,7 @@ //! As a matter of fact, most of these keywords are not used at all //! and could be removed. //! 3) a `RESERVED_FOR_TABLE_ALIAS` array with keywords reserved in a -//! "table alias" context. +//! "table alias" context. #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; diff --git a/src/parser/mod.rs b/src/parser/mod.rs index f8267a7cb..a513fc713 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -1038,7 +1038,7 @@ impl<'a> Parser<'a> { Keyword::CEIL => self.parse_ceil_floor_expr(true), Keyword::FLOOR => self.parse_ceil_floor_expr(false), Keyword::POSITION if self.peek_token().token == Token::LParen => { - self.parse_position_expr() + self.parse_position_expr(w.to_ident()) } Keyword::SUBSTRING => self.parse_substring_expr(), Keyword::OVERLAY => self.parse_overlay_expr(), @@ -1707,24 +1707,26 @@ impl<'a> Parser<'a> { } } - pub fn parse_position_expr(&mut self) -> Result { - // PARSE SELECT POSITION('@' in field) - self.expect_token(&Token::LParen)?; + pub fn parse_position_expr(&mut self, ident: Ident) -> Result { + let position_expr = self.maybe_parse(|p| { + // PARSE SELECT POSITION('@' in field) + p.expect_token(&Token::LParen)?; - // Parse the subexpr till the IN keyword - let expr = self.parse_subexpr(Self::BETWEEN_PREC)?; - if self.parse_keyword(Keyword::IN) { - let from = self.parse_expr()?; - self.expect_token(&Token::RParen)?; + // Parse the subexpr till the IN keyword + let expr = p.parse_subexpr(Self::BETWEEN_PREC)?; + p.expect_keyword(Keyword::IN)?; + let from = p.parse_expr()?; + p.expect_token(&Token::RParen)?; Ok(Expr::Position { expr: Box::new(expr), r#in: Box::new(from), }) - } else { - parser_err!( - "Position function must include IN keyword".to_string(), - self.peek_token().location - ) + }); + match position_expr { + Some(expr) => Ok(expr), + // Snowflake supports `position` as an ordinary function call + // without the special `IN` syntax. + None => self.parse_function(ObjectName(vec![ident])), } } diff --git a/src/test_utils.rs b/src/test_utils.rs index 1f5300be1..5ed6339bd 100644 --- a/src/test_utils.rs +++ b/src/test_utils.rs @@ -111,10 +111,10 @@ impl TestedDialects { /// that: /// /// 1. parsing `sql` results in the same [`Statement`] as parsing - /// `canonical`. + /// `canonical`. /// /// 2. re-serializing the result of parsing `sql` produces the same - /// `canonical` sql string + /// `canonical` sql string pub fn one_statement_parses_to(&self, sql: &str, canonical: &str) -> Statement { let mut statements = self.parse_sql_statements(sql).expect(sql); assert_eq!(statements.len(), 1); @@ -180,10 +180,10 @@ impl TestedDialects { /// Ensures that `sql` parses as a single [`Select`], and that additionally: /// /// 1. parsing `sql` results in the same [`Statement`] as parsing - /// `canonical`. + /// `canonical`. /// /// 2. re-serializing the result of parsing `sql` produces the same - /// `canonical` sql string + /// `canonical` sql string pub fn verified_only_select_with_canonical(&self, query: &str, canonical: &str) -> Select { let q = match self.one_statement_parses_to(query, canonical) { Statement::Query(query) => *query, diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index dd3ed0515..b5dc91b63 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -4151,7 +4151,7 @@ fn parse_scalar_function_in_projection() { for function_name in names { // like SELECT sqrt(id) FROM foo - let sql = dbg!(format!("SELECT {function_name}(id) FROM foo")); + let sql = format!("SELECT {function_name}(id) FROM foo"); let select = verified_only_select(&sql); assert_eq!( &call(function_name, [Expr::Identifier(Ident::new("id"))]), @@ -8236,30 +8236,34 @@ fn parse_time_functions() { #[test] fn parse_position() { - let sql = "SELECT POSITION('@' IN field)"; - let select = verified_only_select(sql); assert_eq!( - &Expr::Position { + Expr::Position { expr: Box::new(Expr::Value(Value::SingleQuotedString("@".to_string()))), r#in: Box::new(Expr::Identifier(Ident::new("field"))), }, - expr_from_projection(only(&select.projection)) + verified_expr("POSITION('@' IN field)"), ); -} -#[test] -fn parse_position_negative() { - let sql = "SELECT POSITION(foo) from bar"; - let res = parse_sql_statements(sql); + // some dialects (e.g. snowflake) support position as a function call (i.e. without IN) assert_eq!( - ParserError::ParserError("Position function must include IN keyword".to_string()), - res.unwrap_err() + call( + "position", + [ + Expr::Value(Value::SingleQuotedString("an".to_owned())), + Expr::Value(Value::SingleQuotedString("banana".to_owned())), + Expr::Value(number("1")), + ] + ), + verified_expr("position('an', 'banana', 1)") ); +} +#[test] +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: )".to_string()), + ParserError::ParserError("Expected: (, found: )".to_string()), res.unwrap_err() ); } diff --git a/tests/sqlparser_snowflake.rs b/tests/sqlparser_snowflake.rs index 7a2288cbb..7abb1a947 100644 --- a/tests/sqlparser_snowflake.rs +++ b/tests/sqlparser_snowflake.rs @@ -2256,3 +2256,9 @@ fn asof_joins() { "ORDER BY s.observed", )); } + +#[test] +fn test_parse_position() { + snowflake().verified_query("SELECT position('an', 'banana', 1)"); + snowflake().verified_query("SELECT n, h, POSITION(n IN h) FROM pos"); +}