Skip to content

Commit dd8382e

Browse files
committed
Allow stored procedures to be defined without BEGIN/END
- formerly, a semicolon after the last statement in a procedure was non-canonical (because they were added via `join`); a `BeginEndStatements` statements list will always write them out - `BeginEndStatements` begin/end tokens won't be written when empty - EOF now concludes parsing a statement list
1 parent 4e392f5 commit dd8382e

File tree

3 files changed

+84
-58
lines changed

3 files changed

+84
-58
lines changed

src/ast/mod.rs

+11-8
Original file line numberDiff line numberDiff line change
@@ -2344,11 +2344,17 @@ impl fmt::Display for BeginEndStatements {
23442344
end_token: AttachedToken(end_token),
23452345
} = self;
23462346

2347-
write!(f, "{begin_token} ")?;
2347+
if begin_token.token != Token::EOF {
2348+
write!(f, "{begin_token} ")?;
2349+
}
23482350
if !statements.is_empty() {
23492351
format_statement_list(f, statements)?;
23502352
}
2351-
write!(f, " {end_token}")
2353+
if end_token.token != Token::EOF {
2354+
write!(f, " {end_token}")?;
2355+
}
2356+
2357+
Ok(())
23522358
}
23532359
}
23542360

@@ -3744,7 +3750,7 @@ pub enum Statement {
37443750
or_alter: bool,
37453751
name: ObjectName,
37463752
params: Option<Vec<ProcedureParam>>,
3747-
body: Vec<Statement>,
3753+
body: BeginEndStatements,
37483754
},
37493755
/// ```sql
37503756
/// CREATE MACRO
@@ -4599,11 +4605,8 @@ impl fmt::Display for Statement {
45994605
write!(f, " ({})", display_comma_separated(p))?;
46004606
}
46014607
}
4602-
write!(
4603-
f,
4604-
" AS BEGIN {body} END",
4605-
body = display_separated(body, "; ")
4606-
)
4608+
4609+
write!(f, " AS {body}")
46074610
}
46084611
Statement::CreateMacro {
46094612
or_replace,

src/parser/mod.rs

+25-7
Original file line numberDiff line numberDiff line change
@@ -4448,10 +4448,14 @@ impl<'a> Parser<'a> {
44484448
) -> Result<Vec<Statement>, ParserError> {
44494449
let mut values = vec![];
44504450
loop {
4451-
if let Token::Word(w) = &self.peek_nth_token_ref(0).token {
4452-
if w.quote_style.is_none() && terminal_keywords.contains(&w.keyword) {
4453-
break;
4451+
match &self.peek_nth_token_ref(0).token {
4452+
Token::EOF => break,
4453+
Token::Word(w) => {
4454+
if w.quote_style.is_none() && terminal_keywords.contains(&w.keyword) {
4455+
break;
4456+
}
44544457
}
4458+
_ => {}
44554459
}
44564460
values.push(self.parse_statement()?);
44574461
self.expect_token(&Token::SemiColon)?;
@@ -15095,14 +15099,28 @@ impl<'a> Parser<'a> {
1509515099
let name = self.parse_object_name(false)?;
1509615100
let params = self.parse_optional_procedure_parameters()?;
1509715101
self.expect_keyword_is(Keyword::AS)?;
15098-
self.expect_keyword_is(Keyword::BEGIN)?;
15099-
let statements = self.parse_statements()?;
15100-
self.expect_keyword_is(Keyword::END)?;
15102+
15103+
let begin_token: AttachedToken = self
15104+
.expect_keyword(Keyword::BEGIN)
15105+
.map(AttachedToken)
15106+
.unwrap_or_else(|_| AttachedToken::empty());
15107+
let statements = self.parse_statement_list(&[Keyword::END])?;
15108+
let end_token = match &begin_token.0.token {
15109+
Token::Word(w) if w.keyword == Keyword::BEGIN => {
15110+
AttachedToken(self.expect_keyword(Keyword::END)?)
15111+
}
15112+
_ => AttachedToken::empty(),
15113+
};
15114+
1510115115
Ok(Statement::CreateProcedure {
1510215116
name,
1510315117
or_alter,
1510415118
params,
15105-
body: statements,
15119+
body: BeginEndStatements {
15120+
begin_token,
15121+
statements,
15122+
end_token,
15123+
},
1510615124
})
1510715125
}
1510815126

tests/sqlparser_mssql.rs

+48-43
Original file line numberDiff line numberDiff line change
@@ -99,47 +99,51 @@ fn parse_mssql_delimited_identifiers() {
9999

100100
#[test]
101101
fn parse_create_procedure() {
102-
let sql = "CREATE OR ALTER PROCEDURE test (@foo INT, @bar VARCHAR(256)) AS BEGIN SELECT 1 END";
102+
let sql = "CREATE OR ALTER PROCEDURE test (@foo INT, @bar VARCHAR(256)) AS BEGIN SELECT 1; END";
103103

104104
assert_eq!(
105105
ms().verified_stmt(sql),
106106
Statement::CreateProcedure {
107107
or_alter: true,
108-
body: vec![Statement::Query(Box::new(Query {
109-
with: None,
110-
limit_clause: None,
111-
fetch: None,
112-
locks: vec![],
113-
for_clause: None,
114-
order_by: None,
115-
settings: None,
116-
format_clause: None,
117-
body: Box::new(SetExpr::Select(Box::new(Select {
118-
select_token: AttachedToken::empty(),
119-
distinct: None,
120-
top: None,
121-
top_before_distinct: false,
122-
projection: vec![SelectItem::UnnamedExpr(Expr::Value(
123-
(number("1")).with_empty_span()
124-
))],
125-
into: None,
126-
from: vec![],
127-
lateral_views: vec![],
128-
prewhere: None,
129-
selection: None,
130-
group_by: GroupByExpr::Expressions(vec![], vec![]),
131-
cluster_by: vec![],
132-
distribute_by: vec![],
133-
sort_by: vec![],
134-
having: None,
135-
named_window: vec![],
136-
window_before_qualify: false,
137-
qualify: None,
138-
value_table_mode: None,
139-
connect_by: None,
140-
flavor: SelectFlavor::Standard,
141-
})))
142-
}))],
108+
body: BeginEndStatements {
109+
begin_token: AttachedToken::empty(),
110+
statements: vec![Statement::Query(Box::new(Query {
111+
with: None,
112+
limit_clause: None,
113+
fetch: None,
114+
locks: vec![],
115+
for_clause: None,
116+
order_by: None,
117+
settings: None,
118+
format_clause: None,
119+
body: Box::new(SetExpr::Select(Box::new(Select {
120+
select_token: AttachedToken::empty(),
121+
distinct: None,
122+
top: None,
123+
top_before_distinct: false,
124+
projection: vec![SelectItem::UnnamedExpr(Expr::Value(
125+
(number("1")).with_empty_span()
126+
))],
127+
into: None,
128+
from: vec![],
129+
lateral_views: vec![],
130+
prewhere: None,
131+
selection: None,
132+
group_by: GroupByExpr::Expressions(vec![], vec![]),
133+
cluster_by: vec![],
134+
distribute_by: vec![],
135+
sort_by: vec![],
136+
having: None,
137+
named_window: vec![],
138+
window_before_qualify: false,
139+
qualify: None,
140+
value_table_mode: None,
141+
connect_by: None,
142+
flavor: SelectFlavor::Standard,
143+
})))
144+
}))],
145+
end_token: AttachedToken::empty(),
146+
},
143147
params: Some(vec![
144148
ProcedureParam {
145149
name: Ident {
@@ -172,19 +176,20 @@ fn parse_create_procedure() {
172176

173177
#[test]
174178
fn parse_mssql_create_procedure() {
175-
let _ = ms_and_generic().verified_stmt("CREATE OR ALTER PROCEDURE foo AS BEGIN SELECT 1 END");
176-
let _ = ms_and_generic().verified_stmt("CREATE PROCEDURE foo AS BEGIN SELECT 1 END");
179+
let _ = ms_and_generic().verified_stmt("CREATE OR ALTER PROCEDURE foo AS SELECT 1;");
180+
let _ = ms_and_generic().verified_stmt("CREATE OR ALTER PROCEDURE foo AS BEGIN SELECT 1; END");
181+
let _ = ms_and_generic().verified_stmt("CREATE PROCEDURE foo AS BEGIN SELECT 1; END");
177182
let _ = ms().verified_stmt(
178-
"CREATE PROCEDURE foo AS BEGIN SELECT [myColumn] FROM [myschema].[mytable] END",
183+
"CREATE PROCEDURE foo AS BEGIN SELECT [myColumn] FROM [myschema].[mytable]; END",
179184
);
180185
let _ = ms_and_generic().verified_stmt(
181-
"CREATE PROCEDURE foo (@CustomerName NVARCHAR(50)) AS BEGIN SELECT * FROM DEV END",
186+
"CREATE PROCEDURE foo (@CustomerName NVARCHAR(50)) AS BEGIN SELECT * FROM DEV; END",
182187
);
183-
let _ = ms().verified_stmt("CREATE PROCEDURE [foo] AS BEGIN UPDATE bar SET col = 'test' END");
188+
let _ = ms().verified_stmt("CREATE PROCEDURE [foo] AS BEGIN UPDATE bar SET col = 'test'; END");
184189
// Test a statement with END in it
185-
let _ = ms().verified_stmt("CREATE PROCEDURE [foo] AS BEGIN SELECT [foo], CASE WHEN [foo] IS NULL THEN 'empty' ELSE 'notempty' END AS [foo] END");
190+
let _ = ms().verified_stmt("CREATE PROCEDURE [foo] AS BEGIN SELECT [foo], CASE WHEN [foo] IS NULL THEN 'empty' ELSE 'notempty' END AS [foo]; END");
186191
// Multiple statements
187-
let _ = ms().verified_stmt("CREATE PROCEDURE [foo] AS BEGIN UPDATE bar SET col = 'test'; SELECT [foo] FROM BAR WHERE [FOO] > 10 END");
192+
let _ = ms().verified_stmt("CREATE PROCEDURE [foo] AS BEGIN UPDATE bar SET col = 'test'; SELECT [foo] FROM BAR WHERE [FOO] > 10; END");
188193
}
189194

190195
#[test]

0 commit comments

Comments
 (0)