1
1
using System ;
2
+ using System . Buffers . Text ;
2
3
using System . Globalization ;
3
4
using System . Text ;
4
5
using MySql . Data . MySqlClient ;
@@ -336,25 +337,23 @@ public object GetValue(int ordinal)
336
337
if ( m_dataOffsets [ ordinal ] == - 1 )
337
338
return DBNull . Value ;
338
339
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 ] ) ;
340
341
var columnDefinition = ResultSet . ColumnDefinitions [ ordinal ] ;
341
342
var isUnsigned = ( columnDefinition . ColumnFlags & ColumnFlags . Unsigned ) != 0 ;
342
343
switch ( columnDefinition . ColumnType )
343
344
{
344
345
case ColumnType . Tiny :
345
- var value = int . Parse ( Encoding . UTF8 . GetString ( data ) , CultureInfo . InvariantCulture ) ;
346
+ var value = ParseInt32 ( data ) ;
346
347
if ( Connection . TreatTinyAsBoolean && columnDefinition . ColumnLength == 1 )
347
348
return value != 0 ;
348
349
return isUnsigned ? ( object ) ( byte ) value : ( sbyte ) value ;
349
350
350
351
case ColumnType . Int24 :
351
352
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 ) ;
354
354
355
355
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 ) ;
358
357
359
358
case ColumnType . Bit :
360
359
// BIT column is transmitted as MSB byte array
@@ -365,9 +364,9 @@ public object GetValue(int ordinal)
365
364
366
365
case ColumnType . String :
367
366
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 ( ) ;
369
368
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 ( ) ;
371
370
goto case ColumnType . VarString ;
372
371
373
372
case ColumnType . VarString :
@@ -378,25 +377,19 @@ public object GetValue(int ordinal)
378
377
case ColumnType . LongBlob :
379
378
if ( columnDefinition . CharacterSet == CharacterSet . Binary )
380
379
{
381
- var result = new byte [ m_dataLengths [ ordinal ] ] ;
382
- Buffer . BlockCopy ( m_payload . Array , m_dataOffsets [ ordinal ] , result , 0 , result . Length ) ;
383
380
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 ) ;
389
383
390
- return result ;
384
+ return data . ToArray ( ) ;
391
385
}
392
386
return Encoding . UTF8 . GetString ( data ) ;
393
387
394
388
case ColumnType . Json :
395
389
return Encoding . UTF8 . GetString ( data ) ;
396
390
397
391
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 ) ;
400
393
401
394
case ColumnType . Date :
402
395
case ColumnType . DateTime :
@@ -407,23 +400,41 @@ public object GetValue(int ordinal)
407
400
return ParseTimeSpan ( data ) ;
408
401
409
402
case ColumnType . Year :
410
- return int . Parse ( Encoding . UTF8 . GetString ( data ) , CultureInfo . InvariantCulture ) ;
403
+ return ParseInt32 ( data ) ;
411
404
412
405
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 ;
414
407
415
408
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 ;
417
410
418
411
case ColumnType . Decimal :
419
412
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 ( ) ;
421
414
422
415
default :
423
416
throw new NotImplementedException ( "Reading {0} not implemented" . FormatInvariant ( columnDefinition . ColumnType ) ) ;
424
417
}
425
418
}
426
419
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
+
427
438
private static void CheckBufferArguments < T > ( long dataOffset , T [ ] buffer , int bufferOffset , int length )
428
439
{
429
440
if ( dataOffset < 0 )
@@ -440,13 +451,18 @@ private static void CheckBufferArguments<T>(long dataOffset, T[] buffer, int buf
440
451
throw new ArgumentException ( "bufferOffset + length cannot exceed buffer.Length" , nameof ( length ) ) ;
441
452
}
442
453
443
- private DateTime ParseDateTime ( ArraySegment < byte > value )
454
+ private DateTime ParseDateTime ( ReadOnlySpan < byte > value )
444
455
{
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 ;
450
466
451
467
if ( year == 0 && month == 0 && day == 0 )
452
468
{
@@ -455,47 +471,102 @@ private DateTime ParseDateTime(ArraySegment<byte> value)
455
471
throw new InvalidCastException ( "Unable to convert MySQL date/time to System.DateTime." ) ;
456
472
}
457
473
458
- if ( parts . Length == 3 )
474
+ if ( value . Length == 10 )
459
475
return new DateTime ( year , month , day , 0 , 0 , 0 , Connection . DateTimeKind ) ;
460
476
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 )
465
491
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 ;
466
499
467
- var microseconds = int . Parse ( parts [ 6 ] + new string ( '0' , 6 - parts [ 6 ] . Length ) , CultureInfo . InvariantCulture ) ;
468
500
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 ) ) ) ;
469
504
}
470
505
471
- private static TimeSpan ParseTimeSpan ( ArraySegment < byte > value )
506
+ private static TimeSpan ParseTimeSpan ( ReadOnlySpan < byte > value )
472
507
{
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 ) ;
477
520
if ( hours < 0 )
478
521
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 ;
480
525
if ( hours < 0 )
481
526
seconds = - seconds ;
482
- if ( parts . Length == 3 )
527
+ if ( value . Length == 2 )
483
528
return new TimeSpan ( hours , minutes , seconds ) ;
484
529
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 ;
486
537
if ( hours < 0 )
487
538
microseconds = - microseconds ;
488
539
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 ) ) ) ;
489
543
}
490
544
491
- private static Guid CreateGuidFromBytes ( MySqlGuidFormat guidFormat , byte [ ] bytes )
545
+ private static Guid CreateGuidFromBytes ( MySqlGuidFormat guidFormat , ReadOnlySpan < byte > bytes )
492
546
{
547
+ #if NET45 || NET461 || NETSTANDARD1_3 || NETSTANDARD2_0
493
548
if ( guidFormat == MySqlGuidFormat . Binary16 )
494
549
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 ] } ) ;
495
- else if ( guidFormat == MySqlGuidFormat . TimeSwapBinary16 )
550
+ if ( guidFormat == MySqlGuidFormat . TimeSwapBinary16 )
496
551
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 ] } ) ;
497
- else
552
+ return new Guid ( bytes . ToArray ( ) ) ;
553
+ #else
554
+ unsafe
555
+ {
556
+ if ( guidFormat == MySqlGuidFormat . Binary16 )
557
+ {
558
+ ReadOnlySpan < byte > guid = stackalloc byte [ 16 ] { 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 ] } ;
559
+ return new Guid ( guid ) ;
560
+ }
561
+ if ( guidFormat == MySqlGuidFormat . TimeSwapBinary16 )
562
+ {
563
+ ReadOnlySpan < byte > guid = stackalloc byte [ 16 ] { 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 ] } ;
564
+ return new Guid ( guid ) ;
565
+ }
498
566
return new Guid ( bytes ) ;
567
+ }
568
+
569
+ #endif
499
570
}
500
571
501
572
public readonly ResultSet ResultSet ;
0 commit comments