mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-12-23 11:12:51 +00:00
Support PostgreSQL C Functions with Multiple AS Parameters (#2095)
Some checks failed
license / Release Audit Tool (RAT) (push) Has been cancelled
Rust / codestyle (push) Has been cancelled
Rust / lint (push) Has been cancelled
Rust / benchmark-lint (push) Has been cancelled
Rust / compile (push) Has been cancelled
Rust / docs (push) Has been cancelled
Rust / test (stable) (push) Has been cancelled
Rust / compile-no-std (push) Has been cancelled
Rust / test (beta) (push) Has been cancelled
Rust / test (nightly) (push) Has been cancelled
Some checks failed
license / Release Audit Tool (RAT) (push) Has been cancelled
Rust / codestyle (push) Has been cancelled
Rust / lint (push) Has been cancelled
Rust / benchmark-lint (push) Has been cancelled
Rust / compile (push) Has been cancelled
Rust / docs (push) Has been cancelled
Rust / test (stable) (push) Has been cancelled
Rust / compile-no-std (push) Has been cancelled
Rust / test (beta) (push) Has been cancelled
Rust / test (nightly) (push) Has been cancelled
Co-authored-by: Ifeanyi Ubah <ify1992@yahoo.com>
This commit is contained in:
parent
e380494eb0
commit
4beea9a4bc
5 changed files with 147 additions and 50 deletions
|
|
@ -3099,8 +3099,12 @@ impl fmt::Display for CreateFunction {
|
|||
if let Some(remote_connection) = &self.remote_connection {
|
||||
write!(f, " REMOTE WITH CONNECTION {remote_connection}")?;
|
||||
}
|
||||
if let Some(CreateFunctionBody::AsBeforeOptions(function_body)) = &self.function_body {
|
||||
write!(f, " AS {function_body}")?;
|
||||
if let Some(CreateFunctionBody::AsBeforeOptions { body, link_symbol }) = &self.function_body
|
||||
{
|
||||
write!(f, " AS {body}")?;
|
||||
if let Some(link_symbol) = link_symbol {
|
||||
write!(f, ", {link_symbol}")?;
|
||||
}
|
||||
}
|
||||
if let Some(CreateFunctionBody::Return(function_body)) = &self.function_body {
|
||||
write!(f, " RETURN {function_body}")?;
|
||||
|
|
|
|||
|
|
@ -9108,7 +9108,20 @@ pub enum CreateFunctionBody {
|
|||
/// ```
|
||||
///
|
||||
/// [BigQuery]: https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#syntax_11
|
||||
AsBeforeOptions(Expr),
|
||||
/// [PostgreSQL]: https://www.postgresql.org/docs/current/sql-createfunction.html
|
||||
AsBeforeOptions {
|
||||
/// The primary expression.
|
||||
body: Expr,
|
||||
/// Link symbol if the primary expression contains the name of shared library file.
|
||||
///
|
||||
/// Example:
|
||||
/// ```sql
|
||||
/// CREATE FUNCTION cas_in(input cstring) RETURNS cas
|
||||
/// AS 'MODULE_PATHNAME', 'cas_in_wrapper'
|
||||
/// ```
|
||||
/// [PostgreSQL]: https://www.postgresql.org/docs/current/sql-createfunction.html
|
||||
link_symbol: Option<Expr>,
|
||||
},
|
||||
/// A function body expression using the 'AS' keyword and shows up
|
||||
/// after any `OPTIONS` clause.
|
||||
///
|
||||
|
|
|
|||
|
|
@ -5204,9 +5204,7 @@ impl<'a> Parser<'a> {
|
|||
}
|
||||
if self.parse_keyword(Keyword::AS) {
|
||||
ensure_not_set(&body.function_body, "AS")?;
|
||||
body.function_body = Some(CreateFunctionBody::AsBeforeOptions(
|
||||
self.parse_create_function_body_string()?,
|
||||
));
|
||||
body.function_body = Some(self.parse_create_function_body_string()?);
|
||||
} else if self.parse_keyword(Keyword::LANGUAGE) {
|
||||
ensure_not_set(&body.language, "LANGUAGE")?;
|
||||
body.language = Some(self.parse_identifier()?);
|
||||
|
|
@ -5298,7 +5296,7 @@ impl<'a> Parser<'a> {
|
|||
let name = self.parse_object_name(false)?;
|
||||
self.expect_keyword_is(Keyword::AS)?;
|
||||
|
||||
let as_ = self.parse_create_function_body_string()?;
|
||||
let body = self.parse_create_function_body_string()?;
|
||||
let using = self.parse_optional_create_function_using()?;
|
||||
|
||||
Ok(Statement::CreateFunction(CreateFunction {
|
||||
|
|
@ -5306,7 +5304,7 @@ impl<'a> Parser<'a> {
|
|||
or_replace,
|
||||
temporary,
|
||||
name,
|
||||
function_body: Some(CreateFunctionBody::AsBeforeOptions(as_)),
|
||||
function_body: Some(body),
|
||||
using,
|
||||
if_not_exists: false,
|
||||
args: None,
|
||||
|
|
@ -5368,7 +5366,10 @@ impl<'a> Parser<'a> {
|
|||
let expr = self.parse_expr()?;
|
||||
if options.is_none() {
|
||||
options = self.maybe_parse_options(Keyword::OPTIONS)?;
|
||||
Some(CreateFunctionBody::AsBeforeOptions(expr))
|
||||
Some(CreateFunctionBody::AsBeforeOptions {
|
||||
body: expr,
|
||||
link_symbol: None,
|
||||
})
|
||||
} else {
|
||||
Some(CreateFunctionBody::AsAfterOptions(expr))
|
||||
}
|
||||
|
|
@ -10574,19 +10575,30 @@ 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)))
|
||||
fn parse_create_function_body_string(&mut self) -> Result<CreateFunctionBody, ParserError> {
|
||||
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),
|
||||
)),
|
||||
}
|
||||
};
|
||||
|
||||
Ok(CreateFunctionBody::AsBeforeOptions {
|
||||
body: parse_string_expr(self)?,
|
||||
link_symbol: if self.consume_token(&Token::Comma) {
|
||||
Some(parse_string_expr(self)?)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/// Parse a literal string
|
||||
|
|
|
|||
|
|
@ -408,10 +408,13 @@ fn parse_create_function() {
|
|||
assert_eq!(name.to_string(), "mydb.myfunc");
|
||||
assert_eq!(
|
||||
function_body,
|
||||
Some(CreateFunctionBody::AsBeforeOptions(Expr::Value(
|
||||
(Value::SingleQuotedString("org.random.class.Name".to_string()))
|
||||
.with_empty_span()
|
||||
)))
|
||||
Some(CreateFunctionBody::AsBeforeOptions {
|
||||
body: Expr::Value(
|
||||
(Value::SingleQuotedString("org.random.class.Name".to_string()))
|
||||
.with_empty_span()
|
||||
),
|
||||
link_symbol: None,
|
||||
})
|
||||
);
|
||||
assert_eq!(
|
||||
using,
|
||||
|
|
|
|||
|
|
@ -4260,9 +4260,12 @@ $$"#;
|
|||
behavior: None,
|
||||
called_on_null: None,
|
||||
parallel: None,
|
||||
function_body: Some(CreateFunctionBody::AsBeforeOptions(Expr::Value(
|
||||
(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()
|
||||
))),
|
||||
function_body: Some(CreateFunctionBody::AsBeforeOptions {
|
||||
body: Expr::Value(
|
||||
(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()
|
||||
),
|
||||
link_symbol: None,
|
||||
}),
|
||||
if_not_exists: false,
|
||||
using: None,
|
||||
determinism_specifier: None,
|
||||
|
|
@ -4298,9 +4301,12 @@ $$"#;
|
|||
behavior: None,
|
||||
called_on_null: None,
|
||||
parallel: None,
|
||||
function_body: Some(CreateFunctionBody::AsBeforeOptions(Expr::Value(
|
||||
(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()
|
||||
))),
|
||||
function_body: Some(CreateFunctionBody::AsBeforeOptions {
|
||||
body: Expr::Value(
|
||||
(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()
|
||||
),
|
||||
link_symbol: None,
|
||||
}),
|
||||
if_not_exists: false,
|
||||
using: None,
|
||||
determinism_specifier: None,
|
||||
|
|
@ -4340,9 +4346,12 @@ $$"#;
|
|||
behavior: None,
|
||||
called_on_null: None,
|
||||
parallel: None,
|
||||
function_body: Some(CreateFunctionBody::AsBeforeOptions(Expr::Value(
|
||||
(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()
|
||||
))),
|
||||
function_body: Some(CreateFunctionBody::AsBeforeOptions {
|
||||
body: Expr::Value(
|
||||
(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()
|
||||
),
|
||||
link_symbol: None,
|
||||
}),
|
||||
if_not_exists: false,
|
||||
using: None,
|
||||
determinism_specifier: None,
|
||||
|
|
@ -4382,9 +4391,12 @@ $$"#;
|
|||
behavior: None,
|
||||
called_on_null: None,
|
||||
parallel: None,
|
||||
function_body: Some(CreateFunctionBody::AsBeforeOptions(Expr::Value(
|
||||
(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()
|
||||
))),
|
||||
function_body: Some(CreateFunctionBody::AsBeforeOptions {
|
||||
body: Expr::Value(
|
||||
(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()
|
||||
),
|
||||
link_symbol: None,
|
||||
}),
|
||||
if_not_exists: false,
|
||||
using: None,
|
||||
determinism_specifier: None,
|
||||
|
|
@ -4417,13 +4429,16 @@ $$"#;
|
|||
behavior: None,
|
||||
called_on_null: None,
|
||||
parallel: None,
|
||||
function_body: Some(CreateFunctionBody::AsBeforeOptions(Expr::Value(
|
||||
(Value::DollarQuotedString(DollarQuotedString {
|
||||
value: "\n BEGIN\n RETURN TRUE;\n END;\n ".to_owned(),
|
||||
tag: None
|
||||
}))
|
||||
.with_empty_span()
|
||||
))),
|
||||
function_body: Some(CreateFunctionBody::AsBeforeOptions {
|
||||
body: Expr::Value(
|
||||
(Value::DollarQuotedString(DollarQuotedString {
|
||||
value: "\n BEGIN\n RETURN TRUE;\n END;\n ".to_owned(),
|
||||
tag: None
|
||||
}))
|
||||
.with_empty_span()
|
||||
),
|
||||
link_symbol: None,
|
||||
}),
|
||||
if_not_exists: false,
|
||||
using: None,
|
||||
determinism_specifier: None,
|
||||
|
|
@ -4455,9 +4470,12 @@ fn parse_create_function() {
|
|||
behavior: Some(FunctionBehavior::Immutable),
|
||||
called_on_null: Some(FunctionCalledOnNull::Strict),
|
||||
parallel: Some(FunctionParallel::Safe),
|
||||
function_body: Some(CreateFunctionBody::AsBeforeOptions(Expr::Value(
|
||||
(Value::SingleQuotedString("select $1 + $2;".into())).with_empty_span()
|
||||
))),
|
||||
function_body: Some(CreateFunctionBody::AsBeforeOptions {
|
||||
body: Expr::Value(
|
||||
(Value::SingleQuotedString("select $1 + $2;".into())).with_empty_span()
|
||||
),
|
||||
link_symbol: None,
|
||||
}),
|
||||
if_not_exists: false,
|
||||
using: None,
|
||||
determinism_specifier: None,
|
||||
|
|
@ -4488,6 +4506,52 @@ 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 {
|
||||
body: Expr::Value(
|
||||
(Value::SingleQuotedString("MODULE_PATHNAME".into())).with_empty_span()
|
||||
),
|
||||
link_symbol: Some(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";
|
||||
|
|
@ -6070,8 +6134,8 @@ fn parse_trigger_related_functions() {
|
|||
args: Some(vec![]),
|
||||
return_type: Some(DataType::Trigger),
|
||||
function_body: Some(
|
||||
CreateFunctionBody::AsBeforeOptions(
|
||||
Expr::Value((
|
||||
CreateFunctionBody::AsBeforeOptions {
|
||||
body: Expr::Value((
|
||||
Value::DollarQuotedString(
|
||||
DollarQuotedString {
|
||||
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() {
|
|||
},
|
||||
)
|
||||
).with_empty_span()),
|
||||
),
|
||||
link_symbol: None,
|
||||
},
|
||||
),
|
||||
behavior: None,
|
||||
called_on_null: None,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue