diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 931033f7..b3120bb3 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/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index e68f25eb..5de76f78 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"))]), @@ -8254,30 +8254,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)"), + ); + + // some dialects (e.g. snowflake) support position as a function call (i.e. without IN) + assert_eq!( + 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) from bar"; - let res = parse_sql_statements(sql); - assert_eq!( - ParserError::ParserError("Position function must include IN keyword".to_string()), - res.unwrap_err() - ); - 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 7a2288cb..7abb1a94 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"); +}