Skip to content

Commit 809b6c0

Browse files
committed
squash-merge mssql-go-keyword
1 parent e2643fb commit 809b6c0

File tree

8 files changed

+383
-6
lines changed

8 files changed

+383
-6
lines changed

src/ast/mod.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4833,6 +4833,12 @@ pub enum Statement {
48334833
/// ```
48344834
/// [PostgreSQL](https://www.postgresql.org/docs/current/sql-reset.html)
48354835
Reset(ResetStatement),
4836+
/// Go (MsSql)
4837+
///
4838+
/// GO is not a Transact-SQL statement; it is a command recognized by various tools as a batch delimiter
4839+
///
4840+
/// See: <https://learn.microsoft.com/en-us/sql/t-sql/language-elements/sql-server-utilities-statements-go>
4841+
Go(GoStatement),
48364842
}
48374843

48384844
impl From<Analyze> for Statement {
@@ -6247,6 +6253,7 @@ impl fmt::Display for Statement {
62476253
Statement::Throw(s) => write!(f, "{s}"),
62486254
Statement::Print(s) => write!(f, "{s}"),
62496255
Statement::WaitFor(s) => write!(f, "{s}"),
6256+
Statement::Go(s) => write!(f, "{s}"),
62506257
Statement::Return(r) => write!(f, "{r}"),
62516258
Statement::List(command) => write!(f, "LIST {command}"),
62526259
Statement::Remove(command) => write!(f, "REMOVE {command}"),
@@ -11547,6 +11554,27 @@ impl fmt::Display for CreateTableLikeDefaults {
1154711554
}
1154811555
}
1154911556

11557+
/// Represents a `GO` statement.
11558+
///
11559+
/// [MsSql](https://learn.microsoft.com/en-us/sql/t-sql/language-elements/sql-server-utilities-statements-go)
11560+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
11561+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
11562+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
11563+
pub struct GoStatement {
11564+
/// How many times the batch should be executed, if specified (e.g., `GO 10`).
11565+
pub count: Option<u64>,
11566+
}
11567+
11568+
impl Display for GoStatement {
11569+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
11570+
if let Some(count) = self.count {
11571+
write!(f, "GO {count}")
11572+
} else {
11573+
write!(f, "GO")
11574+
}
11575+
}
11576+
}
11577+
1155011578
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
1155111579
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1155211580
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]

