@@ -483,6 +483,10 @@ impl<'a> Parser<'a> {
483483 if expecting_statement_delimiter && word.keyword == Keyword::END {
484484 break;
485485 }
486+
487+ if expecting_statement_delimiter && word.keyword == Keyword::GO {
488+ expecting_statement_delimiter = false;
489+ }
486490 }
487491 _ => {}
488492 }
@@ -492,8 +496,9 @@ impl<'a> Parser<'a> {
492496 }
493497
494498 let statement = self.parse_statement()?;
499+ // Treat batch delimiter as an end of statement, so no additional statement delimiter expected here
500+ expecting_statement_delimiter = !matches!(statement, Statement::Go(_));
495501 stmts.push(statement);
496- expecting_statement_delimiter = true;
497502 }
498503 Ok(stmts)
499504 }
@@ -633,6 +638,10 @@ impl<'a> Parser<'a> {
633638 Keyword::COMMENT if self.dialect.supports_comment_on() => self.parse_comment(),
634639 Keyword::PRINT => self.parse_print(),
635640 Keyword::RETURN => self.parse_return(),
641+ Keyword::GO => {
642+ self.prev_token();
643+ self.parse_go()
644+ }
636645 _ => self.expected("an SQL statement", next_token),
637646 },
638647 Token::LParen => {
@@ -4019,6 +4028,17 @@ impl<'a> Parser<'a> {
40194028 })
40204029 }
40214030
4031+ /// Return nth previous token, possibly whitespace
4032+ /// (or [`Token::EOF`] when before the beginning of the stream).
4033+ pub(crate) fn peek_prev_nth_token_no_skip_ref(&self, n: usize) -> &TokenWithSpan {
4034+ // 0 = next token, -1 = current token, -2 = previous token
4035+ let peek_index = self.index.saturating_sub(1).saturating_sub(n);
4036+ if peek_index == 0 {
4037+ return &EOF_TOKEN;
4038+ }
4039+ self.tokens.get(peek_index).unwrap_or(&EOF_TOKEN)
4040+ }
4041+
40224042 /// Return true if the next tokens exactly `expected`
40234043 ///
40244044 /// Does not advance the current token.
@@ -4135,6 +4155,29 @@ impl<'a> Parser<'a> {
41354155 )
41364156 }
41374157
4158+ /// Look backwards in the token stream and expect that there was only whitespace tokens until the previous newline or beginning of string
4159+ pub(crate) fn prev_only_whitespace_until_newline(&mut self) -> bool {
4160+ let mut look_back_count = 1;
4161+ loop {
4162+ let prev_token = self.peek_prev_nth_token_no_skip_ref(look_back_count);
4163+ match prev_token.token {
4164+ Token::EOF => break true,
4165+ Token::Whitespace(ref w) => match w {
4166+ Whitespace::Newline => break true,
4167+ // special consideration required for single line comments since that string includes the newline
4168+ Whitespace::SingleLineComment { comment, prefix: _ } => {
4169+ if comment.ends_with('\n') {
4170+ break true;
4171+ }
4172+ look_back_count += 1;
4173+ }
4174+ _ => look_back_count += 1,
4175+ },
4176+ _ => break false,
4177+ };
4178+ }
4179+ }
4180+
41384181 /// If the current token is the `expected` keyword, consume it and returns
41394182 /// true. Otherwise, no tokens are consumed and returns false.
41404183 #[must_use]
@@ -16378,6 +16421,71 @@ impl<'a> Parser<'a> {
1637816421 }
1637916422 }
1638016423
16424+ /// Parse [Statement::Go]
16425+ fn parse_go(&mut self) -> Result<Statement, ParserError> {
16426+ self.expect_keyword_is(Keyword::GO)?;
16427+
16428+ // disambiguate between GO as batch delimiter & GO as identifier (etc)
16429+ // compare:
16430+ // ```sql
16431+ // select 1 go
16432+ // ```
16433+ // vs
16434+ // ```sql
16435+ // select 1
16436+ // go
16437+ // ```
16438+ if !self.prev_only_whitespace_until_newline() {
16439+ parser_err!(
16440+ "GO may only be preceded by whitespace on a line",
16441+ self.peek_token().span.start
16442+ )?;
16443+ }
16444+
16445+ let count = loop {
16446+ // using this peek function because we want to halt this statement parsing upon newline
16447+ let next_token = self.peek_token_no_skip();
16448+ match next_token.token {
16449+ Token::EOF => break None::<u64>,
16450+ Token::Whitespace(ref w) => match w {
16451+ Whitespace::Newline => break None,
16452+ _ => _ = self.next_token_no_skip(),
16453+ },
16454+ Token::Number(s, _) => {
16455+ let value = Some(Self::parse::<u64>(s, next_token.span.start)?);
16456+ self.advance_token();
16457+ break value;
16458+ }
16459+ _ => self.expected("literal int or newline", next_token)?,
16460+ };
16461+ };
16462+
16463+ loop {
16464+ let next_token = self.peek_token_no_skip();
16465+ match next_token.token {
16466+ Token::EOF => break,
16467+ Token::Whitespace(ref w) => match w {
16468+ Whitespace::Newline => break,
16469+ Whitespace::SingleLineComment { comment, prefix: _ } => {
16470+ if comment.ends_with('\n') {
16471+ break;
16472+ }
16473+ _ = self.next_token_no_skip();
16474+ }
16475+ _ => _ = self.next_token_no_skip(),
16476+ },
16477+ _ => {
16478+ parser_err!(
16479+ "GO must be followed by a newline or EOF",
16480+ self.peek_token().span.start
16481+ )?;
16482+ }
16483+ };
16484+ }
16485+
16486+ Ok(Statement::Go(GoStatement { count }))
16487+ }
16488+
1638116489 /// Consume the parser and return its underlying token buffer
1638216490 pub fn into_tokens(self) -> Vec<TokenWithSpan> {
1638316491 self.tokens
@@ -16619,6 +16727,31 @@ mod tests {
1661916727 })
1662016728 }
1662116729
16730+ #[test]
16731+ fn test_peek_prev_nth_token_no_skip_ref() {
16732+ all_dialects().run_parser_method(
16733+ "SELECT 1;\n-- a comment\nRAISERROR('test', 16, 0);",
16734+ |parser| {
16735+ parser.index = 1;
16736+ assert_eq!(parser.peek_prev_nth_token_no_skip_ref(0), &Token::EOF);
16737+ assert_eq!(parser.index, 1);
16738+ parser.index = 7;
16739+ assert_eq!(
16740+ parser.token_at(parser.index - 1).token,
16741+ Token::Word(Word {
16742+ value: "RAISERROR".to_string(),
16743+ quote_style: None,
16744+ keyword: Keyword::RAISERROR,
16745+ })
16746+ );
16747+ assert_eq!(
16748+ parser.peek_prev_nth_token_no_skip_ref(2),
16749+ &Token::Whitespace(Whitespace::Newline)
16750+ );
16751+ },
16752+ );
16753+ }
16754+
1662216755 #[cfg(test)]
1662316756 mod test_parse_data_type {
1662416757 use crate::ast::{
0 commit comments