Skip to content

Commit 0039c7f

Browse files
LucaCappelletti94iffyio
authored andcommitted
Support PostgreSQL C Functions with Multiple AS Parameters (apache#2095)
Co-authored-by: Ifeanyi Ubah <ify1992@yahoo.com>
1 parent a8ac13c commit 0039c7f

File tree

5 files changed

+147
-50
lines changed

5 files changed

+147
-50
lines changed

src/ast/ddl.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3099,8 +3099,12 @@ impl fmt::Display for CreateFunction {
30993099
if let Some(remote_connection) = &self.remote_connection {
31003100
write!(f, " REMOTE WITH CONNECTION {remote_connection}")?;
31013101
}
3102-
if let Some(CreateFunctionBody::AsBeforeOptions(function_body)) = &self.function_body {
3103-
write!(f, " AS {function_body}")?;
3102+
if let Some(CreateFunctionBody::AsBeforeOptions { body, link_symbol }) = &self.function_body
3103+
{
3104+
write!(f, " AS {body}")?;
3105+
if let Some(link_symbol) = link_symbol {
3106+
write!(f, ", {link_symbol}")?;
3107+
}
31043108
}
31053109
if let Some(CreateFunctionBody::Return(function_body)) = &self.function_body {
31063110
write!(f, " RETURN {function_body}")?;

src/ast/mod.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9142,7 +9142,20 @@ pub enum CreateFunctionBody {
91429142
/// ```
91439143
///
91449144
/// [BigQuery]: https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#syntax_11
9145-
AsBeforeOptions(Expr),
9145+
/// [PostgreSQL]: https://www.postgresql.org/docs/current/sql-createfunction.html
9146+
AsBeforeOptions {
9147+
/// The primary expression.
9148+
body: Expr,
9149+
/// Link symbol if the primary expression contains the name of shared library file.
9150+
///
9151+
/// Example:
9152+
/// ```sql
9153+
/// CREATE FUNCTION cas_in(input cstring) RETURNS cas
9154+
/// AS 'MODULE_PATHNAME', 'cas_in_wrapper'
9155+
/// ```
9156+
/// [PostgreSQL]: https://www.postgresql.org/docs/current/sql-createfunction.html
9157+
link_symbol: Option<Expr>,
9158+
},
91469159
/// A function body expression using the 'AS' keyword and shows up
91479160
/// after any `OPTIONS` clause.
91489161
///

src/parser/mod.rs

Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5235,9 +5235,7 @@ impl<'a> Parser<'a> {
52355235
}
52365236
if self.parse_keyword(Keyword::AS) {
52375237
ensure_not_set(&body.function_body, "AS")?;
5238-
body.function_body = Some(CreateFunctionBody::AsBeforeOptions(
5239-
self.parse_create_function_body_string()?,
5240-
));
5238+
body.function_body = Some(self.parse_create_function_body_string()?);
52415239
} else if self.parse_keyword(Keyword::LANGUAGE) {
52425240
ensure_not_set(&body.language, "LANGUAGE")?;
52435241
body.language = Some(self.parse_identifier()?);
@@ -5329,15 +5327,15 @@ impl<'a> Parser<'a> {
53295327
let name = self.parse_object_name(false)?;
53305328
self.expect_keyword_is(Keyword::AS)?;
53315329

5332-
let as_ = self.parse_create_function_body_string()?;
5330+
let body = self.parse_create_function_body_string()?;
53335331
let using = self.parse_optional_create_function_using()?;
53345332

53355333
Ok(Statement::CreateFunction(CreateFunction {
53365334
or_alter: false,
53375335
or_replace,
53385336
temporary,
53395337
name,
5340-
function_body: Some(CreateFunctionBody::AsBeforeOptions(as_)),
5338+
function_body: Some(body),
53415339
using,
53425340
if_not_exists: false,
53435341
args: None,
@@ -5399,7 +5397,10 @@ impl<'a> Parser<'a> {
53995397
let expr = self.parse_expr()?;
54005398
if options.is_none() {
54015399
options = self.maybe_parse_options(Keyword::OPTIONS)?;
5402-
Some(CreateFunctionBody::AsBeforeOptions(expr))
5400+
Some(CreateFunctionBody::AsBeforeOptions {
5401+
body: expr,
5402+
link_symbol: None,
5403+
})
54035404
} else {
54045405
Some(CreateFunctionBody::AsAfterOptions(expr))
54055406
}
@@ -10605,19 +10606,30 @@ impl<'a> Parser<'a> {
1060510606

1060610607
/// Parse the body of a `CREATE FUNCTION` specified as a string.
1060710608
/// e.g. `CREATE FUNCTION ... AS $$ body $$`.
10608-
fn parse_create_function_body_string(&mut self) -> Result<Expr, ParserError> {
10609-
let peek_token = self.peek_token();
10610-
let span = peek_token.span;
10611-
match peek_token.token {
10612-
Token::DollarQuotedString(s) if dialect_of!(self is PostgreSqlDialect | GenericDialect) =>
10613-
{
10614-
self.next_token();
10615-
Ok(Expr::Value(Value::DollarQuotedString(s).with_span(span)))
10609+
fn parse_create_function_body_string(&mut self) -> Result<CreateFunctionBody, ParserError> {
10610+
let parse_string_expr = |parser: &mut Parser| -> Result<Expr, ParserError> {
10611+
let peek_token = parser.peek_token();
10612+
let span = peek_token.span;
10613+
match peek_token.token {
10614+
Token::DollarQuotedString(s) if dialect_of!(parser is PostgreSqlDialect | GenericDialect) =>
10615+
{
10616+
parser.next_token();
10617+
Ok(Expr::Value(Value::DollarQuotedString(s).with_span(span)))
10618+
}
10619+
_ => Ok(Expr::Value(
10620+
Value::SingleQuotedString(parser.parse_literal_string()?).with_span(span),
10621+
)),
1061610622
}
10617-
_ => Ok(Expr::Value(
10618-
Value::SingleQuotedString(self.parse_literal_string()?).with_span(span),
10619-
)),
10620-
}
10623+
};
10624+
10625+
Ok(CreateFunctionBody::AsBeforeOptions {
10626+
body: parse_string_expr(self)?,
10627+
link_symbol: if self.consume_token(&Token::Comma) {
10628+
Some(parse_string_expr(self)?)
10629+
} else {
10630+
None
10631+
},
10632+
})
1062110633
}
1062210634

1062310635
/// Parse a literal string

tests/sqlparser_hive.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -408,10 +408,13 @@ fn parse_create_function() {
408408
assert_eq!(name.to_string(), "mydb.myfunc");
409409
assert_eq!(
410410
function_body,
411-
Some(CreateFunctionBody::AsBeforeOptions(Expr::Value(
412-
(Value::SingleQuotedString("org.random.class.Name".to_string()))
413-
.with_empty_span()
414-
)))
411+
Some(CreateFunctionBody::AsBeforeOptions {
412+
body: Expr::Value(
413+
(Value::SingleQuotedString("org.random.class.Name".to_string()))
414+
.with_empty_span()
415+
),
416+
link_symbol: None,
417+
})
415418
);
416419
assert_eq!(
417420
using,

tests/sqlparser_postgres.rs

Lines changed: 90 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4260,9 +4260,12 @@ $$"#;
42604260
behavior: None,
42614261
called_on_null: None,
42624262
parallel: None,
4263-
function_body: Some(CreateFunctionBody::AsBeforeOptions(Expr::Value(
4264-
(Value::DollarQuotedString(DollarQuotedString {value: "\nBEGIN\n IF str1 <> str2 THEN\n RETURN TRUE;\n ELSE\n RETURN FALSE;\n END IF;\nEND;\n".to_owned(), tag: None})).with_empty_span()
4265-
))),
4263+
function_body: Some(CreateFunctionBody::AsBeforeOptions {
4264+
body: Expr::Value(
4265+
(Value::DollarQuotedString(DollarQuotedString {value: "\nBEGIN\n IF str1 <> str2 THEN\n RETURN TRUE;\n ELSE\n RETURN FALSE;\n END IF;\nEND;\n".to_owned(), tag: None})).with_empty_span()
4266+
),
4267+
link_symbol: None,
4268+
}),
42664269
if_not_exists: false,
42674270
using: None,
42684271
determinism_specifier: None,
@@ -4298,9 +4301,12 @@ $$"#;
42984301
behavior: None,
42994302
called_on_null: None,
43004303
parallel: None,
4301-
function_body: Some(CreateFunctionBody::AsBeforeOptions(Expr::Value(
4302-
(Value::DollarQuotedString(DollarQuotedString {value: "\nBEGIN\n IF int1 <> 0 THEN\n RETURN TRUE;\n ELSE\n RETURN FALSE;\n END IF;\nEND;\n".to_owned(), tag: None})).with_empty_span()
4303-
))),
4304+
function_body: Some(CreateFunctionBody::AsBeforeOptions {
4305+
body: Expr::Value(
4306+
(Value::DollarQuotedString(DollarQuotedString {value: "\nBEGIN\n IF int1 <> 0 THEN\n RETURN TRUE;\n ELSE\n RETURN FALSE;\n END IF;\nEND;\n".to_owned(), tag: None})).with_empty_span()
4307+
),
4308+
link_symbol: None,
4309+
}),
43044310
if_not_exists: false,
43054311
using: None,
43064312
determinism_specifier: None,
@@ -4340,9 +4346,12 @@ $$"#;
43404346
behavior: None,
43414347
called_on_null: None,
43424348
parallel: None,
4343-
function_body: Some(CreateFunctionBody::AsBeforeOptions(Expr::Value(
4344-
(Value::DollarQuotedString(DollarQuotedString {value: "\nBEGIN\n IF a <> b THEN\n RETURN TRUE;\n ELSE\n RETURN FALSE;\n END IF;\nEND;\n".to_owned(), tag: None})).with_empty_span()
4345-
))),
4349+
function_body: Some(CreateFunctionBody::AsBeforeOptions {
4350+
body: Expr::Value(
4351+
(Value::DollarQuotedString(DollarQuotedString {value: "\nBEGIN\n IF a <> b THEN\n RETURN TRUE;\n ELSE\n RETURN FALSE;\n END IF;\nEND;\n".to_owned(), tag: None})).with_empty_span()
4352+
),
4353+
link_symbol: None,
4354+
}),
43464355
if_not_exists: false,
43474356
using: None,
43484357
determinism_specifier: None,
@@ -4382,9 +4391,12 @@ $$"#;
43824391
behavior: None,
43834392
called_on_null: None,
43844393
parallel: None,
4385-
function_body: Some(CreateFunctionBody::AsBeforeOptions(Expr::Value(
4386-
(Value::DollarQuotedString(DollarQuotedString {value: "\nBEGIN\n IF int1 <> int2 THEN\n RETURN TRUE;\n ELSE\n RETURN FALSE;\n END IF;\nEND;\n".to_owned(), tag: None})).with_empty_span()
4387-
))),
4394+
function_body: Some(CreateFunctionBody::AsBeforeOptions {
4395+
body: Expr::Value(
4396+
(Value::DollarQuotedString(DollarQuotedString {value: "\nBEGIN\n IF int1 <> int2 THEN\n RETURN TRUE;\n ELSE\n RETURN FALSE;\n END IF;\nEND;\n".to_owned(), tag: None})).with_empty_span()
4397+
),
4398+
link_symbol: None,
4399+
}),
43884400
if_not_exists: false,
43894401
using: None,
43904402
determinism_specifier: None,
@@ -4417,13 +4429,16 @@ $$"#;
44174429
behavior: None,
44184430
called_on_null: None,
44194431
parallel: None,
4420-
function_body: Some(CreateFunctionBody::AsBeforeOptions(Expr::Value(
4421-
(Value::DollarQuotedString(DollarQuotedString {
4422-
value: "\n BEGIN\n RETURN TRUE;\n END;\n ".to_owned(),
4423-
tag: None
4424-
}))
4425-
.with_empty_span()
4426-
))),
4432+
function_body: Some(CreateFunctionBody::AsBeforeOptions {
4433+
body: Expr::Value(
4434+
(Value::DollarQuotedString(DollarQuotedString {
4435+
value: "\n BEGIN\n RETURN TRUE;\n END;\n ".to_owned(),
4436+
tag: None
4437+
}))
4438+
.with_empty_span()
4439+
),
4440+
link_symbol: None,
4441+
}),
44274442
if_not_exists: false,
44284443
using: None,
44294444
determinism_specifier: None,
@@ -4455,9 +4470,12 @@ fn parse_create_function() {
44554470
behavior: Some(FunctionBehavior::Immutable),
44564471
called_on_null: Some(FunctionCalledOnNull::Strict),
44574472
parallel: Some(FunctionParallel::Safe),
4458-
function_body: Some(CreateFunctionBody::AsBeforeOptions(Expr::Value(
4459-
(Value::SingleQuotedString("select $1 + $2;".into())).with_empty_span()
4460-
))),
4473+
function_body: Some(CreateFunctionBody::AsBeforeOptions {
4474+
body: Expr::Value(
4475+
(Value::SingleQuotedString("select $1 + $2;".into())).with_empty_span()
4476+
),
4477+
link_symbol: None,
4478+
}),
44614479
if_not_exists: false,
44624480
using: None,
44634481
determinism_specifier: None,
@@ -4488,6 +4506,52 @@ fn parse_incorrect_create_function_parallel() {
44884506
assert!(pg().parse_sql_statements(sql).is_err());
44894507
}
44904508

4509+
#[test]
4510+
fn parse_create_function_c_with_module_pathname() {
4511+
let sql = "CREATE FUNCTION cas_in(input cstring) RETURNS cas LANGUAGE c IMMUTABLE PARALLEL SAFE AS 'MODULE_PATHNAME', 'cas_in_wrapper'";
4512+
assert_eq!(
4513+
pg_and_generic().verified_stmt(sql),
4514+
Statement::CreateFunction(CreateFunction {
4515+
or_alter: false,
4516+
or_replace: false,
4517+
temporary: false,
4518+
name: ObjectName::from(vec![Ident::new("cas_in")]),
4519+
args: Some(vec![OperateFunctionArg::with_name(
4520+
"input",
4521+
DataType::Custom(ObjectName::from(vec![Ident::new("cstring")]), vec![]),
4522+
),]),
4523+
return_type: Some(DataType::Custom(
4524+
ObjectName::from(vec![Ident::new("cas")]),
4525+
vec![]
4526+
)),
4527+
language: Some("c".into()),
4528+
behavior: Some(FunctionBehavior::Immutable),
4529+
called_on_null: None,
4530+
parallel: Some(FunctionParallel::Safe),
4531+
function_body: Some(CreateFunctionBody::AsBeforeOptions {
4532+
body: Expr::Value(
4533+
(Value::SingleQuotedString("MODULE_PATHNAME".into())).with_empty_span()
4534+
),
4535+
link_symbol: Some(Expr::Value(
4536+
(Value::SingleQuotedString("cas_in_wrapper".into())).with_empty_span()
4537+
)),
4538+
}),
4539+
if_not_exists: false,
4540+
using: None,
4541+
determinism_specifier: None,
4542+
options: None,
4543+
remote_connection: None,
4544+
})
4545+
);
4546+
4547+
// Test that attribute order flexibility works (IMMUTABLE before LANGUAGE)
4548+
let sql_alt_order = "CREATE FUNCTION cas_in(input cstring) RETURNS cas IMMUTABLE PARALLEL SAFE LANGUAGE c AS 'MODULE_PATHNAME', 'cas_in_wrapper'";
4549+
pg_and_generic().one_statement_parses_to(
4550+
sql_alt_order,
4551+
"CREATE FUNCTION cas_in(input cstring) RETURNS cas LANGUAGE c IMMUTABLE PARALLEL SAFE AS 'MODULE_PATHNAME', 'cas_in_wrapper'"
4552+
);
4553+
}
4554+
44914555
#[test]
44924556
fn parse_drop_function() {
44934557
let sql = "DROP FUNCTION IF EXISTS test_func";
@@ -6070,8 +6134,8 @@ fn parse_trigger_related_functions() {
60706134
args: Some(vec![]),
60716135
return_type: Some(DataType::Trigger),
60726136
function_body: Some(
6073-
CreateFunctionBody::AsBeforeOptions(
6074-
Expr::Value((
6137+
CreateFunctionBody::AsBeforeOptions {
6138+
body: Expr::Value((
60756139
Value::DollarQuotedString(
60766140
DollarQuotedString {
60776141
value: "\n BEGIN\n -- Check that empname and salary are given\n IF NEW.empname IS NULL THEN\n RAISE EXCEPTION 'empname cannot be null';\n END IF;\n IF NEW.salary IS NULL THEN\n RAISE EXCEPTION '% cannot have null salary', NEW.empname;\n END IF;\n\n -- Who works for us when they must pay for it?\n IF NEW.salary < 0 THEN\n RAISE EXCEPTION '% cannot have a negative salary', NEW.empname;\n END IF;\n\n -- Remember who changed the payroll when\n NEW.last_date := current_timestamp;\n NEW.last_user := current_user;\n RETURN NEW;\n END;\n ".to_owned(),
@@ -6081,7 +6145,8 @@ fn parse_trigger_related_functions() {
60816145
},
60826146
)
60836147
).with_empty_span()),
6084-
),
6148+
link_symbol: None,
6149+
},
60856150
),
60866151
behavior: None,
60876152
called_on_null: None,

0 commit comments

Comments
 (0)