File: System\Data\SqlClient\SqlDataReaderSmi.cs
Project: ndp\fx\src\data\System.Data.csproj (System.Data)
//------------------------------------------------------------------------------
// <copyright file="SqlDataReader.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>
//------------------------------------------------------------------------------
 
namespace System.Data.SqlClient {
    using System;
    using System.Data;
    using System.Data.Sql;
    using System.Data.SqlTypes;
    using System.IO;
    using System.Runtime.InteropServices;
    using System.Threading;
    using System.Diagnostics;        // for Conditional compilation
    using System.Diagnostics.CodeAnalysis;
    using System.Xml;
    using Microsoft.SqlServer.Server;
    using System.Data.ProviderBase;
    using System.Data.Common;
    using System.Threading.Tasks;
 
        // SqlServer provider's implementation of ISqlReader.
    //    Supports ISqlReader and ISqlResultSet objects.
    //
    //    User should never be able to create one of these themselves, nor subclass.
    //        This is accomplished by having no public override constructors.
    internal sealed class SqlDataReaderSmi : SqlDataReader {
 
    
    //
    // IDBRecord properties
    //
        public override int FieldCount {
            get {
                ThrowIfClosed( "FieldCount" );
                return InternalFieldCount;
            }
        }
 
        public override int VisibleFieldCount {
            get {
                ThrowIfClosed("VisibleFieldCount");
 
                if (FNotInResults()) {
                    return 0;
                }
 
                return _visibleColumnCount;
            }
        }
 
    //
    // IDBRecord Metadata Methods
    //
        public override String GetName(int ordinal) {
            EnsureCanGetMetaData( "GetName" );
            return _currentMetaData[ordinal].Name;
        }
 
        public override String GetDataTypeName(int ordinal) {
            EnsureCanGetMetaData( "GetDataTypeName" );
            SmiExtendedMetaData md = _currentMetaData[ordinal];
            if ( SqlDbType.Udt == md.SqlDbType ) {
                return md.TypeSpecificNamePart1 + "." + md.TypeSpecificNamePart2 + "." + md.TypeSpecificNamePart3;
            }
            else {
                return md.TypeName;
            }
        }
 
        public override Type GetFieldType(int ordinal) {
            EnsureCanGetMetaData( "GetFieldType" );
            if (SqlDbType.Udt == _currentMetaData[ordinal].SqlDbType) {
                return _currentMetaData[ordinal].Type;
            }
            else {
                return MetaType.GetMetaTypeFromSqlDbType(
                    _currentMetaData[ordinal].SqlDbType, _currentMetaData[ordinal].IsMultiValued).ClassType ;
            }
        }
 
        override public Type GetProviderSpecificFieldType(int ordinal) {
            EnsureCanGetMetaData( "GetProviderSpecificFieldType" );
 
            if (SqlDbType.Udt == _currentMetaData[ordinal].SqlDbType) {
                return _currentMetaData[ordinal].Type;
            }
            else {
                return MetaType.GetMetaTypeFromSqlDbType(
                    _currentMetaData[ordinal].SqlDbType, _currentMetaData[ordinal].IsMultiValued).SqlType ;
            }
        }
 
        public override int Depth {
            get{
                ThrowIfClosed( "Depth" );
                return 0;
            }
        } // 
 
        public override Object GetValue(int ordinal) {
            EnsureCanGetCol( "GetValue", ordinal);
            SmiQueryMetaData metaData = _currentMetaData[ordinal];
            if (_currentConnection.IsKatmaiOrNewer) {
                return ValueUtilsSmi.GetValue200(_readerEventSink, (SmiTypedGetterSetter)_currentColumnValuesV3, ordinal, metaData, _currentConnection.InternalContext);
            }
            else {
                return ValueUtilsSmi.GetValue(_readerEventSink, _currentColumnValuesV3, ordinal, metaData, _currentConnection.InternalContext);
            }
        }
 
        public override T GetFieldValue<T>(int ordinal) {
            EnsureCanGetCol( "GetFieldValue<T>", ordinal);
            SmiQueryMetaData metaData = _currentMetaData[ordinal];
 
            if (_typeofINullable.IsAssignableFrom(typeof(T))) {
                // If its a SQL Type or Nullable UDT
                if (_currentConnection.IsKatmaiOrNewer) {
                    return (T)ValueUtilsSmi.GetSqlValue200(_readerEventSink, (SmiTypedGetterSetter)_currentColumnValuesV3, ordinal, metaData, _currentConnection.InternalContext);
                }
                else {
                    return (T)ValueUtilsSmi.GetSqlValue(_readerEventSink, _currentColumnValuesV3, ordinal, metaData, _currentConnection.InternalContext);
                }
            }
            else {
                // Otherwise Its a CLR or non-Nullable UDT
                if (_currentConnection.IsKatmaiOrNewer) {
                    return (T)ValueUtilsSmi.GetValue200(_readerEventSink, (SmiTypedGetterSetter)_currentColumnValuesV3, ordinal, metaData, _currentConnection.InternalContext);
                }
                else {
                    return (T)ValueUtilsSmi.GetValue(_readerEventSink, _currentColumnValuesV3, ordinal, metaData, _currentConnection.InternalContext);
                }
            }
        }
 
        public override Task<T> GetFieldValueAsync<T>(int ordinal, CancellationToken cancellationToken) {
            // As per Async spec, Context Connections do not support async
            return ADP.CreatedTaskWithException<T>(ADP.ExceptionWithStackTrace(SQL.NotAvailableOnContextConnection()));
        }
 
        override internal SqlBuffer.StorageType GetVariantInternalStorageType(int ordinal) {
            Debug.Assert(null != _currentColumnValuesV3, "Attempting to get variant internal storage type without calling GetValue first");
            if (IsDBNull(ordinal))
                return SqlBuffer.StorageType.Empty;
 
            SmiMetaData valueMetaData = _currentColumnValuesV3.GetVariantType(_readerEventSink, ordinal);
            if (valueMetaData == null)
                return SqlBuffer.StorageType.Empty;
            else
                return ValueUtilsSmi.SqlDbTypeToStorageType(valueMetaData.SqlDbType);
        }
 
        public override int GetValues(object[] values) {
            EnsureCanGetCol( "GetValues", 0);
            if (null == values) {
                throw ADP.ArgumentNull("values");
            }
 
            int copyLength = (values.Length < _visibleColumnCount) ? values.Length : _visibleColumnCount;
            for(int i=0; i<copyLength; i++) {
                values[_indexMap[i]] = GetValue(i);
            }
            return copyLength;
        }
 
