**** Merged from MCS ****
[mono-project.git] / mcs / class / Mono.Data.MySql / Mono.Data.MySql / MySqlCommand.cs
blob2bcb766198f4312a88b8431d56c045fffc6cb2b7
1 //
2 // Mono.Data.MySql.MySqlCommand.cs
3 //
4 // Author:
5 // Daniel Morgan (danmorg@sc.rr.com)
6 //
7 // (C) Daniel Morgan, 2002
8 //
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
18 //
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
21 //
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31 using System;
32 using System.Collections;
33 using System.ComponentModel;
34 using System.Data;
35 using System.Data.Common;
36 using System.Runtime.InteropServices;
37 using System.Text;
38 using System.Xml;
40 namespace Mono.Data.MySql {
41 public sealed class MySqlCommand : Component, IDbCommand, ICloneable {
43 #region Fields
45 private string sql = "";
46 private int timeout = 30;
47 // default is 30 seconds
48 // for command execution
50 private MySqlConnection conn = null;
51 private MySqlTransaction trans = null;
52 private CommandType cmdType = CommandType.Text;
53 private bool designTime = false;
54 private MySqlParameterCollection parmCollection = new
55 MySqlParameterCollection();
57 // MySqlDataReader state data for ExecuteReader()
58 //private MySqlDataReader dataReader = null;
59 private string[] commands = null;
60 private int currentQuery = -1;
61 private CommandBehavior cmdBehavior = CommandBehavior.Default;
63 private bool disposed = false;
65 private const char bindChar = ':';
67 #endregion // Fields
69 #region Constructors
71 public MySqlCommand() {
72 sql = "";
75 public MySqlCommand (string cmdText) {
76 sql = cmdText;
79 public MySqlCommand (string cmdText, MySqlConnection connection) {
80 sql = cmdText;
81 conn = connection;
84 public MySqlCommand (string cmdText, MySqlConnection connection,
85 MySqlTransaction transaction) {
86 sql = cmdText;
87 conn = connection;
88 trans = transaction;
91 #endregion // Constructors
93 #region Methods
95 [MonoTODO]
96 public void Cancel () {
97 // FIXME: use non-blocking Exec for this
98 throw new NotImplementedException ();
101 // FIXME: is this the correct way to return a stronger type?
102 [MonoTODO]
103 IDbDataParameter IDbCommand.CreateParameter () {
104 return CreateParameter ();
107 [MonoTODO]
108 public MySqlParameter CreateParameter () {
109 return new MySqlParameter ();
112 public int ExecuteNonQuery () {
113 int rowsAffected = -1;
115 IntPtr res = ExecuteSQL (sql);
117 if(res.Equals(IntPtr.Zero)) {
118 // no result set returned, get records affected
119 rowsAffected = (int) MySql.AffectedRows(conn.NativeMySqlInitStruct);
122 MySql.FreeResult(res);
123 res = IntPtr.Zero;
125 // >= 0 of the number of rows affected by
126 // INSERT, UPDATE, DELETE
127 // otherwise, -1
128 return rowsAffected;
131 [MonoTODO]
132 IDataReader IDbCommand.ExecuteReader () {
133 return ExecuteReader ();
136 [MonoTODO]
137 public MySqlDataReader ExecuteReader () {
138 return ExecuteReader(CommandBehavior.Default);
141 [MonoTODO]
142 IDataReader IDbCommand.ExecuteReader (
143 CommandBehavior behavior) {
145 return ExecuteReader (behavior);
148 [MonoTODO]
149 public MySqlDataReader ExecuteReader (CommandBehavior behavior) {
151 MySqlDataReader reader = null;
153 currentQuery = -1;
155 commands = sql.Split(new Char[] {';'});
156 reader = new MySqlDataReader(this, behavior);
157 reader.NextResult();
159 return reader;
162 // called by an MySqlDataReader's NextResult()
163 internal IntPtr NextResult (out bool result) {
165 IntPtr mysqlResult = IntPtr.Zero;
166 result = false;
168 currentQuery++;
169 if(currentQuery < commands.Length) {
170 string query = "";
172 // don't execute empty queries
173 while((query = commands[currentQuery]).Equals("")) {
174 currentQuery++;
175 if(currentQuery >= commands.Length)
176 return IntPtr.Zero;
178 mysqlResult = ExecuteSQL (query);
179 result = true; // has result
182 return mysqlResult;
185 public object ExecuteScalar () {
187 object obj = null;
189 IntPtr res = ExecuteSQL (sql);
191 int numRows = MySql.NumRows(res);
192 int numFields = MySql.NumFields(res);
194 MySqlMarshalledField fd;
195 fd = (MySqlMarshalledField) Marshal.PtrToStructure(MySql.FetchField(res),
196 typeof(MySqlMarshalledField));
197 string fieldName = fd.Name;
198 int fieldType = fd.FieldType;
199 MySqlEnumFieldTypes mysqlFieldType = (MySqlEnumFieldTypes) fieldType;
200 DbType fieldDbType = MySqlHelper.MySqlTypeToDbType(mysqlFieldType);
202 IntPtr row;
203 row = MySql.FetchRow(res);
204 if(row == IntPtr.Zero) {
205 // EOF
206 obj = null;
208 else {
209 // only get first column/first row
210 string objValue = GetColumnData(row, 0);
211 obj = MySqlHelper.ConvertDbTypeToSystem (mysqlFieldType, fieldDbType, objValue);
212 row = IntPtr.Zero;
214 MySql.FreeResult(res);
215 res = IntPtr.Zero;
217 return obj;
220 // command: string in - SQL command
221 // IntPtr (MySqlResult) return - the result
222 // Use of this function needs to check to see if
223 // if the return equal to IntPtr.Zero
224 // Example: IntPtr res = ExecuteSQL ("SELECT * FROM DB");
225 // if (res == IntPtr.Zero) { // do something }
227 internal IntPtr ExecuteSQL (string command) {
228 string msg = "";
230 if (conn == null)
231 throw new InvalidOperationException(
232 "Connection is null");
234 if (conn.State != ConnectionState.Open)
235 throw new InvalidOperationException(
236 "ConnectionState is not Open");
238 if (sql.Equals (String.Empty))
239 throw new InvalidOperationException(
240 "CommandText is Empty");
242 string query = TweakQuery(sql);
244 int rcq = MySql.Query(conn.NativeMySqlInitStruct, query);
245 if (rcq != 0) {
246 msg =
247 "MySql Error: " +
248 "Could not execute command [" +
249 sql +
250 "] on server because: " +
251 MySql.Error(conn.NativeMySqlInitStruct);
252 throw new MySqlException(msg);
254 IntPtr result = MySql.StoreResult(conn.NativeMySqlInitStruct);
255 return result;
258 string TweakQuery(string query) {
259 string statement = "";
261 switch(cmdType) {
262 case CommandType.Text:
263 statement = ReplaceParameterPlaceholders (query);
264 break;
265 case CommandType.StoredProcedure:
266 string sParmList = GetStoredProcParmList ();
267 statement = "SELECT " + query + "(" + sParmList + ")";
268 break;
269 case CommandType.TableDirect:
270 statement =
271 "SELECT * FROM " + query;
272 break;
274 return statement;
277 string GetStoredProcParmList () {
278 StringBuilder s = new StringBuilder();
280 int addedCount = 0;
281 for(int p = 0; p < parmCollection.Count; p++) {
282 MySqlParameter prm = parmCollection[p];
283 if(prm.Direction == ParameterDirection.Input) {
284 string strObj = MySqlHelper.
285 ObjectToString(prm.DbType,
286 prm.Value);
287 if(addedCount > 0)
288 s.Append(",");
289 s.Append(strObj);
290 addedCount++;
293 return s.ToString();
296 // TODO: this only supports input parameters,
297 // need support for output, input/output,
298 // and return parameters
299 // As far as I know, MySQL does not support
300 // parameters so the parameters support in this
301 // provider is just a search and replace.
302 string ReplaceParameterPlaceholders (string query) {
304 string resultSql = "";
306 StringBuilder result = new StringBuilder();
307 char[] chars = sql.ToCharArray();
308 bool bStringConstFound = false;
310 for(int i = 0; i < chars.Length; i++) {
311 if(chars[i] == '\'') {
312 if(bStringConstFound == true)
313 bStringConstFound = false;
314 else
315 bStringConstFound = true;
317 result.Append(chars[i]);
319 else if(chars[i] == bindChar &&
320 bStringConstFound == false) {
322 StringBuilder parm = new StringBuilder();
323 i++;
324 while(i <= chars.Length) {
325 char ch;
326 if(i == chars.Length)
327 ch = ' '; // a space
328 else
329 ch = chars[i];
331 if(Char.IsLetterOrDigit(ch)) {
332 parm.Append(ch);
334 else {
335 string p = parm.ToString();
336 bool found = BindReplace(result, p);
338 if(found == true)
339 break;
340 else {
341 // *** Error Handling
342 Console.WriteLine("Error: parameter not found: " + p);
343 return "";
346 i++;
348 i--;
350 else
351 result.Append(chars[i]);
354 resultSql = result.ToString();
355 return resultSql;
358 bool BindReplace (StringBuilder result, string p) {
359 // bind variable
360 bool found = false;
362 if(parmCollection.Contains(p) == true) {
363 // parameter found
364 MySqlParameter prm = parmCollection[p];
366 // convert object to string and place
367 // into SQL
368 if(prm.Direction == ParameterDirection.Input) {
369 string strObj = MySqlHelper.
370 ObjectToString(prm.DbType,
371 prm.Value);
372 result.Append(strObj);
374 else
375 result.Append(bindChar + p);
377 found = true;
379 return found;
382 [MonoTODO]
383 public XmlReader ExecuteXmlReader () {
384 //MySqlDataReader dataReader = ExecuteReader ();
385 //MySqlXmlTextReader textReader = new MySqlXmlTextReader (dataReader);
386 //XmlReader xmlReader = new XmlTextReader (textReader);
387 //return xmlReader;
388 throw new NotImplementedException ();
391 [MonoTODO]
392 public void Prepare () {
393 // FIXME: parameters have to be implemented for this
394 throw new NotImplementedException ();
397 object ICloneable.Clone() {
398 throw new NotImplementedException ();
401 // Used to marshal a field value from the database result set.
402 // The indexed column data on the current result set row.
403 // res = the result set from a MySql.Query().
404 // index = the column index.
405 internal string GetColumnData(IntPtr res, int index) {
406 IntPtr str = Marshal.ReadIntPtr(res, index*IntPtr.Size);
407 if (str == IntPtr.Zero)
408 return "";
409 string s = Marshal.PtrToStringAnsi(str);
410 return s;
413 #endregion // Methods
415 #region Properties
417 public string CommandText {
418 get {
419 return sql;
422 set {
423 sql = value;
427 public int CommandTimeout {
428 get {
429 return timeout;
432 set {
433 // FIXME: if value < 0, throw
434 // ArgumentException
435 // if (value < 0)
436 // throw ArgumentException;
437 timeout = value;
441 public CommandType CommandType {
442 get {
443 return cmdType;
446 set {
447 cmdType = value;
451 IDbConnection IDbCommand.Connection {
452 get {
453 return Connection;
456 set {
457 // FIXME: throw an InvalidOperationException
458 // if the change was during a
459 // transaction in progress
461 // csc
462 Connection = (MySqlConnection) value;
463 // mcs
464 // Connection = value;
466 // FIXME: set Transaction property to null
470 public MySqlConnection Connection {
471 get {
472 // conn defaults to null
473 return conn;
476 set {
477 // FIXME: throw an InvalidOperationException
478 // if the change was during
479 // a transaction in progress
480 conn = value;
481 // FIXME: set Transaction property to null
485 public bool DesignTimeVisible {
486 get {
487 return designTime;
490 set{
491 designTime = value;
495 IDataParameterCollection IDbCommand.Parameters {
496 get {
497 return Parameters;
501 public MySqlParameterCollection Parameters {
502 get {
503 return parmCollection;
507 IDbTransaction IDbCommand.Transaction {
508 get {
509 return Transaction;
512 set {
513 // FIXME: error handling - do not allow
514 // setting of transaction if transaction
515 // has already begun
516 Transaction = (MySqlTransaction) value;
521 public MySqlTransaction Transaction {
522 get {
523 return trans;
526 set {
527 // FIXME: error handling
528 trans = value;
532 [MonoTODO]
533 public UpdateRowSource UpdatedRowSource {
534 // FIXME: do this once DbDataAdaptor
535 // and DataRow are done
536 get {
537 throw new NotImplementedException ();
539 set {
540 throw new NotImplementedException ();
544 #endregion // Properties
546 #region Inner Classes
548 #endregion // Inner Classes
550 #region Destructors
552 protected override void Dispose(bool disposing) {
553 if(!this.disposed)
554 try {
555 if(disposing) {
556 // release any managed resources
558 // release any unmanaged resources
559 // close any handles
561 this.disposed = true;
563 finally {
564 base.Dispose(disposing);
568 // aka Finalize()
569 ~MySqlCommand () {
570 Dispose (false);
573 #endregion //Destructors