Skip to content

Commit d299b95

Browse files
committed
Use Utf8Parser.
Signed-off-by: Bradley Grainger <[email protected]>
1 parent 1599570 commit d299b95

File tree

2 files changed

+98
-47
lines changed

2 files changed

+98
-47
lines changed

src/MySqlConnector/Core/Row.cs

Lines changed: 98 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Buffers.Text;
23
using System.Globalization;
34
using System.Text;
45
using MySql.Data.MySqlClient;
@@ -336,25 +337,23 @@ public object GetValue(int ordinal)
336337
if (m_dataOffsets[ordinal] == -1)
337338
return DBNull.Value;
338339

339-
var data = new ArraySegment<byte>(m_payload.Array, m_dataOffsets[ordinal], m_dataLengths[ordinal]);
340+
var data = new ReadOnlySpan<byte>(m_payload.Array, m_dataOffsets[ordinal], m_dataLengths[ordinal]);
340341
var columnDefinition = ResultSet.ColumnDefinitions[ordinal];
341342
var isUnsigned = (columnDefinition.ColumnFlags & ColumnFlags.Unsigned) != 0;
342343
switch (columnDefinition.ColumnType)
343344
{
344345
case ColumnType.Tiny:
345-
var value = int.Parse(Encoding.UTF8.GetString(data), CultureInfo.InvariantCulture);
346+
var value = ParseInt32(data);
346347
if (Connection.TreatTinyAsBoolean && columnDefinition.ColumnLength == 1)
347348
return value != 0;
348349
return isUnsigned ? (object) (byte) value : (sbyte) value;
349350

350351
case ColumnType.Int24:
351352
case ColumnType.Long:
352-
return isUnsigned ? (object) uint.Parse(Encoding.UTF8.GetString(data), CultureInfo.InvariantCulture) :
353-
int.Parse(Encoding.UTF8.GetString(data), CultureInfo.InvariantCulture);
353+
return isUnsigned ? (object) ParseUInt32(data) : ParseInt32(data);
354354

355355
case ColumnType.Longlong:
356-
return isUnsigned ? (object) ulong.Parse(Encoding.UTF8.GetString(data), CultureInfo.InvariantCulture) :
357-
long.Parse(Encoding.UTF8.GetString(data), CultureInfo.InvariantCulture);
356+
return isUnsigned ? (object) ParseUInt64(data) : ParseInt64(data);
358357

359358
case ColumnType.Bit:
360359
// BIT column is transmitted as MSB byte array
@@ -365,9 +364,9 @@ public object GetValue(int ordinal)
365364

366365
case ColumnType.String:
367366
if (Connection.GuidFormat == MySqlGuidFormat.Char36 && columnDefinition.ColumnLength / ProtocolUtility.GetBytesPerCharacter(columnDefinition.CharacterSet) == 36)
368-
return Guid.Parse(Encoding.UTF8.GetString(data));
367+
return Utf8Parser.TryParse(data, out Guid guid, out int guid36BytesConsumed, 'D') && guid36BytesConsumed == 36 ? guid : throw new FormatException();
369368
if (Connection.GuidFormat == MySqlGuidFormat.Char32 && columnDefinition.ColumnLength / ProtocolUtility.GetBytesPerCharacter(columnDefinition.CharacterSet) == 32)
370-
return Guid.Parse(Encoding.UTF8.GetString(data));
369+
return Utf8Parser.TryParse(data, out Guid guid, out int guid32BytesConsumed, 'N') && guid32BytesConsumed == 32 ? guid : throw new FormatException();
371370
goto case ColumnType.VarString;
372371

373372
case ColumnType.VarString:
@@ -378,25 +377,19 @@ public object GetValue(int ordinal)
378377
case ColumnType.LongBlob:
379378
if (columnDefinition.CharacterSet == CharacterSet.Binary)
380379
{
381-
var result = new byte[m_dataLengths[ordinal]];
382-
Buffer.BlockCopy(m_payload.Array, m_dataOffsets[ordinal], result, 0, result.Length);
383380
var guidFormat = Connection.GuidFormat;
384-
if ((guidFormat == MySqlGuidFormat.Binary16 || guidFormat == MySqlGuidFormat.TimeSwapBinary16 || guidFormat == MySqlGuidFormat.LittleEndianBinary16) &&
385-
columnDefinition.ColumnLength == 16)
386-
{
387-
return CreateGuidFromBytes(guidFormat, result);
388-
}
381+
if ((guidFormat == MySqlGuidFormat.Binary16 || guidFormat == MySqlGuidFormat.TimeSwapBinary16 || guidFormat == MySqlGuidFormat.LittleEndianBinary16) && columnDefinition.ColumnLength == 16)
382+
return CreateGuidFromBytes(guidFormat, data);
389383

390-
return result;
384+
return data.ToArray();
391385
}
392386
return Encoding.UTF8.GetString(data);
393387

394388
case ColumnType.Json:
395389
return Encoding.UTF8.GetString(data);
396390

397391
case ColumnType.Short:
398-
return isUnsigned ? (object) ushort.Parse(Encoding.UTF8.GetString(data), CultureInfo.InvariantCulture) :
399-
short.Parse(Encoding.UTF8.GetString(data), CultureInfo.InvariantCulture);
392+
return isUnsigned ? (object) ParseUInt16(data) : ParseInt16(data);
400393

401394
case ColumnType.Date:
402395
case ColumnType.DateTime:
@@ -407,23 +400,41 @@ public object GetValue(int ordinal)
407400
return ParseTimeSpan(data);
408401

409402
case ColumnType.Year:
410-
return int.Parse(Encoding.UTF8.GetString(data), CultureInfo.InvariantCulture);
403+
return ParseInt32(data);
411404

412405
case ColumnType.Float:
413-
return float.Parse(Encoding.UTF8.GetString(data), CultureInfo.InvariantCulture);
406+
return !Utf8Parser.TryParse(data, out float floatValue, out var floatBytesConsumed) || floatBytesConsumed != data.Length ? throw new FormatException() : floatValue;
414407

415408
case ColumnType.Double:
416-
return double.Parse(Encoding.UTF8.GetString(data), CultureInfo.InvariantCulture);
409+
return !Utf8Parser.TryParse(data, out double doubleValue, out var doubleBytesConsumed) || doubleBytesConsumed != data.Length ? throw new FormatException() : doubleValue;
417410

418411
case ColumnType.Decimal:
419412
case ColumnType.NewDecimal:
420-
return decimal.Parse(Encoding.UTF8.GetString(data), CultureInfo.InvariantCulture);
413+
return Utf8Parser.TryParse(data, out decimal decimalValue, out int bytesConsumed) && bytesConsumed == data.Length ? decimalValue : throw new FormatException();
421414

422415
default:
423416
throw new NotImplementedException("Reading {0} not implemented".FormatInvariant(columnDefinition.ColumnType));
424417
}
425418
}
426419

