Skip to content

Commit 1599570

Browse files
committed
Use Utf8Formatter to prepare statements.
Create ByteBufferWriter class that abstracts an expandable output buffer (similar to BinaryWriter over MemoryStream). Replace PayloadWriter with ByteBufferWriter. Use ArrayPool for the buffers that are allocated. Signed-off-by: Bradley Grainger <[email protected]>
1 parent 80c56f0 commit 1599570

File tree

14 files changed

+434
-248
lines changed

14 files changed

+434
-248
lines changed

src/MySqlConnector/Core/ServerSession.cs

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -323,8 +323,8 @@ public async Task ConnectAsync(ConnectionSettings cs, ILoadBalancer loadBalancer
323323
if (m_supportsConnectionAttributes && s_connectionAttributes == null)
324324
s_connectionAttributes = CreateConnectionAttributes();
325325

326-
payload = HandshakeResponse41Payload.Create(initialHandshake, cs, m_useCompression, m_supportsConnectionAttributes ? s_connectionAttributes : null);
327-
await SendReplyAsync(payload, ioBehavior, cancellationToken).ConfigureAwait(false);
326+
using (var handshakeResponsePayload = HandshakeResponse41Payload.Create(initialHandshake, cs, m_useCompression, m_supportsConnectionAttributes ? s_connectionAttributes : null))
327+
await SendReplyAsync(handshakeResponsePayload, ioBehavior, cancellationToken).ConfigureAwait(false);
328328
payload = await ReceiveReplyAsync(ioBehavior, cancellationToken).ConfigureAwait(false);
329329

330330
// if server doesn't support the authentication fast path, it will send a new challenge
@@ -378,9 +378,9 @@ public async Task<bool> TryResetConnectionAsync(ConnectionSettings cs, IOBehavio
378378
m_logArguments[1] = ServerVersion.OriginalString;
379379
Log.Debug("Session{0} ServerVersion={1} doesn't support reset connection; sending change user request", m_logArguments);
380380
var hashedPassword = AuthenticationUtility.CreateAuthenticationResponse(AuthPluginData, 0, cs.Password);
381-
var payload = ChangeUserPayload.Create(cs.UserID, hashedPassword, cs.Database, m_supportsConnectionAttributes ? s_connectionAttributes : null);
382-
await SendAsync(payload, ioBehavior, cancellationToken).ConfigureAwait(false);
383-
payload = await ReceiveReplyAsync(ioBehavior, cancellationToken).ConfigureAwait(false);
381+
using (var changeUserPayload = ChangeUserPayload.Create(cs.UserID, hashedPassword, cs.Database, m_supportsConnectionAttributes ? s_connectionAttributes : null))
382+
await SendAsync(changeUserPayload, ioBehavior, cancellationToken).ConfigureAwait(false);
383+
var payload = await ReceiveReplyAsync(ioBehavior, cancellationToken).ConfigureAwait(false);
384384
if (payload.HeaderByte == AuthenticationMethodSwitchRequestPayload.Signature)
385385
{
386386
Log.Debug("Session{0} optimistic reauthentication failed; logging in again", m_logArguments);
@@ -942,8 +942,8 @@ bool ValidateRemoteCertificate(object rcbSender, X509Certificate rcbCertificate,
942942

943943
var checkCertificateRevocation = cs.SslMode == MySqlSslMode.VerifyFull;
944944

945-
var initSsl = HandshakeResponse41Payload.CreateWithSsl(serverCapabilities, cs, m_useCompression);
946-
await SendReplyAsync(initSsl, ioBehavior, cancellationToken).ConfigureAwait(false);
945+
using (var initSsl = HandshakeResponse41Payload.CreateWithSsl(serverCapabilities, cs, m_useCompression))
946+
await SendReplyAsync(initSsl, ioBehavior, cancellationToken).ConfigureAwait(false);
947947

948948
try
949949
{
@@ -1172,7 +1172,7 @@ private void VerifyState(State state1, State state2, State state3)
11721172
private byte[] CreateConnectionAttributes()
11731173
{
11741174
Log.Debug("Session{0} creating connection attributes", m_logArguments);
1175-
var attributesWriter = new PayloadWriter();
1175+
var attributesWriter = new ByteBufferWriter();
11761176
attributesWriter.WriteLengthEncodedString("_client_name");
11771177
attributesWriter.WriteLengthEncodedString("MySqlConnector");
11781178
attributesWriter.WriteLengthEncodedString("_client_version");
@@ -1198,12 +1198,15 @@ private byte[] CreateConnectionAttributes()
11981198
attributesWriter.WriteLengthEncodedString("_pid");
11991199
attributesWriter.WriteLengthEncodedString(process.Id.ToString(CultureInfo.InvariantCulture));
12001200
}
1201-
var connectionAttributes = attributesWriter.ToBytes();
1202-
1203-
var writer = new PayloadWriter();
1204-
writer.WriteLengthEncodedInteger((ulong) connectionAttributes.Length);
1205-
writer.Write(connectionAttributes);
1206-
return writer.ToBytes();
1201+
using (var connectionAttributesPayload = attributesWriter.ToPayloadData())
1202+
{
1203+
var connectionAttributes = connectionAttributesPayload.ArraySegment;
1204+
var writer = new ByteBufferWriter(connectionAttributes.Count + 9);
1205+
writer.WriteLengthEncodedInteger((ulong) connectionAttributes.Count);
1206+
writer.Write(connectionAttributes);
1207+
using (var payload = writer.ToPayloadData())
1208+
return payload.ArraySegment.AsSpan().ToArray();
1209+
}
12071210
}
12081211

12091212
private Exception CreateExceptionForErrorPayload(PayloadData payload)

src/MySqlConnector/Core/StatementPreparer.cs

Lines changed: 12 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
using System;
22
using System.Data;
3-
using System.IO;
4-
using System.Text;
53
using MySql.Data.MySqlClient;
64
using MySqlConnector.Protocol;
5+
using MySqlConnector.Protocol.Serialization;
76
using MySqlConnector.Utilities;
87

98
namespace MySqlConnector.Core
@@ -19,29 +18,21 @@ public StatementPreparer(string commandText, MySqlParameterCollection parameters
1918

2019
public ArraySegment<byte> ParseAndBindParameters()
2120
{
22-
using (var stream = new MemoryStream(m_commandText.Length + 1))
23-
using (var writer = new BinaryWriter(stream, Encoding.UTF8))
24-
{
25-
writer.Write((byte) CommandKind.Query);
26-
27-
if (!string.IsNullOrWhiteSpace(m_commandText))
28-
{
29-
var parser = new ParameterSqlParser(this, writer);
30-
parser.Parse(m_commandText);
31-
}
21+
var writer = new ByteBufferWriter(m_commandText.Length + 1);
22+
writer.Write((byte) CommandKind.Query);
3223

33-
#if NETSTANDARD1_3
34-
var array = stream.ToArray();
35-
#else
36-
var array = stream.GetBuffer();
37-
#endif
38-
return new ArraySegment<byte>(array, 0, checked((int) stream.Length));
24+
if (!string.IsNullOrWhiteSpace(m_commandText))
25+
{
26+
var parser = new ParameterSqlParser(this, writer);
27+
parser.Parse(m_commandText);
3928
}
29+
30+
return writer.ArraySegment;
4031
}
4132

4233
private sealed class ParameterSqlParser : SqlParser
4334
{
44-
public ParameterSqlParser(StatementPreparer preparer, BinaryWriter writer)
35+
public ParameterSqlParser(StatementPreparer preparer, ByteBufferWriter writer)
4536
{
4637
m_preparer = preparer;
4738
m_writer = writer;
@@ -84,11 +75,11 @@ protected override void OnParsed()
8475

8576
private void AppendString(string value, int offset, int length)
8677
{
87-
m_writer.WriteUtf8(value, offset, length);
78+
m_writer.Write(value, offset, length);
8879
}
8980

9081
readonly StatementPreparer m_preparer;
91-
readonly BinaryWriter m_writer;
82+
readonly ByteBufferWriter m_writer;
9283
int m_currentParameterIndex;
9384
int m_lastIndex;
9485
}

src/MySqlConnector/Core/TextCommandExecutor.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ public virtual async Task<DbDataReader> ExecuteReaderAsync(string commandText, M
6262
cancellationToken.ThrowIfCancellationRequested();
6363
if (Log.IsDebugEnabled())
6464
Log.Debug("Session{0} ExecuteBehavior {1} CommandText: {2}", m_command.Connection.Session.Id, ioBehavior, commandText);
65-
var payload = CreateQueryPayload(commandText, parameterCollection);
65+
using (var payload = CreateQueryPayload(commandText, parameterCollection))
6666
using (m_command.RegisterCancel(cancellationToken))
6767
{
6868
m_command.Connection.Session.StartQuerying(m_command);
@@ -119,7 +119,7 @@ private PayloadData CreateQueryPayload(string commandText, MySqlParameterCollect
119119
}
120120

121121
var preparer = new StatementPreparer(commandText, parameterCollection, statementPreparerOptions);
122-
return new PayloadData(preparer.ParseAndBindParameters());
122+
return new PayloadData(preparer.ParseAndBindParameters(), isPooled: true);
123123
}
124124

125125
static readonly IMySqlConnectorLogger Log = MySqlConnectorLogManager.CreateLogger(nameof(TextCommandExecutor));

src/MySqlConnector/MySql.Data.MySqlClient/MySqlConnection.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,8 @@ private async Task ChangeDatabaseAsync(IOBehavior ioBehavior, string databaseNam
124124

125125
CloseDatabase();
126126

127-
await m_session.SendAsync(InitDatabasePayload.Create(databaseName), ioBehavior, cancellationToken).ConfigureAwait(false);
127+
using (var initDatabasePayload = InitDatabasePayload.Create(databaseName))
128+
await m_session.SendAsync(initDatabasePayload, ioBehavior, cancellationToken).ConfigureAwait(false);
128129
var payload = await m_session.ReceiveReplyAsync(ioBehavior, cancellationToken).ConfigureAwait(false);
129130
OkPayload.Create(payload);
130131
m_session.DatabaseOverride = databaseName;

src/MySqlConnector/MySql.Data.MySqlClient/MySqlParameter.cs

Lines changed: 70 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
using System;
2+
using System.Buffers.Text;
23
using System.Data;
34
using System.Data.Common;
4-
using System.IO;
5+
using System.Diagnostics;
56
using MySqlConnector.Core;
7+
using MySqlConnector.Protocol.Serialization;
68
using MySqlConnector.Utilities;
79

810
namespace MySql.Data.MySqlClient
@@ -175,16 +177,16 @@ private MySqlParameter(MySqlParameter other, string parameterName)
175177

176178
internal string NormalizedParameterName { get; private set; }
177179

178-
internal void AppendSqlString(BinaryWriter writer, StatementPreparerOptions options, string parameterName)
180+
internal void AppendSqlString(ByteBufferWriter writer, StatementPreparerOptions options, string parameterName)
179181
{
180182
if (Value == null || Value == DBNull.Value)
181183
{
182-
writer.WriteUtf8("NULL");
184+
writer.Write(s_nullBytes);
183185
}
184186
else if (Value is string stringValue)
185187
{
186188
writer.Write((byte) '\'');
187-
writer.WriteUtf8(stringValue.Replace("\\", "\\\\").Replace("'", "\\'"));
189+
writer.Write(stringValue.Replace("\\", "\\\\").Replace("'", "\\'"));
188190
writer.Write((byte) '\'');
189191
}
190192
else if (Value is char charValue)
@@ -199,44 +201,70 @@ internal void AppendSqlString(BinaryWriter writer, StatementPreparerOptions opti
199201
break;
200202

201203
default:
202-
writer.WriteUtf8(charValue.ToString());
204+
writer.Write(charValue.ToString());
203205
break;
204206
}
205207
writer.Write((byte) '\'');
206208
}
207-
else if (Value is byte || Value is sbyte || Value is short || Value is int || Value is long || Value is ushort || Value is uint || Value is ulong || Value is decimal)
209+
else if (Value is byte || Value is sbyte || Value is decimal)
208210
{
209-
writer.WriteUtf8("{0}".FormatInvariant(Value));
211+
writer.Write("{0}".FormatInvariant(Value));
212+
}
213+
else if (Value is short shortValue)
214+
{
215+
writer.WriteString(shortValue);
216+
}
217+
else if (Value is ushort ushortValue)
218+
{
219+
writer.WriteString(ushortValue);
220+
}
221+
else if (Value is int intValue)
222+
{
223+
writer.WriteString(intValue);
224+
}
225+
else if (Value is uint uintValue)
226+
{
227+
writer.WriteString(uintValue);
228+
}
229+
else if (Value is long longValue)
230+
{
231+
writer.WriteString(longValue);
232+
}
233+
else if (Value is ulong ulongValue)
234+
{
235+
writer.WriteString(ulongValue);
210236
}
211237
else if (Value is byte[] byteArrayValue)
212238
{
213239
// determine the number of bytes to be written
214-
const string c_prefix = "_binary'";
215-
var length = byteArrayValue.Length + c_prefix.Length + 1;
240+
var length = byteArrayValue.Length + s_binaryBytes.Length + 1;
216241
foreach (var by in byteArrayValue)
217242
{
218243
if (by == 0x27 || by == 0x5C)
219244
length++;
220245
}
221246

222-
((MemoryStream) writer.BaseStream).Capacity = (int) writer.BaseStream.Length + length;
223-
224-
writer.WriteUtf8(c_prefix);
247+
var span = writer.GetSpan(length);
248+
s_binaryBytes.CopyTo(span);
249+
var index = s_binaryBytes.Length;
225250
foreach (var by in byteArrayValue)
226251
{
227252
if (by == 0x27 || by == 0x5C)
228-
writer.Write((byte) 0x5C);
229-
writer.Write(by);
253+
span[index++] = 0x5C;
254+
span[index++] = by;
230255
}
231-
writer.Write((byte) '\'');
256+
span[index++] = 0x27;
257+
Debug.Assert(index == length, "index == length");
258+
writer.Advance(index);
232259
}
233260
else if (Value is bool boolValue)
234261
{
235-
writer.WriteUtf8(boolValue ? "true" : "false");
262+
writer.Write(boolValue ? s_trueBytes : s_falseBytes);
236263
}
237264
else if (Value is float || Value is double)
238265
{
239-
writer.WriteUtf8("{0:R}".FormatInvariant(Value));
266+
// NOTE: Utf8Formatter doesn't support "R"
267+
writer.Write("{0:R}".FormatInvariant(Value));
240268
}
241269
else if (Value is DateTime dateTimeValue)
242270
{
@@ -245,22 +273,22 @@ internal void AppendSqlString(BinaryWriter writer, StatementPreparerOptions opti
245273
else if ((options & StatementPreparerOptions.DateTimeLocal) != 0 && dateTimeValue.Kind == DateTimeKind.Utc)
246274
throw new MySqlException("DateTime.Kind must not be Utc when DateTimeKind setting is Local (parameter name: {0})".FormatInvariant(parameterName));
247275

248-
writer.WriteUtf8("timestamp('{0:yyyy'-'MM'-'dd' 'HH':'mm':'ss'.'ffffff}')".FormatInvariant(dateTimeValue));
276+
writer.Write("timestamp('{0:yyyy'-'MM'-'dd' 'HH':'mm':'ss'.'ffffff}')".FormatInvariant(dateTimeValue));
249277
}
250278
else if (Value is DateTimeOffset dateTimeOffsetValue)
251279
{
252280
// store as UTC as it will be read as such when deserialized from a timespan column
253-
writer.WriteUtf8("timestamp('{0:yyyy'-'MM'-'dd' 'HH':'mm':'ss'.'ffffff}')".FormatInvariant(dateTimeOffsetValue.UtcDateTime));
281+
writer.Write("timestamp('{0:yyyy'-'MM'-'dd' 'HH':'mm':'ss'.'ffffff}')".FormatInvariant(dateTimeOffsetValue.UtcDateTime));
254282
}
255283
else if (Value is TimeSpan ts)
256284
{
257-
writer.WriteUtf8("time '");
285+
writer.Write("time '");
258286
if (ts.Ticks < 0)
259287
{
260288
writer.Write((byte) '-');
261289
ts = TimeSpan.FromTicks(-ts.Ticks);
262290
}
263-
writer.WriteUtf8("{0}:{1:mm':'ss'.'ffffff}'".FormatInvariant(ts.Days * 24 + ts.Hours, ts));
291+
writer.Write("{0}:{1:mm':'ss'.'ffffff}'".FormatInvariant(ts.Days * 24 + ts.Hours, ts));
264292
}
265293
else if (Value is Guid guidValue)
266294
{
@@ -287,7 +315,7 @@ internal void AppendSqlString(BinaryWriter writer, StatementPreparerOptions opti
287315
Utility.SwapBytes(bytes, 1, 3);
288316
}
289317
}
290-
writer.WriteUtf8("_binary'");
318+
writer.Write(s_binaryBytes);
291319
foreach (var by in bytes)
292320
{
293321
if (by == 0x27 || by == 0x5C)
@@ -296,46 +324,48 @@ internal void AppendSqlString(BinaryWriter writer, StatementPreparerOptions opti
296324
}
297325
writer.Write((byte) '\'');
298326
}
299-
else if (guidOptions == StatementPreparerOptions.GuidFormatChar32)
300-
{
301-
writer.WriteUtf8("'{0:N}'".FormatInvariant(guidValue));
302-
}
303327
else
304328
{
305-
writer.WriteUtf8("'{0:D}'".FormatInvariant(guidValue));
329+
var is32Characters = guidOptions == StatementPreparerOptions.GuidFormatChar32;
330+
var guidLength = is32Characters ? 34 : 38;
331+
var span = writer.GetSpan(guidLength);
332+
span[0] = 0x27;
333+
Utf8Formatter.TryFormat(guidValue, span.Slice(1), out _, is32Characters ? 'N' : 'D');
334+
span[guidLength - 1] = 0x27;
335+
writer.Advance(guidLength);
306336
}
307337
}
308338
else if (MySqlDbType == MySqlDbType.Int16)
309339
{
310-
writer.WriteUtf8("{0}".FormatInvariant((short) Value));
340+
writer.WriteString((short) Value);
311341
}
312342
else if (MySqlDbType == MySqlDbType.UInt16)
313343
{
314-
writer.WriteUtf8("{0}".FormatInvariant((ushort) Value));
344+
writer.WriteString((ushort) Value);
315345
}
316346
else if (MySqlDbType == MySqlDbType.Int32)
317347
{
318-
writer.WriteUtf8("{0}".FormatInvariant((int) Value));
348+
writer.WriteString((int) Value);
319349
}
320350
else if (MySqlDbType == MySqlDbType.UInt32)
321351
{
322-
writer.WriteUtf8("{0}".FormatInvariant((uint) Value));
352+
writer.WriteString((uint) Value);
323353
}
324354
else if (MySqlDbType == MySqlDbType.Int64)
325355
{
326-
writer.WriteUtf8("{0}".FormatInvariant((long) Value));
356+
writer.WriteString((long) Value);
327357
}
328358
else if (MySqlDbType == MySqlDbType.UInt64)
329359
{
330-
writer.WriteUtf8("{0}".FormatInvariant((ulong) Value));
360+
writer.WriteString((ulong) Value);
331361
}
332362
else if ((MySqlDbType == MySqlDbType.String || MySqlDbType == MySqlDbType.VarChar) && HasSetDbType && Value is Enum)
333363
{
334-
writer.WriteUtf8("'{0:G}'".FormatInvariant(Value));
364+
writer.Write("'{0:G}'".FormatInvariant(Value));
335365
}
336366
else if (Value is Enum)
337367
{
338-
writer.WriteUtf8("{0:d}".FormatInvariant(Value));
368+
writer.Write("{0:d}".FormatInvariant(Value));
339369
}
340370
else
341371
{
@@ -357,6 +387,11 @@ internal static string NormalizeParameterName(string name)
357387
return name.StartsWith("@", StringComparison.Ordinal) || name.StartsWith("?", StringComparison.Ordinal) ? name.Substring(1) : name;
358388
}
359389

390+
static readonly byte[] s_nullBytes = { 0x4E, 0x55, 0x4C, 0x4C }; // NULL
391+
static readonly byte[] s_trueBytes = { 0x74, 0x72, 0x75, 0x65 }; // true
392+
static readonly byte[] s_falseBytes = { 0x66, 0x61, 0x6C, 0x73, 0x65 }; // false
393+
static readonly byte[] s_binaryBytes = { 0x5F, 0x62, 0x69, 0x6E, 0x61, 0x72, 0x79, 0x27 }; // _binary'
394+
360395
DbType m_dbType;
361396
MySqlDbType m_mySqlDbType;
362397
string m_name;

0 commit comments

Comments
 (0)