Skip to content

Commit d173e82

Browse files
Enhance CREATE FUNCTION parsing to support C functions with multiple arguments and flexible attribute order
1 parent 308a723 commit d173e82

File tree

3 files changed

+78
-11
lines changed

3 files changed

+78
-11
lines changed

src/ast/ddl.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2768,7 +2768,15 @@ impl fmt::Display for CreateFunction {
27682768
write!(f, " REMOTE WITH CONNECTION {remote_connection}")?;
27692769
}
27702770
if let Some(CreateFunctionBody::AsBeforeOptions(function_body)) = &self.function_body {
2771-
write!(f, " AS {function_body}")?;
2771+
write!(f, " AS ")?;
2772+
// Special handling for tuple expressions to format without parentheses
2773+
// PostgreSQL C functions use: AS 'obj_file', 'link_symbol'
2774+
// rather than: AS ('obj_file', 'link_symbol')
2775+
if let Expr::Tuple(exprs) = function_body {
2776+
write!(f, "{}", display_comma_separated(exprs))?;
2777+
} else {
2778+
write!(f, "{function_body}")?;
2779+
}
27722780
}
27732781
if let Some(CreateFunctionBody::Return(function_body)) = &self.function_body {
27742782
write!(f, " RETURN {function_body}")?;

src/parser/mod.rs

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10219,17 +10219,32 @@ impl<'a> Parser<'a> {
1021910219
/// Parse the body of a `CREATE FUNCTION` specified as a string.
1022010220
/// e.g. `CREATE FUNCTION ... AS $$ body $$`.
1022110221
fn parse_create_function_body_string(&mut self) -> Result<Expr, ParserError> {
10222-
let peek_token = self.peek_token();
10223-
let span = peek_token.span;
10224-
match peek_token.token {
10225-
Token::DollarQuotedString(s) if dialect_of!(self is PostgreSqlDialect | GenericDialect) =>
10226-
{
10227-
self.next_token();
10228-
Ok(Expr::Value(Value::DollarQuotedString(s).with_span(span)))
10222+
// Helper closure to parse a single string value (quoted or dollar-quoted)
10223+
let parse_string_expr = |parser: &mut Parser| -> Result<Expr, ParserError> {
10224+
let peek_token = parser.peek_token();
10225+
let span = peek_token.span;
10226+
match peek_token.token {
10227+
Token::DollarQuotedString(s) if dialect_of!(parser is PostgreSqlDialect | GenericDialect) =>
10228+
{
10229+
parser.next_token();
10230+
Ok(Expr::Value(Value::DollarQuotedString(s).with_span(span)))
10231+
}
10232+
_ => Ok(Expr::Value(
10233+
Value::SingleQuotedString(parser.parse_literal_string()?).with_span(span),
10234+
)),
1022910235
}
10230-
_ => Ok(Expr::Value(
10231-
Value::SingleQuotedString(self.parse_literal_string()?).with_span(span),
10232-
)),
10236+
};
10237+
10238+
let first_expr = parse_string_expr(self)?;
10239+
10240+
// Check if there's a comma, indicating multiple strings (e.g., AS 'obj_file', 'link_symbol')
10241+
// This is used for C language functions: AS 'MODULE_PATHNAME', 'link_symbol'
10242+
if self.consume_token(&Token::Comma) {
10243+
let mut exprs = vec![first_expr];
10244+
exprs.extend(self.parse_comma_separated(parse_string_expr)?);
10245+
Ok(Expr::Tuple(exprs))
10246+
} else {
10247+
Ok(first_expr)
1023310248
}
1023410249
}
1023510250

tests/sqlparser_postgres.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4482,6 +4482,50 @@ fn parse_incorrect_create_function_parallel() {
44824482
assert!(pg().parse_sql_statements(sql).is_err());
44834483
}
44844484

4485+
#[test]
4486+
fn parse_create_function_c_with_module_pathname() {
4487+
let sql = "CREATE FUNCTION cas_in(input cstring) RETURNS cas LANGUAGE c IMMUTABLE PARALLEL SAFE AS 'MODULE_PATHNAME', 'cas_in_wrapper'";
4488+
assert_eq!(
4489+
pg_and_generic().verified_stmt(sql),
4490+
Statement::CreateFunction(CreateFunction {
4491+
or_alter: false,
4492+
or_replace: false,
4493+
temporary: false,
4494+
name: ObjectName::from(vec![Ident::new("cas_in")]),
4495+
args: Some(vec![OperateFunctionArg::with_name(
4496+
"input",
4497+
DataType::Custom(ObjectName::from(vec![Ident::new("cstring")]), vec![]),
4498+
),]),
4499+
return_type: Some(DataType::Custom(
4500+
ObjectName::from(vec![Ident::new("cas")]),
4501+
vec![]
4502+
)),
4503+
language: Some("c".into()),
4504+
behavior: Some(FunctionBehavior::Immutable),
4505+
called_on_null: None,
4506+
parallel: Some(FunctionParallel::Safe),
4507+
function_body: Some(CreateFunctionBody::AsBeforeOptions(Expr::Tuple(vec![
4508+
Expr::Value(
4509+
(Value::SingleQuotedString("MODULE_PATHNAME".into())).with_empty_span()
4510+
),
4511+
Expr::Value((Value::SingleQuotedString("cas_in_wrapper".into())).with_empty_span()),
4512+
]))),
4513+
if_not_exists: false,
4514+
using: None,
4515+
determinism_specifier: None,
4516+
options: None,
4517+
remote_connection: None,
4518+
})
4519+
);
4520+
4521+
// Test that attribute order flexibility works (IMMUTABLE before LANGUAGE)
4522+
let sql_alt_order = "CREATE FUNCTION cas_in(input cstring) RETURNS cas IMMUTABLE PARALLEL SAFE LANGUAGE c AS 'MODULE_PATHNAME', 'cas_in_wrapper'";
4523+
pg_and_generic().one_statement_parses_to(
4524+
sql_alt_order,
4525+
"CREATE FUNCTION cas_in(input cstring) RETURNS cas LANGUAGE c IMMUTABLE PARALLEL SAFE AS 'MODULE_PATHNAME', 'cas_in_wrapper'"
4526+
);
4527+
}
4528+
44854529
#[test]
44864530
fn parse_drop_function() {
44874531
let sql = "DROP FUNCTION IF EXISTS test_func";

0 commit comments

Comments
 (0)