        public override int GetOrdinal(string name) {
            EnsureCanGetMetaData( "GetOrdinal" );
            if (null == _fieldNameLookup) {
                _fieldNameLookup = new FieldNameLookup( (IDataReader) this, -1 ); // 
            }
            return _fieldNameLookup.GetOrdinal(name); // MDAC 71470
        }
 
        // Generic array access by column index (accesses column value)
        public override object this[int ordinal] {
            get {
                return GetValue( ordinal );
            }
        }
 
        // Generic array access by column name (accesses column value)
        public override object this[string strName] {
            get {
                return GetValue( GetOrdinal( strName ) );
            }
        }
 
    //
    // IDataRecord Data Access methods
    //
        public override bool IsDBNull(int ordinal) {
            EnsureCanGetCol( "IsDBNull", ordinal);
            return ValueUtilsSmi.IsDBNull(_readerEventSink, _currentColumnValuesV3, ordinal);
        }
 
        public override Task<bool> IsDBNullAsync(int ordinal, CancellationToken cancellationToken) {
            // As per Async spec, Context Connections do not support async
            return ADP.CreatedTaskWithException<bool>(ADP.ExceptionWithStackTrace(SQL.NotAvailableOnContextConnection()));
        }
 
        public override bool GetBoolean(int ordinal) {
            EnsureCanGetCol( "GetBoolean", ordinal);
            return ValueUtilsSmi.GetBoolean(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal]);
        }
 