420+
private static short ParseInt16(ReadOnlySpan<byte> data) =>
421+
!Utf8Parser.TryParse(data, out short value, out var bytesConsumed) || bytesConsumed != data.Length ? throw new FormatException() : value;
422+
423+
private static ushort ParseUInt16(ReadOnlySpan<byte> data) =>
424+
!Utf8Parser.TryParse(data, out ushort value, out var bytesConsumed) || bytesConsumed != data.Length ? throw new FormatException() : value;
425+
426+
private static int ParseInt32(ReadOnlySpan<byte> data) =>
427+
!Utf8Parser.TryParse(data, out int value, out var bytesConsumed) || bytesConsumed != data.Length ? throw new FormatException() : value;
428+
429+
private static uint ParseUInt32(ReadOnlySpan<byte> data) =>
430+
!Utf8Parser.TryParse(data, out uint value, out var bytesConsumed) || bytesConsumed != data.Length ? throw new FormatException() : value;
431+
432+
private static long ParseInt64(ReadOnlySpan<byte> data) =>
433+
!Utf8Parser.TryParse(data, out long value, out var bytesConsumed) || bytesConsumed != data.Length ? throw new FormatException() : value;
434+
435+
private static ulong ParseUInt64(ReadOnlySpan<byte> data) =>
436+
!Utf8Parser.TryParse(data, out ulong value, out var bytesConsumed) || bytesConsumed != data.Length ? throw new FormatException() : value;
437+
427438
private static void CheckBufferArguments<T>(long dataOffset, T[] buffer, int bufferOffset, int length)
428439
{
429440
if (dataOffset < 0)
@@ -440,13 +451,18 @@ private static void CheckBufferArguments<T>(long dataOffset, T[] buffer, int buf
440451
throw new ArgumentException("bufferOffset + length cannot exceed buffer.Length", nameof(length));
441452
}
442453

443-
private DateTime ParseDateTime(ArraySegment<byte> value)
454+
private DateTime ParseDateTime(ReadOnlySpan<byte> value)
444455
{
445-
var parts = Encoding.UTF8.GetString(value).Split('-', ' ', ':', '.');
446-
447-
var year = int.Parse(parts[0], CultureInfo.InvariantCulture);
448-
var month = int.Parse(parts[1], CultureInfo.InvariantCulture);
449-
var day = int.Parse(parts[2], CultureInfo.InvariantCulture);
456+
if (!Utf8Parser.TryParse(value, out int year, out var bytesConsumed) || bytesConsumed != 4)
457+
goto InvalidDateTime;
458+
if (value.Length < 5 || value[4] != 45)
459+
goto InvalidDateTime;
460+
if (!Utf8Parser.TryParse(value.Slice(5), out int month, out bytesConsumed) || bytesConsumed != 2)
461+
goto InvalidDateTime;
462+
if (value.Length < 8 || value[7] != 45)
463+
goto InvalidDateTime;
464+
if (!Utf8Parser.TryParse(value.Slice(8), out int day, out bytesConsumed) || bytesConsumed != 2)
465+
goto InvalidDateTime;
450466

451467
if (year == 0 && month == 0 && day == 0)
452468
{
@@ -455,47 +471,85 @@ private DateTime ParseDateTime(ArraySegment<byte> value)
455471
throw new InvalidCastException("Unable to convert MySQL date/time to System.DateTime.");
456472
}
457473

458-
if (parts.Length == 3)
474+
if (value.Length == 10)
459475
return new DateTime(year, month, day, 0, 0, 0, Connection.DateTimeKind);
460476

461-
var hour = int.Parse(parts[3], CultureInfo.InvariantCulture);
462-
var minute = int.Parse(parts[4], CultureInfo.InvariantCulture);
463-
var second = int.Parse(parts[5], CultureInfo.InvariantCulture);
464-
if (parts.Length == 6)
477+
if (value[10] != 32)
478+
goto InvalidDateTime;
479+
if (!Utf8Parser.TryParse(value.Slice(11), out int hour, out bytesConsumed) || bytesConsumed != 2)
480+
goto InvalidDateTime;
481+
if (value.Length < 14 || value[13] != 58)
482+
goto InvalidDateTime;
483+
if (!Utf8Parser.TryParse(value.Slice(14), out int minute, out bytesConsumed) || bytesConsumed != 2)
484+
goto InvalidDateTime;
485+
if (value.Length < 17 || value[16] != 58)
486+
goto InvalidDateTime;
487+
if (!Utf8Parser.TryParse(value.Slice(17), out int second, out bytesConsumed) || bytesConsumed != 2)
488+
goto InvalidDateTime;
489+
490+
if (value.Length == 19)
465491
return new DateTime(year, month, day, hour, minute, second, Connection.DateTimeKind);
492+
if (value[19] != 46)
493+
goto InvalidDateTime;
494+
495+
if (!Utf8Parser.TryParse(value.Slice(20), out int microseconds, out bytesConsumed) || bytesConsumed != value.Length - 20)
496+
goto InvalidDateTime;
497+
for (; bytesConsumed < 6; bytesConsumed++)
498+
microseconds *= 10;
466499

467-
var microseconds = int.Parse(parts[6] + new string('0', 6 - parts[6].Length), CultureInfo.InvariantCulture);
468500
return new DateTime(year, month, day, hour, minute, second, microseconds / 1000, Connection.DateTimeKind).AddTicks(microseconds % 1000 * 10);
501+
502+
InvalidDateTime:
503+
throw new FormatException("Couldn't interpret '{0}' as a valid DateTime".FormatInvariant(Encoding.UTF8.GetString(value)));
469504
}
470505

471-
private static TimeSpan ParseTimeSpan(ArraySegment<byte> value)
506+
private static TimeSpan ParseTimeSpan(ReadOnlySpan<byte> value)
472507
{
473-
var parts = Encoding.UTF8.GetString(value).Split(':', '.');
474-
475-
var hours = int.Parse(parts[0], CultureInfo.InvariantCulture);
476-
var minutes = int.Parse(parts[1], CultureInfo.InvariantCulture);
508+
var originalValue = value;
509+
if (!Utf8Parser.TryParse(value, out int hours, out var bytesConsumed))
510+
goto InvalidTimeSpan;
511+
if (value.Length == bytesConsumed || value[bytesConsumed] != 58)
512+
goto InvalidTimeSpan;
513+
value = value.Slice(bytesConsumed + 1);
514+
515+
if (!Utf8Parser.TryParse(value, out int minutes, out bytesConsumed) || bytesConsumed != 2)
516+
goto InvalidTimeSpan;
517+
if (value.Length < 3 || value[2] != 58)
518+
goto InvalidTimeSpan;
519+
value = value.Slice(3);
477520
if (hours < 0)
478521
minutes = -minutes;
479-
var seconds = int.Parse(parts[2], CultureInfo.InvariantCulture);
522+
523+
if (!Utf8Parser.TryParse(value, out int seconds, out bytesConsumed) || bytesConsumed != 2)
524+
goto InvalidTimeSpan;
480525
if (hours < 0)
481526
seconds = -seconds;
482-
if (parts.Length == 3)
527+
if (value.Length == 2)
483528
return new TimeSpan(hours, minutes, seconds);
484529

485-
var microseconds = int.Parse(parts[3] + new string('0', 6 - parts[3].Length), CultureInfo.InvariantCulture);
530+
if (value[2] != 46)
531+
goto InvalidTimeSpan;
532+
value = value.Slice(3);
533+
if (!Utf8Parser.TryParse(value, out int microseconds, out bytesConsumed) || bytesConsumed != value.Length)
534+
goto InvalidTimeSpan;
535+
for (; bytesConsumed < 6; bytesConsumed++)
536+
microseconds *= 10;
486537
if (hours < 0)
487538
microseconds = -microseconds;
488539
return new TimeSpan(0, hours, minutes, seconds, microseconds / 1000) + TimeSpan.FromTicks(microseconds % 1000 * 10);
540+
541+
InvalidTimeSpan:
542+
throw new FormatException("Couldn't interpret '{0}' as a valid TimeSpan".FormatInvariant(Encoding.UTF8.GetString(originalValue)));
489543
}
490544

491-
private static Guid CreateGuidFromBytes(MySqlGuidFormat guidFormat, byte[] bytes)
545+
private static Guid CreateGuidFromBytes(MySqlGuidFormat guidFormat, ReadOnlySpan<byte> bytes)
492546
{
493547
if (guidFormat == MySqlGuidFormat.Binary16)
494548
return new Guid(new[] { bytes[3], bytes[2], bytes[1], bytes[0], bytes[5], bytes[4], bytes[7], bytes[6], bytes[8], bytes[9], bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], bytes[15] });
495549
else if (guidFormat == MySqlGuidFormat.TimeSwapBinary16)
496550
return new Guid(new[] { bytes[7], bytes[6], bytes[5], bytes[4], bytes[3], bytes[2], bytes[1], bytes[0], bytes[8], bytes[9], bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], bytes[15] });
497551
else
498-
return new Guid(bytes);
552+
return new Guid(bytes.ToArray());
499553
}
500554

501555
public readonly ResultSet ResultSet;

src/MySqlConnector/Utilities/Utility.cs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,6 @@ public static void Dispose<T>(ref T disposable)
2929
public static string FormatInvariant(this string format, params object[] args) =>
3030
string.Format(CultureInfo.InvariantCulture, format, args);
3131

32-
public static string GetString(this Encoding encoding, ArraySegment<byte> arraySegment) =>
33-
encoding.GetString(arraySegment.Array, arraySegment.Offset, arraySegment.Count);
34-
3532
public static string GetString(this Encoding encoding, ReadOnlySpan<byte> span)
3633
{
3734
if (span.Length == 0)

0 commit comments

Comments
 (0)