Updates referencesource to .NET 4.7
[mono.git] / mcs / class / referencesource / System.Data / System / Data / Odbc / OdbcCommand.cs
1 //------------------------------------------------------------------------------
2 // <copyright file="OdbcCommand.cs" company="Microsoft">
3 //      Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 // <owner current="true" primary="true">Microsoft</owner>
6 // <owner current="true" primary="false">Microsoft</owner>
7 //------------------------------------------------------------------------------
8
9 using System;
10 using System.ComponentModel;            //Component
11 using System.Data;
12 using System.Data.Common;
13 using System.Data.ProviderBase;
14 using System.Diagnostics;
15 using System.Runtime.CompilerServices;
16 using System.Runtime.InteropServices;
17 using System.Threading;
18
19 // todo:
20 // There may be two ways to improve performance:
21 // 1. pool statements on the connection object
22 // 2. Do not create a datareader object for non-datareader returning command execution.
23 //
24 // We do not want to do the effort unless we have to squeze performance.
25
26
27
28 namespace System.Data.Odbc {
29
30     [
31     DefaultEvent("RecordsAffected"),
32     ToolboxItem(true),
33     Designer("Microsoft.VSDesigner.Data.VS.OdbcCommandDesigner, " + AssemblyRef.MicrosoftVSDesigner)
34     ]
35     public sealed class OdbcCommand : DbCommand, ICloneable {
36         private static int          _objectTypeCount; // Bid counter
37         internal readonly int       ObjectID = System.Threading.Interlocked.Increment(ref _objectTypeCount);
38
39         private string              _commandText;
40         private CommandType         _commandType;
41         private int                 _commandTimeout = ADP.DefaultCommandTimeout;
42         private UpdateRowSource     _updatedRowSource = UpdateRowSource.Both;
43         private bool                _designTimeInvisible;
44         private bool                _isPrepared;                        // true if the command is prepared
45
46         private OdbcConnection      _connection;
47         private OdbcTransaction     _transaction;
48
49         private WeakReference       weakDataReaderReference;
50
51         private CMDWrapper          _cmdWrapper;
52
53         private OdbcParameterCollection    _parameterCollection;   // Parameter collection
54
55         private ConnectionState     cmdState;
56
57         public OdbcCommand() : base() {
58             GC.SuppressFinalize(this);
59         }
60
61         public OdbcCommand(string cmdText) : this() {
62             // note: arguments are assigned to properties so we do not have to trace them.
63             // We still need to include them into the argument list of the definition!
64             CommandText = cmdText;
65         }
66
67         public OdbcCommand(string cmdText, OdbcConnection connection) : this() {
68             CommandText = cmdText;
69             Connection  = connection;
70         }
71
72         public OdbcCommand(string cmdText, OdbcConnection connection, OdbcTransaction transaction) : this() {
73             CommandText = cmdText;
74             Connection = connection;
75             Transaction = transaction;
76         }
77
78         private void DisposeDeadDataReader() {
79             if (ConnectionState.Fetching == cmdState) {
80                 if (null != this.weakDataReaderReference && !this.weakDataReaderReference.IsAlive) {
81                     if (_cmdWrapper != null) {
82                         _cmdWrapper.FreeKeyInfoStatementHandle(ODBC32.STMT.CLOSE);
83                         _cmdWrapper.FreeStatementHandle(ODBC32.STMT.CLOSE);
84                     }
85                     CloseFromDataReader();
86                 }
87             }
88         }
89
90         private void DisposeDataReader() {
91             if (null != this.weakDataReaderReference) {
92                 IDisposable reader = (IDisposable) this.weakDataReaderReference.Target;
93                 if ((null != reader) && this.weakDataReaderReference.IsAlive) {
94                     ((IDisposable) reader).Dispose();
95                 }
96                 CloseFromDataReader();
97             }
98         }
99
100         internal void DisconnectFromDataReaderAndConnection () {
101             // get a reference to the datareader if it is alive
102             OdbcDataReader liveReader = null;
103             if (this.weakDataReaderReference != null){
104                 OdbcDataReader reader;
105                 reader = (OdbcDataReader)this.weakDataReaderReference.Target;
106                 if (this.weakDataReaderReference.IsAlive) {
107                     liveReader = reader;
108                 }
109             }
110
111             // remove reference to this from the live datareader
112             if (liveReader != null) {
113                 liveReader.Command = null;
114             }
115
116             _transaction = null;
117
118             if (null != _connection) {
119                 _connection.RemoveWeakReference(this);
120                 _connection = null;
121             }
122
123             // if the reader is dead we have to dismiss the statement
124             if (liveReader == null){
125                 CloseCommandWrapper();
126             }
127             // else DataReader now has exclusive ownership
128             _cmdWrapper = null;
129         }
130
131         override protected void Dispose(bool disposing) { // MDAC 65459
132             if (disposing) {
133                 // release mananged objects
134                 // in V1.0, V1.1 the Connection,Parameters,CommandText,Transaction where reset
135                 this.DisconnectFromDataReaderAndConnection ();
136                 _parameterCollection = null;
137                 CommandText = null;
138             }
139             _cmdWrapper = null;                         // let go of the CommandWrapper
140             _isPrepared = false;
141
142             base.Dispose(disposing);    // notify base classes
143         }
144
145         internal bool Canceling {
146             get {
147                 return _cmdWrapper.Canceling;
148             }
149         }
150
151         [
152         ResCategoryAttribute(Res.DataCategory_Data),
153         DefaultValue(""),
154         RefreshProperties(RefreshProperties.All), // MDAC 67707
155         ResDescriptionAttribute(Res.DbCommand_CommandText),
156         Editor("Microsoft.VSDesigner.Data.Odbc.Design.OdbcCommandTextEditor, " + AssemblyRef.MicrosoftVSDesigner, "System.Drawing.Design.UITypeEditor, " + AssemblyRef.SystemDrawing)
157         ]
158         override public string CommandText {
159             get {
160                 string value = _commandText;
161                 return ((null != value) ? value : ADP.StrEmpty);
162             }
163             set {
164                 if (Bid.TraceOn) {
165                     Bid.Trace("<odbc.OdbcCommand.set_CommandText|API> %d#, '", ObjectID);
166                     Bid.PutStr(value); // Use PutStr to write out entire string
167                     Bid.Trace("'\n");
168                 }
169                 if (0 != ADP.SrcCompare(_commandText, value)) {
170                     PropertyChanging();
171                     _commandText = value;
172                 }
173             }
174         }
175
176         [
177         ResCategoryAttribute(Res.DataCategory_Data),
178         ResDescriptionAttribute(Res.DbCommand_CommandTimeout),
179         ]
180         override public int CommandTimeout { // V1.2.3300, XXXCommand V1.0.5000
181             get {
182                 return _commandTimeout;
183             }
184             set {
185                 Bid.Trace("<odbc.OdbcCommand.set_CommandTimeout|API> %d#, %d\n", ObjectID, value);
186                 if (value < 0) {
187                     throw ADP.InvalidCommandTimeout(value);
188                 }
189                 if (value != _commandTimeout) {
190                     PropertyChanging();
191                     _commandTimeout = value;
192                 }
193             }
194         }
195
196         public void ResetCommandTimeout() { // V1.2.3300
197             if (ADP.DefaultCommandTimeout != _commandTimeout) {
198                 PropertyChanging();
199                 _commandTimeout = ADP.DefaultCommandTimeout;
200             }
201         }
202
203         private bool ShouldSerializeCommandTimeout() { // V1.2.3300
204             return (ADP.DefaultCommandTimeout != _commandTimeout);
205         }
206
207         [
208         DefaultValue(System.Data.CommandType.Text),
209         RefreshProperties(RefreshProperties.All),
210         ResCategoryAttribute(Res.DataCategory_Data),
211         ResDescriptionAttribute(Res.DbCommand_CommandType),
212         ]
213         override public CommandType CommandType {
214             get {
215                 CommandType cmdType = _commandType;
216                 return ((0 != cmdType) ? cmdType : CommandType.Text);
217             }
218             set  {
219                 switch(value) { // @perfnote: Enum.IsDefined
220                 case CommandType.Text:
221                 case CommandType.StoredProcedure:
222                     PropertyChanging();
223                     _commandType = value;
224                     break;
225                 case CommandType.TableDirect:
226                     throw ODBC.NotSupportedCommandType(value);
227                 default:
228                     throw ADP.InvalidCommandType(value);
229                 }
230             }
231         }
232
233         // This will establish a relationship between the command and the connection
234         [
235         DefaultValue(null),
236         ResCategoryAttribute(Res.DataCategory_Behavior),
237         ResDescriptionAttribute(Res.DbCommand_Connection),
238         Editor("Microsoft.VSDesigner.Data.Design.DbConnectionEditor, " + AssemblyRef.MicrosoftVSDesigner, "System.Drawing.Design.UITypeEditor, " + AssemblyRef.SystemDrawing),
239         ]
240         new public OdbcConnection Connection {
241             get {
242                 return _connection;
243             }
244             set {
245                 if (value != _connection) {
246                     PropertyChanging();
247                     this.DisconnectFromDataReaderAndConnection();
248                     Debug.Assert(null == _cmdWrapper, "has CMDWrapper when setting connection");
249                     _connection = value;
250                     //OnSchemaChanged();
251                 }
252             }
253         }
254
255         override protected DbConnection DbConnection { // V1.2.3300
256             get {
257                 return Connection;
258             }
259             set {
260                 Connection = (OdbcConnection)value;
261             }
262         }
263
264         override protected DbParameterCollection DbParameterCollection { // V1.2.3300
265             get {
266                 return Parameters;
267             }
268         }
269
270         override protected DbTransaction DbTransaction { // V1.2.3300
271             get {
272                 return Transaction;
273             }
274             set {
275                 Transaction = (OdbcTransaction)value;
276             }
277         }
278
279         // @devnote: By default, the cmd object is visible on the design surface (i.e. VS7 Server Tray)
280         // to limit the number of components that clutter the design surface,
281         // when the DataAdapter design wizard generates the insert/update/delete commands it will
282         // set the DesignTimeVisible property to false so that cmds won't appear as individual objects
283         [
284         DefaultValue(true),
285         DesignOnly(true),
286         Browsable(false),
287         EditorBrowsableAttribute(EditorBrowsableState.Never),
288         ]
289         public override bool DesignTimeVisible { // V1.2.3300, XXXCommand V1.0.5000
290             get {
291                 return !_designTimeInvisible;
292             }
293             set {
294                 _designTimeInvisible = !value;
295                 TypeDescriptor.Refresh(this); // VS7 208845
296             }
297         }
298
299         internal bool HasParameters {
300             get {
301                 return (null != _parameterCollection);
302             }
303         }
304
305         [
306         DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
307         ResCategoryAttribute(Res.DataCategory_Data),
308         ResDescriptionAttribute(Res.DbCommand_Parameters),
309         ]
310         new public OdbcParameterCollection Parameters {
311             get {
312                 if (null == _parameterCollection) {
313                     _parameterCollection = new OdbcParameterCollection();
314                 }
315                 return _parameterCollection;
316             }
317         }
318
319         [
320         Browsable(false),
321         DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden),
322         ResDescriptionAttribute(Res.DbCommand_Transaction),
323         ]
324         new public OdbcTransaction Transaction {
325             get {
326                 if ((null != _transaction) && (null == _transaction.Connection)) {
327                     _transaction = null;       // Dawn of the Dead
328                 }
329                 return _transaction;
330             }
331             set {
332                 if (_transaction != value) {
333                     PropertyChanging(); // fire event before value is validated
334                     _transaction = value;
335                 }
336             }
337         }
338
339         [
340         DefaultValue(System.Data.UpdateRowSource.Both),
341         ResCategoryAttribute(Res.DataCategory_Update),
342         ResDescriptionAttribute(Res.DbCommand_UpdatedRowSource),
343         ]
344         override public UpdateRowSource UpdatedRowSource { // V1.2.3300, XXXCommand V1.0.5000
345             get {
346                 return _updatedRowSource;
347             }
348             set {
349                 switch(value) { // @perfnote: Enum.IsDefined
350                 case UpdateRowSource.None:
351                 case UpdateRowSource.OutputParameters:
352                 case UpdateRowSource.FirstReturnedRecord:
353                 case UpdateRowSource.Both:
354                     _updatedRowSource = value;
355                     break;
356                 default:
357                     throw ADP.InvalidUpdateRowSource(value);
358                 }
359             }
360         }
361
362         internal OdbcDescriptorHandle GetDescriptorHandle(ODBC32.SQL_ATTR attribute) {
363             return _cmdWrapper.GetDescriptorHandle(attribute);
364         }
365
366
367         // GetStatementHandle
368         // ------------------
369         // Try to return a cached statement handle.
370         //
371         // Creates a CmdWrapper object if necessary
372         // If no handle is available a handle will be allocated.
373         // Bindings will be unbound if a handle is cached and the bindings are invalid.
374         //
375         internal CMDWrapper GetStatementHandle () {
376             // update the command wrapper object, allocate buffer
377             // create reader object
378             //
379             if (_cmdWrapper==null) {
380                 _cmdWrapper = new CMDWrapper(_connection);
381
382                 Debug.Assert(null != _connection, "GetStatementHandle without connection?");
383                 _connection.AddWeakReference(this, OdbcReferenceCollection.CommandTag);
384             }
385
386             if (_cmdWrapper._dataReaderBuf == null) {
387                 _cmdWrapper._dataReaderBuf = new CNativeBuffer(4096);
388             }
389
390             // if there is already a statement handle we need to do some cleanup
391             //
392             if (null == _cmdWrapper.StatementHandle) {
393                 _isPrepared = false;
394                 _cmdWrapper.CreateStatementHandle();
395             }
396             else if ((null != _parameterCollection) && _parameterCollection.RebindCollection) {
397                 _cmdWrapper.FreeStatementHandle(ODBC32.STMT.RESET_PARAMS);
398             }
399             return _cmdWrapper;
400         }
401
402         // OdbcCommand.Cancel()
403         //
404         // In ODBC3.0 ... a call to SQLCancel when no processing is done has no effect at all
405         // (ODBC Programmer's Reference ...)
406         //
407
408         override public void Cancel() {
409             CMDWrapper wrapper = _cmdWrapper;
410             if (null != wrapper) {
411                 wrapper.Canceling = true;
412                 OdbcStatementHandle stmt = wrapper.StatementHandle;
413                 if (null != stmt) {
414                     lock (stmt) {
415                         // Cancel the statement
416                         ODBC32.RetCode retcode = stmt.Cancel();
417
418                         // copy of StatementErrorHandler, because stmt may become null
419                         switch(retcode) {
420                         case ODBC32.RetCode.SUCCESS:
421                         case ODBC32.RetCode.SUCCESS_WITH_INFO:
422                             // don't fire info message events on cancel
423                             break;
424                         default:
425                             throw wrapper.Connection.HandleErrorNoThrow(stmt, retcode);
426                         }
427                     }
428                 }
429             }
430         }
431
432
433         object ICloneable.Clone() {
434             OdbcCommand clone = new OdbcCommand();
435             Bid.Trace("<odbc.OdbcCommand.Clone|API> %d#, clone=%d#\n", ObjectID, clone.ObjectID);
436             clone.CommandText = CommandText;
437             clone.CommandTimeout = this.CommandTimeout;
438             clone.CommandType = CommandType;
439             clone.Connection = this.Connection;
440             clone.Transaction = this.Transaction;
441             clone.UpdatedRowSource = UpdatedRowSource;
442
443             if ((null != _parameterCollection) && (0 < Parameters.Count)) {
444                 OdbcParameterCollection parameters = clone.Parameters;
445                 foreach(ICloneable parameter in Parameters) {
446                     parameters.Add(parameter.Clone());
447                 }
448             }
449             return clone;
450         }
451
452         internal bool RecoverFromConnection() {
453             DisposeDeadDataReader();
454             return (ConnectionState.Closed == cmdState);
455         }
456
457         private void CloseCommandWrapper() {
458             CMDWrapper wrapper = _cmdWrapper;
459             if (null != wrapper) {
460                 try {
461                     wrapper.Dispose();
462
463                     if (null != _connection) {
464                         _connection.RemoveWeakReference(this);
465                     }
466                 }
467                 finally {
468                     _cmdWrapper = null;
469                 }
470             }
471         }
472
473         internal void CloseFromConnection () {
474             if (null != _parameterCollection) {
475                 _parameterCollection.RebindCollection = true;
476             }
477             DisposeDataReader();
478             CloseCommandWrapper();
479             _isPrepared = false;
480             _transaction = null;
481         }
482
483         internal void CloseFromDataReader() {
484             this.weakDataReaderReference = null;
485             this.cmdState = ConnectionState.Closed;
486         }
487
488         new public OdbcParameter CreateParameter() {
489             return new OdbcParameter();
490             }
491
492         override protected DbParameter CreateDbParameter() {
493             return CreateParameter();
494         }
495
496         override protected DbDataReader ExecuteDbDataReader(CommandBehavior behavior) {
497             return ExecuteReader(behavior);
498         }
499
500         override public int ExecuteNonQuery() {
501             OdbcConnection.ExecutePermission.Demand();
502             using (OdbcDataReader reader = ExecuteReaderObject(0, ADP.ExecuteNonQuery, false)) {
503                 reader.Close();
504                 return reader.RecordsAffected;
505             }
506         }
507
508         new public OdbcDataReader ExecuteReader() {
509             return ExecuteReader(0/*CommandBehavior*/);
510         }
511
512
513         new public OdbcDataReader ExecuteReader(CommandBehavior behavior) {
514             OdbcConnection.ExecutePermission.Demand();
515             return ExecuteReaderObject(behavior, ADP.ExecuteReader, true);
516         }
517
518         internal  OdbcDataReader ExecuteReaderFromSQLMethod(object[] methodArguments,
519                                                             ODBC32.SQL_API method){
520
521             return ExecuteReaderObject(CommandBehavior.Default,method.ToString(),true,methodArguments,method);
522
523         }
524
525         private OdbcDataReader ExecuteReaderObject(CommandBehavior behavior, string method, bool needReader) { // MDAC 68324
526
527             if ((CommandText == null) || (CommandText.Length == 0)) {
528                 throw (ADP.CommandTextRequired(method));
529             }
530             // using all functions to tell ExecuteReaderObject that
531             return ExecuteReaderObject(behavior,method,needReader,null,ODBC32.SQL_API.SQLEXECDIRECT);
532         }
533
534         private OdbcDataReader ExecuteReaderObject(CommandBehavior behavior,
535                                                    string method,
536                                                    bool needReader,
537                                                    object[] methodArguments,
538                                                    ODBC32.SQL_API odbcApiMethod) { // MDAC 68324
539
540             OdbcDataReader localReader = null;
541             try {
542                 DisposeDeadDataReader();    // this is a no-op if cmdState is not Fetching
543                 ValidateConnectionAndTransaction(method);  // cmdState will change to Executing
544
545                 if(0 != (CommandBehavior.SingleRow & behavior)) {
546                     // CommandBehavior.SingleRow implies CommandBehavior.SingleResult
547                     behavior |= CommandBehavior.SingleResult;
548                 }
549
550                 ODBC32.RetCode retcode;
551
552                 OdbcStatementHandle stmt = GetStatementHandle().StatementHandle;
553                 _cmdWrapper.Canceling = false;
554
555                 if(null != weakDataReaderReference) {
556                     if(weakDataReaderReference.IsAlive) {
557                         object target = weakDataReaderReference.Target;
558                         if(null != target && weakDataReaderReference.IsAlive) {
559                             if(!((OdbcDataReader)target).IsClosed) {
560                                 throw ADP.OpenReaderExists(); // MDAC 66411
561                             }
562                         }
563                     }
564                 }
565                 localReader = new OdbcDataReader(this, _cmdWrapper, behavior);
566
567                 //Set command properties
568                 //Not all drivers support timeout. So fail silently if error
569                 if(!Connection.ProviderInfo.NoQueryTimeout) {
570                     TrySetStatementAttribute(stmt,
571                         ODBC32.SQL_ATTR.QUERY_TIMEOUT,
572                         (IntPtr)this.CommandTimeout);
573                 }
574
575                 // todo: If we remember the state we can omit a lot of SQLSetStmtAttrW calls ...
576                 // if we do not create a reader we do not even need to do that
577                 if(needReader) {
578                     if(Connection.IsV3Driver) {
579                         if(!Connection.ProviderInfo.NoSqlSoptSSNoBrowseTable && !Connection.ProviderInfo.NoSqlSoptSSHiddenColumns) {
580                             // Need to get the metadata information
581
582                             //SQLServer actually requires browse info turned on ahead of time...
583                             //Note: We ignore any failures, since this is SQLServer specific
584                             //We won't specialcase for SQL Server but at least for non-V3 drivers
585                             if(localReader.IsBehavior(CommandBehavior.KeyInfo)) {
586                                 if(!_cmdWrapper._ssKeyInfoModeOn) {
587                                     TrySetStatementAttribute(stmt, (ODBC32.SQL_ATTR)ODBC32.SQL_SOPT_SS.NOBROWSETABLE, (IntPtr)ODBC32.SQL_NB.ON);
588                                     TrySetStatementAttribute(stmt, (ODBC32.SQL_ATTR)ODBC32.SQL_SOPT_SS.HIDDEN_COLUMNS, (IntPtr)ODBC32.SQL_HC.ON);
589                                     _cmdWrapper._ssKeyInfoModeOff = false;
590                                     _cmdWrapper._ssKeyInfoModeOn = true;
591                                 }
592                             }
593                             else {
594                                 if(!_cmdWrapper._ssKeyInfoModeOff) {
595                                     TrySetStatementAttribute(stmt, (ODBC32.SQL_ATTR)ODBC32.SQL_SOPT_SS.NOBROWSETABLE, (IntPtr)ODBC32.SQL_NB.OFF);
596                                     TrySetStatementAttribute(stmt, (ODBC32.SQL_ATTR)ODBC32.SQL_SOPT_SS.HIDDEN_COLUMNS, (IntPtr)ODBC32.SQL_HC.OFF);
597                                     _cmdWrapper._ssKeyInfoModeOff = true;
598                                     _cmdWrapper._ssKeyInfoModeOn = false;
599                                 }
600                             }
601                         }
602                     }
603                 }
604
605                 if(localReader.IsBehavior(CommandBehavior.KeyInfo) ||
606                     localReader.IsBehavior(CommandBehavior.SchemaOnly)) {
607
608                     retcode = stmt.Prepare(CommandText);
609
610                     if(ODBC32.RetCode.SUCCESS != retcode) {
611                         _connection.HandleError(stmt, retcode);
612                     }
613                 }
614
615                 bool mustRelease = false;
616                 CNativeBuffer parameterBuffer = _cmdWrapper._nativeParameterBuffer;
617
618                 RuntimeHelpers.PrepareConstrainedRegions();
619                 try {
620                     //Handle Parameters
621                     //Note: We use the internal variable as to not instante a new object collection,
622                     //for the the common case of using no parameters.
623                     if((null != _parameterCollection) && (0 < _parameterCollection.Count)) {
624                         int parameterBufferSize = _parameterCollection.CalcParameterBufferSize(this);
625
626                         if(null == parameterBuffer || parameterBuffer.Length < parameterBufferSize) {
627                             if (null != parameterBuffer) {
628                                 parameterBuffer.Dispose();
629                             }
630                             parameterBuffer = new CNativeBuffer(parameterBufferSize);
631                             _cmdWrapper._nativeParameterBuffer = parameterBuffer;
632                         }
633                         else {
634                             parameterBuffer.ZeroMemory();
635                         }
636
637                         parameterBuffer.DangerousAddRef(ref mustRelease);
638
639                         _parameterCollection.Bind(this, _cmdWrapper, parameterBuffer);
640                     }
641
642                     if(!localReader.IsBehavior(CommandBehavior.SchemaOnly)) {
643
644                         // Can't get the KeyInfo after command execution (SQL Server only since it does not support multiple
645                         // results on the same connection). Stored procedures (SP) do not return metadata before actual execution
646                         // Need to check the column count since the command type may not be set to SP for a SP.
647                         if((localReader.IsBehavior(CommandBehavior.KeyInfo) || localReader.IsBehavior(CommandBehavior.SchemaOnly))
648                             && (CommandType != CommandType.StoredProcedure)) {
649                             Int16 cColsAffected;
650                             retcode = stmt.NumberOfResultColumns(out cColsAffected);
651                             if(retcode == ODBC32.RetCode.SUCCESS || retcode == ODBC32.RetCode.SUCCESS_WITH_INFO) {
652                                 if(cColsAffected > 0) {
653                                     localReader.GetSchemaTable();
654                                 }
655                             }
656                             else if(retcode == ODBC32.RetCode.NO_DATA) {
657                                 // do nothing
658                             }
659                             else {
660                                 // any other returncode indicates an error
661                                 _connection.HandleError(stmt, retcode);
662                             }
663                         }
664
665                         switch(odbcApiMethod) {
666                             case ODBC32.SQL_API.SQLEXECDIRECT:
667                                 if(localReader.IsBehavior(CommandBehavior.KeyInfo) || _isPrepared) {
668                                     //Already prepared, so use SQLExecute
669                                     retcode = stmt.Execute();
670                                     // Build metadata here
671                                     // localReader.GetSchemaTable();
672                                 }
673                                 else {
674 #if DEBUG
675                                     //if (AdapterSwitches.OleDbTrace.TraceInfo) {
676                                     //    ADP.DebugWriteLine("SQLExecDirectW: " + CommandText);
677                                     //}
678 #endif
679                                     //SQLExecDirect
680                                     retcode = stmt.ExecuteDirect(CommandText);
681                                 }
682                                 break;
683
684                             case ODBC32.SQL_API.SQLTABLES:
685                                 retcode = stmt.Tables((string)methodArguments[0],  //TableCatalog
686                                     (string)methodArguments[1],  //TableSchema,
687                                     (string)methodArguments[2],  //TableName
688                                     (string)methodArguments[3]); //TableType
689                                 break;
690
691                             case ODBC32.SQL_API.SQLCOLUMNS:
692                                 retcode = stmt.Columns((string)methodArguments[0],  //TableCatalog
693                                     (string)methodArguments[1],  //TableSchema
694                                     (string)methodArguments[2],  //TableName
695                                     (string)methodArguments[3]); //ColumnName
696                                 break;
697
698                             case ODBC32.SQL_API.SQLPROCEDURES:
699                                 retcode = stmt.Procedures((string)methodArguments[0],  //ProcedureCatalog
700                                     (string)methodArguments[1],  //ProcedureSchema
701                                     (string)methodArguments[2]); //procedureName
702                                 break;
703
704                             case ODBC32.SQL_API.SQLPROCEDURECOLUMNS:
705                                 retcode = stmt.ProcedureColumns((string)methodArguments[0],  //ProcedureCatalog
706                                     (string)methodArguments[1],  //ProcedureSchema
707                                     (string)methodArguments[2],  //procedureName
708                                     (string)methodArguments[3]); //columnName
709                                 break;
710
711                             case ODBC32.SQL_API.SQLSTATISTICS:
712                                 retcode = stmt.Statistics((string)methodArguments[0],  //TableCatalog
713                                     (string)methodArguments[1],  //TableSchema
714                                     (string)methodArguments[2],  //TableName
715                                     (Int16)methodArguments[3],   //IndexTrpe
716                                     (Int16)methodArguments[4]);  //Accuracy
717                                 break;
718
719                             case ODBC32.SQL_API.SQLGETTYPEINFO:
720                                 retcode = stmt.GetTypeInfo((Int16)methodArguments[0]);  //SQL Type
721                                 break;
722
723                             default:
724                                 // this should NEVER happen
725                                 Debug.Assert(false, "ExecuteReaderObjectcalled with unsupported ODBC API method.");
726                                 throw ADP.InvalidOperation(method.ToString());
727                         }
728
729                         //Note: Execute will return NO_DATA for Update/Delete non-row returning queries
730                         if((ODBC32.RetCode.SUCCESS != retcode) && (ODBC32.RetCode.NO_DATA != retcode)) {
731                             _connection.HandleError(stmt, retcode);
732                         }
733                     } // end SchemaOnly
734                 }
735                 finally {
736                     if(mustRelease) {
737                         parameterBuffer.DangerousRelease();
738                     }
739                 }
740
741                 this.weakDataReaderReference = new WeakReference(localReader);
742
743                 // XXXCommand.Execute should position reader on first row returning result
744                 // any exceptions in the initial non-row returning results should be thrown
745                 // from from ExecuteXXX not the DataReader
746                 if(!localReader.IsBehavior(CommandBehavior.SchemaOnly)) {
747                     localReader.FirstResult();
748                 }
749                 cmdState = ConnectionState.Fetching;
750             }
751             finally {
752                 if(ConnectionState.Fetching != cmdState) {
753                     if(null != localReader) {
754                         // clear bindings so we don't grab output parameters on a failed execute
755                         if(null != _parameterCollection) {
756                             _parameterCollection.ClearBindings();
757                         }
758                         ((IDisposable)localReader).Dispose();
759                     }
760                     if(ConnectionState.Closed != cmdState) {
761                         cmdState = ConnectionState.Closed;
762                     }
763                 }
764             }
765             return localReader;
766         }
767
768         override public object ExecuteScalar() {
769             OdbcConnection.ExecutePermission.Demand();
770
771             object value = null;
772             using(IDataReader reader = ExecuteReaderObject(0, ADP.ExecuteScalar, false)) {
773                 if (reader.Read() && (0 < reader.FieldCount)) {
774                     value = reader.GetValue(0);
775                 }
776                 reader.Close();
777             }
778             return value;
779         }
780
781         internal string GetDiagSqlState() {
782             return _cmdWrapper.GetDiagSqlState();
783         }
784
785         private void PropertyChanging() {
786             _isPrepared = false;
787         }
788
789         // Prepare
790         //
791         // if the CommandType property is set to TableDirect Prepare does nothing.
792         // if the CommandType property is set to StoredProcedure Prepare should succeed but result
793         // in a no-op
794         //
795         // throw InvalidOperationException
796         // if the connection is not set
797         // if the connection is not open
798         //
799         override public void Prepare() {
800             OdbcConnection.ExecutePermission.Demand();
801             ODBC32.RetCode retcode;
802
803             ValidateOpenConnection(ADP.Prepare);
804
805             if (0 != (ConnectionState.Fetching & _connection.InternalState)) {
806                 throw ADP.OpenReaderExists();
807             }
808
809             if (CommandType == CommandType.TableDirect) {
810                 return; // do nothing
811             }
812
813             DisposeDeadDataReader();
814             GetStatementHandle();
815
816             OdbcStatementHandle stmt = _cmdWrapper.StatementHandle;
817
818             retcode = stmt.Prepare(CommandText);
819
820
821             if (ODBC32.RetCode.SUCCESS != retcode) {
822                 _connection.HandleError(stmt, retcode);
823             }
824             _isPrepared = true;
825         }
826
827
828
829         void TrySetStatementAttribute (OdbcStatementHandle stmt, ODBC32.SQL_ATTR stmtAttribute, IntPtr value) {
830
831             ODBC32.RetCode retcode = stmt.SetStatementAttribute(
832                 stmtAttribute,
833                 value,
834                 ODBC32.SQL_IS.UINTEGER);
835
836             if (retcode == ODBC32.RetCode.ERROR) {
837
838                 string sqlState;
839                 stmt.GetDiagnosticField(out sqlState);
840
841                 if ((sqlState == "HYC00") || (sqlState == "HY092")) {
842                     Connection.FlagUnsupportedStmtAttr(stmtAttribute);
843                 }
844                 else {
845                     // now what? Should we throw?
846                 }
847             }
848         }
849
850         private void ValidateOpenConnection(string methodName) {
851             // see if we have a connection
852             OdbcConnection connection = Connection;
853
854             if (null == connection) {
855                 throw ADP.ConnectionRequired(methodName);
856             }
857
858             // must have an open and available connection
859             ConnectionState state = connection.State;
860
861             if (ConnectionState.Open != state) {
862                 throw ADP.OpenConnectionRequired(methodName, state);
863             }
864         }
865
866         private void ValidateConnectionAndTransaction(string method) {
867             if (null == _connection) {
868                 throw ADP.ConnectionRequired(method);
869             }
870             _transaction = _connection.SetStateExecuting(method, Transaction);
871             cmdState = ConnectionState.Executing;
872         }
873
874     }
875     sealed internal class CMDWrapper {
876
877         private OdbcStatementHandle _stmt;                  // hStmt
878         private OdbcStatementHandle _keyinfostmt;           // hStmt for keyinfo
879
880         internal OdbcDescriptorHandle  _hdesc;              // hDesc
881
882         internal CNativeBuffer _nativeParameterBuffer;      // Native memory for internal memory management
883         // (Performance optimization)
884
885         internal CNativeBuffer      _dataReaderBuf;         // Reusable DataReader buffer
886
887         private readonly OdbcConnection _connection;        // Connection
888         private bool                _canceling;             // true if the command is canceling
889         internal bool               _hasBoundColumns;
890         internal bool               _ssKeyInfoModeOn;       // tells us if the SqlServer specific options are on
891         internal bool               _ssKeyInfoModeOff;      // a tri-state value would be much better ...
892
893         internal CMDWrapper (OdbcConnection connection) {
894             _connection = connection;
895         }
896
897         internal bool Canceling {
898             get {
899                 return _canceling;
900             }
901             set {
902                 _canceling = value;
903             }
904         }
905
906         internal OdbcConnection Connection {
907             get {
908                 return _connection;
909             }
910         }
911
912         internal bool HasBoundColumns {
913 //            get {
914 //                return _hasBoundColumns;
915 //            }
916             set {
917                 _hasBoundColumns = value;
918             }
919         }
920
921         internal OdbcStatementHandle StatementHandle {
922             get { return _stmt; }
923         }
924
925         internal OdbcStatementHandle KeyInfoStatement {
926             get {
927                 return _keyinfostmt;
928             }
929         }
930
931         internal void CreateKeyInfoStatementHandle() {
932             DisposeKeyInfoStatementHandle();
933             _keyinfostmt =  _connection.CreateStatementHandle();
934         }
935
936         internal void CreateStatementHandle() {
937             DisposeStatementHandle();
938             _stmt =  _connection.CreateStatementHandle();
939         }
940
941         internal void Dispose() {
942             if (null != _dataReaderBuf) {
943                 _dataReaderBuf.Dispose();
944                 _dataReaderBuf = null;
945             }
946             DisposeStatementHandle();
947
948             CNativeBuffer buffer = _nativeParameterBuffer;
949             _nativeParameterBuffer = null;
950             if (null != buffer) {
951                 buffer.Dispose();
952             }
953             _ssKeyInfoModeOn = false;
954             _ssKeyInfoModeOff = false;
955         }
956
957         private void DisposeDescriptorHandle() {
958             OdbcDescriptorHandle handle = _hdesc;
959             if (null != handle) {
960                 _hdesc = null;
961                 handle.Dispose();
962             }
963         }
964         internal void DisposeStatementHandle() {
965             DisposeKeyInfoStatementHandle();
966             DisposeDescriptorHandle();
967
968             OdbcStatementHandle handle = _stmt;
969             if (null != handle) {
970                 _stmt = null;
971                 handle.Dispose();
972             }
973         }
974
975         internal void DisposeKeyInfoStatementHandle() {
976             OdbcStatementHandle handle = _keyinfostmt;
977             if (null != handle) {
978                 _keyinfostmt = null;
979                 handle.Dispose();
980             }
981         }
982
983         internal void FreeStatementHandle(ODBC32.STMT stmt) {
984             DisposeDescriptorHandle();
985
986             OdbcStatementHandle handle = _stmt;
987             if (null != handle) {
988                 try {
989                     ODBC32.RetCode retcode;
990                     retcode = handle.FreeStatement(stmt);
991                     StatementErrorHandler(retcode);
992                 }
993                 catch (Exception e) {
994                     // 
995                     if (ADP.IsCatchableExceptionType(e)) {
996                         _stmt = null;
997                         handle.Dispose();
998                     }
999
1000                     throw;
1001                 }
1002             }
1003         }
1004
1005         internal void FreeKeyInfoStatementHandle(ODBC32.STMT stmt) {
1006             OdbcStatementHandle handle = _keyinfostmt;
1007             if (null != handle) {
1008                 try {
1009                     handle.FreeStatement(stmt);
1010                 }
1011                 catch (Exception e) {
1012                     // 
1013                     if (ADP.IsCatchableExceptionType(e)) {
1014                         _keyinfostmt = null;
1015                         handle.Dispose();
1016                     }
1017
1018                     throw;
1019                 }
1020             }
1021         }
1022
1023         // Get the Descriptor Handle for the current statement
1024         //
1025         internal OdbcDescriptorHandle GetDescriptorHandle(ODBC32.SQL_ATTR attribute) {
1026             OdbcDescriptorHandle hdesc = _hdesc;
1027             if (null == _hdesc) {
1028                 _hdesc = hdesc = new OdbcDescriptorHandle(_stmt, attribute);
1029             }
1030             return hdesc;
1031         }
1032
1033         internal string GetDiagSqlState () {
1034             string sqlstate;
1035             _stmt.GetDiagnosticField(out sqlstate);
1036             return sqlstate;
1037         }
1038
1039         internal void StatementErrorHandler(ODBC32.RetCode retcode) {
1040             switch(retcode) {
1041             case ODBC32.RetCode.SUCCESS:
1042             case ODBC32.RetCode.SUCCESS_WITH_INFO:
1043                 _connection.HandleErrorNoThrow(_stmt, retcode);
1044                 break;
1045             default:
1046                 throw _connection.HandleErrorNoThrow(_stmt, retcode);
1047             }
1048         }
1049
1050         internal void UnbindStmtColumns() {
1051             if (_hasBoundColumns) {
1052                 FreeStatementHandle(ODBC32.STMT.UNBIND);
1053                 _hasBoundColumns = false;
1054             }
1055         }
1056     }
1057 }