File: System\Data\Odbc\OdbcCommand.cs
Project: ndp\fx\src\data\System.Data.csproj (System.Data)
//------------------------------------------------------------------------------
// <copyright file="OdbcCommand.cs" company="Microsoft">
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
// <owner current="true" primary="true">Microsoft</owner>
// <owner current="true" primary="false">Microsoft</owner>
//------------------------------------------------------------------------------
 
using System;
using System.ComponentModel;            //Component
using System.Data;
using System.Data.Common;
using System.Data.ProviderBase;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
 
// todo:
// There may be two ways to improve performance:
// 1. pool statements on the connection object
// 2. Do not create a datareader object for non-datareader returning command execution.
//
// We do not want to do the effort unless we have to squeze performance.
 
 
 
namespace System.Data.Odbc {
 
    [
    DefaultEvent("RecordsAffected"),
    ToolboxItem(true),
    Designer("Microsoft.VSDesigner.Data.VS.OdbcCommandDesigner, " + AssemblyRef.MicrosoftVSDesigner)
    ]
    public sealed class OdbcCommand : DbCommand, ICloneable {
        private static int          _objectTypeCount; // Bid counter
        internal readonly int       ObjectID = System.Threading.Interlocked.Increment(ref _objectTypeCount);
 
        private string              _commandText;
        private CommandType         _commandType;
        private int                 _commandTimeout = ADP.DefaultCommandTimeout;
        private UpdateRowSource     _updatedRowSource = UpdateRowSource.Both;
        private bool                _designTimeInvisible;
        private bool                _isPrepared;                        // true if the command is prepared
 
        private OdbcConnection      _connection;
        private OdbcTransaction     _transaction;
 
        private WeakReference       weakDataReaderReference;
 
        private CMDWrapper          _cmdWrapper;
 
        private OdbcParameterCollection    _parameterCollection;   // Parameter collection
 
        private ConnectionState     cmdState;
 
        public OdbcCommand() : base() {
            GC.SuppressFinalize(this);
        }
 
        public OdbcCommand(string cmdText) : this() {
            // note: arguments are assigned to properties so we do not have to trace them.
            // We still need to include them into the argument list of the definition!
            CommandText = cmdText;
        }
 
        public OdbcCommand(string cmdText, OdbcConnection connection) : this() {
            CommandText = cmdText;
            Connection  = connection;
        }
 
        public OdbcCommand(string cmdText, OdbcConnection connection, OdbcTransaction transaction) : this() {
            CommandText = cmdText;
            Connection = connection;
            Transaction = transaction;
        }
 
        private void DisposeDeadDataReader() {
            if (ConnectionState.Fetching == cmdState) {
                if (null != this.weakDataReaderReference && !this.weakDataReaderReference.IsAlive) {
                    if (_cmdWrapper != null) {
                        _cmdWrapper.FreeKeyInfoStatementHandle(ODBC32.STMT.CLOSE);
                        _cmdWrapper.FreeStatementHandle(ODBC32.STMT.CLOSE);
                    }
                    CloseFromDataReader();
                }
            }
        }
 
        private void DisposeDataReader() {
            if (null != this.weakDataReaderReference) {
                IDisposable reader = (IDisposable) this.weakDataReaderReference.Target;
                if ((null != reader) && this.weakDataReaderReference.IsAlive) {
                    ((IDisposable) reader).Dispose();
                }
                CloseFromDataReader();
            }
        }
 
        internal void DisconnectFromDataReaderAndConnection () {
            // get a reference to the datareader if it is alive
            OdbcDataReader liveReader = null;
            if (this.weakDataReaderReference != null){
                OdbcDataReader reader;
                reader = (OdbcDataReader)this.weakDataReaderReference.Target;
                if (this.weakDataReaderReference.IsAlive) {
                    liveReader = reader;
                }
            }
 
            // remove reference to this from the live datareader
            if (liveReader != null) {
                liveReader.Command = null;
            }
 
            _transaction = null;
 
            if (null != _connection) {
                _connection.RemoveWeakReference(this);
                _connection = null;
            }
 
            // if the reader is dead we have to dismiss the statement
            if (liveReader == null){
                CloseCommandWrapper();
            }
            // else DataReader now has exclusive ownership
            _cmdWrapper = null;
        }
 
        override protected void Dispose(bool disposing) { // MDAC 65459
            if (disposing) {
                // release mananged objects
                // in V1.0, V1.1 the Connection,Parameters,CommandText,Transaction where reset
                this.DisconnectFromDataReaderAndConnection ();
                _parameterCollection = null;
                CommandText = null;
            }
            _cmdWrapper = null;                         // let go of the CommandWrapper
            _isPrepared = false;
 
            base.Dispose(disposing);    // notify base classes
        }
 
