-
Notifications
You must be signed in to change notification settings - Fork 602
Add support for GO
batch delimiter in SQL Server
#1809
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
src/ast/mod.rs
Outdated
Go { | ||
count: Option<u64>, | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Repr wise can we wrap the statement body in explicit structs? we're transitioning away from anonymous structs in order to make the API more ergonomic. Something like
struct GoStatement {
count: Option<u64>
}
Statement::Go(GoStatement)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done 👍
src/parser/mod.rs
Outdated
// Treat batch delimiter as an end of statement | ||
if expecting_statement_delimiter && dialect_of!(self is MsSqlDialect) { | ||
if let Some(Statement::Go { count: _ }) = stmts.last() { | ||
expecting_statement_delimiter = false; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not sure I followed this part, what was the intention?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried to explain this in the commit message & the "multiple_gos" example in the test. Basically, we want this to parse as 4 statements:
SELECT 1;
GO
SELECT 2;
GO
The second GO
is terminated by EOF, so no problem. But the first GO
cannot end with a semi colon (This overlaps with the ongoing "no semi-colons for statements" discussion but from a different angle). So we need to figure out how to parse it all out without the existing conventional semi-colon logic.
What I came up with is to basically just look for a GO and have that signal that we aren't then also looking for statement delimiter (eg, semi-colon). This also makes sense semantically because statements can't extend between batches, so any batch delimiter also functions as a statement delimiter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In terms of the code, I worked out a perhaps slightly less confusing way to set whether or not a semi-colon is expected, here.
This branch can be updated accordingly if we agree over there
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've now done that due to another problem I noticed, for IF statements (new test case also added...)
src/parser/mod.rs
Outdated
@@ -617,6 +623,9 @@ impl<'a> Parser<'a> { | |||
} | |||
// `COMMENT` is snowflake specific https://docs.snowflake.com/en/sql-reference/sql/comment | |||
Keyword::COMMENT if self.dialect.supports_comment_on() => self.parse_comment(), | |||
Keyword::GO if dialect_of!(self is MsSqlDialect) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Keyword::GO if dialect_of!(self is MsSqlDialect) => { | |
Keyword::GO => { |
I think it should be fine to let the parser always accept the statement if it shows up. Technically if we wanted to gate it behind a feature then we could use a self.dialect.supports
flag since the dialect_of
macro is deprecated, but I think it makes more sense to let the parser accept unconditionally
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done 👍
@@ -15017,6 +15026,48 @@ impl<'a> Parser<'a> { | |||
} | |||
} | |||
|
|||
fn parse_go(&mut self) -> Result<Statement, ParserError> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fn parse_go(&mut self) -> Result<Statement, ParserError> { | |
/// Parse [Statement::Go] | |
fn parse_go(&mut self) -> Result<Statement, ParserError> { |
@@ -15017,6 +15026,48 @@ impl<'a> Parser<'a> { | |||
} | |||
} | |||
|
|||
fn parse_go(&mut self) -> Result<Statement, ParserError> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One thing we could do, in the parse_stmt()
function could we call self.prev_token()
to rewind the Go
keyword so that this function becomes self contained in being able to parse a full Go
statement?
src/parser/mod.rs
Outdated
@@ -15017,6 +15026,48 @@ impl<'a> Parser<'a> { | |||
} | |||
} | |||
|
|||
fn parse_go(&mut self) -> Result<Statement, ParserError> { | |||
// previous token should be a newline (skipping non-newline whitespace) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hmm I didn't look too closely but not sure I followed the function body on a high level, from the docs and the representation in the parser the Go statement only contains an optional int, so that I imagined all the function needs to do would be to maybe_parse(|parser| parser.parse_number_value())
or similar? The function is currently managing whitespace and semicolon but not super clear to me why that is required
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does my comment above & the code comment below, "using this peek function because we want to halt this statement parsing upon newline" help?
-- this should parse as `Go { Some(4) }`
GO 4
-- this should parse as something like `Go { None }`, `Expr(4)`
GO
4
Eg, the whitespace is meaningful for parsing
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah interesting, I think it would be very helpful to add this example to the code to illustrate the implications,
Then for the function, I think using the peek_token_no_skip
could simplify the impl. Something roughly like the following? to avoid manually tracking token counts and lookbacks
let count = loop {
match self.peek_token_no_skip() {
Token::Whitespace(Whitespace::Newline) | Token::Whitespace(Whitespace::SingleLineComment) | Token::EOF => {
self.advance_token();
break None
}
Token::Whitespace(_) => {
self.advance_token();
}
_ => {
break Some(self.parse_number())
}
}
};
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I have something like that already for parsing the batch count, which doesn't need to look backwards and already uses peek_token_no_skip. I think between here & your other comment on the alias function, the concern is regarding the newline logic which looks backwards. I'll try and move that into a helper so it can be simplified.
tests/sqlparser_mssql.rs
Outdated
assert!(err.is_err()); | ||
assert_eq!( | ||
err.unwrap_err().to_string(), | ||
"sql parser error: Expected: newline before GO, found: ;" | ||
); | ||
|
||
let invalid_go_count = "SELECT 1\nGO x"; | ||
let err = ms().parse_sql_statements(invalid_go_count); | ||
assert!(err.is_err()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
assert!(err.is_err()); | |
assert_eq!( | |
err.unwrap_err().to_string(), | |
"sql parser error: Expected: newline before GO, found: ;" | |
); | |
let invalid_go_count = "SELECT 1\nGO x"; | |
let err = ms().parse_sql_statements(invalid_go_count); | |
assert!(err.is_err()); | |
assert_eq!( | |
err.unwrap_err().to_string(), | |
"sql parser error: Expected: newline before GO, found: ;" | |
); | |
let invalid_go_count = "SELECT 1\nGO x"; | |
let err = ms().parse_sql_statements(invalid_go_count); |
since we're already unwrapping the error
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah yes great point, done 👍
3dea67c
to
ea7db7d
Compare
afe44a3
to
fa964b5
Compare
Rebased on main & updated |
dc0bf6a
to
ed181fd
Compare
src/parser/mod.rs
Outdated
@@ -15017,6 +15026,48 @@ impl<'a> Parser<'a> { | |||
} | |||
} | |||
|
|||
fn parse_go(&mut self) -> Result<Statement, ParserError> { | |||
// previous token should be a newline (skipping non-newline whitespace) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah interesting, I think it would be very helpful to add this example to the code to illustrate the implications,
Then for the function, I think using the peek_token_no_skip
could simplify the impl. Something roughly like the following? to avoid manually tracking token counts and lookbacks
let count = loop {
match self.peek_token_no_skip() {
Token::Whitespace(Whitespace::Newline) | Token::Whitespace(Whitespace::SingleLineComment) | Token::EOF => {
self.advance_token();
break None
}
Token::Whitespace(_) => {
self.advance_token();
}
_ => {
break Some(self.parse_number())
}
}
};
|
||
#[test] | ||
fn parse_mssql_go_keyword() { | ||
let single_go_keyword = "USE some_database;\nGO"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we have one of the test cases use ms().one_statement_parses_to(sql, canonical)
? looking at the tests its unclear what the behavior of the parser is in terms of serializing an AST that has GO statements in it, and that it needs special behavior to inject new lines manually I imagine
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm unsure the best way to approach this question. one_statement_parses_to
requires one statement, right? But most of these SQL text examples are multiple statements. I didn't see anything in TestUtils for a series of statements, so I added a new helper accordingly with canonical strings, let me know if that covers it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah yeah a new helper sounds reasonable to me thanks!
tests/sqlparser_mssql.rs
Outdated
assert_eq!(stmts.len(), 2); | ||
assert_eq!(stmts[1], Statement::Go(GoStatement { count: None })); | ||
|
||
let comment_following_go = "USE some_database;\nGO -- okay"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe we can add a multine_line_comment_following_go
test case? e.g. GO /* whitespace */ 42
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done 👍
src/ast/mod.rs
Outdated
/// | ||
/// GO is not a Transact-SQL statement; it is a command recognized by various tools as a batch delimiter | ||
/// | ||
/// See <https://learn.microsoft.com/en-us/sql/t-sql/language-elements/sql-server-utilities-statements-go> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For visibility we can move the comment to the struct definition similar to the print statement?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is already the same as print 🤔. Are you saying both comments should move to their respective structs?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah indeed, ideally both would have the doc comments on their respective structs, I think I meant to reference return statement in the other PR as the example
src/dialect/mssql.rs
Outdated
// - looking backwards there's only (any) whitespace preceded by a newline | ||
// then: `GO` iSN'T a column alias | ||
if kw == &Keyword::GO { | ||
let mut look_back_count = 2; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
double checking, why does the index start at 2 vs 1?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's similar to parse_go, which references the existing previous token logic which also starts by subtracting 2. My understanding is that 0 is the next token, -1 is the current token, and -2 is the first previous token.
src/dialect/mssql.rs
Outdated
// then: `GO` iSN'T a column alias | ||
if kw == &Keyword::GO { | ||
let mut look_back_count = 2; | ||
loop { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we have this logic as a helper function?
fn prev_nth_token_no_skip(&self, mut n: usize) -> Option<&TokenWithSpan> {
// ...
}
like a combination of peek_nth_token and prev_token
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added a new helper, done 👍
7689e0c
to
b8a184c
Compare
src/test_utils.rs
Outdated
@@ -166,6 +168,32 @@ impl TestedDialects { | |||
only_statement | |||
} | |||
|
|||
/// The same as [`one_statement_parses_to`] but it works for a multiple statements | |||
pub fn multiple_statements_parse_to( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pub fn multiple_statements_parse_to( | |
pub fn statements_parse_to( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done 👍
|
||
#[test] | ||
fn parse_mssql_go_keyword() { | ||
let single_go_keyword = "USE some_database;\nGO"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah yeah a new helper sounds reasonable to me thanks!
src/parser/mod.rs
Outdated
@@ -4055,6 +4070,44 @@ impl<'a> Parser<'a> { | |||
) | |||
} | |||
|
|||
/// Look backwards in the token stream and expect that there was only whitespace tokens until the previous newline | |||
pub fn expect_previously_only_whitespace_until_newline(&mut self) -> Result<(), ParserError> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pub fn expect_previously_only_whitespace_until_newline(&mut self) -> Result<(), ParserError> { | |
pub(crate) fn expect_previously_only_whitespace_until_newline(&mut self) -> Result<(), ParserError> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done 👍
src/parser/mod.rs
Outdated
@@ -618,6 +632,7 @@ impl<'a> Parser<'a> { | |||
// `COMMENT` is snowflake specific https://docs.snowflake.com/en/sql-reference/sql/comment | |||
Keyword::COMMENT if self.dialect.supports_comment_on() => self.parse_comment(), | |||
Keyword::PRINT => self.parse_print(), | |||
Keyword::GO => self.parse_go(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we rewind with self.prev_token()
here before calling parse_go? so that the parse_go
function is self contained and can parse a full GO statement. (I also realise now we missed that for print)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see that some of the statement types in parse_statement are doing that but most aren't. What's the strategy on this? How do you know when you're supposed to do it or not?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed the code is inconsistent in that regard historically, for newer statements ideally the parser functions are self contained when possible
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, that makes sense. I've updated it to rewind before parsing (using raiserror as a reference)
self.expect_previously_only_whitespace_until_newline()?; | ||
|
||
let count = loop { | ||
// using this peek function because we want to halt this statement parsing upon newline |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we include the example you had in the comment earlier, explicitly highlighting why this statement is special? I think otherwise it would not be obvious to folks that come across the code later on why the current code is a special case
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I can add that comment. However there is also test coverage for this behavior, so it should be protected from future refactoring
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done 👍
src/parser/mod.rs
Outdated
@@ -15064,6 +15117,38 @@ impl<'a> Parser<'a> { | |||
})) | |||
} | |||
|
|||
/// Parse [Statement::Go] | |||
fn parse_go(&mut self) -> Result<Statement, ParserError> { | |||
self.expect_previously_only_whitespace_until_newline()?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hmm this call look equivalent to the loop below that peeks forward to skip newlines? unclear why we're peeking backwards here right after we parsed the GO keyword (so that this will always be infallible?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hmm this call look equivalent to the loop below that peeks forward to skip newlines? unclear why we're peeking backwards here right after we parsed the GO keyword (so that this will always be infallible?)
The too loops aren't the same. The reason we need to peek look backwards here is to forbid this syntax:
-- `GO` is disallowed
select 1; go
If you actually run this on a real SQL Server instance, you will also get a similar error: Incorrect syntax near 'go'.
. Therefore it seems prudent to forbid this syntax from parsing here in this library. This situation has test coverage with the invalid_go_position
example.
Note that the extremely similar syntax select 1 go
is allowed, but in this example go
is a column alias rather than a batch delimiter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah that makes sense! And the added comment clarifies as well thanks!
src/dialect/mssql.rs
Outdated
.expect_previously_only_whitespace_until_newline() | ||
.is_ok() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if the intent is for the condition to return a bool, then we can update this function to return a bool instead of a result (where the latter is flagging an error to be handled)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this case, it's most useful to know if there was previously only whitespace until a newline. However in the other call of this function in parse_go, we legitimately do want a parser error. Therefore we have choices:
- expect_previously_only_whitespace_until_newline raises a parse error which makes sense in parse_go but is converted to a boolean here
- expect_previously_only_whitespace_until_newline returns a boolean which makes sense here and then parse_go needs to separately raise a parse error
Which is the best choice for the time being?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah I see, 2. sounds desirable to return a boolean, in that the function is only checking if a condition holds, then interpretation of the condition is left to the caller of the function. in the other usage the caller does e.g.
if !self.prev_whitespace_only_is_newline() {
return self.expected("newline ...")
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Gotcha, I will update it to do that
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done 👍
src/parser/mod.rs
Outdated
@@ -4055,6 +4070,44 @@ impl<'a> Parser<'a> { | |||
) | |||
} | |||
|
|||
/// Look backwards in the token stream and expect that there was only whitespace tokens until the previous newline | |||
pub fn expect_previously_only_whitespace_until_newline(&mut self) -> Result<(), ParserError> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hmm so i think it would be good to write this function in a reusable manner if possible since it feels similar to the peek_*
, prev_*
helper functions that we have. Would it be possible to introduce a generic prev_nth_token_no_skip(n: usize)
that can be called by parser and dialect methods?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done 👍
f3cbaa1
to
80591f1
Compare
src/parser/mod.rs
Outdated
@@ -15064,6 +15117,38 @@ impl<'a> Parser<'a> { | |||
})) | |||
} | |||
|
|||
/// Parse [Statement::Go] | |||
fn parse_go(&mut self) -> Result<Statement, ParserError> { | |||
self.expect_previously_only_whitespace_until_newline()?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah that makes sense! And the added comment clarifies as well thanks!
src/parser/mod.rs
Outdated
@@ -618,6 +632,7 @@ impl<'a> Parser<'a> { | |||
// `COMMENT` is snowflake specific https://docs.snowflake.com/en/sql-reference/sql/comment | |||
Keyword::COMMENT if self.dialect.supports_comment_on() => self.parse_comment(), | |||
Keyword::PRINT => self.parse_print(), | |||
Keyword::GO => self.parse_go(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed the code is inconsistent in that regard historically, for newer statements ideally the parser functions are self contained when possible
src/parser/mod.rs
Outdated
expecting_statement_delimiter = match &statement { | ||
Statement::If(s) => match &s.if_block.conditional_statements { | ||
// the `END` keyword doesn't need to be followed by a statement delimiter, so it shouldn't be expected here | ||
ConditionalStatements::BeginEnd { .. } => false, | ||
// parsing the statement sequence consumes the statement delimiter, so it shouldn't be expected here | ||
ConditionalStatements::Sequence { .. } => false, | ||
}, | ||
// Treat batch delimiter as an end of statement, so no additional statement delimiter expected here |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will this be replaced by similar solution as in #1808?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suspect so... need to investigate. (I need to rebuild my mental model on this first 😆).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've simplified this to reduce it down to only concerns within this PR 👍
// special consideration required for single line comments since that string includes the newline | ||
Whitespace::SingleLineComment { comment, prefix: _ } => { | ||
if comment.ends_with('\n') { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
double checking: is there a scenario where a single line comment doesn't end with a new line? spontaneously sounds like that should always hold true so that the manual newline check would not be required
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
double checking: is there a scenario where a single line comment doesn't end with a new line? spontaneously sounds like that should always hold true so that the manual newline check would not be required
I actually don't know, I was surprised to find that the newline is actually part of the comment text in this library. It seemed prudent to be defensive in this new code, since that comment parsing behavior maybe isn't particularly intentional.
src/parser/mod.rs
Outdated
@@ -3939,6 +3954,26 @@ impl<'a> Parser<'a> { | |||
}) | |||
} | |||
|
|||
/// Return nth previous token, possibly whitespace | |||
/// (or [`Token::EOF`] when before the beginning of the stream). | |||
pub fn peek_prev_nth_token_no_skip(&self, n: usize) -> TokenWithSpan { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pub fn peek_prev_nth_token_no_skip(&self, n: usize) -> TokenWithSpan { | |
pub(crate) fn peek_prev_nth_token_no_skip(&self, n: usize) -> &TokenWithSpan { |
we can probably start with the API being non-public. Then can we switch to returning a reference to avoid the mandatory cloning?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done 👍
src/dialect/mssql.rs
Outdated
.expect_previously_only_whitespace_until_newline() | ||
.is_ok() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah I see, 2. sounds desirable to return a boolean, in that the function is only checking if a condition holds, then interpretation of the condition is left to the caller of the function. in the other usage the caller does e.g.
if !self.prev_whitespace_only_is_newline() {
return self.expected("newline ...")
}
Here's another example case this PR should parse properly, before merging (on my todo list...)
The peek logic in parse_go should be seeing that newline, but it's accidentally advancing past the whitespace, seeing the semi-colon, and failing. Done 👍 |
- per documentation, "not a statement" but acts like one in all other regards - since it's a batch delimiter and statements can't extend beyond a batch, it also acts as a statement delimiter
- and use it for `expect_previously_only_whitespace_until_newline` so that can be simplified accordingly
b56ae20
to
b46fefc
Compare
…a bool - in `parse_go` we don't have a direct way to find what that previous non-whitespace token was, so the error message is adjusted accordingly
5972d59
to
6726d42
Compare
Reference: https://learn.microsoft.com/en-us/sql/t-sql/language-elements/sql-server-utilities-statements-go
Lots of conventional SQL Server tooling supports
GO
, so it seems really useful & prudent to be able to parse it. I realize that strictly speaking, it isn't a SQL "statement". However, I think it makes sense to model here as a statement since it acts as one in all regards except documentation.In terms of parsing,
GO
has some peculiarities I tried to express with the new tests, such as with regards to position and basically treating newline as a delimiter. This also starts to nudge on #1800, where newlines may possibly be interpreted as statement delimiters in a general case. However, my approach here forGO
is isolated to only that statement, at least for the time being.Also, this will conflict with #1808 when either is merged, because I'm adding things that end up being adjacent to each other which can confuse git, depending on merge strategy. There's no actual conflict.