Skip to content

Commit 0e91fa1

Browse files
authored
Migrations: Generate valid SQL when escaping new lines in seed data (#23634)
Resolves #23518 Resolves #23459
1 parent 45d27f2 commit 0e91fa1

File tree

10 files changed

+181
-41
lines changed

10 files changed

+181
-41
lines changed

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

Lines changed: 50 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
5+
using System.Collections.Generic;
56
using System.Data;
67
using System.Data.Common;
78
using System.Text;
@@ -171,6 +172,10 @@ protected override string GenerateNonNullSqlLiteral(object value)
171172
int length;
172173
var concatenated = false;
173174
var openApostrophe = false;
175+
var lastConcatStartPoint = 0;
176+
var concatCount = 1;
177+
var concatStartList = new List<int>();
178+
var useOldBehavior = AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue23518", out var enabled) && enabled;
174179
for (i = 0; i < stringValue.Length; i++)
175180
{
176181
var lineFeed = stringValue[i] == '\n';
@@ -183,11 +188,7 @@ protected override string GenerateNonNullSqlLiteral(object value)
183188
{
184189
if (!openApostrophe)
185190
{
186-
if (builder.Length != 0)
187-
{
188-
builder.Append(", ");
189-
concatenated = true;
190-
}
191+
AddConcatOperatorIfNeeded();
191192

192193
if (IsUnicode)
193194
{
@@ -209,11 +210,7 @@ protected override string GenerateNonNullSqlLiteral(object value)
209210
openApostrophe = false;
210211
}
211212

212-
if (builder.Length != 0)
213-
{
214-
builder.Append(", ");
215-
concatenated = true;
216-
}
213+
AddConcatOperatorIfNeeded();
217214

218215
if (IsUnicode)
219216
{
@@ -229,11 +226,7 @@ protected override string GenerateNonNullSqlLiteral(object value)
229226
{
230227
if (!openApostrophe)
231228
{
232-
if (builder.Length != 0)
233-
{
234-
builder.Append(", ");
235-
concatenated = true;
236-
}
229+
AddConcatOperatorIfNeeded();
237230

238231
if (IsUnicode)
239232
{
@@ -253,11 +246,7 @@ protected override string GenerateNonNullSqlLiteral(object value)
253246
{
254247
if (!openApostrophe)
255248
{
256-
if (builder.Length != 0)
257-
{
258-
builder.Append(", ");
259-
concatenated = true;
260-
}
249+
AddConcatOperatorIfNeeded();
261250

262251
if (IsUnicode)
263252
{
@@ -276,11 +265,21 @@ protected override string GenerateNonNullSqlLiteral(object value)
276265
builder.Append('\'');
277266
}
278267

279-
if (concatenated)
268+
if (useOldBehavior)
269+
{
270+
if (concatenated)
271+
{
272+
builder.Insert(0, "CONCAT(")
273+
.Append(')');
274+
}
275+
}
276+
else
280277
{
281-
builder
282-
.Insert(0, "CONCAT(")
283-
.Append(')');
278+
for (var j = concatStartList.Count - 1; j >= 0; j--)
279+
{
280+
builder.Insert(concatStartList[j], "CONCAT(")
281+
.Append(')');
282+
}
284283
}
285284

286285
if (builder.Length == 0)
@@ -294,6 +293,33 @@ protected override string GenerateNonNullSqlLiteral(object value)
294293
}
295294

296295
return builder.ToString();
296+
297+
void AddConcatOperatorIfNeeded()
298+
{
299+
if (builder.Length != 0)
300+
{
301+
builder.Append(", ");
302+
if (useOldBehavior)
303+
{
304+
concatenated = true;
305+
}
306+
else
307+
{
308+
concatCount++;
309+
310+
if (concatCount == 2)
311+
{
312+
concatStartList.Add(lastConcatStartPoint);
313+
}
314+
315+
if (concatCount == 254)
316+
{
317+
lastConcatStartPoint = builder.Length;
318+
concatCount = 1;
319+
}
320+
}
321+
}
322+
}
297323
}
298324
}
299325
}

src/EFCore.Sqlite.Core/Storage/Internal/SqliteStringTypeMapping.cs

Lines changed: 93 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
5+
using System.Collections.Generic;
56
using System.Data;
67
using System.Text;
78
using JetBrains.Annotations;
@@ -61,11 +62,15 @@ protected override string GenerateNonNullSqlLiteral(object value)
6162
var stringValue = (string)value;
6263
var builder = new StringBuilder();
6364

65+
var useOldBehavior = AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue23459", out var enabled) && enabled;
66+
6467
var start = 0;
6568
int i;
6669
int length;
6770
var concatenated = false;
6871
var openApostrophe = false;
72+
var lengths = new List<int>();
73+
var startIndexes = new List<int> { 0 };
6974
for (i = 0; i < stringValue.Length; i++)
7075
{
7176
var lineFeed = stringValue[i] == '\n';
@@ -80,8 +85,16 @@ protected override string GenerateNonNullSqlLiteral(object value)
8085
{
8186
if (builder.Length != 0)
8287
{
83-
builder.Append(" || ");
84-
concatenated = true;
88+
if (useOldBehavior)
89+
{
90+
builder.Append(" || ");
91+
concatenated = true;
92+
}
93+
else
94+
{
95+
lengths.Add(builder.Length - startIndexes[^1]);
96+
startIndexes.Add(builder.Length);
97+
}
8598
}
8699

87100
builder.Append('\'');
@@ -101,8 +114,16 @@ protected override string GenerateNonNullSqlLiteral(object value)
101114

102115
if (builder.Length != 0)
103116
{
104-
builder.Append(" || ");
105-
concatenated = true;
117+
if (useOldBehavior)
118+
{
119+
builder.Append(" || ");
120+
concatenated = true;
121+
}
122+
else
123+
{
124+
lengths.Add(builder.Length - startIndexes[^1]);
125+
startIndexes.Add(builder.Length);
126+
}
106127
}
107128

108129
builder
@@ -117,8 +138,16 @@ protected override string GenerateNonNullSqlLiteral(object value)
117138
{
118139
if (builder.Length != 0)
119140
{
120-
builder.Append(" || ");
121-
concatenated = true;
141+
if (useOldBehavior)
142+
{
143+
builder.Append(" || ");
144+
concatenated = true;
145+
}
146+
else
147+
{
148+
lengths.Add(builder.Length - startIndexes[^1]);
149+
startIndexes.Add(builder.Length);
150+
}
122151
}
123152

124153
builder.Append("'");
@@ -139,8 +168,16 @@ protected override string GenerateNonNullSqlLiteral(object value)
139168
{
140169
if (builder.Length != 0)
141170
{
142-
builder.Append(" || ");
143-
concatenated = true;
171+
if (useOldBehavior)
172+
{
173+
builder.Append(" || ");
174+
concatenated = true;
175+
}
176+
else
177+
{
178+
lengths.Add(builder.Length - startIndexes[^1]);
179+
startIndexes.Add(builder.Length);
180+
}
144181
}
145182

146183
builder.Append('\'');
@@ -155,19 +192,60 @@ protected override string GenerateNonNullSqlLiteral(object value)
155192
builder.Append('\'');
156193
}
157194

158-
if (concatenated)
195+
if (useOldBehavior)
159196
{
160-
builder
161-
.Insert(0, '(')
162-
.Append(')');
197+
if (concatenated)
198+
{
199+
builder
200+
.Insert(0, '(')
201+
.Append(')');
202+
}
203+
204+
if (builder.Length == 0)
205+
{
206+
builder.Append("''");
207+
}
208+
209+
return builder.ToString();
163210
}
164211

165-
if (builder.Length == 0)
212+
if (builder.Length != 0)
166213
{
167-
builder.Append("''");
214+
lengths.Add(builder.Length - startIndexes[^1]);
168215
}
169216

170-
return builder.ToString();
217+
if (lengths.Count == 0
218+
&& builder.Length == 0)
219+
{
220+
return "''";
221+
}
222+
223+
var newBuilder = new StringBuilder();
224+
GenerateBalancedTree(0, lengths.Count);
225+
226+
return newBuilder.ToString();
227+
228+
void GenerateBalancedTree(int start, int end)
229+
{
230+
var count = end - start;
231+
if (count < 1)
232+
{
233+
return;
234+
}
235+
236+
if (count == 1)
237+
{
238+
newBuilder.Append(builder, startIndexes[start], lengths[start]);
239+
return;
240+
}
241+
242+
var mid = start + count / 2;
243+
newBuilder.Append("(");
244+
GenerateBalancedTree(start, mid);
245+
newBuilder.Append(" || ");
246+
GenerateBalancedTree(mid, end);
247+
newBuilder.Append(")");
248+
}
171249
}
172250
}
173251
}

test/EFCore.Specification.Tests/BuiltInDataTypesTestBase.cs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2084,13 +2084,24 @@ public virtual void Optional_datetime_reading_null_from_database()
20842084
}
20852085
}
20862086

2087+
[ConditionalFact]
2088+
public virtual void Can_insert_query_multiline_string()
2089+
{
2090+
using var context = CreateContext();
2091+
2092+
Assert.Equal(Fixture.ReallyLargeString, Assert.Single(context.Set<StringEnclosure>()).Value);
2093+
}
2094+
20872095
public abstract class BuiltInDataTypesFixtureBase : SharedStoreFixtureBase<PoolableDbContext>
20882096
{
20892097
protected override string StoreName { get; } = "BuiltInDataTypes";
20902098

20912099
public virtual int LongStringLength
20922100
=> 9000;
20932101

2102+
public virtual string ReallyLargeString
2103+
=> string.Join("", Enumerable.Repeat(Environment.NewLine, 1001));
2104+
20942105
protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context)
20952106
{
20962107
modelBuilder.Entity<BinaryKeyDataType>();
@@ -2346,6 +2357,14 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con
23462357
.HasData(
23472358
new DateTimeEnclosure { Id = 1, DateTimeOffset = new DateTimeOffset(2020, 3, 12, 1, 1, 1, new TimeSpan(3, 0, 0)) },
23482359
new DateTimeEnclosure { Id = 2 });
2360+
2361+
modelBuilder.Entity<StringEnclosure>()
2362+
.HasData(
2363+
new StringEnclosure
2364+
{
2365+
Id = 1,
2366+
Value = ReallyLargeString
2367+
});
23492368
}
23502369

23512370
protected static void MakeRequired<TEntity>(ModelBuilder modelBuilder)
@@ -3152,5 +3171,12 @@ protected class DateTimeEnclosure
31523171
public int Id { get; set; }
31533172
public DateTimeOffset? DateTimeOffset { get; set; }
31543173
}
3174+
3175+
protected class StringEnclosure
3176+
{
3177+
public int Id { get; set; }
3178+
3179+
public string Value { get; set; }
3180+
}
31553181
}
31563182
}

test/EFCore.SqlServer.FunctionalTests/BuiltInDataTypesSqlServerTest.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3329,6 +3329,8 @@ public virtual void Columns_have_expected_data_types()
33293329
MaxLengthDataTypes.Id ---> [int] [Precision = 10 Scale = 0]
33303330
MaxLengthDataTypes.String3 ---> [nullable nvarchar] [MaxLength = 3]
33313331
MaxLengthDataTypes.String9000 ---> [nullable nvarchar] [MaxLength = -1]
3332+
StringEnclosure.Id ---> [int] [Precision = 10 Scale = 0]
3333+
StringEnclosure.Value ---> [nullable nvarchar] [MaxLength = -1]
33323334
StringForeignKeyDataType.Id ---> [int] [Precision = 10 Scale = 0]
33333335
StringForeignKeyDataType.StringKeyDataTypeId ---> [nullable nvarchar] [MaxLength = 450]
33343336
StringKeyDataType.Id ---> [nvarchar] [MaxLength = 450]

test/EFCore.SqlServer.FunctionalTests/ConvertToProviderTypesSqlServerTest.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,8 @@ public virtual void Columns_have_expected_data_types()
152152
MaxLengthDataTypes.Id ---> [int] [Precision = 10 Scale = 0]
153153
MaxLengthDataTypes.String3 ---> [nullable varbinary] [MaxLength = 3]
154154
MaxLengthDataTypes.String9000 ---> [nullable varbinary] [MaxLength = -1]
155+
StringEnclosure.Id ---> [int] [Precision = 10 Scale = 0]
156+
StringEnclosure.Value ---> [nullable nvarchar] [MaxLength = -1]
155157
StringForeignKeyDataType.Id ---> [int] [Precision = 10 Scale = 0]
156158
StringForeignKeyDataType.StringKeyDataTypeId ---> [nullable varbinary] [MaxLength = 900]
157159
StringKeyDataType.Id ---> [varbinary] [MaxLength = 900]

test/EFCore.SqlServer.FunctionalTests/CustomConvertersSqlServerTest.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,8 @@ public virtual void Columns_have_expected_data_types()
184184
SimpleCounter.Discriminator ---> [nullable nvarchar] [MaxLength = -1]
185185
SimpleCounter.IsTest ---> [bit]
186186
SimpleCounter.StyleKey ---> [nullable nvarchar] [MaxLength = -1]
187+
StringEnclosure.Id ---> [int] [Precision = 10 Scale = 0]
188+
StringEnclosure.Value ---> [nullable nvarchar] [MaxLength = -1]
187189
StringForeignKeyDataType.Id ---> [int] [Precision = 10 Scale = 0]
188190
StringForeignKeyDataType.StringKeyDataTypeId ---> [nullable nvarchar] [MaxLength = 450]
189191
StringKeyDataType.Id ---> [nvarchar] [MaxLength = 450]

test/EFCore.SqlServer.FunctionalTests/EverythingIsBytesSqlServerTest.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,8 @@ public virtual void Columns_have_expected_data_types()
154154
MaxLengthDataTypes.Id ---> [varbinary] [MaxLength = 4]
155155
MaxLengthDataTypes.String3 ---> [nullable varbinary] [MaxLength = 3]
156156
MaxLengthDataTypes.String9000 ---> [nullable varbinary] [MaxLength = -1]
157+
StringEnclosure.Id ---> [varbinary] [MaxLength = 4]
158+
StringEnclosure.Value ---> [nullable varbinary] [MaxLength = -1]
157159
StringForeignKeyDataType.Id ---> [varbinary] [MaxLength = 4]
158160
StringForeignKeyDataType.StringKeyDataTypeId ---> [nullable varbinary] [MaxLength = 900]
159161
StringKeyDataType.Id ---> [varbinary] [MaxLength = 900]

test/EFCore.SqlServer.FunctionalTests/EverythingIsStringsSqlServerTest.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,8 @@ public virtual void Columns_have_expected_data_types()
155155
MaxLengthDataTypes.Id ---> [nvarchar] [MaxLength = 64]
156156
MaxLengthDataTypes.String3 ---> [nullable nvarchar] [MaxLength = 3]
157157
MaxLengthDataTypes.String9000 ---> [nullable nvarchar] [MaxLength = -1]
158+
StringEnclosure.Id ---> [nvarchar] [MaxLength = 64]
159+
StringEnclosure.Value ---> [nullable nvarchar] [MaxLength = -1]
158160
StringForeignKeyDataType.Id ---> [nvarchar] [MaxLength = 64]
159161
StringForeignKeyDataType.StringKeyDataTypeId ---> [nullable nvarchar] [MaxLength = 450]
160162
StringKeyDataType.Id ---> [nvarchar] [MaxLength = 450]

0 commit comments

Comments
 (0)