Add support for ODBC functions (#1585)
diff --git a/src/ast/mod.rs b/src/ast/mod.rs
index bc4dda3..cfd0ac0 100644
--- a/src/ast/mod.rs
+++ b/src/ast/mod.rs
@@ -5523,6 +5523,15 @@
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub struct Function {
pub name: ObjectName,
+ /// Flags whether this function call uses the [ODBC syntax].
+ ///
+ /// Example:
+ /// ```sql
+ /// SELECT {fn CONCAT('foo', 'bar')}
+ /// ```
+ ///
+ /// [ODBC syntax]: https://learn.microsoft.com/en-us/sql/odbc/reference/develop-app/scalar-function-calls?view=sql-server-2017
+ pub uses_odbc_syntax: bool,
/// The parameters to the function, including any options specified within the
/// delimiting parentheses.
///
@@ -5561,6 +5570,10 @@
impl fmt::Display for Function {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ if self.uses_odbc_syntax {
+ write!(f, "{{fn ")?;
+ }
+
write!(f, "{}{}{}", self.name, self.parameters, self.args)?;
if !self.within_group.is_empty() {
@@ -5583,6 +5596,10 @@
write!(f, " OVER {o}")?;
}
+ if self.uses_odbc_syntax {
+ write!(f, "}}")?;
+ }
+
Ok(())
}
}
diff --git a/src/ast/spans.rs b/src/ast/spans.rs
index de577c9..7e45f83 100644
--- a/src/ast/spans.rs
+++ b/src/ast/spans.rs
@@ -1478,6 +1478,7 @@
fn span(&self) -> Span {
let Function {
name,
+ uses_odbc_syntax: _,
parameters,
args,
filter,
diff --git a/src/ast/visitor.rs b/src/ast/visitor.rs
index eacd268..f7562b6 100644
--- a/src/ast/visitor.rs
+++ b/src/ast/visitor.rs
@@ -530,6 +530,7 @@
/// let old_expr = std::mem::replace(expr, Expr::Value(Value::Null));
/// *expr = Expr::Function(Function {
/// name: ObjectName(vec![Ident::new("f")]),
+/// uses_odbc_syntax: false,
/// args: FunctionArguments::List(FunctionArgumentList {
/// duplicate_treatment: None,
/// args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(old_expr))],
diff --git a/src/keywords.rs b/src/keywords.rs
index 25a719d..d0cfcd0 100644
--- a/src/keywords.rs
+++ b/src/keywords.rs
@@ -333,6 +333,7 @@
FLOAT8,
FLOOR,
FLUSH,
+ FN,
FOLLOWING,
FOR,
FORCE,
diff --git a/src/parser/mod.rs b/src/parser/mod.rs
index 04d6edc..39ab2db 100644
--- a/src/parser/mod.rs
+++ b/src/parser/mod.rs
@@ -1053,6 +1053,7 @@
{
Ok(Some(Expr::Function(Function {
name: ObjectName(vec![w.to_ident(w_span)]),
+ uses_odbc_syntax: false,
parameters: FunctionArguments::None,
args: FunctionArguments::None,
null_treatment: None,
@@ -1111,6 +1112,7 @@
self.expect_token(&Token::RParen)?;
Ok(Some(Expr::Function(Function {
name: ObjectName(vec![w.to_ident(w_span)]),
+ uses_odbc_syntax: false,
parameters: FunctionArguments::None,
args: FunctionArguments::Subquery(query),
filter: None,
@@ -1408,9 +1410,9 @@
self.prev_token();
Ok(Expr::Value(self.parse_value()?))
}
- Token::LBrace if self.dialect.supports_dictionary_syntax() => {
+ Token::LBrace => {
self.prev_token();
- self.parse_duckdb_struct_literal()
+ self.parse_lbrace_expr()
}
_ => self.expected("an expression", next_token),
}?;
@@ -1509,7 +1511,29 @@
}
}
+ /// Tries to parse the body of an [ODBC function] call.
+ /// i.e. without the enclosing braces
+ ///
+ /// ```sql
+ /// fn myfunc(1,2,3)
+ /// ```
+ ///
+ /// [ODBC function]: https://learn.microsoft.com/en-us/sql/odbc/reference/develop-app/scalar-function-calls?view=sql-server-2017
+ fn maybe_parse_odbc_fn_body(&mut self) -> Result<Option<Expr>, ParserError> {
+ self.maybe_parse(|p| {
+ p.expect_keyword(Keyword::FN)?;
+ let fn_name = p.parse_object_name(false)?;
+ let mut fn_call = p.parse_function_call(fn_name)?;
+ fn_call.uses_odbc_syntax = true;
+ Ok(Expr::Function(fn_call))
+ })
+ }
+
pub fn parse_function(&mut self, name: ObjectName) -> Result<Expr, ParserError> {
+ self.parse_function_call(name).map(Expr::Function)
+ }
+
+ fn parse_function_call(&mut self, name: ObjectName) -> Result<Function, ParserError> {
self.expect_token(&Token::LParen)?;
// Snowflake permits a subquery to be passed as an argument without
@@ -1517,15 +1541,16 @@
if dialect_of!(self is SnowflakeDialect) && self.peek_sub_query() {
let subquery = self.parse_query()?;
self.expect_token(&Token::RParen)?;
- return Ok(Expr::Function(Function {
+ return Ok(Function {
name,
+ uses_odbc_syntax: false,
parameters: FunctionArguments::None,
args: FunctionArguments::Subquery(subquery),
filter: None,
null_treatment: None,
over: None,
within_group: vec![],
- }));
+ });
}
let mut args = self.parse_function_argument_list()?;
@@ -1584,15 +1609,16 @@
None
};
- Ok(Expr::Function(Function {
+ Ok(Function {
name,
+ uses_odbc_syntax: false,
parameters,
args: FunctionArguments::List(args),
null_treatment,
filter,
over,
within_group,
- }))
+ })
}
/// Optionally parses a null treatment clause.
@@ -1619,6 +1645,7 @@
};
Ok(Expr::Function(Function {
name,
+ uses_odbc_syntax: false,
parameters: FunctionArguments::None,
args,
filter: None,
@@ -2211,6 +2238,31 @@
}
}
+ /// Parse expression types that start with a left brace '{'.
+ /// Examples:
+ /// ```sql
+ /// -- Dictionary expr.
+ /// {'key1': 'value1', 'key2': 'value2'}
+ ///
+ /// -- Function call using the ODBC syntax.
+ /// { fn CONCAT('foo', 'bar') }
+ /// ```
+ fn parse_lbrace_expr(&mut self) -> Result<Expr, ParserError> {
+ let token = self.expect_token(&Token::LBrace)?;
+
+ if let Some(fn_expr) = self.maybe_parse_odbc_fn_body()? {
+ self.expect_token(&Token::RBrace)?;
+ return Ok(fn_expr);
+ }
+
+ if self.dialect.supports_dictionary_syntax() {
+ self.prev_token(); // Put back the '{'
+ return self.parse_duckdb_struct_literal();
+ }
+
+ self.expected("an expression", token)
+ }
+
/// Parses fulltext expressions [`sqlparser::ast::Expr::MatchAgainst`]
///
/// # Errors
@@ -7578,6 +7630,7 @@
} else {
Ok(Statement::Call(Function {
name: object_name,
+ uses_odbc_syntax: false,
parameters: FunctionArguments::None,
args: FunctionArguments::None,
over: None,
diff --git a/src/test_utils.rs b/src/test_utils.rs
index aaee20c..6e60a31 100644
--- a/src/test_utils.rs
+++ b/src/test_utils.rs
@@ -376,6 +376,7 @@
pub fn call(function: &str, args: impl IntoIterator<Item = Expr>) -> Expr {
Expr::Function(Function {
name: ObjectName(vec![Ident::new(function)]),
+ uses_odbc_syntax: false,
parameters: FunctionArguments::None,
args: FunctionArguments::List(FunctionArgumentList {
duplicate_treatment: None,
diff --git a/tests/sqlparser_clickhouse.rs b/tests/sqlparser_clickhouse.rs
index ed0c740..9d78557 100644
--- a/tests/sqlparser_clickhouse.rs
+++ b/tests/sqlparser_clickhouse.rs
@@ -199,6 +199,7 @@
assert_eq!(
&Expr::Function(Function {
name: ObjectName(vec![Ident::with_quote('"', "myfun")]),
+ uses_odbc_syntax: false,
parameters: FunctionArguments::None,
args: FunctionArguments::List(FunctionArgumentList {
duplicate_treatment: None,
@@ -821,6 +822,7 @@
name: None,
option: ColumnOption::Materialized(Expr::Function(Function {
name: ObjectName(vec![Ident::new("now")]),
+ uses_odbc_syntax: false,
args: FunctionArguments::List(FunctionArgumentList {
args: vec![],
duplicate_treatment: None,
@@ -842,6 +844,7 @@
name: None,
option: ColumnOption::Ephemeral(Some(Expr::Function(Function {
name: ObjectName(vec![Ident::new("now")]),
+ uses_odbc_syntax: false,
args: FunctionArguments::List(FunctionArgumentList {
args: vec![],
duplicate_treatment: None,
@@ -872,6 +875,7 @@
name: None,
option: ColumnOption::Alias(Expr::Function(Function {
name: ObjectName(vec![Ident::new("toString")]),
+ uses_odbc_syntax: false,
args: FunctionArguments::List(FunctionArgumentList {
args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(
Identifier(Ident::new("c"))
diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs
index f76516e..7dfb98d 100644
--- a/tests/sqlparser_common.rs
+++ b/tests/sqlparser_common.rs
@@ -1108,6 +1108,7 @@
assert_eq!(
&Expr::Function(Function {
name: ObjectName(vec![Ident::new("COUNT")]),
+ uses_odbc_syntax: false,
parameters: FunctionArguments::None,
args: FunctionArguments::List(FunctionArgumentList {
duplicate_treatment: None,
@@ -1130,6 +1131,7 @@
assert_eq!(
&Expr::Function(Function {
name: ObjectName(vec![Ident::new("COUNT")]),
+ uses_odbc_syntax: false,
parameters: FunctionArguments::None,
args: FunctionArguments::List(FunctionArgumentList {
duplicate_treatment: Some(DuplicateTreatment::Distinct),
@@ -2366,6 +2368,7 @@
Some(Expr::BinaryOp {
left: Box::new(Expr::Function(Function {
name: ObjectName(vec![Ident::new("COUNT")]),
+ uses_odbc_syntax: false,
parameters: FunctionArguments::None,
args: FunctionArguments::List(FunctionArgumentList {
duplicate_treatment: None,
@@ -2396,6 +2399,7 @@
Some(Expr::BinaryOp {
left: Box::new(Expr::Function(Function {
name: ObjectName(vec![Ident::new("ROW_NUMBER")]),
+ uses_odbc_syntax: false,
parameters: FunctionArguments::None,
args: FunctionArguments::List(FunctionArgumentList {
duplicate_treatment: None,
@@ -2802,6 +2806,7 @@
assert_eq!(
&Expr::Function(Function {
name: ObjectName(vec![Ident::new("LISTAGG")]),
+ uses_odbc_syntax: false,
parameters: FunctionArguments::None,
args: FunctionArguments::List(FunctionArgumentList {
duplicate_treatment: Some(DuplicateTreatment::Distinct),
@@ -4603,6 +4608,7 @@
assert_eq!(
&Expr::Function(Function {
name: ObjectName(vec![Ident::new("FUN")]),
+ uses_odbc_syntax: false,
parameters: FunctionArguments::None,
args: FunctionArguments::List(FunctionArgumentList {
duplicate_treatment: None,
@@ -4642,6 +4648,7 @@
assert_eq!(
&Expr::Function(Function {
name: ObjectName(vec![Ident::new("FUN")]),
+ uses_odbc_syntax: false,
parameters: FunctionArguments::None,
args: FunctionArguments::List(FunctionArgumentList {
duplicate_treatment: None,
@@ -4716,6 +4723,7 @@
assert_eq!(
&Expr::Function(Function {
name: ObjectName(vec![Ident::new("row_number")]),
+ uses_odbc_syntax: false,
parameters: FunctionArguments::None,
args: FunctionArguments::List(FunctionArgumentList {
duplicate_treatment: None,
@@ -4846,6 +4854,7 @@
quote_style: None,
span: Span::empty(),
}]),
+ uses_odbc_syntax: false,
parameters: FunctionArguments::None,
args: FunctionArguments::List(FunctionArgumentList {
duplicate_treatment: None,
@@ -4880,6 +4889,7 @@
quote_style: None,
span: Span::empty(),
}]),
+ uses_odbc_syntax: false,
parameters: FunctionArguments::None,
args: FunctionArguments::List(FunctionArgumentList {
duplicate_treatment: None,
@@ -9008,6 +9018,7 @@
let select = verified_only_select(&sql);
let select_localtime_func_call_ast = Function {
name: ObjectName(vec![Ident::new(func_name)]),
+ uses_odbc_syntax: false,
parameters: FunctionArguments::None,
args: FunctionArguments::List(FunctionArgumentList {
duplicate_treatment: None,
@@ -10021,6 +10032,7 @@
assert_eq!(
verified_stmt("CALL my_procedure('a')"),
Statement::Call(Function {
+ uses_odbc_syntax: false,
parameters: FunctionArguments::None,
args: FunctionArguments::List(FunctionArgumentList {
duplicate_treatment: None,
@@ -10511,6 +10523,7 @@
vec![
SelectItem::UnnamedExpr(Expr::Function(Function {
name: ObjectName(vec![Ident::new("ARRAY_AGG")]),
+ uses_odbc_syntax: false,
parameters: FunctionArguments::None,
args: FunctionArguments::List(FunctionArgumentList {
duplicate_treatment: None,
@@ -10529,6 +10542,7 @@
SelectItem::ExprWithAlias {
expr: Expr::Function(Function {
name: ObjectName(vec![Ident::new("ARRAY_AGG")]),
+ uses_odbc_syntax: false,
parameters: FunctionArguments::None,
args: FunctionArguments::List(FunctionArgumentList {
duplicate_treatment: None,
@@ -10969,6 +10983,35 @@
}
#[test]
+fn parse_odbc_scalar_function() {
+ let select = verified_only_select("SELECT {fn my_func(1, 2)}");
+ let Expr::Function(Function {
+ name,
+ uses_odbc_syntax,
+ args,
+ ..
+ }) = expr_from_projection(only(&select.projection))
+ else {
+ unreachable!("expected function")
+ };
+ assert_eq!(name, &ObjectName(vec![Ident::new("my_func")]));
+ assert!(uses_odbc_syntax);
+ matches!(args, FunctionArguments::List(l) if l.args.len() == 2);
+
+ verified_stmt("SELECT {fn fna()} AS foo, fnb(1)");
+
+ // Testing invalid SQL with any-one dialect is intentional.
+ // Depending on dialect flags the error message may be different.
+ let pg = TestedDialects::new(vec![Box::new(PostgreSqlDialect {})]);
+ assert_eq!(
+ pg.parse_sql_statements("SELECT {fn2 my_func()}")
+ .unwrap_err()
+ .to_string(),
+ "sql parser error: Expected: an expression, found: {"
+ );
+}
+
+#[test]
fn test_dictionary_syntax() {
fn check(sql: &str, expect: Expr) {
assert_eq!(
diff --git a/tests/sqlparser_duckdb.rs b/tests/sqlparser_duckdb.rs
index 01ac064..a0fc49b 100644
--- a/tests/sqlparser_duckdb.rs
+++ b/tests/sqlparser_duckdb.rs
@@ -606,6 +606,7 @@
assert_eq!(
&Expr::Function(Function {
name: ObjectName(vec![Ident::new("FUN")]),
+ uses_odbc_syntax: false,
parameters: FunctionArguments::None,
args: FunctionArguments::List(FunctionArgumentList {
duplicate_treatment: None,
diff --git a/tests/sqlparser_hive.rs b/tests/sqlparser_hive.rs
index 546b289..9812183 100644
--- a/tests/sqlparser_hive.rs
+++ b/tests/sqlparser_hive.rs
@@ -480,6 +480,7 @@
assert_eq!(
&Expr::Function(Function {
name: ObjectName(vec![Ident::with_quote('"', "myfun")]),
+ uses_odbc_syntax: false,
parameters: FunctionArguments::None,
args: FunctionArguments::List(FunctionArgumentList {
duplicate_treatment: None,
diff --git a/tests/sqlparser_mssql.rs b/tests/sqlparser_mssql.rs
index 31668c8..66e40f4 100644
--- a/tests/sqlparser_mssql.rs
+++ b/tests/sqlparser_mssql.rs
@@ -635,6 +635,7 @@
assert_eq!(
&Expr::Function(Function {
name: ObjectName(vec![Ident::with_quote('"', "myfun")]),
+ uses_odbc_syntax: false,
parameters: FunctionArguments::None,
args: FunctionArguments::List(FunctionArgumentList {
duplicate_treatment: None,
@@ -1388,6 +1389,7 @@
},
],
),
+ uses_odbc_syntax: false,
parameters: FunctionArguments::None,
args: FunctionArguments::List(
FunctionArgumentList {
diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs
index 92368e9..2e204d9 100644
--- a/tests/sqlparser_postgres.rs
+++ b/tests/sqlparser_postgres.rs
@@ -2529,6 +2529,7 @@
assert_eq!(
&Expr::Function(Function {
name: ObjectName(vec![Ident::new("ARRAY")]),
+ uses_odbc_syntax: false,
parameters: FunctionArguments::None,
args: FunctionArguments::Subquery(Box::new(Query {
with: None,
@@ -2911,6 +2912,7 @@
Ident::new("information_schema"),
Ident::new("_pg_expandarray")
]),
+ uses_odbc_syntax: false,
parameters: FunctionArguments::None,
args: FunctionArguments::List(FunctionArgumentList {
duplicate_treatment: None,
@@ -3088,6 +3090,7 @@
assert_eq!(
&Expr::Function(Function {
name: ObjectName(vec![Ident::new("CURRENT_CATALOG")]),
+ uses_odbc_syntax: false,
parameters: FunctionArguments::None,
args: FunctionArguments::None,
null_treatment: None,
@@ -3100,6 +3103,7 @@
assert_eq!(
&Expr::Function(Function {
name: ObjectName(vec![Ident::new("CURRENT_USER")]),
+ uses_odbc_syntax: false,
parameters: FunctionArguments::None,
args: FunctionArguments::None,
null_treatment: None,
@@ -3112,6 +3116,7 @@
assert_eq!(
&Expr::Function(Function {
name: ObjectName(vec![Ident::new("SESSION_USER")]),
+ uses_odbc_syntax: false,
parameters: FunctionArguments::None,
args: FunctionArguments::None,
null_treatment: None,
@@ -3124,6 +3129,7 @@
assert_eq!(
&Expr::Function(Function {
name: ObjectName(vec![Ident::new("USER")]),
+ uses_odbc_syntax: false,
parameters: FunctionArguments::None,
args: FunctionArguments::None,
null_treatment: None,
@@ -3599,6 +3605,7 @@
assert_eq!(
&Expr::Function(Function {
name: ObjectName(vec![Ident::with_quote('"', "myfun")]),
+ uses_odbc_syntax: false,
parameters: FunctionArguments::None,
args: FunctionArguments::List(FunctionArgumentList {
duplicate_treatment: None,
diff --git a/tests/sqlparser_redshift.rs b/tests/sqlparser_redshift.rs
index f0c1f0c..2fd855a 100644
--- a/tests/sqlparser_redshift.rs
+++ b/tests/sqlparser_redshift.rs
@@ -154,6 +154,7 @@
assert_eq!(
&Expr::Function(Function {
name: ObjectName(vec![Ident::with_quote('"', "myfun")]),
+ uses_odbc_syntax: false,
parameters: FunctionArguments::None,
args: FunctionArguments::List(FunctionArgumentList {
duplicate_treatment: None,
diff --git a/tests/sqlparser_snowflake.rs b/tests/sqlparser_snowflake.rs
index 5ad861f..d6774c3 100644
--- a/tests/sqlparser_snowflake.rs
+++ b/tests/sqlparser_snowflake.rs
@@ -1212,6 +1212,7 @@
assert_eq!(
&Expr::Function(Function {
name: ObjectName(vec![Ident::with_quote('"', "myfun")]),
+ uses_odbc_syntax: false,
parameters: FunctionArguments::None,
args: FunctionArguments::List(FunctionArgumentList {
duplicate_treatment: None,
@@ -1423,6 +1424,7 @@
Expr::Identifier(Ident::with_quote('"', "c2")),
Expr::Function(Function {
name: ObjectName(vec![Ident::new("TO_DATE")]),
+ uses_odbc_syntax: false,
parameters: FunctionArguments::None,
args: FunctionArguments::List(FunctionArgumentList {
args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(
diff --git a/tests/sqlparser_sqlite.rs b/tests/sqlparser_sqlite.rs
index 4f23979..987b126 100644
--- a/tests/sqlparser_sqlite.rs
+++ b/tests/sqlparser_sqlite.rs
@@ -419,6 +419,7 @@
select.projection,
vec![SelectItem::UnnamedExpr(Expr::Function(Function {
name: ObjectName(vec![Ident::new(func_name)]),
+ uses_odbc_syntax: false,
parameters: FunctionArguments::None,
args: FunctionArguments::List(FunctionArgumentList {
duplicate_treatment: None,