Skip to content

Snowflake: support position with normal function call syntax #1341

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jul 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/keywords.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down
30 changes: 16 additions & 14 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down Expand Up @@ -1707,24 +1707,26 @@ impl<'a> Parser<'a> {
}
}

pub fn parse_position_expr(&mut self) -> Result<Expr, ParserError> {
// PARSE SELECT POSITION('@' in field)
self.expect_token(&Token::LParen)?;
pub fn parse_position_expr(&mut self, ident: Ident) -> Result<Expr, ParserError> {
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])),
}
}

Expand Down
8 changes: 4 additions & 4 deletions src/test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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,
Expand Down
30 changes: 17 additions & 13 deletions tests/sqlparser_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"))]),
Expand Down Expand Up @@ -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()
);
}
Expand Down
6 changes: 6 additions & 0 deletions tests/sqlparser_snowflake.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
Loading