Skip to content

Commit 930b0d9

Browse files
committed
Squashed merge of mssql-go-keyword
1 parent 6506814 commit 930b0d9

File tree

7 files changed

+361
-5
lines changed

7 files changed

+361
-5
lines changed

src/ast/mod.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4339,6 +4339,12 @@ pub enum Statement {
43394339
///
43404340
/// See [ReturnStatement]
43414341
Return(ReturnStatement),
4342+
/// Go (MsSql)
4343+
///
4344+
/// GO is not a Transact-SQL statement; it is a command recognized by various tools as a batch delimiter
4345+
///
4346+
/// See: <https://learn.microsoft.com/en-us/sql/t-sql/language-elements/sql-server-utilities-statements-go>
4347+
Go(GoStatement),
43424348
}
43434349

43444350
/// ```sql
@@ -6166,6 +6172,7 @@ impl fmt::Display for Statement {
61666172
Ok(())
61676173
}
61686174
Statement::Print(s) => write!(f, "{s}"),
6175+
Statement::Go(s) => write!(f, "{s}"),
61696176
Statement::Return(r) => write!(f, "{r}"),
61706177
Statement::List(command) => write!(f, "LIST {command}"),
61716178
Statement::Remove(command) => write!(f, "REMOVE {command}"),
@@ -10074,6 +10081,26 @@ impl fmt::Display for MemberOf {
1007410081
}
1007510082
}
1007610083

10084+
/// Represents a `GO` statement.
10085+
///
10086+
/// [MsSql](https://learn.microsoft.com/en-us/sql/t-sql/language-elements/sql-server-utilities-statements-go)
10087+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
10088+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
10089+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
10090+
pub struct GoStatement {
10091+
pub count: Option<u64>,
10092+
}
10093+
10094+
impl Display for GoStatement {
10095+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
10096+
if let Some(count) = self.count {
10097+
write!(f, "GO {count}")
10098+
} else {
10099+
write!(f, "GO")
10100+
}
10101+
}
10102+
}
10103+
1007710104
#[cfg(test)]
1007810105
mod tests {
1007910106
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
@@ -128,7 +128,13 @@ impl Dialect for MsSqlDialect {
128128
&[GranteesType::Public]
129129
}
130130

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

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: 134 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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::{

src/test_utils.rs

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

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

0 commit comments

Comments
 (0)