        internal bool Canceling {
            get {
                return _cmdWrapper.Canceling;
            }
        }
 
        [
        ResCategoryAttribute(Res.DataCategory_Data),
        DefaultValue(""),
        RefreshProperties(RefreshProperties.All), // MDAC 67707
        ResDescriptionAttribute(Res.DbCommand_CommandText),
        Editor("Microsoft.VSDesigner.Data.Odbc.Design.OdbcCommandTextEditor, " + AssemblyRef.MicrosoftVSDesigner, "System.Drawing.Design.UITypeEditor, " + AssemblyRef.SystemDrawing)
        ]
        override public string CommandText {
            get {
                string value = _commandText;
                return ((null != value) ? value : ADP.StrEmpty);
            }
            set {
                if (Bid.TraceOn) {
                    Bid.Trace("<odbc.OdbcCommand.set_CommandText|API> %d#, '", ObjectID);
                    Bid.PutStr(value); // Use PutStr to write out entire string
                    Bid.Trace("'\n");
                }
                if (0 != ADP.SrcCompare(_commandText, value)) {
                    PropertyChanging();
                    _commandText = value;
                }
            }
        }
 
        [
        ResCategoryAttribute(Res.DataCategory_Data),
        ResDescriptionAttribute(Res.DbCommand_CommandTimeout),
        ]
        override public int CommandTimeout { // V1.2.3300, XXXCommand V1.0.5000
            get {
                return _commandTimeout;
            }
            set {
                Bid.Trace("<odbc.OdbcCommand.set_CommandTimeout|API> %d#, %d\n", ObjectID, value);
                if (value < 0) {
                    throw ADP.InvalidCommandTimeout(value);
                }
                if (value != _commandTimeout) {
                    PropertyChanging();
                    _commandTimeout = value;
                }
            }
        }
 
        public void ResetCommandTimeout() { // V1.2.3300
            if (ADP.DefaultCommandTimeout != _commandTimeout) {
                PropertyChanging();
                _commandTimeout = ADP.DefaultCommandTimeout;
            }
        }
 
        private bool ShouldSerializeCommandTimeout() { // V1.2.3300
            return (ADP.DefaultCommandTimeout != _commandTimeout);
        }
 
        [
        DefaultValue(System.Data.CommandType.Text),
        RefreshProperties(RefreshProperties.All),
        ResCategoryAttribute(Res.DataCategory_Data),
        ResDescriptionAttribute(Res.DbCommand_CommandType),
        ]
        override public CommandType CommandType {
            get {
                CommandType cmdType = _commandType;
                return ((0 != cmdType) ? cmdType : CommandType.Text);
            }
            set  {
                switch(value) { // @perfnote: Enum.IsDefined
                case CommandType.Text:
                case CommandType.StoredProcedure:
                    PropertyChanging();
                    _commandType = value;
                    break;
                case CommandType.TableDirect:
                    throw ODBC.NotSupportedCommandType(value);
                default:
                    throw ADP.InvalidCommandType(value);
                }
            }
        }
 
        // This will establish a relationship between the command and the connection
        [
        DefaultValue(null),
        ResCategoryAttribute(Res.DataCategory_Behavior),
        ResDescriptionAttribute(Res.DbCommand_Connection),
        Editor("Microsoft.VSDesigner.Data.Design.DbConnectionEditor, " + AssemblyRef.MicrosoftVSDesigner, "System.Drawing.Design.UITypeEditor, " + AssemblyRef.SystemDrawing),
        ]
        new public OdbcConnection Connection {
            get {
                return _connection;
            }
            set {
                if (value != _connection) {
                    PropertyChanging();
                    this.DisconnectFromDataReaderAndConnection();
                    Debug.Assert(null == _cmdWrapper, "has CMDWrapper when setting connection");
                    _connection = value;
                    //OnSchemaChanged();
                }
            }
        }
 
        override protected DbConnection DbConnection { // V1.2.3300
            get {
                return Connection;
            }
            set {
                Connection = (OdbcConnection)value;
            }
        }
 
        override protected DbParameterCollection DbParameterCollection { // V1.2.3300
            get {
                return Parameters;
            }
        }
 
        override protected DbTransaction DbTransaction { // V1.2.3300
            get {
                return Transaction;
            }
            set {
                Transaction = (OdbcTransaction)value;
            }
        }
 
