1
1
using System ;
2
+ using System . Collections . Generic ;
2
3
using System . Data ;
3
4
using System . Data . Common ;
4
5
using System . Threading ;
5
6
using System . Threading . Tasks ;
6
7
using MySqlConnector . Core ;
8
+ using MySqlConnector . Protocol ;
9
+ using MySqlConnector . Protocol . Payloads ;
7
10
using MySqlConnector . Protocol . Serialization ;
8
11
using MySqlConnector . Utilities ;
9
12
@@ -62,14 +65,85 @@ public MySqlCommand(string commandText, MySqlConnection connection, MySqlTransac
62
65
63
66
public new MySqlDataReader ExecuteReader ( CommandBehavior commandBehavior ) => ( MySqlDataReader ) base . ExecuteReader ( commandBehavior ) ;
64
67
65
- public override void Prepare ( )
68
+ public override void Prepare ( ) => PrepareAsync ( IOBehavior . Synchronous , default ) . GetAwaiter ( ) . GetResult ( ) ;
69
+ public Task PrepareAsync ( ) => PrepareAsync ( AsyncIOBehavior , default ) ;
70
+ public Task PrepareAsync ( CancellationToken cancellationToken ) => PrepareAsync ( AsyncIOBehavior , cancellationToken ) ;
71
+
72
+ private async Task PrepareAsync ( IOBehavior ioBehavior , CancellationToken cancellationToken )
66
73
{
67
74
if ( Connection == null )
68
75
throw new InvalidOperationException ( "Connection property must be non-null." ) ;
69
76
if ( Connection . State != ConnectionState . Open )
70
77
throw new InvalidOperationException ( "Connection must be Open; current state is {0}" . FormatInvariant ( Connection . State ) ) ;
71
78
if ( string . IsNullOrWhiteSpace ( CommandText ) )
72
79
throw new InvalidOperationException ( "CommandText must be specified" ) ;
80
+ if ( m_connection ? . HasActiveReader ?? false )
81
+ throw new InvalidOperationException ( "Cannot call Prepare when there is an open DataReader for this command; it must be closed first." ) ;
82
+ if ( Connection . IgnorePrepare )
83
+ return ;
84
+
85
+ if ( CommandType != CommandType . Text )
86
+ throw new NotSupportedException ( "Only CommandType.Text is currently supported by MySqlCommand.Prepare" ) ;
87
+
88
+ var statementPreparer = new StatementPreparer ( CommandText , Parameters , CreateStatementPreparerOptions ( ) ) ;
89
+ var parsedStatements = statementPreparer . SplitStatements ( ) ;
90
+
91
+ if ( parsedStatements . Statements . Count > 1 )
92
+ throw new NotSupportedException ( "Multiple semicolon-delimited SQL statements are not supported by MySqlCommand.Prepare" ) ;
93
+
94
+ var columnsAndParameters = new ResizableArray < byte > ( ) ;
95
+ var columnsAndParametersSize = 0 ;
96
+
97
+ var preparedStatements = new List < PreparedStatement > ( parsedStatements . Statements . Count ) ;
98
+ foreach ( var statement in parsedStatements . Statements )
99
+ {
100
+ await Connection . Session . SendAsync ( new PayloadData ( statement . StatementBytes ) , ioBehavior , cancellationToken ) . ConfigureAwait ( false ) ;
101
+ var payload = await Connection . Session . ReceiveReplyAsync ( ioBehavior , cancellationToken ) . ConfigureAwait ( false ) ;
102
+ var response = StatementPrepareResponsePayload . Create ( payload ) ;
103
+
104
+ ColumnDefinitionPayload [ ] parameters = null ;
105
+ if ( response . ParameterCount > 0 )
106
+ {
107
+ parameters = new ColumnDefinitionPayload [ response . ParameterCount ] ;
108
+ for ( var i = 0 ; i < response . ParameterCount ; i ++ )
109
+ {
110
+ payload = await Connection . Session . ReceiveReplyAsync ( ioBehavior , cancellationToken ) . ConfigureAwait ( false ) ;
111
+ Utility . Resize ( ref columnsAndParameters , columnsAndParametersSize + payload . ArraySegment . Count ) ;
112
+ Buffer . BlockCopy ( payload . ArraySegment . Array , payload . ArraySegment . Offset , columnsAndParameters . Array , columnsAndParametersSize , payload . ArraySegment . Count ) ;
113
+ parameters [ i ] = ColumnDefinitionPayload . Create ( new ResizableArraySegment < byte > ( columnsAndParameters , columnsAndParametersSize , payload . ArraySegment . Count ) ) ;
114
+ columnsAndParametersSize += payload . ArraySegment . Count ;
115
+ }
116
+ if ( ! Connection . Session . SupportsDeprecateEof )
117
+ {
118
+ payload = await Connection . Session . ReceiveReplyAsync ( ioBehavior , cancellationToken ) . ConfigureAwait ( false ) ;
119
+ EofPayload . Create ( payload ) ;
120
+ }
121
+ }
122
+
123
+ ColumnDefinitionPayload [ ] columns = null ;
124
+ if ( response . ColumnCount > 0 )
125
+ {
126
+ columns = new ColumnDefinitionPayload [ response . ColumnCount ] ;
127
+ for ( var i = 0 ; i < response . ColumnCount ; i ++ )
128
+ {
129
+ payload = await Connection . Session . ReceiveReplyAsync ( ioBehavior , cancellationToken ) . ConfigureAwait ( false ) ;
130
+ Utility . Resize ( ref columnsAndParameters , columnsAndParametersSize + payload . ArraySegment . Count ) ;
131
+ Buffer . BlockCopy ( payload . ArraySegment . Array , payload . ArraySegment . Offset , columnsAndParameters . Array , columnsAndParametersSize , payload . ArraySegment . Count ) ;
132
+ columns [ i ] = ColumnDefinitionPayload . Create ( new ResizableArraySegment < byte > ( columnsAndParameters , columnsAndParametersSize , payload . ArraySegment . Count ) ) ;
133
+ columnsAndParametersSize += payload . ArraySegment . Count ;
134
+ }
135
+ if ( ! Connection . Session . SupportsDeprecateEof )
136
+ {
137
+ payload = await Connection . Session . ReceiveReplyAsync ( ioBehavior , cancellationToken ) . ConfigureAwait ( false ) ;
138
+ EofPayload . Create ( payload ) ;
139
+ }
140
+ }
141
+
142
+ preparedStatements . Add ( new PreparedStatement ( response . StatementId , statement , columns , parameters ) ) ;
143
+ }
144
+
145
+ m_parsedStatements = parsedStatements ;
146
+ m_statements = preparedStatements ;
73
147
}
74
148
75
149
public override string CommandText
@@ -80,6 +154,7 @@ public override string CommandText
80
154
if ( m_connection ? . HasActiveReader ?? false )
81
155
throw new InvalidOperationException ( "Cannot set MySqlCommand.CommandText when there is an open DataReader for this command; it must be closed first." ) ;
82
156
m_commandText = value ;
157
+ m_statements = null ;
83
158
}
84
159
}
85
160
@@ -104,22 +179,12 @@ public override int CommandTimeout
104
179
105
180
public override CommandType CommandType
106
181
{
107
- get
108
- {
109
- return m_commandType ;
110
- }
182
+ get => m_commandType ;
111
183
set
112
184
{
113
185
if ( value != CommandType . Text && value != CommandType . StoredProcedure )
114
186
throw new ArgumentException ( "CommandType must be Text or StoredProcedure." , nameof ( value ) ) ;
115
- if ( value == m_commandType )
116
- return ;
117
-
118
187
m_commandType = value ;
119
- if ( value == CommandType . Text )
120
- m_commandExecutor = new TextCommandExecutor ( this ) ;
121
- else if ( value == CommandType . StoredProcedure )
122
- m_commandExecutor = new StoredProcedureCommandExecutor ( this ) ;
123
188
}
124
189
}
125
190
@@ -203,9 +268,20 @@ protected override Task<DbDataReader> ExecuteDbDataReaderAsync(CommandBehavior b
203
268
return ExecuteReaderAsync ( behavior , AsyncIOBehavior , cancellationToken ) ;
204
269
}
205
270
206
- internal Task < DbDataReader > ExecuteReaderAsync ( CommandBehavior behavior , IOBehavior ioBehavior , CancellationToken cancellationToken ) =>
207
- ! IsValid ( out var exception ) ? Utility . TaskFromException < DbDataReader > ( exception ) :
208
- m_commandExecutor . ExecuteReaderAsync ( CommandText , m_parameterCollection , behavior , ioBehavior , cancellationToken ) ;
271
+ internal Task < DbDataReader > ExecuteReaderAsync ( CommandBehavior behavior , IOBehavior ioBehavior , CancellationToken cancellationToken )
272
+ {
273
+ if ( ! IsValid ( out var exception ) )
274
+ return Utility . TaskFromException < DbDataReader > ( exception ) ;
275
+
276
+ if ( m_statements != null )
277
+ m_commandExecutor = new PreparedStatementCommandExecutor ( this ) ;
278
+ else if ( m_commandType == CommandType . Text )
279
+ m_commandExecutor = new TextCommandExecutor ( this ) ;
280
+ else if ( m_commandType == CommandType . StoredProcedure )
281
+ m_commandExecutor = new StoredProcedureCommandExecutor ( this ) ;
282
+
283
+ return m_commandExecutor . ExecuteReaderAsync ( CommandText , m_parameterCollection , behavior , ioBehavior , cancellationToken ) ;
284
+ }
209
285
210
286
protected override void Dispose ( bool disposing )
211
287
{
@@ -214,6 +290,8 @@ protected override void Dispose(bool disposing)
214
290
if ( disposing )
215
291
{
216
292
m_parameterCollection = null ;
293
+ m_parsedStatements ? . Dispose ( ) ;
294
+ m_parsedStatements = null ;
217
295
}
218
296
}
219
297
finally
@@ -243,6 +321,8 @@ internal IDisposable RegisterCancel(CancellationToken token)
243
321
244
322
internal int CancelAttemptCount { get ; set ; }
245
323
324
+ internal IReadOnlyList < PreparedStatement > PreparedStatements => m_statements ;
325
+
246
326
/// <summary>
247
327
/// Causes the effective command timeout to be reset back to the value specified by <see cref="CommandTimeout"/>.
248
328
/// </summary>
@@ -324,6 +404,8 @@ private bool IsValid(out Exception exception)
324
404
MySqlConnection m_connection ;
325
405
string m_commandText ;
326
406
MySqlParameterCollection m_parameterCollection ;
407
+ ParsedStatements m_parsedStatements ;
408
+ IReadOnlyList < PreparedStatement > m_statements ;
327
409
int ? m_commandTimeout ;
328
410
CommandType m_commandType ;
329
411
ICommandExecutor m_commandExecutor ;
0 commit comments