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
1 change: 1 addition & 0 deletions fuzz/fuzz_seeds/postgres_create_procedure_string_body.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
CREATE PROCEDURE p (a INT) LANGUAGE plpgsql AS $$ BEGIN INSERT INTO t VALUES (a); END; $$
16 changes: 16 additions & 0 deletions src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5633,6 +5633,13 @@ impl fmt::Display for Statement {

match body {
CreateProcedureBody::As(body) => write!(f, " AS {body}"),
CreateProcedureBody::AsString { body, link_symbol } => {
write!(f, " AS {body}")?;
if let Some(link_symbol) = link_symbol {
write!(f, ", {link_symbol}")?;
}
Ok(())
}
CreateProcedureBody::BeginAtomic(BeginEndStatements { statements, .. }) => {
format_begin_atomic(f, statements)
}
Expand Down Expand Up @@ -10611,6 +10618,15 @@ pub enum CreateFunctionBody {
pub enum CreateProcedureBody {
/// Statements after `AS`.
As(ConditionalStatements),
/// A definition string after `AS`, e.g. `AS $$ body $$` or `AS 'obj_file', 'link_symbol'`.
///
/// [PostgreSQL]: https://www.postgresql.org/docs/current/sql-createprocedure.html
AsString {
/// The definition.
body: Expr,
/// The symbol to link when the definition names a shared library file.
link_symbol: Option<Expr>,
},
/// SQL-standard body enclosed by `BEGIN ATOMIC` and `END`.
///
/// [PostgreSQL]: https://www.postgresql.org/docs/current/sql-createprocedure.html
Expand Down
40 changes: 30 additions & 10 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13515,6 +13515,13 @@ 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<CreateFunctionBody, ParserError> {
let (body, link_symbol) = self.parse_definition_string()?;
Ok(CreateFunctionBody::AsBeforeOptions { body, link_symbol })
}

/// Parse a definition string and the link symbol that may follow it,
/// e.g. `$$ body $$` or `'obj_file', 'link_symbol'`.
fn parse_definition_string(&mut self) -> Result<(Expr, Option<Expr>), ParserError> {
let parse_string_expr = |parser: &mut Parser| -> Result<Expr, ParserError> {
let peek_token = parser.peek_token();
let span = peek_token.span;
Expand All @@ -13530,14 +13537,13 @@ impl<'a> Parser<'a> {
}
};

Ok(CreateFunctionBody::AsBeforeOptions {
body: parse_string_expr(self)?,
link_symbol: if self.consume_token(&Token::Comma) {
Some(parse_string_expr(self)?)
} else {
None
},
})
let body = parse_string_expr(self)?;
let link_symbol = if self.consume_token(&Token::Comma) {
Some(parse_string_expr(self)?)
} else {
None
};
Ok((body, link_symbol))
}

/// Parse a literal string
Expand Down Expand Up @@ -22010,7 +22016,7 @@ impl<'a> Parser<'a> {
let name = self.parse_object_name(false)?;
let params = self.parse_optional_procedure_parameters()?;

let language = if self.parse_keyword(Keyword::LANGUAGE) {
let mut language = if self.parse_keyword(Keyword::LANGUAGE) {
Some(self.parse_identifier()?)
} else {
None
Expand All @@ -22020,7 +22026,21 @@ impl<'a> Parser<'a> {
Some(statements) => CreateProcedureBody::BeginAtomic(statements),
None => {
self.expect_keyword_is(Keyword::AS)?;
CreateProcedureBody::As(self.parse_conditional_statements(&[Keyword::END])?)
if matches!(
self.peek_token_ref().token,
Token::SingleQuotedString(_)
| Token::DollarQuotedString(_)
| Token::EscapedStringLiteral(_)
| Token::UnicodeStringLiteral(_)
) {
let (body, link_symbol) = self.parse_definition_string()?;
if language.is_none() && self.parse_keyword(Keyword::LANGUAGE) {
language = Some(self.parse_identifier()?);
}
CreateProcedureBody::AsString { body, link_symbol }
} else {
CreateProcedureBody::As(self.parse_conditional_statements(&[Keyword::END])?)
}
}
};

Expand Down
63 changes: 63 additions & 0 deletions tests/sqlparser_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23516,3 +23516,66 @@ fn parse_national_character_string_types() {
ParserError::ParserError("Expected: ), found: EOF".to_string())
);
}

#[test]
fn parse_create_procedure_string_body() {
match verified_stmt("CREATE PROCEDURE p (a INT) LANGUAGE sql AS 'INSERT INTO t VALUES (a)'") {
Statement::CreateProcedure {
language,
body: CreateProcedureBody::AsString { body, link_symbol },
..
} => {
assert_eq!(language, Some(Ident::new("sql")));
assert_eq!(
body,
Expr::Value(
Value::SingleQuotedString("INSERT INTO t VALUES (a)".to_string())
.with_empty_span()
)
);
assert_eq!(link_symbol, None);
}
stmt => panic!("unexpected statement: {stmt:?}"),
}

verified_stmt("CREATE PROCEDURE p () LANGUAGE C AS 'MODULE_PATHNAME', 'p_wrapper'");
one_statement_parses_to(
"CREATE PROCEDURE p () AS 'SELECT 1' LANGUAGE sql",
"CREATE PROCEDURE p () LANGUAGE sql AS 'SELECT 1'",
);

pg_and_generic().verified_stmt(
"CREATE PROCEDURE p (a INT) LANGUAGE plpgsql AS $$ BEGIN INSERT INTO t VALUES (a); END; $$",
);
pg_and_generic().verified_stmt("CREATE PROCEDURE p () LANGUAGE sql AS $body$ SELECT 1 $body$");
Comment thread
MrBisonte marked this conversation as resolved.
Resolved
pg_and_generic().one_statement_parses_to(
"CREATE PROCEDURE p () LANGUAGE sql AS E'SELECT 1'",
"CREATE PROCEDURE p () LANGUAGE sql AS 'SELECT 1'",
);
pg_and_generic().one_statement_parses_to(
r#"CREATE PROCEDURE insert_data(a integer, b integer)
LANGUAGE SQL
AS $$
INSERT INTO tbl VALUES (a);
INSERT INTO tbl VALUES (b);
$$"#,
"CREATE PROCEDURE insert_data (a INTEGER, b INTEGER) LANGUAGE SQL AS $$\nINSERT INTO tbl VALUES (a);\nINSERT INTO tbl VALUES (b);\n$$",
);

for (sql, err) in [
(
"CREATE PROCEDURE p () LANGUAGE sql AS 'SELECT 1' LANGUAGE sql",
"Expected: end of statement, found: LANGUAGE",
),
(
"CREATE PROCEDURE p () LANGUAGE C AS 'MODULE_PATHNAME',",
"Expected: literal string, found: EOF",
),
] {
assert_eq!(
parse_sql_statements(sql).unwrap_err(),
ParserError::ParserError(err.to_string()),
"{sql}"
);
}
}
Loading