1 //------------------------------------------------------------------------------
2 // <copyright file="SqlDataReader.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 // <owner current="true" primary="true">Microsoft</owner>
6 // <owner current="true" primary="false">Microsoft</owner>
7 //------------------------------------------------------------------------------
9 namespace System.Data.SqlClient {
12 using System.Data.Sql;
13 using System.Data.SqlTypes;
15 using System.Runtime.InteropServices;
16 using System.Threading;
17 using System.Diagnostics; // for Conditional compilation
18 using System.Diagnostics.CodeAnalysis;
20 using Microsoft.SqlServer.Server;
21 using System.Data.ProviderBase;
22 using System.Data.Common;
23 using System.Threading.Tasks;
25 // SqlServer provider's implementation of ISqlReader.
26 // Supports ISqlReader and ISqlResultSet objects.
28 // User should never be able to create one of these themselves, nor subclass.
29 // This is accomplished by having no public override constructors.
30 internal sealed class SqlDataReaderSmi : SqlDataReader {
34 // IDBRecord properties
36 public override int FieldCount {
38 ThrowIfClosed( "FieldCount" );
39 return InternalFieldCount;
43 public override int VisibleFieldCount {
45 ThrowIfClosed("VisibleFieldCount");
47 if (FNotInResults()) {
51 return _visibleColumnCount;
56 // IDBRecord Metadata Methods
58 public override String GetName(int ordinal) {
59 EnsureCanGetMetaData( "GetName" );
60 return _currentMetaData[ordinal].Name;
63 public override String GetDataTypeName(int ordinal) {
64 EnsureCanGetMetaData( "GetDataTypeName" );
65 SmiExtendedMetaData md = _currentMetaData[ordinal];
66 if ( SqlDbType.Udt == md.SqlDbType ) {
67 return md.TypeSpecificNamePart1 + "." + md.TypeSpecificNamePart2 + "." + md.TypeSpecificNamePart3;
74 public override Type GetFieldType(int ordinal) {
75 EnsureCanGetMetaData( "GetFieldType" );
76 if (SqlDbType.Udt == _currentMetaData[ordinal].SqlDbType) {
77 return _currentMetaData[ordinal].Type;
80 return MetaType.GetMetaTypeFromSqlDbType(
81 _currentMetaData[ordinal].SqlDbType, _currentMetaData[ordinal].IsMultiValued).ClassType ;
85 override public Type GetProviderSpecificFieldType(int ordinal) {
86 EnsureCanGetMetaData( "GetProviderSpecificFieldType" );
88 if (SqlDbType.Udt == _currentMetaData[ordinal].SqlDbType) {
89 return _currentMetaData[ordinal].Type;
92 return MetaType.GetMetaTypeFromSqlDbType(
93 _currentMetaData[ordinal].SqlDbType, _currentMetaData[ordinal].IsMultiValued).SqlType ;
97 public override int Depth {
99 ThrowIfClosed( "Depth" );
104 public override Object GetValue(int ordinal) {
105 EnsureCanGetCol( "GetValue", ordinal);
106 SmiQueryMetaData metaData = _currentMetaData[ordinal];
107 if (_currentConnection.IsKatmaiOrNewer) {
108 return ValueUtilsSmi.GetValue200(_readerEventSink, (SmiTypedGetterSetter)_currentColumnValuesV3, ordinal, metaData, _currentConnection.InternalContext);
111 return ValueUtilsSmi.GetValue(_readerEventSink, _currentColumnValuesV3, ordinal, metaData, _currentConnection.InternalContext);
115 public override T GetFieldValue<T>(int ordinal) {
116 EnsureCanGetCol( "GetFieldValue<T>", ordinal);
117 SmiQueryMetaData metaData = _currentMetaData[ordinal];
119 if (_typeofINullable.IsAssignableFrom(typeof(T))) {
120 // If its a SQL Type or Nullable UDT
121 if (_currentConnection.IsKatmaiOrNewer) {
122 return (T)ValueUtilsSmi.GetSqlValue200(_readerEventSink, (SmiTypedGetterSetter)_currentColumnValuesV3, ordinal, metaData, _currentConnection.InternalContext);
125 return (T)ValueUtilsSmi.GetSqlValue(_readerEventSink, _currentColumnValuesV3, ordinal, metaData, _currentConnection.InternalContext);
129 // Otherwise Its a CLR or non-Nullable UDT
130 if (_currentConnection.IsKatmaiOrNewer) {
131 return (T)ValueUtilsSmi.GetValue200(_readerEventSink, (SmiTypedGetterSetter)_currentColumnValuesV3, ordinal, metaData, _currentConnection.InternalContext);
134 return (T)ValueUtilsSmi.GetValue(_readerEventSink, _currentColumnValuesV3, ordinal, metaData, _currentConnection.InternalContext);
139 public override Task<T> GetFieldValueAsync<T>(int ordinal, CancellationToken cancellationToken) {
140 // As per Async spec, Context Connections do not support async
141 return ADP.CreatedTaskWithException<T>(ADP.ExceptionWithStackTrace(SQL.NotAvailableOnContextConnection()));
144 override internal SqlBuffer.StorageType GetVariantInternalStorageType(int ordinal) {
145 Debug.Assert(null != _currentColumnValuesV3, "Attempting to get variant internal storage type without calling GetValue first");
146 if (IsDBNull(ordinal))
147 return SqlBuffer.StorageType.Empty;
149 SmiMetaData valueMetaData = _currentColumnValuesV3.GetVariantType(_readerEventSink, ordinal);
150 if (valueMetaData == null)
151 return SqlBuffer.StorageType.Empty;
153 return ValueUtilsSmi.SqlDbTypeToStorageType(valueMetaData.SqlDbType);
156 public override int GetValues(object[] values) {
157 EnsureCanGetCol( "GetValues", 0);
158 if (null == values) {
159 throw ADP.ArgumentNull("values");
162 int copyLength = (values.Length < _visibleColumnCount) ? values.Length : _visibleColumnCount;
163 for(int i=0; i<copyLength; i++) {
164 values[_indexMap[i]] = GetValue(i);
169 public override int GetOrdinal(string name) {
170 EnsureCanGetMetaData( "GetOrdinal" );
171 if (null == _fieldNameLookup) {
172 _fieldNameLookup = new FieldNameLookup( (IDataReader) this, -1 ); //
174 return _fieldNameLookup.GetOrdinal(name); // MDAC 71470
177 // Generic array access by column index (accesses column value)
178 public override object this[int ordinal] {
180 return GetValue( ordinal );
184 // Generic array access by column name (accesses column value)
185 public override object this[string strName] {
187 return GetValue( GetOrdinal( strName ) );
192 // IDataRecord Data Access methods
194 public override bool IsDBNull(int ordinal) {
195 EnsureCanGetCol( "IsDBNull", ordinal);
196 return ValueUtilsSmi.IsDBNull(_readerEventSink, _currentColumnValuesV3, ordinal);
199 public override Task<bool> IsDBNullAsync(int ordinal, CancellationToken cancellationToken) {
200 // As per Async spec, Context Connections do not support async
201 return ADP.CreatedTaskWithException<bool>(ADP.ExceptionWithStackTrace(SQL.NotAvailableOnContextConnection()));
204 public override bool GetBoolean(int ordinal) {
205 EnsureCanGetCol( "GetBoolean", ordinal);
206 return ValueUtilsSmi.GetBoolean(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal]);
209 public override byte GetByte(int ordinal) {
210 EnsureCanGetCol( "GetByte", ordinal);
211 return ValueUtilsSmi.GetByte(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal]);
214 public override long GetBytes(int ordinal, long fieldOffset, byte[] buffer, int bufferOffset, int length) {
215 EnsureCanGetCol( "GetBytes", ordinal);
216 return ValueUtilsSmi.GetBytes(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal], fieldOffset, buffer, bufferOffset, length, true);
219 // XmlReader support code calls this method.
220 internal override long GetBytesInternal(int ordinal, long fieldOffset, byte[] buffer, int bufferOffset, int length) {
221 EnsureCanGetCol( "GetBytes", ordinal);
222 return ValueUtilsSmi.GetBytesInternal(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal], fieldOffset, buffer, bufferOffset, length, false);
225 public override char GetChar(int ordinal) {
226 throw ADP.NotSupported();
229 public override long GetChars(int ordinal, long fieldOffset, char[] buffer, int bufferOffset, int length) {
230 EnsureCanGetCol( "GetChars", ordinal);
231 SmiExtendedMetaData metaData = _currentMetaData[ordinal];
232 if (IsCommandBehavior(CommandBehavior.SequentialAccess)) {
233 if (metaData.SqlDbType == SqlDbType.Xml) {
234 return GetStreamingXmlChars(ordinal, fieldOffset, buffer, bufferOffset, length);
237 return ValueUtilsSmi.GetChars(_readerEventSink, _currentColumnValuesV3, ordinal, metaData, fieldOffset, buffer, bufferOffset, length);
240 public override Guid GetGuid(int ordinal) {
241 EnsureCanGetCol( "GetGuid", ordinal);
242 return ValueUtilsSmi.GetGuid(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal]);
245 public override Int16 GetInt16(int ordinal) {
246 EnsureCanGetCol( "GetInt16", ordinal);
247 return ValueUtilsSmi.GetInt16(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal]);
250 public override Int32 GetInt32(int ordinal) {
251 EnsureCanGetCol( "GetInt32", ordinal);
252 return ValueUtilsSmi.GetInt32(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal]);
255 public override Int64 GetInt64(int ordinal) {
256 EnsureCanGetCol( "GetInt64", ordinal);
257 return ValueUtilsSmi.GetInt64(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal]);
260 public override Single GetFloat(int ordinal) {
261 EnsureCanGetCol( "GetFloat", ordinal);
262 return ValueUtilsSmi.GetSingle(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal]);
265 public override Double GetDouble(int ordinal) {
266 EnsureCanGetCol( "GetDouble", ordinal);
267 return ValueUtilsSmi.GetDouble(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal]);
270 public override String GetString(int ordinal) {
271 EnsureCanGetCol( "GetString", ordinal);
272 return ValueUtilsSmi.GetString(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal]);
275 public override Decimal GetDecimal(int ordinal) {
276 EnsureCanGetCol( "GetDecimal", ordinal);
277 return ValueUtilsSmi.GetDecimal(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal]);
280 public override DateTime GetDateTime(int ordinal) {
281 EnsureCanGetCol( "GetDateTime", ordinal);
282 return ValueUtilsSmi.GetDateTime(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal]);
286 // IDataReader properties
288 // Logically closed test. I.e. is this object closed as far as external access is concerned?
289 public override bool IsClosed {
291 return IsReallyClosed();
295 public override int RecordsAffected {
297 return base.Command.InternalRecordsAffected;
302 // IDataReader methods
304 internal override void CloseReaderFromConnection() {
305 // Context Connections do not support async - so there is no threading issues with closing from the connection
306 CloseInternal(closeConnection: false);
309 public override void Close() {
310 // Connection should be open at this point, so we can do multiple checks of HasEvents, and we may need to close the connection afterwards
311 CloseInternal(closeConnection: IsCommandBehavior(CommandBehavior.CloseConnection));
314 private void CloseInternal(bool closeConnection) {
316 Bid.ScopeEnter(out hscp, "<sc.SqlDataReaderSmi.Close|API> %d#", ObjectID);
317 bool processFinallyBlock = true;
322 // Process the remaining events. This makes sure that environment changes are applied and any errors are picked up.
323 while(_eventStream.HasEvents) {
324 _eventStream.ProcessEvent( _readerEventSink );
325 _readerEventSink.ProcessMessagesAndThrow(true);
328 // Close the request executor
329 _requestExecutor.Close(_readerEventSink);
330 _readerEventSink.ProcessMessagesAndThrow(true);
333 catch (Exception e) {
334 processFinallyBlock = ADP.IsCatchableExceptionType(e);
338 if (processFinallyBlock) {
341 if ((closeConnection) && (Connection != null)) {
345 Bid.ScopeLeave(ref hscp);
350 // Move to the next resultset
351 public override unsafe bool NextResult() {
352 ThrowIfClosed( "NextResult" );
354 bool hasAnotherResult = InternalNextResult(false);
356 return hasAnotherResult;
359 public override Task<bool> NextResultAsync(CancellationToken cancellationToken)
361 // Async not supported on Context Connections
362 return ADP.CreatedTaskWithException<bool>(ADP.ExceptionWithStackTrace(SQL.NotAvailableOnContextConnection()));
365 internal unsafe bool InternalNextResult(bool ignoreNonFatalMessages) {
366 IntPtr hscp = IntPtr.Zero;
367 if (Bid.AdvancedOn) {
368 Bid.ScopeEnter(out hscp, "<sc.SqlDataReaderSmi.InternalNextResult|ADV> %d#", ObjectID);
373 if( PositionState.AfterResults != _currentPosition )
375 // Consume any remaning rows in the current result.
377 while( InternalRead(ignoreNonFatalMessages) ) {
378 // This space intentionally left blank
381 // reset resultset metadata - it will be created again if there is a pending resultset
384 // Process the events until metadata is found or all of the
385 // available events have been consumed. If there is another
386 // result, the metadata for it will be available after the last
387 // read on the prior result.
389 while(null == _currentMetaData && _eventStream.HasEvents) {
390 _eventStream.ProcessEvent( _readerEventSink );
391 _readerEventSink.ProcessMessagesAndThrow(ignoreNonFatalMessages);
395 return PositionState.AfterResults != _currentPosition;
398 if (Bid.AdvancedOn) {
399 Bid.ScopeLeave(ref hscp);
404 public override bool Read() {
405 ThrowIfClosed( "Read" );
406 bool hasAnotherRow = InternalRead(false);
408 return hasAnotherRow;
411 public override Task<bool> ReadAsync(CancellationToken cancellationToken)
413 // Async not supported on Context Connections
414 return ADP.CreatedTaskWithException<bool>(ADP.ExceptionWithStackTrace(SQL.NotAvailableOnContextConnection()));
417 internal unsafe bool InternalRead(bool ignoreNonFatalErrors) {
418 IntPtr hscp = IntPtr.Zero;
419 if (Bid.AdvancedOn) {
420 Bid.ScopeEnter(out hscp, "<sc.SqlDataReaderSmi.InternalRead|ADV> %d#", ObjectID);
423 // Don't move unless currently in results.
426 // Set current row to null so we can see if we get a new one
427 _currentColumnValues = null;
428 _currentColumnValuesV3 = null;
431 if (_currentStream != null) {
432 _currentStream.SetClosed();
433 _currentStream = null;
435 if (_currentTextReader != null) {
436 _currentTextReader.SetClosed();
437 _currentTextReader = null;
440 // NOTE: SQLBUDT #386118 -- may indicate that we want to break this loop when we get a MessagePosted callback, but we can't prove that.
441 while( null == _currentColumnValues && // Did we find a row?
442 null == _currentColumnValuesV3 && // Did we find a V3 row?
443 FInResults() && // Was the batch terminated due to a serious error?
444 PositionState.AfterRows != _currentPosition && // Have we seen a statement completed event?
445 _eventStream.HasEvents ) { // Have we processed all events?
446 _eventStream.ProcessEvent( _readerEventSink );
447 _readerEventSink.ProcessMessagesAndThrow(ignoreNonFatalErrors);
451 return PositionState.OnRow == _currentPosition;
454 if (Bid.AdvancedOn) {
455 Bid.ScopeLeave(ref hscp);
460 public override DataTable GetSchemaTable() {
461 ThrowIfClosed( "GetSchemaTable" );
463 if ( null == _schemaTable && FInResults() )
466 DataTable schemaTable = new DataTable( "SchemaTable" );
467 schemaTable.Locale = System.Globalization.CultureInfo.InvariantCulture;
468 schemaTable.MinimumCapacity = InternalFieldCount;
470 DataColumn ColumnName = new DataColumn(SchemaTableColumn.ColumnName, typeof(System.String));
471 DataColumn Ordinal = new DataColumn(SchemaTableColumn.ColumnOrdinal, typeof(System.Int32));
472 DataColumn Size = new DataColumn(SchemaTableColumn.ColumnSize, typeof(System.Int32));
473 DataColumn Precision = new DataColumn(SchemaTableColumn.NumericPrecision, typeof(System.Int16));
474 DataColumn Scale = new DataColumn(SchemaTableColumn.NumericScale, typeof(System.Int16));
476 DataColumn DataType = new DataColumn(SchemaTableColumn.DataType, typeof(System.Type));
477 DataColumn ProviderSpecificDataType = new DataColumn(SchemaTableOptionalColumn.ProviderSpecificDataType, typeof(System.Type));
478 DataColumn ProviderType = new DataColumn(SchemaTableColumn.ProviderType, typeof(System.Int32));
479 DataColumn NonVersionedProviderType = new DataColumn(SchemaTableColumn.NonVersionedProviderType, typeof(System.Int32));
481 DataColumn IsLong = new DataColumn(SchemaTableColumn.IsLong, typeof(System.Boolean));
482 DataColumn AllowDBNull = new DataColumn(SchemaTableColumn.AllowDBNull, typeof(System.Boolean));
483 DataColumn IsReadOnly = new DataColumn(SchemaTableOptionalColumn.IsReadOnly, typeof(System.Boolean));
484 DataColumn IsRowVersion = new DataColumn(SchemaTableOptionalColumn.IsRowVersion, typeof(System.Boolean));
486 DataColumn IsUnique = new DataColumn(SchemaTableColumn.IsUnique, typeof(System.Boolean));
487 DataColumn IsKey = new DataColumn(SchemaTableColumn.IsKey, typeof(System.Boolean));
488 DataColumn IsAutoIncrement = new DataColumn(SchemaTableOptionalColumn.IsAutoIncrement, typeof(System.Boolean));
489 DataColumn IsHidden = new DataColumn(SchemaTableOptionalColumn.IsHidden, typeof(System.Boolean));
491 DataColumn BaseCatalogName = new DataColumn(SchemaTableOptionalColumn.BaseCatalogName, typeof(System.String));
492 DataColumn BaseSchemaName = new DataColumn(SchemaTableColumn.BaseSchemaName, typeof(System.String));
493 DataColumn BaseTableName = new DataColumn(SchemaTableColumn.BaseTableName, typeof(System.String));
494 DataColumn BaseColumnName = new DataColumn(SchemaTableColumn.BaseColumnName, typeof(System.String));
496 // unique to SqlClient
497 DataColumn BaseServerName = new DataColumn(SchemaTableOptionalColumn.BaseServerName, typeof(System.String));
498 DataColumn IsAliased = new DataColumn(SchemaTableColumn.IsAliased, typeof(System.Boolean));
499 DataColumn IsExpression = new DataColumn(SchemaTableColumn.IsExpression, typeof(System.Boolean));
500 DataColumn IsIdentity = new DataColumn("IsIdentity", typeof(System.Boolean));
501 // UDT specific. Holds UDT typename ONLY if the type of the column is UDT, otherwise the data type
502 DataColumn DataTypeName = new DataColumn("DataTypeName", typeof(System.String));
503 DataColumn UdtAssemblyQualifiedName = new DataColumn("UdtAssemblyQualifiedName", typeof(System.String));
504 // Xml metadata specific
505 DataColumn XmlSchemaCollectionDatabase = new DataColumn("XmlSchemaCollectionDatabase", typeof(System.String));
506 DataColumn XmlSchemaCollectionOwningSchema = new DataColumn("XmlSchemaCollectionOwningSchema", typeof(System.String));
507 DataColumn XmlSchemaCollectionName = new DataColumn("XmlSchemaCollectionName", typeof(System.String));
509 DataColumn IsColumnSet = new DataColumn("IsColumnSet", typeof(System.Boolean));
511 Ordinal.DefaultValue = 0;
512 IsLong.DefaultValue = false;
514 DataColumnCollection columns = schemaTable.Columns;
516 // must maintain order for backward compatibility
517 columns.Add(ColumnName);
518 columns.Add(Ordinal);
520 columns.Add(Precision);
522 columns.Add(IsUnique);
524 columns.Add(BaseServerName);
525 columns.Add(BaseCatalogName);
526 columns.Add(BaseColumnName);
527 columns.Add(BaseSchemaName);
528 columns.Add(BaseTableName);
529 columns.Add(DataType);
530 columns.Add(AllowDBNull);
531 columns.Add(ProviderType);
532 columns.Add(IsAliased);
533 columns.Add(IsExpression);
534 columns.Add(IsIdentity);
535 columns.Add(IsAutoIncrement);
536 columns.Add(IsRowVersion);
537 columns.Add(IsHidden);
539 columns.Add(IsReadOnly);
540 columns.Add(ProviderSpecificDataType);
541 columns.Add(DataTypeName);
542 columns.Add(XmlSchemaCollectionDatabase);
543 columns.Add(XmlSchemaCollectionOwningSchema);
544 columns.Add(XmlSchemaCollectionName);
545 columns.Add(UdtAssemblyQualifiedName);
546 columns.Add(NonVersionedProviderType);
547 columns.Add(IsColumnSet);
549 for (int i = 0; i < InternalFieldCount; i++) {
550 SmiQueryMetaData colMetaData = _currentMetaData[i];
552 long maxLength = colMetaData.MaxLength;
554 MetaType metaType = MetaType.GetMetaTypeFromSqlDbType(colMetaData.SqlDbType, colMetaData.IsMultiValued);
555 if ( SmiMetaData.UnlimitedMaxLengthIndicator == maxLength ) {
556 metaType = MetaType.GetMaxMetaTypeFromMetaType( metaType );
557 maxLength = (metaType.IsSizeInCharacters && !metaType.IsPlp) ? (0x7fffffff / 2) : 0x7fffffff;
560 DataRow schemaRow = schemaTable.NewRow();
562 // NOTE: there is an impedence mismatch here - the server always
563 // treats numeric data as variable length and sends a maxLength
564 // based upon the precision, whereas TDS always sends 17 for
565 // the max length; rather than push this logic into the server,
566 // I've elected to make a fixup here instead.
567 if (SqlDbType.Decimal == colMetaData.SqlDbType) {
569 maxLength = TdsEnums.MAX_NUMERIC_LEN; // SQLBUDT 339686
571 else if (SqlDbType.Variant == colMetaData.SqlDbType) {
573 maxLength = 8009; // SQLBUDT 340726
576 schemaRow[ColumnName] = colMetaData.Name;
577 schemaRow[Ordinal] = i;
578 schemaRow[Size] = maxLength;
580 schemaRow[ProviderType] = (int) colMetaData.SqlDbType; // SqlDbType
581 schemaRow[NonVersionedProviderType] = (int) colMetaData.SqlDbType; // SqlDbType
583 if (colMetaData.SqlDbType != SqlDbType.Udt) {
584 schemaRow[DataType] = metaType.ClassType; // com+ type
585 schemaRow[ProviderSpecificDataType] = metaType.SqlType;
588 schemaRow[UdtAssemblyQualifiedName] = colMetaData.Type.AssemblyQualifiedName;
589 schemaRow[DataType] = colMetaData.Type;
590 schemaRow[ProviderSpecificDataType] = colMetaData.Type;
593 // NOTE: there is also an impedence mismatch here - the server
594 // has different ideas about what the precision value should be
595 // than does the client bits. I tried fixing up the default
596 // meta data values in SmiMetaData, however, it caused the
597 // server suites to fall over dead. Rather than attempt to
598 // bake it into the server, I'm fixing it up in the client.
599 byte precision = 0xff; // default for everything, except certain numeric types.
602 switch (colMetaData.SqlDbType) {
603 case SqlDbType.BigInt:
604 case SqlDbType.DateTime:
605 case SqlDbType.Decimal:
607 case SqlDbType.Money:
608 case SqlDbType.SmallDateTime:
609 case SqlDbType.SmallInt:
610 case SqlDbType.SmallMoney:
611 case SqlDbType.TinyInt:
612 precision = colMetaData.Precision;
614 case SqlDbType.Float:
621 precision = 0xff; // everything else is unknown;
625 schemaRow[Precision] = precision;
628 if ( SqlDbType.Decimal == colMetaData.SqlDbType ||
629 SqlDbType.Time == colMetaData.SqlDbType ||
630 SqlDbType.DateTime2 == colMetaData.SqlDbType ||
631 SqlDbType.DateTimeOffset == colMetaData.SqlDbType) {
632 schemaRow[Scale] = colMetaData.Scale;
635 schemaRow[Scale] = MetaType.GetMetaTypeFromSqlDbType(
636 colMetaData.SqlDbType, colMetaData.IsMultiValued).Scale;
639 schemaRow[AllowDBNull] = colMetaData.AllowsDBNull;
640 if ( !( colMetaData.IsAliased.IsNull ) ) {
641 schemaRow[IsAliased] = colMetaData.IsAliased.Value;
644 if ( !( colMetaData.IsKey.IsNull ) ) {
645 schemaRow[IsKey] = colMetaData.IsKey.Value;
648 if ( !( colMetaData.IsHidden.IsNull ) ) {
649 schemaRow[IsHidden] = colMetaData.IsHidden.Value;
652 if ( !( colMetaData.IsExpression.IsNull ) ) {
653 schemaRow[IsExpression] = colMetaData.IsExpression.Value;
656 schemaRow[IsReadOnly] = colMetaData.IsReadOnly;
657 schemaRow[IsIdentity] = colMetaData.IsIdentity;
658 schemaRow[IsColumnSet] = colMetaData.IsColumnSet;
659 schemaRow[IsAutoIncrement] = colMetaData.IsIdentity;
660 schemaRow[IsLong] = metaType.IsLong;
662 // mark unique for timestamp columns
663 if ( SqlDbType.Timestamp == colMetaData.SqlDbType ) {
664 schemaRow[IsUnique] = true;
665 schemaRow[IsRowVersion] = true;
668 schemaRow[IsUnique] = false;
669 schemaRow[IsRowVersion] = false;
672 if ( !ADP.IsEmpty( colMetaData.ColumnName ) ) {
673 schemaRow[BaseColumnName] = colMetaData.ColumnName;
675 else if (!ADP.IsEmpty( colMetaData.Name)) {
676 // Use projection name if base column name is not present
677 schemaRow[BaseColumnName] = colMetaData.Name;
680 if ( !ADP.IsEmpty(colMetaData.TableName ) ) {
681 schemaRow[BaseTableName] = colMetaData.TableName;
684 if (!ADP.IsEmpty(colMetaData.SchemaName)) {
685 schemaRow[BaseSchemaName] = colMetaData.SchemaName;
688 if (!ADP.IsEmpty(colMetaData.CatalogName)) {
689 schemaRow[BaseCatalogName] = colMetaData.CatalogName;
692 if (!ADP.IsEmpty(colMetaData.ServerName)) {
693 schemaRow[BaseServerName] = colMetaData.ServerName;
696 if ( SqlDbType.Udt == colMetaData.SqlDbType ) {
697 schemaRow[DataTypeName] = colMetaData.TypeSpecificNamePart1 + "." + colMetaData.TypeSpecificNamePart2 + "." + colMetaData.TypeSpecificNamePart3;
700 schemaRow[DataTypeName] = metaType.TypeName;
704 if ( SqlDbType.Xml == colMetaData.SqlDbType ) {
705 schemaRow[XmlSchemaCollectionDatabase] = colMetaData.TypeSpecificNamePart1;
706 schemaRow[XmlSchemaCollectionOwningSchema] = colMetaData.TypeSpecificNamePart2;
707 schemaRow[XmlSchemaCollectionName] = colMetaData.TypeSpecificNamePart3;
710 schemaTable.Rows.Add(schemaRow);
711 schemaRow.AcceptChanges();
714 // mark all columns as readonly
715 foreach(DataColumn column in columns) {
716 column.ReadOnly = true; // MDAC 70943
719 _schemaTable = schemaTable;
726 // ISqlRecord methods
728 public override SqlBinary GetSqlBinary(int ordinal) {
729 EnsureCanGetCol( "GetSqlBinary", ordinal);
730 return ValueUtilsSmi.GetSqlBinary(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal]);
733 public override SqlBoolean GetSqlBoolean(int ordinal) {
734 EnsureCanGetCol( "GetSqlBoolean", ordinal);
735 return ValueUtilsSmi.GetSqlBoolean(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal]);
738 public override SqlByte GetSqlByte(int ordinal) {
739 EnsureCanGetCol( "GetSqlByte", ordinal);
740 return ValueUtilsSmi.GetSqlByte(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal]);
743 public override SqlInt16 GetSqlInt16(int ordinal) {
744 EnsureCanGetCol( "GetSqlInt16", ordinal);
745 return ValueUtilsSmi.GetSqlInt16(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal]);
748 public override SqlInt32 GetSqlInt32(int ordinal) {
749 EnsureCanGetCol( "GetSqlInt32", ordinal);
750 return ValueUtilsSmi.GetSqlInt32(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal]);
753 public override SqlInt64 GetSqlInt64(int ordinal) {
754 EnsureCanGetCol( "GetSqlInt64", ordinal);
755 return ValueUtilsSmi.GetSqlInt64(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal]);
758 public override SqlSingle GetSqlSingle(int ordinal) {
759 EnsureCanGetCol( "GetSqlSingle", ordinal);
760 return ValueUtilsSmi.GetSqlSingle(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal]);
763 public override SqlDouble GetSqlDouble(int ordinal) {
764 EnsureCanGetCol( "GetSqlDouble", ordinal);
765 return ValueUtilsSmi.GetSqlDouble(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal]);
768 public override SqlMoney GetSqlMoney(int ordinal) {
769 EnsureCanGetCol( "GetSqlMoney", ordinal);
770 return ValueUtilsSmi.GetSqlMoney(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal]);
773 public override SqlDateTime GetSqlDateTime(int ordinal) {
774 EnsureCanGetCol( "GetSqlDateTime", ordinal);
775 return ValueUtilsSmi.GetSqlDateTime(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal]);
779 public override SqlDecimal GetSqlDecimal(int ordinal) {
780 EnsureCanGetCol( "GetSqlDecimal", ordinal);
781 return ValueUtilsSmi.GetSqlDecimal(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal]);
784 public override SqlString GetSqlString(int ordinal) {
785 EnsureCanGetCol( "GetSqlString", ordinal);
786 return ValueUtilsSmi.GetSqlString(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal]);
789 public override SqlGuid GetSqlGuid(int ordinal) {
790 EnsureCanGetCol( "GetSqlGuid", ordinal);
791 return ValueUtilsSmi.GetSqlGuid(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal]);
794 public override SqlChars GetSqlChars(int ordinal) {
795 EnsureCanGetCol( "GetSqlChars", ordinal);
796 return ValueUtilsSmi.GetSqlChars(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal], _currentConnection.InternalContext);
799 public override SqlBytes GetSqlBytes(int ordinal) {
800 EnsureCanGetCol( "GetSqlBytes", ordinal);
801 return ValueUtilsSmi.GetSqlBytes(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal], _currentConnection.InternalContext);
804 public override SqlXml GetSqlXml(int ordinal) {
805 EnsureCanGetCol( "GetSqlXml", ordinal);
806 return ValueUtilsSmi.GetSqlXml(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal], _currentConnection.InternalContext);
809 public override TimeSpan GetTimeSpan(int ordinal) {
810 EnsureCanGetCol("GetTimeSpan", ordinal);
811 return ValueUtilsSmi.GetTimeSpan(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal], _currentConnection.IsKatmaiOrNewer);
814 public override DateTimeOffset GetDateTimeOffset(int ordinal) {
815 EnsureCanGetCol("GetDateTimeOffset", ordinal);
816 return ValueUtilsSmi.GetDateTimeOffset(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal], _currentConnection.IsKatmaiOrNewer);
819 public override object GetSqlValue(int ordinal) {
820 EnsureCanGetCol( "GetSqlValue", ordinal);
822 SmiMetaData metaData = _currentMetaData[ordinal];
823 if (_currentConnection.IsKatmaiOrNewer) {
824 return ValueUtilsSmi.GetSqlValue200(_readerEventSink, (SmiTypedGetterSetter)_currentColumnValuesV3, ordinal, metaData, _currentConnection.InternalContext);
826 return ValueUtilsSmi.GetSqlValue(_readerEventSink, _currentColumnValuesV3, ordinal, metaData, _currentConnection.InternalContext); ;
829 public override int GetSqlValues(object[] values) {
830 EnsureCanGetCol( "GetSqlValues", 0);
832 if (null == values) {
833 throw ADP.ArgumentNull("values");
836 int copyLength = (values.Length < _visibleColumnCount) ? values.Length : _visibleColumnCount;
837 for(int i=0; i<copyLength; i++) {
838 values[_indexMap[i]] = GetSqlValue(i);
845 // ISqlReader methods/properties
847 public override bool HasRows {
848 get {return _hasRows;}
852 // SqlDataReader method/properties
854 public override Stream GetStream(int ordinal) {
855 EnsureCanGetCol("GetStream", ordinal);
857 SmiQueryMetaData metaData = _currentMetaData[ordinal];
859 // For non-null, non-variant types with sequential access, we support proper streaming
860 if ((metaData.SqlDbType != SqlDbType.Variant) && (IsCommandBehavior(CommandBehavior.SequentialAccess)) && (!ValueUtilsSmi.IsDBNull(_readerEventSink, _currentColumnValuesV3, ordinal))) {
861 if (HasActiveStreamOrTextReaderOnColumn(ordinal)) {
862 throw ADP.NonSequentialColumnAccess(ordinal, ordinal + 1);
864 _currentStream = ValueUtilsSmi.GetSequentialStream(_readerEventSink, _currentColumnValuesV3, ordinal, metaData);
865 return _currentStream;
868 return ValueUtilsSmi.GetStream(_readerEventSink, _currentColumnValuesV3, ordinal, metaData);
872 public override TextReader GetTextReader(int ordinal) {
873 EnsureCanGetCol("GetTextReader", ordinal);
875 SmiQueryMetaData metaData = _currentMetaData[ordinal];
877 // For non-variant types with sequential access, we support proper streaming
878 if ((metaData.SqlDbType != SqlDbType.Variant) && (IsCommandBehavior(CommandBehavior.SequentialAccess)) && (!ValueUtilsSmi.IsDBNull(_readerEventSink, _currentColumnValuesV3, ordinal))) {
879 if (HasActiveStreamOrTextReaderOnColumn(ordinal)) {
880 throw ADP.NonSequentialColumnAccess(ordinal, ordinal + 1);
882 _currentTextReader = ValueUtilsSmi.GetSequentialTextReader(_readerEventSink, _currentColumnValuesV3, ordinal, metaData);
883 return _currentTextReader;
886 return ValueUtilsSmi.GetTextReader(_readerEventSink, _currentColumnValuesV3, ordinal, metaData);
890 public override XmlReader GetXmlReader(int ordinal) {
891 // NOTE: sql_variant can not contain a XML data type: http://msdn.microsoft.com/en-us/library/ms173829.aspx
893 EnsureCanGetCol("GetXmlReader", ordinal);
894 if (_currentMetaData[ordinal].SqlDbType != SqlDbType.Xml) {
895 throw ADP.InvalidCast();
898 Stream stream = null;
899 if ((IsCommandBehavior(CommandBehavior.SequentialAccess)) && (!ValueUtilsSmi.IsDBNull(_readerEventSink, _currentColumnValuesV3, ordinal))) {
900 if (HasActiveStreamOrTextReaderOnColumn(ordinal)) {
901 throw ADP.NonSequentialColumnAccess(ordinal, ordinal + 1);
903 // Need to bypass the type check since streams are not usually allowed on XML types
904 _currentStream = ValueUtilsSmi.GetSequentialStream(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal], bypassTypeCheck: true);
905 stream = _currentStream;
908 stream = ValueUtilsSmi.GetStream(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal], bypassTypeCheck: true);
911 return SqlXml.CreateSqlXmlReader(stream);
915 // Internal reader state
918 // Logical state of reader/resultset as viewed by the client
919 // Does not necessarily match up with server state.
920 internal enum PositionState
922 BeforeResults, // Before all resultset in request
923 BeforeRows, // Before all rows in current resultset
924 OnRow, // On a valid row in the current resultset
925 AfterRows, // After all rows in current resultset
926 AfterResults // After all resultsets in request
928 private PositionState _currentPosition; // Where is the reader relative to incoming results?
934 private bool _isOpen; // Is the reader open?
935 private SmiQueryMetaData[] _currentMetaData; // Metadata for current resultset
936 private int[] _indexMap; // map of indices for visible column
937 private int _visibleColumnCount; // number of visible columns
938 private DataTable _schemaTable; // Cache of user-visible extended metadata while in results.
939 private ITypedGetters _currentColumnValues; // Unmanaged-managed data marshalers/cache
940 private ITypedGettersV3 _currentColumnValuesV3; // Unmanaged-managed data marshalers/cache for SMI V3
941 private bool _hasRows; // Are there any rows in the current resultset? Must be able to say before moving to first row.
942 private SmiEventStream _eventStream; // The event buffer that receives the events from the execution engine.
943 private SmiRequestExecutor _requestExecutor; // The used to request actions from the execution engine.
944 private SqlInternalConnectionSmi _currentConnection;
945 private ReaderEventSink _readerEventSink; // The event sink that will process events from the event buffer.
946 private FieldNameLookup _fieldNameLookup; // cached lookup object to improve access time based on field name
947 private SqlSequentialStreamSmi _currentStream; // The stream on the current column (if any)
948 private SqlSequentialTextReaderSmi _currentTextReader; // The text reader on the current column (if any)
951 // Internal methods for use by other classes in project
955 // Assumes that if there were any results, the first chunk of them are in the data stream
956 // (up to the first actual row or the end of the resultsets).
957 unsafe internal SqlDataReaderSmi (
958 SmiEventStream eventStream, // the event stream that receives the events from the execution engine
959 SqlCommand parent, // command that owns reader
960 CommandBehavior behavior, // behavior specified for this execution
961 SqlInternalConnectionSmi connection, // connection that owns everybody
962 SmiEventSink parentSink, // Event sink of parent command
963 SmiRequestExecutor requestExecutor
964 ) : base( parent, behavior ) { //
965 _eventStream = eventStream;
966 _currentConnection = connection;
967 _readerEventSink = new ReaderEventSink( this, parentSink );
968 _currentPosition = PositionState.BeforeResults;
971 _visibleColumnCount = 0;
972 _currentStream = null;
973 _currentTextReader = null;
974 _requestExecutor = requestExecutor;
977 internal override SmiExtendedMetaData[] GetInternalSmiMetaData() {
978 if (null == _currentMetaData || _visibleColumnCount == this.InternalFieldCount) {
979 return _currentMetaData;
983 // DEVNOTE: Interpretation of returned array currently depends on hidden columns
984 // always appearing at the end, since there currently is no access to the index map
985 // outside of this class. In Debug code, we check this assumption.
986 bool sawHiddenColumn = false;
988 SmiExtendedMetaData[] visibleMetaData = new SmiExtendedMetaData[_visibleColumnCount];
989 for(int i=0; i<_visibleColumnCount; i++) {
991 if (_currentMetaData[_indexMap[i]].IsHidden.IsTrue) {
992 sawHiddenColumn = true;
995 Debug.Assert(!sawHiddenColumn);
998 visibleMetaData[i] = _currentMetaData[_indexMap[i]];
1001 return visibleMetaData;
1005 internal override int GetLocaleId(int ordinal) {
1006 EnsureCanGetMetaData( "GetLocaleId" );
1007 return (int)_currentMetaData[ordinal].LocaleId;
1011 // Private implementation methods
1014 private int InternalFieldCount {
1016 if ( FNotInResults() ) {
1020 return _currentMetaData.Length;
1025 // Have we cleaned up internal resources?
1026 private bool IsReallyClosed() {
1030 // Central checkpoint for closed recordset.
1031 // Any code that requires an open recordset should call this method first!
1032 // Especially any code that accesses unmanaged memory structures whose lifetime
1033 // matches the lifetime of the unmanaged recordset.
1034 internal void ThrowIfClosed( string operationName ) {
1036 throw ADP.DataReaderClosed( operationName );
1039 // Central checkpoint to ensure the requested column can be accessed.
1040 // Calling this function serves to notify that it has been accessed by the user.
1041 [SuppressMessage("Microsoft.Performance", "CA1801:AvoidUnusedParameters")] // for future compatibility
1042 private void EnsureCanGetCol( string operationName, int ordinal) {
1043 EnsureOnRow( operationName );
1046 internal void EnsureOnRow( string operationName ) {
1047 ThrowIfClosed( operationName );
1048 if (_currentPosition != PositionState.OnRow) {
1049 throw SQL.InvalidRead();
1053 internal void EnsureCanGetMetaData( string operationName ) {
1054 ThrowIfClosed( operationName );
1055 if (FNotInResults()) {
1056 throw SQL.InvalidRead(); //
1060 private bool FInResults() {
1061 return !FNotInResults();
1064 private bool FNotInResults() {
1065 return (PositionState.AfterResults == _currentPosition || PositionState.BeforeResults == _currentPosition);
1068 private void MetaDataAvailable( SmiQueryMetaData[] md, bool nextEventIsRow ) {
1069 Debug.Assert( _currentPosition != PositionState.AfterResults );
1071 _currentMetaData = md;
1072 _hasRows = nextEventIsRow;
1073 _fieldNameLookup = null;
1074 _schemaTable = null; // will be rebuilt based on new metadata
1075 _currentPosition = PositionState.BeforeRows;
1077 // calculate visible column indices
1078 _indexMap = new int[_currentMetaData.Length];
1080 int visibleCount = 0;
1081 for(i=0; i<_currentMetaData.Length; i++) {
1082 if (!_currentMetaData[i].IsHidden.IsTrue) {
1083 _indexMap[visibleCount] = i;
1087 _visibleColumnCount = visibleCount;
1090 private bool HasActiveStreamOrTextReaderOnColumn(int columnIndex) {
1091 bool active = false;
1093 active |= ((_currentStream != null) && (_currentStream.ColumnIndex == columnIndex));
1094 active |= ((_currentTextReader != null) && (_currentTextReader.ColumnIndex == columnIndex));
1099 // Obsolete V2- method
1100 private void RowAvailable( ITypedGetters row ) {
1101 Debug.Assert( _currentPosition != PositionState.AfterResults );
1103 _currentColumnValues = row;
1104 _currentPosition = PositionState.OnRow;
1107 private void RowAvailable( ITypedGettersV3 row ) {
1108 Debug.Assert( _currentPosition != PositionState.AfterResults );
1110 _currentColumnValuesV3 = row;
1111 _currentPosition = PositionState.OnRow;
1114 private void StatementCompleted( ) {
1115 Debug.Assert( _currentPosition != PositionState.AfterResults );
1117 _currentPosition = PositionState.AfterRows;
1120 private void ResetResultSet() {
1121 _currentMetaData = null;
1122 _visibleColumnCount = 0;
1123 _schemaTable = null;
1126 private void BatchCompleted() {
1127 Debug.Assert( _currentPosition != PositionState.AfterResults );
1131 _currentPosition = PositionState.AfterResults;
1132 _eventStream.Close( _readerEventSink );
1135 // An implementation of the IEventSink interface that either performs
1136 // the required enviornment changes or forwards the events on to the
1137 // corresponding reader instance. Having the event sink be a separate
1138 // class keeps the IEventSink methods out of SqlDataReader's inteface.
1140 private sealed class ReaderEventSink : SmiEventSink_Default {
1141 private readonly SqlDataReaderSmi reader;
1143 internal ReaderEventSink( SqlDataReaderSmi reader, SmiEventSink parent )
1145 this.reader = reader;
1148 internal override void MetaDataAvailable( SmiQueryMetaData[] md, bool nextEventIsRow ) {
1149 if (Bid.AdvancedOn) {
1150 Bid.Trace("<sc.SqlDataReaderSmi.ReaderEventSink.MetaDataAvailable|ADV> %d#, md.Length=%d nextEventIsRow=%d.\n", reader.ObjectID, (null != md) ? md.Length : -1, nextEventIsRow);
1153 for (int i=0; i < md.Length; i++) {
1154 Bid.Trace("<sc.SqlDataReaderSmi.ReaderEventSink.MetaDataAvailable|ADV> %d#, metaData[%d] is %ls%ls\n",
1155 reader.ObjectID, i, md[i].GetType().ToString(), md[i].TraceString());
1159 this.reader.MetaDataAvailable( md, nextEventIsRow );
1162 // Obsolete V2- method
1163 internal override void RowAvailable( ITypedGetters row ) {
1164 if (Bid.AdvancedOn) {
1165 Bid.Trace("<sc.SqlDataReaderSmi.ReaderEventSink.RowAvailable|ADV> %d# (v2).\n", reader.ObjectID);
1167 this.reader.RowAvailable( row );
1170 internal override void RowAvailable( ITypedGettersV3 row ) {
1171 if (Bid.AdvancedOn) {
1172 Bid.Trace("<sc.SqlDataReaderSmi.ReaderEventSink.RowAvailable|ADV> %d# (ITypedGettersV3).\n", reader.ObjectID);
1174 this.reader.RowAvailable( row );
1177 internal override void RowAvailable(SmiTypedGetterSetter rowData) {
1178 if (Bid.AdvancedOn) {
1179 Bid.Trace("<sc.SqlDataReaderSmi.ReaderEventSink.RowAvailable|ADV> %d# (SmiTypedGetterSetter).\n", reader.ObjectID);
1181 this.reader.RowAvailable(rowData);
1184 internal override void StatementCompleted( int recordsAffected ) {
1185 if (Bid.AdvancedOn) {
1186 Bid.Trace("<sc.SqlDataReaderSmi.ReaderEventSink.StatementCompleted|ADV> %d# recordsAffected=%d.\n", reader.ObjectID, recordsAffected);
1189 // devnote: relies on SmiEventSink_Default to pass event to parent
1190 // Both command and reader care about StatementCompleted, but for different reasons.
1192 base.StatementCompleted( recordsAffected );
1193 this.reader.StatementCompleted( );
1196 internal override void BatchCompleted() {
1197 if (Bid.AdvancedOn) {
1198 Bid.Trace("<sc.SqlDataReaderSmi.ReaderEventSink.BatchCompleted|ADV> %d#.\n", reader.ObjectID);
1201 // devnote: relies on SmiEventSink_Default to pass event to parent
1202 // parent's callback *MUST* come before reader's BatchCompleted, since
1203 // reader will close the event stream during this call, and parent wants
1204 // to extract parameter values before that happens.
1206 base.BatchCompleted();
1207 this.reader.BatchCompleted();