Skip to content

Commit 50813ee

Browse files
PostgreSQL: Support BEGIN ATOMIC function bodies (#2728)
1 parent b22bf61 commit 50813ee

5 files changed

Lines changed: 125 additions & 9 deletions

File tree

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
CREATE FUNCTION f(a INT) RETURNS INT LANGUAGE sql BEGIN ATOMIC INSERT INTO t VALUES (a); RETURN a + 1; END

‎src/ast/ddl.rs‎

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,19 +36,19 @@ use sqlparser_derive::{Visit, VisitMut};
3636

3737
use crate::ast::value::escape_single_quote_string;
3838
use crate::ast::{
39-
display_comma_separated, display_separated,
39+
display_comma_separated, display_separated, format_statement_list,
4040
table_constraints::{
4141
CheckConstraint, ForeignKeyConstraint, PrimaryKeyConstraint, TableConstraint,
4242
UniqueConstraint,
4343
},
44-
ArgMode, AttachedToken, CommentDef, ConditionalStatements, CreateFunctionBody,
45-
CreateFunctionUsing, CreateServerOption, CreateTableLikeKind, CreateTableOptions,
46-
CreateViewParams, DataType, Expr, FileFormat, FunctionBehavior, FunctionCalledOnNull,
47-
FunctionDefinitionSetParam, FunctionDesc, FunctionDeterminismSpecifier, FunctionParallel,
48-
FunctionSecurity, HiveDistributionStyle, HiveFormat, HiveIOFormat, HiveRowFormat,
49-
HiveSetLocation, Ident, InitializeKind, MySQLColumnPosition, ObjectName, OnCommit,
50-
OneOrManyWithParens, OperateFunctionArg, OrderByExpr, ProjectionSelect, Query, RefreshModeKind,
51-
ResetConfig, RowAccessPolicy, SequenceOptions, Spanned, SqlOption, Statement,
44+
ArgMode, AttachedToken, BeginEndStatements, CommentDef, ConditionalStatements,
45+
CreateFunctionBody, CreateFunctionUsing, CreateServerOption, CreateTableLikeKind,
46+
CreateTableOptions, CreateViewParams, DataType, Expr, FileFormat, FunctionBehavior,
47+
FunctionCalledOnNull, FunctionDefinitionSetParam, FunctionDesc, FunctionDeterminismSpecifier,
48+
FunctionParallel, FunctionSecurity, HiveDistributionStyle, HiveFormat, HiveIOFormat,
49+
HiveRowFormat, HiveSetLocation, Ident, InitializeKind, MySQLColumnPosition, ObjectName,
50+
OnCommit, OneOrManyWithParens, OperateFunctionArg, OrderByExpr, ProjectionSelect, Query,
51+
RefreshModeKind, ResetConfig, RowAccessPolicy, SequenceOptions, Spanned, SqlOption, Statement,
5252
StorageLifecyclePolicy, StorageSerializationPolicy, TableVersion, Tag, TriggerEvent,
5353
TriggerExecBody, TriggerObject, TriggerPeriod, TriggerReferencing, Value, ValueWithSpan,
5454
WrappedCollection,
@@ -3882,6 +3882,16 @@ impl fmt::Display for CreateFunction {
38823882
if let Some(CreateFunctionBody::Return(function_body)) = &self.function_body {
38833883
write!(f, " RETURN {function_body}")?;
38843884
}
3885+
if let Some(CreateFunctionBody::BeginAtomic(BeginEndStatements { statements, .. })) =
3886+
&self.function_body
3887+
{
3888+
write!(f, " BEGIN ATOMIC")?;
3889+
if !statements.is_empty() {
3890+
write!(f, " ")?;
3891+
format_statement_list(f, statements)?;
3892+
}
3893+
write!(f, " END")?;
3894+
}
38853895
if let Some(CreateFunctionBody::AsReturnExpr(function_body)) = &self.function_body {
38863896
write!(f, " AS RETURN {function_body}")?;
38873897
}

‎src/ast/mod.rs‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10506,6 +10506,10 @@ pub enum CreateFunctionBody {
1050610506
///
1050710507
/// [PostgreSQL]: https://www.postgresql.org/docs/current/sql-createfunction.html
1050810508
Return(Expr),
10509+
/// SQL-standard function body enclosed by `BEGIN ATOMIC` and `END`.
10510+
///
10511+
/// [PostgreSQL]: https://www.postgresql.org/docs/current/sql-createfunction.html
10512+
BeginAtomic(BeginEndStatements),
1050910513

1051010514
/// Function body expression using the 'AS RETURN' keywords
1051110515
///

‎src/parser/mod.rs‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6185,6 +6185,17 @@ impl<'a> Parser<'a> {
61856185
} else if self.parse_keyword(Keyword::RETURN) {
61866186
ensure_not_set(&body.function_body, "RETURN")?;
61876187
body.function_body = Some(CreateFunctionBody::Return(self.parse_expr()?));
6188+
} else if self.peek_keywords(&[Keyword::BEGIN, Keyword::ATOMIC]) {
6189+
ensure_not_set(&body.function_body, "BEGIN ATOMIC")?;
6190+
let begin_token = self.next_token();
6191+
self.advance_token();
6192+
let statements = self.parse_statement_list(&[Keyword::END])?;
6193+
let end_token = self.expect_keyword(Keyword::END)?;
6194+
body.function_body = Some(CreateFunctionBody::BeginAtomic(BeginEndStatements {
6195+
begin_token: AttachedToken(begin_token),
6196+
statements,
6197+
end_token: AttachedToken(end_token),
6198+
}));
61886199
} else {
61896200
break;
61906201
}

‎tests/sqlparser_postgres.rs‎

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11405,3 +11405,93 @@ fn parse_non_decimal_integer_token_with_invalid_digits() {
1140511405
ParserError::ParserError("Could not parse '0xZZ' as an integer".to_string())
1140611406
);
1140711407
}
11408+
11409+
#[test]
11410+
fn parse_create_function_begin_atomic() {
11411+
let sql = "CREATE FUNCTION f() RETURNS INT LANGUAGE sql BEGIN ATOMIC SELECT 1; END";
11412+
pg_and_generic().verified_stmt(sql);
11413+
match pg_and_generic().run_parser_method(sql, |parser| parser.parse_statement().unwrap()) {
11414+
Statement::CreateFunction(CreateFunction {
11415+
function_body: Some(CreateFunctionBody::BeginAtomic(body)),
11416+
..
11417+
}) => {
11418+
assert_eq!(
11419+
body.statements,
11420+
vec![pg_and_generic().verified_stmt("SELECT 1")]
11421+
);
11422+
assert_eq!(
11423+
body.span(),
11424+
Span::new(Location::new(1, 46), Location::new(1, 72))
11425+
);
11426+
}
11427+
stmt => panic!("unexpected statement: {stmt:?}"),
11428+
}
11429+
11430+
for sql in [
11431+
"CREATE FUNCTION f(a INT) RETURNS INT LANGUAGE sql IMMUTABLE BEGIN ATOMIC SELECT a; RETURN a + 1; END",
11432+
"CREATE OR REPLACE FUNCTION f(a INT) RETURNS INT LANGUAGE sql BEGIN ATOMIC SELECT CASE WHEN a > 0 THEN 1 END; END",
11433+
"CREATE FUNCTION f() RETURNS VOID LANGUAGE sql BEGIN ATOMIC END",
11434+
] {
11435+
pg_and_generic().verified_stmt(sql);
11436+
}
11437+
11438+
pg_and_generic().one_statement_parses_to(
11439+
r#"CREATE OR REPLACE FUNCTION public.f(a integer)
11440+
RETURNS integer
11441+
LANGUAGE sql
11442+
BEGIN ATOMIC
11443+
INSERT INTO t VALUES (a);
11444+
RETURN a;
11445+
END"#,
11446+
"CREATE OR REPLACE FUNCTION public.f(a INTEGER) RETURNS INTEGER LANGUAGE sql BEGIN ATOMIC INSERT INTO t VALUES (a); RETURN a; END",
11447+
);
11448+
11449+
for (sql, err) in [
11450+
(
11451+
"CREATE FUNCTION f() RETURNS INT LANGUAGE sql BEGIN ATOMIC SELECT 1;",
11452+
"Expected: END, found: EOF",
11453+
),
11454+
(
11455+
"CREATE FUNCTION f() RETURNS INT LANGUAGE sql BEGIN ATOMIC SELECT 1 END",
11456+
"Expected: ;, found: END",
11457+
),
11458+
(
11459+
"CREATE FUNCTION f() RETURNS INT LANGUAGE sql RETURN 1 BEGIN ATOMIC SELECT 1; END",
11460+
"BEGIN ATOMIC specified more than once",
11461+
),
11462+
(
11463+
"CREATE FUNCTION f() RETURNS INT LANGUAGE sql BEGIN SELECT 1; END",
11464+
"Expected: end of statement, found: BEGIN",
11465+
),
11466+
] {
11467+
assert_eq!(
11468+
pg_and_generic().parse_sql_statements(sql).unwrap_err(),
11469+
ParserError::ParserError(err.to_string()),
11470+
"{sql}"
11471+
);
11472+
}
11473+
}
11474+
11475+
#[test]
11476+
fn display_create_function_begin_atomic_propagates_write_errors() {
11477+
use core::fmt::{self, Write};
11478+
11479+
struct RejectingWriter(&'static str);
11480+
11481+
impl Write for RejectingWriter {
11482+
fn write_str(&mut self, value: &str) -> fmt::Result {
11483+
if value == self.0 {
11484+
Err(fmt::Error)
11485+
} else {
11486+
Ok(())
11487+
}
11488+
}
11489+
}
11490+
11491+
let stmt = pg_and_generic()
11492+
.verified_stmt("CREATE FUNCTION f() RETURNS INT LANGUAGE sql BEGIN ATOMIC SELECT 1; END");
11493+
for rejected in [" BEGIN ATOMIC", " ", ";", " END"] {
11494+
let mut writer = RejectingWriter(rejected);
11495+
assert_eq!(write!(writer, "{stmt}"), Err(fmt::Error), "{rejected:?}");
11496+
}
11497+
}

0 commit comments

Comments
 (0)