        // @devnote: By default, the cmd object is visible on the design surface (i.e. VS7 Server Tray)
        // to limit the number of components that clutter the design surface,
        // when the DataAdapter design wizard generates the insert/update/delete commands it will
        // set the DesignTimeVisible property to false so that cmds won't appear as individual objects
        [
        DefaultValue(true),
        DesignOnly(true),
        Browsable(false),
        EditorBrowsableAttribute(EditorBrowsableState.Never),
        ]
        public override bool DesignTimeVisible { // V1.2.3300, XXXCommand V1.0.5000
            get {
                return !_designTimeInvisible;
            }
            set {
                _designTimeInvisible = !value;
                TypeDescriptor.Refresh(this); // VS7 208845
            }
        }
 
        internal bool HasParameters {
            get {
                return (null != _parameterCollection);
            }
        }
 
        [
        DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
        ResCategoryAttribute(Res.DataCategory_Data),
        ResDescriptionAttribute(Res.DbCommand_Parameters),
        ]
        new public OdbcParameterCollection Parameters {
            get {
                if (null == _parameterCollection) {
                    _parameterCollection = new OdbcParameterCollection();
                }
                return _parameterCollection;
            }
        }
 
        [
        Browsable(false),
        DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden),
        ResDescriptionAttribute(Res.DbCommand_Transaction),
        ]
        new public OdbcTransaction Transaction {
            get {
                if ((null != _transaction) && (null == _transaction.Connection)) {
                    _transaction = null;       // Dawn of the Dead
                }
                return _transaction;
            }
            set {
                if (_transaction != value) {
                    PropertyChanging(); // fire event before value is validated
                    _transaction = value;
                }
            }
        }
 
        [
        DefaultValue(System.Data.UpdateRowSource.Both),
        ResCategoryAttribute(Res.DataCategory_Update),
        ResDescriptionAttribute(Res.DbCommand_UpdatedRowSource),
        ]
        override public UpdateRowSource UpdatedRowSource { // V1.2.3300, XXXCommand V1.0.5000
            get {
                return _updatedRowSource;
            }
            set {
                switch(value) { // @perfnote: Enum.IsDefined
                case UpdateRowSource.None:
                case UpdateRowSource.OutputParameters:
                case UpdateRowSource.FirstReturnedRecord:
                case UpdateRowSource.Both:
                    _updatedRowSource = value;
                    break;
                default:
                    throw ADP.InvalidUpdateRowSource(value);
                }
            }
        }
 
        internal OdbcDescriptorHandle GetDescriptorHandle(ODBC32.SQL_ATTR attribute) {
            return _cmdWrapper.GetDescriptorHandle(attribute);
        }
 
 
        // GetStatementHandle
        // ------------------
        // Try to return a cached statement handle.
        //
        // Creates a CmdWrapper object if necessary
        // If no handle is available a handle will be allocated.
        // Bindings will be unbound if a handle is cached and the bindings are invalid.
        //
        internal CMDWrapper GetStatementHandle () {
            // update the command wrapper object, allocate buffer
            // create reader object
            //
            if (_cmdWrapper==null) {
                _cmdWrapper = new CMDWrapper(_connection);
 
                Debug.Assert(null != _connection, "GetStatementHandle without connection?");
                _connection.AddWeakReference(this, OdbcReferenceCollection.CommandTag);
            }
 
            if (_cmdWrapper._dataReaderBuf == null) {
                _cmdWrapper._dataReaderBuf = new CNativeBuffer(4096);
            }
 
            // if there is already a statement handle we need to do some cleanup
            //
            if (null == _cmdWrapper.StatementHandle) {
                _isPrepared = false;
                _cmdWrapper.CreateStatementHandle();
            }
            else if ((null != _parameterCollection) && _parameterCollection.RebindCollection) {
                _cmdWrapper.FreeStatementHandle(ODBC32.STMT.RESET_PARAMS);
            }
            return _cmdWrapper;
        }
 
        // OdbcCommand.Cancel()
        //
        // In ODBC3.0 ... a call to SQLCancel when no processing is done has no effect at all
        // (ODBC Programmer's Reference ...)
        //
 
