@@ -505,6 +505,17 @@ impl<'a> Parser<'a> {
505
505
match self.peek_token().token {
506
506
Token::EOF => break,
507
507
508
+ // end of statement
509
+ Token::Word(word) => {
510
+ if expecting_statement_delimiter && word.keyword == Keyword::END {
511
+ break;
512
+ }
513
+
514
+ if expecting_statement_delimiter && word.keyword == Keyword::GO {
515
+ expecting_statement_delimiter = false;
516
+ }
517
+ }
518
+
508
519
// don't expect a semicolon statement delimiter after a newline when not otherwise required
509
520
Token::Whitespace(Whitespace::Newline) => {
510
521
if !self.options.require_semicolon_stmt_delimiter {
@@ -519,8 +530,10 @@ impl<'a> Parser<'a> {
519
530
}
520
531
521
532
let statement = self.parse_statement()?;
533
+ // Treat batch delimiter as an end of statement, so no additional statement delimiter expected here
534
+ expecting_statement_delimiter = !matches!(statement, Statement::Go(_))
535
+ && self.options.require_semicolon_stmt_delimiter;
522
536
stmts.push(statement);
523
- expecting_statement_delimiter = self.options.require_semicolon_stmt_delimiter;
524
537
}
525
538
Ok(stmts)
526
539
}
@@ -660,6 +673,10 @@ impl<'a> Parser<'a> {
660
673
Keyword::COMMENT if self.dialect.supports_comment_on() => self.parse_comment(),
661
674
Keyword::PRINT => self.parse_print(),
662
675
Keyword::RETURN => self.parse_return(),
676
+ Keyword::GO => {
677
+ self.prev_token();
678
+ self.parse_go()
679
+ }
663
680
_ => self.expected("an SQL statement", next_token),
664
681
},
665
682
Token::LParen => {
@@ -4064,6 +4081,17 @@ impl<'a> Parser<'a> {
4064
4081
})
4065
4082
}
4066
4083
4084
+ /// Return nth previous token, possibly whitespace
4085
+ /// (or [`Token::EOF`] when before the beginning of the stream).
4086
+ pub(crate) fn peek_prev_nth_token_no_skip_ref(&self, n: usize) -> &TokenWithSpan {
4087
+ // 0 = next token, -1 = current token, -2 = previous token
4088
+ let peek_index = self.index.saturating_sub(1).saturating_sub(n);
4089
+ if peek_index == 0 {
4090
+ return &EOF_TOKEN;
4091
+ }
4092
+ self.tokens.get(peek_index).unwrap_or(&EOF_TOKEN)
4093
+ }
4094
+
4067
4095
/// Return true if the next tokens exactly `expected`
4068
4096
///
4069
4097
/// Does not advance the current token.
@@ -4180,6 +4208,29 @@ impl<'a> Parser<'a> {
4180
4208
)
4181
4209
}
4182
4210
4211
+ /// Look backwards in the token stream and expect that there was only whitespace tokens until the previous newline or beginning of string
4212
+ pub(crate) fn prev_only_whitespace_until_newline(&mut self) -> bool {
4213
+ let mut look_back_count = 1;
4214
+ loop {
4215
+ let prev_token = self.peek_prev_nth_token_no_skip_ref(look_back_count);
4216
+ match prev_token.token {
4217
+ Token::EOF => break true,
4218
+ Token::Whitespace(ref w) => match w {
4219
+ Whitespace::Newline => break true,
4220
+ // special consideration required for single line comments since that string includes the newline
4221
+ Whitespace::SingleLineComment { comment, prefix: _ } => {
4222
+ if comment.ends_with('\n') {
4223
+ break true;
4224
+ }
4225
+ look_back_count += 1;
4226
+ }
4227
+ _ => look_back_count += 1,
4228
+ },
4229
+ _ => break false,
4230
+ };
4231
+ }
4232
+ }
4233
+
4183
4234
/// If the current token is the `expected` keyword, consume it and returns
4184
4235
/// true. Otherwise, no tokens are consumed and returns false.
4185
4236
#[must_use]
@@ -16534,6 +16585,71 @@ impl<'a> Parser<'a> {
16534
16585
}
16535
16586
}
16536
16587
16588
+ /// Parse [Statement::Go]
16589
+ fn parse_go(&mut self) -> Result<Statement, ParserError> {
16590
+ self.expect_keyword_is(Keyword::GO)?;
16591
+
16592
+ // disambiguate between GO as batch delimiter & GO as identifier (etc)
16593
+ // compare:
16594
+ // ```sql
16595
+ // select 1 go
16596
+ // ```
16597
+ // vs
16598
+ // ```sql
16599
+ // select 1
16600
+ // go
16601
+ // ```
16602
+ if !self.prev_only_whitespace_until_newline() {
16603
+ parser_err!(
16604
+ "GO may only be preceded by whitespace on a line",
16605
+ self.peek_token().span.start
16606
+ )?;
16607
+ }
16608
+
16609
+ let count = loop {
16610
+ // using this peek function because we want to halt this statement parsing upon newline
16611
+ let next_token = self.peek_token_no_skip();
16612
+ match next_token.token {
16613
+ Token::EOF => break None::<u64>,
16614
+ Token::Whitespace(ref w) => match w {
16615
+ Whitespace::Newline => break None,
16616
+ _ => _ = self.next_token_no_skip(),
16617
+ },
16618
+ Token::Number(s, _) => {
16619
+ let value = Some(Self::parse::<u64>(s, next_token.span.start)?);
16620
+ self.advance_token();
16621
+ break value;
16622
+ }
16623
+ _ => self.expected("literal int or newline", next_token)?,
16624
+ };
16625
+ };
16626
+
16627
+ loop {
16628
+ let next_token = self.peek_token_no_skip();
16629
+ match next_token.token {
16630
+ Token::EOF => break,
16631
+ Token::Whitespace(ref w) => match w {
16632
+ Whitespace::Newline => break,
16633
+ Whitespace::SingleLineComment { comment, prefix: _ } => {
16634
+ if comment.ends_with('\n') {
16635
+ break;
16636
+ }
16637
+ _ = self.next_token_no_skip();
16638
+ }
16639
+ _ => _ = self.next_token_no_skip(),
16640
+ },
16641
+ _ => {
16642
+ parser_err!(
16643
+ "GO must be followed by a newline or EOF",
16644
+ self.peek_token().span.start
16645
+ )?;
16646
+ }
16647
+ };
16648
+ }
16649
+
16650
+ Ok(Statement::Go(GoStatement { count }))
16651
+ }
16652
+
16537
16653
/// Consume the parser and return its underlying token buffer
16538
16654
pub fn into_tokens(self) -> Vec<TokenWithSpan> {
16539
16655
self.tokens
@@ -16779,6 +16895,31 @@ mod tests {
16779
16895
})
16780
16896
}
16781
16897
16898
+ #[test]
16899
+ fn test_peek_prev_nth_token_no_skip_ref() {
16900
+ all_dialects().run_parser_method(
16901
+ "SELECT 1;\n-- a comment\nRAISERROR('test', 16, 0);",
16902
+ |parser| {
16903
+ parser.index = 1;
16904
+ assert_eq!(parser.peek_prev_nth_token_no_skip_ref(0), &Token::EOF);
16905
+ assert_eq!(parser.index, 1);
16906
+ parser.index = 7;
16907
+ assert_eq!(
16908
+ parser.token_at(parser.index - 1).token,
16909
+ Token::Word(Word {
16910
+ value: "RAISERROR".to_string(),
16911
+ quote_style: None,
16912
+ keyword: Keyword::RAISERROR,
16913
+ })
16914
+ );
16915
+ assert_eq!(
16916
+ parser.peek_prev_nth_token_no_skip_ref(2),
16917
+ &Token::Whitespace(Whitespace::Newline)
16918
+ );
16919
+ },
16920
+ );
16921
+ }
16922
+
16782
16923
#[cfg(test)]
16783
16924
mod test_parse_data_type {
16784
16925
use crate::ast::{
0 commit comments