@@ -364,6 +364,7 @@ internal async Task SendDataReaderAsync(IOBehavior ioBehavior, CancellationToken
364
364
try
365
365
{
366
366
var values = new object ? [ m_valuesEnumerator ! . FieldCount ] ;
367
+ Encoder ? utf8Encoder = null ;
367
368
while ( true )
368
369
{
369
370
var hasMore = ioBehavior == IOBehavior . Asynchronous ?
@@ -373,46 +374,31 @@ await m_valuesEnumerator.MoveNextAsync().ConfigureAwait(false) :
373
374
break ;
374
375
375
376
m_valuesEnumerator . GetValues ( values ) ;
376
- retryRow :
377
- var startOutputIndex = outputIndex ;
378
- var wroteRow = true ;
379
- var shouldAppendSeparator = false ;
380
- foreach ( var value in values )
377
+ for ( var valueIndex = 0 ; valueIndex < values . Length ; valueIndex ++ )
381
378
{
382
- if ( shouldAppendSeparator )
379
+ if ( valueIndex > 0 )
383
380
buffer [ outputIndex ++ ] = ( byte ) '\t ' ;
384
- else
385
- shouldAppendSeparator = true ;
386
381
387
- if ( outputIndex >= maxLength || ! WriteValue ( m_connection , value , buffer . AsSpan ( 0 , maxLength ) . Slice ( outputIndex ) , out var bytesWritten ) )
382
+ var inputIndex = 0 ;
383
+ var bytesWritten = 0 ;
384
+ while ( outputIndex >= maxLength || ! WriteValue ( m_connection , values [ valueIndex ] , ref inputIndex , ref utf8Encoder , buffer . AsSpan ( 0 , maxLength ) . Slice ( outputIndex ) , out bytesWritten ) )
388
385
{
389
- wroteRow = false ;
390
- break ;
386
+ var payload = new PayloadData ( new ArraySegment < byte > ( buffer , 0 , outputIndex + bytesWritten ) ) ;
387
+ await m_connection . Session . SendReplyAsync ( payload , ioBehavior , cancellationToken ) . ConfigureAwait ( false ) ;
388
+ outputIndex = 0 ;
389
+ bytesWritten = 0 ;
391
390
}
392
391
outputIndex += bytesWritten ;
393
392
}
393
+ buffer [ outputIndex ++ ] = ( byte ) '\n ' ;
394
394
395
- if ( ! wroteRow )
396
- {
397
- if ( startOutputIndex == 0 )
398
- throw new NotSupportedException ( "Total row length must be less than 1 MiB." ) ;
399
- var payload = new PayloadData ( new ArraySegment < byte > ( buffer , 0 , startOutputIndex ) ) ;
400
- await m_connection . Session . SendReplyAsync ( payload , ioBehavior , cancellationToken ) . ConfigureAwait ( false ) ;
401
- outputIndex = 0 ;
402
- goto retryRow ;
403
- }
404
- else
395
+ RowsCopied ++ ;
396
+ if ( eventArgs is not null && RowsCopied % NotifyAfter == 0 )
405
397
{
406
- buffer [ outputIndex ++ ] = ( byte ) '\n ' ;
407
-
408
- RowsCopied ++ ;
409
- if ( eventArgs is not null && RowsCopied % NotifyAfter == 0 )
410
- {
411
- eventArgs . RowsCopied = RowsCopied ;
412
- MySqlRowsCopied ! ( this , eventArgs ) ;
413
- if ( eventArgs . Abort )
414
- break ;
415
- }
398
+ eventArgs . RowsCopied = RowsCopied ;
399
+ MySqlRowsCopied ! ( this , eventArgs ) ;
400
+ if ( eventArgs . Abort )
401
+ break ;
416
402
}
417
403
}
418
404
@@ -428,8 +414,14 @@ await m_valuesEnumerator.MoveNextAsync().ConfigureAwait(false) :
428
414
m_wasAborted = eventArgs ? . Abort ?? false ;
429
415
}
430
416
431
- static bool WriteValue ( MySqlConnection connection , object ? value , Span < byte > output , out int bytesWritten )
417
+ static bool WriteValue ( MySqlConnection connection , object ? value , ref int inputIndex , ref Encoder ? utf8Encoder , Span < byte > output , out int bytesWritten )
432
418
{
419
+ if ( output . Length == 0 )
420
+ {
421
+ bytesWritten = 0 ;
422
+ return false ;
423
+ }
424
+
433
425
if ( value is null || value == DBNull . Value )
434
426
{
435
427
if ( output . Length < EscapedNull . Length )
@@ -443,11 +435,11 @@ static bool WriteValue(MySqlConnection connection, object? value, Span<byte> out
443
435
}
444
436
else if ( value is string stringValue )
445
437
{
446
- return WriteString ( stringValue , output , out bytesWritten ) ;
438
+ return WriteSubstring ( stringValue , ref inputIndex , ref utf8Encoder , output , out bytesWritten ) ;
447
439
}
448
440
else if ( value is char charValue )
449
441
{
450
- return WriteString ( charValue . ToString ( ) , output , out bytesWritten ) ;
442
+ return WriteString ( charValue . ToString ( ) , ref utf8Encoder , output , out bytesWritten ) ;
451
443
}
452
444
else if ( value is byte byteValue )
453
445
{
@@ -493,7 +485,7 @@ static bool WriteValue(MySqlConnection connection, object? value, Span<byte> out
493
485
value is MySqlGeometry geometry ? geometry . ValueSpan :
494
486
( ( ReadOnlyMemory < byte > ) value ) . Span ;
495
487
496
- return WriteBytes ( inputSpan , output , out bytesWritten ) ;
488
+ return WriteBytes ( inputSpan , ref inputIndex , output , out bytesWritten ) ;
497
489
}
498
490
else if ( value is bool boolValue )
499
491
{
@@ -509,14 +501,14 @@ static bool WriteValue(MySqlConnection connection, object? value, Span<byte> out
509
501
else if ( value is float || value is double )
510
502
{
511
503
// NOTE: Utf8Formatter doesn't support "R"
512
- return WriteString ( "{0:R}" . FormatInvariant ( value ) , output , out bytesWritten ) ;
504
+ return WriteString ( "{0:R}" . FormatInvariant ( value ) , ref utf8Encoder , output , out bytesWritten ) ;
513
505
}
514
506
else if ( value is MySqlDateTime mySqlDateTimeValue )
515
507
{
516
508
if ( mySqlDateTimeValue . IsValidDateTime )
517
- return WriteString ( "{0:yyyy'-'MM'-'dd' 'HH':'mm':'ss'.'ffffff}" . FormatInvariant ( mySqlDateTimeValue . GetDateTime ( ) ) , output , out bytesWritten ) ;
509
+ return WriteString ( "{0:yyyy'-'MM'-'dd' 'HH':'mm':'ss'.'ffffff}" . FormatInvariant ( mySqlDateTimeValue . GetDateTime ( ) ) , ref utf8Encoder , output , out bytesWritten ) ;
518
510
else
519
- return WriteString ( "0000-00-00" , output , out bytesWritten ) ;
511
+ return WriteString ( "0000-00-00" , ref utf8Encoder , output , out bytesWritten ) ;
520
512
}
521
513
else if ( value is DateTime dateTimeValue )
522
514
{
@@ -525,12 +517,12 @@ static bool WriteValue(MySqlConnection connection, object? value, Span<byte> out
525
517
else if ( connection . DateTimeKind == DateTimeKind . Local && dateTimeValue . Kind == DateTimeKind . Utc )
526
518
throw new MySqlException ( "DateTime.Kind must not be Utc when DateTimeKind setting is Local" ) ;
527
519
528
- return WriteString ( "{0:yyyy'-'MM'-'dd' 'HH':'mm':'ss'.'ffffff}" . FormatInvariant ( dateTimeValue ) , output , out bytesWritten ) ;
520
+ return WriteString ( "{0:yyyy'-'MM'-'dd' 'HH':'mm':'ss'.'ffffff}" . FormatInvariant ( dateTimeValue ) , ref utf8Encoder , output , out bytesWritten ) ;
529
521
}
530
522
else if ( value is DateTimeOffset dateTimeOffsetValue )
531
523
{
532
524
// store as UTC as it will be read as such when deserialized from a timespan column
533
- return WriteString ( "{0:yyyy'-'MM'-'dd' 'HH':'mm':'ss'.'ffffff}" . FormatInvariant ( dateTimeOffsetValue . UtcDateTime ) , output , out bytesWritten ) ;
525
+ return WriteString ( "{0:yyyy'-'MM'-'dd' 'HH':'mm':'ss'.'ffffff}" . FormatInvariant ( dateTimeOffsetValue . UtcDateTime ) , ref utf8Encoder , output , out bytesWritten ) ;
534
526
}
535
527
else if ( value is TimeSpan ts )
536
528
{
@@ -540,7 +532,7 @@ static bool WriteValue(MySqlConnection connection, object? value, Span<byte> out
540
532
isNegative = true ;
541
533
ts = TimeSpan . FromTicks ( - ts . Ticks ) ;
542
534
}
543
- return WriteString ( "{0}{1}:{2:mm':'ss'.'ffffff}'" . FormatInvariant ( isNegative ? "-" : "" , ts . Days * 24 + ts . Hours , ts ) , output , out bytesWritten ) ;
535
+ return WriteString ( "{0}{1}:{2:mm':'ss'.'ffffff}'" . FormatInvariant ( isNegative ? "-" : "" , ts . Days * 24 + ts . Hours , ts ) , ref utf8Encoder , output , out bytesWritten ) ;
544
536
}
545
537
else if ( value is Guid guidValue )
546
538
{
@@ -566,7 +558,7 @@ static bool WriteValue(MySqlConnection connection, object? value, Span<byte> out
566
558
Utility . SwapBytes ( bytes , 1 , 3 ) ;
567
559
}
568
560
}
569
- return WriteBytes ( bytes , output , out bytesWritten ) ;
561
+ return WriteBytes ( bytes , ref inputIndex , output , out bytesWritten ) ;
570
562
}
571
563
else
572
564
{
@@ -576,72 +568,80 @@ static bool WriteValue(MySqlConnection connection, object? value, Span<byte> out
576
568
}
577
569
else if ( value is Enum )
578
570
{
579
- return WriteString ( "{0:d}" . FormatInvariant ( value ) , output , out bytesWritten ) ;
571
+ return WriteString ( "{0:d}" . FormatInvariant ( value ) , ref utf8Encoder , output , out bytesWritten ) ;
580
572
}
581
573
else
582
574
{
583
575
throw new NotSupportedException ( "Type {0} not currently supported. Value: {1}" . FormatInvariant ( value . GetType ( ) . Name , value ) ) ;
584
576
}
585
577
}
586
578
587
- static bool WriteString ( string value , Span < byte > output , out int bytesWritten )
579
+ static bool WriteString ( string value , ref Encoder ? utf8Encoder , Span < byte > output , out int bytesWritten )
580
+ {
581
+ var inputIndex = 0 ;
582
+ if ( WriteSubstring ( value , ref inputIndex , ref utf8Encoder , output , out bytesWritten ) )
583
+ return true ;
584
+ bytesWritten = 0 ;
585
+ return false ;
586
+ }
587
+
588
+ // Writes as much of 'value' as possible, starting at 'inputIndex' and writing UTF-8-encoded bytes to 'output'.
589
+ // 'inputIndex' will be updated to the next character to be written, and 'bytesWritten' the number of bytes written to 'output'.
590
+ static bool WriteSubstring ( string value , ref int inputIndex , ref Encoder ? utf8Encoder , Span < byte > output , out int bytesWritten )
588
591
{
589
- var index = 0 ;
590
592
bytesWritten = 0 ;
591
- while ( index < value . Length )
593
+ while ( inputIndex < value . Length )
592
594
{
593
- if ( Array . IndexOf ( s_specialCharacters , value [ index ] ) != - 1 )
595
+ if ( Array . IndexOf ( s_specialCharacters , value [ inputIndex ] ) != - 1 )
594
596
{
595
- if ( output . Length < 2 )
596
- {
597
- bytesWritten = 0 ;
597
+ if ( output . Length <= 2 )
598
598
return false ;
599
- }
600
-
599
+
601
600
output [ 0 ] = ( byte ) '\\ ' ;
602
- output [ 1 ] = ( byte ) value [ index ] ;
601
+ output [ 1 ] = ( byte ) value [ inputIndex ] ;
603
602
output = output . Slice ( 2 ) ;
604
603
bytesWritten += 2 ;
605
- index ++ ;
604
+ inputIndex ++ ;
606
605
}
607
606
else
608
607
{
609
- var nextIndex = value . IndexOfAny ( s_specialCharacters , index ) ;
608
+ var nextIndex = value . IndexOfAny ( s_specialCharacters , inputIndex ) ;
610
609
if ( nextIndex == - 1 )
611
610
nextIndex = value . Length ;
612
- var encodedSize = Encoding . UTF8 . GetByteCount ( value . AsSpan ( index , nextIndex - index ) ) ;
613
- if ( encodedSize > output . Length )
614
- {
615
- bytesWritten = 0 ;
611
+
612
+ utf8Encoder ??= Encoding . UTF8 . GetEncoder ( ) ;
613
+ #if NETSTANDARD1_3
614
+ var buffer = new byte [ output . Length ] ;
615
+ utf8Encoder . Convert ( value . ToCharArray ( ) , inputIndex , nextIndex - inputIndex , buffer , 0 , buffer . Length , nextIndex == value . Length , out var charsUsed , out var bytesUsed , out var completed ) ;
616
+ buffer . AsSpan ( ) . CopyTo ( output ) ;
617
+ #else
618
+ utf8Encoder . Convert ( value . AsSpan ( inputIndex , nextIndex - inputIndex ) , output , nextIndex == value . Length , out var charsUsed , out var bytesUsed , out var completed ) ;
619
+ #endif
620
+
621
+ bytesWritten += bytesUsed ;
622
+ output = output . Slice ( bytesUsed ) ;
623
+ inputIndex += charsUsed ;
624
+
625
+ if ( ! completed )
616
626
return false ;
617
- }
618
- var encodedBytesWritten = Encoding . UTF8 . GetBytes ( value . AsSpan ( index , nextIndex - index ) , output ) ;
619
- bytesWritten += encodedBytesWritten ;
620
- output = output . Slice ( encodedBytesWritten ) ;
621
- index = nextIndex ;
622
627
}
623
628
}
624
629
625
630
return true ;
626
631
}
627
632
628
- static bool WriteBytes ( ReadOnlySpan < byte > value , Span < byte > output , out int bytesWritten )
633
+ static bool WriteBytes ( ReadOnlySpan < byte > value , ref int inputIndex , Span < byte > output , out int bytesWritten )
629
634
{
630
- if ( output . Length < value . Length * 2 )
631
- {
632
- bytesWritten = 0 ;
633
- return false ;
634
- }
635
-
636
- foreach ( var by in value )
635
+ bytesWritten = 0 ;
636
+ for ( ; inputIndex < value . Length && output . Length > 2 ; inputIndex ++ )
637
637
{
638
- WriteNibble ( by >> 4 , output ) ;
639
- WriteNibble ( by & 0xF , output . Slice ( 1 ) ) ;
638
+ WriteNibble ( value [ inputIndex ] >> 4 , output ) ;
639
+ WriteNibble ( value [ inputIndex ] & 0xF , output . Slice ( 1 ) ) ;
640
640
output = output . Slice ( 2 ) ;
641
+ bytesWritten += 2 ;
641
642
}
642
643
643
- bytesWritten = value . Length * 2 ;
644
- return true ;
644
+ return inputIndex == value . Length ;
645
645
}
646
646
647
647
static void WriteNibble ( int value , Span < byte > output ) => output [ 0 ] = value < 10 ? ( byte ) ( value + 0x30 ) : ( byte ) ( value + 0x57 ) ;
0 commit comments