src/ast/spans.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -485,6 +485,7 @@ impl Spanned for Statement {
485485
Statement::Print { .. } => Span::empty(),
486486
Statement::WaitFor(_) => Span::empty(),
487487
Statement::Return { .. } => Span::empty(),
488+
Statement::Go { .. } => Span::empty(),
488489
Statement::List(..) | Statement::Remove(..) => Span::empty(),
489490
Statement::ExportData(ExportData {
490491
options,

src/dialect/mssql.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,12 @@ impl Dialect for MsSqlDialect {
132132
}
133133

134134
fn is_select_item_alias(&self, explicit: bool, kw: &Keyword, parser: &mut Parser) -> bool {
135+
// if we find maybe whitespace then a newline looking backward, then `GO` ISN'T a column alias
136+
// if we can't find a newline then we assume that `GO` IS a column alias
137+
if kw == &Keyword::GO && parser.prev_only_whitespace_until_newline() {
138+
return false;
139+
}
140+
135141
match kw {
136142
// List of keywords that cannot be used as select item (column) aliases in MSSQL
137143
// regardless of whether the alias is explicit or implicit.
@@ -188,6 +194,7 @@ impl Dialect for MsSqlDialect {
188194
| Keyword::PRINT
189195
| Keyword::WHILE
190196
| Keyword::RETURN
197+
| Keyword::GO
191198
| Keyword::THROW
192199
| Keyword::RAISERROR
193200
| Keyword::MERGE => false,

src/keywords.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -463,6 +463,7 @@ define_keywords!(
463463
GIN,
464464
GIST,
465465
GLOBAL,
466+
GO,
466467
GRANT,
467468
GRANTED,
468469
GRANTS,

src/parser/mod.rs

Lines changed: 137 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -531,6 +531,12 @@ impl<'a> Parser<'a> {
531531
if expecting_statement_delimiter && word.keyword == Keyword::END {
532532
break;
533533
}
534+
535+
// MSSQL: the `GO` keyword is a batch separator which also means it concludes the current statement
536+
// `GO` may not be followed by a semicolon, so turn off that expectation
537+
if expecting_statement_delimiter && word.keyword == Keyword::GO {
538+
expecting_statement_delimiter = false;
539+
}
534540
}
535541
// don't expect a semicolon statement delimiter after a newline when not otherwise required
536542
Token::Whitespace(Whitespace::Newline) => {
@@ -546,8 +552,10 @@ impl<'a> Parser<'a> {
546552
}
547553

548554
let statement = self.parse_statement()?;
555+
// MSSQL: the `GO` keyword is a batch separator which also means it concludes the current statement
556+
// `GO` may not be followed by a semicolon, so turn off that expectation
557+
expecting_statement_delimiter = !matches!(statement, Statement::Go(_)) && self.options.require_semicolon_stmt_delimiter;
549558
stmts.push(statement);
550-
expecting_statement_delimiter = self.options.require_semicolon_stmt_delimiter;
551559
}
552560
Ok(stmts)
553561
}
@@ -740,6 +748,10 @@ impl<'a> Parser<'a> {
740748
self.parse_vacuum()
741749
}
742750
Keyword::RESET => self.parse_reset().map(Into::into),
751+
Keyword::GO => {
752+
self.prev_token();
753+
self.parse_go()
754+
}
743755
_ => self.expected("an SQL statement", next_token),
744756
},
745757
Token::LParen => {
@@ -4473,6 +4485,17 @@ impl<'a> Parser<'a> {
44734485
self.tokens.get(self.index + n).unwrap_or(&EOF_TOKEN)
44744486
}
44754487

4488+
/// Return nth previous token, possibly whitespace
4489+
/// (or [`Token::EOF`] when before the beginning of the stream).
4490+
pub(crate) fn peek_prev_nth_token_no_skip_ref(&self, n: usize) -> &TokenWithSpan {
4491+
// 0 = next token, -1 = current token, -2 = previous token
4492+
let peek_index = self.index.saturating_sub(1).saturating_sub(n);
4493+
if peek_index == 0 {
4494+
return &EOF_TOKEN;
4495+
}
4496+
self.tokens.get(peek_index).unwrap_or(&EOF_TOKEN)
4497+
}
4498+
44764499
/// Return true if the next tokens exactly `expected`
44774500
///
44784501
/// Does not advance the current token.
@@ -4589,6 +4612,29 @@ impl<'a> Parser<'a> {
45894612
)
45904613
}
45914614

4615+
/// Look backwards in the token stream and expect that there was only whitespace tokens until the previous newline or beginning of string
4616+
pub(crate) fn prev_only_whitespace_until_newline(&mut self) -> bool {
4617+
let mut look_back_count = 1;
4618+
loop {
4619+
let prev_token = self.peek_prev_nth_token_no_skip_ref(look_back_count);
4620+
match prev_token.token {
4621+
Token::EOF => break true,
4622+
Token::Whitespace(ref w) => match w {
4623+
Whitespace::Newline => break true,
4624+
// special consideration required for single line comments since that string includes the newline
4625+
Whitespace::SingleLineComment { comment, prefix: _ } => {
4626+
if comment.ends_with('\n') {
4627+
break true;
4628+
}
4629+
look_back_count += 1;
4630+
}
4631+
_ => look_back_count += 1,
4632+
},
4633+
_ => break false,
4634+
};
4635+
}
4636+
}
4637+
45924638
/// If the current token is the `expected` keyword, consume it and returns
45934639
/// true. Otherwise, no tokens are consumed and returns false.
45944640
#[must_use]
@@ -19700,6 +19746,71 @@ impl<'a> Parser<'a> {
1970019746
}))
1970119747
}
1970219748

19749+
/// Parse [Statement::Go]
19750+
fn parse_go(&mut self) -> Result<Statement, ParserError> {
19751+
self.expect_keyword_is(Keyword::GO)?;
19752+
19753+
// disambiguate between GO as batch delimiter & GO as identifier (etc)
19754+
// compare:
19755+
// ```sql
19756+
// select 1 go
19757+
// ```
19758+
// vs
19759+
// ```sql
19760+
// select 1
19761+
// go
19762+
// ```
19763+
if !self.prev_only_whitespace_until_newline() {
19764+
parser_err!(
19765+
"GO may only be preceded by whitespace on a line",
19766+
self.peek_token().span.start
19767+
)?;
19768+
}
19769+
19770+
let count = loop {
19771+
// using this peek function because we want to halt this statement parsing upon newline
19772+
let next_token = self.peek_token_no_skip();
19773+
match next_token.token {
19774+
Token::EOF => break None::<u64>,
19775+
Token::Whitespace(ref w) => match w {
19776+
Whitespace::Newline => break None,
19777+
_ => _ = self.next_token_no_skip(),
19778+
},
19779+
Token::Number(s, _) => {
19780+
let value = Some(Self::parse::<u64>(s, next_token.span.start)?);
19781+
self.advance_token();
19782+
break value;
19783+
}
19784+
_ => self.expected("literal int or newline", next_token)?,
19785+
};
19786+
};
19787+
19788+
loop {
19789+
let next_token = self.peek_token_no_skip();
19790+
match next_token.token {
19791+
Token::EOF => break,
19792+
Token::Whitespace(ref w) => match w {
19793+
Whitespace::Newline => break,
19794+
Whitespace::SingleLineComment { comment, prefix: _ } => {
19795+
if comment.ends_with('\n') {
19796+
break;
19797+
}
19798+
_ = self.next_token_no_skip();
19799+
}
19800+
_ => _ = self.next_token_no_skip(),
19801+
},
19802+
_ => {
19803+
parser_err!(
19804+
"GO must be followed by a newline or EOF",
19805+
self.peek_token().span.start
19806+
)?;
19807+
}
19808+
};
19809+
}
19810+
19811+
Ok(Statement::Go(GoStatement { count }))
19812+
}
19813+
1970319814
/// Consume the parser and return its underlying token buffer
1970419815
pub fn into_tokens(self) -> Vec<TokenWithSpan> {
1970519816
self.tokens
@@ -20066,6 +20177,31 @@ mod tests {
2006620177
})
2006720178
}
2006820179

20180+
#[test]
20181+
fn test_peek_prev_nth_token_no_skip_ref() {
20182+
all_dialects().run_parser_method(
20183+
"SELECT 1;\n-- a comment\nRAISERROR('test', 16, 0);",
20184+
|parser| {
20185+
parser.index = 1;
20186+
assert_eq!(parser.peek_prev_nth_token_no_skip_ref(0), &Token::EOF);
20187+
assert_eq!(parser.index, 1);
20188+
parser.index = 7;
20189+
assert_eq!(
20190+
parser.token_at(parser.index - 1).token,
20191+
Token::Word(Word {
20192+
value: "RAISERROR".to_string(),
20193+
quote_style: None,
20194+
keyword: Keyword::RAISERROR,
20195+
})
20196+
);
20197+
assert_eq!(
20198+
parser.peek_prev_nth_token_no_skip_ref(2),
20199+
&Token::Whitespace(Whitespace::Newline)
20200+
);
20201+
},
20202+
);
20203+
}
20204+
2006920205
#[cfg(test)]
2007020206
mod test_parse_data_type {
2007120207
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)