Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1388,6 +1388,16 @@ protected override void Generate(
builder.EndCommand();
}

private enum ParsingState
{
Normal,
Quoted,
InBlockComment,
MaybeLineComment,
MaybeBlockCommentStart,
MaybeBlockCommentEnd
}

/// <summary>
/// Builds commands for the given <see cref="SqlOperation" /> by making calls on the given
/// <see cref="MigrationCommandListBuilder" />, and then terminates the final command.
Expand All @@ -1402,12 +1412,20 @@ protected override void Generate(SqlOperation operation, IModel? model, Migratio
.Replace("\\\r\n", "")
.Split(["\r\n", "\n"], StringSplitOptions.None);

var quoted = false;
var state = ParsingState.Normal;
var batchBuilder = new StringBuilder();
foreach (var line in preBatched)
{
var trimmed = line.TrimStart();
if (!quoted
// Reset "Maybe" states at line start
if (state == ParsingState.MaybeLineComment
|| state == ParsingState.MaybeBlockCommentStart
|| state == ParsingState.MaybeBlockCommentEnd)
{
state = state == ParsingState.MaybeBlockCommentEnd ? ParsingState.InBlockComment : ParsingState.Normal;
}

if (state == ParsingState.Normal
&& trimmed.StartsWith("GO", StringComparison.OrdinalIgnoreCase)
&& (trimmed.Length == 2
|| char.IsWhiteSpace(trimmed[2])))
Expand All @@ -1427,30 +1445,73 @@ protected override void Generate(SqlOperation operation, IModel? model, Migratio
}
else
{
var commentStart = false;
foreach (var c in trimmed)
{
switch (c)
// Handle MaybeLineComment and MaybeBlockCommentStart first
// When transitioning to Normal, fall through to process the current character
if (state == ParsingState.MaybeLineComment)
{
if (c == '-')
{
goto LineEnd;
}
state = ParsingState.Normal;
}
else if (state == ParsingState.MaybeBlockCommentStart)
{
if (c == '*')
{
state = ParsingState.InBlockComment;
continue;
}
state = ParsingState.Normal;
}

switch (state)
{
case '\'':
quoted = !quoted;
commentStart = false;
case ParsingState.Normal when c == '/':
state = ParsingState.MaybeBlockCommentStart;
break;

case ParsingState.Normal when c == '\'':
state = ParsingState.Quoted;
break;

case ParsingState.Normal when c == '-':
state = ParsingState.MaybeLineComment;
break;

case ParsingState.Normal:
break;
case '-':
if (!quoted)
{
if (commentStart)
{
goto LineEnd;
}

commentStart = true;
}
case ParsingState.Quoted when c == '\'':
state = ParsingState.Normal;
break;

case ParsingState.Quoted:
break;

case ParsingState.InBlockComment when c == '*':
state = ParsingState.MaybeBlockCommentEnd;
break;
default:
commentStart = false;

case ParsingState.InBlockComment:
break;

case ParsingState.MaybeBlockCommentEnd when c == '/':
state = ParsingState.Normal;
break;

case ParsingState.MaybeBlockCommentEnd when c == '*':
// Stay in MaybeBlockCommentEnd for consecutive asterisks
break;

case ParsingState.MaybeBlockCommentEnd:
state = ParsingState.InBlockComment;
break;

default:
throw new UnreachableException();
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -800,6 +800,207 @@ public override void SqlOperation()
""");
}

[ConditionalFact]
public virtual void SqlOperation_handles_block_comment_with_single_quote()
{
Generate(
new SqlOperation { Sql = "/* It's a comment */" + EOL + "SELECT 1;" + EOL + "go" + EOL + "SELECT 2;" });

AssertSql(
"""
/* It's a comment */
SELECT 1;
GO

SELECT 2;
""");
}

[ConditionalFact]
public virtual void SqlOperation_handles_multiline_block_comment_with_single_quote()
{
Generate(
new SqlOperation
{
Sql = "/* This is" + EOL + " a multiline comment with ' quote */" + EOL + "SELECT 1;" + EOL + "go" + EOL + "SELECT 2;"
});

AssertSql(
"""
/* This is
a multiline comment with ' quote */
SELECT 1;
GO

SELECT 2;
""");
}

[ConditionalFact]
public virtual void SqlOperation_handles_block_comment_with_multiple_quotes()
{
Generate(
new SqlOperation
{
Sql = "/* It's a comment with 'multiple' quotes */" + EOL + "SELECT 1;" + EOL + "go" + EOL + "SELECT 2;"
});

AssertSql(
"""
/* It's a comment with 'multiple' quotes */
SELECT 1;
GO

SELECT 2;
""");
}

[ConditionalFact]
public virtual void SqlOperation_handles_block_comment_before_procedure()
{
Generate(
new SqlOperation
{
Sql = "/* It's a procedure */" + EOL + "CREATE PROCEDURE dbo.proc1 AS SELECT 1;" + EOL + "go" + EOL
+ "/* Another one */" + EOL + "CREATE PROCEDURE dbo.proc2 AS SELECT 2;"
});

AssertSql(
"""
/* It's a procedure */
CREATE PROCEDURE dbo.proc1 AS SELECT 1;
GO

/* Another one */
CREATE PROCEDURE dbo.proc2 AS SELECT 2;
""");
}

[ConditionalFact]
public virtual void SqlOperation_handles_empty_block_comment()
{
Generate(
new SqlOperation { Sql = "/**/" + EOL + "SELECT 1;" + EOL + "go" + EOL + "SELECT 2;" });

AssertSql(
"""
/**/
SELECT 1;
GO

SELECT 2;
""");
}

[ConditionalFact]
public virtual void SqlOperation_handles_nested_comments_and_strings()
{
Generate(
new SqlOperation
{
Sql = "/* Block comment */" + EOL + "SELECT 'string with '' escaped quotes';" + EOL + "go" + EOL
+ "-- Line comment" + EOL + "SELECT 1;"
});

AssertSql(
"""
/* Block comment */
SELECT 'string with '' escaped quotes';
GO

-- Line comment
SELECT 1;
""");
}

[ConditionalFact]
public virtual void SqlOperation_handles_block_comment_after_string()
{
Generate(
new SqlOperation { Sql = "SELECT 'test';" + EOL + "/* It's a comment */" + EOL + "go" + EOL + "SELECT 2;" });

AssertSql(
"""
SELECT 'test';
/* It's a comment */
GO

SELECT 2;
""");
}

[ConditionalFact]
public virtual void SqlOperation_handles_block_comment_with_asterisks()
{
Generate(
new SqlOperation
{
Sql = "/** It's a comment with extra stars **/" + EOL + "SELECT 1;" + EOL + "go" + EOL + "SELECT 2;"
});

AssertSql(
"""
/** It's a comment with extra stars **/
SELECT 1;
GO

SELECT 2;
""");
}

[ConditionalFact]
public virtual void SqlOperation_handles_line_comment_with_block_comment_start()
{
Generate(
new SqlOperation { Sql = "-- /*" + EOL + "SELECT 1;" + EOL + "go" + EOL + "SELECT 2;" });

AssertSql(
"""
-- /*
SELECT 1;
GO

SELECT 2;
""");
}

[ConditionalFact]
public virtual void SqlOperation_handles_block_comment_with_line_comment_inside()
{
Generate(
new SqlOperation { Sql = "/* Block comment -- with line comment */" + EOL + "SELECT 1;" + EOL + "go" + EOL + "SELECT 2;" });

AssertSql(
"""
/* Block comment -- with line comment */
SELECT 1;
GO

SELECT 2;
""");
}

[ConditionalFact]
public virtual void SqlOperation_handles_multiline_block_comment_with_go_on_separate_line()
{
Generate(
new SqlOperation
{
Sql = "/* Comment with" + EOL + "go" + EOL + "inside */" + EOL + "SELECT 1;" + EOL + "go" + EOL + "SELECT 2;"
});

AssertSql(
"""
/* Comment with
go
inside */
SELECT 1;
GO

SELECT 2;
""");
}

public override void InsertDataOperation_all_args_spatial()
{
base.InsertDataOperation_all_args_spatial();
Expand Down