Skip to content

Commit c07aedc

Browse files
committed
squash-merge: mssql-go-keyword
1 parent 57b12b5 commit c07aedc

File tree

7 files changed

+369
-5
lines changed

7 files changed

+369
-5
lines changed

src/ast/mod.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4355,6 +4355,12 @@ pub enum Statement {
43554355
///
43564356
/// See [ReturnStatement]
43574357
Return(ReturnStatement),
4358+
/// Go (MsSql)
4359+
///
4360+
/// GO is not a Transact-SQL statement; it is a command recognized by various tools as a batch delimiter
4361+
///
4362+
/// See: <https://learn.microsoft.com/en-us/sql/t-sql/language-elements/sql-server-utilities-statements-go>
4363+
Go(GoStatement),
43584364
}
43594365

43604366
/// ```sql
@@ -6190,6 +6196,7 @@ impl fmt::Display for Statement {
61906196
Ok(())
61916197
}
61926198
Statement::Print(s) => write!(f, "{s}"),
6199+
Statement::Go(s) => write!(f, "{s}"),
61936200
Statement::Return(r) => write!(f, "{r}"),
61946201
Statement::List(command) => write!(f, "LIST {command}"),
61956202
Statement::Remove(command) => write!(f, "REMOVE {command}"),
@@ -10125,6 +10132,26 @@ impl fmt::Display for MemberOf {
1012510132
}
1012610133
}
1012710134

10135+
/// Represents a `GO` statement.
10136+
///
10137+
/// [MsSql](https://learn.microsoft.com/en-us/sql/t-sql/language-elements/sql-server-utilities-statements-go)
10138+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
10139+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
10140+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
10141+
pub struct GoStatement {
10142+
pub count: Option<u64>,
10143+
}
10144+
10145+
impl Display for GoStatement {
10146+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
10147+
if let Some(count) = self.count {
10148+
write!(f, "GO {count}")
10149+
} else {
10150+
write!(f, "GO")
10151+
}
10152+
}
10153+
}
10154+
1012810155
#[cfg(test)]
1012910156
mod tests {
1013010157
use crate::tokenizer::Location;

src/ast/spans.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -530,6 +530,7 @@ impl Spanned for Statement {
530530
Statement::RaisError { .. } => Span::empty(),
531531
Statement::Print { .. } => Span::empty(),
532532
Statement::Return { .. } => Span::empty(),
533+
Statement::Go { .. } => Span::empty(),
533534
Statement::List(..) | Statement::Remove(..) => Span::empty(),
534535
}
535536
}

src/dialect/mssql.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,13 @@ impl Dialect for MsSqlDialect {
132132
&[GranteesType::Public]
133133
}
134134

135-
fn is_column_alias(&self, kw: &Keyword, _parser: &mut Parser) -> bool {
135+
fn is_column_alias(&self, kw: &Keyword, parser: &mut Parser) -> bool {
136+
// if we find maybe whitespace then a newline looking backward, then `GO` ISN'T a column alias
137+
// if we can't find a newline then we assume that `GO` IS a column alias
138+
if kw == &Keyword::GO && parser.prev_only_whitespace_until_newline() {
139+
return false;
140+
}
141+
136142
!keywords::RESERVED_FOR_COLUMN_ALIAS.contains(kw) && !RESERVED_FOR_COLUMN_ALIAS.contains(kw)
137143
}
138144

src/keywords.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,7 @@ define_keywords!(
404404
GIN,
405405
GIST,
406406
GLOBAL,
407+
GO,
407408
GRANT,
408409
GRANTED,
409410
GRANTS,

src/parser/mod.rs

Lines changed: 142 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -505,6 +505,17 @@ impl<'a> Parser<'a> {
505505
match self.peek_token().token {
506506
Token::EOF => break,
507507

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+
508519
// don't expect a semicolon statement delimiter after a newline when not otherwise required
509520
Token::Whitespace(Whitespace::Newline) => {
510521
if !self.options.require_semicolon_stmt_delimiter {
@@ -519,8 +530,10 @@ impl<'a> Parser<'a> {
519530
}
520531

521532
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;
522536
stmts.push(statement);
523-
expecting_statement_delimiter = self.options.require_semicolon_stmt_delimiter;
524537
}
525538
Ok(stmts)
526539
}
@@ -660,6 +673,10 @@ impl<'a> Parser<'a> {
660673
Keyword::COMMENT if self.dialect.supports_comment_on() => self.parse_comment(),
661674
Keyword::PRINT => self.parse_print(),
662675
Keyword::RETURN => self.parse_return(),
676+
Keyword::GO => {
677+
self.prev_token();
678+
self.parse_go()
679+
}
663680
_ => self.expected("an SQL statement", next_token),
664681
},
665682
Token::LParen => {
@@ -4064,6 +4081,17 @@ impl<'a> Parser<'a> {
40644081
})
40654082
}
40664083

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+
40674095
/// Return true if the next tokens exactly `expected`
40684096
///
40694097
/// Does not advance the current token.
@@ -4180,6 +4208,29 @@ impl<'a> Parser<'a> {
41804208
)
41814209
}
41824210

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+
41834234
/// If the current token is the `expected` keyword, consume it and returns
41844235
/// true. Otherwise, no tokens are consumed and returns false.
41854236
#[must_use]
@@ -16534,6 +16585,71 @@ impl<'a> Parser<'a> {
1653416585
}
1653516586
}
1653616587

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+
1653716653
/// Consume the parser and return its underlying token buffer
1653816654
pub fn into_tokens(self) -> Vec<TokenWithSpan> {
1653916655
self.tokens
@@ -16779,6 +16895,31 @@ mod tests {
1677916895
})
1678016896
}
1678116897

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+
1678216923
#[cfg(test)]
1678316924
mod test_parse_data_type {
1678416925
use crate::ast::{

src/test_utils.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ impl TestedDialects {
153153
/// 2. re-serializing the result of parsing `sql` produces the same
154154
/// `canonical` sql string
155155
///
156-
/// For multiple statements, use [`statements_parse_to`].
156+
/// For multiple statements, use [`statements_parse_to`].
157157
pub fn one_statement_parses_to(&self, sql: &str, canonical: &str) -> Statement {
158158
let mut statements = self.parse_sql_statements(sql).expect(sql);
159159
assert_eq!(statements.len(), 1);
@@ -170,8 +170,15 @@ impl TestedDialects {
170170
}
171171

172172
/// The same as [`one_statement_parses_to`] but it works for a multiple statements
173-
pub fn statements_parse_to(&self, sql: &str, canonical: &str) -> Vec<Statement> {
173+
pub fn statements_parse_to(
174+
&self,
175+
sql: &str,
176+
statement_count: usize,
177+
canonical: &str,
178+
) -> Vec<Statement> {
174179
let statements = self.parse_sql_statements(sql).expect(sql);
180+
assert_eq!(statements.len(), statement_count);
181+
175182
if !canonical.is_empty() && sql != canonical {
176183
assert_eq!(self.parse_sql_statements(canonical).unwrap(), statements);
177184
} else {

0 commit comments

Comments
 (0)