1
+ using System . Runtime . InteropServices ;
1
2
using MySqlConnector . Logging ;
2
3
using MySqlConnector . Protocol ;
3
4
using MySqlConnector . Protocol . Serialization ;
@@ -12,6 +13,74 @@ internal sealed class SingleCommandPayloadCreator : ICommandPayloadCreator
12
13
// with this as the first column name, the result set will be treated as 'out' parameters for the previous command.
13
14
public static string OutParameterSentinelColumnName => "\uE001 \b \x0B " ;
14
15
16
+ public async ValueTask WritePrologueAsync ( MySqlConnection connection , CommandListPosition commandListPosition , IOBehavior ioBehavior , CancellationToken cancellationToken )
17
+ {
18
+ // get the current command and check for prepared statements
19
+ var command = commandListPosition . CommandAt ( commandListPosition . CommandIndex ) ;
20
+ var preparedStatements = commandListPosition . PreparedStatements ?? command . TryGetPreparedStatements ( ) ;
21
+ if ( preparedStatements is not null )
22
+ {
23
+ // get the current prepared statement; WriteQueryCommand will advance this
24
+ var preparedStatement = preparedStatements . Statements [ commandListPosition . PreparedStatementIndex ] ;
25
+ if ( preparedStatement . Parameters is { } parameters )
26
+ {
27
+ // check each parameter
28
+ for ( var i = 0 ; i < parameters . Length ; i ++ )
29
+ {
30
+ // look up this parameter in the command's parameter collection and check if it is a Stream
31
+ var parameterName = preparedStatement . Statement . NormalizedParameterNames ! [ i ] ;
32
+ var parameterIndex = parameterName is not null ? ( command . RawParameters ? . UnsafeIndexOf ( parameterName ) ?? - 1 ) : preparedStatement . Statement . ParameterIndexes [ i ] ;
33
+ if ( parameterIndex != - 1 && command . RawParameters ! [ parameterIndex ] is { Value : Stream stream and not MemoryStream } )
34
+ {
35
+ // send almost-full packets, but don't send exactly ProtocolUtility.MaxPacketSize bytes in one payload (as that's ambiguous to whether another packet follows).
36
+ const int maxDataSize = 16_000_000 ;
37
+ int totalBytesRead ;
38
+ while ( true )
39
+ {
40
+ // write seven-byte COM_STMT_SEND_LONG_DATA header: https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_com_stmt_send_long_data.html
41
+ var writer = new ByteBufferWriter ( maxDataSize ) ;
42
+ writer . Write ( ( byte ) CommandKind . StatementSendLongData ) ;
43
+ writer . Write ( preparedStatement . StatementId ) ;
44
+ writer . Write ( ( ushort ) i ) ;
45
+
46
+ // keep reading from the stream until we've filled the buffer to send
47
+ #if NET7_0_OR_GREATER
48
+ if ( ioBehavior == IOBehavior . Synchronous )
49
+ totalBytesRead = stream . ReadAtLeast ( writer . GetSpan ( maxDataSize ) . Slice ( 0 , maxDataSize ) , maxDataSize , throwOnEndOfStream : false ) ;
50
+ else
51
+ totalBytesRead = await stream . ReadAtLeastAsync ( writer . GetMemory ( maxDataSize ) . Slice ( 0 , maxDataSize ) , maxDataSize , throwOnEndOfStream : false , cancellationToken ) . ConfigureAwait ( false ) ;
52
+ writer . Advance ( totalBytesRead ) ;
53
+ #else
54
+ totalBytesRead = 0 ;
55
+ int bytesRead ;
56
+ do
57
+ {
58
+ var sizeToRead = maxDataSize - totalBytesRead ;
59
+ ReadOnlyMemory < byte > bufferMemory = writer . GetMemory ( sizeToRead ) ;
60
+ if ( ! MemoryMarshal . TryGetArray ( bufferMemory , out var arraySegment ) )
61
+ throw new InvalidOperationException ( "Failed to get array segment from buffer memory." ) ;
62
+ if ( ioBehavior == IOBehavior . Synchronous )
63
+ bytesRead = stream . Read ( arraySegment . Array ! , arraySegment . Offset , sizeToRead ) ;
64
+ else
65
+ bytesRead = await stream . ReadAsync ( arraySegment . Array ! , arraySegment . Offset , sizeToRead , cancellationToken ) . ConfigureAwait ( false ) ;
66
+ totalBytesRead += bytesRead ;
67
+ writer . Advance ( bytesRead ) ;
68
+ } while ( bytesRead > 0 ) ;
69
+ #endif
70
+
71
+ if ( totalBytesRead == 0 )
72
+ break ;
73
+
74
+ // send StatementSendLongData; MySQL Server will keep appending the sent data to the parameter value
75
+ using var payload = writer . ToPayloadData ( ) ;
76
+ await connection . Session . SendAsync ( payload , ioBehavior , cancellationToken ) . ConfigureAwait ( false ) ;
77
+ }
78
+ }
79
+ }
80
+ }
81
+ }
82
+ }
83
+
15
84
public bool WriteQueryCommand ( ref CommandListPosition commandListPosition , IDictionary < string , CachedProcedure ? > cachedProcedures , ByteBufferWriter writer , bool appendSemicolon )
16
85
{
17
86
if ( commandListPosition . CommandIndex == commandListPosition . CommandCount )
@@ -58,6 +127,7 @@ public bool WriteQueryCommand(ref CommandListPosition commandListPosition, IDict
58
127
{
59
128
commandListPosition . CommandIndex ++ ;
60
129
commandListPosition . PreparedStatementIndex = 0 ;
130
+ commandListPosition . PreparedStatements = null ;
61
131
}
62
132
}
63
133
return true ;
0 commit comments