**** Merged from MCS ****
[mono-project.git] / mcs / class / ByteFX.Data / mysqlclient / command.cs
blob1d1c7c446375d9b77aea6e0a0811e18084045db5
1 // ByteFX.Data data access components for .Net
2 // Copyright (C) 2002-2003 ByteFX, Inc.
3 //
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.
8 //
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.
13 //
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
18 using System;
19 using System.Data;
20 using System.ComponentModel;
21 using System.Collections;
23 namespace ByteFX.Data.MySqlClient
25 /// <summary>
26 /// Represents a SQL statement to execute against a MySQL database. This class cannot be inherited.
27 /// </summary>
28 /// <include file='docs/MySqlCommand.xml' path='MyDocs/MyMembers[@name="Class"]/*'/>
29 #if WINDOWS
30 [System.Drawing.ToolboxBitmap( typeof(MySqlCommand), "MySqlClient.resources.command.bmp")]
31 #endif
32 [System.ComponentModel.DesignerCategory("Code")]
33 public sealed class MySqlCommand : Component, IDbCommand, ICloneable
35 MySqlConnection connection;
36 MySqlTransaction curTransaction;
37 string cmdText;
38 int updateCount;
39 UpdateRowSource updatedRowSource = UpdateRowSource.Both;
40 MySqlParameterCollection parameters = new MySqlParameterCollection();
41 private ArrayList arraySql = new ArrayList();
43 /// <summary>
44 /// Overloaded. Initializes a new instance of the MySqlCommand class.
45 /// </summary>
46 public MySqlCommand()
50 /// <summary>
51 /// Overloaded. Initializes a new instance of the MySqlCommand class.
52 /// </summary>
53 public MySqlCommand(string cmdText)
55 this.cmdText = cmdText;
58 /// <summary>
59 /// Overloaded. Initializes a new instance of the MySqlCommand class.
60 /// </summary>
61 public MySqlCommand(System.ComponentModel.IContainer container)
63 // Required for Windows.Forms Class Composition Designer support
64 container.Add(this);
67 /// <summary>
68 /// Overloaded. Initializes a new instance of the MySqlCommand class.
69 /// </summary>
70 public MySqlCommand(string cmdText, MySqlConnection connection)
72 this.cmdText = cmdText;
73 this.connection = connection;
76 /// <summary>
77 /// Disposes of this instance of MySqlCommand
78 /// </summary>
79 public new void Dispose()
81 base.Dispose();
84 /// <summary>
85 /// Overloaded. Initializes a new instance of the MySqlCommand class.
86 /// </summary>
87 public MySqlCommand(string cmdText, MySqlConnection connection, MySqlTransaction txn)
89 this.cmdText = cmdText;
90 this.connection = connection;
91 curTransaction = txn;
94 #region Properties
96 /// <summary>
97 /// Gets or sets the SQL statement to execute at the data source.
98 /// </summary>
99 [Category("Data")]
100 [Description("Command text to execute")]
101 #if WINDOWS
102 [Editor("ByteFX.Data.Common.Design.SqlCommandTextEditor,MySqlClient.Design", typeof(System.Drawing.Design.UITypeEditor))]
103 #endif
104 public string CommandText
106 get { return cmdText; }
107 set { cmdText = value; }
110 internal int UpdateCount
112 get { return updateCount; }
115 /// <summary>
116 /// Gets or sets the wait time before terminating the attempt to execute a command and generating an error.
117 /// </summary>
118 [Category("Misc")]
119 [Description("Time to wait for command to execute")]
120 public int CommandTimeout
122 // TODO: support this
123 get { return 0; }
124 set { if (value != 0) throw new NotSupportedException(); }
127 /// <summary>
128 /// Gets or sets a value indicating how the CommandText property is to be interpreted. Only
129 /// type Text is currently supported.
130 /// </summary>
131 [Category("Data")]
132 public CommandType CommandType
134 get { return CommandType.Text; }
135 set
137 if (value != CommandType.Text)
138 throw new NotSupportedException("This version of the MySql provider only supports Text command types");
142 /// <summary>
143 /// Gets or sets the MySqlConnection used by this instance of the MySqlCommand.
144 /// </summary>
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
151 * any time.
153 get
155 return connection;
160 * The connection is associated with the transaction
161 * so set the transaction object to return a null reference if the connection
162 * is reset.
164 if (connection != value)
165 this.Transaction = null;
167 connection = (MySqlConnection)value;
171 /// <summary>
172 /// Gets the MySqlParameterCollection.
173 /// </summary>
174 [Category("Data")]
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; }
187 /// <summary>
188 /// Gets or sets the MySqlTransaction within which the MySqlCommand executes.
189 /// </summary>
190 [Browsable(false)]
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.
197 get
199 return curTransaction;
201 set
203 curTransaction = (MySqlTransaction)value;
207 /// <summary>
208 /// Gets or sets how command results are applied to the DataRow when used by the Update method of the DbDataAdapter.
209 /// </summary>
210 [Category("Behavior")]
211 public UpdateRowSource UpdatedRowSource
213 get
215 return updatedRowSource;
217 set
219 updatedRowSource = value;
222 #endregion
224 #region Methods
225 /// <summary>
226 /// Attempts to cancel the execution of a MySqlCommand. This operation is not supported.
227 /// </summary>
228 /// <exception cref="NotSupportedException">This operation is not supported.</exception>
229 public void Cancel()
231 throw new NotSupportedException();
234 /// <summary>
235 /// Creates a new instance of a MySqlParameter object.
236 /// </summary>
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 + ";");
258 byte left_byte = 0;
259 bool escaped = false;
260 int parm_start=-1;
261 for (int x=0; x < bytes.Length; x++)
263 byte b = bytes[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;
273 else if (b == '\\')
275 escaped = !escaped;
278 // if we see the marker for a parameter, then save its position and
279 // look for the end
280 else if (b == '@' && left_byte == 0 && ! escaped && parm_start==-1)
281 parm_start = x;
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 );
294 else
296 // otherwise assume system param. just write it out
297 byte[] buf = connection.Encoding.GetBytes(parm_name);
298 ms.Write(buf, 0, buf.Length);
300 parm_start=-1;
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; }
312 if (goodcmd)
313 commands.Add( byteArray );
314 ms.SetLength(0);
316 else if (parm_start == -1)
317 ms.WriteByte(b);
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;
324 return commands;
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();
342 /// <summary>
343 /// Internal function to execute the next command in an array of commands
344 /// </summary>
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;
359 result.Clear();
360 continue;
363 // at this point, we know it is a zero field count
364 if (updateCount == -1) updateCount = 0;
365 updateCount += result.RowsAffected;
367 return null;
370 /// <summary>
371 /// Executes a SQL statement against the connection and returns the number of rows affected.
372 /// </summary>
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 );
388 updateCount = 0;
390 ExecuteBatch(false);
392 return (int)updateCount;
395 IDataReader IDbCommand.ExecuteReader ()
397 return ExecuteReader ();
400 IDataReader IDbCommand.ExecuteReader (CommandBehavior behavior)
402 return ExecuteReader (behavior);
405 /// <summary>
406 /// Overloaded. Sends the CommandText to the Connection and builds a MySqlDataReader.
407 /// </summary>
408 /// <returns></returns>
409 public MySqlDataReader ExecuteReader()
411 return ExecuteReader(CommandBehavior.Default);
415 /// <summary>
416 /// Overloaded. Sends the CommandText to the Connection and builds a MySqlDataReader.
417 /// </summary>
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 );
454 updateCount = -1;
455 MySqlDataReader reader = new MySqlDataReader(this, behavior);
457 // move to the first resultset
458 reader.NextResult();
459 connection.Reader = reader;
460 return reader;
463 /// <summary>
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.
466 /// </summary>
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 );
480 updateCount = -1;
481 MySqlDataReader reader = new MySqlDataReader(this, 0);
482 reader.NextResult();
483 object val = null;
484 if (reader.Read())
485 val = reader.GetValue(0);
486 reader.Close();
487 return val;
490 /// <summary>
491 /// Creates a prepared version of the command on an instance of MySQL Server. This
492 /// is currently not supported.
493 /// </summary>
494 public void Prepare()
497 #endregion
499 #region ICloneable
500 /// <summary>
501 /// Creates a clone of this MySqlCommand object. CommandText, Connection, and Transaction properties
502 /// are included as well as the entire parameter list.
503 /// </summary>
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());
512 return clone;
514 #endregion