1 //------------------------------------------------------------------------------
2 // <copyright file="OdbcDataReader.cs" company="Microsoft">
3 //      Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 // <owner current="true" primary="true">Microsoft</owner>
6 // <owner current="true" primary="false">Microsoft</owner>
7 //------------------------------------------------------------------------------
8 
9 using System;
10 using System.Collections;
11 using System.Collections.Generic;
12 using System.ComponentModel;
13 using System.Data;
14 using System.Data.Common;
15 using System.Data.ProviderBase;
16 using System.Diagnostics;
17 using System.Globalization;
18 using System.Runtime.CompilerServices;
19 using System.Runtime.InteropServices;
20 using System.Text;              // StringBuilder
21 using System.Threading;
22 
23 namespace System.Data.Odbc
24 {
25     public sealed class OdbcDataReader : DbDataReader {
26 
27         private OdbcCommand command;
28 
29         private int recordAffected = -1;
30         private FieldNameLookup _fieldNameLookup;
31 
32         private DbCache dataCache;
33         private enum HasRowsStatus {
34             DontKnow    = 0,
35             HasRows     = 1,
36             HasNoRows   = 2,
37         }
38         private HasRowsStatus _hasRows = HasRowsStatus.DontKnow;
39         private bool _isClosed;
40         private bool _isRead;
41         private bool _isValidResult;
42         private bool _noMoreResults;
43         private bool _noMoreRows;
44         private bool _skipReadOnce;
45         private int _hiddenColumns;                 // number of hidden columns
46         private CommandBehavior     _commandBehavior;
47 
48         // track current row and column, will be set on the first Fetch call
49         private int _row = -1;
50         private int _column = -1;
51 
52         // used to track position in field for sucessive reads in case of Sequential Access
53         private long _sequentialBytesRead;
54 
55         private static int          _objectTypeCount; // Bid counter
56         internal readonly int       ObjectID = System.Threading.Interlocked.Increment(ref _objectTypeCount);
57 
58         // the statement handle here is just a copy of the statement handle owned by the command
59         // the DataReader must not free the statement handle. this is done by the command
60         //
61 
62         private MetaData[] metadata;
63         private DataTable schemaTable; // MDAC 68336
64         private string _cmdText;    // get a copy in case the command text on the command is changed ...
65         private CMDWrapper _cmdWrapper;
66 
OdbcDataReader(OdbcCommand command, CMDWrapper cmdWrapper, CommandBehavior commandbehavior)67         internal OdbcDataReader(OdbcCommand command, CMDWrapper cmdWrapper, CommandBehavior commandbehavior) {
68             OdbcConnection.VerifyExecutePermission();
69 
70             Debug.Assert(command != null, "Command null on OdbcDataReader ctor");
71             this.command = command;
72             _commandBehavior = commandbehavior;
73             _cmdText = command.CommandText;    // get a copy in case the command text on the command is changed ...
74             _cmdWrapper = cmdWrapper;
75         }
76 
77         private CNativeBuffer Buffer {
78             get {
79                 CNativeBuffer value = _cmdWrapper._dataReaderBuf;
80                 if (null == value) {
81                     Debug.Assert(false, "object is disposed");
82                     throw new ObjectDisposedException(GetType().Name);
83                 }
84                 return value;
85             }
86         }
87 
88         private OdbcConnection Connection {
89             get {
90                 if (null != _cmdWrapper) {
91                     return _cmdWrapper.Connection;
92                 }
93                 else {
94                     return null;
95                 }
96             }
97         }
98 
99         internal OdbcCommand Command {
100             get {
101                 return this.command;
102             }
103             set {
104                 this.command=value;
105             }
106         }
107 
108         private OdbcStatementHandle StatementHandle {
109             get {
110                 return _cmdWrapper.StatementHandle;
111             }
112         }
113 
114         private OdbcStatementHandle KeyInfoStatementHandle {
115             get { return _cmdWrapper.KeyInfoStatement; }
116         }
117 
IsBehavior(CommandBehavior behavior)118         internal bool IsBehavior(CommandBehavior behavior) {
119             return IsCommandBehavior(behavior);
120         }
121 
122         internal bool IsCancelingCommand {
123             get {
124                 if (this.command != null) {
125                     return command.Canceling;
126                 }
127                 return false;
128             }
129         }
130 
131         internal bool IsNonCancelingCommand {
132             get {
133                 if (this.command != null) {
134                     return !command.Canceling;
135                 }
136                 return false;
137             }
138         }
139 
140         override public int Depth {
141             get {
142                 if (IsClosed) { // MDAC 63669
143                     throw ADP.DataReaderClosed("Depth");
144                 }
145                 return 0;
146             }
147         }
148 
149         override public int FieldCount  {
150             get {
151                 if (IsClosed) { // MDAC 63669
152                     throw ADP.DataReaderClosed("FieldCount");
153                 }
154                 if (_noMoreResults) {   // MDAC 93325
155                     return 0;
156                 }
157                 if (null == this.dataCache) {
158                     Int16 cColsAffected;
159                     ODBC32.RetCode retcode = this.FieldCountNoThrow(out cColsAffected);
160                     if(retcode != ODBC32.RetCode.SUCCESS) {
161                         Connection.HandleError(StatementHandle, retcode);
162                     }
163                 }
164                 return ((null != this.dataCache) ? this.dataCache._count : 0);
165             }
166         }
167 
168         // HasRows
169         //
170         // Use to detect wheter there are one ore more rows in the result without going through Read
171         // May be called at any time
172         // Basically it calls Read and sets a flag so that the actual Read call will be skipped once
173         //
174         override public bool HasRows {
175             get {
176                 if (IsClosed) {
177                     throw ADP.DataReaderClosed("HasRows");
178                 }
179                 if (_hasRows == HasRowsStatus.DontKnow){
180                     Read();                     //
181                     _skipReadOnce = true;       // need to skip Read once because we just did it
182                 }
183                 return (_hasRows == HasRowsStatus.HasRows);
184             }
185         }
186 
FieldCountNoThrow(out Int16 cColsAffected)187         internal ODBC32.RetCode FieldCountNoThrow(out Int16 cColsAffected) {
188             if (IsCancelingCommand) {
189                 cColsAffected = 0;
190                 return ODBC32.RetCode.ERROR;
191             }
192 
193             ODBC32.RetCode retcode = StatementHandle.NumberOfResultColumns(out cColsAffected);
194             if (retcode == ODBC32.RetCode.SUCCESS) {
195                 _hiddenColumns = 0;
196                 if (IsCommandBehavior(CommandBehavior.KeyInfo)) {
197                     // we need to search for the first hidden column
198                     //
199                     if (!Connection.ProviderInfo.NoSqlSoptSSNoBrowseTable && !Connection.ProviderInfo.NoSqlSoptSSHiddenColumns) {
200                         for (int i=0; i<cColsAffected; i++)
201                         {
202                             SQLLEN isHidden = GetColAttribute(i, (ODBC32.SQL_DESC)ODBC32.SQL_CA_SS.COLUMN_HIDDEN, (ODBC32.SQL_COLUMN)(-1), ODBC32.HANDLER.IGNORE);
203                             if (isHidden.ToInt64() == 1) {
204                                 _hiddenColumns = (int)cColsAffected-i;
205                                 cColsAffected = (Int16)i;
206                                 break;
207                             }
208                         }
209                     }
210                 }
211                 this.dataCache = new DbCache(this, cColsAffected);
212             }
213             else {
214                 cColsAffected = 0;
215             }
216             return retcode;
217         }
218 
219         override public bool IsClosed {
220             get {
221                 return _isClosed;
222             }
223         }
224 
GetRowCount()225         private SQLLEN GetRowCount() {
226             if (!IsClosed) {
227                 SQLLEN cRowsAffected;
228                 ODBC32.RetCode retcode = StatementHandle.RowCount(out cRowsAffected);
229                 if (ODBC32.RetCode.SUCCESS == retcode || ODBC32.RetCode.SUCCESS_WITH_INFO == retcode) {
230                     return cRowsAffected;
231                 }
232             }
233             return -1;
234         }
235 
CalculateRecordsAffected(int cRowsAffected)236         internal int CalculateRecordsAffected(int cRowsAffected) {
237             if (0 <= cRowsAffected) {
238                 if (-1 == this.recordAffected) {
239                     this.recordAffected = cRowsAffected;
240                 }
241                 else  {
242                     this.recordAffected += cRowsAffected;
243                 }
244             }
245             return this.recordAffected;
246         }
247 
248 
249         override public int RecordsAffected {
250             get {
251                 return this.recordAffected;
252             }
253         }
254 
255         override public object this[int i] {
256             get {
257                 return GetValue(i);
258             }
259         }
260 
261         override public object this[string value] {
262             get {
263                 return GetValue(GetOrdinal(value));
264             }
265         }
266 
Close()267         override public void Close() {
268             Close(false);
269         }
270 
Close(bool disposing)271         private void Close(bool disposing) {
272             Exception error = null;
273 
274             CMDWrapper wrapper = _cmdWrapper;
275             if (null != wrapper && wrapper.StatementHandle != null) {
276                         // disposing
277                         // true to release both managed and unmanaged resources; false to release only unmanaged resources.
278                         //
279                 if (IsNonCancelingCommand) {
280                             //Read any remaining results off the wire
281                     // some batch statements may not be executed until SQLMoreResults is called.
282                     // We want the user to be able to do ExecuteNonQuery or ExecuteReader
283                     // and close without having iterate to get params or batch.
284                     //
285                     NextResult(disposing, !disposing); // false,true or true,false
286                             if (null != command) {
287                         if (command.HasParameters) {
288                             // Output Parameters are not guareenteed to be returned until all the data
289                             // from any restssets are read, so we do this after the above NextResult call(s)
290                             command.Parameters.GetOutputValues(_cmdWrapper);
291                         }
292                         wrapper.FreeStatementHandle(ODBC32.STMT.CLOSE);
293                                 command.CloseFromDataReader();
294                             }
295                         }
296                 wrapper.FreeKeyInfoStatementHandle(ODBC32.STMT.CLOSE);
297                 }
298 
299                 // if the command is still around we call CloseFromDataReader,
300                 // otherwise we need to dismiss the statement handle ourselves
301                 //
302                 if (null != command) {
303                     command.CloseFromDataReader();
304 
305                 if(IsCommandBehavior(CommandBehavior.CloseConnection)) {
306                         Debug.Assert(null != Connection, "null cmd connection");
307                     command.Parameters.RebindCollection = true;
308                         Connection.Close();
309                     }
310                 }
311             else if (null != wrapper) {
312                 wrapper.Dispose();
313             }
314 
315             this.command = null;
316             _isClosed=true;
317             this.dataCache = null;
318             this.metadata = null;
319             this.schemaTable = null;
320             _isRead = false;
321             _hasRows = HasRowsStatus.DontKnow;
322             _isValidResult = false;
323             _noMoreResults = true;
324             _noMoreRows = true;
325             _fieldNameLookup = null;
326 
327             SetCurrentRowColumnInfo(-1, 0);
328 
329             if ((null != error) && !disposing) {
330                 throw error;
331             }
332             _cmdWrapper = null;
333         }
334 
Dispose(bool disposing)335         protected override void Dispose(bool disposing) {
336             if (disposing) {
337                 Close(true);
338             }
339             // not delegating to base class because we know it only calls Close
340             //base.Dispose(disposing)
341         }
342 
GetDataTypeName(int i)343         override public String GetDataTypeName(int i) {
344             if (null != this.dataCache) {
345                 DbSchemaInfo info = this.dataCache.GetSchema(i);
346                 if(info._typename == null) {
347                     info._typename = GetColAttributeStr(i, ODBC32.SQL_DESC.TYPE_NAME, ODBC32.SQL_COLUMN.TYPE_NAME, ODBC32.HANDLER.THROW);
348                 }
349                 return info._typename;
350             }
351             throw ADP.DataReaderNoData();
352         }
353 
GetEnumerator()354         override public IEnumerator GetEnumerator() {
355             return new DbEnumerator((IDataReader)this, IsCommandBehavior(CommandBehavior.CloseConnection));
356         }
357 
GetFieldType(int i)358         override public Type GetFieldType(int i) {
359             if (null != this.dataCache) {
360                 DbSchemaInfo info = this.dataCache.GetSchema(i);
361                 if(info._type == null) {
362                     info._type = GetSqlType(i)._type;
363                 }
364                 return info._type;
365             }
366             throw ADP.DataReaderNoData();
367         }
368 
GetName(int i)369         override public String GetName(int i) {
370             if (null != this.dataCache) {
371                 DbSchemaInfo info = this.dataCache.GetSchema(i);
372                 if(info._name == null) {
373                     info._name = GetColAttributeStr(i, ODBC32.SQL_DESC.NAME, ODBC32.SQL_COLUMN.NAME, ODBC32.HANDLER.THROW);
374                     if (null == info._name) { // MDAC 66681
375                         info._name = "";
376                     }
377                 }
378                 return info._name;
379             }
380             throw ADP.DataReaderNoData();
381         }
382 
GetOrdinal(string value)383         override public int GetOrdinal(string value) {
384             if (null == _fieldNameLookup) {
385                 if (null == this.dataCache) {
386                     throw ADP.DataReaderNoData();
387                 }
388                 _fieldNameLookup = new FieldNameLookup(this, -1);
389             }
390             return _fieldNameLookup.GetOrdinal(value); // MDAC 71470
391         }
392 
IndexOf(string value)393         private int IndexOf(string value) {
394             if (null == _fieldNameLookup) {
395                 if (null == this.dataCache) {
396                     throw ADP.DataReaderNoData();
397                 }
398                 _fieldNameLookup = new FieldNameLookup(this, -1);
399             }
400             return _fieldNameLookup.IndexOf(value);
401         }
402 
IsCommandBehavior(CommandBehavior condition)403         private bool IsCommandBehavior(CommandBehavior condition) {
404             return (condition == (condition & _commandBehavior));
405         }
406 
GetValue(int i, TypeMap typemap)407         internal object GetValue(int i, TypeMap typemap) {
408             switch(typemap._sql_type) {
409 
410                 case ODBC32.SQL_TYPE.CHAR:
411                 case ODBC32.SQL_TYPE.VARCHAR:
412                 case ODBC32.SQL_TYPE.LONGVARCHAR:
413                 case ODBC32.SQL_TYPE.WCHAR:
414                 case ODBC32.SQL_TYPE.WVARCHAR:
415                 case ODBC32.SQL_TYPE.WLONGVARCHAR:
416                     return internalGetString(i);
417 
418                 case ODBC32.SQL_TYPE.DECIMAL:
419                 case ODBC32.SQL_TYPE.NUMERIC:
420                     return   internalGetDecimal(i);
421 
422                 case ODBC32.SQL_TYPE.SMALLINT:
423                     return  internalGetInt16(i);
424 
425                 case ODBC32.SQL_TYPE.INTEGER:
426                     return internalGetInt32(i);
427 
428                 case ODBC32.SQL_TYPE.REAL:
429                     return  internalGetFloat(i);
430 
431                 case ODBC32.SQL_TYPE.FLOAT:
432                 case ODBC32.SQL_TYPE.DOUBLE:
433                     return  internalGetDouble(i);
434 
435                 case ODBC32.SQL_TYPE.BIT:
436                     return  internalGetBoolean(i);
437 
438                 case ODBC32.SQL_TYPE.TINYINT:
439                     return  internalGetByte(i);
440 
441                 case ODBC32.SQL_TYPE.BIGINT:
442                     return  internalGetInt64(i);
443 
444                 case ODBC32.SQL_TYPE.BINARY:
445                 case ODBC32.SQL_TYPE.VARBINARY:
446                 case ODBC32.SQL_TYPE.LONGVARBINARY:
447                     return  internalGetBytes(i);
448 
449                 case ODBC32.SQL_TYPE.TYPE_DATE:
450                     return  internalGetDate(i);
451 
452                 case ODBC32.SQL_TYPE.TYPE_TIME:
453                     return  internalGetTime(i);
454 
455 //                  case ODBC32.SQL_TYPE.TIMESTAMP:
456                 case ODBC32.SQL_TYPE.TYPE_TIMESTAMP:
457                     return  internalGetDateTime(i);
458 
459                 case ODBC32.SQL_TYPE.GUID:
460                     return  internalGetGuid(i);
461 
462                 case ODBC32.SQL_TYPE.SS_VARIANT:
463                     //Note: SQL Variant is not an ODBC defined type.
464                     //Instead of just binding it as a byte[], which is not very useful,
465                     //we will actually code this specific for SQL Server.
466 
467                     //To obtain the sub-type, we need to first load the context (obtaining the length
468                     //will work), and then query for a speicial SQLServer specific attribute.
469                     if (_isRead) {
470                         if(this.dataCache.AccessIndex(i) == null) {
471                             int dummy;
472                             bool isNotDbNull = QueryFieldInfo(i, ODBC32.SQL_C.BINARY, out dummy);
473                             // if the value is DBNull, QueryFieldInfo will cache it
474                             if (isNotDbNull) {
475                                 //Delegate (for the sub type)
476                                 ODBC32.SQL_TYPE subtype = (ODBC32.SQL_TYPE)(int)GetColAttribute(i, (ODBC32.SQL_DESC)ODBC32.SQL_CA_SS.VARIANT_SQL_TYPE, (ODBC32.SQL_COLUMN)(-1), ODBC32.HANDLER.THROW);
477                                 return  GetValue(i, TypeMap.FromSqlType(subtype));
478                             }
479                         }
480                         return this.dataCache[i];
481                     }
482                     throw ADP.DataReaderNoData();
483 
484 
485 
486                 default:
487                     //Unknown types are bound strictly as binary
488                     return  internalGetBytes(i);
489              }
490         }
491 
GetValue(int i)492         override public object GetValue(int i) {
493             if (_isRead) {
494                 if(dataCache.AccessIndex(i) == null) {
495                     dataCache[i] = GetValue(i, GetSqlType(i));
496                 }
497                 return dataCache[i];
498             }
499             throw ADP.DataReaderNoData();
500         }
501 
GetValues(object[] values)502         override public int GetValues(object[] values) {
503             if (_isRead) {
504                 int nValues = Math.Min(values.Length, FieldCount);
505                 for (int i = 0; i < nValues; ++i) {
506                     values[i] = GetValue(i);
507                 }
508                 return nValues;
509             }
510             throw ADP.DataReaderNoData();
511         }
512 
GetSqlType(int i)513         private TypeMap GetSqlType(int i) {
514             //Note: Types are always returned (advertised) from ODBC as SQL_TYPEs, and
515             //are always bound by the user as SQL_C types.
516             TypeMap typeMap;
517             DbSchemaInfo info = this.dataCache.GetSchema(i);
518             if(!info._dbtype.HasValue) {
519                 info._dbtype = unchecked((ODBC32.SQL_TYPE)(int)GetColAttribute(i, ODBC32.SQL_DESC.CONCISE_TYPE, ODBC32.SQL_COLUMN.TYPE,ODBC32.HANDLER.THROW));
520                 typeMap = TypeMap.FromSqlType(info._dbtype.Value);
521                 if (typeMap._signType == true) {
522                     bool sign = (GetColAttribute(i, ODBC32.SQL_DESC.UNSIGNED, ODBC32.SQL_COLUMN.UNSIGNED, ODBC32.HANDLER.THROW).ToInt64() != 0);
523                     typeMap = TypeMap.UpgradeSignedType(typeMap, sign);
524                     info._dbtype = typeMap._sql_type;
525                 }
526             }
527             else {
528                 typeMap = TypeMap.FromSqlType(info._dbtype.Value);
529             }
530             Connection.SetSupportedType(info._dbtype.Value);
531             return typeMap;
532         }
533 
IsDBNull(int i)534         override public bool IsDBNull(int i) {
535             //  Note: ODBC SQLGetData doesn't allow retriving the column value twice.
536             //  The reational is that for ForwardOnly access (the default and LCD of drivers)
537             //  we cannot obtain the data more than once, and even GetData(0) (to determine is-null)
538             //  still obtains data for fixed length types.
539 
540             //  So simple code like:
541             //      if(!rReader.IsDBNull(i))
542             //          rReader.GetInt32(i)
543             //
544             //  Would fail, unless we cache on the IsDBNull call, and return the cached
545             //  item for GetInt32.  This actually improves perf anyway, (even if the driver could
546             //  support it), since we are not making a seperate interop call...
547 
548             // Bug SQLBUVSTS01:110664 - available cases:
549             // 1. random access - always cache the value (as before the fix), to minimize regression risk
550             // 2. sequential access, fixed-size value: continue caching the value as before, again to minimize regression risk
551             // 3. sequential access, variable-length value: this scenario did not work properly before the fix. Fix
552             //                                              it now by calling GetData(length = 0).
553             // 4. sequential access, cache value exists: just check the cache for DbNull (no validations done, again to minimize regressions)
554 
555             if (!IsCommandBehavior(CommandBehavior.SequentialAccess))
556                 return Convert.IsDBNull(GetValue(i)); // case 1, cache the value
557 
558             // in 'ideal' Sequential access support, we do not want cache the value in order to check if it is DbNull or not.
559             // But, to minimize regressions, we will continue caching the fixed-size values (case 2), even with SequentialAccess
560             // only in case of SequentialAccess with variable length data types (case 3), we will use GetData with zero length.
561 
562             object cachedObj = this.dataCache[i];
563             if (cachedObj != null)
564             {
565                 // case 4 - if cached object was created before, use it
566                 return Convert.IsDBNull(cachedObj);
567             }
568 
569             // no cache, check for the type (cases 2 and 3)
570             TypeMap typeMap = GetSqlType(i);
571             if (typeMap._bufferSize > 0) {
572                 // case 2 - fixed-size types have _bufferSize set to positive value
573                 // call GetValue(i) as before the fix of SQLBUVSTS01:110664
574                 // note, when SQLGetData is called for a fixed length type, the buffer size is always ignored and
575                 // the data will always be read off the wire
576                 return Convert.IsDBNull(GetValue(i));
577             }
578             else {
579                 // case 3 - the data has variable-length type, read zero-length data to query for null
580                 // QueryFieldInfo will return false only if the object cached as DbNull
581                 // QueryFieldInfo will put DbNull in cache only if the SQLGetData returns SQL_NULL_DATA, otherwise it does not change it
582                 int dummy;
583                 return !QueryFieldInfo(i, typeMap._sql_c, out dummy);
584             }
585         }
586 
GetByte(int i)587         override public Byte GetByte(int i) {
588             return (Byte)internalGetByte(i);
589         }
590 
internalGetByte(int i)591         private object internalGetByte(int i) {
592             if (_isRead) {
593                 if(this.dataCache.AccessIndex(i) == null) {
594                     if (GetData(i, ODBC32.SQL_C.UTINYINT)) {
595                         this.dataCache[i] = Buffer.ReadByte(0);
596                     }
597                 }
598                 return this.dataCache[i];
599             }
600             throw ADP.DataReaderNoData();
601         }
602 
GetChar(int i)603         override public Char GetChar(int i) {
604             return (Char)internalGetChar(i);
605         }
internalGetChar(int i)606         private object internalGetChar(int i) {
607             if (_isRead) {
608                 if(this.dataCache.AccessIndex(i) == null) {
609                     if (GetData(i, ODBC32.SQL_C.WCHAR)) {
610                         this.dataCache[i] = Buffer.ReadChar(0);
611                     }
612                 }
613                 return this.dataCache[i];
614             }
615             throw ADP.DataReaderNoData();
616         }
617 
GetInt16(int i)618         override public Int16 GetInt16(int i) {
619             return (Int16)internalGetInt16(i);
620         }
internalGetInt16(int i)621         private object internalGetInt16(int i) {
622             if (_isRead) {
623                 if(this.dataCache.AccessIndex(i) == null) {
624                     if (GetData(i, ODBC32.SQL_C.SSHORT)) {
625                         this.dataCache[i] = Buffer.ReadInt16(0);
626                     }
627                 }
628                 return this.dataCache[i];
629             }
630             throw ADP.DataReaderNoData();
631         }
632 
GetInt32(int i)633         override public Int32 GetInt32(int i) {
634             return (Int32)internalGetInt32(i);
635        }
internalGetInt32(int i)636         private object internalGetInt32(int i){
637             if (_isRead) {
638                 if(this.dataCache.AccessIndex(i) == null) {
639                     if(GetData(i, ODBC32.SQL_C.SLONG)){
640                         this.dataCache[i] = Buffer.ReadInt32(0);
641                     }
642                 }
643                 return this.dataCache[i];
644             }
645             throw ADP.DataReaderNoData();
646         }
647 
GetInt64(int i)648         override public Int64 GetInt64(int i) {
649             return (Int64)internalGetInt64(i);
650         }
651         // ---------------------------------------------------------------------------------------------- //
652         // internal internalGetInt64
653         // -------------------------
654         // Get Value of type SQL_BIGINT
655         // Since the driver refused to accept the type SQL_BIGINT we read that
656         // as SQL_C_WCHAR and convert it back to the Int64 data type
657         //
internalGetInt64(int i)658         private object internalGetInt64(int i) {
659             if (_isRead) {
660                 if(this.dataCache.AccessIndex(i) == null) {
661                     if (GetData(i, ODBC32.SQL_C.WCHAR)) {
662                         string value = (string)Buffer.MarshalToManaged(0, ODBC32.SQL_C.WCHAR, ODBC32.SQL_NTS);
663                         this.dataCache[i] = Int64.Parse(value, CultureInfo.InvariantCulture);
664                     }
665                 }
666                 return this.dataCache[i];
667             }
668             throw ADP.DataReaderNoData();
669         }
670 
GetBoolean(int i)671         override public bool GetBoolean(int i) {
672             return (bool) internalGetBoolean(i);
673         }
internalGetBoolean(int i)674         private object internalGetBoolean(int i) {
675             if (_isRead) {
676                 if(this.dataCache.AccessIndex(i) == null) {
677                     if(GetData(i, ODBC32.SQL_C.BIT)){
678                         this.dataCache[i] = Buffer.MarshalToManaged(0, ODBC32.SQL_C.BIT, -1);
679                     }
680                 }
681                 return this.dataCache[i];
682             }
683             throw ADP.DataReaderNoData();
684         }
685 
GetFloat(int i)686         override public float GetFloat(int i) {
687             return (float)internalGetFloat(i);
688         }
internalGetFloat(int i)689         private object internalGetFloat(int i) {
690             if (_isRead) {
691                 if(this.dataCache.AccessIndex(i) == null) {
692                     if(GetData(i, ODBC32.SQL_C.REAL)){
693                         this.dataCache[i] = Buffer.ReadSingle(0);
694                     }
695                 }
696                 return this.dataCache[i];
697             }
698             throw ADP.DataReaderNoData();
699         }
700 
GetDate(int i)701         public DateTime GetDate(int i) {
702             return (DateTime)internalGetDate(i);
703         }
704 
internalGetDate(int i)705         private object internalGetDate(int i) {
706             if (_isRead) {
707                 if(this.dataCache.AccessIndex(i) == null) {
708                     if(GetData(i, ODBC32.SQL_C.TYPE_DATE)){
709                         this.dataCache[i] = Buffer.MarshalToManaged(0, ODBC32.SQL_C.TYPE_DATE, -1);
710                     }
711                 }
712                 return this.dataCache[i];
713             }
714             throw ADP.DataReaderNoData();
715         }
716 
GetDateTime(int i)717         override public DateTime GetDateTime(int i) {
718             return (DateTime)internalGetDateTime(i);
719         }
720 
internalGetDateTime(int i)721         private object internalGetDateTime(int i) {
722             if (_isRead) {
723                 if(this.dataCache.AccessIndex(i) == null) {
724                     if(GetData(i, ODBC32.SQL_C.TYPE_TIMESTAMP)){
725                         this.dataCache[i] = Buffer.MarshalToManaged(0, ODBC32.SQL_C.TYPE_TIMESTAMP, -1);
726                     }
727                 }
728                 return this.dataCache[i];
729             }
730             throw ADP.DataReaderNoData();
731         }
732 
GetDecimal(int i)733         override public decimal GetDecimal(int i) {
734             return (decimal)internalGetDecimal(i);
735         }
736 
737         // ---------------------------------------------------------------------------------------------- //
738         // internal GetDecimal
739         // -------------------
740         // Get Value of type SQL_DECIMAL or SQL_NUMERIC
741         // Due to provider incompatibilities with SQL_DECIMAL or SQL_NUMERIC types we always read the value
742         // as SQL_C_WCHAR and convert it back to the Decimal data type
743         //
internalGetDecimal(int i)744         private object internalGetDecimal(int i) {
745             if (_isRead) {
746                 if(this.dataCache.AccessIndex(i) == null) {
747                     if(GetData(i, ODBC32.SQL_C.WCHAR )){
748                         string s = null;
749                         try {
750                             s = (string)Buffer.MarshalToManaged(0, ODBC32.SQL_C.WCHAR, ODBC32.SQL_NTS);
751                             this.dataCache[i] = Decimal.Parse(s, System.Globalization.CultureInfo.InvariantCulture);
752                         }
753                         catch(OverflowException e) {
754                             this.dataCache[i] = s;
755                             throw e;
756                         }
757                     }
758                 }
759                 return this.dataCache[i];
760             }
761             throw ADP.DataReaderNoData();
762         }
763 
GetDouble(int i)764         override public double GetDouble(int i) {
765             return (double)internalGetDouble(i);
766         }
internalGetDouble(int i)767         private object internalGetDouble(int i) {
768             if (_isRead) {
769                 if(this.dataCache.AccessIndex(i) == null) {
770                     if(GetData(i, ODBC32.SQL_C.DOUBLE)){
771                         this.dataCache[i] = Buffer.ReadDouble(0);
772                     }
773                 }
774                 return this.dataCache[i];
775             }
776             throw ADP.DataReaderNoData();
777         }
778 
GetGuid(int i)779         override public Guid GetGuid(int i) {
780             return (Guid)internalGetGuid(i);
781         }
782 
internalGetGuid(int i)783         private object internalGetGuid(int i) {
784             if (_isRead) {
785                 if(this.dataCache.AccessIndex(i) == null) {
786                     if(GetData(i, ODBC32.SQL_C.GUID)){
787                         this.dataCache[i] = Buffer.ReadGuid(0);
788                     }
789                 }
790                 return this.dataCache[i];
791             }
792             throw ADP.DataReaderNoData();
793         }
794 
GetString(int i)795         override public String GetString(int i) {
796             return (String)internalGetString(i);
797         }
798 
internalGetString(int i)799         private object internalGetString(int i) {
800             if(_isRead) {
801                 if(this.dataCache.AccessIndex(i) == null) {
802                     // Obtain _ALL_ the characters
803                     // Note: We can bind directly as WCHAR in ODBC and the DM will convert to and
804                     // from ANSI if not supported by the driver.
805                     //
806 
807                     // Note: The driver always returns the raw length of the data, minus the
808                     // terminator.  This means that our buffer length always includes the terminator
809                     // charactor, so when determining which characters to count, and if more data
810                     // exists, it should not take the terminator into effect.
811                     //
812                     CNativeBuffer buffer = Buffer;
813                     // that does not make sense unless we expect four byte terminators
814                     int cbMaxData = buffer.Length - 4;
815 
816                     // The first time GetData returns the true length (so we have to min it).
817                     // We also pass in the true length to the marshal function since there could be
818                     // embedded nulls
819                     //
820                     int lengthOrIndicator;
821                     if (GetData(i, ODBC32.SQL_C.WCHAR, buffer.Length - 2, out lengthOrIndicator)) {
822                         // RFC 50002644: we do not expect negative values from GetData call except SQL_NO_TOTAL(== -4)
823                         // note that in general you should not trust third-party providers so such asserts should be
824                         // followed by exception. I did not add it now to avoid breaking change
825                         Debug.Assert(lengthOrIndicator >= 0 || lengthOrIndicator == ODBC32.SQL_NO_TOTAL, "unexpected lengthOrIndicator value");
826 
827                         if (lengthOrIndicator <= cbMaxData && (ODBC32.SQL_NO_TOTAL != lengthOrIndicator)) {
828                             // all data read? good! Directly marshal to a string and we're done
829                             //
830                             string strdata = buffer.PtrToStringUni(0, Math.Min(lengthOrIndicator, cbMaxData) / 2);
831                             this.dataCache[i] = strdata;
832                             return strdata;
833                         }
834 
835                         // We need to chunk the data
836                         // Char[] buffer for the junks
837                         // StringBuilder for the actual string
838                         //
839                         Char[] rgChars = new Char[cbMaxData / 2];
840 
841                         // RFC 50002644: negative value cannot be used for capacity.
842                         // in case of SQL_NO_TOTAL, set the capacity to cbMaxData, StringBuilder will automatically reallocate
843                         // its internal buffer when appending more data
844                         int cbBuilderInitialCapacity = (lengthOrIndicator == ODBC32.SQL_NO_TOTAL) ? cbMaxData : lengthOrIndicator;
845                         StringBuilder builder = new StringBuilder(cbBuilderInitialCapacity / 2);
846 
847                         bool gotData;
848                         int cchJunk;
849                         int cbActual = cbMaxData;
850                         int cbMissing = (ODBC32.SQL_NO_TOTAL == lengthOrIndicator) ? -1 : lengthOrIndicator - cbActual;
851 
852                         do {
853                             cchJunk = cbActual / 2;
854                             buffer.ReadChars(0, rgChars, 0, cchJunk);
855                             builder.Append(rgChars, 0, cchJunk);
856 
857                             if(0 == cbMissing) {
858                                 break;  // done
859                             }
860 
861                             gotData = GetData(i, ODBC32.SQL_C.WCHAR, buffer.Length - 2, out lengthOrIndicator);
862                             // RFC 50002644: we do not expect negative values from GetData call except SQL_NO_TOTAL(== -4)
863                             // note that in general you should not trust third-party providers so such asserts should be
864                             // followed by exception. I did not add it now to avoid breaking change
865                             Debug.Assert(lengthOrIndicator >= 0 || lengthOrIndicator == ODBC32.SQL_NO_TOTAL, "unexpected lengthOrIndicator value");
866 
867                             if (ODBC32.SQL_NO_TOTAL != lengthOrIndicator) {
868                                 cbActual = Math.Min(lengthOrIndicator, cbMaxData);
869                                 if(0 < cbMissing) {
870                                     cbMissing -= cbActual;
871                                 }
872                                 else {
873                                     // it is a last call to SqlGetData that started with SQL_NO_TOTAL
874                                     // the last call to SqlGetData must always return the length of the
875                                     // data, not zero or SqlNoTotal (see Odbc Programmers Reference)
876                                     Debug.Assert(cbMissing == -1 && lengthOrIndicator <= cbMaxData);
877                                     cbMissing = 0;
878                                 }
879                             }
880                         }
881                         while(gotData);
882 
883                         this.dataCache[i] = builder.ToString();
884                     }
885                 }
886                 return this.dataCache[i];
887             }
888             throw ADP.DataReaderNoData();
889         }
890 
GetTime(int i)891         public TimeSpan GetTime(int i) {
892             return (TimeSpan)internalGetTime(i);
893         }
894 
internalGetTime(int i)895         private object internalGetTime(int i) {
896             if (_isRead) {
897                 if(this.dataCache.AccessIndex(i) == null) {
898                     if(GetData(i, ODBC32.SQL_C.TYPE_TIME)){
899                         this.dataCache[i] = Buffer.MarshalToManaged(0, ODBC32.SQL_C.TYPE_TIME, -1);
900                     }
901                 }
902                 return this.dataCache[i];
903             }
904             throw ADP.DataReaderNoData();
905         }
906 
SetCurrentRowColumnInfo(int row, int column)907         private void SetCurrentRowColumnInfo(int row, int column)
908         {
909             if (_row != row || _column != column)
910             {
911                 _row = row;
912                 _column = column;
913 
914                 // reset the blob reader when moved to new column
915                 _sequentialBytesRead = 0;
916             }
917         }
918 
GetBytes(int i, long dataIndex, byte[] buffer, int bufferIndex, int length)919         override public long GetBytes(int i, long dataIndex, byte[] buffer, int bufferIndex, int length) {
920             return GetBytesOrChars(i, dataIndex, buffer, false /* bytes buffer */, bufferIndex, length);
921         }
GetChars(int i, long dataIndex, char[] buffer, int bufferIndex, int length)922         override public long GetChars(int i, long dataIndex, char[] buffer, int bufferIndex, int length) {
923             return GetBytesOrChars(i, dataIndex, buffer, true /* chars buffer */, bufferIndex, length);
924         }
925 
926         // unify the implementation of GetChars and GetBytes to prevent code duplicate
GetBytesOrChars(int i, long dataIndex, Array buffer, bool isCharsBuffer, int bufferIndex, int length)927         private long GetBytesOrChars(int i, long dataIndex, Array buffer, bool isCharsBuffer, int bufferIndex, int length) {
928             if (IsClosed) {
929                 throw ADP.DataReaderNoData();
930             }
931             if(!_isRead) {
932                 throw ADP.DataReaderNoData();
933             }
934             if(dataIndex < 0 ) {
935                 // test only for negative value here, Int32.MaxValue will be validated only in case of random access
936                 throw ADP.ArgumentOutOfRange("dataIndex");
937             }
938             if(bufferIndex < 0) {
939                 throw ADP.ArgumentOutOfRange("bufferIndex");
940             }
941             if(length < 0) {
942                 throw ADP.ArgumentOutOfRange("length");
943             }
944 
945             string originalMethodName = isCharsBuffer ? "GetChars" : "GetBytes";
946 
947             // row/column info will be reset only if changed
948             SetCurrentRowColumnInfo(_row, i);
949 
950             // Possible cases:
951             // 1. random access, user asks for the value first time: bring it and cache the value
952             // 2. random access, user already queried the value: use the cache
953             // 3. sequential access, cache exists: user already read this value using different method (it becomes cached)
954             //                       use the cache - preserve the original behavior to minimize regression risk
955             // 4. sequential access, no cache: (fixed now) user reads the bytes/chars in sequential order (no cache)
956 
957             object cachedObj = null;                 // The cached object (if there is one)
958 
959             // Get cached object, ensure the correct type using explicit cast, to preserve same behavior as before
960             if (isCharsBuffer)
961                 cachedObj = (string)this.dataCache[i];
962             else
963                 cachedObj = (byte[])this.dataCache[i];
964 
965             bool isRandomAccess = !IsCommandBehavior(CommandBehavior.SequentialAccess);
966 
967             if (isRandomAccess || (cachedObj != null))
968             {
969                 // random access (cases 1 or 2) and sequential access with cache (case 3)
970                 // preserve the original behavior as before the fix
971 
972                 if (Int32.MaxValue < dataIndex)
973                 {
974                     // indices greater than allocable size are not supported in random access
975                     // (negative value is already tested in the beginning of ths function)
976                     throw ADP.ArgumentOutOfRange("dataIndex");
977                 }
978 
979                 if(cachedObj == null) {
980                     // case 1, get the value and cache it
981                     // internalGetString/internalGetBytes will get the entire value and cache it,
982                     // since we are not in SequentialAccess (isRandomAccess is true), it is OK
983 
984                     if (isCharsBuffer) {
985                         cachedObj = (String)internalGetString(i);
986                         Debug.Assert((cachedObj != null), "internalGetString should always return non-null or raise exception");
987                     }
988                     else {
989                         cachedObj = (byte[])internalGetBytes(i);
990                         Debug.Assert((cachedObj != null), "internalGetBytes should always return non-null or raise exception");
991                     }
992 
993                     // continue to case 2
994                 }
995 
996                 // after this point the value is cached (case 2 or 3)
997                 // if it is DbNull, cast exception will be raised (same as before the 110664 fix)
998                 int cachedObjectLength = isCharsBuffer ? ((string)cachedObj).Length : ((byte[])cachedObj).Length;
999 
1000                 // the user can ask for the length of the field by passing in a null pointer for the buffer
1001                 if (buffer == null) {
1002                     // return the length if that's all what user needs
1003                     return cachedObjectLength;
1004                 }
1005 
1006                 // user asks for bytes
1007 
1008                 if (length == 0) {
1009                     return 0;   // Nothing to do ...
1010                 }
1011 
1012                 if (dataIndex >= cachedObjectLength)
1013                 {
1014                     // no more bytes to read
1015                     // see also MDAC bug 73298
1016                     return 0;
1017                 }
1018 
1019                 int lengthFromDataIndex = cachedObjectLength - (int)dataIndex;
1020                 int lengthOfCopy = Math.Min(lengthFromDataIndex, length);
1021 
1022                 // silently reduce the length to avoid regression from EVERETT
1023                 lengthOfCopy = Math.Min(lengthOfCopy, buffer.Length - bufferIndex);
1024                 if (lengthOfCopy <= 0) return 0;                    // MDAC Bug 73298
1025 
1026                 if (isCharsBuffer)
1027                     ((string)cachedObj).CopyTo((int)dataIndex, (char[])buffer, bufferIndex, lengthOfCopy);
1028                 else
1029                     Array.Copy((byte[])cachedObj, (int)dataIndex, (byte[])buffer, bufferIndex, lengthOfCopy);
1030 
1031                 return lengthOfCopy;
1032             }
1033             else {
1034                 // sequential access, case 4
1035 
1036                 // SQLBU:532243 -- For SequentialAccess we need to read a chunk of
1037                 // data and not cache it.
1038                 // Note: If the object was previous cached (see case 3 above), the function will go thru 'if' path, to minimize
1039                 // regressions
1040 
1041                 // the user can ask for the length of the field by passing in a null pointer for the buffer
1042                 if (buffer == null) {
1043                     // Get len. of remaining data from provider
1044                     ODBC32.SQL_C sqlctype;
1045                     int cbLengthOrIndicator;
1046                     bool isDbNull;
1047 
1048                     sqlctype = isCharsBuffer ? ODBC32.SQL_C.WCHAR : ODBC32.SQL_C.BINARY;
1049                     isDbNull = !QueryFieldInfo(i, sqlctype, out cbLengthOrIndicator);
1050 
1051                     if (isDbNull) {
1052                         // SQLBU 266054:
1053 
1054                         // GetChars:
1055                         //   in Orcas RTM: GetChars has always raised InvalidCastException.
1056                         //   in Orcas SP1: GetChars returned 0 if DbNull is not cached yet and InvalidCastException if it is in cache (from case 3).
1057                         //   Managed Providers team has decided to fix the GetChars behavior and raise InvalidCastException, as it was in RTM
1058                         //   Reason: returing 0 is wrong behavior since it conflicts with return value in case of empty data
1059 
1060                         // GetBytes:
1061                         //   In Orcas RTM: GetBytes(null buffer) returned -1 for null value if DbNull is not cached yet.
1062                         //   But, after calling IsDBNull, GetBytes(null) raised InvalidCastException.
1063                         //   In Orcas SP1: GetBytes always raises InvalidCastException for null value.
1064                         //   Managed Providers team has decided to keep the behavior of RTM for this case to fix the RTM's breaking change.
1065                         //   Reason: while -1 is wrong behavior, people might be already relying on it, so we should not be changing it.
1066                         //   Note: this will happen only on the first call to GetBytes(with null buffer).
1067                         //   If IsDbNull has already been called before or for second call to query for size,
1068                         //   DBNull is cached and GetBytes raises InvalidCastException in case 3 (see the cases above in this method).
1069 
1070                         if (isCharsBuffer) {
1071                             throw ADP.InvalidCast();
1072                         }
1073                         else {
1074                             return -1;
1075                         }
1076                     }
1077                     else {
1078                         // the value is not null
1079 
1080                         // SQLBU 266054:
1081                         // If cbLengthOrIndicator is SQL_NO_TOTAL (-4), this call returns -4 or -2, depending on the type (GetChars=>-2, GetBytes=>-4).
1082                         // This is the Orcas RTM and SP1 behavior, changing this would be a breaking change.
1083                         // SQL_NO_TOTAL means that the driver does not know what is the remained lenght of the data, so we cannot really guess the value here.
1084                         // Reason: while returning different negative values depending on the type seems inconsistent,
1085                         // this is what we did in Orcas RTM and SP1 and user code might rely on this behavior => changing it would be a breaking change.
1086                         if (isCharsBuffer) {
1087                             return cbLengthOrIndicator / 2; // return length in wide characters or -2 if driver returns SQL_NO_TOTAL
1088                         }
1089                         else {
1090                             return cbLengthOrIndicator; // return length in bytes or -4 if driver returns SQL_NO_TOTAL
1091                         }
1092                     }
1093                 }
1094                 else {
1095                     // buffer != null, read the data
1096 
1097                     // check if user tries to read data that was already received
1098                     // if yes, this violates 'sequential access'
1099                     if ((isCharsBuffer && dataIndex < _sequentialBytesRead / 2) ||
1100                         (!isCharsBuffer && dataIndex < _sequentialBytesRead)) {
1101                         // backward reading is not allowed in sequential access
1102                         throw ADP.NonSeqByteAccess(
1103                             dataIndex,
1104                             _sequentialBytesRead,
1105                             originalMethodName
1106                             );
1107                     }
1108 
1109                     // note that it is actually not possible to read with an offset (dataIndex)
1110                     // therefore, adjust the data index relative to number of bytes already read
1111                     if (isCharsBuffer)
1112                         dataIndex -= _sequentialBytesRead / 2;
1113                     else
1114                         dataIndex -= _sequentialBytesRead;
1115 
1116                     if (dataIndex > 0)
1117                     {
1118                         // user asked to skip bytes - it is OK, even in case of sequential access
1119                         // forward the stream by dataIndex bytes/chars
1120                         int charsOrBytesRead = readBytesOrCharsSequentialAccess(i, null, isCharsBuffer, 0, dataIndex);
1121                         if (charsOrBytesRead < dataIndex)
1122                         {
1123                             // the stream ended before we forwarded to the requested index, stop now
1124                             return 0;
1125                         }
1126                     }
1127 
1128                     // ODBC driver now points to the correct position, start filling the user buffer from now
1129 
1130                     // Make sure we don't overflow the user provided buffer
1131                     // Note: SqlDataReader will raise exception if there is no enough room for length requested.
1132                     // In case of ODBC, I decided to keep this consistent with random access after consulting with PM.
1133                     length = Math.Min(length, buffer.Length - bufferIndex);
1134                     if (length <= 0) {
1135                         // SQLBU 266054:
1136                         // if the data is null, the ideal behavior here is to raise InvalidCastException. But,
1137                         // * GetBytes returned 0 in Orcas RTM and SP1, continue to do so to avoid breaking change from Orcas RTM and SP1.
1138                         // * GetChars raised exception in RTM, and returned 0 in SP1: we decided to revert back to the RTM's behavior and raise InvalidCast
1139                         if (isCharsBuffer) {
1140                             // for GetChars, ensure data is not null
1141                             // 2 bytes for '\0' termination, no data is actually read from the driver
1142                             int cbLengthOrIndicator;
1143                             bool isDbNull = !QueryFieldInfo(i, ODBC32.SQL_C.WCHAR, out cbLengthOrIndicator);
1144                             if (isDbNull) {
1145                                 throw ADP.InvalidCast();
1146                             }
1147                         }
1148                         // else - GetBytes - return now
1149                         return 0;
1150                     }
1151 
1152                     // fill the user's buffer
1153                     return readBytesOrCharsSequentialAccess(i, buffer, isCharsBuffer, bufferIndex, length);
1154                 }
1155             }
1156         }
1157 
1158         // fill the user's buffer (char[] or byte[], depending on isCharsBuffer)
1159         // if buffer is null, just skip the bytesOrCharsLength bytes or chars
readBytesOrCharsSequentialAccess(int i, Array buffer, bool isCharsBuffer, int bufferIndex, long bytesOrCharsLength)1160         private int readBytesOrCharsSequentialAccess(int i, Array buffer, bool isCharsBuffer, int bufferIndex, long bytesOrCharsLength) {
1161             Debug.Assert(bufferIndex >= 0, "Negative buffer index");
1162             Debug.Assert(bytesOrCharsLength >= 0, "Negative number of bytes or chars to read");
1163 
1164             // validated by the caller
1165             Debug.Assert(buffer == null || bytesOrCharsLength <= (buffer.Length - bufferIndex), "Not enough space in user's buffer");
1166 
1167             int totalBytesOrCharsRead = 0;
1168             string originalMethodName = isCharsBuffer ? "GetChars" : "GetBytes";
1169 
1170             // we need length in bytes, b/c that is what SQLGetData expects
1171             long cbLength = (isCharsBuffer)? checked(bytesOrCharsLength * 2) : bytesOrCharsLength;
1172 
1173             // continue reading from the driver until we fill the user's buffer or until no more data is available
1174             // the data is pumped first into the internal native buffer and after that copied into the user's one if buffer is not null
1175             CNativeBuffer internalNativeBuffer = this.Buffer;
1176 
1177             // read the data in loop up to th user's length
1178             // if the data size is less than requested or in case of error, the while loop will stop in the middle
1179             while (cbLength > 0)
1180             {
1181                 // max data to be read, in bytes, not including null-terminator for WCHARs
1182                 int cbReadMax;
1183 
1184                 // read from the driver
1185                 bool isNotDbNull;
1186                 int cbTotal;
1187                 // read either bytes or chars, depending on the method called
1188                 if (isCharsBuffer) {
1189                     // for WCHAR buffers, we need to leave space for null-terminator (2 bytes)
1190                     // reserve 2 bytes for null-terminator and 2 bytes to prevent assert in GetData
1191                     // if SQL_NO_TOTAL is returned, this ammount is read from the wire, in bytes
1192                     cbReadMax = (int)Math.Min(cbLength, internalNativeBuffer.Length - 4);
1193 
1194                     // SQLGetData will always append it - we do not to copy it to user's buffer
1195                     isNotDbNull = GetData(i, ODBC32.SQL_C.WCHAR, cbReadMax + 2, out cbTotal);
1196                 }
1197                 else {
1198                     // reserve 2 bytes to prevent assert in GetData
1199                     // when querying bytes, no need to reserve space for null
1200                     cbReadMax = (int)Math.Min(cbLength, internalNativeBuffer.Length - 2);
1201 
1202                     isNotDbNull = GetData(i, ODBC32.SQL_C.BINARY, cbReadMax, out cbTotal);
1203                 }
1204 
1205                 if (!isNotDbNull)
1206                 {
1207                     // DbNull received, neither GetBytes nor GetChars should be used with DbNull value
1208                     // two options
1209                     // 1. be consistent with SqlDataReader, raise SqlNullValueException
1210                     // 2. be consistent with other Get* methods of OdbcDataReader and raise InvalidCastException
1211                     // after consulting with Himanshu (PM), decided to go with option 2 (raise cast exception)
1212                     throw ADP.InvalidCast();
1213                 }
1214 
1215                 int cbRead; // will hold number of bytes read in this loop
1216                 bool noDataRemained = false;
1217                 if (cbTotal == 0)
1218                 {
1219                     // no bytes read, stop
1220                     break;
1221                 }
1222                 else if (ODBC32.SQL_NO_TOTAL == cbTotal)
1223                 {
1224                     // the driver has filled the internal buffer, but the length of remained data is still unknown
1225                     // we will continue looping until SQLGetData indicates the end of data or user buffer is fully filled
1226                     cbRead = cbReadMax;
1227                 }
1228                 else
1229                 {
1230                     Debug.Assert((cbTotal > 0), "GetData returned negative value, which is not SQL_NO_TOTAL");
1231                     // GetData uses SQLGetData, which StrLen_or_IndPtr (cbTotal in our case) to the current buf + remained buf (if any)
1232                     if (cbTotal > cbReadMax)
1233                     {
1234                         // in this case the amount of bytes/chars read will be the max requested (and more bytes can be read)
1235                         cbRead = cbReadMax;
1236                     }
1237                     else
1238                     {
1239                         // SQLGetData read all the available data, no more remained
1240                         // continue processing this chunk and stop
1241                         cbRead = cbTotal;
1242                         noDataRemained = true;
1243                     }
1244                 }
1245 
1246                 _sequentialBytesRead += cbRead;
1247 
1248                 // update internal state and copy the data to user's buffer
1249                 if (isCharsBuffer)
1250                 {
1251                     int cchRead = cbRead / 2;
1252                     if (buffer != null) {
1253                         internalNativeBuffer.ReadChars(0, (char[])buffer, bufferIndex, cchRead);
1254                         bufferIndex += cchRead;
1255                     }
1256                     totalBytesOrCharsRead += cchRead;
1257 
1258                 }
1259                 else
1260                 {
1261                     if (buffer != null) {
1262                         internalNativeBuffer.ReadBytes(0, (byte[])buffer, bufferIndex, cbRead);
1263                         bufferIndex += cbRead;
1264                     }
1265                     totalBytesOrCharsRead += cbRead;
1266                 }
1267 
1268                 cbLength -= cbRead;
1269 
1270                 // stop if no data remained
1271                 if (noDataRemained)
1272                     break;
1273             }
1274 
1275             return totalBytesOrCharsRead;
1276         }
1277 
internalGetBytes(int i)1278         private object internalGetBytes(int i) {
1279             if(this.dataCache.AccessIndex(i) == null) {
1280                 // Obtain _ALL_ the bytes...
1281                 // The first time GetData returns the true length (so we have to min it).
1282                 Byte[] rgBytes;
1283                 int cbBufferLen = Buffer.Length - 4;
1284                 int cbActual;
1285                 int cbOffset = 0;
1286 
1287                 if(GetData(i, ODBC32.SQL_C.BINARY, cbBufferLen, out cbActual)) {
1288                     CNativeBuffer buffer = Buffer;
1289 
1290                     if(ODBC32.SQL_NO_TOTAL != cbActual) {
1291                         rgBytes = new Byte[cbActual];
1292                         Buffer.ReadBytes(0, rgBytes, cbOffset, Math.Min(cbActual, cbBufferLen));
1293 
1294                         // Chunking.  The data may be larger than our native buffer.  In which case
1295                         // instead of growing the buffer (out of control), we will read in chunks to
1296                         // reduce memory footprint size.
1297                         while(cbActual > cbBufferLen) {
1298                             // The first time GetData returns the true length.  Then successive calls
1299                             // return the remaining data.
1300                             bool flag = GetData(i, ODBC32.SQL_C.BINARY, cbBufferLen, out cbActual);
1301                             Debug.Assert(flag, "internalGetBytes - unexpected invalid result inside if-block");
1302 
1303                             cbOffset += cbBufferLen;
1304                             buffer.ReadBytes(0, rgBytes, cbOffset, Math.Min(cbActual, cbBufferLen));
1305                         }
1306                     }
1307                     else {
1308                         List<Byte[]> junkArray = new List<Byte[]>();
1309                         int junkSize;
1310                         int totalSize = 0;
1311                         do {
1312                             junkSize = (ODBC32.SQL_NO_TOTAL != cbActual) ? cbActual : cbBufferLen;
1313                             rgBytes = new Byte[junkSize];
1314                             totalSize += junkSize;
1315                             buffer.ReadBytes(0, rgBytes, 0, junkSize);
1316                             junkArray.Add(rgBytes);
1317                         }
1318                         while ((ODBC32.SQL_NO_TOTAL == cbActual) && GetData(i, ODBC32.SQL_C.BINARY, cbBufferLen, out cbActual));
1319 
1320                         rgBytes = new Byte[totalSize];
1321                         foreach(Byte[] junk in junkArray) {
1322                             junk.CopyTo(rgBytes, cbOffset);
1323                             cbOffset += junk.Length;
1324                         }
1325                     }
1326 
1327                     // always update the cache
1328                     this.dataCache[i] = rgBytes;
1329                 }
1330             }
1331             return this.dataCache[i];
1332         }
1333 
1334         // GetColAttribute
1335         // ---------------
1336         // [IN] iColumn   ColumnNumber
1337         // [IN] v3FieldId FieldIdentifier of the attribute for version3 drivers (>=3.0)
1338         // [IN] v2FieldId FieldIdentifier of the attribute for version2 drivers (<3.0)
1339         //
1340         // returns the value of the FieldIdentifier field of the column
1341         // or -1 if the FieldIdentifier wasn't supported by the driver
1342         //
GetColAttribute(int iColumn, ODBC32.SQL_DESC v3FieldId, ODBC32.SQL_COLUMN v2FieldId, ODBC32.HANDLER handler)1343         private SQLLEN GetColAttribute(int iColumn, ODBC32.SQL_DESC v3FieldId, ODBC32.SQL_COLUMN v2FieldId, ODBC32.HANDLER handler) {
1344             Int16   cchNameLength = 0;
1345             SQLLEN   numericAttribute;
1346             ODBC32.RetCode retcode;
1347 
1348             // protect against dead connection, dead or canceling command.
1349             if ((Connection == null) || _cmdWrapper.Canceling) {
1350                 return -1;
1351             }
1352 
1353             //Ordinals are 1:base in odbc
1354             OdbcStatementHandle stmt = StatementHandle;
1355             if (Connection.IsV3Driver) {
1356                 retcode = stmt.ColumnAttribute(iColumn+1, (short)v3FieldId, Buffer, out cchNameLength, out numericAttribute);
1357             }
1358             else if (v2FieldId != (ODBC32.SQL_COLUMN)(-1)) {
1359                 retcode = stmt.ColumnAttribute(iColumn+1, (short)v2FieldId, Buffer, out cchNameLength, out numericAttribute);
1360             }
1361             else {
1362                 return 0;
1363             }
1364             if (retcode != ODBC32.RetCode.SUCCESS)
1365             {
1366                 if (retcode == ODBC32.RetCode.ERROR) {
1367                     if ("HY091" == Command.GetDiagSqlState()) {
1368                         Connection.FlagUnsupportedColAttr(v3FieldId, v2FieldId);
1369                     }
1370                 }
1371                 if(handler == ODBC32.HANDLER.THROW) {
1372                     Connection.HandleError(stmt, retcode);
1373                 }
1374                 return -1;
1375             }
1376             return numericAttribute;
1377         }
1378 
1379         // GetColAttributeStr
1380         // ---------------
1381         // [IN] iColumn   ColumnNumber
1382         // [IN] v3FieldId FieldIdentifier of the attribute for version3 drivers (>=3.0)
1383         // [IN] v2FieldId FieldIdentifier of the attribute for version2 drivers (<3.0)
1384         //
1385         // returns the stringvalue of the FieldIdentifier field of the column
1386         // or null if the string returned was empty or if the FieldIdentifier wasn't supported by the driver
1387         //
GetColAttributeStr(int i, ODBC32.SQL_DESC v3FieldId, ODBC32.SQL_COLUMN v2FieldId, ODBC32.HANDLER handler)1388         private String GetColAttributeStr(int i, ODBC32.SQL_DESC v3FieldId, ODBC32.SQL_COLUMN v2FieldId, ODBC32.HANDLER handler) {
1389             ODBC32.RetCode retcode;
1390             Int16   cchNameLength = 0;
1391             SQLLEN   numericAttribute;
1392             CNativeBuffer buffer = Buffer;
1393             buffer.WriteInt16(0, 0);
1394 
1395             OdbcStatementHandle stmt = StatementHandle;
1396 
1397             // protect against dead connection
1398             if (Connection == null || _cmdWrapper.Canceling || stmt == null) {
1399                 return "";
1400             }
1401 
1402             if (Connection.IsV3Driver) {
1403                 retcode = stmt.ColumnAttribute(i+1, (short)v3FieldId, buffer, out cchNameLength, out numericAttribute);
1404             }
1405             else if (v2FieldId != (ODBC32.SQL_COLUMN)(-1)) {
1406                 retcode = stmt.ColumnAttribute(i+1, (short)v2FieldId, buffer, out cchNameLength, out numericAttribute);
1407             }
1408             else {
1409                 return null;
1410             }
1411             if((retcode != ODBC32.RetCode.SUCCESS) || (cchNameLength == 0))
1412             {
1413                 if (retcode == ODBC32.RetCode.ERROR) {
1414                     if ("HY091" == Command.GetDiagSqlState()) {
1415                         Connection.FlagUnsupportedColAttr(v3FieldId, v2FieldId);
1416                     }
1417                 }
1418                 if(handler == ODBC32.HANDLER.THROW) {
1419                     Connection.HandleError(stmt, retcode);
1420                 }
1421                 return null;
1422             }
1423             string retval = buffer.PtrToStringUni(0, cchNameLength/2 /*cch*/);
1424             return retval;
1425         }
1426 
1427 // todo: Another 3.0 only attribute that is guaranteed to fail on V2 driver.
1428 // need to special case this for V2 drivers.
1429 //
GetDescFieldStr(int i, ODBC32.SQL_DESC attribute, ODBC32.HANDLER handler)1430         private String GetDescFieldStr(int i, ODBC32.SQL_DESC attribute, ODBC32.HANDLER handler) {
1431             Int32   numericAttribute = 0;
1432 
1433             // protect against dead connection, dead or canceling command.
1434             if ((Connection == null) || _cmdWrapper.Canceling) {
1435                 return "";
1436             }
1437 
1438             // APP_PARAM_DESC is a (ODBCVER >= 0x0300) attribute
1439             if (!Connection.IsV3Driver) {
1440                 Debug.Assert (false, "Non-V3 driver. Must not call GetDescFieldStr");
1441                 return null;
1442             }
1443 
1444             ODBC32.RetCode retcode;
1445             CNativeBuffer buffer = Buffer;
1446 
1447             // Need to set the APP_PARAM_DESC values here
1448             using(OdbcDescriptorHandle hdesc = new OdbcDescriptorHandle(StatementHandle, ODBC32.SQL_ATTR.APP_PARAM_DESC)) {
1449                 //SQLGetDescField
1450                 retcode = hdesc.GetDescriptionField(i+1, attribute, buffer, out numericAttribute);
1451 
1452                 //Since there are many attributes (column, statement, etc), that may or may not be
1453                 //supported, we don't want to throw (which obtains all errorinfo, marshals strings,
1454                 //builds exceptions, etc), in common cases, unless we absolutly need this info...
1455                 if((retcode != ODBC32.RetCode.SUCCESS) || (numericAttribute == 0))
1456                 {
1457                     if (retcode == ODBC32.RetCode.ERROR) {
1458                         if ("HY091" == Command.GetDiagSqlState()) {
1459                             Connection.FlagUnsupportedColAttr(attribute, (ODBC32.SQL_COLUMN)0);
1460                         }
1461                     }
1462                     if(handler == ODBC32.HANDLER.THROW) {
1463                         Connection.HandleError(StatementHandle, retcode);
1464                     }
1465                     return null;
1466                 }
1467             }
1468             string retval = buffer.PtrToStringUni(0, numericAttribute/2 /*cch*/);
1469             return retval;
1470         }
1471 
1472         /// <summary>
1473         /// This methods queries the following field information: isDbNull and remained size/indicator. No data is read from the driver.
1474         /// If the value is DbNull, this value will be cached. Refer to GetData for more details.
1475         /// </summary>
1476         /// <returns>false if value is DbNull, true otherwise</returns>
QueryFieldInfo(int i, ODBC32.SQL_C sqlctype, out int cbLengthOrIndicator)1477         private bool QueryFieldInfo(int i, ODBC32.SQL_C sqlctype, out int cbLengthOrIndicator) {
1478             int cb = 0;
1479             if (sqlctype == ODBC32.SQL_C.WCHAR) {
1480                 // SQLBU 266054 - in case of WCHAR data, we need to provide buffer with a space for null terminator (two bytes)
1481                 cb = 2;
1482             }
1483             return GetData(i, sqlctype, cb /* no data should be lost */, out cbLengthOrIndicator);
1484         }
1485 
GetData(int i, ODBC32.SQL_C sqlctype)1486         private bool GetData(int i, ODBC32.SQL_C sqlctype) {
1487             // Never call GetData with anything larger than _buffer.Length-2.
1488             // We keep reallocating native buffers and it kills performance!!!
1489             int dummy;
1490             return GetData(i, sqlctype, Buffer.Length - 4, out dummy);
1491         }
1492 
1493         /// <summary>
1494         /// Note: use only this method to call SQLGetData! It caches the null value so the fact that the value is null is kept and no other calls
1495         /// are made after it.
1496         ///
1497         /// retrieves the data into this.Buffer.
1498         /// * If the data is DbNull, the value be also cached and false is returned.
1499         /// * if the data is not DbNull, the value is not cached and true is returned
1500         ///
1501         /// Note: cbLengthOrIndicator can be either the length of (remained) data or SQL_NO_TOTAL (-4) when the length is not known.
1502         /// in case of SQL_NO_TOTAL, driver fills the buffer till the end.
1503         /// The indicator will NOT be SQL_NULL_DATA, GetData will replace it with zero and return false.
1504         /// </summary>
1505         /// <returns>false if value is DbNull, true otherwise</returns>
GetData(int i, ODBC32.SQL_C sqlctype, int cb, out int cbLengthOrIndicator)1506         private bool GetData(int i, ODBC32.SQL_C sqlctype, int cb, out int cbLengthOrIndicator) {
1507             IntPtr cbActual = IntPtr.Zero;  // Length or an indicator value
1508 
1509             if (IsCancelingCommand){
1510                 throw ADP.DataReaderNoData();
1511             }
1512             Debug.Assert (null != StatementHandle, "Statement handle is null in DateReader");
1513 
1514             // see notes on ODBC32.RetCode.NO_DATA case below.
1515             Debug.Assert(this.dataCache == null || !Convert.IsDBNull(this.dataCache[i]), "Cannot call GetData without checking for cache first!");
1516 
1517             // Never call GetData with anything larger than _buffer.Length-2.
1518             // We keep reallocating native buffers and it kills performance!!!
1519 
1520             Debug.Assert(cb <= Buffer.Length-2, "GetData needs to Reallocate. Perf bug");
1521 
1522             // SQLGetData
1523             CNativeBuffer buffer = Buffer;
1524             ODBC32.RetCode retcode = StatementHandle.GetData(
1525                (i+1),    // Column ordinals start at 1 in odbc
1526                sqlctype,
1527                buffer,
1528                cb,
1529                out cbActual);
1530 
1531             switch (retcode) {
1532                 case ODBC32.RetCode.SUCCESS:
1533                     break;
1534                 case ODBC32.RetCode.SUCCESS_WITH_INFO:
1535                     if ((Int32)cbActual == ODBC32.SQL_NO_TOTAL) {
1536                         break;
1537                     }
1538                     // devnote: don't we want to fire an event?
1539                     break;
1540 
1541                 case ODBC32.RetCode.NO_DATA:
1542                     // SQLBU 266054: System.Data.Odbc: Fails with truncated error when we pass BufferLength  as 0
1543                     // NO_DATA return value is success value - it means that the driver has fully consumed the current column value
1544                     // but did not move to the next column yet.
1545                     // For fixed-size values, we do not expect this to happen because we fully consume the data and store it in cache after the first call.
1546                     // For variable-length values (any character or binary data), SQLGetData can be called several times on the same column,
1547                     // to query for the next chunk of value, even after reaching its end!
1548                     // Thus, ignore NO_DATA for variable length data, but raise exception for fixed-size types
1549                     if (sqlctype != ODBC32.SQL_C.WCHAR && sqlctype != ODBC32.SQL_C.BINARY) {
1550                         Connection.HandleError(StatementHandle, retcode);
1551                     }
1552 
1553                     if (cbActual == (IntPtr)ODBC32.SQL_NO_TOTAL) {
1554                         // ensure SQL_NO_TOTAL value gets replaced with zero if the driver has fully consumed the current column
1555                         cbActual = (IntPtr)0;
1556                     }
1557                     break;
1558 
1559                 default:
1560                     Connection.HandleError(StatementHandle, retcode);
1561                     break;
1562             }
1563 
1564             // reset the current row and column
1565             SetCurrentRowColumnInfo(_row, i);
1566 
1567             // test for SQL_NULL_DATA
1568             if (cbActual == (IntPtr)ODBC32.SQL_NULL_DATA) {
1569                 // Store the DBNull value in cache. Note that if we need to do it, because second call into the SQLGetData returns NO_DATA, which means
1570                 // we already consumed the value (see above) and the NULL information is lost. By storing the null in cache, we avoid second call into the driver
1571                 // for the same row/column.
1572                 this.dataCache[i] = DBNull.Value;
1573                 // the indicator is never -1 (and it should not actually be used if the data is DBNull)
1574                 cbLengthOrIndicator = 0;
1575                 return false;
1576             }
1577             else {
1578                 //Return the actual size (for chunking scenarios)
1579                 // note the return value can be SQL_NO_TOTAL (-4)
1580                 cbLengthOrIndicator = (int)cbActual;
1581                 return true;
1582             }
1583         }
1584 
Read()1585         override public bool Read() {
1586             if (IsClosed) {
1587                 throw ADP.DataReaderClosed("Read");
1588             }
1589 
1590             if (IsCancelingCommand) {
1591                 _isRead = false;
1592                 return false;
1593             }
1594 
1595             // HasRows needs to call into Read so we don't want to read on the actual Read call
1596             if (_skipReadOnce){
1597                 _skipReadOnce = false;
1598                 return _isRead;
1599             }
1600 
1601             if (_noMoreRows || _noMoreResults || IsCommandBehavior(CommandBehavior.SchemaOnly))
1602                 return false;
1603 
1604             if (!_isValidResult) {
1605                 return false;
1606             }
1607 
1608             ODBC32.RetCode  retcode;
1609 
1610             //SQLFetch is only valid to call for row returning queries
1611             //We get: [24000]Invalid cursor state.  So we could either check the count
1612             //ahead of time (which is cached), or check on error and compare error states.
1613             //Note: SQLFetch is also invalid to be called on a prepared (schemaonly) statement
1614             //SqlFetch
1615             retcode = StatementHandle.Fetch();
1616 
1617             switch(retcode) {
1618             case ODBC32.RetCode.SUCCESS_WITH_INFO:
1619                 Connection.HandleErrorNoThrow(StatementHandle, retcode);
1620                 _hasRows = HasRowsStatus.HasRows;
1621                 _isRead = true;
1622                 break;
1623             case ODBC32.RetCode.SUCCESS:
1624                 _hasRows = HasRowsStatus.HasRows;
1625                 _isRead = true;
1626                 break;
1627             case ODBC32.RetCode.NO_DATA:
1628                 _isRead = false;
1629                 if (_hasRows == HasRowsStatus.DontKnow) {
1630                     _hasRows = HasRowsStatus.HasNoRows;
1631                 }
1632                 break;
1633             default:
1634                 Connection.HandleError(StatementHandle, retcode);
1635                 break;
1636             }
1637             //Null out previous cached row values.
1638             this.dataCache.FlushValues();
1639 
1640             // if CommandBehavior == SingleRow we set _noMoreResults to true so that following reads will fail
1641             if (IsCommandBehavior(CommandBehavior.SingleRow)) {
1642                 _noMoreRows = true;
1643                 // no more rows, set to -1
1644                 SetCurrentRowColumnInfo(-1, 0);
1645             }
1646             else {
1647                 // move to the next row
1648                 SetCurrentRowColumnInfo(_row + 1, 0);
1649             }
1650             return _isRead;
1651         }
1652 
1653         // Called by odbccommand when executed for the first time
FirstResult()1654         internal void FirstResult() {
1655             Int16 cCols;
1656             SQLLEN cRowsAffected;
1657 
1658             cRowsAffected = GetRowCount();              // get rowcount of the current resultset (if any)
1659             CalculateRecordsAffected(cRowsAffected);    // update recordsaffected
1660 
1661             ODBC32.RetCode retcode = FieldCountNoThrow(out cCols);
1662             if ((retcode == ODBC32.RetCode.SUCCESS) && (cCols == 0)) {
1663                 NextResult();
1664             }
1665             else {
1666                 this._isValidResult = true;
1667             }
1668         }
1669 
NextResult()1670         override public bool NextResult() {
1671             return NextResult(false, false);
1672         }
1673 
NextResult(bool disposing, bool allresults)1674         private bool NextResult(bool disposing, bool allresults) {
1675             // if disposing, loop through all the remaining results and ignore error messages
1676             // if allresults, loop through all results and collect all error messages for a single exception
1677             // callers are via Close(false, true), Dispose(true, false), NextResult(false,false)
1678             Debug.Assert(!disposing || !allresults, "both disposing & allresults are true");
1679             const int MaxConsecutiveFailure = 2000; // see WebData 72126 for why more than 1000
1680 
1681             SQLLEN cRowsAffected;
1682             Int16 cColsAffected;
1683             ODBC32.RetCode retcode, firstRetCode = ODBC32.RetCode.SUCCESS;
1684             bool hasMoreResults;
1685             bool hasColumns = false;
1686             bool singleResult = IsCommandBehavior(CommandBehavior.SingleResult);
1687 
1688             if (IsClosed) {
1689                 throw ADP.DataReaderClosed("NextResult");
1690             }
1691             _fieldNameLookup = null;
1692 
1693             if (IsCancelingCommand || _noMoreResults) {
1694                 return false;
1695             }
1696 
1697             //Blow away the previous cache (since the next result set will have a different shape,
1698             //different schema data, and different data.
1699             _isRead = false;
1700             _hasRows = HasRowsStatus.DontKnow;
1701             _fieldNameLookup = null;
1702             this.metadata = null;
1703             this.schemaTable = null;
1704 
1705             int loop = 0; // infinite loop protection, max out after 2000 consecutive failed results
1706             OdbcErrorCollection errors = null; // SQLBU 342112
1707             do {
1708                 _isValidResult = false;
1709                 retcode = StatementHandle.MoreResults();
1710                 hasMoreResults = ((retcode == ODBC32.RetCode.SUCCESS)
1711                                 ||(retcode == ODBC32.RetCode.SUCCESS_WITH_INFO));
1712 
1713                 if (retcode == ODBC32.RetCode.SUCCESS_WITH_INFO) {
1714                     Connection.HandleErrorNoThrow(StatementHandle, retcode);
1715                 }
1716                 else if (!disposing && (retcode != ODBC32.RetCode.NO_DATA) && (ODBC32.RetCode.SUCCESS != retcode)) {
1717                     // allow for building comulative error messages.
1718                     if (null == errors) {
1719                         firstRetCode = retcode;
1720                         errors = new OdbcErrorCollection();
1721                     }
1722                     ODBC32.GetDiagErrors(errors, null, StatementHandle, retcode);
1723                     ++loop;
1724                 }
1725 
1726                 if (!disposing && hasMoreResults) {
1727                     loop = 0;
1728                     cRowsAffected = GetRowCount();              // get rowcount of the current resultset (if any)
1729                     CalculateRecordsAffected(cRowsAffected);    // update recordsaffected
1730                     if (!singleResult) {
1731                         // update row- and columncount
1732                         FieldCountNoThrow(out cColsAffected);
1733                         hasColumns = (0 != cColsAffected);
1734                         _isValidResult = hasColumns;
1735                     }
1736                 }
1737             } while ((!singleResult && hasMoreResults && !hasColumns)  // repeat for results with no columns
1738                      || ((ODBC32.RetCode.NO_DATA != retcode) && allresults && (loop < MaxConsecutiveFailure)) // or process all results until done
1739                      || (singleResult && hasMoreResults));           // or for any result in singelResult mode
1740             if (MaxConsecutiveFailure <= loop) {
1741                 Bid.Trace("<odbc.OdbcDataReader.NextResult|INFO> 2000 consecutive failed results");
1742             }
1743 
1744             if(retcode == ODBC32.RetCode.NO_DATA) {
1745                 this.dataCache = null;
1746                 _noMoreResults = true;
1747             }
1748             if (null != errors) {
1749                 Debug.Assert(!disposing, "errors while disposing");
1750                 errors.SetSource(Connection.Driver);
1751                 OdbcException exception = OdbcException.CreateException(errors, firstRetCode);
1752                 Connection.ConnectionIsAlive(exception);
1753                 throw exception;
1754             }
1755             return (hasMoreResults);
1756         }
1757 
BuildMetaDataInfo()1758         private void BuildMetaDataInfo() {
1759             int count = FieldCount;
1760             MetaData[] metaInfos = new MetaData[count];
1761             List<string>  qrytables;
1762             bool needkeyinfo = IsCommandBehavior(CommandBehavior.KeyInfo);
1763             bool isKeyColumn;
1764             bool isHidden;
1765             ODBC32.SQL_NULLABILITY nullable;
1766 
1767             if (needkeyinfo)
1768                 qrytables = new List<string>();
1769             else
1770                 qrytables = null;
1771 
1772             // Find out all the metadata info, not all of this info will be available in all cases
1773             //
1774             for(int i=0; i<count; i++)
1775             {
1776                 metaInfos[i] = new MetaData();
1777                 metaInfos[i].ordinal = i;
1778                 TypeMap typeMap;
1779 
1780                 // for precision and scale we take the SQL_COLUMN_ attributes.
1781                 // Those attributes are supported by all provider versions.
1782                 // for size we use the octet length. We can't use column length because there is an incompatibility with the jet driver.
1783                 // furthermore size needs to be special cased for wchar types
1784                 //
1785                 typeMap = TypeMap.FromSqlType((ODBC32.SQL_TYPE)unchecked((int) GetColAttribute(i, ODBC32.SQL_DESC.CONCISE_TYPE, ODBC32.SQL_COLUMN.TYPE, ODBC32.HANDLER.THROW)));
1786                 if (typeMap._signType == true) {
1787                     bool sign = (GetColAttribute(i, ODBC32.SQL_DESC.UNSIGNED, ODBC32.SQL_COLUMN.UNSIGNED, ODBC32.HANDLER.THROW).ToInt64() != 0);
1788                     // sign = true if the column is unsigned
1789                     typeMap = TypeMap.UpgradeSignedType(typeMap, sign);
1790                 }
1791 
1792                 metaInfos[i].typemap = typeMap;
1793                 metaInfos[i].size = GetColAttribute(i, ODBC32.SQL_DESC.OCTET_LENGTH, ODBC32.SQL_COLUMN.LENGTH, ODBC32.HANDLER.IGNORE);
1794 
1795                 // special case the 'n' types
1796                 //
1797                 switch(metaInfos[i].typemap._sql_type) {
1798                     case ODBC32.SQL_TYPE.WCHAR:
1799                     case ODBC32.SQL_TYPE.WLONGVARCHAR:
1800                     case ODBC32.SQL_TYPE.WVARCHAR:
1801                         metaInfos[i].size /= 2;
1802                         break;
1803                 }
1804 
1805                 metaInfos[i].precision = (byte) GetColAttribute(i, (ODBC32.SQL_DESC)ODBC32.SQL_COLUMN.PRECISION, ODBC32.SQL_COLUMN.PRECISION, ODBC32.HANDLER.IGNORE);
1806                 metaInfos[i].scale = (byte) GetColAttribute(i, (ODBC32.SQL_DESC)ODBC32.SQL_COLUMN.SCALE, ODBC32.SQL_COLUMN.SCALE, ODBC32.HANDLER.IGNORE);
1807 
1808                 metaInfos[i].isAutoIncrement = GetColAttribute(i, ODBC32.SQL_DESC.AUTO_UNIQUE_VALUE, ODBC32.SQL_COLUMN.AUTO_INCREMENT, ODBC32.HANDLER.IGNORE) == 1;
1809                 metaInfos[i].isReadOnly = (GetColAttribute(i, ODBC32.SQL_DESC.UPDATABLE, ODBC32.SQL_COLUMN.UPDATABLE, ODBC32.HANDLER.IGNORE) == (Int32)ODBC32.SQL_UPDATABLE.READONLY);
1810 
1811                 nullable = (ODBC32.SQL_NULLABILITY) (int) GetColAttribute(i, ODBC32.SQL_DESC.NULLABLE, ODBC32.SQL_COLUMN.NULLABLE, ODBC32.HANDLER.IGNORE);
1812                 metaInfos[i].isNullable = (nullable == ODBC32.SQL_NULLABILITY.NULLABLE);
1813 
1814                 switch(metaInfos[i].typemap._sql_type) {
1815                     case ODBC32.SQL_TYPE.LONGVARCHAR:
1816                     case ODBC32.SQL_TYPE.WLONGVARCHAR:
1817                     case ODBC32.SQL_TYPE.LONGVARBINARY:
1818                         metaInfos[i].isLong = true;
1819                         break;
1820                     default:
1821                         metaInfos[i].isLong = false;
1822                         break;
1823                 }
1824 
1825                 if(IsCommandBehavior(CommandBehavior.KeyInfo))
1826                 {
1827                     // Note: Following two attributes are SQL Server specific (hence _SS in the name)
1828 
1829                     // SSS_WARNINGS_OFF
1830                     if (!Connection.ProviderInfo.NoSqlCASSColumnKey) {
1831                         isKeyColumn = GetColAttribute(i, (ODBC32.SQL_DESC)ODBC32.SQL_CA_SS.COLUMN_KEY, (ODBC32.SQL_COLUMN)(-1), ODBC32.HANDLER.IGNORE) == 1;
1832                         if (isKeyColumn) {
1833                             metaInfos[i].isKeyColumn = isKeyColumn;
1834                             metaInfos[i].isUnique = true;
1835                             needkeyinfo = false;
1836                         }
1837                     }
1838                     // SSS_WARNINGS_ON
1839 
1840                     metaInfos[i].baseSchemaName = GetColAttributeStr(i, ODBC32.SQL_DESC.SCHEMA_NAME, ODBC32.SQL_COLUMN.OWNER_NAME, ODBC32.HANDLER.IGNORE);
1841                     metaInfos[i].baseCatalogName = GetColAttributeStr(i, ODBC32.SQL_DESC.CATALOG_NAME, (ODBC32.SQL_COLUMN)(-1), ODBC32.HANDLER.IGNORE);
1842                     metaInfos[i].baseTableName = GetColAttributeStr(i, ODBC32.SQL_DESC.BASE_TABLE_NAME, ODBC32.SQL_COLUMN.TABLE_NAME, ODBC32.HANDLER.IGNORE);
1843                     metaInfos[i].baseColumnName = GetColAttributeStr(i, ODBC32.SQL_DESC.BASE_COLUMN_NAME, ODBC32.SQL_COLUMN.NAME, ODBC32.HANDLER.IGNORE);
1844 
1845                     if (Connection.IsV3Driver) {
1846                         if ((metaInfos[i].baseTableName == null) ||(metaInfos[i].baseTableName.Length == 0))  {
1847                             // Driver didn't return the necessary information from GetColAttributeStr.
1848                             // Try GetDescField()
1849                             metaInfos[i].baseTableName = GetDescFieldStr(i, ODBC32.SQL_DESC.BASE_TABLE_NAME, ODBC32.HANDLER.IGNORE);
1850                         }
1851                         if ((metaInfos[i].baseColumnName == null) ||(metaInfos[i].baseColumnName.Length == 0))  {
1852                             // Driver didn't return the necessary information from GetColAttributeStr.
1853                             // Try GetDescField()
1854                             metaInfos[i].baseColumnName = GetDescFieldStr(i, ODBC32.SQL_DESC.BASE_COLUMN_NAME, ODBC32.HANDLER.IGNORE);
1855                         }
1856                     }
1857                     if ((metaInfos[i].baseTableName != null)  && !(qrytables.Contains(metaInfos[i].baseTableName))) {
1858                         qrytables.Add(metaInfos[i].baseTableName);
1859                     }
1860                 }
1861 
1862                 // If primary key or autoincrement, then must also be unique
1863                 if (metaInfos[i].isKeyColumn || metaInfos[i].isAutoIncrement ) {
1864                     if (nullable == ODBC32.SQL_NULLABILITY.UNKNOWN)
1865                         metaInfos[i].isNullable = false;    // We can safely assume these are not nullable
1866                 }
1867             }
1868 
1869             // now loop over the hidden columns (if any)
1870 
1871             // SSS_WARNINGS_OFF
1872             if (!Connection.ProviderInfo.NoSqlCASSColumnKey) {
1873                 for (int i=count; i<count+_hiddenColumns; i++) {
1874                     isKeyColumn = GetColAttribute(i, (ODBC32.SQL_DESC)ODBC32.SQL_CA_SS.COLUMN_KEY, (ODBC32.SQL_COLUMN)(-1), ODBC32.HANDLER.IGNORE) == 1;
1875                     if (isKeyColumn) {
1876                         isHidden = GetColAttribute(i, (ODBC32.SQL_DESC)ODBC32.SQL_CA_SS.COLUMN_HIDDEN, (ODBC32.SQL_COLUMN)(-1), ODBC32.HANDLER.IGNORE) == 1;
1877                         if (isHidden) {
1878                             for (int j=0; j<count; j++) {
1879                                 metaInfos[j].isKeyColumn = false;   // downgrade keycolumn
1880                                 metaInfos[j].isUnique = false;      // downgrade uniquecolumn
1881                             }
1882                         }
1883                     }
1884                 }
1885             }
1886             // SSS_WARNINGS_ON
1887 
1888             // Blow away the previous metadata
1889             this.metadata = metaInfos;
1890 
1891             // If key info is requested, then we have to make a few more calls to get the
1892             //  special columns. This may not succeed for all drivers, so ignore errors and
1893             // fill in as much as possible.
1894             if (IsCommandBehavior(CommandBehavior.KeyInfo)) {
1895                 if((qrytables != null) && (qrytables.Count > 0) ) {
1896                     List<string>.Enumerator tablesEnum = qrytables.GetEnumerator();
1897                     QualifiedTableName qualifiedTableName = new QualifiedTableName(Connection.QuoteChar(ADP.GetSchemaTable));
1898                     while(tablesEnum.MoveNext()) {
1899                         // Find the primary keys, identity and autincrement columns
1900                         qualifiedTableName.Table = tablesEnum.Current;
1901                         if (RetrieveKeyInfo(needkeyinfo, qualifiedTableName, false) <= 0) {
1902                             RetrieveKeyInfo(needkeyinfo, qualifiedTableName, true);
1903                         }
1904                     }
1905                 }
1906                 else {
1907                     // Some drivers ( < 3.x ?) do not provide base table information. In this case try to
1908                     // find it by parsing the statement
1909 
1910                     QualifiedTableName qualifiedTableName = new QualifiedTableName(Connection.QuoteChar(ADP.GetSchemaTable), GetTableNameFromCommandText());
1911                     if (!ADP.IsEmpty(qualifiedTableName.Table)) { // fxcop
1912                        SetBaseTableNames(qualifiedTableName);
1913                         if (RetrieveKeyInfo(needkeyinfo, qualifiedTableName, false) <= 0) {
1914                             RetrieveKeyInfo(needkeyinfo, qualifiedTableName, true);
1915                         }
1916                     }
1917                 }
1918             }
1919         }
1920 
NewSchemaTable()1921         private DataTable NewSchemaTable() {
1922             DataTable schematable = new DataTable("SchemaTable");
1923             schematable.Locale = CultureInfo.InvariantCulture;
1924             schematable.MinimumCapacity = this.FieldCount;
1925 
1926             //Schema Columns
1927             DataColumnCollection columns    = schematable.Columns;
1928             columns.Add(new DataColumn("ColumnName",        typeof(System.String)));
1929             columns.Add(new DataColumn("ColumnOrdinal",     typeof(System.Int32))); // UInt32
1930             columns.Add(new DataColumn("ColumnSize",        typeof(System.Int32))); // UInt32
1931             columns.Add(new DataColumn("NumericPrecision",  typeof(System.Int16))); // UInt16
1932             columns.Add(new DataColumn("NumericScale",      typeof(System.Int16)));
1933             columns.Add(new DataColumn("DataType",          typeof(System.Object)));
1934             columns.Add(new DataColumn("ProviderType",        typeof(System.Int32)));
1935             columns.Add(new DataColumn("IsLong",            typeof(System.Boolean)));
1936             columns.Add(new DataColumn("AllowDBNull",       typeof(System.Boolean)));
1937             columns.Add(new DataColumn("IsReadOnly",        typeof(System.Boolean)));
1938             columns.Add(new DataColumn("IsRowVersion",      typeof(System.Boolean)));
1939             columns.Add(new DataColumn("IsUnique",          typeof(System.Boolean)));
1940             columns.Add(new DataColumn("IsKey",       typeof(System.Boolean)));
1941             columns.Add(new DataColumn("IsAutoIncrement",   typeof(System.Boolean)));
1942             columns.Add(new DataColumn("BaseSchemaName",    typeof(System.String)));
1943             columns.Add(new DataColumn("BaseCatalogName",   typeof(System.String)));
1944             columns.Add(new DataColumn("BaseTableName",     typeof(System.String)));
1945             columns.Add(new DataColumn("BaseColumnName",    typeof(System.String)));
1946 
1947             // MDAC Bug 79231
1948             foreach (DataColumn column in columns) {
1949                 column.ReadOnly = true;
1950             }
1951             return schematable;
1952         }
1953 
1954         // The default values are already defined in DbSchemaRows (see DbSchemaRows.cs) so there is no need to set any default value
1955         //
1956 
GetSchemaTable()1957         override public DataTable GetSchemaTable() {
1958             if (IsClosed) { // MDAC 68331
1959                 throw ADP.DataReaderClosed("GetSchemaTable");           // can't use closed connection
1960             }
1961             if (_noMoreResults) {
1962                 return null;                                            // no more results
1963             }
1964             if (null != this.schemaTable) {
1965                 return this.schemaTable;                                // return cached schematable
1966             }
1967 
1968             //Delegate, to have the base class setup the structure
1969             DataTable schematable = NewSchemaTable();
1970 
1971             if (FieldCount == 0) {
1972                 return schematable;
1973             }
1974             if (this.metadata == null) {
1975                 BuildMetaDataInfo();
1976             }
1977 
1978             DataColumn columnName = schematable.Columns["ColumnName"];
1979             DataColumn columnOrdinal = schematable.Columns["ColumnOrdinal"];
1980             DataColumn columnSize = schematable.Columns["ColumnSize"];
1981             DataColumn numericPrecision = schematable.Columns["NumericPrecision"];
1982             DataColumn numericScale = schematable.Columns["NumericScale"];
1983             DataColumn dataType = schematable.Columns["DataType"];
1984             DataColumn providerType = schematable.Columns["ProviderType"];
1985             DataColumn isLong = schematable.Columns["IsLong"];
1986             DataColumn allowDBNull = schematable.Columns["AllowDBNull"];
1987             DataColumn isReadOnly = schematable.Columns["IsReadOnly"];
1988             DataColumn isRowVersion = schematable.Columns["IsRowVersion"];
1989             DataColumn isUnique = schematable.Columns["IsUnique"];
1990             DataColumn isKey = schematable.Columns["IsKey"];
1991             DataColumn isAutoIncrement = schematable.Columns["IsAutoIncrement"];
1992             DataColumn baseSchemaName = schematable.Columns["BaseSchemaName"];
1993             DataColumn baseCatalogName = schematable.Columns["BaseCatalogName"];
1994             DataColumn baseTableName = schematable.Columns["BaseTableName"];
1995             DataColumn baseColumnName = schematable.Columns["BaseColumnName"];
1996 
1997 
1998             //Populate the rows (1 row for each column)
1999             int count = FieldCount;
2000             for(int i=0; i<count; i++) {
2001                 DataRow row = schematable.NewRow();
2002 
2003                 row[columnName] = GetName(i);        //ColumnName
2004                 row[columnOrdinal] = i;                 //ColumnOrdinal
2005                 row[columnSize] = unchecked((int)Math.Min(Math.Max(Int32.MinValue, metadata[i].size.ToInt64()), Int32.MaxValue));
2006                 row[numericPrecision] = (Int16) metadata[i].precision;
2007                 row[numericScale] = (Int16) metadata[i].scale;
2008                 row[dataType] = metadata[i].typemap._type;          //DataType
2009                 row[providerType] = metadata[i].typemap._odbcType;          // ProviderType
2010                 row[isLong] = metadata[i].isLong;           // IsLong
2011                 row[allowDBNull] = metadata[i].isNullable;       //AllowDBNull
2012                 row[isReadOnly] = metadata[i].isReadOnly;      // IsReadOnly
2013                 row[isRowVersion] = metadata[i].isRowVersion;    //IsRowVersion
2014                 row[isUnique] = metadata[i].isUnique;        //IsUnique
2015                 row[isKey] =  metadata[i].isKeyColumn;    // IsKey
2016                 row[isAutoIncrement] = metadata[i].isAutoIncrement; //IsAutoIncrement
2017 
2018                 //BaseSchemaName
2019                 row[baseSchemaName] =  metadata[i].baseSchemaName;
2020                 //BaseCatalogName
2021                 row[baseCatalogName] = metadata[i].baseCatalogName;
2022                 //BaseTableName
2023                 row[baseTableName] = metadata[i].baseTableName ;
2024                 //BaseColumnName
2025                 row[baseColumnName] =  metadata[i].baseColumnName;
2026 
2027                 schematable.Rows.Add(row);
2028                 row.AcceptChanges();
2029             }
2030             this.schemaTable = schematable;
2031             return schematable;
2032         }
2033 
RetrieveKeyInfo(bool needkeyinfo, QualifiedTableName qualifiedTableName, bool quoted)2034         internal int RetrieveKeyInfo(bool needkeyinfo, QualifiedTableName qualifiedTableName, bool quoted) {
2035             ODBC32.RetCode retcode;
2036             string      columnname;
2037             int         ordinal;
2038             int         keyColumns = 0;
2039             IntPtr cbActual = IntPtr.Zero;
2040 
2041             if (IsClosed || (_cmdWrapper == null)) {
2042                 return 0;     // Can't do anything without a second handle
2043             }
2044             _cmdWrapper.CreateKeyInfoStatementHandle();
2045 
2046             CNativeBuffer   buffer = Buffer;
2047             bool            mustRelease = false;
2048             Debug.Assert(buffer.Length >= 264, "Native buffer to small (_buffer.Length < 264)");
2049 
2050             RuntimeHelpers.PrepareConstrainedRegions();
2051             try {
2052                 buffer.DangerousAddRef(ref mustRelease);
2053 
2054                 if (needkeyinfo) {
2055                     if (!Connection.ProviderInfo.NoSqlPrimaryKeys) {
2056                         // Get the primary keys
2057                         retcode = KeyInfoStatementHandle.PrimaryKeys(
2058                                     qualifiedTableName.Catalog,
2059                                     qualifiedTableName.Schema,
2060                                     qualifiedTableName.GetTable(quoted));
2061 
2062                         if ((retcode == ODBC32.RetCode.SUCCESS) || (retcode == ODBC32.RetCode.SUCCESS_WITH_INFO)) {
2063                             bool noUniqueKey = false;
2064 
2065                             // We are only interested in column name
2066                             buffer.WriteInt16(0, 0);
2067                             retcode = KeyInfoStatementHandle.BindColumn2(
2068                                            (short)(ODBC32.SQL_PRIMARYKEYS.COLUMNNAME),    // Column Number
2069                                            ODBC32.SQL_C.WCHAR,
2070                                            buffer.PtrOffset(0, 256),
2071                                            (IntPtr)256,
2072                                            buffer.PtrOffset(256, IntPtr.Size).Handle);
2073                             while (ODBC32.RetCode.SUCCESS == (retcode = KeyInfoStatementHandle.Fetch())) {
2074                                 cbActual = buffer.ReadIntPtr(256);
2075                                 columnname = buffer.PtrToStringUni(0, (int)cbActual/2/*cch*/);
2076                                 ordinal = this.GetOrdinalFromBaseColName(columnname);
2077                                 if (ordinal != -1) {
2078                                     keyColumns ++;
2079                                     this.metadata[ordinal].isKeyColumn = true;
2080                                     this.metadata[ordinal].isUnique = true;
2081                                     this.metadata[ordinal].isNullable = false;
2082                                     this.metadata[ordinal].baseTableName = qualifiedTableName.Table;
2083 
2084                                     if (this.metadata[ordinal].baseColumnName == null) {
2085                                         this.metadata[ordinal].baseColumnName = columnname;
2086                                     }
2087                                 }
2088                                 else {
2089                                     noUniqueKey = true;
2090                                     break;  // no need to go over the remaining columns anymore
2091                                 }
2092                             }
2093 //
2094 
2095 
2096 
2097 
2098 // if we got keyinfo from the column we dont even get to here!
2099 //
2100                             // reset isUnique flag if the key(s) are not unique
2101                             //
2102                             if (noUniqueKey) {
2103                                 foreach (MetaData metadata in this.metadata) {
2104                                     metadata.isKeyColumn = false;
2105                                 }
2106                             }
2107 
2108                             // Unbind the column
2109                             retcode = KeyInfoStatementHandle.BindColumn3(
2110                                 (short)(ODBC32.SQL_PRIMARYKEYS.COLUMNNAME),      // SQLUSMALLINT ColumnNumber
2111                                 ODBC32.SQL_C.WCHAR,                     // SQLSMALLINT  TargetType
2112                                 buffer.DangerousGetHandle());                                   // SQLLEN *     StrLen_or_Ind
2113                         }
2114                         else {
2115                             if ("IM001" == Command.GetDiagSqlState()) {
2116                                 Connection.ProviderInfo.NoSqlPrimaryKeys = true;
2117                             }
2118                         }
2119                     }
2120 
2121                     if (keyColumns == 0) {
2122                         // SQLPrimaryKeys did not work. Have to use the slower SQLStatistics to obtain key information
2123                         KeyInfoStatementHandle.MoreResults();
2124                         keyColumns += RetrieveKeyInfoFromStatistics(qualifiedTableName, quoted);
2125                     }
2126                     KeyInfoStatementHandle.MoreResults();
2127                 }
2128 
2129                 // Get the special columns for version
2130                 retcode = KeyInfoStatementHandle.SpecialColumns(qualifiedTableName.GetTable(quoted));
2131 
2132                 if ((retcode == ODBC32.RetCode.SUCCESS) || (retcode == ODBC32.RetCode.SUCCESS_WITH_INFO)) {
2133                     // We are only interested in column name
2134                     cbActual = IntPtr.Zero;
2135                     buffer.WriteInt16(0, 0);
2136                     retcode = KeyInfoStatementHandle.BindColumn2(
2137                                    (short)(ODBC32.SQL_SPECIALCOLUMNSET.COLUMN_NAME),
2138                                    ODBC32.SQL_C.WCHAR,
2139                                    buffer.PtrOffset(0, 256),
2140                                    (IntPtr)256,
2141                                    buffer.PtrOffset(256, IntPtr.Size).Handle);
2142 
2143                     while (ODBC32.RetCode.SUCCESS == (retcode = KeyInfoStatementHandle.Fetch())) {
2144                         cbActual = buffer.ReadIntPtr(256);
2145                         columnname = buffer.PtrToStringUni(0, (int)cbActual/2/*cch*/);
2146                         ordinal = this.GetOrdinalFromBaseColName(columnname);
2147                         if (ordinal != -1) {
2148                             this.metadata[ordinal].isRowVersion = true;
2149                             if (this.metadata[ordinal].baseColumnName == null) {
2150                                 this.metadata[ordinal].baseColumnName = columnname;
2151                             }
2152                         }
2153                     }
2154                     // Unbind the column
2155                     retcode = KeyInfoStatementHandle.BindColumn3(
2156                                    (short)(ODBC32.SQL_SPECIALCOLUMNSET.COLUMN_NAME),
2157                                    ODBC32.SQL_C.WCHAR,
2158                                    buffer.DangerousGetHandle());
2159 
2160                     retcode = KeyInfoStatementHandle.MoreResults();
2161                 }
2162                 else {
2163                 //  i've seen "DIAG [HY000] [Microsoft][ODBC SQL Server Driver]Connection is busy with results for another hstmt (0) "
2164                 //  how did we get here? SqlServer does not allow a second handle (Keyinfostmt) anyway...
2165                 //
2166                 /*
2167                     string msg = "Unexpected failure of SQLSpecialColumns. Code = " + Command.GetDiagSqlState();
2168                     Debug.Assert (false, msg);
2169                 */
2170                 }
2171             }
2172             finally {
2173                 if (mustRelease) {
2174                     buffer.DangerousRelease();
2175                 }
2176             }
2177             return keyColumns;
2178         }
2179 
2180         // Uses SQLStatistics to retrieve key information for a table
RetrieveKeyInfoFromStatistics(QualifiedTableName qualifiedTableName, bool quoted)2181         private int RetrieveKeyInfoFromStatistics(QualifiedTableName qualifiedTableName, bool quoted) {
2182             ODBC32.RetCode retcode;
2183             String      columnname = String.Empty;
2184             String      indexname = String.Empty;
2185             String      currentindexname = String.Empty;
2186             int[]       indexcolumnordinals = new int[16];
2187             int[]        pkcolumnordinals = new int[16];
2188             int         npkcols = 0;
2189             int         ncols = 0;                  // No of cols in the index
2190             bool        partialcolumnset = false;
2191             int         ordinal;
2192             int         indexordinal;
2193             IntPtr cbIndexLen = IntPtr.Zero;
2194             IntPtr cbColnameLen = IntPtr.Zero;
2195             int         keyColumns = 0;
2196 
2197             // devnote: this test is already done by calling method ...
2198             // if (IsClosed) return;   // protect against dead connection
2199 
2200             // MDAC Bug 75928 - SQLStatisticsW damages the string passed in
2201             // To protect the tablename we need to pass in a copy of that string
2202             String tablename1 = String.Copy(qualifiedTableName.GetTable(quoted));
2203 
2204             // Select only unique indexes
2205             retcode = KeyInfoStatementHandle.Statistics(tablename1);
2206 
2207             if (retcode != ODBC32.RetCode.SUCCESS) {
2208                 // We give up at this point
2209                 return 0;
2210             }
2211 
2212             CNativeBuffer buffer = Buffer;
2213             bool          mustRelease = false;
2214             Debug.Assert(buffer.Length >= 544, "Native buffer to small (_buffer.Length < 544)");
2215 
2216             RuntimeHelpers.PrepareConstrainedRegions();
2217             try {
2218                 buffer.DangerousAddRef(ref mustRelease);
2219 
2220                 const int colnameBufOffset = 0;
2221                 const int indexBufOffset = 256;
2222                 const int ordinalBufOffset = 512;
2223                 const int colnameActualOffset = 520;
2224                 const int indexActualOffset = 528;
2225                 const int ordinalActualOffset = 536;
2226                 HandleRef colnamebuf = buffer.PtrOffset(colnameBufOffset, 256);
2227                 HandleRef indexbuf = buffer.PtrOffset(indexBufOffset, 256);
2228                 HandleRef ordinalbuf = buffer.PtrOffset(ordinalBufOffset, 4);
2229 
2230                 IntPtr colnameActual = buffer.PtrOffset(colnameActualOffset, IntPtr.Size).Handle;
2231                 IntPtr indexActual = buffer.PtrOffset(indexActualOffset, IntPtr.Size).Handle;
2232                 IntPtr ordinalActual = buffer.PtrOffset(ordinalActualOffset, IntPtr.Size).Handle;
2233 
2234                 //We are interested in index name, column name, and ordinal
2235                 buffer.WriteInt16(indexBufOffset, 0);
2236                 retcode = KeyInfoStatementHandle.BindColumn2(
2237                             (short)(ODBC32.SQL_STATISTICS.INDEXNAME),
2238                             ODBC32.SQL_C.WCHAR,
2239                             indexbuf,
2240                             (IntPtr)256,
2241                             indexActual);
2242                 retcode = KeyInfoStatementHandle.BindColumn2(
2243                             (short)(ODBC32.SQL_STATISTICS.ORDINAL_POSITION),
2244                             ODBC32.SQL_C.SSHORT,
2245                             ordinalbuf,
2246                             (IntPtr)4,
2247                             ordinalActual);
2248                 buffer.WriteInt16(ordinalBufOffset, 0);
2249                 retcode = KeyInfoStatementHandle.BindColumn2(
2250                             (short)(ODBC32.SQL_STATISTICS.COLUMN_NAME),
2251                             ODBC32.SQL_C.WCHAR,
2252                             colnamebuf,
2253                             (IntPtr)256,
2254                             colnameActual);
2255                 // Find the best unique index on the table, use the ones whose columns are
2256                 // completely covered by the query.
2257                 while (ODBC32.RetCode.SUCCESS == (retcode = KeyInfoStatementHandle.Fetch())) {
2258 
2259                     cbColnameLen = buffer.ReadIntPtr(colnameActualOffset);
2260                     cbIndexLen = buffer.ReadIntPtr(indexActualOffset);
2261 
2262                     // If indexname is not returned, skip this row
2263                     if (0 == buffer.ReadInt16(indexBufOffset))
2264                         continue;       // Not an index row, get next row.
2265 
2266                     columnname = buffer.PtrToStringUni(colnameBufOffset, (int)cbColnameLen/2/*cch*/);
2267                     indexname = buffer.PtrToStringUni(indexBufOffset, (int)cbIndexLen/2/*cch*/);
2268                     ordinal = (int) buffer.ReadInt16(ordinalBufOffset);
2269 
2270                     if (SameIndexColumn(currentindexname, indexname, ordinal, ncols)) {
2271                         // We are still working on the same index
2272                         if (partialcolumnset)
2273                             continue;       // We don't have all the keys for this index, so we can't use it
2274 
2275                         ordinal = this.GetOrdinalFromBaseColName(columnname, qualifiedTableName.Table);
2276                         if (ordinal == -1) {
2277                              partialcolumnset = true;
2278                         }
2279                         else {
2280                             // Add the column to the index column set
2281                             if (ncols < 16)
2282                                 indexcolumnordinals[ncols++] = ordinal;
2283                             else    // Can't deal with indexes > 16 columns
2284                                 partialcolumnset = true;
2285                         }
2286                     }
2287                     else {
2288                         // We got a new index, save the previous index information
2289                         if (!partialcolumnset && (ncols != 0)) {
2290                             // Choose the unique index with least columns as primary key
2291                             if ((npkcols == 0) || (npkcols > ncols)){
2292                                 npkcols = ncols;
2293                                 for (int i = 0 ; i < ncols ; i++)
2294                                     pkcolumnordinals[i] = indexcolumnordinals[i];
2295                             }
2296                         }
2297                         // Reset the parameters for a new index
2298                         ncols = 0;
2299                         currentindexname = indexname;
2300                         partialcolumnset = false;
2301                         // Add this column to index
2302                         ordinal = this.GetOrdinalFromBaseColName(columnname, qualifiedTableName.Table);
2303                         if (ordinal == -1) {
2304                              partialcolumnset = true;
2305                         }
2306                         else {
2307                             // Add the column to the index column set
2308                             indexcolumnordinals[ncols++] = ordinal;
2309                         }
2310                     }
2311                     // Go on to the next column
2312                 }
2313                 // Do we have an index?
2314                 if (!partialcolumnset && (ncols != 0)) {
2315                     // Choose the unique index with least columns as primary key
2316                     if ((npkcols == 0) || (npkcols > ncols)){
2317                         npkcols = ncols;
2318                         for (int i = 0 ; i < ncols ; i++)
2319                             pkcolumnordinals[i] = indexcolumnordinals[i];
2320                     }
2321                 }
2322                 // Mark the chosen index as primary key
2323                 if (npkcols != 0) {
2324                     for (int i = 0 ; i < npkcols ; i++) {
2325                         indexordinal = pkcolumnordinals[i];
2326                         keyColumns++;
2327                         this.metadata[indexordinal].isKeyColumn = true;
2328     // should we set isNullable = false?
2329     // This makes the QuikTest against Jet fail
2330     //
2331     // test test test - we don't know if this is nulalble or not so why do we want to set it to a value?
2332                         this.metadata[indexordinal].isNullable = false;
2333                         this.metadata[indexordinal].isUnique = true;
2334                         if (this.metadata[indexordinal].baseTableName == null) {
2335                             this.metadata[indexordinal].baseTableName = qualifiedTableName.Table;
2336                         }
2337                         if (this.metadata[indexordinal].baseColumnName == null) {
2338                             this.metadata[indexordinal].baseColumnName = columnname;
2339                         }
2340                     }
2341                 }
2342                 // Unbind the columns
2343                 _cmdWrapper.FreeKeyInfoStatementHandle(ODBC32.STMT.UNBIND);
2344             }
2345             finally {
2346                 if (mustRelease) {
2347                     buffer.DangerousRelease();
2348                 }
2349             }
2350             return keyColumns;
2351         }
2352 
SameIndexColumn(String currentindexname, String indexname, int ordinal, int ncols)2353         internal bool SameIndexColumn(String currentindexname, String indexname, int ordinal, int ncols)
2354         {
2355             if (ADP.IsEmpty(currentindexname)){
2356                 return false;
2357             }
2358             if ((currentindexname == indexname)  &&
2359                 (ordinal == ncols+1))
2360                     return true;
2361             return false;
2362         }
2363 
GetOrdinalFromBaseColName(String columnname)2364         internal int GetOrdinalFromBaseColName(String columnname) {
2365             return GetOrdinalFromBaseColName(columnname, null);
2366         }
2367 
GetOrdinalFromBaseColName(String columnname, String tablename)2368         internal int GetOrdinalFromBaseColName(String columnname, String tablename)
2369         {
2370             if (ADP.IsEmpty(columnname)) {
2371                 return -1;
2372             }
2373             if (this.metadata != null) {
2374                 int count = FieldCount;
2375                 for (int i = 0 ; i < count ; i++) {
2376                     if ( (this.metadata[i].baseColumnName != null) &&
2377                         (columnname == this.metadata[i].baseColumnName)) {
2378                         if (!ADP.IsEmpty(tablename)) {
2379                             if (tablename == this.metadata[i].baseTableName) {
2380                                 return i;
2381                             } // end if
2382                         } // end if
2383                         else {
2384                             return i;
2385                         } // end else
2386                     }
2387                 }
2388             }
2389             // We can't find it in base column names, try regular colnames
2390             return this.IndexOf(columnname);
2391         }
2392 
2393         // We try parsing the SQL statement to get the table name as a last resort when
2394         // the driver doesn't return this information back to us.
2395         //
2396         // we can't handle multiple tablenames (JOIN)
2397         // only the first tablename will be returned
2398 
GetTableNameFromCommandText()2399         internal string GetTableNameFromCommandText()
2400         {
2401             if (command == null){
2402                 return null;
2403             }
2404             String localcmdtext = _cmdText;
2405             if (ADP.IsEmpty(localcmdtext)) { // fxcop
2406                 return null;
2407             }
2408             String tablename;
2409             int     idx;
2410             CStringTokenizer tokenstmt = new CStringTokenizer(localcmdtext, Connection.QuoteChar(ADP.GetSchemaTable)[0], Connection.EscapeChar(ADP.GetSchemaTable));
2411 
2412             if (tokenstmt.StartsWith("select") == true) {
2413               // select command, search for from clause
2414               idx = tokenstmt.FindTokenIndex("from");
2415             }
2416             else {
2417                 if ((tokenstmt.StartsWith("insert") == true) ||
2418                     (tokenstmt.StartsWith("update") == true) ||
2419                     (tokenstmt.StartsWith("delete") == true) ) {
2420                     // Get the following word
2421                     idx = tokenstmt.CurrentPosition;
2422                 }
2423                 else
2424                     idx = -1;
2425             }
2426             if (idx == -1)
2427                 return null;
2428             // The next token is the table name
2429             tablename = tokenstmt.NextToken();
2430 
2431             localcmdtext = tokenstmt.NextToken();
2432             if ((localcmdtext.Length > 0) && (localcmdtext[0] == ',')) {
2433                 return null;        // can't handle multiple tables
2434             }
2435             if ((localcmdtext.Length == 2) &&
2436                 ((localcmdtext[0] == 'a') || (localcmdtext[0] == 'A')) &&
2437                 ((localcmdtext[1] == 's') || (localcmdtext[1] == 'S'))) {
2438                 // aliased table, skip the alias name
2439                 localcmdtext = tokenstmt.NextToken();
2440                 localcmdtext = tokenstmt.NextToken();
2441                 if ((localcmdtext.Length > 0) && (localcmdtext[0] == ',')) {
2442                     return null;        // Multiple tables
2443                 }
2444             }
2445             return tablename;
2446         }
2447 
SetBaseTableNames(QualifiedTableName qualifiedTableName)2448         internal void SetBaseTableNames(QualifiedTableName qualifiedTableName)
2449         {
2450             int count = FieldCount;
2451 
2452             for(int i=0; i<count; i++)
2453             {
2454                 if (metadata[i].baseTableName == null) {
2455                     metadata[i].baseTableName = qualifiedTableName.Table;
2456                     metadata[i].baseSchemaName = qualifiedTableName.Schema;
2457                     metadata[i].baseCatalogName = qualifiedTableName.Catalog;
2458                 }
2459             }
2460             return;
2461         }
2462 
2463         sealed internal class QualifiedTableName {
2464             private string _catalogName;
2465             private string _schemaName;
2466             private string _tableName;
2467             private string _quotedTableName;
2468             private string _quoteChar;
2469 
2470             internal string Catalog {
2471                 get {
2472                     return _catalogName;
2473                 }
2474             }
2475 
2476             internal string Schema {
2477                 get {
2478                     return _schemaName;
2479                 }
2480             }
2481 
2482             internal string Table {
2483                 get {
2484                     return _tableName;
2485                 }
2486                 set {
2487                     _quotedTableName = value;
2488                     _tableName = UnQuote(value);
2489                 }
2490             }
2491             internal string QuotedTable {
2492                 get {
2493                     return _quotedTableName;
2494                 }
2495             }
GetTable(bool flag)2496             internal string GetTable(bool flag) {
2497                 return (flag ? QuotedTable : Table);
2498             }
QualifiedTableName(string quoteChar)2499             internal QualifiedTableName (string quoteChar) {
2500                 _quoteChar = quoteChar;
2501             }
QualifiedTableName(string quoteChar, string qualifiedname)2502             internal QualifiedTableName (string quoteChar, string qualifiedname) {
2503                 _quoteChar = quoteChar;
2504 
2505                 string[] names = DbCommandBuilder.ParseProcedureName (qualifiedname, quoteChar, quoteChar);
2506                 _catalogName = UnQuote(names[1]);
2507                 _schemaName = UnQuote(names[2]);
2508                 _quotedTableName = names[3];
2509                 _tableName = UnQuote(names[3]);
2510             }
2511 
UnQuote(string str)2512             private string UnQuote (string str) {
2513                 if ((str != null) && (str.Length > 0)) {
2514                     char quoteChar = _quoteChar[0];
2515                     if (str[0] == quoteChar) {
2516                         Debug.Assert (str.Length > 1, "Illegal string, only one char that is a quote");
2517                         Debug.Assert (str[str.Length-1] == quoteChar, "Missing quote at end of string that begins with quote");
2518                         if (str.Length > 1 && str[str.Length-1] == quoteChar) {
2519                             str = str.Substring(1, str.Length-2);
2520                         }
2521                     }
2522                 }
2523                 return str;
2524             }
2525         }
2526 
2527         sealed private class MetaData {
2528 
2529             internal int ordinal;
2530             internal TypeMap typemap;
2531 
2532             internal SQLLEN size;
2533             internal byte precision;
2534             internal byte scale;
2535 
2536             internal bool isAutoIncrement;
2537             internal bool isUnique;
2538             internal bool isReadOnly;
2539             internal bool isNullable;
2540             internal bool isRowVersion;
2541             internal bool isLong;
2542 
2543             internal bool isKeyColumn;
2544             internal string baseSchemaName;
2545             internal string baseCatalogName;
2546             internal string baseTableName;
2547             internal string baseColumnName;
2548         }
2549     }
2550 }
2551