OdbcDataReader.cs source code in C# .NET

Source code for the .NET framework in C#

                        

Code:

/ 4.0 / 4.0 / untmp / DEVDIV_TFS / Dev10 / Releases / RTMRel / ndp / fx / src / Data / System / Data / Odbc / OdbcDataReader.cs / 1305376 / OdbcDataReader.cs

                            //------------------------------------------------------------------------------ 
// 
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// 
// [....] 
// [....]
//----------------------------------------------------------------------------- 
 
using System;
using System.Collections; 
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Data.Common; 
using System.Data.ProviderBase;
using System.Diagnostics; 
using System.Globalization; 
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; 
using System.Text;              // StringBuilder
using System.Threading;

namespace System.Data.Odbc 
{
    public sealed class OdbcDataReader : DbDataReader { 
 
        private OdbcCommand command;
 
        private int recordAffected = -1;
        private FieldNameLookup _fieldNameLookup;

        private DbCache dataCache; 
        private enum HasRowsStatus {
            DontKnow    = 0, 
            HasRows     = 1, 
            HasNoRows   = 2,
        } 
        private HasRowsStatus _hasRows = HasRowsStatus.DontKnow;
        private bool _isClosed;
        private bool _isRead;
        private bool _isValidResult; 
        private bool _noMoreResults;
        private bool _noMoreRows; 
        private bool _skipReadOnce; 
        private int _hiddenColumns;                 // number of hidden columns
        private CommandBehavior     _commandBehavior; 

        // track current row and column, will be set on the first Fetch call
        private int _row = -1;
        private int _column = -1; 

        // used to track position in field for sucessive reads in case of Sequential Access 
        private long _sequentialBytesRead; 

        private static int          _objectTypeCount; // Bid counter 
        internal readonly int       ObjectID = System.Threading.Interlocked.Increment(ref _objectTypeCount);

        // the statement handle here is just a copy of the statement handle owned by the command
        // the DataReader must not free the statement handle. this is done by the command 
        //
 
        private MetaData[] metadata; 
        private DataTable schemaTable; // MDAC 68336
        private string _cmdText;    // get a copy in case the command text on the command is changed ... 
        private CMDWrapper _cmdWrapper;

        internal OdbcDataReader(OdbcCommand command, CMDWrapper cmdWrapper, CommandBehavior commandbehavior) {
            OdbcConnection.VerifyExecutePermission(); 

            Debug.Assert(command != null, "Command null on OdbcDataReader ctor"); 
            this.command = command; 
            _commandBehavior = commandbehavior;
            _cmdText = command.CommandText;    // get a copy in case the command text on the command is changed ... 
            _cmdWrapper = cmdWrapper;
        }

        private CNativeBuffer Buffer { 
            get {
                CNativeBuffer value = _cmdWrapper._dataReaderBuf; 
                if (null == value) { 
                    Debug.Assert(false, "object is disposed");
                    throw new ObjectDisposedException(GetType().Name); 
                }
                return value;
            }
        } 

        private OdbcConnection Connection { 
            get { 
                if (null != _cmdWrapper) {
                    return _cmdWrapper.Connection; 
                }
                else {
                    return null;
                } 
            }
        } 
 
        internal OdbcCommand Command {
            get { 
                return this.command;
            }
            set {
                this.command=value; 
            }
        } 
 
        private OdbcStatementHandle StatementHandle {
            get { 
                return _cmdWrapper.StatementHandle;
            }
        }
 
        private OdbcStatementHandle KeyInfoStatementHandle {
            get { return _cmdWrapper.KeyInfoStatement; } 
        } 

        internal bool IsBehavior(CommandBehavior behavior) { 
            return IsCommandBehavior(behavior);
        }

        internal bool IsCancelingCommand { 
            get {
                if (this.command != null) { 
                    return command.Canceling; 
                }
                return false; 
            }
        }

        internal bool IsNonCancelingCommand { 
            get {
                if (this.command != null) { 
                    return !command.Canceling; 
                }
                return false; 
            }
        }

        override public int Depth { 
            get {
                if (IsClosed) { // MDAC 63669 
                    throw ADP.DataReaderClosed("Depth"); 
                }
                return 0; 
            }
        }

        override public int FieldCount  { 
            get {
                if (IsClosed) { // MDAC 63669 
                    throw ADP.DataReaderClosed("FieldCount"); 
                }
                if (_noMoreResults) {   // MDAC 93325 
                    return 0;
                }
                if (null == this.dataCache) {
                    Int16 cColsAffected; 
                    ODBC32.RetCode retcode = this.FieldCountNoThrow(out cColsAffected);
                    if(retcode != ODBC32.RetCode.SUCCESS) { 
                        Connection.HandleError(StatementHandle, retcode); 
                    }
                } 
                return ((null != this.dataCache) ? this.dataCache._count : 0);
            }
        }
 
        // HasRows
        // 
        // Use to detect wheter there are one ore more rows in the result without going through Read 
        // May be called at any time
        // Basically it calls Read and sets a flag so that the actual Read call will be skipped once 
        //
        override public bool HasRows {
            get {
                if (IsClosed) { 
                    throw ADP.DataReaderClosed("HasRows");
                } 
                if (_hasRows == HasRowsStatus.DontKnow){ 
                    Read();                     //
                    _skipReadOnce = true;       // need to skip Read once because we just did it 
                }
                return (_hasRows == HasRowsStatus.HasRows);
            }
        } 