        override public void Cancel() {
            CMDWrapper wrapper = _cmdWrapper;
            if (null != wrapper) {
                wrapper.Canceling = true;
                OdbcStatementHandle stmt = wrapper.StatementHandle;
                if (null != stmt) {
                    lock (stmt) {
                        // Cancel the statement
                        ODBC32.RetCode retcode = stmt.Cancel();
 
                        // copy of StatementErrorHandler, because stmt may become null
                        switch(retcode) {
                        case ODBC32.RetCode.SUCCESS:
                        case ODBC32.RetCode.SUCCESS_WITH_INFO:
                            // don't fire info message events on cancel
                            break;
                        default:
                            throw wrapper.Connection.HandleErrorNoThrow(stmt, retcode);
                        }
                    }
                }
            }
        }
 
 
        object ICloneable.Clone() {
            OdbcCommand clone = new OdbcCommand();
            Bid.Trace("<odbc.OdbcCommand.Clone|API> %d#, clone=%d#\n", ObjectID, clone.ObjectID);
            clone.CommandText = CommandText;
            clone.CommandTimeout = this.CommandTimeout;
            clone.CommandType = CommandType;
            clone.Connection = this.Connection;
            clone.Transaction = this.Transaction;
            clone.UpdatedRowSource = UpdatedRowSource;
 
            if ((null != _parameterCollection) && (0 < Parameters.Count)) {
                OdbcParameterCollection parameters = clone.Parameters;
                foreach(ICloneable parameter in Parameters) {
                    parameters.Add(parameter.Clone());
                }
            }
            return clone;
        }
 
        internal bool RecoverFromConnection() {
            DisposeDeadDataReader();
            return (ConnectionState.Closed == cmdState);
        }
 
        private void CloseCommandWrapper() {
            CMDWrapper wrapper = _cmdWrapper;
            if (null != wrapper) {
                try {
                    wrapper.Dispose();
 
                    if (null != _connection) {
                        _connection.RemoveWeakReference(this);
                    }
                }
                finally {
                    _cmdWrapper = null;
                }
            }
        }
 
        internal void CloseFromConnection () {
            if (null != _parameterCollection) {
                _parameterCollection.RebindCollection = true;
            }
            DisposeDataReader();
            CloseCommandWrapper();
            _isPrepared = false;
            _transaction = null;
        }
 
        internal void CloseFromDataReader() {
            this.weakDataReaderReference = null;
            this.cmdState = ConnectionState.Closed;
        }
 
        new public OdbcParameter CreateParameter() {
            return new OdbcParameter();
            }
 
        override protected DbParameter CreateDbParameter() {
            return CreateParameter();
        }
 
        override protected DbDataReader ExecuteDbDataReader(CommandBehavior behavior) {
            return ExecuteReader(behavior);
        }
 
        override public int ExecuteNonQuery() {
            OdbcConnection.ExecutePermission.Demand();
            using (OdbcDataReader reader = ExecuteReaderObject(0, ADP.ExecuteNonQuery, false)) {
                reader.Close();
                return reader.RecordsAffected;
            }
        }
 
        new public OdbcDataReader ExecuteReader() {
            return ExecuteReader(0/*CommandBehavior*/);
        }
 
 
        new public OdbcDataReader ExecuteReader(CommandBehavior behavior) {
            OdbcConnection.ExecutePermission.Demand();
            return ExecuteReaderObject(behavior, ADP.ExecuteReader, true);
        }
 
        internal  OdbcDataReader ExecuteReaderFromSQLMethod(object[] methodArguments,
                                                            ODBC32.SQL_API method){
 
            return ExecuteReaderObject(CommandBehavior.Default,method.ToString(),true,methodArguments,method);
 
        }
 
        private OdbcDataReader ExecuteReaderObject(CommandBehavior behavior, string method, bool needReader) { // MDAC 68324
 
            if ((CommandText == null) || (CommandText.Length == 0)) {
                throw (ADP.CommandTextRequired(method));
            }
            // using all functions to tell ExecuteReaderObject that
            return ExecuteReaderObject(behavior,method,needReader,null,ODBC32.SQL_API.SQLEXECDIRECT);
        }
 
