1 // ByteFX.Data data access components for .Net
2 // Copyright (C) 2002-2003 ByteFX, Inc.
4 // This library is free software; you can redistribute it and/or
5 // modify it under the terms of the GNU Lesser General Public
6 // License as published by the Free Software Foundation; either
7 // version 2.1 of the License, or (at your option) any later version.
9 // This library is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 // Lesser General Public License for more details.
14 // You should have received a copy of the GNU Lesser General Public
15 // License along with this library; if not, write to the Free Software
16 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 using System
.ComponentModel
;
21 using System
.Collections
;
23 namespace ByteFX
.Data
.MySqlClient
26 /// Represents a SQL statement to execute against a MySQL database. This class cannot be inherited.
28 /// <include file='docs/MySqlCommand.xml' path='MyDocs/MyMembers[@name="Class"]/*'/>
30 [System
.Drawing
.ToolboxBitmap( typeof(MySqlCommand
), "MySqlClient.resources.command.bmp")]
32 [System
.ComponentModel
.DesignerCategory("Code")]
33 public sealed class MySqlCommand
: Component
, IDbCommand
, ICloneable
35 MySqlConnection connection
;
36 MySqlTransaction curTransaction
;
39 UpdateRowSource updatedRowSource
= UpdateRowSource
.Both
;
40 MySqlParameterCollection parameters
= new MySqlParameterCollection();
41 private ArrayList arraySql
= new ArrayList();
44 /// Overloaded. Initializes a new instance of the MySqlCommand class.
51 /// Overloaded. Initializes a new instance of the MySqlCommand class.
53 public MySqlCommand(string cmdText
)
55 this.cmdText
= cmdText
;
59 /// Overloaded. Initializes a new instance of the MySqlCommand class.
61 public MySqlCommand(System
.ComponentModel
.IContainer container
)
63 // Required for Windows.Forms Class Composition Designer support
68 /// Overloaded. Initializes a new instance of the MySqlCommand class.
70 public MySqlCommand(string cmdText
, MySqlConnection connection
)
72 this.cmdText
= cmdText
;
73 this.connection
= connection
;
77 /// Disposes of this instance of MySqlCommand
79 public new void Dispose()
85 /// Overloaded. Initializes a new instance of the MySqlCommand class.
87 public MySqlCommand(string cmdText
, MySqlConnection connection
, MySqlTransaction txn
)
89 this.cmdText
= cmdText
;
90 this.connection
= connection
;
97 /// Gets or sets the SQL statement to execute at the data source.
100 [Description("Command text to execute")]
102 [Editor("ByteFX.Data.Common.Design.SqlCommandTextEditor,MySqlClient.Design", typeof(System
.Drawing
.Design
.UITypeEditor
))]
104 public string CommandText
106 get { return cmdText; }
107 set { cmdText = value; }
110 internal int UpdateCount
112 get { return updateCount; }
116 /// Gets or sets the wait time before terminating the attempt to execute a command and generating an error.
119 [Description("Time to wait for command to execute")]
120 public int CommandTimeout
122 // TODO: support this
124 set { if (value != 0) throw new NotSupportedException(); }
128 /// Gets or sets a value indicating how the CommandText property is to be interpreted. Only
129 /// type Text is currently supported.
132 public CommandType CommandType
134 get { return CommandType.Text; }
137 if (value != CommandType
.Text
)
138 throw new NotSupportedException("This version of the MySql provider only supports Text command types");
143 /// Gets or sets the MySqlConnection used by this instance of the MySqlCommand.
145 [Category("Behavior")]
146 [Description("Connection used by the command")]
147 public IDbConnection Connection
150 * The user should be able to set or change the connection at
160 * The connection is associated with the transaction
161 * so set the transaction object to return a null reference if the connection
164 if (connection
!= value)
165 this.Transaction
= null;
167 connection
= (MySqlConnection
)value;
172 /// Gets the MySqlParameterCollection.
175 [Description("The parameters collection")]
176 [DesignerSerializationVisibility(DesignerSerializationVisibility
.Content
)]
177 public MySqlParameterCollection Parameters
179 get { return parameters; }
182 IDataParameterCollection IDbCommand
.Parameters
184 get { return parameters; }
188 /// Gets or sets the MySqlTransaction within which the MySqlCommand executes.
191 public IDbTransaction Transaction
194 * Set the transaction. Consider additional steps to ensure that the transaction
195 * is compatible with the connection, because the two are usually linked.
199 return curTransaction
;
203 curTransaction
= (MySqlTransaction
)value;
208 /// Gets or sets how command results are applied to the DataRow when used by the Update method of the DbDataAdapter.
210 [Category("Behavior")]
211 public UpdateRowSource UpdatedRowSource
215 return updatedRowSource
;
219 updatedRowSource
= value;
226 /// Attempts to cancel the execution of a MySqlCommand. This operation is not supported.
228 /// <exception cref="NotSupportedException">This operation is not supported.</exception>
231 throw new NotSupportedException();
235 /// Creates a new instance of a MySqlParameter object.
237 /// <returns></returns>
238 public MySqlParameter
CreateParameter()
240 return new MySqlParameter();
243 IDbDataParameter IDbCommand
.CreateParameter()
245 return CreateParameter();
248 private ArrayList
SplitSql(string sql
)
250 ArrayList commands
= new ArrayList();
251 System
.IO
.MemoryStream ms
= new System
.IO
.MemoryStream(sql
.Length
);
253 // first we tack on a semi-colon, if not already there, to make our
254 // sql processing code easier. Then we ask our encoder to give us
255 // the bytes for this sql string
256 byte[] bytes
= connection
.Encoding
.GetBytes(sql
+ ";");
259 bool escaped
= false;
261 for (int x
=0; x
< bytes
.Length
; x
++)
265 // if we see a quote marker, then check to see if we are opening
266 // or closing a quote
267 if ((b
== '\'' || b
== '\"') && ! escaped
)
269 if (b
== left_byte
) left_byte
= 0;
270 else if (left_byte
== 0) left_byte
= b
;
278 // if we see the marker for a parameter, then save its position and
280 else if (b
== '@' && left_byte
== 0 && ! escaped
&& parm_start
==-1)
283 // if we see a space and we are tracking a parameter, then end the parameter and have
284 // that parameter serialize itself to the memory stream
285 else if (parm_start
> -1 && (b
!= '@') && (b
!= '$') && (b
!= '_') && (b
!= '.') && ! Char
.IsLetterOrDigit((char)b
))
287 string parm_name
= sql
.Substring(parm_start
, x
-parm_start
);
289 if (parameters
.Contains( parm_name
))
291 MySqlParameter p
= (parameters
[parm_name
] as MySqlParameter
);
292 p
.SerializeToBytes(ms
, connection
);
296 // otherwise assume system param. just write it out
297 byte[] buf
= connection
.Encoding
.GetBytes(parm_name
);
298 ms
.Write(buf
, 0, buf
.Length
);
303 // if we are not in a string and we are not escaped and we are on a semi-colon,
304 // then write out what we have as a command
305 if (left_byte
== 0 && ! escaped
&& b
== ';' && ms
.Length
> 0)
307 bool goodcmd
= false;
308 byte[] byteArray
= ms
.ToArray();
309 foreach (byte cmdByte
in byteArray
)
310 if (cmdByte
!= ' ') { goodcmd = true; break; }
313 commands
.Add( byteArray
);
316 else if (parm_start
== -1)
320 // we want to write out the bytes in all cases except when we are parsing out a parameter
321 if (escaped
&& b
!= '\\') escaped
= false;
327 private void ReadOffResultSet()
329 Driver driver
= connection
.InternalConnection
.Driver
;
331 // first read off the schema
332 Packet packet
= driver
.ReadPacket();
333 while (! packet
.IsLastPacket())
334 packet
= driver
.ReadPacket();
336 // now read off the data
337 packet
= driver
.ReadPacket();
338 while (! packet
.IsLastPacket())
339 packet
= driver
.ReadPacket();
343 /// Internal function to execute the next command in an array of commands
345 internal CommandResult
ExecuteBatch( bool stopAtResultSet
)
347 Driver driver
= connection
.InternalConnection
.Driver
;
349 while (arraySql
.Count
> 0)
351 byte[] sql
= (byte[])arraySql
[0];
352 arraySql
.RemoveAt(0);
354 CommandResult result
= driver
.Send( DBCmd
.QUERY
, sql
);
356 if (result
.IsResultSet
)
358 if (stopAtResultSet
) return result
;
363 // at this point, we know it is a zero field count
364 if (updateCount
== -1) updateCount
= 0;
365 updateCount
+= result
.RowsAffected
;
371 /// Executes a SQL statement against the connection and returns the number of rows affected.
373 /// <returns>Number of rows affected</returns>
374 public int ExecuteNonQuery()
376 // There must be a valid and open connection.
377 if (connection
== null || connection
.State
!= ConnectionState
.Open
)
378 throw new InvalidOperationException("Connection must be valid and open");
380 // Data readers have to be closed first
381 if (connection
.Reader
!= null)
382 throw new MySqlException("There is already an open DataReader associated with this Connection which must be closed first.");
384 // execute any commands left in the queue from before.
385 //ExecuteBatch(false);
387 arraySql
= SplitSql( cmdText
);
392 return (int)updateCount
;
395 IDataReader IDbCommand
.ExecuteReader ()
397 return ExecuteReader ();
400 IDataReader IDbCommand
.ExecuteReader (CommandBehavior behavior
)
402 return ExecuteReader (behavior
);
406 /// Overloaded. Sends the CommandText to the Connection and builds a MySqlDataReader.
408 /// <returns></returns>
409 public MySqlDataReader
ExecuteReader()
411 return ExecuteReader(CommandBehavior
.Default
);
416 /// Overloaded. Sends the CommandText to the Connection and builds a MySqlDataReader.
418 /// <returns></returns>
419 public MySqlDataReader
ExecuteReader(CommandBehavior behavior
)
421 // There must be a valid and open connection.
422 if (connection
== null || connection
.State
!= ConnectionState
.Open
)
423 throw new InvalidOperationException("Connection must be valid and open");
425 // make sure all readers on this connection are closed
426 if (connection
.Reader
!= null)
427 throw new InvalidOperationException("There is already an open DataReader associated with this Connection which must be closed first.");
429 string sql
= cmdText
;
431 if (0 != (behavior
& CommandBehavior
.KeyInfo
))
435 if (0 != (behavior
& CommandBehavior
.SchemaOnly
))
439 if (0 != (behavior
& CommandBehavior
.SequentialAccess
))
443 if (0 != (behavior
& CommandBehavior
.SingleResult
))
447 if (0 != (behavior
& CommandBehavior
.SingleRow
))
449 sql
= String
.Format("SET SQL_SELECT_LIMIT=1;{0};SET sql_select_limit=-1;", cmdText
);
452 arraySql
= SplitSql( sql
);
455 MySqlDataReader reader
= new MySqlDataReader(this, behavior
);
457 // move to the first resultset
459 connection
.Reader
= reader
;
464 /// Executes the query, and returns the first column of the first row in the
465 /// result set returned by the query. Extra columns or rows are ignored.
467 /// <returns></returns>
468 public object ExecuteScalar()
470 // There must be a valid and open connection.
471 if (connection
== null || connection
.State
!= ConnectionState
.Open
)
472 throw new InvalidOperationException("Connection must be valid and open");
474 // Data readers have to be closed first
475 if (connection
.Reader
!= null)
476 throw new MySqlException("There is already an open DataReader associated with this Connection which must be closed first.");
478 arraySql
= SplitSql( cmdText
);
481 MySqlDataReader reader
= new MySqlDataReader(this, 0);
485 val
= reader
.GetValue(0);
491 /// Creates a prepared version of the command on an instance of MySQL Server. This
492 /// is currently not supported.
494 public void Prepare()
501 /// Creates a clone of this MySqlCommand object. CommandText, Connection, and Transaction properties
502 /// are included as well as the entire parameter list.
504 /// <returns>The cloned MySqlCommand object</returns>
505 public object Clone()
507 MySqlCommand clone
= new MySqlCommand(cmdText
, connection
, curTransaction
);
508 foreach (MySqlParameter p
in parameters
)
510 clone
.Parameters
.Add((p
as ICloneable
).Clone());