Skip to content

Commit 9f804ca

Browse files
committed
Support StringBuilder in MySqlParameter. Fixes #975
1 parent ba778fa commit 9f804ca

File tree

5 files changed

+129
-9
lines changed

5 files changed

+129
-9
lines changed

docs/content/troubleshooting/parameter-types.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
lastmod: 2021-01-16
2+
lastmod: 2021-04-23
33
date: 2021-01-15
44
title: MySqlParameter Types
55
customtitle: "Type of value supplied to MySqlParameter.Value isn’t supported"
@@ -38,4 +38,5 @@ In some cases, this may be as simple as calling `.ToString()` or `.ToString(Cult
3838
* .NET primitives: `bool`, `byte`, `char`, `double`, `float`, `int`, `long`, `sbyte`, `short`, `uint`, `ulong`, `ushort`
3939
* Common types: `DateTime`, `DateTimeOffset`, `decimal`, `enum`, `Guid`, `string`, `TimeSpan`
4040
* BLOB types: `ArraySegment<byte>`, `byte[]`, `Memory<byte>`, `ReadOnlyMemory<byte>`
41+
* String types: `Memory<char>`, `ReadOnlyMemory<char>`, `StringBuilder`
4142
* Custom MySQL types: `MySqlDateTime`, `MySqlGeometry`

src/MySqlConnector/MySqlParameter.cs

Lines changed: 90 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Diagnostics;
66
using System.Diagnostics.CodeAnalysis;
77
using System.IO;
8+
using System.Text;
89
using MySqlConnector.Core;
910
using MySqlConnector.Protocol.Serialization;
1011
using MySqlConnector.Utilities;
@@ -218,17 +219,25 @@ internal void AppendSqlString(ByteBufferWriter writer, StatementPreparerOptions
218219
ReadOnlySpan<byte> nullBytes = new byte[] { 0x4E, 0x55, 0x4C, 0x4C }; // NULL
219220
writer.Write(nullBytes);
220221
}
222+
#if NET45 || NETSTANDARD1_3
221223
else if (Value is string stringValue)
222224
{
223-
writer.Write((byte) '\'');
224-
225-
if (noBackslashEscapes)
226-
writer.Write(stringValue.Replace("'", "''"));
227-
else
228-
writer.Write(stringValue.Replace("\\", "\\\\").Replace("'", "''"));
229-
230-
writer.Write((byte) '\'');
225+
WriteString(writer, noBackslashEscapes, stringValue);
226+
}
227+
#else
228+
else if (Value is string stringValue)
229+
{
230+
WriteString(writer, noBackslashEscapes, writeDelimiters: true, stringValue.AsSpan());
231+
}
232+
else if (Value is ReadOnlyMemory<char> readOnlyMemoryChar)
233+
{
234+
WriteString(writer, noBackslashEscapes, writeDelimiters: true, readOnlyMemoryChar.Span);
235+
}
236+
else if (Value is Memory<char> memoryChar)
237+
{
238+
WriteString(writer, noBackslashEscapes, writeDelimiters: true, memoryChar.Span);
231239
}
240+
#endif
232241
else if (Value is char charValue)
233242
{
234243
writer.Write((byte) '\'');
@@ -398,6 +407,19 @@ internal void AppendSqlString(ByteBufferWriter writer, StatementPreparerOptions
398407
writer.Advance(guidLength);
399408
}
400409
}
410+
else if (Value is StringBuilder stringBuilder)
411+
{
412+
#if NETCOREAPP3_1 || NET5_0
413+
writer.Write((byte) '\'');
414+
foreach (var chunk in stringBuilder.GetChunks())
415+
WriteString(writer, noBackslashEscapes, writeDelimiters: false, chunk.Span);
416+
writer.Write((byte) '\'');
417+
#elif NET45 || NETSTANDARD1_3
418+
WriteString(writer, noBackslashEscapes, stringBuilder.ToString());
419+
#else
420+
WriteString(writer, noBackslashEscapes, writeDelimiters: true, stringBuilder.ToString().AsSpan());
421+
#endif
422+
}
401423
else if (MySqlDbType == MySqlDbType.Int16)
402424
{
403425
writer.WriteString((short) Value);
@@ -434,6 +456,52 @@ internal void AppendSqlString(ByteBufferWriter writer, StatementPreparerOptions
434456
{
435457
throw new NotSupportedException("Parameter type {0} is not supported; see https://fl.vu/mysql-param-type. Value: {1}".FormatInvariant(Value.GetType().Name, Value));
436458
}
459+
460+
#if NET45 || NETSTANDARD1_3
461+
static void WriteString(ByteBufferWriter writer, bool noBackslashEscapes, string value)
462+
{
463+
writer.Write((byte) '\'');
464+
465+
if (noBackslashEscapes)
466+
writer.Write(value.Replace("'", "''"));
467+
else
468+
writer.Write(value.Replace("\\", "\\\\").Replace("'", "''"));
469+
470+
writer.Write((byte) '\'');
471+
}
472+
#else
473+
static void WriteString(ByteBufferWriter writer, bool noBackslashEscapes, bool writeDelimiters, ReadOnlySpan<char> value)
474+
{
475+
if (writeDelimiters)
476+
writer.Write((byte) '\'');
477+
478+
var charsWritten = 0;
479+
while (charsWritten < value.Length)
480+
{
481+
var remainingValue = value.Slice(charsWritten);
482+
var nextDelimiterIndex = remainingValue.IndexOfAny('\'', '\\');
483+
if (nextDelimiterIndex == -1)
484+
{
485+
// write the rest of the string
486+
writer.Write(remainingValue);
487+
charsWritten += remainingValue.Length;
488+
}
489+
else
490+
{
491+
// write up to (and including) the delimiter, then double it
492+
writer.Write(remainingValue.Slice(0, nextDelimiterIndex + 1));
493+
if (remainingValue[nextDelimiterIndex] == '\\' && !noBackslashEscapes)
494+
writer.Write((byte) '\\');
495+
else if (remainingValue[nextDelimiterIndex] == '\'')
496+
writer.Write((byte) '\'');
497+
charsWritten += nextDelimiterIndex + 1;
498+
}
499+
}
500+
501+
if (writeDelimiters)
502+
writer.Write((byte) '\'');
503+
}
504+
#endif
437505
}
438506

439507
internal void AppendBinary(ByteBufferWriter writer, StatementPreparerOptions options)
@@ -591,6 +659,20 @@ internal void AppendBinary(ByteBufferWriter writer, StatementPreparerOptions opt
591659
writer.Advance(guidLength);
592660
}
593661
}
662+
#if !NET45 && !NETSTANDARD1_3
663+
else if (Value is ReadOnlyMemory<char> readOnlyMemoryChar)
664+
{
665+
writer.WriteLengthEncodedString(readOnlyMemoryChar.Span);
666+
}
667+
else if (Value is Memory<char> memoryChar)
668+
{
669+
writer.WriteLengthEncodedString(memoryChar.Span);
670+
}
671+
#endif
672+
else if (Value is StringBuilder stringBuilder)
673+
{
674+
writer.WriteLengthEncodedString(stringBuilder.ToString());
675+
}
594676
else if (MySqlDbType == MySqlDbType.Int16)
595677
{
596678
writer.Write((ushort) (short) Value);

src/MySqlConnector/Protocol/Serialization/ByteBufferWriter.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,12 +241,23 @@ public static void WriteLengthEncodedInteger(this ByteBufferWriter writer, ulong
241241
}
242242
}
243243

244+
#if NET45 || NETSTANDARD1_3
244245
public static void WriteLengthEncodedString(this ByteBufferWriter writer, string value)
245246
{
246247
var byteCount = Encoding.UTF8.GetByteCount(value);
247248
writer.WriteLengthEncodedInteger((ulong) byteCount);
248249
writer.Write(value);
249250
}
251+
#else
252+
public static void WriteLengthEncodedString(this ByteBufferWriter writer, string value) => writer.WriteLengthEncodedString(value.AsSpan());
253+
254+
public static void WriteLengthEncodedString(this ByteBufferWriter writer, ReadOnlySpan<char> value)
255+
{
256+
var byteCount = Encoding.UTF8.GetByteCount(value);
257+
writer.WriteLengthEncodedInteger((ulong) byteCount);
258+
writer.Write(value);
259+
}
260+
#endif
250261

251262
public static void WriteNullTerminatedString(this ByteBufferWriter writer, string value)
252263
{

tests/MySqlConnector.Tests/StatementPreparerTests.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,8 @@ public void FormatParameter(object parameterValue, string replacedValue, bool no
178178
new object[] { new byte[] { 0x41, 0x42, 0x27, 0x61 }, "_binary'AB\\'a'" },
179179
new object[] { new MemoryStream(new byte[] { 0x41, 0x42, 0x27, 0x61 }), "_binary'AB\\'a'" },
180180
new object[] { new MemoryStream(new byte[] { 0, 0x41, 0x42, 0x27, 0x61, 0x62 }, 1, 4, false, true), "_binary'AB\\'a'" },
181+
new object[] { "\"AB\\ab'".AsMemory(), @"'""AB\\ab'''" },
182+
new object[] { new StringBuilder("\"AB\\ab'"), @"'""AB\\ab'''" },
181183
};
182184

183185
[Theory]

tests/SideBySide/InsertTests.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.Data;
44
using System.IO;
55
using System.Linq;
6+
using System.Text;
67
using System.Threading.Tasks;
78
using Dapper;
89
#if BASELINE
@@ -248,6 +249,29 @@ public void InsertMemoryStream(bool prepare)
248249
Assert.Equal(new byte[] { 97, 98, 99, 100 }, reader.GetValue(1));
249250
}
250251

252+
[Theory]
253+
[InlineData(false)]
254+
[InlineData(true)]
255+
public void InsertStringBuilder(bool prepare)
256+
{
257+
using var connection = new MySqlConnection(AppConfig.ConnectionString);
258+
connection.Open();
259+
connection.Execute(@"drop table if exists insert_string_builder;
260+
create table insert_string_builder(rowid integer not null primary key auto_increment, str text);");
261+
262+
var value = "\aAB\\12'ab\\'\\'";
263+
using var cmd = connection.CreateCommand();
264+
cmd.CommandText = @"insert into insert_string_builder(str) values(@str);";
265+
cmd.Parameters.AddWithValue("@str", new StringBuilder(value));
266+
if (prepare)
267+
cmd.Prepare();
268+
cmd.ExecuteNonQuery();
269+
270+
using var reader = connection.ExecuteReader(@"select str from insert_string_builder order by rowid;");
271+
Assert.True(reader.Read());
272+
Assert.Equal(value, reader.GetValue(0));
273+
}
274+
251275
[Fact]
252276
public void InsertOldGuid()
253277
{

0 commit comments

Comments
 (0)