//------------------------------------------------------------------------------ // // Copyright (c) Microsoft Corporation. All rights reserved. // // @owner Microsoft // @backupOwner Microsoft //--------------------------------------------------------------------- namespace System.Data.Query.ResultAssembly { using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Data.Common; using System.Data.Common.Internal.Materialization; using System.Data.Common.Utils; using System.Data.Metadata.Edm; using System.Data.Query.InternalTrees; using System.Data.Query.PlanCompiler; using System.Diagnostics; /// /// DbDataReader functionality for the bridge. /// internal sealed class BridgeDataReader : DbDataReader, IExtendedDataRecord { #region private state /// /// Object that holds the state needed by the coordinator and the root enumerator /// private Shaper Shaper; /// /// Enumerator over shapers for NextResult() calls. /// Null for nested data readers (depth > 0); /// private IEnumerator, CoordinatorFactory>> NextResultShaperInfoEnumerator; /// /// The coordinator we're responsible for returning results for. /// private CoordinatorFactory CoordinatorFactory; /// /// The default record (pre-read/past-end) state /// private RecordState DefaultRecordState; /// /// We delegate to this on our getters, to avoid duplicate code. /// private BridgeDataRecord DataRecord; /// /// Do we have a row to read? Determined in the constructor and /// should not be changed. /// private bool _hasRows; /// /// Set to true only when we've been closed through the Close() method /// private bool _isClosed; #endregion #region constructors /// /// Constructor used by the ResultColumn when doing GetValue, and by the Create factory /// method. /// /// /// /// enumrator of the shapers for NextResult() calls internal BridgeDataReader(Shaper shaper, CoordinatorFactory coordinatorFactory, int depth, IEnumerator, CoordinatorFactory>> nextResultShaperInfos) : base() { Debug.Assert(null != shaper, "null shaper?"); Debug.Assert(null != coordinatorFactory, "null coordinatorFactory?"); Debug.Assert(depth == 0 || nextResultShaperInfos == null, "Nested data readers should not have multiple result sets."); NextResultShaperInfoEnumerator = nextResultShaperInfos != null ? nextResultShaperInfos : null; SetShaper(shaper, coordinatorFactory, depth); } private void SetShaper(Shaper shaper, CoordinatorFactory coordinatorFactory, int depth) { Shaper = shaper; CoordinatorFactory = coordinatorFactory; DataRecord = new BridgeDataRecord(shaper, depth); // To determine whether there are any rows for this coordinator at this place in // the root enumerator, we pretty much just look at it's current record (we'll read // one if there isn't one waiting) and if it matches our coordinator, we've got rows. _hasRows = false; if (!Shaper.DataWaiting) { Shaper.DataWaiting = Shaper.RootEnumerator.MoveNext(); } if (Shaper.DataWaiting) { RecordState currentRecord = Shaper.RootEnumerator.Current; if (null != currentRecord) { _hasRows = (currentRecord.CoordinatorFactory == CoordinatorFactory); } } // Once we've created the root enumerator, we can get the default record state DefaultRecordState = coordinatorFactory.GetDefaultRecordState(Shaper); Debug.Assert(null != DefaultRecordState, "no default?"); } /// /// The primary factory method to produce the BridgeDataReader; given a store data /// reader and a column map, create the BridgeDataReader, hooking up the IteratorSources /// and ResultColumn Hierarchy. All construction of top level data readers go through /// this method. /// /// /// column map of the first result set /// enumerable of the column maps for NextResult() calls. /// static internal DbDataReader Create(DbDataReader storeDataReader, ColumnMap columnMap, MetadataWorkspace workspace, IEnumerable nextResultColumnMaps) { Debug.Assert(storeDataReader != null, "null storeDataReaders?"); Debug.Assert(columnMap != null, "null columnMap?"); Debug.Assert(workspace != null, "null workspace?"); var shaperInfo = CreateShaperInfo(storeDataReader, columnMap, workspace); DbDataReader result = new BridgeDataReader(shaperInfo.Key, shaperInfo.Value, /*depth:*/ 0, GetNextResultShaperInfo(storeDataReader, workspace, nextResultColumnMaps).GetEnumerator()); return result; } private static KeyValuePair, CoordinatorFactory> CreateShaperInfo(DbDataReader storeDataReader, ColumnMap columnMap, MetadataWorkspace workspace) { Debug.Assert(storeDataReader != null, "null storeDataReaders?"); Debug.Assert(columnMap != null, "null columnMap?"); Debug.Assert(workspace != null, "null workspace?"); System.Data.Common.QueryCache.QueryCacheManager cacheManager = workspace.GetQueryCacheManager(); const System.Data.Objects.MergeOption NoTracking = System.Data.Objects.MergeOption.NoTracking; ShaperFactory shaperFactory = Translator.TranslateColumnMap(cacheManager, columnMap, workspace, null, NoTracking, true); Shaper recordShaper = shaperFactory.Create(storeDataReader, null, workspace, System.Data.Objects.MergeOption.NoTracking, true); return new KeyValuePair, CoordinatorFactory>(recordShaper, recordShaper.RootCoordinator.TypedCoordinatorFactory); } private static IEnumerable, CoordinatorFactory>> GetNextResultShaperInfo(DbDataReader storeDataReader, MetadataWorkspace workspace, IEnumerable nextResultColumnMaps) { foreach (var nextResultColumnMap in nextResultColumnMaps) { yield return CreateShaperInfo(storeDataReader, nextResultColumnMap, workspace); } } #endregion #region helpers /// /// Implicitly close this (nested) data reader; will be called whenever /// the user has done a GetValue() or a Read() on a parent reader/record /// to ensure that we consume all our results. We do that because we /// our design requires us to be positioned at the next nested reader's /// first row. /// internal void CloseImplicitly() { Consume(); DataRecord.CloseImplicitly(); } /// /// Reads to the end of the source enumerator provided /// private void Consume() { while (ReadInternal()) ; } /// /// Figure out the CLR type from the TypeMetadata object; For scalars, /// we can get this from the metadata workspace, but for the rest, we /// just guess at "Object". You need to use the DataRecordInfo property /// to get better information for those. /// /// /// internal static Type GetClrTypeFromTypeMetadata(TypeUsage typeUsage) { Type result; PrimitiveType primitiveType; if (TypeHelpers.TryGetEdmType(typeUsage, out primitiveType)) { result = primitiveType.ClrEquivalentType; } else { if (TypeSemantics.IsReferenceType(typeUsage)) { result = typeof(EntityKey); } else if (TypeUtils.IsStructuredType(typeUsage)) { result = typeof(DbDataRecord); } else if (TypeUtils.IsCollectionType(typeUsage)) { result = typeof(DbDataReader); } else if (TypeUtils.IsEnumerationType(typeUsage)) { result = ((EnumType)typeUsage.EdmType).UnderlyingType.ClrEquivalentType; } else { result = typeof(object); } } return result; } #endregion #region data reader specific properties and methods /// /// implementation for DbDataReader.Depth property /// override public int Depth { get { AssertReaderIsOpen("Depth"); return DataRecord.Depth; } } /// /// implementation for DbDataReader.HasRows property /// override public bool HasRows { get { AssertReaderIsOpen("HasRows"); return _hasRows; } } /// /// implementation for DbDataReader.IsClosed property /// override public bool IsClosed { get { // Rather that try and track this in two places; we just delegate // to the data record that we constructed; it has more reasons to // have to know this than we do in the data reader. (Of course, // we look at our own closed state too...) return ((_isClosed) || DataRecord.IsClosed); } } /// /// implementation for DbDataReader.RecordsAffected property /// override public int RecordsAffected { get { int result = -1; // For nested readers, return -1 which is the default for queries. // We defer to the store reader for rows affected count. Note that for queries, // the provider is generally expected to return -1. // if (DataRecord.Depth == 0) { result = Shaper.Reader.RecordsAffected; } return result; } } /// /// Ensures that the reader is actually open, and throws an exception if not /// private void AssertReaderIsOpen(string methodName) { if (IsClosed) { if (DataRecord.IsImplicitlyClosed) { throw EntityUtil.ImplicitlyClosedDataReaderError(); } if (DataRecord.IsExplicitlyClosed) { throw EntityUtil.DataReaderClosed(methodName); } } } /// /// implementation for DbDataReader.Close() method /// override public void Close() { // Make sure we explicitly closed the data record, since that's what // where using to track closed state. DataRecord.CloseExplicitly(); if (!_isClosed) { _isClosed = true; if (0 == DataRecord.Depth) { // If we're the root collection, we want to ensure the remainder of // the result column hierarchy is closed out, to avoid dangling // references to it, should it be reused. We also want to physically // close out the source reader as well. Shaper.Reader.Close(); } else { // For non-root collections, we have to consume all the data, or we'll // not be positioned propertly for what comes afterward. Consume(); } } if (NextResultShaperInfoEnumerator != null) { NextResultShaperInfoEnumerator.Dispose(); NextResultShaperInfoEnumerator = null; } } /// /// implementation for DbDataReader.GetEnumerator() method /// [EditorBrowsableAttribute(EditorBrowsableState.Never)] override public IEnumerator GetEnumerator() { IEnumerator result = new DbEnumerator((IDataReader)this, true); // We always want to close the reader; return result; } /// /// implementation for DbDataReader.GetSchemaTable() method /// /// This is awaiting some common code /// /// /// GetSchemaTable is not supported at this time override public DataTable GetSchemaTable() { throw EntityUtil.NotSupported(System.Data.Entity.Strings.ADP_GetSchemaTableIsNotSupported); } /// /// implementation for DbDataReader.NextResult() method /// /// override public bool NextResult() { AssertReaderIsOpen("NextResult"); // If there is a next result set available, serve it. if (NextResultShaperInfoEnumerator != null && Shaper.Reader.NextResult() && NextResultShaperInfoEnumerator.MoveNext()) { Debug.Assert(DataRecord.Depth == 0, "Nested data readers should not have multiple result sets."); var nextResultShaperInfo = NextResultShaperInfoEnumerator.Current; DataRecord.CloseImplicitly(); SetShaper(nextResultShaperInfo.Key, nextResultShaperInfo.Value, depth: 0); return true; } if (0 == DataRecord.Depth) { // NOTE:: this is required to ensure that output parameter values // are set in SQL Server, and other providers where they come after // the results. CommandHelper.ConsumeReader(Shaper.Reader); } else { // For nested readers, make sure we're positioned properly for // the following columns... Consume(); } // SQLBUDT #631726 - ensure we close the records that may be // outstanding... // SQLBUDT #632494 - do this after we consume the underlying reader // so we don't run result assembly through it... CloseImplicitly(); // Reset any state on our attached data record, since we've now // gone past the end of the reader. DataRecord.SetRecordSource(null, false); return false; } /// /// implementation for DbDataReader.Read() method /// /// override public bool Read() { AssertReaderIsOpen("Read"); // First of all we need to inform each of the nested records that // have been returned that they're "implicitly" closed -- that is // we've moved on. This will also ensure that any records remaining // in any active nested readers are consumed DataRecord.CloseImplicitly(); // OK, now go ahead and advance the source enumerator and set the // record source up bool result = ReadInternal(); DataRecord.SetRecordSource(Shaper.RootEnumerator.Current, result); return result; } /// /// Internal read method; does the work of advancing the root enumerator /// as needed and determining whether it's current record is for our /// coordinator. The public Read method does the assertions and such that /// we don't want to do when we're called from internal methods to do things /// like consume the rest of the reader's contents. /// /// /// private bool ReadInternal() { bool result = false; // If there's nothing waiting for the root enumerator, then attempt // to advance it. if (!Shaper.DataWaiting) { Shaper.DataWaiting = Shaper.RootEnumerator.MoveNext(); } // If we have some data (we may have just read it above) then figure // out who it belongs to-- us or someone else. We also skip over any // records that are for our children (nested readers); if we're being // asked to read, it's too late for them to read them. while (Shaper.DataWaiting && Shaper.RootEnumerator.Current.CoordinatorFactory != CoordinatorFactory && Shaper.RootEnumerator.Current.CoordinatorFactory.Depth > CoordinatorFactory.Depth) { Shaper.DataWaiting = Shaper.RootEnumerator.MoveNext(); } if (Shaper.DataWaiting) { // We found something, go ahead and indicate to the shaper we want // this record, set up the data record, etc. if (Shaper.RootEnumerator.Current.CoordinatorFactory == CoordinatorFactory) { Shaper.DataWaiting = false; Shaper.RootEnumerator.Current.AcceptPendingValues(); result = true; } } return result; } #endregion #region metadata properties and methods /// /// implementation for DbDataReader.DataRecordInfo property /// public DataRecordInfo DataRecordInfo { get { AssertReaderIsOpen("DataRecordInfo"); DataRecordInfo result; if (DataRecord.HasData) { result = DataRecord.DataRecordInfo; } else { result = DefaultRecordState.DataRecordInfo; } return result; } } /// /// implementation for DbDataReader.FieldCount property /// override public int FieldCount { get { AssertReaderIsOpen("FieldCount"); // In this method, we need to return a constant value, regardless // of how polymorphic the result is, because there is a lot of code // in the wild that expects it to be constant; Ideally, we'd return // the number of columns in the actual type that we have, but since // that would probably break folks, I'm leaving it at returning the // base set of columns that all rows will have. int result = DefaultRecordState.ColumnCount; return result; } } /// /// implementation for DbDataReader.GetDataTypeName() method /// /// /// override public string GetDataTypeName(int ordinal) { AssertReaderIsOpen("GetDataTypeName"); string result; if (DataRecord.HasData) { result = DataRecord.GetDataTypeName(ordinal); } else { result = TypeHelpers.GetFullName(DefaultRecordState.GetTypeUsage(ordinal)); } return result; } /// /// implementation for DbDataReader.GetFieldType() method /// /// /// override public Type GetFieldType(int ordinal) { AssertReaderIsOpen("GetFieldType"); Type result; if (DataRecord.HasData) { result = DataRecord.GetFieldType(ordinal); } else { result = GetClrTypeFromTypeMetadata(DefaultRecordState.GetTypeUsage(ordinal)); } return result; } /// /// implementation for DbDataReader.GetName() method /// /// /// override public string GetName(int ordinal) { AssertReaderIsOpen("GetName"); string result; if (DataRecord.HasData) { result = DataRecord.GetName(ordinal); } else { result = DefaultRecordState.GetName(ordinal); } return result; } /// /// implementation for DbDataReader.GetOrdinal() method /// /// /// override public int GetOrdinal(string name) { AssertReaderIsOpen("GetOrdinal"); int result; if (DataRecord.HasData) { result = DataRecord.GetOrdinal(name); } else { result = DefaultRecordState.GetOrdinal(name); } return result; } /// /// implementation for DbDataReader.GetProviderSpecificFieldType() method /// /// /// /// GetProviderSpecificFieldType is not supported at this time [EditorBrowsableAttribute(EditorBrowsableState.Never)] override public Type GetProviderSpecificFieldType(int ordinal) { throw EntityUtil.NotSupported(); } #endregion #region data record properties and methods //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// // // The remaining methods on this class delegate to the inner data record // //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////// #region general getter methods and indexer properties /// /// implementation for DbDataReader[ordinal] indexer value getter /// override public object this[int ordinal] { get { return DataRecord[ordinal]; } } /// /// implementation for DbDataReader[name] indexer value getter /// override public object this[string name] { get { int ordinal = GetOrdinal(name); return DataRecord[ordinal]; } } /// /// implementation for DbDataReader.GetProviderSpecificValue() method /// /// /// /// GetProviderSpecificValue is not supported at this time [EditorBrowsableAttribute(EditorBrowsableState.Never)] public override object GetProviderSpecificValue(int ordinal) { throw EntityUtil.NotSupported(); } /// /// implementation for DbDataReader.GetProviderSpecificValues() method /// /// /// /// GetProviderSpecificValues is not supported at this time [EditorBrowsableAttribute(EditorBrowsableState.Never)] public override int GetProviderSpecificValues(object[] values) { throw EntityUtil.NotSupported(); } /// /// implementation for DbDataReader.GetValue() method /// /// /// override public Object GetValue(int ordinal) { return DataRecord.GetValue(ordinal); } /// /// implementation for DbDataReader.GetValues() method /// /// /// override public int GetValues(object[] values) { return DataRecord.GetValues(values); } #endregion #region simple scalar value getter methods /// /// implementation for DbDataReader.GetBoolean() method /// /// /// override public bool GetBoolean(int ordinal) { return DataRecord.GetBoolean(ordinal); } /// /// implementation for DbDataReader.GetByte() method /// /// /// override public byte GetByte(int ordinal) { return DataRecord.GetByte(ordinal); } /// /// implementation for DbDataReader.GetChar() method /// /// /// override public char GetChar(int ordinal) { return DataRecord.GetChar(ordinal); } /// /// implementation for DbDataReader.GetDateTime() method /// /// /// override public DateTime GetDateTime(int ordinal) { return DataRecord.GetDateTime(ordinal); } /// /// implementation for DbDataReader.GetDecimal() method /// /// /// override public Decimal GetDecimal(int ordinal) { return DataRecord.GetDecimal(ordinal); } /// /// implementation for DbDataReader.GetDouble() method /// /// /// override public double GetDouble(int ordinal) { return DataRecord.GetDouble(ordinal); } /// /// implementation for DbDataReader.GetFloat() method /// /// /// override public float GetFloat(int ordinal) { return DataRecord.GetFloat(ordinal); } /// /// implementation for DbDataReader.GetGuid() method /// /// /// override public Guid GetGuid(int ordinal) { return DataRecord.GetGuid(ordinal); } /// /// implementation for DbDataReader.GetInt16() method /// /// /// override public Int16 GetInt16(int ordinal) { return DataRecord.GetInt16(ordinal); } /// /// implementation for DbDataReader.GetInt32() method /// /// /// override public Int32 GetInt32(int ordinal) { return DataRecord.GetInt32(ordinal); } /// /// implementation for DbDataReader.GetInt64() method /// /// /// override public Int64 GetInt64(int ordinal) { return DataRecord.GetInt64(ordinal); } /// /// implementation for DbDataReader.GetString() method /// /// /// override public String GetString(int ordinal) { return DataRecord.GetString(ordinal); } /// /// implementation for DbDataReader.IsDBNull() method /// /// /// override public bool IsDBNull(int ordinal) { return DataRecord.IsDBNull(ordinal); } #endregion #region array scalar value getter methods /// /// implementation for DbDataReader.GetBytes() method /// /// /// /// /// /// /// override public long GetBytes(int ordinal, long dataOffset, byte[] buffer, int bufferOffset, int length) { return DataRecord.GetBytes(ordinal, dataOffset, buffer, bufferOffset, length); } /// /// implementation for DbDataReader.GetChars() method /// /// /// /// /// /// /// override public long GetChars(int ordinal, long dataOffset, char[] buffer, int bufferOffset, int length) { return DataRecord.GetChars(ordinal, dataOffset, buffer, bufferOffset, length); } #endregion #region complex type getters /// /// implementation for DbDataReader.GetData() method /// /// /// override protected DbDataReader GetDbDataReader(int ordinal) { return (DbDataReader)DataRecord.GetData(ordinal); } /// /// implementation for DbDataReader.GetDataRecord() method /// /// /// public DbDataRecord GetDataRecord(int ordinal) { return DataRecord.GetDataRecord(ordinal); } /// /// Used to return a nested result /// /// /// public DbDataReader GetDataReader(int ordinal) { return this.GetDbDataReader(ordinal); } #endregion #endregion } }