Skip to content

Commit e93d40a

Browse files
committed
Add support for ODBC functions
Adds support for dialects like Snowflake and MsSQL that support function calls using the ODBC syntax. ```sql SELECT {fn CONCAT('foo', 'bar')} ``` https://learn.microsoft.com/en-us/sql/odbc/reference/develop-app/scalar-function-calls?view=sql-server-2017
1 parent 00abaf2 commit e93d40a

16 files changed

+148
-6
lines changed

src/ast/mod.rs

+17
Original file line numberDiff line numberDiff line change
@@ -5523,6 +5523,15 @@ impl fmt::Display for CloseCursor {
55235523
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
55245524
pub struct Function {
55255525
pub name: ObjectName,
5526+
/// Flags whether this function call uses the [ODBC syntax].
5527+
///
5528+
/// Example:
5529+
/// ```sql
5530+
/// SELECT {fn CONCAT('foo', 'bar')}
5531+
/// ```
5532+
///
5533+
/// [ODBC syntax]: https://learn.microsoft.com/en-us/sql/odbc/reference/develop-app/scalar-function-calls?view=sql-server-2017
5534+
pub uses_odbc_syntax: bool,
55265535
/// The parameters to the function, including any options specified within the
55275536
/// delimiting parentheses.
55285537
///
@@ -5561,6 +5570,10 @@ pub struct Function {
55615570

55625571
impl fmt::Display for Function {
55635572
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
5573+
if self.uses_odbc_syntax {
5574+
write!(f, "{{fn ")?;
5575+
}
5576+
55645577
write!(f, "{}{}{}", self.name, self.parameters, self.args)?;
55655578

55665579
if !self.within_group.is_empty() {
@@ -5583,6 +5596,10 @@ impl fmt::Display for Function {
55835596
write!(f, " OVER {o}")?;
55845597
}
55855598

5599+
if self.uses_odbc_syntax {
5600+
write!(f, "}}")?;
5601+
}
5602+
55865603
Ok(())
55875604
}
55885605
}

src/ast/spans.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1478,6 +1478,7 @@ impl Spanned for Function {
14781478
fn span(&self) -> Span {
14791479
let Function {
14801480
name,
1481+
uses_odbc_syntax: _,
14811482
parameters,
14821483
args,
14831484
filter,

src/ast/visitor.rs

+1
Original file line numberDiff line numberDiff line change
@@ -530,6 +530,7 @@ where
530530
/// let old_expr = std::mem::replace(expr, Expr::Value(Value::Null));
531531
/// *expr = Expr::Function(Function {
532532
/// name: ObjectName(vec![Ident::new("f")]),
533+
/// uses_odbc_syntax: false,
533534
/// args: FunctionArguments::List(FunctionArgumentList {
534535
/// duplicate_treatment: None,
535536
/// args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(old_expr))],

src/dialect/mod.rs

+6
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,12 @@ pub trait Dialect: Debug + Any {
385385
false
386386
}
387387

388+
/// Return true if the dialect supports
389+
/// []: https://learn.microsoft.com/en-us/sql/odbc/reference/develop-app/scalar-function-calls?view=sql-server-2017
390+
fn supports_odbc_fn(&self) -> bool {
391+
false
392+
}
393+
388394
/// Dialect-specific infix parser override
389395
///
390396
/// This method is called to parse the next infix expression.

src/keywords.rs

+1
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,7 @@ define_keywords!(
333333
FLOAT8,
334334
FLOOR,
335335
FLUSH,
336+
FN,
336337
FOLLOWING,
337338
FOR,
338339
FORCE,

src/parser/mod.rs

+59-6
Original file line numberDiff line numberDiff line change
@@ -1053,6 +1053,7 @@ impl<'a> Parser<'a> {
10531053
{
10541054
Ok(Some(Expr::Function(Function {
10551055
name: ObjectName(vec![w.to_ident(w_span)]),
1056+
uses_odbc_syntax: false,
10561057
parameters: FunctionArguments::None,
10571058
args: FunctionArguments::None,
10581059
null_treatment: None,
@@ -1111,6 +1112,7 @@ impl<'a> Parser<'a> {
11111112
self.expect_token(&Token::RParen)?;
11121113
Ok(Some(Expr::Function(Function {
11131114
name: ObjectName(vec![w.to_ident(w_span)]),
1115+
uses_odbc_syntax: false,
11141116
parameters: FunctionArguments::None,
11151117
args: FunctionArguments::Subquery(query),
11161118
filter: None,
@@ -1408,9 +1410,9 @@ impl<'a> Parser<'a> {
14081410
self.prev_token();
14091411
Ok(Expr::Value(self.parse_value()?))
14101412
}
1411-
Token::LBrace if self.dialect.supports_dictionary_syntax() => {
1413+
Token::LBrace => {
14121414
self.prev_token();
1413-
self.parse_duckdb_struct_literal()
1415+
self.parse_lbrace_expr()
14141416
}
14151417
_ => self.expected("an expression", next_token),
14161418
}?;
@@ -1509,23 +1511,46 @@ impl<'a> Parser<'a> {
15091511
}
15101512
}
15111513

1514+
/// Tries to parse the body of an [ODBC function] call.
1515+
/// i.e. without the enclosing braces
1516+
///
1517+
/// ```sql
1518+
/// fn myfunc(1,2,3)
1519+
/// ```
1520+
///
1521+
/// [ODBC function]: https://learn.microsoft.com/en-us/sql/odbc/reference/develop-app/scalar-function-calls?view=sql-server-2017
1522+
fn maybe_parse_odbc_fn_body(&mut self) -> Result<Option<Expr>, ParserError> {
1523+
self.maybe_parse(|p| {
1524+
p.expect_keyword(Keyword::FN)?;
1525+
let fn_name = p.parse_object_name(false)?;
1526+
let mut fn_call = p.parse_function_call(fn_name)?;
1527+
fn_call.uses_odbc_syntax = true;
1528+
Ok(Expr::Function(fn_call))
1529+
})
1530+
}
1531+
15121532
pub fn parse_function(&mut self, name: ObjectName) -> Result<Expr, ParserError> {
1533+
self.parse_function_call(name).map(Expr::Function)
1534+
}
1535+
1536+
fn parse_function_call(&mut self, name: ObjectName) -> Result<Function, ParserError> {
15131537
self.expect_token(&Token::LParen)?;
15141538

15151539
// Snowflake permits a subquery to be passed as an argument without
15161540
// an enclosing set of parens if it's the only argument.
15171541
if dialect_of!(self is SnowflakeDialect) && self.peek_sub_query() {
15181542
let subquery = self.parse_query()?;
15191543
self.expect_token(&Token::RParen)?;
1520-
return Ok(Expr::Function(Function {
1544+
return Ok(Function {
15211545
name,
1546+
uses_odbc_syntax: false,
15221547
parameters: FunctionArguments::None,
15231548
args: FunctionArguments::Subquery(subquery),
15241549
filter: None,
15251550
null_treatment: None,
15261551
over: None,
15271552
within_group: vec![],
1528-
}));
1553+
});
15291554
}
15301555

15311556
let mut args = self.parse_function_argument_list()?;
@@ -1584,15 +1609,16 @@ impl<'a> Parser<'a> {
15841609
None
15851610
};
15861611

1587-
Ok(Expr::Function(Function {
1612+
Ok(Function {
15881613
name,
1614+
uses_odbc_syntax: false,
15891615
parameters,
15901616
args: FunctionArguments::List(args),
15911617
null_treatment,
15921618
filter,
15931619
over,
15941620
within_group,
1595-
}))
1621+
})
15961622
}
15971623

15981624
/// Optionally parses a null treatment clause.
@@ -1619,6 +1645,7 @@ impl<'a> Parser<'a> {
16191645
};
16201646
Ok(Expr::Function(Function {
16211647
name,
1648+
uses_odbc_syntax: false,
16221649
parameters: FunctionArguments::None,
16231650
args,
16241651
filter: None,
@@ -2211,6 +2238,31 @@ impl<'a> Parser<'a> {
22112238
}
22122239
}
22132240

2241+
/// Parse expression types that start with a left brace '{'.
2242+
/// Examples:
2243+
/// ```sql
2244+
/// -- Dictionary expr.
2245+
/// {'key1': 'value1', 'key2': 'value2'}
2246+
///
2247+
/// -- Function call using the ODBC syntax.
2248+
/// { fn CONCAT('foo', 'bar') }
2249+
/// ```
2250+
fn parse_lbrace_expr(&mut self) -> Result<Expr, ParserError> {
2251+
let token = self.expect_token(&Token::LBrace)?;
2252+
2253+
if let Some(fn_expr) = self.maybe_parse_odbc_fn_body()? {
2254+
self.expect_token(&Token::RBrace)?;
2255+
return Ok(fn_expr);
2256+
}
2257+
2258+
if self.dialect.supports_dictionary_syntax() {
2259+
self.prev_token(); // Put back the '{'
2260+
return self.parse_duckdb_struct_literal();
2261+
}
2262+
2263+
self.expected("an expression", token)
2264+
}
2265+
22142266
/// Parses fulltext expressions [`sqlparser::ast::Expr::MatchAgainst`]
22152267
///
22162268
/// # Errors
@@ -7578,6 +7630,7 @@ impl<'a> Parser<'a> {
75787630
} else {
75797631
Ok(Statement::Call(Function {
75807632
name: object_name,
7633+
uses_odbc_syntax: false,
75817634
parameters: FunctionArguments::None,
75827635
args: FunctionArguments::None,
75837636
over: None,

src/test_utils.rs

+1
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,7 @@ pub fn join(relation: TableFactor) -> Join {
376376
pub fn call(function: &str, args: impl IntoIterator<Item = Expr>) -> Expr {
377377
Expr::Function(Function {
378378
name: ObjectName(vec![Ident::new(function)]),
379+
uses_odbc_syntax: false,
379380
parameters: FunctionArguments::None,
380381
args: FunctionArguments::List(FunctionArgumentList {
381382
duplicate_treatment: None,

tests/sqlparser_clickhouse.rs

+4
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,7 @@ fn parse_delimited_identifiers() {
199199
assert_eq!(
200200
&Expr::Function(Function {
201201
name: ObjectName(vec![Ident::with_quote('"', "myfun")]),
202+
uses_odbc_syntax: false,
202203
parameters: FunctionArguments::None,
203204
args: FunctionArguments::List(FunctionArgumentList {
204205
duplicate_treatment: None,
@@ -821,6 +822,7 @@ fn parse_create_table_with_variant_default_expressions() {
821822
name: None,
822823
option: ColumnOption::Materialized(Expr::Function(Function {
823824
name: ObjectName(vec![Ident::new("now")]),
825+
uses_odbc_syntax: false,
824826
args: FunctionArguments::List(FunctionArgumentList {
825827
args: vec![],
826828
duplicate_treatment: None,
@@ -842,6 +844,7 @@ fn parse_create_table_with_variant_default_expressions() {
842844
name: None,
843845
option: ColumnOption::Ephemeral(Some(Expr::Function(Function {
844846
name: ObjectName(vec![Ident::new("now")]),
847+
uses_odbc_syntax: false,
845848
args: FunctionArguments::List(FunctionArgumentList {
846849
args: vec![],
847850
duplicate_treatment: None,
@@ -872,6 +875,7 @@ fn parse_create_table_with_variant_default_expressions() {
872875
name: None,
873876
option: ColumnOption::Alias(Expr::Function(Function {
874877
name: ObjectName(vec![Ident::new("toString")]),
878+
uses_odbc_syntax: false,
875879
args: FunctionArguments::List(FunctionArgumentList {
876880
args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(
877881
Identifier(Ident::new("c"))

0 commit comments

Comments
 (0)