Skip to content
Open
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
10 changes: 9 additions & 1 deletion src/ast/ddl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3094,7 +3094,15 @@ impl fmt::Display for CreateFunction {
write!(f, " REMOTE WITH CONNECTION {remote_connection}")?;
}
if let Some(CreateFunctionBody::AsBeforeOptions(function_body)) = &self.function_body {
write!(f, " AS {function_body}")?;
write!(f, " AS ")?;
// Special handling for tuple expressions to format without parentheses
// PostgreSQL C functions use: AS 'obj_file', 'link_symbol'
// rather than: AS ('obj_file', 'link_symbol')
if let Expr::Tuple(exprs) = function_body {
write!(f, "{}", display_comma_separated(exprs))?;
} else {
write!(f, "{function_body}")?;
}
}
if let Some(CreateFunctionBody::Return(function_body)) = &self.function_body {
write!(f, " RETURN {function_body}")?;
Expand Down
35 changes: 25 additions & 10 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10224,17 +10224,32 @@ impl<'a> Parser<'a> {
/// Parse the body of a `CREATE FUNCTION` specified as a string.
/// e.g. `CREATE FUNCTION ... AS $$ body $$`.
fn parse_create_function_body_string(&mut self) -> Result<Expr, ParserError> {
let peek_token = self.peek_token();
let span = peek_token.span;
match peek_token.token {
Token::DollarQuotedString(s) if dialect_of!(self is PostgreSqlDialect | GenericDialect) =>
{
self.next_token();
Ok(Expr::Value(Value::DollarQuotedString(s).with_span(span)))
// Helper closure to parse a single string value (quoted or dollar-quoted)
let parse_string_expr = |parser: &mut Parser| -> Result<Expr, ParserError> {
let peek_token = parser.peek_token();
let span = peek_token.span;
match peek_token.token {
Token::DollarQuotedString(s) if dialect_of!(parser is PostgreSqlDialect | GenericDialect) =>
{
parser.next_token();
Ok(Expr::Value(Value::DollarQuotedString(s).with_span(span)))
}
_ => Ok(Expr::Value(
Value::SingleQuotedString(parser.parse_literal_string()?).with_span(span),
)),
}
_ => Ok(Expr::Value(
Value::SingleQuotedString(self.parse_literal_string()?).with_span(span),
)),
};

let first_expr = parse_string_expr(self)?;

// Check if there's a comma, indicating multiple strings (e.g., AS 'obj_file', 'link_symbol')
// This is used for C language functions: AS 'MODULE_PATHNAME', 'link_symbol'
if self.consume_token(&Token::Comma) {
let mut exprs = vec![first_expr];
exprs.extend(self.parse_comma_separated(parse_string_expr)?);
Ok(Expr::Tuple(exprs))
} else {
Ok(first_expr)
}
}

Expand Down
44 changes: 44 additions & 0 deletions tests/sqlparser_postgres.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4482,6 +4482,50 @@ fn parse_incorrect_create_function_parallel() {
assert!(pg().parse_sql_statements(sql).is_err());
}

#[test]
fn parse_create_function_c_with_module_pathname() {
let sql = "CREATE FUNCTION cas_in(input cstring) RETURNS cas LANGUAGE c IMMUTABLE PARALLEL SAFE AS 'MODULE_PATHNAME', 'cas_in_wrapper'";
assert_eq!(
pg_and_generic().verified_stmt(sql),
Statement::CreateFunction(CreateFunction {
or_alter: false,
or_replace: false,
temporary: false,
name: ObjectName::from(vec![Ident::new("cas_in")]),
args: Some(vec![OperateFunctionArg::with_name(
"input",
DataType::Custom(ObjectName::from(vec![Ident::new("cstring")]), vec![]),
),]),
return_type: Some(DataType::Custom(
ObjectName::from(vec![Ident::new("cas")]),
vec![]
)),
language: Some("c".into()),
behavior: Some(FunctionBehavior::Immutable),
called_on_null: None,
parallel: Some(FunctionParallel::Safe),
function_body: Some(CreateFunctionBody::AsBeforeOptions(Expr::Tuple(vec![
Expr::Value(
(Value::SingleQuotedString("MODULE_PATHNAME".into())).with_empty_span()
),
Expr::Value((Value::SingleQuotedString("cas_in_wrapper".into())).with_empty_span()),
]))),
if_not_exists: false,
using: None,
determinism_specifier: None,
options: None,
remote_connection: None,
})
);

// Test that attribute order flexibility works (IMMUTABLE before LANGUAGE)
let sql_alt_order = "CREATE FUNCTION cas_in(input cstring) RETURNS cas IMMUTABLE PARALLEL SAFE LANGUAGE c AS 'MODULE_PATHNAME', 'cas_in_wrapper'";
pg_and_generic().one_statement_parses_to(
sql_alt_order,
"CREATE FUNCTION cas_in(input cstring) RETURNS cas LANGUAGE c IMMUTABLE PARALLEL SAFE AS 'MODULE_PATHNAME', 'cas_in_wrapper'"
);
}

#[test]
fn parse_drop_function() {
let sql = "DROP FUNCTION IF EXISTS test_func";
Expand Down
Loading