-
Notifications
You must be signed in to change notification settings - Fork 3.4k
[release/10.0] Fix GO batch separator not recognized when block comment contains single quote #37521
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: release/10.0
Are you sure you want to change the base?
Conversation
- Add tracking for block comments (/* ... */) to prevent incorrect quote state toggling - Block comments can now contain single quotes without affecting GO batch separator recognition - Add comprehensive tests for various block comment scenarios - All tests pass successfully Co-authored-by: AndriySvyryd <[email protected]>
src/EFCore.SqlServer/Migrations/SqlServerMigrationsSqlGenerator.cs
Outdated
Show resolved
Hide resolved
Removed separate if statements and i++ increments. Block comment detection now integrated into the main switch statement, making the code cleaner and more maintainable. Co-authored-by: AndriySvyryd <[email protected]>
test/EFCore.SqlServer.FunctionalTests/Migrations/SqlServerMigrationsSqlGeneratorTest.cs
Show resolved
Hide resolved
Added test to verify that /* inside a line comment (-- /*) doesn't trigger block comment state and GO is still correctly recognized as a batch separator. Co-authored-by: AndriySvyryd <[email protected]>
test/EFCore.SqlServer.FunctionalTests/Migrations/SqlServerMigrationsSqlGeneratorTest.cs
Show resolved
Hide resolved
- Changed all GO to go in new test SQL strings (will still appear as GO in output) - Added test for multiline block comment with go on separate line to verify go inside block comments is correctly ignored Co-authored-by: AndriySvyryd <[email protected]>
src/EFCore.SqlServer/Migrations/SqlServerMigrationsSqlGenerator.cs
Outdated
Show resolved
Hide resolved
Refactored to use foreach instead of for loop with index by tracking blockCommentStart and blockCommentEnd flags to detect /* and */ patterns across iterations. Co-authored-by: AndriySvyryd <[email protected]>
src/EFCore.SqlServer/Migrations/SqlServerMigrationsSqlGenerator.cs
Outdated
Show resolved
Hide resolved
src/EFCore.SqlServer/Migrations/SqlServerMigrationsSqlGenerator.cs
Outdated
Show resolved
Hide resolved
src/EFCore.SqlServer/Migrations/SqlServerMigrationsSqlGenerator.cs
Outdated
Show resolved
Hide resolved
|
@copilot Update progress by rewriting the PR summary using this template: |
Removed comment "Fall through to process c with Normal state" as the fall-through behavior is self-evident from the code structure. Co-authored-by: AndriySvyryd <[email protected]>
Removed "Fall through to process c with Normal state" comment as the behavior is clear from the code structure. Co-authored-by: AndriySvyryd <[email protected]>
src/EFCore.SqlServer/Migrations/SqlServerMigrationsSqlGenerator.cs
Outdated
Show resolved
Hide resolved
src/EFCore.SqlServer/Migrations/SqlServerMigrationsSqlGenerator.cs
Outdated
Show resolved
Hide resolved
src/EFCore.SqlServer/Migrations/SqlServerMigrationsSqlGenerator.cs
Outdated
Show resolved
Hide resolved
Removed redundant comments from Normal, Quoted, and InBlockComment cases that just stated "continue" as this behavior is self-evident from the break statements. Co-authored-by: AndriySvyryd <[email protected]>
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.
Pull request overview
This PR fixes a bug in SQL Server migrations where GO batch separators were not recognized when block comments contained single quotes. The issue was introduced in EF Core 9.0 when quote-tracking logic was added, but it didn't account for block comments.
Changes:
- Replaces simple boolean quote tracking with a state machine that properly handles SQL strings, line comments, and block comments
- Adds comprehensive test coverage with 11 new test cases covering various comment and string scenarios
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| src/EFCore.SqlServer/Migrations/SqlServerMigrationsSqlGenerator.cs | Implements a state machine with 6 states (Normal, Quoted, InBlockComment, MaybeLineComment, MaybeBlockCommentStart, MaybeBlockCommentEnd) to correctly parse SQL and ignore quotes inside block comments |
| test/EFCore.SqlServer.FunctionalTests/Migrations/SqlServerMigrationsSqlGeneratorTest.cs | Adds 11 test cases covering block comments with quotes, multiline comments, empty comments, nested comments/strings, and edge cases like GO inside comments |
src/EFCore.SqlServer/Migrations/SqlServerMigrationsSqlGenerator.cs
Outdated
Show resolved
Hide resolved
…r.cs Co-authored-by: Copilot <[email protected]>
artl93
left a comment
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.
@roji - With this being medium risk (though customer reported regression), is there an obvious workaround for the issue?
|
@artl93 The workarounds are straightforward once the root cause is discovered, but since the exception is very sparse on details: |
|
How can we reduce risk around the parser? |
I've added 11 new test cases. We could keep adding more |
artl93
left a comment
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.
Obviously, we want to avoid regression. The more we can add, the better, but otherwise, approved.
| if (state == ParsingState.MaybeLineComment | ||
| || state == ParsingState.MaybeBlockCommentStart | ||
| || state == ParsingState.MaybeBlockCommentEnd) |
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 (state == ParsingState.MaybeLineComment | |
| || state == ParsingState.MaybeBlockCommentStart | |
| || state == ParsingState.MaybeBlockCommentEnd) | |
| if (state is ParsingState.MaybeLineComment or ParsingState.MaybeBlockCommentStart or ParsingState.MaybeBlockCommentEnd) |
| switch (c) | ||
| // Handle MaybeLineComment and MaybeBlockCommentStart first | ||
| // When transitioning to Normal, fall through to process the current character | ||
| if (state == ParsingState.MaybeLineComment) |
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.
IIUC the "Maybe" states are for multi-character tokens (--, /*), representing the state where we've seen the first character but not the second. If so, this can probably be considerably simplified by iterating over the characters with a for loop instead of a foreach, and then simply peeking ahead to look at the second character (*) when we're on the first (/). For example, this would obviate the Maybe states reset code code.
| switch (c) | ||
| // Handle MaybeLineComment and MaybeBlockCommentStart first | ||
| // When transitioning to Normal, fall through to process the current character | ||
| if (state == ParsingState.MaybeLineComment) |
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.
Also, any reason these two ifs aren't simply cases within the switch below?
| commentStart = false; | ||
| case ParsingState.Normal when c == '/': | ||
| state = ParsingState.MaybeBlockCommentStart; | ||
| break; |
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 change to continue for (very) slight clarity.
| state = ParsingState.MaybeBlockCommentStart; | ||
| break; | ||
|
|
||
| case ParsingState.Normal when c == '\'': |
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.
What about GO within square brackets and double quotes (like if the SQL script has INSERT INTO [GO] ...)?
| state = ParsingState.Normal; | ||
| } | ||
|
|
||
| switch (state) |
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.
nit: consider changing this whole switch into a switch expression, where the default case simply returns the current state (i.e. no state change).
Fixes #37494
Description
EF Core splits SQL Server migration scripts into batches on lines starting with
GO. To avoid splitting inside string literals,SqlServerMigrationsSqlGeneratortracks whether it is inside a quoted string using a quoted boolean that toggles on single quotes ('). This quote-tracking logic was introduced in #34917.The implementation does not handle standard SQL block comments (
/* ... */). If a block comment contains a single quote, the quoted state is incorrectly toggled even though the quote is inside a comment. Since there is no matching closing quote, quoted remains true across subsequent lines. As a result, a laterGOline is not recognized as a batch separator and is sent to SQL Server as literal text, causing:Microsoft.Data.SqlClient.SqlException: Incorrect syntax near 'go'.Customer impact
Customers using SQL migrations with stored procedures or other SQL scripts that include block comments containing single quotes will experience migration failures with "Incorrect syntax near 'go'" errors. This can occur when documentation comments in SQL scripts use contractions or possessives (e.g., "It's", "user's").
Workarounds:
How found
Customer reported on 10.0.1
Regression
Yes, from EF Core 9.0. Introduced in #34917.
Testing
Added 11 tests.
Risk
Medium. The changes only affect custom SQL operations in migrations.
💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.