        private OdbcDataReader ExecuteReaderObject(CommandBehavior behavior,
                                                   string method,
                                                   bool needReader,
                                                   object[] methodArguments,
                                                   ODBC32.SQL_API odbcApiMethod) { // MDAC 68324
 
            OdbcDataReader localReader = null;
            try {
                DisposeDeadDataReader();    // this is a no-op if cmdState is not Fetching
                ValidateConnectionAndTransaction(method);  // cmdState will change to Executing
 
                if(0 != (CommandBehavior.SingleRow & behavior)) {
                    // CommandBehavior.SingleRow implies CommandBehavior.SingleResult
                    behavior |= CommandBehavior.SingleResult;
                }
 
                ODBC32.RetCode retcode;
 
                OdbcStatementHandle stmt = GetStatementHandle().StatementHandle;
                _cmdWrapper.Canceling = false;
 
                if(null != weakDataReaderReference) {
                    if(weakDataReaderReference.IsAlive) {
                        object target = weakDataReaderReference.Target;
                        if(null != target && weakDataReaderReference.IsAlive) {
                            if(!((OdbcDataReader)target).IsClosed) {
                                throw ADP.OpenReaderExists(); // MDAC 66411
                            }
                        }
                    }
                }
                localReader = new OdbcDataReader(this, _cmdWrapper, behavior);
 
                //Set command properties
                //Not all drivers support timeout. So fail silently if error
                if(!Connection.ProviderInfo.NoQueryTimeout) {
                    TrySetStatementAttribute(stmt,
                        ODBC32.SQL_ATTR.QUERY_TIMEOUT,
                        (IntPtr)this.CommandTimeout);
                }
 
                // todo: If we remember the state we can omit a lot of SQLSetStmtAttrW calls ...
                // if we do not create a reader we do not even need to do that
                if(needReader) {
                    if(Connection.IsV3Driver) {
                        if(!Connection.ProviderInfo.NoSqlSoptSSNoBrowseTable && !Connection.ProviderInfo.NoSqlSoptSSHiddenColumns) {
                            // Need to get the metadata information
 
                            //SQLServer actually requires browse info turned on ahead of time...
                            //Note: We ignore any failures, since this is SQLServer specific
                            //We won't specialcase for SQL Server but at least for non-V3 drivers
                            if(localReader.IsBehavior(CommandBehavior.KeyInfo)) {
                                if(!_cmdWrapper._ssKeyInfoModeOn) {
                                    TrySetStatementAttribute(stmt, (ODBC32.SQL_ATTR)ODBC32.SQL_SOPT_SS.NOBROWSETABLE, (IntPtr)ODBC32.SQL_NB.ON);
                                    TrySetStatementAttribute(stmt, (ODBC32.SQL_ATTR)ODBC32.SQL_SOPT_SS.HIDDEN_COLUMNS, (IntPtr)ODBC32.SQL_HC.ON);
                                    _cmdWrapper._ssKeyInfoModeOff = false;
                                    _cmdWrapper._ssKeyInfoModeOn = true;
                                }
                            }
                            else {
                                if(!_cmdWrapper._ssKeyInfoModeOff) {
                                    TrySetStatementAttribute(stmt, (ODBC32.SQL_ATTR)ODBC32.SQL_SOPT_SS.NOBROWSETABLE, (IntPtr)ODBC32.SQL_NB.OFF);
                                    TrySetStatementAttribute(stmt, (ODBC32.SQL_ATTR)ODBC32.SQL_SOPT_SS.HIDDEN_COLUMNS, (IntPtr)ODBC32.SQL_HC.OFF);
                                    _cmdWrapper._ssKeyInfoModeOff = true;
                                    _cmdWrapper._ssKeyInfoModeOn = false;
                                }
                            }
                        }
                    }
                }
 
                if(localReader.IsBehavior(CommandBehavior.KeyInfo) ||
                    localReader.IsBehavior(CommandBehavior.SchemaOnly)) {
 
                    retcode = stmt.Prepare(CommandText);
 
                    if(ODBC32.RetCode.SUCCESS != retcode) {
                        _connection.HandleError(stmt, retcode);
                    }
                }
 
                bool mustRelease = false;
                CNativeBuffer parameterBuffer = _cmdWrapper._nativeParameterBuffer;
 
                RuntimeHelpers.PrepareConstrainedRegions();
                try {
                    //Handle Parameters
                    //Note: We use the internal variable as to not instante a new object collection,
                    //for the the common case of using no parameters.
                    if((null != _parameterCollection) && (0 < _parameterCollection.Count)) {
                        int parameterBufferSize = _parameterCollection.CalcParameterBufferSize(this);
 
                        if(null == parameterBuffer || parameterBuffer.Length < parameterBufferSize) {
                            if (null != parameterBuffer) {
                                parameterBuffer.Dispose();
                            }
                            parameterBuffer = new CNativeBuffer(parameterBufferSize);
                            _cmdWrapper._nativeParameterBuffer = parameterBuffer;
                        }
                        else {
                            parameterBuffer.ZeroMemory();
                        }
 
                        parameterBuffer.DangerousAddRef(ref mustRelease);
 
                        _parameterCollection.Bind(this, _cmdWrapper, parameterBuffer);
                    }
 
                    if(!localReader.IsBehavior(CommandBehavior.SchemaOnly)) {
 
                        // Can't get the KeyInfo after command execution (SQL Server only since it does not support multiple
                        // results on the same connection). Stored procedures (SP) do not return metadata before actual execution
                        // Need to check the column count since the command type may not be set to SP for a SP.
                        if((localReader.IsBehavior(CommandBehavior.KeyInfo) || localReader.IsBehavior(CommandBehavior.SchemaOnly))
                            && (CommandType != CommandType.StoredProcedure)) {
                            Int16 cColsAffected;
                            retcode = stmt.NumberOfResultColumns(out cColsAffected);
                            if(retcode == ODBC32.RetCode.SUCCESS || retcode == ODBC32.RetCode.SUCCESS_WITH_INFO) {
                                if(cColsAffected > 0) {
                                    localReader.GetSchemaTable();
                                }
                            }
                            else if(retcode == ODBC32.RetCode.NO_DATA) {
                                // do nothing
                            }
                            else {
                                // any other returncode indicates an error
                                _connection.HandleError(stmt, retcode);
                            }
                        }
 
                        switch(odbcApiMethod) {
                            case ODBC32.SQL_API.SQLEXECDIRECT:
                                if(localReader.IsBehavior(CommandBehavior.KeyInfo) || _isPrepared) {
                                    //Already prepared, so use SQLExecute
                                    retcode = stmt.Execute();
                                    // Build metadata here
                                    // localReader.GetSchemaTable();
                                }
                                else {
#if DEBUG
                                    //if (AdapterSwitches.OleDbTrace.TraceInfo) {
                                    //    ADP.DebugWriteLine("SQLExecDirectW: " + CommandText);
                                    //}
#endif
                                    //SQLExecDirect
                                    retcode = stmt.ExecuteDirect(CommandText);
                                }
                                break;
 
                            case ODBC32.SQL_API.SQLTABLES:
                                retcode = stmt.Tables((string)methodArguments[0],  //TableCatalog
                                    (string)methodArguments[1],  //TableSchema,
                                    (string)methodArguments[2],  //TableName
                                    (string)methodArguments[3]); //TableType
                                break;
 
                            case ODBC32.SQL_API.SQLCOLUMNS:
                                retcode = stmt.Columns((string)methodArguments[0],  //TableCatalog
                                    (string)methodArguments[1],  //TableSchema
                                    (string)methodArguments[2],  //TableName
                                    (string)methodArguments[3]); //ColumnName
                                break;
 
                            case ODBC32.SQL_API.SQLPROCEDURES:
                                retcode = stmt.Procedures((string)methodArguments[0],  //ProcedureCatalog
                                    (string)methodArguments[1],  //ProcedureSchema
                                    (string)methodArguments[2]); //procedureName
                                break;
 
                            case ODBC32.SQL_API.SQLPROCEDURECOLUMNS:
                                retcode = stmt.ProcedureColumns((string)methodArguments[0],  //ProcedureCatalog
                                    (string)methodArguments[1],  //ProcedureSchema
                                    (string)methodArguments[2],  //procedureName
                                    (string)methodArguments[3]); //columnName
                                break;
 
                            case ODBC32.SQL_API.SQLSTATISTICS:
                                retcode = stmt.Statistics((string)methodArguments[0],  //TableCatalog
                                    (string)methodArguments[1],  //TableSchema
                                    (string)methodArguments[2],  //TableName
                                    (Int16)methodArguments[3],   //IndexTrpe
                                    (Int16)methodArguments[4]);  //Accuracy
                                break;
 
                            case ODBC32.SQL_API.SQLGETTYPEINFO:
                                retcode = stmt.GetTypeInfo((Int16)methodArguments[0]);  //SQL Type
                                break;
 
                            default:
                                // this should NEVER happen
                                Debug.Assert(false, "ExecuteReaderObjectcalled with unsupported ODBC API method.");
                                throw ADP.InvalidOperation(method.ToString());
                        }
 
                        //Note: Execute will return NO_DATA for Update/Delete non-row returning queries
                        if((ODBC32.RetCode.SUCCESS != retcode) && (ODBC32.RetCode.NO_DATA != retcode)) {
                            _connection.HandleError(stmt, retcode);
                        }
                    } // end SchemaOnly
                }
                finally {
                    if(mustRelease) {
                        parameterBuffer.DangerousRelease();
                    }
                }
 
                this.weakDataReaderReference = new WeakReference(localReader);
 
                // XXXCommand.Execute should position reader on first row returning result
                // any exceptions in the initial non-row returning results should be thrown
                // from from ExecuteXXX not the DataReader
                if(!localReader.IsBehavior(CommandBehavior.SchemaOnly)) {
                    localReader.FirstResult();
                }
                cmdState = ConnectionState.Fetching;
            }
            finally {
                if(ConnectionState.Fetching != cmdState) {
                    if(null != localReader) {
                        // clear bindings so we don't grab output parameters on a failed execute
                        if(null != _parameterCollection) {
                            _parameterCollection.ClearBindings();
                        }
                        ((IDisposable)localReader).Dispose();
                    }
                    if(ConnectionState.Closed != cmdState) {
                        cmdState = ConnectionState.Closed;
                    }
                }
            }
            return localReader;
        }
 