        internal ODBC32.RetCode FieldCountNoThrow(out Int16 cColsAffected) { 
            if (IsCancelingCommand) { 
                cColsAffected = 0;
                return ODBC32.RetCode.ERROR; 
            }

            ODBC32.RetCode retcode = StatementHandle.NumberOfResultColumns(out cColsAffected);
            if (retcode == ODBC32.RetCode.SUCCESS) { 
                _hiddenColumns = 0;
                if (IsCommandBehavior(CommandBehavior.KeyInfo)) { 
                    // we need to search for the first hidden column 
                    //
                    if (!Connection.ProviderInfo.NoSqlSoptSSNoBrowseTable && !Connection.ProviderInfo.NoSqlSoptSSHiddenColumns) { 
                        for (int i=0; i 0) {
                // case 2 - fixed-size types have _bufferSize set to positive value 
                // call GetValue(i) as before the fix of SQLBUVSTS01:110664
                // note, when SQLGetData is called for a fixed length type, the buffer size is always ignored and 
                // the data will always be read off the wire 
                return Convert.IsDBNull(GetValue(i));
            } 
            else {
                // case 3 - the data has variable-length type, read zero-length data to query for null
                // QueryFieldInfo will return false only if the object cached as DbNull
                // QueryFieldInfo will put DbNull in cache only if the SQLGetData returns SQL_NULL_DATA, otherwise it does not change it 
                int dummy;
                return !QueryFieldInfo(i, typeMap._sql_c, out dummy); 
            } 
        }
 
        override public Byte GetByte(int i) {
            return (Byte)internalGetByte(i);
        }
 
        private object internalGetByte(int i) {
            if (_isRead) { 
                if(this.dataCache.AccessIndex(i) == null) { 
                    if (GetData(i, ODBC32.SQL_C.UTINYINT)) {
                        this.dataCache[i] = Buffer.ReadByte(0); 
                    }
                }
                return this.dataCache[i];
            } 
            throw ADP.DataReaderNoData();
        } 
 
        override public Char GetChar(int i) {
            return (Char)internalGetChar(i); 
        }
        private object internalGetChar(int i) {
            if (_isRead) {
                if(this.dataCache.AccessIndex(i) == null) { 
                    if (GetData(i, ODBC32.SQL_C.WCHAR)) {
                        this.dataCache[i] = Buffer.ReadChar(0); 
                    } 
                }
                return this.dataCache[i]; 
            }
            throw ADP.DataReaderNoData();
        }
 
        override public Int16 GetInt16(int i) {
            return (Int16)internalGetInt16(i); 
        } 
        private object internalGetInt16(int i) {
            if (_isRead) { 
                if(this.dataCache.AccessIndex(i) == null) {
                    if (GetData(i, ODBC32.SQL_C.SSHORT)) {
                        this.dataCache[i] = Buffer.ReadInt16(0);
                    } 
                }
                return this.dataCache[i]; 
            } 
            throw ADP.DataReaderNoData();
        } 

        override public Int32 GetInt32(int i) {
            return (Int32)internalGetInt32(i);
       } 
        private object internalGetInt32(int i){
            if (_isRead) { 
                if(this.dataCache.AccessIndex(i) == null) { 
                    if(GetData(i, ODBC32.SQL_C.SLONG)){
                        this.dataCache[i] = Buffer.ReadInt32(0); 
                    }
                }
                return this.dataCache[i];
            } 
            throw ADP.DataReaderNoData();
        } 
 
        override public Int64 GetInt64(int i) {
            return (Int64)internalGetInt64(i); 
        }
        // --------------------------------------------------------------------------------------------- //
        // internal internalGetInt64
        // ------------------------- 
        // Get Value of type SQL_BIGINT
        // Since the driver refused to accept the type SQL_BIGINT we read that 
        // as SQL_C_WCHAR and convert it back to the Int64 data type 
        //
        private object internalGetInt64(int i) { 
            if (_isRead) {
                if(this.dataCache.AccessIndex(i) == null) {
                    if (GetData(i, ODBC32.SQL_C.WCHAR)) {
                        string value = (string)Buffer.MarshalToManaged(0, ODBC32.SQL_C.WCHAR, ODBC32.SQL_NTS); 
                        this.dataCache[i] = Int64.Parse(value, CultureInfo.InvariantCulture);
                    } 
                } 
                return this.dataCache[i];
            } 
            throw ADP.DataReaderNoData();
        }

        override public bool GetBoolean(int i) { 
            return (bool) internalGetBoolean(i);
        } 
        private object internalGetBoolean(int i) { 
            if (_isRead) {
                if(this.dataCache.AccessIndex(i) == null) { 
                    if(GetData(i, ODBC32.SQL_C.BIT)){
                        this.dataCache[i] = Buffer.MarshalToManaged(0, ODBC32.SQL_C.BIT, -1);
                    }
                } 
                return this.dataCache[i];
            } 
            throw ADP.DataReaderNoData(); 
        }
 
        override public float GetFloat(int i) {
            return (float)internalGetFloat(i);
        }
        private object internalGetFloat(int i) { 
            if (_isRead) {
                if(this.dataCache.AccessIndex(i) == null) { 
                    if(GetData(i, ODBC32.SQL_C.REAL)){ 
                        this.dataCache[i] = Buffer.ReadSingle(0);
                    } 
                }
                return this.dataCache[i];
            }
            throw ADP.DataReaderNoData(); 
        }
 
        public DateTime GetDate(int i) { 
            return (DateTime)internalGetDate(i);
        } 

        private object internalGetDate(int i) {
            if (_isRead) {
                if(this.dataCache.AccessIndex(i) == null) { 
                    if(GetData(i, ODBC32.SQL_C.TYPE_DATE)){
                        this.dataCache[i] = Buffer.MarshalToManaged(0, ODBC32.SQL_C.TYPE_DATE, -1); 
                    } 
                }
                return this.dataCache[i]; 
            }
            throw ADP.DataReaderNoData();
        }
 
        override public DateTime GetDateTime(int i) {
            return (DateTime)internalGetDateTime(i); 
        } 

        private object internalGetDateTime(int i) { 
            if (_isRead) {
                if(this.dataCache.AccessIndex(i) == null) {
                    if(GetData(i, ODBC32.SQL_C.TYPE_TIMESTAMP)){
                        this.dataCache[i] = Buffer.MarshalToManaged(0, ODBC32.SQL_C.TYPE_TIMESTAMP, -1); 
                    }
                } 
                return this.dataCache[i]; 
            }
            throw ADP.DataReaderNoData(); 
        }

        override public decimal GetDecimal(int i) {
            return (decimal)internalGetDecimal(i); 
        }
 
        // --------------------------------------------------------------------------------------------- // 
        // internal GetDecimal
        // ------------------- 
        // Get Value of type SQL_DECIMAL or SQL_NUMERIC
        // Due to provider incompatibilities with SQL_DECIMAL or SQL_NUMERIC types we always read the value
        // as SQL_C_WCHAR and convert it back to the Decimal data type
        // 
        private object internalGetDecimal(int i) {
            if (_isRead) { 
                if(this.dataCache.AccessIndex(i) == null) { 
                    if(GetData(i, ODBC32.SQL_C.WCHAR )){
                        string s = null; 
                        try {
                            s = (string)Buffer.MarshalToManaged(0, ODBC32.SQL_C.WCHAR, ODBC32.SQL_NTS);
                            this.dataCache[i] = Decimal.Parse(s, System.Globalization.CultureInfo.InvariantCulture);
                        } 
                        catch(OverflowException e) {
                            this.dataCache[i] = s; 
                            throw e; 
                        }
                    } 
                }
                return this.dataCache[i];
            }
            throw ADP.DataReaderNoData(); 
        }
 
        override public double GetDouble(int i) { 
            return (double)internalGetDouble(i);
        } 
        private object internalGetDouble(int i) {
            if (_isRead) {
                if(this.dataCache.AccessIndex(i) == null) {
                    if(GetData(i, ODBC32.SQL_C.DOUBLE)){ 
                        this.dataCache[i] = Buffer.ReadDouble(0);
                    } 
                } 
                return this.dataCache[i];
            } 
            throw ADP.DataReaderNoData();
        }

        override public Guid GetGuid(int i) { 
            return (Guid)internalGetGuid(i);
        } 
 
        private object internalGetGuid(int i) {
            if (_isRead) { 
                if(this.dataCache.AccessIndex(i) == null) {
                    if(GetData(i, ODBC32.SQL_C.GUID)){
                        this.dataCache[i] = Buffer.ReadGuid(0);
                    } 
                }
                return this.dataCache[i]; 
            } 
            throw ADP.DataReaderNoData();
        } 

        override public String GetString(int i) {
            return (String)internalGetString(i);
        } 

        private object internalGetString(int i) { 
            if(_isRead) { 
                if(this.dataCache.AccessIndex(i) == null) {
                    // Obtain _ALL_ the characters 
                    // Note: We can bind directly as WCHAR in ODBC and the DM will convert to and
                    // from ANSI if not supported by the driver.
                    //
 
                    // Note: The driver always returns the raw length of the data, minus the
                    // terminator.  This means that our buffer length always includes the terminator 
                    // charactor, so when determining which characters to count, and if more data 
                    // exists, it should not take the terminator into effect.
                    // 
                    CNativeBuffer buffer = Buffer;
                    // that does not make sense unless we expect four byte terminators
                    int cbMaxData = buffer.Length - 4;
 
                    // The first time GetData returns the true length (so we have to min it).
                    // We also pass in the true length to the marshal function since there could be 
                    // embedded nulls 
                    //
                    int lengthOrIndicator; 
                    if (GetData(i, ODBC32.SQL_C.WCHAR, buffer.Length - 2, out lengthOrIndicator)) {
                        // RFC 50002644: we do not expect negative values from GetData call except SQL_NO_TOTAL(== -4)
                        // note that in general you should not trust third-party providers so such asserts should be
                        // followed by exception. I did not add it now to avoid breaking change 
                        Debug.Assert(lengthOrIndicator >= 0 || lengthOrIndicator == ODBC32.SQL_NO_TOTAL, "unexpected lengthOrIndicator value");
 
                        if (lengthOrIndicator <= cbMaxData && (ODBC32.SQL_NO_TOTAL != lengthOrIndicator)) { 
                            // all data read? good! Directly marshal to a string and we're done
                            // 
                            string strdata = buffer.PtrToStringUni(0, Math.Min(lengthOrIndicator, cbMaxData) / 2);
                            this.dataCache[i] = strdata;
                            return strdata;
                        } 

                        // We need to chunk the data 
                        // Char[] buffer for the junks 
                        // StringBuilder for the actual string
                        // 
                        Char[] rgChars = new Char[cbMaxData / 2];

                        // RFC 50002644: negative value cannot be used for capacity.
                        // in case of SQL_NO_TOTAL, set the capacity to cbMaxData, StringBuilder will automatically reallocate 
                        // its internal buffer when appending more data
                        int cbBuilderInitialCapacity = (lengthOrIndicator == ODBC32.SQL_NO_TOTAL) ? cbMaxData : lengthOrIndicator; 
                        StringBuilder builder = new StringBuilder(cbBuilderInitialCapacity / 2); 

                        bool gotData; 
                        int cchJunk;
                        int cbActual = cbMaxData;
                        int cbMissing = (ODBC32.SQL_NO_TOTAL == lengthOrIndicator) ? -1 : lengthOrIndicator - cbActual;
 
                        do {
                            cchJunk = cbActual / 2; 
                            buffer.ReadChars(0, rgChars, 0, cchJunk); 
                            builder.Append(rgChars, 0, cchJunk);
 
                            if(0 == cbMissing) {
                                break;  // done
                            }
 
                            gotData = GetData(i, ODBC32.SQL_C.WCHAR, buffer.Length - 2, out lengthOrIndicator);
                            // RFC 50002644: we do not expect negative values from GetData call except SQL_NO_TOTAL(== -4) 
                            // note that in general you should not trust third-party providers so such asserts should be 
                            // followed by exception. I did not add it now to avoid breaking change
                            Debug.Assert(lengthOrIndicator >= 0 || lengthOrIndicator == ODBC32.SQL_NO_TOTAL, "unexpected lengthOrIndicator value"); 

                            if (ODBC32.SQL_NO_TOTAL != lengthOrIndicator) {
                                cbActual = Math.Min(lengthOrIndicator, cbMaxData);
                                if(0 < cbMissing) { 
                                    cbMissing -= cbActual;
                                } 
                                else { 
                                    // it is a last call to SqlGetData that started with SQL_NO_TOTAL
                                    // the last call to SqlGetData must always return the length of the 
                                    // data, not zero or SqlNoTotal (see Odbc Programmers Reference)
                                    Debug.Assert(cbMissing == -1 && lengthOrIndicator <= cbMaxData);
                                    cbMissing = 0;
                                } 
                            }
                        } 
                        while(gotData); 

                        this.dataCache[i] = builder.ToString(); 
                    }
                }
                return this.dataCache[i];
            } 
            throw ADP.DataReaderNoData();
        } 
 
        public TimeSpan GetTime(int i) {
            return (TimeSpan)internalGetTime(i); 
        }

        private object internalGetTime(int i) {
            if (_isRead) { 
                if(this.dataCache.AccessIndex(i) == null) {
                    if(GetData(i, ODBC32.SQL_C.TYPE_TIME)){ 
                        this.dataCache[i] = Buffer.MarshalToManaged(0, ODBC32.SQL_C.TYPE_TIME, -1); 
                    }
                } 
                return this.dataCache[i];
            }
            throw ADP.DataReaderNoData();
        } 

        private void SetCurrentRowColumnInfo(int row, int column) 
        { 
            if (_row != row || _column != column)
            { 
                _row = row;
                _column = column;

                // reset the blob reader when moved to new column 
                _sequentialBytesRead = 0;
            } 
        } 

        override public long GetBytes(int i, long dataIndex, byte[] buffer, int bufferIndex, int length) { 
            return GetBytesOrChars(i, dataIndex, buffer, false /* bytes buffer */, bufferIndex, length);
        }
        override public long GetChars(int i, long dataIndex, char[] buffer, int bufferIndex, int length) {
            return GetBytesOrChars(i, dataIndex, buffer, true /* chars buffer */, bufferIndex, length); 
        }
 
        // unify the implementation of GetChars and GetBytes to prevent code duplicate 
        private long GetBytesOrChars(int i, long dataIndex, Array buffer, bool isCharsBuffer, int bufferIndex, int length) {
            if (IsClosed) { 
                throw ADP.DataReaderNoData();
            }
            if(!_isRead) {
                throw ADP.DataReaderNoData(); 
            }
            if(dataIndex < 0 ) { 
                // test only for negative value here, Int32.MaxValue will be validated only in case of random access 
                throw ADP.ArgumentOutOfRange("dataIndex");
            } 
            if(bufferIndex < 0) {
                throw ADP.ArgumentOutOfRange("bufferIndex");
            }
            if(length < 0) { 
                throw ADP.ArgumentOutOfRange("length");
            } 
 
            string originalMethodName = isCharsBuffer ? "GetChars" : "GetBytes";
 
            // row/column info will be reset only if changed
            SetCurrentRowColumnInfo(_row, i);

            // Possible cases: 
            // 1. random access, user asks for the value first time: bring it and cache the value
            // 2. random access, user already queried the value: use the cache 
            // 3. sequential access, cache exists: user already read this value using different method (it becomes cached) 
            //                       use the cache - preserve the original behavior to minimize regression risk
            // 4. sequential access, no cache: (fixed now) user reads the bytes/chars in sequential order (no cache) 

            object cachedObj = null;                 // The cached object (if there is one)

            // Get cached object, ensure the correct type using explicit cast, to preserve same behavior as before 
            if (isCharsBuffer)
                cachedObj = (string)this.dataCache[i]; 
            else 
                cachedObj = (byte[])this.dataCache[i];
 
            bool isRandomAccess = !IsCommandBehavior(CommandBehavior.SequentialAccess);

            if (isRandomAccess || (cachedObj != null))
            { 
                // random access (cases 1 or 2) and sequential access with cache (case 3)
                // preserve the original behavior as before the fix 
 
                if (Int32.MaxValue < dataIndex)
                { 
                    // indices greater than allocable size are not supported in random access
                    // (negative value is already tested in the beginning of ths function)
                    throw ADP.ArgumentOutOfRange("dataIndex");
                } 

                if(cachedObj == null) { 
                    // case 1, get the value and cache it 
                    // internalGetString/internalGetBytes will get the entire value and cache it,
                    // since we are not in SequentialAccess (isRandomAccess is true), it is OK 

                    if (isCharsBuffer) {
                        cachedObj = (String)internalGetString(i);
                        Debug.Assert((cachedObj != null), "internalGetString should always return non-null or raise exception"); 
                    }
                    else { 
                        cachedObj = (byte[])internalGetBytes(i); 
                        Debug.Assert((cachedObj != null), "internalGetBytes should always return non-null or raise exception");
                    } 

                    // continue to case 2
                }
 
                // after this point the value is cached (case 2 or 3)
                // if it is DbNull, cast exception will be raised (same as before the 110664 fix) 
                int cachedObjectLength = isCharsBuffer ? ((string)cachedObj).Length : ((byte[])cachedObj).Length; 

                // the user can ask for the length of the field by passing in a null pointer for the buffer 
                if (buffer == null) {
                    // return the length if that's all what user needs
                    return cachedObjectLength;
                } 

                // user asks for bytes 
 
                if (length == 0) {
                    return 0;   // Nothing to do ... 
                }

                if (dataIndex >= cachedObjectLength)
                { 
                    // no more bytes to read
                    // see also MDAC bug 73298 
                    return 0; 
                }
 
                int lengthFromDataIndex = cachedObjectLength - (int)dataIndex;
                int lengthOfCopy = Math.Min(lengthFromDataIndex, length);

                // silently reduce the length to avoid regression from EVERETT 
                lengthOfCopy = Math.Min(lengthOfCopy, buffer.Length - bufferIndex);
                if (lengthOfCopy <= 0) return 0;                    // MDAC Bug 73298 
 
                if (isCharsBuffer)
                    ((string)cachedObj).CopyTo((int)dataIndex, (char[])buffer, bufferIndex, lengthOfCopy); 
                else
                    Array.Copy((byte[])cachedObj, (int)dataIndex, (byte[])buffer, bufferIndex, lengthOfCopy);

                return lengthOfCopy; 
            }
            else { 
                // sequential access, case 4 

                // SQLBU:532243 -- For SequentialAccess we need to read a chunk of 
                // data and not cache it.
                // Note: If the object was previous cached (see case 3 above), the function will go thru 'if' path, to minimize
                // regressions
 
                // the user can ask for the length of the field by passing in a null pointer for the buffer
                if (buffer == null) { 
                    // Get len. of remaining data from provider 
                    ODBC32.SQL_C sqlctype;
                    int cbLengthOrIndicator; 
                    bool isDbNull;

                    sqlctype = isCharsBuffer ? ODBC32.SQL_C.WCHAR : ODBC32.SQL_C.BINARY;
                    isDbNull = !QueryFieldInfo(i, sqlctype, out cbLengthOrIndicator); 

                    if (isDbNull) { 
                        // SQLBU 266054: 

                        // GetChars: 
                        //   in Orcas RTM: GetChars has always raised InvalidCastException.
                        //   in Orcas SP1: GetChars returned 0 if DbNull is not cached yet and InvalidCastException if it is in cache (from case 3).
                        //   Managed Providers team has decided to fix the GetChars behavior and raise InvalidCastException, as it was in RTM
                        //   Reason: returing 0 is wrong behavior since it conflicts with return value in case of empty data 

                        // GetBytes: 
                        //   In Orcas RTM: GetBytes(null buffer) returned -1 for null value if DbNull is not cached yet. 
                        //   But, after calling IsDBNull, GetBytes(null) raised InvalidCastException.
                        //   In Orcas SP1: GetBytes always raises InvalidCastException for null value. 
                        //   Managed Providers team has decided to keep the behavior of RTM for this case to fix the RTM's breaking change.
                        //   Reason: while -1 is wrong behavior, people might be already relying on it, so we should not be changing it.
                        //   Note: this will happen only on the first call to GetBytes(with null buffer).
                        //   If IsDbNull has already been called before or for second call to query for size, 
                        //   DBNull is cached and GetBytes raises InvalidCastException in case 3 (see the cases above in this method).
 
                        if (isCharsBuffer) { 
                            throw ADP.InvalidCast();
                        } 
                        else {
                            return -1;
                        }
                    } 
                    else {
                        // the value is not null 
 
                        // SQLBU 266054:
                        // If cbLengthOrIndicator is SQL_NO_TOTAL (-4), this call returns -4 or -2, depending on the type (GetChars=>-2, GetBytes=>-4). 
                        // This is the Orcas RTM and SP1 behavior, changing this would be a breaking change.
                        // SQL_NO_TOTAL means that the driver does not know what is the remained lenght of the data, so we cannot really guess the value here.
                        // Reason: while returning different negative values depending on the type seems inconsistent,
                        // this is what we did in Orcas RTM and SP1 and user code might rely on this behavior => changing it would be a breaking change. 
                        if (isCharsBuffer) {
                            return cbLengthOrIndicator / 2; // return length in wide characters or -2 if driver returns SQL_NO_TOTAL 
                        } 
                        else {
                            return cbLengthOrIndicator; // return length in bytes or -4 if driver returns SQL_NO_TOTAL 
                        }
                    }
                }
                else { 
                    // buffer != null, read the data
 
                    // check if user tries to read data that was already received 
                    // if yes, this violates 'sequential access'
                    if ((isCharsBuffer && dataIndex < _sequentialBytesRead / 2) || 
                        (!isCharsBuffer && dataIndex < _sequentialBytesRead)) {
                        // backward reading is not allowed in sequential access
                        throw ADP.NonSeqByteAccess(
                            dataIndex, 
                            _sequentialBytesRead,
                            originalMethodName 
                            ); 
                    }
 
                    // note that it is actually not possible to read with an offset (dataIndex)
                    // therefore, adjust the data index relative to number of bytes already read
                    if (isCharsBuffer)
                        dataIndex -= _sequentialBytesRead / 2; 
                    else
                        dataIndex -= _sequentialBytesRead; 
 
                    if (dataIndex > 0)
                    { 
                        // user asked to skip bytes - it is OK, even in case of sequential access
                        // forward the stream by dataIndex bytes/chars
                        int charsOrBytesRead = readBytesOrCharsSequentialAccess(i, null, isCharsBuffer, 0, dataIndex);
                        if (charsOrBytesRead < dataIndex) 
                        {
                            // the stream ended before we forwarded to the requested index, stop now 
                            return 0; 
                        }
                    } 

                    // ODBC driver now points to the correct position, start filling the user buffer from now

                    // Make sure we don't overflow the user provided buffer 
                    // Note: SqlDataReader will raise exception if there is no enough room for length requested.
                    // In case of ODBC, I decided to keep this consistent with random access after consulting with PM. 
                    length = Math.Min(length, buffer.Length - bufferIndex); 
                    if (length <= 0) {
                        // SQLBU 266054: 
                        // if the data is null, the ideal behavior here is to raise InvalidCastException. But,
                        // * GetBytes returned 0 in Orcas RTM and SP1, continue to do so to avoid breaking change from Orcas RTM and SP1.
                        // * GetChars raised exception in RTM, and returned 0 in SP1: we decided to revert back to the RTM's behavior and raise InvalidCast
                        if (isCharsBuffer) { 
                            // for GetChars, ensure data is not null
                            // 2 bytes for '\0' termination, no data is actually read from the driver 
                            int cbLengthOrIndicator; 
                            bool isDbNull = !QueryFieldInfo(i, ODBC32.SQL_C.WCHAR, out cbLengthOrIndicator);
                            if (isDbNull) { 
                                throw ADP.InvalidCast();
                            }
                        }
                        // else - GetBytes - return now 
                        return 0;
                    } 
 
                    // fill the user's buffer
                    return readBytesOrCharsSequentialAccess(i, buffer, isCharsBuffer, bufferIndex, length); 
                }
            }
        }
 
        // fill the user's buffer (char[] or byte[], depending on isCharsBuffer)
        // if buffer is null, just skip the bytesOrCharsLength bytes or chars 
        private int readBytesOrCharsSequentialAccess(int i, Array buffer, bool isCharsBuffer, int bufferIndex, long bytesOrCharsLength) { 
            Debug.Assert(bufferIndex >= 0, "Negative buffer index");
            Debug.Assert(bytesOrCharsLength >= 0, "Negative number of bytes or chars to read"); 

            // validated by the caller
            Debug.Assert(buffer == null || bytesOrCharsLength <= (buffer.Length - bufferIndex), "Not enough space in user's buffer");
 
            int totalBytesOrCharsRead = 0;
            string originalMethodName = isCharsBuffer ? "GetChars" : "GetBytes"; 
 
            // we need length in bytes, b/c that is what SQLGetData expects
            long cbLength = (isCharsBuffer)? checked(bytesOrCharsLength * 2) : bytesOrCharsLength; 

            // continue reading from the driver until we fill the user's buffer or until no more data is available
            // the data is pumped first into the internal native buffer and after that copied into the user's one if buffer is not null
            CNativeBuffer internalNativeBuffer = this.Buffer; 

            // read the data in loop up to th user's length 
            // if the data size is less than requested or in case of error, the while loop will stop in the middle 
            while (cbLength > 0)
            { 
                // max data to be read, in bytes, not including null-terminator for WCHARs
                int cbReadMax;

                // read from the driver 
                bool isNotDbNull;
                int cbTotal; 
                // read either bytes or chars, depending on the method called 
                if (isCharsBuffer) {
                    // for WCHAR buffers, we need to leave space for null-terminator (2 bytes) 
                    // reserve 2 bytes for null-terminator and 2 bytes to prevent assert in GetData
                    // if SQL_NO_TOTAL is returned, this ammount is read from the wire, in bytes
                    cbReadMax = (int)Math.Min(cbLength, internalNativeBuffer.Length - 4);
 
                    // SQLGetData will always append it - we do not to copy it to user's buffer
                    isNotDbNull = GetData(i, ODBC32.SQL_C.WCHAR, cbReadMax + 2, out cbTotal); 
                } 
                else {
                    // reserve 2 bytes to prevent assert in GetData 
                    // when querying bytes, no need to reserve space for null
                    cbReadMax = (int)Math.Min(cbLength, internalNativeBuffer.Length - 2);

                    isNotDbNull = GetData(i, ODBC32.SQL_C.BINARY, cbReadMax, out cbTotal); 
                }
 
                if (!isNotDbNull) 
                {
                    // DbNull received, neither GetBytes nor GetChars should be used with DbNull value 
                    // two options
                    // 1. be consistent with SqlDataReader, raise SqlNullValueException
                    // 2. be consistent with other Get* methods of OdbcDataReader and raise InvalidCastException
                    // after consulting with Himanshu (PM), decided to go with option 2 (raise cast exception) 
                    throw ADP.InvalidCast();
                } 
 
                int cbRead; // will hold number of bytes read in this loop
                bool noDataRemained = false; 
                if (cbTotal == 0)
                {
                    // no bytes read, stop
                    break; 
                }
                else if (ODBC32.SQL_NO_TOTAL == cbTotal) 
                { 
                    // the driver has filled the internal buffer, but the length of remained data is still unknown
                    // we will continue looping until SQLGetData indicates the end of data or user buffer is fully filled 
                    cbRead = cbReadMax;
                }
                else
                { 
                    Debug.Assert((cbTotal > 0), "GetData returned negative value, which is not SQL_NO_TOTAL");
                    // GetData uses SQLGetData, which StrLen_or_IndPtr (cbTotal in our case) to the current buf + remained buf (if any) 
                    if (cbTotal > cbReadMax) 
                    {
                        // in this case the amount of bytes/chars read will be the max requested (and more bytes can be read) 
                        cbRead = cbReadMax;
                    }
                    else
                    { 
                        // SQLGetData read all the available data, no more remained
                        // continue processing this chunk and stop 
                        cbRead = cbTotal; 
                        noDataRemained = true;
                    } 
                }

                _sequentialBytesRead += cbRead;
 
                // update internal state and copy the data to user's buffer
                if (isCharsBuffer) 
                { 
                    int cchRead = cbRead / 2;
                    if (buffer != null) { 
                        internalNativeBuffer.ReadChars(0, (char[])buffer, bufferIndex, cchRead);
                        bufferIndex += cchRead;
                    }
                    totalBytesOrCharsRead += cchRead; 

                } 
                else 
                {
                    if (buffer != null) { 
                        internalNativeBuffer.ReadBytes(0, (byte[])buffer, bufferIndex, cbRead);
                        bufferIndex += cbRead;
                    }
                    totalBytesOrCharsRead += cbRead; 
                }
 
                cbLength -= cbRead; 

                // stop if no data remained 
                if (noDataRemained)
                    break;
            }
 
            return totalBytesOrCharsRead;
        } 
 
        private object internalGetBytes(int i) {
            if(this.dataCache.AccessIndex(i) == null) { 
                // Obtain _ALL_ the bytes...
                // The first time GetData returns the true length (so we have to min it).
                Byte[] rgBytes;
                int cbBufferLen = Buffer.Length - 4; 
                int cbActual;
                int cbOffset = 0; 
 
                if(GetData(i, ODBC32.SQL_C.BINARY, cbBufferLen, out cbActual)) {
                    CNativeBuffer buffer = Buffer; 

                    if(ODBC32.SQL_NO_TOTAL != cbActual) {
                        rgBytes = new Byte[cbActual];
                        Buffer.ReadBytes(0, rgBytes, cbOffset, Math.Min(cbActual, cbBufferLen)); 

                        // Chunking.  The data may be larger than our native buffer.  In which case 
                        // instead of growing the buffer (out of control), we will read in chunks to 
                        // reduce memory footprint size.
                        while(cbActual > cbBufferLen) { 
                            // The first time GetData returns the true length.  Then successive calls
                            // return the remaining data.
                            bool flag = GetData(i, ODBC32.SQL_C.BINARY, cbBufferLen, out cbActual);
                            Debug.Assert(flag, "internalGetBytes - unexpected invalid result inside if-block"); 

                            cbOffset += cbBufferLen; 
                            buffer.ReadBytes(0, rgBytes, cbOffset, Math.Min(cbActual, cbBufferLen)); 
                        }
                    } 
                    else {
                        List junkArray = new List();
                        int junkSize;
                        int totalSize = 0; 
                        do {
                            junkSize = (ODBC32.SQL_NO_TOTAL != cbActual) ? cbActual : cbBufferLen; 
                            rgBytes = new Byte[junkSize]; 
                            totalSize += junkSize;
                            buffer.ReadBytes(0, rgBytes, 0, junkSize); 
                            junkArray.Add(rgBytes);
                        }
                        while ((ODBC32.SQL_NO_TOTAL == cbActual) && GetData(i, ODBC32.SQL_C.BINARY, cbBufferLen, out cbActual));
 
                        rgBytes = new Byte[totalSize];
                        foreach(Byte[] junk in junkArray) { 
                            junk.CopyTo(rgBytes, cbOffset); 
                            cbOffset += junk.Length;
                        } 
                    }

                    // always update the cache
                    this.dataCache[i] = rgBytes; 
                }
            } 
            return this.dataCache[i]; 
        }
 
        // GetColAttribute
        // ---------------
        // [IN] iColumn   ColumnNumber
        // [IN] v3FieldId FieldIdentifier of the attribute for version3 drivers (>=3.0) 
        // [IN] v2FieldId FieldIdentifier of the attribute for version2 drivers (<3.0)
        // 
        // returns the value of the FieldIdentifier field of the column 
        // or -1 if the FieldIdentifier wasn't supported by the driver
        // 
        private SQLLEN GetColAttribute(int iColumn, ODBC32.SQL_DESC v3FieldId, ODBC32.SQL_COLUMN v2FieldId, ODBC32.HANDLER handler) {
            Int16   cchNameLength = 0;
            SQLLEN   numericAttribute;
            ODBC32.RetCode retcode; 

            // protect against dead connection, dead or canceling command. 
            if ((Connection == null) || _cmdWrapper.Canceling) { 
                return -1;
            } 

            //Ordinals are 1:base in odbc
            OdbcStatementHandle stmt = StatementHandle;
            if (Connection.IsV3Driver) { 
                retcode = stmt.ColumnAttribute(iColumn+1, (short)v3FieldId, Buffer, out cchNameLength, out numericAttribute);
            } 
            else if (v2FieldId != (ODBC32.SQL_COLUMN)(-1)) { 
                retcode = stmt.ColumnAttribute(iColumn+1, (short)v2FieldId, Buffer, out cchNameLength, out numericAttribute);
            } 
            else {
                return 0;
            }
            if (retcode != ODBC32.RetCode.SUCCESS) 
            {
                if (retcode == ODBC32.RetCode.ERROR) { 
                    if ("HY091" == Command.GetDiagSqlState()) { 
                        Connection.FlagUnsupportedColAttr(v3FieldId, v2FieldId);
                    } 
                }
                if(handler == ODBC32.HANDLER.THROW) {
                    Connection.HandleError(stmt, retcode);
                } 
                return -1;
            } 
            return numericAttribute; 
        }
 
        // GetColAttributeStr
        // ---------------
        // [IN] iColumn   ColumnNumber
        // [IN] v3FieldId FieldIdentifier of the attribute for version3 drivers (>=3.0) 
        // [IN] v2FieldId FieldIdentifier of the attribute for version2 drivers (<3.0)
        // 
        // returns the stringvalue of the FieldIdentifier field of the column 
        // or null if the string returned was empty or if the FieldIdentifier wasn't supported by the driver
        // 
        private String GetColAttributeStr(int i, ODBC32.SQL_DESC v3FieldId, ODBC32.SQL_COLUMN v2FieldId, ODBC32.HANDLER handler) {
            ODBC32.RetCode retcode;
            Int16   cchNameLength = 0;
            SQLLEN   numericAttribute; 
            CNativeBuffer buffer = Buffer;
            buffer.WriteInt16(0, 0); 
 
            OdbcStatementHandle stmt = StatementHandle;
 
            // protect against dead connection
            if (Connection == null || _cmdWrapper.Canceling || stmt == null) {
                return "";
            } 

            if (Connection.IsV3Driver) { 
                retcode = stmt.ColumnAttribute(i+1, (short)v3FieldId, buffer, out cchNameLength, out numericAttribute); 
            }
            else if (v2FieldId != (ODBC32.SQL_COLUMN)(-1)) { 
                retcode = stmt.ColumnAttribute(i+1, (short)v2FieldId, buffer, out cchNameLength, out numericAttribute);
            }
            else {
                return null; 
            }
            if((retcode != ODBC32.RetCode.SUCCESS) || (cchNameLength == 0)) 
            { 
                if (retcode == ODBC32.RetCode.ERROR) {
                    if ("HY091" == Command.GetDiagSqlState()) { 
                        Connection.FlagUnsupportedColAttr(v3FieldId, v2FieldId);
                    }
                }
                if(handler == ODBC32.HANDLER.THROW) { 
                    Connection.HandleError(stmt, retcode);
                } 
                return null; 
            }
            string retval = buffer.PtrToStringUni(0, cchNameLength/2 /*cch*/); 
            return retval;
        }

// todo: Another 3.0 only attribute that is guaranteed to fail on V2 driver. 
// need to special case this for V2 drivers.
// 
        private String GetDescFieldStr(int i, ODBC32.SQL_DESC attribute, ODBC32.HANDLER handler) { 
            Int32   numericAttribute = 0;
 
            // protect against dead connection, dead or canceling command.
            if ((Connection == null) || _cmdWrapper.Canceling) {
                return "";
            } 

            // APP_PARAM_DESC is a (ODBCVER >= 0x0300) attribute 
            if (!Connection.IsV3Driver) { 
                Debug.Assert (false, "Non-V3 driver. Must not call GetDescFieldStr");
                return null; 
            }

            ODBC32.RetCode retcode;
            CNativeBuffer buffer = Buffer; 

            // Need to set the APP_PARAM_DESC values here 
            using(OdbcDescriptorHandle hdesc = new OdbcDescriptorHandle(StatementHandle, ODBC32.SQL_ATTR.APP_PARAM_DESC)) { 
                //SQLGetDescField
                retcode = hdesc.GetDescriptionField(i+1, attribute, buffer, out numericAttribute); 

                //Since there are many attributes (column, statement, etc), that may or may not be
                //supported, we don't want to throw (which obtains all errorinfo, marshals strings,
                //builds exceptions, etc), in common cases, unless we absolutly need this info... 
                if((retcode != ODBC32.RetCode.SUCCESS) || (numericAttribute == 0))
                { 
                    if (retcode == ODBC32.RetCode.ERROR) { 
                        if ("HY091" == Command.GetDiagSqlState()) {
                            Connection.FlagUnsupportedColAttr(attribute, (ODBC32.SQL_COLUMN)0); 
                        }
                    }
                    if(handler == ODBC32.HANDLER.THROW) {
                        Connection.HandleError(StatementHandle, retcode); 
                    }
                    return null; 
                } 
            }
            string retval = buffer.PtrToStringUni(0, numericAttribute/2 /*cch*/); 
            return retval;
        }

        ///  
        /// This methods queries the following field information: isDbNull and remained size/indicator. No data is read from the driver.
        /// If the value is DbNull, this value will be cached. Refer to GetData for more details. 
        ///  
        /// false if value is DbNull, true otherwise
        private bool QueryFieldInfo(int i, ODBC32.SQL_C sqlctype, out int cbLengthOrIndicator) { 
            int cb = 0;
            if (sqlctype == ODBC32.SQL_C.WCHAR) {
                // SQLBU 266054 - in case of WCHAR data, we need to provide buffer with a space for null terminator (two bytes)
                cb = 2; 
            }
            return GetData(i, sqlctype, cb /* no data should be lost */, out cbLengthOrIndicator); 
        } 

        private bool GetData(int i, ODBC32.SQL_C sqlctype) { 
            // Never call GetData with anything larger than _buffer.Length-2.
            // We keep reallocating native buffers and it kills performance!!!
            int dummy;
            return GetData(i, sqlctype, Buffer.Length - 4, out dummy); 
        }
 
        ///  
        /// Note: use only this method to call SQLGetData! It caches the null value so the fact that the value is null is kept and no other calls
        /// are made after it. 
        ///
        /// retrieves the data into this.Buffer.
        /// * If the data is DbNull, the value be also cached and false is returned.
        /// * if the data is not DbNull, the value is not cached and true is returned 
        ///
        /// Note: cbLengthOrIndicator can be either the length of (remained) data or SQL_NO_TOTAL (-4) when the length is not known. 
        /// in case of SQL_NO_TOTAL, driver fills the buffer till the end. 
        /// The indicator will NOT be SQL_NULL_DATA, GetData will replace it with zero and return false.
        ///  
        /// false if value is DbNull, true otherwise
        private bool GetData(int i, ODBC32.SQL_C sqlctype, int cb, out int cbLengthOrIndicator) {
            IntPtr cbActual = IntPtr.Zero;  // Length or an indicator value
 
            if (IsCancelingCommand){
                throw ADP.DataReaderNoData(); 
            } 
            Debug.Assert (null != StatementHandle, "Statement handle is null in DateReader");
 
            // see notes on ODBC32.RetCode.NO_DATA case below.
            Debug.Assert(this.dataCache == null || !Convert.IsDBNull(this.dataCache[i]), "Cannot call GetData without checking for cache first!");

            // Never call GetData with anything larger than _buffer.Length-2. 
            // We keep reallocating native buffers and it kills performance!!!
 
            Debug.Assert(cb <= Buffer.Length-2, "GetData needs to Reallocate. Perf bug"); 

            // SQLGetData 
            CNativeBuffer buffer = Buffer;
            ODBC32.RetCode retcode = StatementHandle.GetData(
               (i+1),    // Column ordinals start at 1 in odbc
               sqlctype, 
               buffer,
               cb, 
               out cbActual); 

            switch (retcode) { 
                case ODBC32.RetCode.SUCCESS:
                    break;
                case ODBC32.RetCode.SUCCESS_WITH_INFO:
                    if ((Int32)cbActual == ODBC32.SQL_NO_TOTAL) { 
                        break;
                    } 
                    // devnote: don't we want to fire an event? 
                    break;
 
                case ODBC32.RetCode.NO_DATA:
                    // SQLBU 266054: System.Data.Odbc: Fails with truncated error when we pass BufferLength  as 0
                    // NO_DATA return value is success value - it means that the driver has fully consumed the current column value
                    // but did not move to the next column yet. 
                    // For fixed-size values, we do not expect this to happen because we fully consume the data and store it in cache after the first call.
                    // For variable-length values (any character or binary data), SQLGetData can be called several times on the same column, 
                    // to query for the next chunk of value, even after reaching its end! 
                    // Thus, ignore NO_DATA for variable length data, but raise exception for fixed-size types
                    if (sqlctype != ODBC32.SQL_C.WCHAR && sqlctype != ODBC32.SQL_C.BINARY) { 
                        Connection.HandleError(StatementHandle, retcode);
                    }

                    if (cbActual == (IntPtr)ODBC32.SQL_NO_TOTAL) { 
                        // ensure SQL_NO_TOTAL value gets replaced with zero if the driver has fully consumed the current column
                        cbActual = (IntPtr)0; 
                    } 
                    break;
 
                default:
                    Connection.HandleError(StatementHandle, retcode);
                    break;
            } 

            // reset the current row and column 
            SetCurrentRowColumnInfo(_row, i); 

            // test for SQL_NULL_DATA 
            if (cbActual == (IntPtr)ODBC32.SQL_NULL_DATA) {
                // Store the DBNull value in cache. Note that if we need to do it, because second call into the SQLGetData returns NO_DATA, which means
                // we already consumed the value (see above) and the NULL information is lost. By storing the null in cache, we avoid second call into the driver
                // for the same row/column. 
                this.dataCache[i] = DBNull.Value;
                // the indicator is never -1 (and it should not actually be used if the data is DBNull) 
                cbLengthOrIndicator = 0; 
                return false;
            } 
            else {
                //Return the actual size (for chunking scenarios)
                // note the return value can be SQL_NO_TOTAL (-4)
                cbLengthOrIndicator = (int)cbActual; 
                return true;
            } 
        } 

        override public bool Read() { 
            if (IsClosed) {
                throw ADP.DataReaderClosed("Read");
            }
 
            if (IsCancelingCommand) {
                _isRead = false; 
                return false; 
            }
 
            // HasRows needs to call into Read so we don't want to read on the actual Read call
            if (_skipReadOnce){
                _skipReadOnce = false;
                return _isRead; 
            }
 
            if (_noMoreRows || _noMoreResults || IsCommandBehavior(CommandBehavior.SchemaOnly)) 
                return false;
 
            if (!_isValidResult) {
                return false;
            }
 
            ODBC32.RetCode  retcode;
 
            //SQLFetch is only valid to call for row returning queries 
            //We get: [24000]Invalid cursor state.  So we could either check the count
            //ahead of time (which is cached), or check on error and compare error states. 
            //Note: SQLFetch is also invalid to be called on a prepared (schemaonly) statement
            //SqlFetch
            retcode = StatementHandle.Fetch();
 
            switch(retcode) {
            case ODBC32.RetCode.SUCCESS_WITH_INFO: 
                Connection.HandleErrorNoThrow(StatementHandle, retcode); 
                _hasRows = HasRowsStatus.HasRows;
                _isRead = true; 
                break;
            case ODBC32.RetCode.SUCCESS:
                _hasRows = HasRowsStatus.HasRows;
                _isRead = true; 
                break;
            case ODBC32.RetCode.NO_DATA: 
                _isRead = false; 
                if (_hasRows == HasRowsStatus.DontKnow) {
                    _hasRows = HasRowsStatus.HasNoRows; 
                }
                break;
            default:
                Connection.HandleError(StatementHandle, retcode); 
                break;
            } 
            //Null out previous cached row values. 
            this.dataCache.FlushValues();
 
            // if CommandBehavior == SingleRow we set _noMoreResults to true so that following reads will fail
            if (IsCommandBehavior(CommandBehavior.SingleRow)) {
                _noMoreRows = true;
                // no more rows, set to -1 
                SetCurrentRowColumnInfo(-1, 0);
            } 
            else { 
                // move to the next row
                SetCurrentRowColumnInfo(_row + 1, 0); 
            }
            return _isRead;
        }
 
        // Called by odbccommand when executed for the first time
        internal void FirstResult() { 
            Int16 cCols; 
            SQLLEN cRowsAffected;
 
            cRowsAffected = GetRowCount();              // get rowcount of the current resultset (if any)
            CalculateRecordsAffected(cRowsAffected);    // update recordsaffected

            ODBC32.RetCode retcode = FieldCountNoThrow(out cCols); 
            if ((retcode == ODBC32.RetCode.SUCCESS) && (cCols == 0)) {
                NextResult(); 
            } 
            else {
                this._isValidResult = true; 
            }
        }

        override public bool NextResult() { 
            return NextResult(false, false);
        } 
 
        private bool NextResult(bool disposing, bool allresults) {
            // if disposing, loop through all the remaining results and ignore error messages 
            // if allresults, loop through all results and collect all error messages for a single exception
            // callers are via Close(false, true), Dispose(true, false), NextResult(false,false)
            Debug.Assert(!disposing || !allresults, "both disposing & allresults are true");
            const int MaxConsecutiveFailure = 2000; // see WebData 72126 for why more than 1000 

            SQLLEN cRowsAffected; 
            Int16 cColsAffected; 
            ODBC32.RetCode retcode, firstRetCode = ODBC32.RetCode.SUCCESS;
            bool hasMoreResults; 
            bool hasColumns = false;
            bool singleResult = IsCommandBehavior(CommandBehavior.SingleResult);

            if (IsClosed) { 
                throw ADP.DataReaderClosed("NextResult");
            } 
            _fieldNameLookup = null; 

            if (IsCancelingCommand || _noMoreResults) { 
                return false;
            }

            //Blow away the previous cache (since the next result set will have a different shape, 
            //different schema data, and different data.
            _isRead = false; 
            _hasRows = HasRowsStatus.DontKnow; 
            _fieldNameLookup = null;
            this.metadata = null; 
            this.schemaTable = null;

            int loop = 0; // infinite loop protection, max out after 2000 consecutive failed results
            OdbcErrorCollection errors = null; // SQLBU 342112 
            do {
                _isValidResult = false; 
                retcode = StatementHandle.MoreResults(); 
                hasMoreResults = ((retcode == ODBC32.RetCode.SUCCESS)
                                ||(retcode == ODBC32.RetCode.SUCCESS_WITH_INFO)); 

                if (retcode == ODBC32.RetCode.SUCCESS_WITH_INFO) {
                    Connection.HandleErrorNoThrow(StatementHandle, retcode);
                } 
                else if (!disposing && (retcode != ODBC32.RetCode.NO_DATA) && (ODBC32.RetCode.SUCCESS != retcode)) {
                    // allow for building comulative error messages. 
                    if (null == errors) { 
                        firstRetCode = retcode;
                        errors = new OdbcErrorCollection(); 
                    }
                    ODBC32.GetDiagErrors(errors, null, StatementHandle, retcode);
                    ++loop;
                } 

                if (!disposing && hasMoreResults) { 
                    loop = 0; 
                    cRowsAffected = GetRowCount();              // get rowcount of the current resultset (if any)
                    CalculateRecordsAffected(cRowsAffected);    // update recordsaffected 
                    if (!singleResult) {
                        // update row- and columncount
                        FieldCountNoThrow(out cColsAffected);
                        hasColumns = (0 != cColsAffected); 
                        _isValidResult = hasColumns;
                    } 
                } 
            } while ((!singleResult && hasMoreResults && !hasColumns)  // repeat for results with no columns
                     || ((ODBC32.RetCode.NO_DATA != retcode) && allresults && (loop < MaxConsecutiveFailure)) // or process all results until done 
                     || (singleResult && hasMoreResults));           // or for any result in singelResult mode
            if (MaxConsecutiveFailure <= loop) {
                Bid.Trace(" 2000 consecutive failed results");
            } 

            if(retcode == ODBC32.RetCode.NO_DATA) { 
                this.dataCache = null; 
                _noMoreResults = true;
            } 
            if (null != errors) {
                Debug.Assert(!disposing, "errors while disposing");
                errors.SetSource(Connection.Driver);
                OdbcException exception = OdbcException.CreateException(errors, firstRetCode); 
                Connection.ConnectionIsAlive(exception);
                throw exception; 
            } 
            return (hasMoreResults);
        } 

        private void BuildMetaDataInfo() {
            int count = FieldCount;
            MetaData[] metaInfos = new MetaData[count]; 
            List  qrytables;
            bool needkeyinfo = IsCommandBehavior(CommandBehavior.KeyInfo); 
            bool isKeyColumn; 
            bool isHidden;
            ODBC32.SQL_NULLABILITY nullable; 

            if (needkeyinfo)
                qrytables = new List();
            else 
                qrytables = null;
 
            // Find out all the metadata info, not all of this info will be available in all cases 
            //
            for(int i=0; i

Link Menu

Network programming in C#, Network Programming in VB.NET, Network Programming in .NET
This book is available now!
Buy at Amazon US or
Buy at Amazon UK