1 //------------------------------------------------------------------------------
2 // <copyright file="BridgeDataRecord.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
7 //---------------------------------------------------------------------
9 namespace System.Data.Query.ResultAssembly {
11 using System.Collections;
12 using System.Collections.Generic;
13 using System.ComponentModel;
15 using System.Data.Common;
16 using System.Data.Common.CommandTrees;
17 using System.Data.Common.Internal.Materialization;
18 using System.Data.Metadata.Edm;
19 using System.Data.Query.PlanCompiler;
20 using System.Diagnostics;
22 using System.Threading;
25 /// DbDataRecord functionality for the bridge.
27 sealed internal class BridgeDataRecord : DbDataRecord, IExtendedDataRecord {
32 /// How deep down the hierarchy are we?
34 internal readonly int Depth;
37 /// Where the data comes from
39 private readonly Shaper<RecordState> Shaper;
42 /// The current record that we're responsible for; this will change from row to row
43 /// on the source data reader. Will be set to null when parent the enumerator has
46 private RecordState _source;
49 /// Current state of the record;
51 private Status _status;
59 /// the column ordinal of the last column read, used to enforce sequential access
61 private int _lastColumnRead;
64 /// the last data offset of a read returned, used to enforce sequential access
66 private long _lastDataOffsetRead;
69 /// the last ordinal that IsDBNull was called for; used to avoid re-reading the value;
71 private int _lastOrdinalCheckedForNull;
74 /// value, of the last column that IsDBNull was called for; used to avoid re-reading the value;
76 private object _lastValueCheckedForNull;
79 /// Set to the current data record when we hand them out. (For data reader columns,
80 /// we use it's attached data record) The Close, GetValue and Read methods ensures
81 /// that this is implicitly closed when we move past it.
83 private BridgeDataReader _currentNestedReader;
84 private BridgeDataRecord _currentNestedRecord;
90 internal BridgeDataRecord(Shaper<RecordState> shaper, int depth)
92 Debug.Assert(null != shaper, "null shaper?");
95 // Rest of state is set through the SetRecordSource method.
100 #region state management
103 /// Called by our owning datareader when it is explicitly closed; will
104 /// not be called for nested structures, they go through the ClosedImplicitly.
107 internal void CloseExplicitly() {
108 _status = Status.ClosedExplicitly;
109 _source = null; // can't have data any longer once we're closed.
110 CloseNestedObjectImplicitly();
114 /// Called by our parent object to ensure that we're marked as implicitly
115 /// closed; will not be called for root level data readers.
117 internal void CloseImplicitly() {
118 _status = Status.ClosedImplicitly;
119 _source = null; // can't have data any longer once we're closed.
120 CloseNestedObjectImplicitly();
124 /// Ensure that whatever column we're currently processing is implicitly closed;
126 private void CloseNestedObjectImplicitly() {
127 // it would be nice to use Interlocked.Exchange to avoid multi-thread `race condition risk
128 // when the the bridge is being misused by the user accessing it with multiple threads.
129 // but this is called frequently enough to have a performance impact
130 BridgeDataRecord currentNestedRecord = _currentNestedRecord;
131 if (null != currentNestedRecord) {
132 _currentNestedRecord = null;
133 currentNestedRecord.CloseImplicitly();
135 BridgeDataReader currentNestedReader = _currentNestedReader;
136 if (null != currentNestedReader) {
137 _currentNestedReader = null;
138 currentNestedReader.CloseImplicitly();
143 /// Should be called after each Read on the data reader.
145 internal void SetRecordSource(RecordState newSource, bool hasData) {
146 Debug.Assert(null == _currentNestedRecord, "didn't close the nested record?");
147 Debug.Assert(null == _currentNestedReader, "didn't close the nested reader?");
149 // A peculiar behavior of IEnumerator is that when MoveNext() returns
150 // false, the Current still points to the last value, which is not
151 // what we really want to reflect here.
153 Debug.Assert(null != newSource, "hasData but null newSource?"); // this shouldn't happen...
159 _status = Status.Open;
161 _lastColumnRead = -1;
162 _lastDataOffsetRead = -1;
163 _lastOrdinalCheckedForNull = -1;
164 _lastValueCheckedForNull = null;
169 #region assertion helpers
172 /// Ensures that the reader is actually open, and throws an exception if not
174 private void AssertReaderIsOpen() {
175 if (IsExplicitlyClosed) {
176 throw EntityUtil.ClosedDataReaderError();
178 if (IsImplicitlyClosed) {
179 throw EntityUtil.ImplicitlyClosedDataReaderError();
186 private void AssertReaderIsOpenWithData() {
187 AssertReaderIsOpen();
190 throw EntityUtil.NoData();
195 /// Ensures that sequential access rules are being obeyed for non-array
196 /// getter methods, throws the appropriate exception if not. Also ensures
197 /// that the last column and array offset is set appropriately.
199 /// <param name="ordinal"></param>
200 private void AssertSequentialAccess(int ordinal) {
201 Debug.Assert(null != _source, "null _source?"); // we should have already called AssertReaderIsOpen.
203 if (ordinal < 0 || ordinal >= _source.ColumnCount) {
204 throw EntityUtil.ArgumentOutOfRange("ordinal");
206 if (_lastColumnRead >= ordinal) {
207 throw EntityUtil.NonSequentialColumnAccess(ordinal, _lastColumnRead + 1);
209 _lastColumnRead = ordinal;
210 // SQLBUDT #442001 -- we need to mark things that are not using GetBytes/GetChars
211 // in a way that prevents them from being read a second time
212 // using those methods. Pointing past any potential data is
214 _lastDataOffsetRead = long.MaxValue;
218 /// Ensures that sequential access rules are being obeyed for array offset
219 /// getter methods, throws the appropriate exception if not. Also ensures
220 /// that the last column and array offset is set appropriately.
222 /// <param name="ordinal"></param>
223 /// <param name="dataOffset"></param>
224 /// <param name="methodName"></param>
225 private void AssertSequentialAccess(int ordinal, long dataOffset, string methodName) {
226 Debug.Assert(null != _source, "null _source?"); // we should have already called AssertReaderIsOpen.
228 if (ordinal < 0 || ordinal >= _source.ColumnCount) {
229 throw EntityUtil.ArgumentOutOfRange("ordinal");
231 if (_lastColumnRead > ordinal || (_lastColumnRead == ordinal && _lastDataOffsetRead == long.MaxValue)) {
232 throw EntityUtil.NonSequentialColumnAccess(ordinal, _lastColumnRead + 1);
234 if (_lastColumnRead == ordinal) {
235 if (_lastDataOffsetRead >= dataOffset) {
236 throw EntityUtil.NonSequentialArrayOffsetAccess(dataOffset, _lastDataOffsetRead + 1, methodName);
238 // _lastDataOffsetRead will be set by GetBytes/GetChars, since we need to set it
239 // to the last offset that was actually read, which isn't necessarily what was
243 // Doin' a new thang...
244 _lastColumnRead = ordinal;
245 _lastDataOffsetRead = -1;
250 /// True when the record has data (SetRecordSource was called with true)
252 internal bool HasData {
254 bool result = (_source != null);
260 /// True so long as we haven't been closed either implicity or explictly
262 internal bool IsClosed {
264 return (_status != Status.Open);
269 /// Determine whether we have been explicitly closed by our owning
270 /// data reader; only data records that are responsible for processing
271 /// data reader requests can be explicitly closed;
273 internal bool IsExplicitlyClosed {
275 return (_status == Status.ClosedExplicitly);
280 /// Determine whether the parent data reader or record moved on from
281 /// where we can be considered open, (because the consumer of the
282 /// parent data reader/record called either the GetValue() or Read()
283 /// methods on the parent);
285 internal bool IsImplicitlyClosed {
287 return (_status == Status.ClosedImplicitly);
292 #region metadata properties and methods
295 /// implementation of DbDataRecord.DataRecordInfo property
297 public DataRecordInfo DataRecordInfo {
299 AssertReaderIsOpen();
300 DataRecordInfo result = _source.DataRecordInfo;
306 /// implementation of DbDataRecord.FieldCount property
308 override public int FieldCount {
310 AssertReaderIsOpen();
311 return _source.ColumnCount;
316 /// Helper method to get the edm TypeUsage for the specified column;
318 /// If the column requested is a record, we'll pick up whatever the
319 /// current record says it is, otherwise we'll take whatever was stored
320 /// on our record state.
322 /// <param name="ordinal"></param>
323 /// <returns></returns>
324 private TypeUsage GetTypeUsage(int ordinal) {
325 // Some folks are picky about the exception we throw
326 if (ordinal < 0 || ordinal >= _source.ColumnCount) {
327 throw EntityUtil.ArgumentOutOfRange("ordinal");
332 RecordState recordState = _source.CurrentColumnValues[ordinal] as RecordState;
333 if (null != recordState) {
334 result = recordState.DataRecordInfo.RecordType;
337 result = _source.GetTypeUsage(ordinal);
343 /// implementation of DbDataRecord.GetDataTypeName() method
345 /// <param name="ordinal"></param>
346 /// <returns></returns>
347 override public string GetDataTypeName(int ordinal) {
348 AssertReaderIsOpenWithData();
349 return TypeHelpers.GetFullName(GetTypeUsage(ordinal));
353 /// implementation of DbDataRecord.GetFieldType() method
355 /// <param name="ordinal"></param>
356 /// <returns></returns>
357 override public Type GetFieldType(int ordinal) {
358 AssertReaderIsOpenWithData();
359 return BridgeDataReader.GetClrTypeFromTypeMetadata(GetTypeUsage(ordinal));
363 /// implementation of DbDataRecord.GetName() method
365 /// <param name="ordinal"></param>
366 /// <returns></returns>
367 override public string GetName(int ordinal) {
368 AssertReaderIsOpen();
369 return _source.GetName(ordinal);
373 /// implementation of DbDataRecord.GetOrdinal() method
375 /// <param name="name"></param>
376 /// <returns></returns>
377 override public int GetOrdinal(string name) {
378 AssertReaderIsOpen();
379 return _source.GetOrdinal(name);
384 #region general getter methods and indexer properties
387 /// implementation for DbDataRecord[ordinal] indexer property
389 /// <param name="ordinal"></param>
390 /// <returns></returns>
391 override public object this[int ordinal] {
393 return GetValue(ordinal);
398 /// implementation for DbDataRecord[name] indexer property
400 /// <param name="name"></param>
401 /// <returns></returns>
402 override public object this[string name] {
404 return GetValue(GetOrdinal(name));
409 /// implementation for DbDataRecord.GetValue() method
411 /// This method is used by most of the column getters on this
412 /// class to retrieve the value from the source reader. Therefore,
413 /// it asserts all the good things, like that the reader is open,
414 /// and that it has data, and that you're not trying to circumvent
415 /// sequential access requirements.
417 /// <param name="ordinal"></param>
418 /// <returns></returns>
419 override public Object GetValue(int ordinal) {
420 AssertReaderIsOpenWithData();
421 AssertSequentialAccess(ordinal);
423 object result = null;
425 if (ordinal == _lastOrdinalCheckedForNull) {
426 result = _lastValueCheckedForNull;
429 _lastOrdinalCheckedForNull = -1;
430 _lastValueCheckedForNull = null;
432 CloseNestedObjectImplicitly();
434 result = _source.CurrentColumnValues[ordinal];
436 // If we've got something that's nested, then make sure we
437 // update the current nested record with it so we can be certain
438 // to close it implicitly when we move past it.
439 if (_source.IsNestedObject(ordinal)) {
440 result = GetNestedObjectValue(result);
447 /// For nested objects (records/readers) we have a bit more work to do; this
448 /// method extracts it all out from the main GetValue method so it doesn't
449 /// have to be so big.
451 /// <param name="result"></param>
452 /// <returns></returns>
453 private object GetNestedObjectValue(object result) {
454 if (result != DBNull.Value) {
455 RecordState recordState = result as RecordState;
456 if (null != recordState) {
457 if (recordState.IsNull) {
458 result = DBNull.Value;
461 BridgeDataRecord nestedRecord = new BridgeDataRecord(Shaper, Depth + 1);
462 nestedRecord.SetRecordSource(recordState, true);
463 result = nestedRecord;
464 _currentNestedRecord = nestedRecord;
465 _currentNestedReader = null;
469 Coordinator<RecordState> coordinator = result as Coordinator<RecordState>;
470 if (null != coordinator) {
471 BridgeDataReader nestedReader = new BridgeDataReader(Shaper, coordinator.TypedCoordinatorFactory, Depth + 1, nextResultShaperInfos: null);
472 result = nestedReader;
473 _currentNestedRecord = null;
474 _currentNestedReader = nestedReader;
477 Debug.Fail("unexpected type of nested object result: " + result.GetType().ToString());
485 /// implementation for DbDataRecord.GetValues() method
487 /// <param name="values"></param>
488 /// <returns></returns>
489 override public int GetValues(object[] values) {
490 EntityUtil.CheckArgumentNull(values, "values");
492 int copy = Math.Min(values.Length, FieldCount);
493 for (int i = 0; i < copy; ++i) {
494 values[i] = GetValue(i);
501 #region simple scalar value getter methods
504 /// implementation of DbDataRecord.GetBoolean() method
506 /// <param name="ordinal"></param>
507 /// <returns></returns>
508 override public bool GetBoolean(int ordinal) {
509 return (bool)GetValue(ordinal);
513 /// implementation of DbDataRecord.GetByte() method
515 /// <param name="ordinal"></param>
516 /// <returns></returns>
517 override public byte GetByte(int ordinal) {
518 return (byte)GetValue(ordinal);
522 /// implementation of DbDataRecord.GetChar() method
524 /// <param name="ordinal"></param>
525 /// <returns></returns>
526 override public char GetChar(int ordinal) {
527 return (char)GetValue(ordinal);
531 /// implementation of DbDataRecord.GetDateTime() method
533 /// <param name="ordinal"></param>
534 /// <returns></returns>
535 override public DateTime GetDateTime(int ordinal) {
536 return (DateTime)GetValue(ordinal);
540 /// implementation of DbDataRecord.GetDecimal() method
542 /// <param name="ordinal"></param>
543 /// <returns></returns>
544 override public Decimal GetDecimal(int ordinal) {
545 return (Decimal)GetValue(ordinal);
549 /// implementation of DbDataRecord.GetDouble() method
551 /// <param name="ordinal"></param>
552 /// <returns></returns>
553 override public double GetDouble(int ordinal) {
554 return (double)GetValue(ordinal);
558 /// implementation of DbDataRecord.GetFloat() method
560 /// <param name="ordinal"></param>
561 /// <returns></returns>
562 override public float GetFloat(int ordinal) {
563 return (float)GetValue(ordinal);
567 /// implementation of DbDataRecord.GetGuid() method
569 /// <param name="ordinal"></param>
570 /// <returns></returns>
571 override public Guid GetGuid(int ordinal) {
572 return (Guid)GetValue(ordinal);
576 /// implementation of DbDataRecord.GetInt16() method
578 /// <param name="ordinal"></param>
579 /// <returns></returns>
580 override public Int16 GetInt16(int ordinal) {
581 return (Int16)GetValue(ordinal);
585 /// implementation of DbDataRecord.GetInt32() method
587 /// <param name="ordinal"></param>
588 /// <returns></returns>
589 override public Int32 GetInt32(int ordinal) {
590 return (Int32)GetValue(ordinal);
594 /// implementation of DbDataRecord.GetInt64() method
596 /// <param name="ordinal"></param>
597 /// <returns></returns>
598 override public Int64 GetInt64(int ordinal) {
599 return (Int64)GetValue(ordinal);
603 /// implementation of DbDataRecord.GetString() method
605 /// <param name="ordinal"></param>
606 /// <returns></returns>
607 override public String GetString(int ordinal) {
608 return (String)GetValue(ordinal);
612 /// implementation of DbDataRecord.IsDBNull() method
614 /// <param name="ordinal"></param>
615 /// <returns></returns>
616 override public bool IsDBNull(int ordinal) {
617 // This seems like a hack, but the the problem is that I need
618 // to make sure I don't monkey with caching things, and if I
619 // call IsDBNull directly on the store reader, I'll potentially
620 // lose data because I'm expecting SequentialAccess rules.
622 object columnValue = GetValue(ordinal);
624 // Need to backup one because we technically didn't read the
625 // value yet but the GetValue method advanced our pointer to
626 // what the value was. Another hack, but it's way less code
627 // than trying to avoid advancing to begin with.
629 _lastDataOffsetRead = -1;
631 // So as to avoid reconstructing nested records, readers, and
632 // rereading data from the iterator source cache, we just cache
633 // the value we read and the ordinal it came from, so if someone
634 // is doing the right thing(TM) and calling IsDBNull before calling
635 // GetValue, we won't construct another one.
636 _lastValueCheckedForNull = columnValue;
637 _lastOrdinalCheckedForNull = ordinal;
639 bool result = (DBNull.Value == columnValue);
646 #region array scalar value getter methods
649 /// implementation for DbDataRecord.GetBytes() method
651 /// <param name="ordinal"></param>
652 /// <param name="dataOffset"></param>
653 /// <param name="buffer"></param>
654 /// <param name="bufferOffset"></param>
655 /// <param name="length"></param>
656 /// <returns></returns>
657 override public long GetBytes(int ordinal, long dataOffset, byte[] buffer, int bufferOffset, int length) {
658 AssertReaderIsOpenWithData();
659 AssertSequentialAccess(ordinal, dataOffset, "GetBytes");
661 long result = _source.GetBytes(ordinal, dataOffset, buffer, bufferOffset, length);
663 if (buffer != null) {
664 _lastDataOffsetRead = dataOffset + result - 1; // just what was read, nothing more.
670 /// implementation for DbDataRecord.GetChars() method
672 /// <param name="ordinal"></param>
673 /// <param name="dataOffset"></param>
674 /// <param name="buffer"></param>
675 /// <param name="bufferOffset"></param>
676 /// <param name="length"></param>
677 /// <returns></returns>
678 override public long GetChars(int ordinal, long dataOffset, char[] buffer, int bufferOffset, int length) {
679 AssertReaderIsOpenWithData();
680 AssertSequentialAccess(ordinal, dataOffset, "GetChars");
682 long result = _source.GetChars(ordinal, dataOffset, buffer, bufferOffset, length);
684 if (buffer != null) {
685 _lastDataOffsetRead = dataOffset + result - 1; // just what was read, nothing more.
692 #region complex type getters
695 /// implementation for DbDataRecord.GetData() method
697 /// <param name="ordinal"></param>
698 /// <returns></returns>
699 override protected DbDataReader GetDbDataReader(int ordinal) {
700 return (DbDataReader)GetValue(ordinal);
704 /// implementation for DbDataRecord.GetDataRecord() method
706 /// <param name="ordinal"></param>
707 /// <returns></returns>
708 public DbDataRecord GetDataRecord(int ordinal) {
709 return (DbDataRecord)GetValue(ordinal);
713 /// Used to return a nested result
715 /// <param name="ordinal"></param>
716 /// <returns></returns>
717 public DbDataReader GetDataReader(int ordinal) {
718 return this.GetDbDataReader(ordinal);