        override public object ExecuteScalar() {
            OdbcConnection.ExecutePermission.Demand();
 
            object value = null;
            using(IDataReader reader = ExecuteReaderObject(0, ADP.ExecuteScalar, false)) {
                if (reader.Read() && (0 < reader.FieldCount)) {
                    value = reader.GetValue(0);
                }
                reader.Close();
            }
            return value;
        }
 
        internal string GetDiagSqlState() {
            return _cmdWrapper.GetDiagSqlState();
        }
 
        private void PropertyChanging() {
            _isPrepared = false;
        }
 
        // Prepare
        //
        // if the CommandType property is set to TableDirect Prepare does nothing.
        // if the CommandType property is set to StoredProcedure Prepare should succeed but result
        // in a no-op
        //
        // throw InvalidOperationException
        // if the connection is not set
        // if the connection is not open
        //
        override public void Prepare() {
            OdbcConnection.ExecutePermission.Demand();
            ODBC32.RetCode retcode;
 
            ValidateOpenConnection(ADP.Prepare);
 
            if (0 != (ConnectionState.Fetching & _connection.InternalState)) {
                throw ADP.OpenReaderExists();
            }
 
            if (CommandType == CommandType.TableDirect) {
                return; // do nothing
            }
 
            DisposeDeadDataReader();
            GetStatementHandle();
 
            OdbcStatementHandle stmt = _cmdWrapper.StatementHandle;
 
            retcode = stmt.Prepare(CommandText);
 
 
            if (ODBC32.RetCode.SUCCESS != retcode) {
                _connection.HandleError(stmt, retcode);
            }
            _isPrepared = true;
        }
 
 
 
