Skip to content

Commit

Permalink
SqlServer: Add Cast to (n)varchar(max) when injecting Concat for mult…
Browse files Browse the repository at this point in the history
…i-line seed data

Resolves #24112
  • Loading branch information
smitpatel committed May 21, 2021
1 parent f8a6198 commit 90a7b2c
Show file tree
Hide file tree
Showing 4 changed files with 153 additions and 20 deletions.
117 changes: 116 additions & 1 deletion src/EFCore.SqlServer/Migrations/SqlServerMigrationsSqlGenerator.cs
Expand Up @@ -1877,6 +1877,7 @@ protected override void ForeignKeyAction(ReferentialAction referentialAction, Mi
bool omitVariableDeclarations = false)
{
var stringTypeMapping = Dependencies.TypeMappingSource.GetMapping(typeof(string));
var useOldBehavior = AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue24112", out var enabled) && enabled;

string schemaLiteral;
if (schema == null)
Expand Down Expand Up @@ -1923,7 +1924,121 @@ protected override void ForeignKeyAction(ReferentialAction referentialAction, Mi
builder.AppendLine(Dependencies.SqlGenerationHelper.StatementTerminator);

string Literal(string s)
=> stringTypeMapping.GenerateSqlLiteral(s);
=> useOldBehavior
? stringTypeMapping.GenerateSqlLiteral(s)
: SqlLiteral(s);

static string SqlLiteral(string value)
{
var builder = new StringBuilder();

var start = 0;
int i;
int length;
var openApostrophe = false;
var lastConcatStartPoint = 0;
var concatCount = 1;
var concatStartList = new List<int>();
for (i = 0; i < value.Length; i++)
{
var lineFeed = value[i] == '\n';
var carriageReturn = value[i] == '\r';
var apostrophe = value[i] == '\'';
if (lineFeed || carriageReturn || apostrophe)
{
length = i - start;
if (length != 0)
{
if (!openApostrophe)
{
AddConcatOperatorIfNeeded();
builder.Append("N\'");
openApostrophe = true;
}

builder.Append(value.AsSpan().Slice(start, length));
}

if (lineFeed || carriageReturn)
{
if (openApostrophe)
{
builder.Append('\'');
openApostrophe = false;
}

AddConcatOperatorIfNeeded();
builder
.Append("NCHAR(")
.Append(lineFeed ? "10" : "13")
.Append(')');
}
else if (apostrophe)
{
if (!openApostrophe)
{
AddConcatOperatorIfNeeded();
builder.Append("N'");
openApostrophe = true;
}
builder.Append("''");
}
start = i + 1;
}
}
length = i - start;
if (length != 0)
{
if (!openApostrophe)
{
AddConcatOperatorIfNeeded();
builder.Append("N\'");
openApostrophe = true;
}

builder.Append(value.AsSpan().Slice(start, length));
}

if (openApostrophe)
{
builder.Append('\'');
}

for (var j = concatStartList.Count - 1; j >= 0; j--)
{
builder.Insert(concatStartList[j], "CONCAT(");
builder.Append(')');
}

if (builder.Length == 0)
{
builder.Append("N''");
}

var result = builder.ToString();

return result;

void AddConcatOperatorIfNeeded()
{
if (builder.Length != 0)
{
builder.Append(", ");
concatCount++;

if (concatCount == 2)
{
concatStartList.Add(lastConcatStartPoint);
}

if (concatCount == 254)
{
lastConcatStartPoint = builder.Length;
concatCount = 1;
}
}
}
}
}

/// <summary>
Expand Down
Expand Up @@ -176,6 +176,8 @@ protected override string GenerateNonNullSqlLiteral(object value)
var concatCount = 1;
var concatStartList = new List<int>();
var useOldBehavior = AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue23518", out var enabled) && enabled;
var useOldBehavior2 = AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue24112", out var enabled2) && enabled2;
var castApplied = false;
for (i = 0; i < stringValue.Length; i++)
{
var lineFeed = stringValue[i] == '\n';
Expand Down Expand Up @@ -277,8 +279,12 @@ protected override string GenerateNonNullSqlLiteral(object value)
{
for (var j = concatStartList.Count - 1; j >= 0; j--)
{
builder.Insert(concatStartList[j], "CONCAT(")
.Append(')');
if (castApplied && j == 0)
{
builder.Insert(concatStartList[j], "CAST(");
}
builder.Insert(concatStartList[j], "CONCAT(");
builder.Append(')');
}
}

Expand All @@ -298,6 +304,18 @@ void AddConcatOperatorIfNeeded()
{
if (builder.Length != 0)
{
if (!useOldBehavior2
&& !castApplied)
{
builder.Append(" AS ");
if (IsUnicode)
{
builder.Append("N");
}
builder.Append("VARCHAR(MAX))");
castApplied = true;
}

builder.Append(", ");
if (useOldBehavior)
{
Expand Down
Expand Up @@ -963,7 +963,7 @@ public override void DefaultValue_with_line_breaks(bool isUnicode)
var storeType = isUnicode ? "nvarchar(max)" : "varchar(max)";
var unicodePrefix = isUnicode ? "N" : string.Empty;
var expectedSql = @$"CREATE TABLE [dbo].[TestLineBreaks] (
[TestDefaultValue] {storeType} NOT NULL DEFAULT CONCAT({unicodePrefix}CHAR(13), {unicodePrefix}CHAR(10), {unicodePrefix}'Various Line', {unicodePrefix}CHAR(13), {unicodePrefix}'Breaks', {unicodePrefix}CHAR(10))
[TestDefaultValue] {storeType} NOT NULL DEFAULT CONCAT(CAST({unicodePrefix}CHAR(13) AS {unicodePrefix}VARCHAR(MAX)), {unicodePrefix}CHAR(10), {unicodePrefix}'Various Line', {unicodePrefix}CHAR(13), {unicodePrefix}'Breaks', {unicodePrefix}CHAR(10))
);
";
AssertSql(expectedSql);
Expand Down
Expand Up @@ -19,14 +19,14 @@ public class SqlServerStringTypeMappingTest
[InlineData("I'm lovin' it", "'I''m lovin'' it'")]
[InlineData("\r", "CHAR(13)")]
[InlineData("\n", "CHAR(10)")]
[InlineData("\r\n", "CONCAT(CHAR(13), CHAR(10))")]
[InlineData("\n'sup", "CONCAT(CHAR(10), '''sup')")]
[InlineData("I'm\n", "CONCAT('I''m', CHAR(10))")]
[InlineData("lovin'\n", "CONCAT('lovin''', CHAR(10))")]
[InlineData("it\n", "CONCAT('it', CHAR(10))")]
[InlineData("\nit", "CONCAT(CHAR(10), 'it')")]
[InlineData("\nit\n", "CONCAT(CHAR(10), 'it', CHAR(10))")]
[InlineData("'\n", "CONCAT('''', CHAR(10))")]
[InlineData("\r\n", "CONCAT(CAST(CHAR(13) AS VARCHAR(MAX)), CHAR(10))")]
[InlineData("\n'sup", "CONCAT(CAST(CHAR(10) AS VARCHAR(MAX)), '''sup')")]
[InlineData("I'm\n", "CONCAT(CAST('I''m' AS VARCHAR(MAX)), CHAR(10))")]
[InlineData("lovin'\n", "CONCAT(CAST('lovin''' AS VARCHAR(MAX)), CHAR(10))")]
[InlineData("it\n", "CONCAT(CAST('it' AS VARCHAR(MAX)), CHAR(10))")]
[InlineData("\nit", "CONCAT(CAST(CHAR(10) AS VARCHAR(MAX)), 'it')")]
[InlineData("\nit\n", "CONCAT(CAST(CHAR(10) AS VARCHAR(MAX)), 'it', CHAR(10))")]
[InlineData("'\n", "CONCAT(CAST('''' AS VARCHAR(MAX)), CHAR(10))")]
public void GenerateProviderValueSqlLiteral_works(string value, string expected)
{
var mapping = new SqlServerStringTypeMapping("varchar(max)");
Expand All @@ -45,14 +45,14 @@ public void GenerateProviderValueSqlLiteral_works(string value, string expected)
[InlineData("I'm lovin' it", "N'I''m lovin'' it'")]
[InlineData("\r", "NCHAR(13)")]
[InlineData("\n", "NCHAR(10)")]
[InlineData("\r\n", "CONCAT(NCHAR(13), NCHAR(10))")]
[InlineData("\n'sup", "CONCAT(NCHAR(10), N'''sup')")]
[InlineData("I'm\n", "CONCAT(N'I''m', NCHAR(10))")]
[InlineData("lovin'\n", "CONCAT(N'lovin''', NCHAR(10))")]
[InlineData("it\n", "CONCAT(N'it', NCHAR(10))")]
[InlineData("\nit", "CONCAT(NCHAR(10), N'it')")]
[InlineData("\nit\n", "CONCAT(NCHAR(10), N'it', NCHAR(10))")]
[InlineData("'\n", "CONCAT(N'''', NCHAR(10))")]
[InlineData("\r\n", "CONCAT(CAST(NCHAR(13) AS NVARCHAR(MAX)), NCHAR(10))")]
[InlineData("\n'sup", "CONCAT(CAST(NCHAR(10) AS NVARCHAR(MAX)), N'''sup')")]
[InlineData("I'm\n", "CONCAT(CAST(N'I''m' AS NVARCHAR(MAX)), NCHAR(10))")]
[InlineData("lovin'\n", "CONCAT(CAST(N'lovin''' AS NVARCHAR(MAX)), NCHAR(10))")]
[InlineData("it\n", "CONCAT(CAST(N'it' AS NVARCHAR(MAX)), NCHAR(10))")]
[InlineData("\nit", "CONCAT(CAST(NCHAR(10) AS NVARCHAR(MAX)), N'it')")]
[InlineData("\nit\n", "CONCAT(CAST(NCHAR(10) AS NVARCHAR(MAX)), N'it', NCHAR(10))")]
[InlineData("'\n", "CONCAT(CAST(N'''' AS NVARCHAR(MAX)), NCHAR(10))")]
public void GenerateProviderValueSqlLiteral_works_unicode(string value, string expected)
{
var mapping = new SqlServerStringTypeMapping("nvarchar(max)", unicode: true);
Expand Down

0 comments on commit 90a7b2c

Please sign in to comment.