Skip to content

Commit 707896a

Browse files
authored
SqlServer: Add Cast to (n)varchar(max) when injecting Concat for multi-line seed data (#24944)
Resolves #24112
1 parent 78e9cb3 commit 707896a

File tree

4 files changed

+160
-26
lines changed

4 files changed

+160
-26
lines changed

src/EFCore.SqlServer/Migrations/SqlServerMigrationsSqlGenerator.cs

+116-1
Original file line numberDiff line numberDiff line change
@@ -1877,6 +1877,7 @@ protected virtual void AddDescription(
18771877
bool omitVariableDeclarations = false)
18781878
{
18791879
var stringTypeMapping = Dependencies.TypeMappingSource.GetMapping(typeof(string));
1880+
var useOldBehavior = AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue24112", out var enabled) && enabled;
18801881

18811882
string schemaLiteral;
18821883
if (schema == null)
@@ -1923,7 +1924,121 @@ protected virtual void AddDescription(
19231924
builder.AppendLine(Dependencies.SqlGenerationHelper.StatementTerminator);
19241925

19251926
string Literal(string s)
1926-
=> stringTypeMapping.GenerateSqlLiteral(s);
1927+
=> useOldBehavior
1928+
? stringTypeMapping.GenerateSqlLiteral(s)
1929+
: SqlLiteral(s);
1930+
1931+
static string SqlLiteral(string value)
1932+
{
1933+
var builder = new StringBuilder();
1934+
1935+
var start = 0;
1936+
int i;
1937+
int length;
1938+
var openApostrophe = false;
1939+
var lastConcatStartPoint = 0;
1940+
var concatCount = 1;
1941+
var concatStartList = new List<int>();
1942+
for (i = 0; i < value.Length; i++)
1943+
{
1944+
var lineFeed = value[i] == '\n';
1945+
var carriageReturn = value[i] == '\r';
1946+
var apostrophe = value[i] == '\'';
1947+
if (lineFeed || carriageReturn || apostrophe)
1948+
{
1949+
length = i - start;
1950+
if (length != 0)
1951+
{
1952+
if (!openApostrophe)
1953+
{
1954+
AddConcatOperatorIfNeeded();
1955+
builder.Append("N\'");
1956+
openApostrophe = true;
1957+
}
1958+
1959+
builder.Append(value.AsSpan().Slice(start, length));
1960+
}
1961+
1962+
if (lineFeed || carriageReturn)
1963+
{
1964+
if (openApostrophe)
1965+
{
1966+
builder.Append('\'');
1967+
openApostrophe = false;
1968+
}
1969+
1970+
AddConcatOperatorIfNeeded();
1971+
builder
1972+
.Append("NCHAR(")
1973+
.Append(lineFeed ? "10" : "13")
1974+
.Append(')');
1975+
}
1976+
else if (apostrophe)
1977+
{
1978+
if (!openApostrophe)
1979+
{
1980+
AddConcatOperatorIfNeeded();
1981+
builder.Append("N'");
1982+
openApostrophe = true;
1983+
}
1984+
builder.Append("''");
1985+
}
1986+
start = i + 1;
1987+
}
1988+
}
1989+
length = i - start;
1990+
if (length != 0)
1991+
{
1992+
if (!openApostrophe)
1993+
{
1994+
AddConcatOperatorIfNeeded();
1995+
builder.Append("N\'");
1996+
openApostrophe = true;
1997+
}
1998+
1999+
builder.Append(value.AsSpan().Slice(start, length));
2000+
}
2001+
2002+
if (openApostrophe)
2003+
{
2004+
builder.Append('\'');
2005+
}
2006+
2007+
for (var j = concatStartList.Count - 1; j >= 0; j--)
2008+
{
2009+
builder.Insert(concatStartList[j], "CONCAT(");
2010+
builder.Append(')');
2011+
}
2012+
2013+
if (builder.Length == 0)
2014+
{
2015+
builder.Append("N''");
2016+
}
2017+
2018+
var result = builder.ToString();
2019+
2020+
return result;
2021+
2022+
void AddConcatOperatorIfNeeded()
2023+
{
2024+
if (builder.Length != 0)
2025+
{
2026+
builder.Append(", ");
2027+
concatCount++;
2028+
2029+
if (concatCount == 2)
2030+
{
2031+
concatStartList.Add(lastConcatStartPoint);
2032+
}
2033+
2034+
if (concatCount == 254)
2035+
{
2036+
lastConcatStartPoint = builder.Length;
2037+
concatCount = 1;
2038+
}
2039+
}
2040+
}
2041+
}
19272042
}
19282043

19292044
/// <summary>

src/EFCore.SqlServer/Storage/Internal/SqlServerStringTypeMapping.cs

+22-4
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,8 @@ protected override string GenerateNonNullSqlLiteral(object value)
176176
var concatCount = 1;
177177
var concatStartList = new List<int>();
178178
var useOldBehavior = AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue23518", out var enabled) && enabled;
179+
var useOldBehavior2 = AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue24112", out var enabled2) && enabled2;
180+
var castApplied = false;
179181
for (i = 0; i < stringValue.Length; i++)
180182
{
181183
var lineFeed = stringValue[i] == '\n';
@@ -214,11 +216,11 @@ protected override string GenerateNonNullSqlLiteral(object value)
214216

215217
if (IsUnicode)
216218
{
217-
builder.Append('N');
219+
builder.Append('n');
218220
}
219221

220222
builder
221-
.Append("CHAR(")
223+
.Append("char(")
222224
.Append(lineFeed ? "10" : "13")
223225
.Append(')');
224226
}
@@ -277,8 +279,12 @@ protected override string GenerateNonNullSqlLiteral(object value)
277279
{
278280
for (var j = concatStartList.Count - 1; j >= 0; j--)
279281
{
280-
builder.Insert(concatStartList[j], "CONCAT(")
281-
.Append(')');
282+
if (castApplied && j == 0)
283+
{
284+
builder.Insert(concatStartList[j], "CAST(");
285+
}
286+
builder.Insert(concatStartList[j], "CONCAT(");
287+
builder.Append(')');
282288
}
283289
}
284290

@@ -298,6 +304,18 @@ void AddConcatOperatorIfNeeded()
298304
{
299305
if (builder.Length != 0)
300306
{
307+
if (!useOldBehavior2
308+
&& !castApplied)
309+
{
310+
builder.Append(" AS ");
311+
if (IsUnicode)
312+
{
313+
builder.Append("n");
314+
}
315+
builder.Append("varchar(max))");
316+
castApplied = true;
317+
}
318+
301319
builder.Append(", ");
302320
if (useOldBehavior)
303321
{

test/EFCore.SqlServer.Tests/Migrations/SqlServerMigrationsSqlGeneratorTest.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -962,8 +962,9 @@ public override void DefaultValue_with_line_breaks(bool isUnicode)
962962

963963
var storeType = isUnicode ? "nvarchar(max)" : "varchar(max)";
964964
var unicodePrefix = isUnicode ? "N" : string.Empty;
965+
var unicodePrefixForType = isUnicode ? "n" : string.Empty;
965966
var expectedSql = @$"CREATE TABLE [dbo].[TestLineBreaks] (
966-
[TestDefaultValue] {storeType} NOT NULL DEFAULT CONCAT({unicodePrefix}CHAR(13), {unicodePrefix}CHAR(10), {unicodePrefix}'Various Line', {unicodePrefix}CHAR(13), {unicodePrefix}'Breaks', {unicodePrefix}CHAR(10))
967+
[TestDefaultValue] {storeType} NOT NULL DEFAULT CONCAT(CAST({unicodePrefixForType}char(13) AS {storeType}), {unicodePrefixForType}char(10), {unicodePrefix}'Various Line', {unicodePrefixForType}char(13), {unicodePrefix}'Breaks', {unicodePrefixForType}char(10))
967968
);
968969
";
969970
AssertSql(expectedSql);

test/EFCore.SqlServer.Tests/Storage/SqlServerStringTypeMappingTest.cs

+20-20
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,16 @@ public class SqlServerStringTypeMappingTest
1717
[InlineData("''", "''''''")]
1818
[InlineData("I'm lovin'", "'I''m lovin'''")]
1919
[InlineData("I'm lovin' it", "'I''m lovin'' it'")]
20-
[InlineData("\r", "CHAR(13)")]
21-
[InlineData("\n", "CHAR(10)")]
22-
[InlineData("\r\n", "CONCAT(CHAR(13), CHAR(10))")]
23-
[InlineData("\n'sup", "CONCAT(CHAR(10), '''sup')")]
24-
[InlineData("I'm\n", "CONCAT('I''m', CHAR(10))")]
25-
[InlineData("lovin'\n", "CONCAT('lovin''', CHAR(10))")]
26-
[InlineData("it\n", "CONCAT('it', CHAR(10))")]
27-
[InlineData("\nit", "CONCAT(CHAR(10), 'it')")]
28-
[InlineData("\nit\n", "CONCAT(CHAR(10), 'it', CHAR(10))")]
29-
[InlineData("'\n", "CONCAT('''', CHAR(10))")]
20+
[InlineData("\r", "char(13)")]
21+
[InlineData("\n", "char(10)")]
22+
[InlineData("\r\n", "CONCAT(CAST(char(13) AS varchar(max)), char(10))")]
23+
[InlineData("\n'sup", "CONCAT(CAST(char(10) AS varchar(max)), '''sup')")]
24+
[InlineData("I'm\n", "CONCAT(CAST('I''m' AS varchar(max)), char(10))")]
25+
[InlineData("lovin'\n", "CONCAT(CAST('lovin''' AS varchar(max)), char(10))")]
26+
[InlineData("it\n", "CONCAT(CAST('it' AS varchar(max)), char(10))")]
27+
[InlineData("\nit", "CONCAT(CAST(char(10) AS varchar(max)), 'it')")]
28+
[InlineData("\nit\n", "CONCAT(CAST(char(10) AS varchar(max)), 'it', char(10))")]
29+
[InlineData("'\n", "CONCAT(CAST('''' AS varchar(max)), char(10))")]
3030
public void GenerateProviderValueSqlLiteral_works(string value, string expected)
3131
{
3232
var mapping = new SqlServerStringTypeMapping("varchar(max)");
@@ -43,16 +43,16 @@ public void GenerateProviderValueSqlLiteral_works(string value, string expected)
4343
[InlineData("''", "N''''''")]
4444
[InlineData("I'm lovin'", "N'I''m lovin'''")]
4545
[InlineData("I'm lovin' it", "N'I''m lovin'' it'")]
46-
[InlineData("\r", "NCHAR(13)")]
47-
[InlineData("\n", "NCHAR(10)")]
48-
[InlineData("\r\n", "CONCAT(NCHAR(13), NCHAR(10))")]
49-
[InlineData("\n'sup", "CONCAT(NCHAR(10), N'''sup')")]
50-
[InlineData("I'm\n", "CONCAT(N'I''m', NCHAR(10))")]
51-
[InlineData("lovin'\n", "CONCAT(N'lovin''', NCHAR(10))")]
52-
[InlineData("it\n", "CONCAT(N'it', NCHAR(10))")]
53-
[InlineData("\nit", "CONCAT(NCHAR(10), N'it')")]
54-
[InlineData("\nit\n", "CONCAT(NCHAR(10), N'it', NCHAR(10))")]
55-
[InlineData("'\n", "CONCAT(N'''', NCHAR(10))")]
46+
[InlineData("\r", "nchar(13)")]
47+
[InlineData("\n", "nchar(10)")]
48+
[InlineData("\r\n", "CONCAT(CAST(nchar(13) AS nvarchar(max)), nchar(10))")]
49+
[InlineData("\n'sup", "CONCAT(CAST(nchar(10) AS nvarchar(max)), N'''sup')")]
50+
[InlineData("I'm\n", "CONCAT(CAST(N'I''m' AS nvarchar(max)), nchar(10))")]
51+
[InlineData("lovin'\n", "CONCAT(CAST(N'lovin''' AS nvarchar(max)), nchar(10))")]
52+
[InlineData("it\n", "CONCAT(CAST(N'it' AS nvarchar(max)), nchar(10))")]
53+
[InlineData("\nit", "CONCAT(CAST(nchar(10) AS nvarchar(max)), N'it')")]
54+
[InlineData("\nit\n", "CONCAT(CAST(nchar(10) AS nvarchar(max)), N'it', nchar(10))")]
55+
[InlineData("'\n", "CONCAT(CAST(N'''' AS nvarchar(max)), nchar(10))")]
5656
public void GenerateProviderValueSqlLiteral_works_unicode(string value, string expected)
5757
{
5858
var mapping = new SqlServerStringTypeMapping("nvarchar(max)", unicode: true);

0 commit comments

Comments
 (0)