        public override byte GetByte(int ordinal) {
            EnsureCanGetCol( "GetByte", ordinal);
            return ValueUtilsSmi.GetByte(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal]);
        }
 
        public override long GetBytes(int ordinal, long fieldOffset, byte[] buffer, int bufferOffset, int length) {
            EnsureCanGetCol( "GetBytes", ordinal);
            return ValueUtilsSmi.GetBytes(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal], fieldOffset, buffer, bufferOffset, length, true);
        }
 
        // XmlReader support code calls this method.
        internal override long GetBytesInternal(int ordinal, long fieldOffset, byte[] buffer, int bufferOffset, int length) {
            EnsureCanGetCol( "GetBytes", ordinal);
            return ValueUtilsSmi.GetBytesInternal(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal], fieldOffset, buffer, bufferOffset, length, false);
        }
 
        public override char GetChar(int ordinal) {
            throw ADP.NotSupported();
        }
 
        public override long GetChars(int ordinal, long fieldOffset, char[] buffer, int bufferOffset, int length) {
            EnsureCanGetCol( "GetChars", ordinal);
            SmiExtendedMetaData metaData = _currentMetaData[ordinal];
            if (IsCommandBehavior(CommandBehavior.SequentialAccess)) {
                if (metaData.SqlDbType == SqlDbType.Xml) {
                    return GetStreamingXmlChars(ordinal, fieldOffset, buffer, bufferOffset, length);
                }
            }
            return ValueUtilsSmi.GetChars(_readerEventSink, _currentColumnValuesV3, ordinal, metaData, fieldOffset, buffer, bufferOffset, length);
        }
 
        public override Guid GetGuid(int ordinal) {
            EnsureCanGetCol( "GetGuid", ordinal);
            return ValueUtilsSmi.GetGuid(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal]);
        }
 
        public override Int16 GetInt16(int ordinal) {
            EnsureCanGetCol( "GetInt16", ordinal);
            return ValueUtilsSmi.GetInt16(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal]);
        }
 
        public override Int32 GetInt32(int ordinal) {
            EnsureCanGetCol( "GetInt32", ordinal);
            return ValueUtilsSmi.GetInt32(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal]);
        }
 
        public override Int64 GetInt64(int ordinal) {
            EnsureCanGetCol( "GetInt64", ordinal);
            return ValueUtilsSmi.GetInt64(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal]);
        }
 
        public override Single GetFloat(int ordinal) {
            EnsureCanGetCol( "GetFloat", ordinal);
            return ValueUtilsSmi.GetSingle(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal]);
        }
 
        public override Double GetDouble(int ordinal) {
            EnsureCanGetCol( "GetDouble", ordinal);
            return ValueUtilsSmi.GetDouble(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal]);
        }
 
        public override String GetString(int ordinal) {
            EnsureCanGetCol( "GetString", ordinal);
            return ValueUtilsSmi.GetString(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal]);
        }
 
        public override Decimal GetDecimal(int ordinal) {
            EnsureCanGetCol( "GetDecimal", ordinal);
            return ValueUtilsSmi.GetDecimal(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal]);
        }
 
        public override DateTime GetDateTime(int ordinal) {
            EnsureCanGetCol( "GetDateTime", ordinal);
            return ValueUtilsSmi.GetDateTime(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal]);
        }
 
    //
    // IDataReader properties
    //
        // Logically closed test. I.e. is this object closed as far as external access is concerned?
        public override bool IsClosed {
            get { 
                return IsReallyClosed(); 
            }
        }
 
        public override int RecordsAffected {
            get {
                return base.Command.InternalRecordsAffected;
            }
        }
 
    //
    // IDataReader methods
    //
        internal override void CloseReaderFromConnection() {
            // Context Connections do not support async - so there is no threading issues with closing from the connection
            CloseInternal(closeConnection: false);
        }
 
        public override void Close() {
            // Connection should be open at this point, so we can do multiple checks of HasEvents, and we may need to close the connection afterwards
            CloseInternal(closeConnection: IsCommandBehavior(CommandBehavior.CloseConnection));
        }
 
        private void CloseInternal(bool closeConnection) {
            IntPtr hscp;
            Bid.ScopeEnter(out hscp, "<sc.SqlDataReaderSmi.Close|API> %d#", ObjectID);
            bool processFinallyBlock = true;
            try  {
                if(!IsClosed) {
                    _hasRows = false;
 
                    // Process the remaining events. This makes sure that environment changes are applied and any errors are picked up.
                    while(_eventStream.HasEvents) {
                        _eventStream.ProcessEvent( _readerEventSink );
                        _readerEventSink.ProcessMessagesAndThrow(true);
                    }
 
                    // Close the request executor
                    _requestExecutor.Close(_readerEventSink);
                    _readerEventSink.ProcessMessagesAndThrow(true);
                }
            }
            catch (Exception e) {
                processFinallyBlock = ADP.IsCatchableExceptionType(e);
                throw;
            }
            finally {
                if (processFinallyBlock) {
                    _isOpen = false;
 
                    if ((closeConnection) && (Connection != null)) {
                        Connection.Close();
                    }
 
                    Bid.ScopeLeave(ref hscp);
                }
            }
        }
 
        // Move to the next resultset
        public override unsafe bool NextResult() {
            ThrowIfClosed( "NextResult" );
 
            bool hasAnotherResult = InternalNextResult(false);
 
            return hasAnotherResult;
        }
        
        public override Task<bool> NextResultAsync(CancellationToken cancellationToken)
        {
            // Async not supported on Context Connections
            return ADP.CreatedTaskWithException<bool>(ADP.ExceptionWithStackTrace(SQL.NotAvailableOnContextConnection()));
        }
 
        internal unsafe bool InternalNextResult(bool ignoreNonFatalMessages) {
            IntPtr hscp = IntPtr.Zero;
            if (Bid.AdvancedOn) {
                Bid.ScopeEnter(out hscp, "<sc.SqlDataReaderSmi.InternalNextResult|ADV> %d#", ObjectID);
            }
            try {
                _hasRows = false;
 
                if( PositionState.AfterResults != _currentPosition )
                {
                    // Consume any remaning rows in the current result. 
                    
                    while( InternalRead(ignoreNonFatalMessages) ) {
                        // This space intentionally left blank
                    }
 
                    // reset resultset metadata - it will be created again if there is a pending resultset
                    ResetResultSet();
 
                    // Process the events until metadata is found or all of the
                    // available events have been consumed. If there is another
                    // result, the metadata for it will be available after the last
                    // read on the prior result.
 
                    while(null == _currentMetaData && _eventStream.HasEvents) {
                        _eventStream.ProcessEvent( _readerEventSink );
                        _readerEventSink.ProcessMessagesAndThrow(ignoreNonFatalMessages);
                    }
                }
 
                return PositionState.AfterResults != _currentPosition;
            }
            finally {
                if (Bid.AdvancedOn) {
                    Bid.ScopeLeave(ref hscp);
                }
            }
        }
 
        public override bool Read() {
            ThrowIfClosed( "Read" );
            bool hasAnotherRow = InternalRead(false);
 
            return hasAnotherRow;
        }
 
        public override Task<bool> ReadAsync(CancellationToken cancellationToken)
        {
            // Async not supported on Context Connections
            return ADP.CreatedTaskWithException<bool>(ADP.ExceptionWithStackTrace(SQL.NotAvailableOnContextConnection()));
        }
 
        internal unsafe bool InternalRead(bool ignoreNonFatalErrors) {
            IntPtr hscp = IntPtr.Zero;
            if (Bid.AdvancedOn) {
                Bid.ScopeEnter(out hscp, "<sc.SqlDataReaderSmi.InternalRead|ADV> %d#", ObjectID);
            }
            try {
                // Don't move unless currently in results.
                if( FInResults() ) {
                
                    // Set current row to null so we can see if we get a new one
                    _currentColumnValues = null;
                    _currentColumnValuesV3 = null;
                    
                    // Reset blobs
                    if (_currentStream != null) {
                        _currentStream.SetClosed();
                        _currentStream = null;
                    }
                    if (_currentTextReader != null) {
                        _currentTextReader.SetClosed();
                        _currentTextReader = null;
                    }
 
                    // NOTE: SQLBUDT #386118 -- may indicate that we want to break this loop when we get a MessagePosted callback, but we can't prove that.
                    while(  null == _currentColumnValues &&                         // Did we find a row?
                            null == _currentColumnValuesV3 &&                       // Did we find a V3 row?
                            FInResults() &&                         // Was the batch terminated due to a serious error?
                            PositionState.AfterRows != _currentPosition &&              // Have we seen a statement completed event?
                            _eventStream.HasEvents ) {              // Have we processed all events?
                        _eventStream.ProcessEvent( _readerEventSink );
                        _readerEventSink.ProcessMessagesAndThrow(ignoreNonFatalErrors);
                    }
                }
                
                return PositionState.OnRow == _currentPosition;
            }
            finally {
                if (Bid.AdvancedOn) {
                    Bid.ScopeLeave(ref hscp);
                }
            }
        }    
 
        public override DataTable GetSchemaTable() {
            ThrowIfClosed( "GetSchemaTable" );
 
            if ( null == _schemaTable && FInResults() )
                {
 
                DataTable schemaTable = new DataTable( "SchemaTable" );
                schemaTable.Locale = System.Globalization.CultureInfo.InvariantCulture;
                schemaTable.MinimumCapacity = InternalFieldCount;
 
                DataColumn ColumnName                       = new DataColumn(SchemaTableColumn.ColumnName,                       typeof(System.String));
                DataColumn Ordinal                          = new DataColumn(SchemaTableColumn.ColumnOrdinal,                    typeof(System.Int32));
                DataColumn Size                             = new DataColumn(SchemaTableColumn.ColumnSize,                       typeof(System.Int32));
                DataColumn Precision                        = new DataColumn(SchemaTableColumn.NumericPrecision,                 typeof(System.Int16));
                DataColumn Scale                            = new DataColumn(SchemaTableColumn.NumericScale,                     typeof(System.Int16));
 
                DataColumn DataType                         = new DataColumn(SchemaTableColumn.DataType,                         typeof(System.Type));
                DataColumn ProviderSpecificDataType         = new DataColumn(SchemaTableOptionalColumn.ProviderSpecificDataType, typeof(System.Type));
                DataColumn ProviderType                     = new DataColumn(SchemaTableColumn.ProviderType,                     typeof(System.Int32));
                DataColumn NonVersionedProviderType         = new DataColumn(SchemaTableColumn.NonVersionedProviderType,         typeof(System.Int32));
 
                DataColumn IsLong                           = new DataColumn(SchemaTableColumn.IsLong,                           typeof(System.Boolean));
                DataColumn AllowDBNull                      = new DataColumn(SchemaTableColumn.AllowDBNull,                      typeof(System.Boolean));
                DataColumn IsReadOnly                       = new DataColumn(SchemaTableOptionalColumn.IsReadOnly,               typeof(System.Boolean));
                DataColumn IsRowVersion                     = new DataColumn(SchemaTableOptionalColumn.IsRowVersion,             typeof(System.Boolean));
 
                DataColumn IsUnique                         = new DataColumn(SchemaTableColumn.IsUnique,                         typeof(System.Boolean));
                DataColumn IsKey                            = new DataColumn(SchemaTableColumn.IsKey,                            typeof(System.Boolean));
                DataColumn IsAutoIncrement                  = new DataColumn(SchemaTableOptionalColumn.IsAutoIncrement,          typeof(System.Boolean));
                DataColumn IsHidden                         = new DataColumn(SchemaTableOptionalColumn.IsHidden,                 typeof(System.Boolean));
 
                DataColumn BaseCatalogName                  = new DataColumn(SchemaTableOptionalColumn.BaseCatalogName,          typeof(System.String));
                DataColumn BaseSchemaName                   = new DataColumn(SchemaTableColumn.BaseSchemaName,                   typeof(System.String));
                DataColumn BaseTableName                    = new DataColumn(SchemaTableColumn.BaseTableName,                    typeof(System.String));
                DataColumn BaseColumnName                   = new DataColumn(SchemaTableColumn.BaseColumnName,                   typeof(System.String));
 
                // unique to SqlClient
                DataColumn BaseServerName                   = new DataColumn(SchemaTableOptionalColumn.BaseServerName,           typeof(System.String));
                DataColumn IsAliased                        = new DataColumn(SchemaTableColumn.IsAliased,                        typeof(System.Boolean));
                DataColumn IsExpression                     = new DataColumn(SchemaTableColumn.IsExpression,                     typeof(System.Boolean));
                DataColumn IsIdentity                       = new DataColumn("IsIdentity",                                       typeof(System.Boolean));
                // UDT specific. Holds UDT typename ONLY if the type of the column is UDT, otherwise the data type
                DataColumn DataTypeName                     = new DataColumn("DataTypeName",                                     typeof(System.String));
                DataColumn UdtAssemblyQualifiedName         = new DataColumn("UdtAssemblyQualifiedName",                         typeof(System.String));
                // Xml metadata specific
                DataColumn XmlSchemaCollectionDatabase      = new DataColumn("XmlSchemaCollectionDatabase",                      typeof(System.String));
                DataColumn XmlSchemaCollectionOwningSchema  = new DataColumn("XmlSchemaCollectionOwningSchema",                  typeof(System.String));
                DataColumn XmlSchemaCollectionName          = new DataColumn("XmlSchemaCollectionName",                          typeof(System.String));
                // SparseColumnSet
                DataColumn IsColumnSet = new DataColumn("IsColumnSet", typeof(System.Boolean));
 
                Ordinal.DefaultValue = 0;
                IsLong.DefaultValue = false;
 
                DataColumnCollection columns = schemaTable.Columns;
 
                // must maintain order for backward compatibility
                columns.Add(ColumnName);
                columns.Add(Ordinal);
                columns.Add(Size);
                columns.Add(Precision);
                columns.Add(Scale);
                columns.Add(IsUnique);
                columns.Add(IsKey);
                columns.Add(BaseServerName);
                columns.Add(BaseCatalogName);
                columns.Add(BaseColumnName);
                columns.Add(BaseSchemaName);
                columns.Add(BaseTableName);
                columns.Add(DataType);
                columns.Add(AllowDBNull);
                columns.Add(ProviderType);
                columns.Add(IsAliased);
                columns.Add(IsExpression);
                columns.Add(IsIdentity);
                columns.Add(IsAutoIncrement);
                columns.Add(IsRowVersion);
                columns.Add(IsHidden);
                columns.Add(IsLong);
                columns.Add(IsReadOnly);
                columns.Add(ProviderSpecificDataType);
                columns.Add(DataTypeName);
                columns.Add(XmlSchemaCollectionDatabase);
                columns.Add(XmlSchemaCollectionOwningSchema);
                columns.Add(XmlSchemaCollectionName);
                columns.Add(UdtAssemblyQualifiedName);
                columns.Add(NonVersionedProviderType);
                columns.Add(IsColumnSet);
 
                for (int i = 0; i < InternalFieldCount; i++) {
                    SmiQueryMetaData colMetaData = _currentMetaData[i];
 
                    long maxLength = colMetaData.MaxLength;
                        
                    MetaType metaType = MetaType.GetMetaTypeFromSqlDbType(colMetaData.SqlDbType, colMetaData.IsMultiValued);
                    if ( SmiMetaData.UnlimitedMaxLengthIndicator == maxLength ) {
                        metaType = MetaType.GetMaxMetaTypeFromMetaType( metaType );
                        maxLength = (metaType.IsSizeInCharacters && !metaType.IsPlp) ? (0x7fffffff / 2) : 0x7fffffff;
                    }
 
                    DataRow schemaRow = schemaTable.NewRow();
 
                    // NOTE: there is an impedence mismatch here - the server always 
                    // treats numeric data as variable length and sends a maxLength
                    // based upon the precision, whereas TDS always sends 17 for 
                    // the max length; rather than push this logic into the server,
                    // I've elected to make a fixup here instead.
                    if (SqlDbType.Decimal == colMetaData.SqlDbType) {
                        // 
                        maxLength = TdsEnums.MAX_NUMERIC_LEN;   // SQLBUDT 339686
                    }
                    else if (SqlDbType.Variant == colMetaData.SqlDbType) {
                        // 
                        maxLength = 8009;   // SQLBUDT 340726
                    }
 
                    schemaRow[ColumnName]   = colMetaData.Name;
                    schemaRow[Ordinal]      = i;
                    schemaRow[Size]         = maxLength;
                    
                    schemaRow[ProviderType] = (int) colMetaData.SqlDbType; // SqlDbType
                    schemaRow[NonVersionedProviderType] = (int) colMetaData.SqlDbType; // SqlDbType
 
                    if (colMetaData.SqlDbType != SqlDbType.Udt) {
                        schemaRow[DataType]                 = metaType.ClassType; // com+ type
                        schemaRow[ProviderSpecificDataType] = metaType.SqlType;
                    }
                    else {
                        schemaRow[UdtAssemblyQualifiedName] = colMetaData.Type.AssemblyQualifiedName;
                        schemaRow[DataType]                 = colMetaData.Type;
                        schemaRow[ProviderSpecificDataType] = colMetaData.Type;
                    }
 
                    // NOTE: there is also an impedence mismatch here - the server 
                    // has different ideas about what the precision value should be
                    // than does the client bits.  I tried fixing up the default
                    // meta data values in SmiMetaData, however, it caused the 
                    // server suites to fall over dead.  Rather than attempt to 
                    // bake it into the server, I'm fixing it up in the client.
                    byte precision = 0xff;  // default for everything, except certain numeric types.
                    
                    // 
                    switch (colMetaData.SqlDbType) {
                        case SqlDbType.BigInt:
                        case SqlDbType.DateTime:
                        case SqlDbType.Decimal:
                        case SqlDbType.Int:
                        case SqlDbType.Money:
                        case SqlDbType.SmallDateTime:
                        case SqlDbType.SmallInt:
                        case SqlDbType.SmallMoney:
                        case SqlDbType.TinyInt:
                            precision = colMetaData.Precision;  
                            break;
                        case SqlDbType.Float:       
                            precision = 15;  
                            break;
                        case SqlDbType.Real:        
                            precision = 7;  
                            break;
                        default:
                            precision = 0xff;   // everything else is unknown;
                            break;
                    }
 
                    schemaRow[Precision]        = precision;
                    
                    // 
                    if ( SqlDbType.Decimal == colMetaData.SqlDbType ||
                        SqlDbType.Time == colMetaData.SqlDbType ||
                        SqlDbType.DateTime2 == colMetaData.SqlDbType ||
                        SqlDbType.DateTimeOffset == colMetaData.SqlDbType) {
                        schemaRow[Scale]            = colMetaData.Scale;
                    }
                    else {
                        schemaRow[Scale]            = MetaType.GetMetaTypeFromSqlDbType(
                                                        colMetaData.SqlDbType, colMetaData.IsMultiValued).Scale;
                    }
                    
                    schemaRow[AllowDBNull]      = colMetaData.AllowsDBNull;
                    if ( !( colMetaData.IsAliased.IsNull ) ) {
                        schemaRow[IsAliased]        = colMetaData.IsAliased.Value;
                    }
 
                    if ( !( colMetaData.IsKey.IsNull ) ) {
                        schemaRow[IsKey]            = colMetaData.IsKey.Value;
                    }
 
                    if ( !( colMetaData.IsHidden.IsNull ) ) {
                        schemaRow[IsHidden]         = colMetaData.IsHidden.Value;
                    }
 
                    if ( !( colMetaData.IsExpression.IsNull ) ) {
                        schemaRow[IsExpression]     = colMetaData.IsExpression.Value;
                    }
 
                    schemaRow[IsReadOnly]       = colMetaData.IsReadOnly;
                    schemaRow[IsIdentity]       = colMetaData.IsIdentity;
                    schemaRow[IsColumnSet]      = colMetaData.IsColumnSet;
                    schemaRow[IsAutoIncrement]  = colMetaData.IsIdentity;
                    schemaRow[IsLong]           = metaType.IsLong;
 
                    // mark unique for timestamp columns
                    if ( SqlDbType.Timestamp == colMetaData.SqlDbType ) {
                        schemaRow[IsUnique]         = true;
                        schemaRow[IsRowVersion]     = true;
                    }
                    else {
                        schemaRow[IsUnique]         = false;
                        schemaRow[IsRowVersion]     = false;
                    }
 
                    if ( !ADP.IsEmpty( colMetaData.ColumnName ) ) {
                        schemaRow[BaseColumnName]   = colMetaData.ColumnName;
                    }
                    else if (!ADP.IsEmpty( colMetaData.Name)) {
                        // Use projection name if base column name is not present
                        schemaRow[BaseColumnName]   = colMetaData.Name;
                    }
 
                    if ( !ADP.IsEmpty(colMetaData.TableName ) ) {
                        schemaRow[BaseTableName]    = colMetaData.TableName;
                    }
 
                    if (!ADP.IsEmpty(colMetaData.SchemaName)) {
                        schemaRow[BaseSchemaName]   = colMetaData.SchemaName;
                    }
 
                    if (!ADP.IsEmpty(colMetaData.CatalogName)) {
                        schemaRow[BaseCatalogName]  = colMetaData.CatalogName;
                    }
 
                    if (!ADP.IsEmpty(colMetaData.ServerName)) {
                        schemaRow[BaseServerName]   = colMetaData.ServerName;
                    }
 
                    if ( SqlDbType.Udt == colMetaData.SqlDbType ) {
                        schemaRow[DataTypeName] = colMetaData.TypeSpecificNamePart1 + "." + colMetaData.TypeSpecificNamePart2 + "." + colMetaData.TypeSpecificNamePart3;
                    }
                    else {                        
                        schemaRow[DataTypeName] = metaType.TypeName;  
                    }
 
                    // Add Xml metadata
                    if ( SqlDbType.Xml == colMetaData.SqlDbType ) {
                        schemaRow[XmlSchemaCollectionDatabase]      = colMetaData.TypeSpecificNamePart1;
                        schemaRow[XmlSchemaCollectionOwningSchema]  = colMetaData.TypeSpecificNamePart2;
                        schemaRow[XmlSchemaCollectionName]          = colMetaData.TypeSpecificNamePart3;
                    }
 
                    schemaTable.Rows.Add(schemaRow);
                    schemaRow.AcceptChanges();
                }
 
                // mark all columns as readonly
                foreach(DataColumn column in columns) {
                    column.ReadOnly = true; // MDAC 70943
                }
 
                _schemaTable = schemaTable;
            }
 
            return _schemaTable;
        }
 
    //
    //    ISqlRecord methods
    //
        public override SqlBinary GetSqlBinary(int ordinal) {
            EnsureCanGetCol( "GetSqlBinary", ordinal);
            return ValueUtilsSmi.GetSqlBinary(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal]);
        }
 
        public override SqlBoolean GetSqlBoolean(int ordinal) {
            EnsureCanGetCol( "GetSqlBoolean", ordinal);
            return ValueUtilsSmi.GetSqlBoolean(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal]);
        }
 
        public override SqlByte GetSqlByte(int ordinal) {
            EnsureCanGetCol( "GetSqlByte", ordinal);
            return ValueUtilsSmi.GetSqlByte(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal]);
        }
 
        public override SqlInt16 GetSqlInt16(int ordinal) {
            EnsureCanGetCol( "GetSqlInt16", ordinal);
            return ValueUtilsSmi.GetSqlInt16(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal]);
        }
 
        public override SqlInt32 GetSqlInt32(int ordinal) {
            EnsureCanGetCol( "GetSqlInt32", ordinal);
            return ValueUtilsSmi.GetSqlInt32(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal]);
        }
 
        public override SqlInt64 GetSqlInt64(int ordinal) {
            EnsureCanGetCol( "GetSqlInt64", ordinal);
            return ValueUtilsSmi.GetSqlInt64(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal]);
        }
 
        public override SqlSingle GetSqlSingle(int ordinal) {
            EnsureCanGetCol( "GetSqlSingle", ordinal);
            return ValueUtilsSmi.GetSqlSingle(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal]);
        }
 
        public override SqlDouble GetSqlDouble(int ordinal) {
            EnsureCanGetCol( "GetSqlDouble", ordinal);
            return ValueUtilsSmi.GetSqlDouble(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal]);
        }
 
        public override SqlMoney GetSqlMoney(int ordinal) {
            EnsureCanGetCol( "GetSqlMoney", ordinal);
            return ValueUtilsSmi.GetSqlMoney(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal]);
        }
 
        public override SqlDateTime GetSqlDateTime(int ordinal) {
            EnsureCanGetCol( "GetSqlDateTime", ordinal);
            return ValueUtilsSmi.GetSqlDateTime(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal]);
        }
 
 
        public override SqlDecimal GetSqlDecimal(int ordinal) {
            EnsureCanGetCol( "GetSqlDecimal", ordinal);
            return ValueUtilsSmi.GetSqlDecimal(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal]);
        }
 
        public override SqlString GetSqlString(int ordinal) {
            EnsureCanGetCol( "GetSqlString", ordinal);
            return ValueUtilsSmi.GetSqlString(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal]);
        }
 
        public override SqlGuid GetSqlGuid(int ordinal) {
            EnsureCanGetCol( "GetSqlGuid", ordinal);
            return ValueUtilsSmi.GetSqlGuid(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal]);
        }
 
        public override SqlChars GetSqlChars(int ordinal) {
            EnsureCanGetCol( "GetSqlChars", ordinal);
            return ValueUtilsSmi.GetSqlChars(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal], _currentConnection.InternalContext);
        }
 
        public override SqlBytes GetSqlBytes(int ordinal) {
            EnsureCanGetCol( "GetSqlBytes", ordinal);
            return ValueUtilsSmi.GetSqlBytes(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal], _currentConnection.InternalContext);
        }
 
        public override SqlXml GetSqlXml(int ordinal) {
            EnsureCanGetCol( "GetSqlXml", ordinal);
            return ValueUtilsSmi.GetSqlXml(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal], _currentConnection.InternalContext);
        }
 
        public override TimeSpan GetTimeSpan(int ordinal) {
            EnsureCanGetCol("GetTimeSpan", ordinal);
            return ValueUtilsSmi.GetTimeSpan(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal], _currentConnection.IsKatmaiOrNewer);
        }
 
        public override DateTimeOffset GetDateTimeOffset(int ordinal) {
            EnsureCanGetCol("GetDateTimeOffset", ordinal);
            return ValueUtilsSmi.GetDateTimeOffset(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal], _currentConnection.IsKatmaiOrNewer);
        }
 
        public override object GetSqlValue(int ordinal) {
            EnsureCanGetCol( "GetSqlValue", ordinal);
 
            SmiMetaData metaData = _currentMetaData[ordinal];
            if (_currentConnection.IsKatmaiOrNewer) {
                return ValueUtilsSmi.GetSqlValue200(_readerEventSink, (SmiTypedGetterSetter)_currentColumnValuesV3, ordinal, metaData, _currentConnection.InternalContext);
            }
            return ValueUtilsSmi.GetSqlValue(_readerEventSink, _currentColumnValuesV3, ordinal, metaData, _currentConnection.InternalContext); ;
        }
 
        public override int GetSqlValues(object[] values) {
            EnsureCanGetCol( "GetSqlValues", 0);
 
            if (null == values) {
                throw ADP.ArgumentNull("values");
            }
 
            int copyLength = (values.Length < _visibleColumnCount) ? values.Length : _visibleColumnCount;
            for(int i=0; i<copyLength; i++) {
                values[_indexMap[i]] = GetSqlValue(i);
            }
 
            return copyLength;
        }
 
    //
    //    ISqlReader methods/properties
    //
        public override bool HasRows {
            get {return _hasRows;}
        }
 
    //
    //    SqlDataReader method/properties
    //
        public override Stream GetStream(int ordinal) {
            EnsureCanGetCol("GetStream", ordinal);
 
            SmiQueryMetaData metaData = _currentMetaData[ordinal];
 
            // For non-null, non-variant types with sequential access, we support proper streaming
            if ((metaData.SqlDbType != SqlDbType.Variant) && (IsCommandBehavior(CommandBehavior.SequentialAccess)) && (!ValueUtilsSmi.IsDBNull(_readerEventSink, _currentColumnValuesV3, ordinal))) {
                if (HasActiveStreamOrTextReaderOnColumn(ordinal)) {
                    throw ADP.NonSequentialColumnAccess(ordinal, ordinal + 1);
                }
                _currentStream = ValueUtilsSmi.GetSequentialStream(_readerEventSink, _currentColumnValuesV3, ordinal, metaData);
                return _currentStream;
            }
            else {
                return ValueUtilsSmi.GetStream(_readerEventSink, _currentColumnValuesV3, ordinal, metaData);
            }
        }
 
        public override TextReader GetTextReader(int ordinal) {
            EnsureCanGetCol("GetTextReader", ordinal);
            
            SmiQueryMetaData metaData = _currentMetaData[ordinal];
 
            // For non-variant types with sequential access, we support proper streaming
            if ((metaData.SqlDbType != SqlDbType.Variant) && (IsCommandBehavior(CommandBehavior.SequentialAccess)) && (!ValueUtilsSmi.IsDBNull(_readerEventSink, _currentColumnValuesV3, ordinal))) {
                if (HasActiveStreamOrTextReaderOnColumn(ordinal)) {
                    throw ADP.NonSequentialColumnAccess(ordinal, ordinal + 1);
                }
                _currentTextReader = ValueUtilsSmi.GetSequentialTextReader(_readerEventSink, _currentColumnValuesV3, ordinal, metaData);
                return _currentTextReader;
            }
            else {
                return ValueUtilsSmi.GetTextReader(_readerEventSink, _currentColumnValuesV3, ordinal, metaData);
            }
        }
 
        public override XmlReader GetXmlReader(int ordinal) {
            // NOTE: sql_variant can not contain a XML data type: http://msdn.microsoft.com/en-us/library/ms173829.aspx
            
            EnsureCanGetCol("GetXmlReader", ordinal);
            if (_currentMetaData[ordinal].SqlDbType != SqlDbType.Xml) {
                throw ADP.InvalidCast();
            }
 
            Stream stream = null;
            if ((IsCommandBehavior(CommandBehavior.SequentialAccess)) && (!ValueUtilsSmi.IsDBNull(_readerEventSink, _currentColumnValuesV3, ordinal))) {
                if (HasActiveStreamOrTextReaderOnColumn(ordinal)) {
                    throw ADP.NonSequentialColumnAccess(ordinal, ordinal + 1);
                }
                // Need to bypass the type check since streams are not usually allowed on XML types
                _currentStream = ValueUtilsSmi.GetSequentialStream(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal], bypassTypeCheck: true);
                stream = _currentStream;
            }
            else {
                stream = ValueUtilsSmi.GetStream(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal], bypassTypeCheck: true);
            }
 
            return SqlXml.CreateSqlXmlReader(stream);
        }
 
    //
    //    Internal reader state
    //
 
        // Logical state of reader/resultset as viewed by the client
        //    Does not necessarily match up with server state.
        internal enum PositionState
            {
            BeforeResults,            // Before all resultset in request
            BeforeRows,                // Before all rows in current resultset
            OnRow,                    // On a valid row in the current resultset
            AfterRows,                // After all rows in current resultset
            AfterResults            // After all resultsets in request
            }
        private PositionState _currentPosition;        // Where is the reader relative to incoming results?
 
 
    //
    //    Fields
    //
        private bool                        _isOpen;                    // Is the reader open?
        private SmiQueryMetaData[]          _currentMetaData;           // Metadata for current resultset
        private int[]                       _indexMap;                  // map of indices for visible column
        private int                         _visibleColumnCount;        // number of visible columns
        private DataTable                   _schemaTable;               // Cache of user-visible extended metadata while in results.
        private ITypedGetters               _currentColumnValues;       // Unmanaged-managed data marshalers/cache
        private ITypedGettersV3             _currentColumnValuesV3;     // Unmanaged-managed data marshalers/cache for SMI V3
        private bool                        _hasRows;                   // Are there any rows in the current resultset?  Must be able to say before moving to first row.
        private SmiEventStream              _eventStream;               // The event buffer that receives the events from the execution engine.
        private SmiRequestExecutor          _requestExecutor;           // The used to request actions from the execution engine.
        private SqlInternalConnectionSmi    _currentConnection;
        private ReaderEventSink             _readerEventSink;           // The event sink that will process events from the event buffer.
        private FieldNameLookup             _fieldNameLookup;           // cached lookup object to improve access time based on field name
        private SqlSequentialStreamSmi      _currentStream;             // The stream on the current column (if any)
        private SqlSequentialTextReaderSmi  _currentTextReader;         // The text reader on the current column (if any)
 
    //
    // Internal methods for use by other classes in project
    //
        // Constructor
        //
        //  Assumes that if there were any results, the first chunk of them are in the data stream
        //      (up to the first actual row or the end of the resultsets).
        unsafe internal SqlDataReaderSmi (
                SmiEventStream              eventStream,        // the event stream that receives the events from the execution engine
                SqlCommand                  parent,             // command that owns reader
                CommandBehavior             behavior,           // behavior specified for this execution
                SqlInternalConnectionSmi    connection,         // connection that owns everybody
                SmiEventSink                parentSink,         // Event sink of parent command
                SmiRequestExecutor          requestExecutor
            ) : base( parent, behavior ) {  // 
            _eventStream = eventStream;
            _currentConnection = connection;
            _readerEventSink = new ReaderEventSink( this, parentSink );
            _currentPosition = PositionState.BeforeResults;
            _isOpen = true;
            _indexMap = null;
            _visibleColumnCount = 0;
            _currentStream = null;
            _currentTextReader = null;
            _requestExecutor = requestExecutor;
        }
 
        internal override SmiExtendedMetaData[] GetInternalSmiMetaData() {
            if (null == _currentMetaData || _visibleColumnCount == this.InternalFieldCount) {
                return _currentMetaData;
            }
            else {
#if DEBUG
                // DEVNOTE: Interpretation of returned array currently depends on hidden columns
                //  always appearing at the end, since there currently is no access to the index map
                //  outside of this class.  In Debug code, we check this assumption.
                bool sawHiddenColumn = false;
#endif
                SmiExtendedMetaData[] visibleMetaData = new SmiExtendedMetaData[_visibleColumnCount];
                for(int i=0; i<_visibleColumnCount; i++) {
#if DEBUG
                    if (_currentMetaData[_indexMap[i]].IsHidden.IsTrue) {
                        sawHiddenColumn = true;
                    }
                    else {
                        Debug.Assert(!sawHiddenColumn);
                    }
#endif
                    visibleMetaData[i] = _currentMetaData[_indexMap[i]];
                }
 
                return visibleMetaData;
            }
        }
 
        internal override int GetLocaleId(int ordinal) {
            EnsureCanGetMetaData( "GetLocaleId" );
            return (int)_currentMetaData[ordinal].LocaleId;
        }
    
        //
        // Private implementation methods
        //
 
        private int InternalFieldCount {
            get {
                if ( FNotInResults() ) {
                    return 0;
                }
                else {
                    return _currentMetaData.Length;
                }
            }
        }
 
        // Have we cleaned up internal resources?
        private bool IsReallyClosed() {
            return !_isOpen;
        }
 
        // Central checkpoint for closed recordset.
        //    Any code that requires an open recordset should call this method first!
        //    Especially any code that accesses unmanaged memory structures whose lifetime
        //      matches the lifetime of the unmanaged recordset.
        internal void ThrowIfClosed( string operationName ) {
            if (IsClosed)
                throw ADP.DataReaderClosed( operationName );
        }
 
        // Central checkpoint to ensure the requested column can be accessed.
        //    Calling this function serves to notify that it has been accessed by the user.
        [SuppressMessage("Microsoft.Performance", "CA1801:AvoidUnusedParameters")] // for future compatibility
        private void EnsureCanGetCol( string operationName, int ordinal) {
            EnsureOnRow( operationName );
        }
 
        internal void EnsureOnRow( string operationName ) {
            ThrowIfClosed( operationName );
            if (_currentPosition != PositionState.OnRow) {
                throw SQL.InvalidRead();
            }
        }
 
        internal void EnsureCanGetMetaData( string operationName ) {
            ThrowIfClosed( operationName );
            if (FNotInResults()) {
                throw SQL.InvalidRead(); // 
            }
        }
 
        private bool FInResults() {
            return !FNotInResults();
        }
 
        private bool FNotInResults() {
            return (PositionState.AfterResults == _currentPosition || PositionState.BeforeResults == _currentPosition);
        }
 
        private void MetaDataAvailable( SmiQueryMetaData[] md, bool nextEventIsRow ) {
            Debug.Assert( _currentPosition != PositionState.AfterResults );
            
            _currentMetaData = md;
            _hasRows = nextEventIsRow;
            _fieldNameLookup = null;
            _schemaTable = null; // will be rebuilt based on new metadata
            _currentPosition = PositionState.BeforeRows;
 
            // calculate visible column indices
            _indexMap = new int[_currentMetaData.Length];
            int i;
            int visibleCount = 0;
            for(i=0; i<_currentMetaData.Length; i++) {
                if (!_currentMetaData[i].IsHidden.IsTrue) {
                    _indexMap[visibleCount] = i;
                    visibleCount++;
                }
            }
            _visibleColumnCount = visibleCount;
        }
 
        private bool HasActiveStreamOrTextReaderOnColumn(int columnIndex) {
            bool active = false;
 
            active |= ((_currentStream != null) && (_currentStream.ColumnIndex == columnIndex));
            active |= ((_currentTextReader != null) && (_currentTextReader.ColumnIndex == columnIndex));
 
            return active;
        }
 
        // Obsolete V2- method
        private void RowAvailable( ITypedGetters row ) {
            Debug.Assert( _currentPosition != PositionState.AfterResults );
 
            _currentColumnValues = row;
            _currentPosition = PositionState.OnRow;
        }
 
        private void RowAvailable( ITypedGettersV3 row ) {
            Debug.Assert( _currentPosition != PositionState.AfterResults );
 
            _currentColumnValuesV3 = row;
            _currentPosition = PositionState.OnRow;
        }
 
        private void StatementCompleted( ) {
            Debug.Assert( _currentPosition != PositionState.AfterResults );
 
            _currentPosition = PositionState.AfterRows;
        }
 
        private void ResetResultSet() {
            _currentMetaData = null;
            _visibleColumnCount = 0;
            _schemaTable = null;
        }
 
        private void BatchCompleted() {
            Debug.Assert( _currentPosition != PositionState.AfterResults );
 
            ResetResultSet();
 
            _currentPosition = PositionState.AfterResults;
            _eventStream.Close( _readerEventSink );
        }
 
        // An implementation of the IEventSink interface that either performs
        // the required enviornment changes or forwards the events on to the
        // corresponding reader instance. Having the event sink be a separate
        // class keeps the IEventSink methods out of SqlDataReader's inteface.
 
        private sealed class ReaderEventSink : SmiEventSink_Default {
            private readonly SqlDataReaderSmi reader;
 
            internal ReaderEventSink( SqlDataReaderSmi reader, SmiEventSink parent )
                : base( parent ) {
                this.reader = reader;
            }
 
            internal override void MetaDataAvailable( SmiQueryMetaData[] md, bool nextEventIsRow ) {
                if (Bid.AdvancedOn) {
                    Bid.Trace("<sc.SqlDataReaderSmi.ReaderEventSink.MetaDataAvailable|ADV> %d#, md.Length=%d nextEventIsRow=%d.\n", reader.ObjectID, (null != md) ? md.Length : -1, nextEventIsRow);
                
                    if (null != md) {
                        for (int i=0; i < md.Length; i++) {
                            Bid.Trace("<sc.SqlDataReaderSmi.ReaderEventSink.MetaDataAvailable|ADV> %d#, metaData[%d] is %ls%ls\n",
                                            reader.ObjectID, i, md[i].GetType().ToString(), md[i].TraceString());
                        }
                    }
                }
                this.reader.MetaDataAvailable( md, nextEventIsRow );
            }
 
            // Obsolete V2- method
            internal override void RowAvailable( ITypedGetters row ) {
                if (Bid.AdvancedOn) {
                    Bid.Trace("<sc.SqlDataReaderSmi.ReaderEventSink.RowAvailable|ADV> %d# (v2).\n", reader.ObjectID);
                }
                this.reader.RowAvailable( row );
            }
 
            internal override void RowAvailable( ITypedGettersV3 row ) {
                if (Bid.AdvancedOn) {
                    Bid.Trace("<sc.SqlDataReaderSmi.ReaderEventSink.RowAvailable|ADV> %d# (ITypedGettersV3).\n", reader.ObjectID);
                }
                this.reader.RowAvailable( row );
            }
 
            internal override void RowAvailable(SmiTypedGetterSetter rowData) {
                if (Bid.AdvancedOn) {
                    Bid.Trace("<sc.SqlDataReaderSmi.ReaderEventSink.RowAvailable|ADV> %d# (SmiTypedGetterSetter).\n", reader.ObjectID);
                }
                this.reader.RowAvailable(rowData);
            }
 
            internal override void StatementCompleted( int recordsAffected ) {
                if (Bid.AdvancedOn) {
                    Bid.Trace("<sc.SqlDataReaderSmi.ReaderEventSink.StatementCompleted|ADV> %d# recordsAffected=%d.\n", reader.ObjectID, recordsAffected);
                }
 
                // devnote: relies on SmiEventSink_Default to pass event to parent
                // Both command and reader care about StatementCompleted, but for different reasons.
 
                base.StatementCompleted( recordsAffected );
                this.reader.StatementCompleted( );
            }
 
            internal override void BatchCompleted() {
                if (Bid.AdvancedOn) {
                    Bid.Trace("<sc.SqlDataReaderSmi.ReaderEventSink.BatchCompleted|ADV> %d#.\n", reader.ObjectID);
                }
 
                // devnote: relies on SmiEventSink_Default to pass event to parent
                //  parent's callback *MUST* come before reader's BatchCompleted, since
                //  reader will close the event stream during this call, and parent wants
                //  to extract parameter values before that happens.
 
                base.BatchCompleted();
                this.reader.BatchCompleted();
            }
        }
        
    }
}