3 // Permission is hereby granted, free of charge, to any person obtaining
4 // a copy of this software and associated documentation files (the
5 // "Software"), to deal in the Software without restriction, including
6 // without limitation the rights to use, copy, modify, merge, publish,
7 // distribute, sublicense, and/or sell copies of the Software, and to
8 // permit persons to whom the Software is furnished to do so, subject to
9 // the following conditions:
11 // The above copyright notice and this permission notice shall be
12 // included in all copies or substantial portions of the Software.
14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 using System.Runtime.InteropServices;
27 namespace IBM.Data.DB2
30 public class DB2Command : System.ComponentModel.Component, IDbCommand, ICloneable
32 #region Private data members
34 private WeakReference refDataReader;
\r
35 private string commandText;
\r
36 private CommandType commandType = CommandType.Text;
\r
37 private DB2Connection db2Conn;
\r
38 private DB2Transaction db2Trans;
\r
39 private int commandTimeout = 30;
\r
40 private bool prepared = false;
\r
41 private bool binded = false;
\r
42 private IntPtr hwndStmt = IntPtr.Zero; //Our statement handle
\r
43 private DB2ParameterCollection parameters = new DB2ParameterCollection();
\r
44 private bool disposed = false;
\r
45 private bool statementOpen;
\r
46 private CommandBehavior previousBehavior;
\r
47 private UpdateRowSource updatedRowSource = UpdateRowSource.Both;
\r
48 private IntPtr statementParametersMemory;
\r
49 private int statementParametersMemorySize;
57 hwndStmt = IntPtr.Zero;
59 public DB2Command(string commandStr):this()
61 commandText = commandStr;
64 public DB2Command(string commandStr, DB2Connection con) : this()
67 commandText = commandStr;
70 con.AddCommand(this);
\r
73 public DB2Command (string commandStr, DB2Connection con, DB2Transaction trans)
75 commandText = commandStr;
\r
80 con.AddCommand(this);
\r
86 public new void Dispose()
\r
89 GC.SuppressFinalize(this);
\r
92 protected override void Dispose(bool disposing)
\r
101 db2Conn.RemoveCommand(this);
\r
105 if(statementParametersMemory != IntPtr.Zero)
\r
107 Marshal.FreeHGlobal(statementParametersMemory);
\r
108 statementParametersMemory = IntPtr.Zero;
\r
111 base.Dispose(disposing);
\r
121 internal void DataReaderClosed()
\r
123 CloseStatementHandle(false);
\r
124 if((previousBehavior & CommandBehavior.CloseConnection) != 0)
\r
125 Connection.Close();
\r
126 refDataReader = null;
\r
129 private void CloseStatementHandle(bool dispose)
\r
131 if(hwndStmt != IntPtr.Zero)
\r
135 short sqlRet = DB2CLIWrapper.SQLFreeStmt(hwndStmt, DB2Constants.SQL_CLOSE);
\r
137 if((!prepared && statementOpen) ||
\r
140 short sqlRet = DB2CLIWrapper.SQLFreeHandle(DB2Constants.SQL_HANDLE_STMT, hwndStmt);
\r
142 hwndStmt = IntPtr.Zero;
\r
145 statementOpen = false;
\r
149 internal void ConnectionClosed()
\r
151 DB2DataReader reader = null;
\r
152 if((refDataReader != null) && refDataReader.IsAlive)
\r
154 reader = (DB2DataReader)refDataReader.Target;
\r
156 if((reader != null) && refDataReader.IsAlive)
\r
159 refDataReader = null;
\r
161 CloseStatementHandle(true);
\r
168 #region SelfDescribe property
170 /// Property dictates whether or not any paramter markers will get their describe info
171 /// from the database, or if the user will supply the information
173 bool selfDescribe = false;
174 public bool SelfDescribe
182 selfDescribe = value;
187 #region CommandText property
189 ///The query; If it gets set, reset the prepared property
191 public string CommandText
205 #region CommandTimeout property
207 /// The Timeout property states how long we wait for results to return
209 public int CommandTimeout
\r
213 return commandTimeout;
\r
217 commandTimeout = value;
\r
218 if(hwndStmt != IntPtr.Zero)
\r
219 SetStatementTimeout();
\r
224 #region CommandType property
226 public CommandType CommandType
239 #region Connection property
241 /// The connection we'll be executing on.
243 IDbConnection IDbCommand.Connection
251 db2Conn = (DB2Connection)value;
255 public DB2Connection Connection
\r
263 if(db2Conn != null)
\r
265 db2Conn.RemoveCommand(this);
\r
268 if(db2Conn != null)
\r
270 db2Conn.AddCommand(this);
\r
276 #region Parameters property
278 /// Parameter list, Not yet implemented
280 public DB2ParameterCollection Parameters
287 IDataParameterCollection IDbCommand.Parameters
296 #region Transaction property
298 /// The transaction this command is associated with
300 IDbTransaction IDbCommand.Transaction
308 db2Trans = (DB2Transaction)value;
312 public DB2Transaction Transaction
\r
325 #region UpdatedRowSource property
327 public UpdateRowSource UpdatedRowSource
\r
329 get { return updatedRowSource; }
\r
330 set { updatedRowSource = value; }
\r
334 #region Statement Handle
336 /// returns the DB2Client statement handle
338 public IntPtr statementHandle
347 #region AllocateStatement function
349 internal void AllocateStatement(string location)
\r
351 if (db2Conn.DBHandle.ToInt32() == 0) return;
\r
353 sqlRet = DB2CLIWrapper.SQLAllocHandle(DB2Constants.SQL_HANDLE_STMT, db2Conn.DBHandle, out hwndStmt);
\r
354 if ((sqlRet != DB2Constants.SQL_SUCCESS) && (sqlRet != DB2Constants.SQL_SUCCESS_WITH_INFO))
\r
355 throw new DB2Exception(DB2Constants.SQL_HANDLE_DBC, db2Conn.DBHandle, location +": Unable to allocate statement handle.");
\r
357 parameters.HwndStmt = hwndStmt;
\r
359 SetStatementTimeout();
\r
362 private void SetStatementTimeout()
\r
364 short sqlRet = DB2CLIWrapper.SQLSetStmtAttr(hwndStmt, DB2Constants.SQL_ATTR_QUERY_TIMEOUT, new IntPtr(commandTimeout), 0);
\r
365 DB2ClientUtils.DB2CheckReturn(sqlRet, DB2Constants.SQL_HANDLE_STMT, hwndStmt, "Set statement timeout.", db2Conn);
\r
371 /// Attempt to cancel an executing command
373 public void Cancel()
\r
375 if(hwndStmt == IntPtr.Zero)
\r
377 throw new InvalidOperationException("Nothing to Cancel.");
\r
379 DB2CLIWrapper.SQLCancel(hwndStmt);
\r
383 #region CreateParameter
385 ///Returns a parameter
387 public IDbDataParameter CreateParameter()
389 return new DB2Parameter();
393 #region ExecuteNonQuery
395 public int ExecuteNonQuery()
\r
397 ExecuteNonQueryInternal(CommandBehavior.Default);
\r
401 //How many rows affected. numRows will be -1 if we aren't dealing with an Insert, Delete or Update, or if the statement did not execute successfully
\r
402 short sqlRet = DB2CLIWrapper.SQLRowCount(hwndStmt, out numRows);
\r
403 DB2ClientUtils.DB2CheckReturn(sqlRet, DB2Constants.SQL_HANDLE_STMT, hwndStmt, "SQLExecDirect error.", db2Conn);
\r
407 sqlRet = DB2CLIWrapper.SQLMoreResults(this.hwndStmt);
\r
408 DB2ClientUtils.DB2CheckReturn(sqlRet, DB2Constants.SQL_HANDLE_STMT, hwndStmt, "DB2ClientDataReader - SQLMoreResults", db2Conn);
\r
409 } while(sqlRet != DB2Constants.SQL_NO_DATA_FOUND);
\r
411 CloseStatementHandle(false);
\r
416 public void ExecuteNonQueryInternal(CommandBehavior behavior)
\r
420 if(prepared && binded)
\r
422 sqlRet = DB2CLIWrapper.SQLExecute(hwndStmt);
\r
423 DB2ClientUtils.DB2CheckReturn(sqlRet, DB2Constants.SQL_HANDLE_STMT, hwndStmt, "SQLExecute error.", db2Conn);
\r
427 if((db2Conn == null) || (db2Conn.State != ConnectionState.Open))
\r
428 throw new InvalidOperationException("Prepare needs an open connection");
\r
429 if((refDataReader != null) &&
\r
430 (refDataReader.IsAlive))
\r
431 throw new InvalidOperationException("There is already an open DataReader associated with this Connection which must be closed first.");
\r
432 DB2Transaction connectionTransaction = null;
\r
433 if(db2Conn.WeakRefTransaction != null)
\r
434 connectionTransaction = (DB2Transaction)db2Conn.WeakRefTransaction.Target;
\r
435 if(!Object.ReferenceEquals(connectionTransaction, Transaction))
\r
437 if(Transaction == null)
\r
438 throw new InvalidOperationException("A transaction was started in the connection, but the command doesn't specify a transaction");
\r
439 throw new InvalidOperationException("The transaction specified at the connection doesn't belong to the connection");
\r
442 if (hwndStmt == IntPtr.Zero)
\r
444 AllocateStatement("InternalExecuteNonQuery");
\r
445 previousBehavior = 0;
\r
447 if(previousBehavior != behavior)
\r
449 if(((previousBehavior ^ behavior) & CommandBehavior.SchemaOnly) != 0)
\r
451 sqlRet = DB2CLIWrapper.SQLSetStmtAttr(hwndStmt, DB2Constants.SQL_ATTR_DEFERRED_PREPARE,
\r
452 new IntPtr((behavior & CommandBehavior.SchemaOnly) != 0 ? 0 : 1), 0);
\r
453 // TODO: don't check. what if it is not supported???
\r
454 DB2ClientUtils.DB2CheckReturn(sqlRet, DB2Constants.SQL_HANDLE_STMT, hwndStmt, "Defered prepare.", db2Conn);
\r
456 previousBehavior = (previousBehavior & ~CommandBehavior.SchemaOnly) | (behavior & CommandBehavior.SchemaOnly);
\r
458 if(((previousBehavior ^ behavior) & CommandBehavior.SingleRow) != 0)
\r
460 sqlRet = DB2CLIWrapper.SQLSetStmtAttr(hwndStmt, DB2Constants.SQL_ATTR_MAX_ROWS,
\r
461 new IntPtr((behavior & CommandBehavior.SingleRow) == 0 ? 0 : 1), 0);
\r
462 // TODO: don't check. what if it is not supported???
\r
463 DB2ClientUtils.DB2CheckReturn(sqlRet, DB2Constants.SQL_HANDLE_STMT, hwndStmt, "Set max rows", db2Conn);
\r
465 previousBehavior = (previousBehavior & ~CommandBehavior.SingleRow) | (behavior & CommandBehavior.SingleRow);
\r
467 previousBehavior = behavior;
\r
469 if((Transaction == null) &&
\r
470 !db2Conn.openConnection.autoCommit)
\r
472 sqlRet = DB2CLIWrapper.SQLSetConnectAttr(db2Conn.DBHandle, DB2Constants.SQL_ATTR_AUTOCOMMIT, new IntPtr(DB2Constants.SQL_AUTOCOMMIT_ON), 0);
\r
473 DB2ClientUtils.DB2CheckReturn(sqlRet, DB2Constants.SQL_HANDLE_DBC, db2Conn.DBHandle, "Error setting AUTOCOMMIT ON in transaction CTOR.", db2Conn);
\r
474 db2Conn.openConnection.autoCommit = true;
\r
476 sqlRet = DB2CLIWrapper.SQLSetConnectAttr(db2Conn.DBHandle, DB2Constants.SQL_ATTR_TXN_ISOLATION, new IntPtr(DB2Constants.SQL_TXN_READ_COMMITTED), 0);
\r
477 DB2ClientUtils.DB2CheckReturn(sqlRet, DB2Constants.SQL_HANDLE_DBC, db2Conn.DBHandle, "Error setting isolation level.", db2Conn);
\r
481 if ((commandText == null) ||(commandText.Length == 0))
\r
482 throw new InvalidOperationException("Command string is empty");
\r
484 if(CommandType.StoredProcedure == commandType && !commandText.StartsWith("CALL "))
\r
485 commandText = "CALL " + commandText + " ()";
\r
487 if((behavior & CommandBehavior.SchemaOnly) != 0)
\r
496 if(statementParametersMemory != IntPtr.Zero)
\r
498 Marshal.FreeHGlobal(statementParametersMemory);
\r
499 statementParametersMemory = IntPtr.Zero;
\r
506 sqlRet = DB2CLIWrapper.SQLExecute(hwndStmt);
\r
507 DB2ClientUtils.DB2CheckReturn(sqlRet, DB2Constants.SQL_HANDLE_STMT, hwndStmt, "SQLExecute error.", db2Conn);
\r
511 sqlRet = DB2CLIWrapper.SQLExecDirect(hwndStmt, commandText, commandText.Length);
\r
512 DB2ClientUtils.DB2CheckReturn(sqlRet, DB2Constants.SQL_HANDLE_STMT, hwndStmt, "SQLExecDirect error.", db2Conn);
\r
514 statementOpen = true;
\r
516 parameters.GetOutValues();
\r
518 if(statementParametersMemory != IntPtr.Zero)
\r
520 Marshal.FreeHGlobal(statementParametersMemory);
\r
521 statementParametersMemory = IntPtr.Zero;
\r
527 #region ExecuteReader calls
531 IDataReader IDbCommand.ExecuteReader()
\r
533 return ExecuteReader(CommandBehavior.Default);
\r
536 IDataReader IDbCommand.ExecuteReader(CommandBehavior behavior)
\r
538 return ExecuteReader(behavior);
\r
541 public DB2DataReader ExecuteReader()
\r
543 return ExecuteReader(CommandBehavior.Default);
\r
546 public DB2DataReader ExecuteReader(CommandBehavior behavior)
\r
548 if((db2Conn == null) || (db2Conn.State != ConnectionState.Open))
\r
549 throw new InvalidOperationException("Prepare needs an open connection");
\r
551 DB2DataReader reader;
\r
553 ExecuteNonQueryInternal(behavior);
\r
554 reader = new DB2DataReader(db2Conn, this, behavior);
\r
556 refDataReader = new WeakReference(reader);
\r
562 #region ExecuteScalar
566 public object ExecuteScalar()
\r
568 if((db2Conn == null) || (db2Conn.State != ConnectionState.Open))
\r
569 throw new InvalidOperationException("Prepare needs an open connection");
\r
571 using(DB2DataReader reader = ExecuteReader(CommandBehavior.SingleResult | CommandBehavior.SingleRow))
\r
573 if(reader.Read() && (reader.FieldCount > 0))
\r
584 public void Prepare ()
\r
586 if((db2Conn == null) || (db2Conn.State != ConnectionState.Open))
\r
587 throw new InvalidOperationException("Prepare needs an open connection");
\r
589 CloseStatementHandle(false);
\r
590 if (hwndStmt == IntPtr.Zero)
\r
592 AllocateStatement("InternalExecuteNonQuery");
\r
597 IntPtr numParams = IntPtr.Zero;
\r
598 sqlRet = DB2CLIWrapper.SQLPrepare(hwndStmt, commandText, commandText.Length);
\r
599 DB2ClientUtils.DB2CheckReturn(sqlRet, DB2Constants.SQL_HANDLE_STMT, hwndStmt, "SQLPrepare error.", db2Conn);
\r
602 statementOpen = true;
\r
607 private string AddCallParam( string _cmString)
\r
609 if(_cmString.IndexOf("()") != -1)
\r
611 return _cmString.Replace("()","(?)");
\r
613 return _cmString.Replace(")", ",?)");
616 private void BindParams()
\r
618 if(parameters.Count > 0)
\r
620 statementParametersMemorySize = 0;
\r
623 for(int i = 0; i < parameters.Count; i++)
\r
625 if(commandType == CommandType.StoredProcedure)
\r
627 commandText = AddCallParam(commandText);
\r
629 DB2Parameter param = parameters[i];
\r
630 param.CalculateRequiredmemory();
\r
631 statementParametersMemorySize += param.requiredMemory + 8;
\r
632 param.internalBuffer = Marshal.AllocHGlobal(param.requiredMemory);
\r
633 offset += param.requiredMemory;
\r
634 param.internalLengthBuffer = Marshal.AllocHGlobal(4);
\r
635 Marshal.WriteInt32(param.internalLengthBuffer, param.requiredMemory);
\r
636 sqlRet = param.Bind(this.hwndStmt, (short)(i + 1));
\r
637 DB2ClientUtils.DB2CheckReturn(sqlRet, DB2Constants.SQL_HANDLE_STMT, hwndStmt, "Error binding parameter in DB2Command: ", db2Conn);
\r
643 #region ICloneable Members
\r
645 object ICloneable.Clone()
\r
647 DB2Command clone = new DB2Command();
\r
649 clone.Connection = Connection;
\r
650 clone.commandText = commandText;
\r
651 clone.commandType = commandType;
\r
652 clone.Transaction = db2Trans;
\r
653 clone.commandTimeout = commandTimeout;
\r
654 clone.updatedRowSource = updatedRowSource;
\r
655 clone.parameters = new DB2ParameterCollection();
\r
656 for(int i = 0; i < parameters.Count; i++)
\r
657 clone.Parameters.Add(((ICloneable)parameters[i]).Clone());
\r