        void TrySetStatementAttribute (OdbcStatementHandle stmt, ODBC32.SQL_ATTR stmtAttribute, IntPtr value) {
 
            ODBC32.RetCode retcode = stmt.SetStatementAttribute(
                stmtAttribute,
                value,
                ODBC32.SQL_IS.UINTEGER);
 
            if (retcode == ODBC32.RetCode.ERROR) {
 
                string sqlState;
                stmt.GetDiagnosticField(out sqlState);
 
                if ((sqlState == "HYC00") || (sqlState == "HY092")) {
                    Connection.FlagUnsupportedStmtAttr(stmtAttribute);
                }
                else {
                    // now what? Should we throw?
                }
            }
        }
 
        private void ValidateOpenConnection(string methodName) {
            // see if we have a connection
            OdbcConnection connection = Connection;
 
            if (null == connection) {
                throw ADP.ConnectionRequired(methodName);
            }
 
            // must have an open and available connection
            ConnectionState state = connection.State;
 
            if (ConnectionState.Open != state) {
                throw ADP.OpenConnectionRequired(methodName, state);
            }
        }
 
        private void ValidateConnectionAndTransaction(string method) {
            if (null == _connection) {
                throw ADP.ConnectionRequired(method);
            }
            _transaction = _connection.SetStateExecuting(method, Transaction);
            cmdState = ConnectionState.Executing;
        }
 
    }
    sealed internal class CMDWrapper {
 
        private OdbcStatementHandle _stmt;                  // hStmt
        private OdbcStatementHandle _keyinfostmt;           // hStmt for keyinfo
 
        internal OdbcDescriptorHandle  _hdesc;              // hDesc
 
        internal CNativeBuffer _nativeParameterBuffer;      // Native memory for internal memory management
        // (Performance optimization)
 
        internal CNativeBuffer      _dataReaderBuf;         // Reusable DataReader buffer
 
        private readonly OdbcConnection _connection;        // Connection
        private bool                _canceling;             // true if the command is canceling
        internal bool               _hasBoundColumns;
        internal bool               _ssKeyInfoModeOn;       // tells us if the SqlServer specific options are on
        internal bool               _ssKeyInfoModeOff;      // a tri-state value would be much better ...
 
        internal CMDWrapper (OdbcConnection connection) {
            _connection = connection;
        }
 
        internal bool Canceling {
            get {
                return _canceling;
            }
            set {
                _canceling = value;
            }
        }
 
        internal OdbcConnection Connection {
            get {
                return _connection;
            }
        }
 
        internal bool HasBoundColumns {
//            get {
//                return _hasBoundColumns;
//            }
            set {
                _hasBoundColumns = value;
            }
        }
 
        internal OdbcStatementHandle StatementHandle {
            get { return _stmt; }
        }
 
        internal OdbcStatementHandle KeyInfoStatement {
            get {
                return _keyinfostmt;
            }
        }
 
        internal void CreateKeyInfoStatementHandle() {
            DisposeKeyInfoStatementHandle();
            _keyinfostmt =  _connection.CreateStatementHandle();
        }
 
        internal void CreateStatementHandle() {
            DisposeStatementHandle();
            _stmt =  _connection.CreateStatementHandle();
        }
 
        internal void Dispose() {
            if (null != _dataReaderBuf) {
                _dataReaderBuf.Dispose();
                _dataReaderBuf = null;
            }
            DisposeStatementHandle();
 
            CNativeBuffer buffer = _nativeParameterBuffer;
            _nativeParameterBuffer = null;
            if (null != buffer) {
                buffer.Dispose();
            }
            _ssKeyInfoModeOn = false;
            _ssKeyInfoModeOff = false;
        }
 
        private void DisposeDescriptorHandle() {
            OdbcDescriptorHandle handle = _hdesc;
            if (null != handle) {
                _hdesc = null;
                handle.Dispose();
            }
        }
        internal void DisposeStatementHandle() {
            DisposeKeyInfoStatementHandle();
            DisposeDescriptorHandle();
 
            OdbcStatementHandle handle = _stmt;
            if (null != handle) {
                _stmt = null;
                handle.Dispose();
            }
        }
 
        internal void DisposeKeyInfoStatementHandle() {
            OdbcStatementHandle handle = _keyinfostmt;
            if (null != handle) {
                _keyinfostmt = null;
                handle.Dispose();
            }
        }
 
        internal void FreeStatementHandle(ODBC32.STMT stmt) {
            DisposeDescriptorHandle();
 
            OdbcStatementHandle handle = _stmt;
            if (null != handle) {
                try {
                    ODBC32.RetCode retcode;
                    retcode = handle.FreeStatement(stmt);
                    StatementErrorHandler(retcode);
                }
                catch (Exception e) {
                    // 
                    if (ADP.IsCatchableExceptionType(e)) {
                        _stmt = null;
                        handle.Dispose();
                    }
 
                    throw;
                }
            }
        }
 
        internal void FreeKeyInfoStatementHandle(ODBC32.STMT stmt) {
            OdbcStatementHandle handle = _keyinfostmt;
            if (null != handle) {
                try {
                    handle.FreeStatement(stmt);
                }
                catch (Exception e) {
                    // 
                    if (ADP.IsCatchableExceptionType(e)) {
                        _keyinfostmt = null;
                        handle.Dispose();
                    }
 
                    throw;
                }
            }
        }
 
        // Get the Descriptor Handle for the current statement
        //
        internal OdbcDescriptorHandle GetDescriptorHandle(ODBC32.SQL_ATTR attribute) {
            OdbcDescriptorHandle hdesc = _hdesc;
            if (null == _hdesc) {
                _hdesc = hdesc = new OdbcDescriptorHandle(_stmt, attribute);
            }
            return hdesc;
        }
 
        internal string GetDiagSqlState () {
            string sqlstate;
            _stmt.GetDiagnosticField(out sqlstate);
            return sqlstate;
        }
 
        internal void StatementErrorHandler(ODBC32.RetCode retcode) {
            switch(retcode) {
            case ODBC32.RetCode.SUCCESS:
            case ODBC32.RetCode.SUCCESS_WITH_INFO:
                _connection.HandleErrorNoThrow(_stmt, retcode);
                break;
            default:
                throw _connection.HandleErrorNoThrow(_stmt, retcode);
            }
        }
 
        internal void UnbindStmtColumns() {
            if (_hasBoundColumns) {
                FreeStatementHandle(ODBC32.STMT.UNBIND);
                _hasBoundColumns = false;
            }
        }
    }
}