Skip to content

Commit dd2f851

Browse files
iffyioayman-sigma
authored andcommitted
BigQuery: Add support for BEGIN (apache#1718)
1 parent 1a75092 commit dd2f851

File tree

6 files changed

+190
-33
lines changed

6 files changed

+190
-33
lines changed

src/ast/mod.rs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3090,6 +3090,28 @@ pub enum Statement {
30903090
begin: bool,
30913091
transaction: Option<BeginTransactionKind>,
30923092
modifier: Option<TransactionModifier>,
3093+
/// List of statements belonging to the `BEGIN` block.
3094+
/// Example:
3095+
/// ```sql
3096+
/// BEGIN
3097+
/// SELECT 1;
3098+
/// SELECT 2;
3099+
/// END;
3100+
/// ```
3101+
statements: Vec<Statement>,
3102+
/// Statements of an exception clause.
3103+
/// Example:
3104+
/// ```sql
3105+
/// BEGIN
3106+
/// SELECT 1;
3107+
/// EXCEPTION WHEN ERROR THEN
3108+
/// SELECT 2;
3109+
/// SELECT 3;
3110+
/// END;
3111+
/// <https://cloud.google.com/bigquery/docs/reference/standard-sql/procedural-language#beginexceptionend>
3112+
exception_statements: Option<Vec<Statement>>,
3113+
/// TRUE if the statement has an `END` keyword.
3114+
has_end_keyword: bool,
30933115
},
30943116
/// ```sql
30953117
/// SET TRANSACTION ...
@@ -4833,6 +4855,9 @@ impl fmt::Display for Statement {
48334855
begin: syntax_begin,
48344856
transaction,
48354857
modifier,
4858+
statements,
4859+
exception_statements,
4860+
has_end_keyword,
48364861
} => {
48374862
if *syntax_begin {
48384863
if let Some(modifier) = *modifier {
@@ -4849,6 +4874,24 @@ impl fmt::Display for Statement {
48494874
if !modes.is_empty() {
48504875
write!(f, " {}", display_comma_separated(modes))?;
48514876
}
4877+
if !statements.is_empty() {
4878+
write!(f, " {}", display_separated(statements, "; "))?;
4879+
// We manually insert semicolon for the last statement,
4880+
// since display_separated doesn't handle that case.
4881+
write!(f, ";")?;
4882+
}
4883+
if let Some(exception_statements) = exception_statements {
4884+
write!(f, " EXCEPTION WHEN ERROR THEN")?;
4885+
if !exception_statements.is_empty() {
4886+
write!(f, " {}", display_separated(exception_statements, "; "))?;
4887+
// We manually insert semicolon for the last statement,
4888+
// since display_separated doesn't handle that case.
4889+
write!(f, ";")?;
4890+
}
4891+
}
4892+
if *has_end_keyword {
4893+
write!(f, " END")?;
4894+
}
48524895
Ok(())
48534896
}
48544897
Statement::SetTransaction {

src/dialect/bigquery.rs

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,10 @@
1515
// specific language governing permissions and limitations
1616
// under the License.
1717

18+
use crate::ast::Statement;
1819
use crate::dialect::Dialect;
1920
use crate::keywords::Keyword;
20-
use crate::parser::Parser;
21+
use crate::parser::{Parser, ParserError};
2122

2223
/// These keywords are disallowed as column identifiers. Such that
2324
/// `SELECT 5 AS <col> FROM T` is rejected by BigQuery.
@@ -44,7 +45,11 @@ const RESERVED_FOR_COLUMN_ALIAS: &[Keyword] = &[
4445
pub struct BigQueryDialect;
4546

4647
impl Dialect for BigQueryDialect {
47-
// See https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#identifiers
48+
fn parse_statement(&self, parser: &mut Parser) -> Option<Result<Statement, ParserError>> {
49+
self.maybe_parse_statement(parser)
50+
}
51+
52+
/// See <https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#identifiers>
4853
fn is_delimited_identifier_start(&self, ch: char) -> bool {
4954
ch == '`'
5055
}
@@ -60,6 +65,9 @@ impl Dialect for BigQueryDialect {
6065

6166
fn is_identifier_start(&self, ch: char) -> bool {
6267
ch.is_ascii_lowercase() || ch.is_ascii_uppercase() || ch == '_'
68+
// BigQuery supports `@@foo.bar` variable syntax in its procedural language.
69+
// https://cloud.google.com/bigquery/docs/reference/standard-sql/procedural-language#beginexceptionend
70+
|| ch == '@'
6371
}
6472

6573
fn is_identifier_part(&self, ch: char) -> bool {
@@ -129,3 +137,48 @@ impl Dialect for BigQueryDialect {
129137
!RESERVED_FOR_COLUMN_ALIAS.contains(kw)
130138
}
131139
}
140+
141+
impl BigQueryDialect {
142+
fn maybe_parse_statement(&self, parser: &mut Parser) -> Option<Result<Statement, ParserError>> {
143+
if parser.peek_keyword(Keyword::BEGIN) {
144+
return Some(self.parse_begin(parser));
145+
}
146+
None
147+
}
148+
149+
/// Parse a `BEGIN` statement.
150+
/// <https://cloud.google.com/bigquery/docs/reference/standard-sql/procedural-language#beginexceptionend>
151+
fn parse_begin(&self, parser: &mut Parser) -> Result<Statement, ParserError> {
152+
parser.expect_keyword(Keyword::BEGIN)?;
153+
154+
let statements = parser.parse_statement_list(&[Keyword::EXCEPTION, Keyword::END])?;
155+
156+
let has_exception_when_clause = parser.parse_keywords(&[
157+
Keyword::EXCEPTION,
158+
Keyword::WHEN,
159+
Keyword::ERROR,
160+
Keyword::THEN,
161+
]);
162+
let exception_statements = if has_exception_when_clause {
163+
if !parser.peek_keyword(Keyword::END) {
164+
Some(parser.parse_statement_list(&[Keyword::END])?)
165+
} else {
166+
Some(Default::default())
167+
}
168+
} else {
169+
None
170+
};
171+
172+
parser.expect_keyword(Keyword::END)?;
173+
174+
Ok(Statement::StartTransaction {
175+
begin: true,
176+
statements,
177+
exception_statements,
178+
has_end_keyword: true,
179+
transaction: None,
180+
modifier: None,
181+
modes: Default::default(),
182+
})
183+
}
184+
}

src/parser/mod.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4283,6 +4283,27 @@ impl<'a> Parser<'a> {
42834283
self.parse_comma_separated(f)
42844284
}
42854285

4286+
/// Parses 0 or more statements, each followed by a semicolon.
4287+
/// If the next token is any of `terminal_keywords` then no more
4288+
/// statements will be parsed.
4289+
pub(crate) fn parse_statement_list(
4290+
&mut self,
4291+
terminal_keywords: &[Keyword],
4292+
) -> Result<Vec<Statement>, ParserError> {
4293+
let mut values = vec![];
4294+
loop {
4295+
if let Token::Word(w) = &self.peek_nth_token_ref(0).token {
4296+
if w.quote_style.is_none() && terminal_keywords.contains(&w.keyword) {
4297+
break;
4298+
}
4299+
}
4300+
4301+
values.push(self.parse_statement()?);
4302+
self.expect_token(&Token::SemiColon)?;
4303+
}
4304+
Ok(values)
4305+
}
4306+
42864307
/// Default implementation of a predicate that returns true if
42874308
/// the specified keyword is reserved for column alias.
42884309
/// See [Dialect::is_column_alias]
@@ -13793,6 +13814,9 @@ impl<'a> Parser<'a> {
1379313814
begin: false,
1379413815
transaction: Some(BeginTransactionKind::Transaction),
1379513816
modifier: None,
13817+
statements: vec![],
13818+
exception_statements: None,
13819+
has_end_keyword: false,
1379613820
})
1379713821
}
1379813822

@@ -13822,6 +13846,9 @@ impl<'a> Parser<'a> {
1382213846
begin: true,
1382313847
transaction,
1382413848
modifier,
13849+
statements: vec![],
13850+
exception_statements: None,
13851+
has_end_keyword: false,
1382513852
})
1382613853
}
1382713854

tests/sqlparser_bigquery.rs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,52 @@ fn parse_big_query_non_reserved_column_alias() {
236236
bigquery().verified_stmt(sql);
237237
}
238238

239+
#[test]
240+
fn parse_at_at_identifier() {
241+
bigquery().verified_stmt("SELECT @@error.stack_trace, @@error.message");
242+
}
243+
244+
#[test]
245+
fn parse_begin() {
246+
let sql = r#"BEGIN SELECT 1; EXCEPTION WHEN ERROR THEN SELECT 2; END"#;
247+
let Statement::StartTransaction {
248+
statements,
249+
exception_statements,
250+
has_end_keyword,
251+
..
252+
} = bigquery().verified_stmt(sql)
253+
else {
254+
unreachable!();
255+
};
256+
assert_eq!(1, statements.len());
257+
assert_eq!(1, exception_statements.unwrap().len());
258+
assert!(has_end_keyword);
259+
260+
bigquery().verified_stmt(
261+
"BEGIN SELECT 1; SELECT 2; EXCEPTION WHEN ERROR THEN SELECT 2; SELECT 4; END",
262+
);
263+
bigquery()
264+
.verified_stmt("BEGIN SELECT 1; EXCEPTION WHEN ERROR THEN SELECT @@error.stack_trace; END");
265+
bigquery().verified_stmt("BEGIN EXCEPTION WHEN ERROR THEN SELECT 2; END");
266+
bigquery().verified_stmt("BEGIN SELECT 1; SELECT 2; EXCEPTION WHEN ERROR THEN END");
267+
bigquery().verified_stmt("BEGIN EXCEPTION WHEN ERROR THEN END");
268+
bigquery().verified_stmt("BEGIN SELECT 1; SELECT 2; END");
269+
bigquery().verified_stmt("BEGIN END");
270+
271+
assert_eq!(
272+
bigquery()
273+
.parse_sql_statements("BEGIN SELECT 1; SELECT 2 END")
274+
.unwrap_err(),
275+
ParserError::ParserError("Expected: ;, found: END".to_string())
276+
);
277+
assert_eq!(
278+
bigquery()
279+
.parse_sql_statements("BEGIN SELECT 1; EXCEPTION WHEN ERROR THEN SELECT 2 END")
280+
.unwrap_err(),
281+
ParserError::ParserError("Expected: ;, found: END".to_string())
282+
);
283+
}
284+
239285
#[test]
240286
fn parse_delete_statement() {
241287
let sql = "DELETE \"table\" WHERE 1";

tests/sqlparser_common.rs

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8344,7 +8344,12 @@ fn lateral_function() {
83448344

83458345
#[test]
83468346
fn parse_start_transaction() {
8347-
match verified_stmt("START TRANSACTION READ ONLY, READ WRITE, ISOLATION LEVEL SERIALIZABLE") {
8347+
let dialects = all_dialects_except(|d|
8348+
// BigQuery does not support this syntax
8349+
d.is::<BigQueryDialect>());
8350+
match dialects
8351+
.verified_stmt("START TRANSACTION READ ONLY, READ WRITE, ISOLATION LEVEL SERIALIZABLE")
8352+
{
83488353
Statement::StartTransaction { modes, .. } => assert_eq!(
83498354
modes,
83508355
vec![
@@ -8358,7 +8363,7 @@ fn parse_start_transaction() {
83588363

83598364
// For historical reasons, PostgreSQL allows the commas between the modes to
83608365
// be omitted.
8361-
match one_statement_parses_to(
8366+
match dialects.one_statement_parses_to(
83628367
"START TRANSACTION READ ONLY READ WRITE ISOLATION LEVEL SERIALIZABLE",
83638368
"START TRANSACTION READ ONLY, READ WRITE, ISOLATION LEVEL SERIALIZABLE",
83648369
) {
@@ -8373,40 +8378,40 @@ fn parse_start_transaction() {
83738378
_ => unreachable!(),
83748379
}
83758380

8376-
verified_stmt("START TRANSACTION");
8377-
verified_stmt("BEGIN");
8378-
verified_stmt("BEGIN WORK");
8379-
verified_stmt("BEGIN TRANSACTION");
8381+
dialects.verified_stmt("START TRANSACTION");
8382+
dialects.verified_stmt("BEGIN");
8383+
dialects.verified_stmt("BEGIN WORK");
8384+
dialects.verified_stmt("BEGIN TRANSACTION");
83808385

8381-
verified_stmt("START TRANSACTION ISOLATION LEVEL READ UNCOMMITTED");
8382-
verified_stmt("START TRANSACTION ISOLATION LEVEL READ COMMITTED");
8383-
verified_stmt("START TRANSACTION ISOLATION LEVEL REPEATABLE READ");
8384-
verified_stmt("START TRANSACTION ISOLATION LEVEL SERIALIZABLE");
8386+
dialects.verified_stmt("START TRANSACTION ISOLATION LEVEL READ UNCOMMITTED");
8387+
dialects.verified_stmt("START TRANSACTION ISOLATION LEVEL READ COMMITTED");
8388+
dialects.verified_stmt("START TRANSACTION ISOLATION LEVEL REPEATABLE READ");
8389+
dialects.verified_stmt("START TRANSACTION ISOLATION LEVEL SERIALIZABLE");
83858390

83868391
// Regression test for https://github.com/sqlparser-rs/sqlparser-rs/pull/139,
83878392
// in which START TRANSACTION would fail to parse if followed by a statement
83888393
// terminator.
83898394
assert_eq!(
8390-
parse_sql_statements("START TRANSACTION; SELECT 1"),
8395+
dialects.parse_sql_statements("START TRANSACTION; SELECT 1"),
83918396
Ok(vec![
83928397
verified_stmt("START TRANSACTION"),
83938398
verified_stmt("SELECT 1"),
83948399
])
83958400
);
83968401

8397-
let res = parse_sql_statements("START TRANSACTION ISOLATION LEVEL BAD");
8402+
let res = dialects.parse_sql_statements("START TRANSACTION ISOLATION LEVEL BAD");
83988403
assert_eq!(
83998404
ParserError::ParserError("Expected: isolation level, found: BAD".to_string()),
84008405
res.unwrap_err()
84018406
);
84028407

8403-
let res = parse_sql_statements("START TRANSACTION BAD");
8408+
let res = dialects.parse_sql_statements("START TRANSACTION BAD");
84048409
assert_eq!(
84058410
ParserError::ParserError("Expected: end of statement, found: BAD".to_string()),
84068411
res.unwrap_err()
84078412
);
84088413

8409-
let res = parse_sql_statements("START TRANSACTION READ ONLY,");
8414+
let res = dialects.parse_sql_statements("START TRANSACTION READ ONLY,");
84108415
assert_eq!(
84118416
ParserError::ParserError("Expected: transaction mode, found: EOF".to_string()),
84128417
res.unwrap_err()

tests/sqlparser_sqlite.rs

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -518,23 +518,6 @@ fn parse_start_transaction_with_modifier() {
518518
sqlite_and_generic().verified_stmt("BEGIN DEFERRED");
519519
sqlite_and_generic().verified_stmt("BEGIN IMMEDIATE");
520520
sqlite_and_generic().verified_stmt("BEGIN EXCLUSIVE");
521-
522-
let unsupported_dialects = all_dialects_except(|d| d.supports_start_transaction_modifier());
523-
let res = unsupported_dialects.parse_sql_statements("BEGIN DEFERRED");
524-
assert_eq!(
525-
ParserError::ParserError("Expected: end of statement, found: DEFERRED".to_string()),
526-
res.unwrap_err(),
527-
);
528-
let res = unsupported_dialects.parse_sql_statements("BEGIN IMMEDIATE");
529-
assert_eq!(
530-
ParserError::ParserError("Expected: end of statement, found: IMMEDIATE".to_string()),
531-
res.unwrap_err(),
532-
);
533-
let res = unsupported_dialects.parse_sql_statements("BEGIN EXCLUSIVE");
534-
assert_eq!(
535-
ParserError::ParserError("Expected: end of statement, found: EXCLUSIVE".to_string()),
536-
res.unwrap_err(),
537-
);
538521
}
539522

540523
#[test]

0 commit comments

Comments
 (0)