Add unit test for bug 660294.
[mono.git] / mcs / class / System.Data.OracleClient / System.Data.OracleClient / OracleDataReader.cs
1 //
2 // OracleDataReader.cs 
3 //
4 // Part of the Mono class libraries at
5 // mcs/class/System.Data.OracleClient/System.Data.OracleClient
6 //
7 // Assembly: System.Data.OracleClient.dll
8 // Namespace: System.Data.OracleClient
9 //
10 // Authors: Tim Coleman <tim@timcoleman.com>
11 //          Daniel Morgan <danmorg@sc.rr.com>
12 //
13 // Copyright (C) Tim Coleman, 2003
14 // Copyright (C) Daniel Morgan, 2003, 2005
15 //
16 // Licensed under the MIT/X11 License.
17 //
18
19 using System;
20 using System.Collections;
21 using System.Collections.Specialized;
22 using System.ComponentModel;
23 using System.Data;
24 using System.Data.Common;
25 using System.Data.OracleClient.Oci;
26 using System.Globalization;
27 using System.Runtime.InteropServices;
28 using System.Text;
29
30 namespace System.Data.OracleClient
31 {
32         public sealed class OracleDataReader :
33 #if NET_2_0
34                 DbDataReader
35 #else
36                 MarshalByRefObject, IDataReader, IDisposable, IDataRecord, IEnumerable
37 #endif
38         {
39                 #region Fields
40
41                 OracleCommand command;
42                 ArrayList dataTypeNames;
43 #if !NET_2_0
44                 bool disposed;
45 #endif
46                 bool isClosed;
47                 bool hasRows;
48                 DataTable schemaTable;
49                 CommandBehavior behavior;
50
51                 int recordsAffected = -1;
52                 OciStatementType statementType;
53                 OciStatementHandle statement;
54
55                 #endregion // Fields
56
57                 #region Constructors
58
59                 internal OracleDataReader (OracleCommand command, OciStatementHandle statement, bool extHasRows, CommandBehavior behavior)
60                 {
61                         this.command = command;
62                         this.hasRows = extHasRows;
63                         this.schemaTable = ConstructSchemaTable ();
64                         this.statement = statement;
65                         this.statementType = statement.GetStatementType ();
66                         this.behavior = behavior;
67                 }
68
69                 ~OracleDataReader ()
70                 {
71                         Dispose (false);
72                 }
73
74                 #endregion // Constructors
75
76                 #region Properties
77
78                 public
79 #if NET_2_0
80                 override
81 #endif
82                 int Depth {
83                         get { return 0; }
84                 }
85
86                 public
87 #if NET_2_0
88                 override
89 #endif
90                 int FieldCount {
91                         get { return statement.ColumnCount; }
92                 }
93
94                 public
95 #if NET_2_0
96                 override
97 #endif
98                 bool HasRows {
99                         get { return hasRows; }
100                 }
101
102                 public
103 #if NET_2_0
104                 override
105 #endif
106                 bool IsClosed {
107                         get { return isClosed; }
108                 }
109
110                 public
111 #if NET_2_0
112                 override
113 #endif
114                 object this [string name] {
115                         get { return GetValue (GetOrdinal (name)); }
116                 }
117
118                 public
119 #if NET_2_0
120                 override
121 #endif
122                 object this [int i] {
123                         get { return GetValue (i); }
124                 }
125
126                 public
127 #if NET_2_0
128                 override
129 #endif
130                 int RecordsAffected {
131                         get {
132                                 return GetRecordsAffected ();
133                         }
134                 }
135
136                 #endregion // Properties
137
138                 #region Methods
139
140                 public
141 #if NET_2_0
142                 override
143 #endif
144                 void Close ()
145                 {
146                         if (!isClosed) {
147                                 GetRecordsAffected ();
148                                 if (command != null)
149                                         command.CloseDataReader ();
150                         }
151                         if (statement != null) {
152                                 statement.Dispose();
153                                 statement = null;
154                         }
155 #if NET_2_0
156                         if (schemaTable != null) {
157                                 schemaTable.Dispose ();
158                                 schemaTable = null;
159                         }
160 #endif
161                         isClosed = true;
162                 }
163
164                 private static DataTable ConstructSchemaTable ()
165                 {
166                         Type booleanType = typeof (bool);
167                         Type stringType = typeof (string);
168                         Type intType = typeof (int);
169                         Type typeType = typeof (Type);
170                         Type shortType = typeof (short);
171
172                         DataTable schemaTable = new DataTable ("SchemaTable");
173                         schemaTable.Columns.Add ("ColumnName", stringType);
174                         schemaTable.Columns.Add ("ColumnOrdinal", intType);
175                         schemaTable.Columns.Add ("ColumnSize", intType);
176                         schemaTable.Columns.Add ("NumericPrecision", shortType);
177                         schemaTable.Columns.Add ("NumericScale", shortType);
178                         schemaTable.Columns.Add ("DataType", typeType);
179                         schemaTable.Columns.Add ("ProviderType", intType);
180                         schemaTable.Columns.Add ("IsLong", booleanType);
181                         schemaTable.Columns.Add ("AllowDBNull", booleanType);
182                         schemaTable.Columns.Add ("IsAliased", booleanType);
183                         schemaTable.Columns.Add ("IsExpression", booleanType);
184                         schemaTable.Columns.Add ("IsKey", booleanType);
185                         schemaTable.Columns.Add ("IsUnique", booleanType);
186                         schemaTable.Columns.Add ("BaseSchemaName", stringType);
187                         schemaTable.Columns.Add ("BaseTableName", stringType);
188                         schemaTable.Columns.Add ("BaseColumnName", stringType);
189
190                         return schemaTable;
191                 }
192
193 #if !NET_2_0
194                 private void Dispose (bool disposing)
195                 {
196                         if (!disposed) {
197                                 if (disposing)
198                                         Close ();
199                                 disposed = true;
200                         }
201                 }
202
203                 public void Dispose ()
204                 {
205                         Dispose (true);
206                         GC.SuppressFinalize (this);
207                 }
208 #endif
209
210                 public
211 #if NET_2_0
212                 override
213 #endif
214                 bool GetBoolean (int i)
215                 {
216                         throw new NotSupportedException ();
217                 }
218
219                 public
220 #if NET_2_0
221                 override
222 #endif
223                 byte GetByte (int i)
224                 {
225                         throw new NotSupportedException ();
226                 }
227
228                 public
229 #if NET_2_0
230                 override
231 #endif
232                 long GetBytes (int i, long fieldOffset, byte[] buffer2, int bufferoffset, int length)
233                 {
234                         byte[] value = (byte[]) GetValue (i);
235
236                         if (buffer2 == null)
237                                 return value.Length; // Return length of data
238
239                         // Copy data into buffer
240                         long lobLength = value.Length;
241                         if ((lobLength - fieldOffset) < length)
242                                 length = (int) (lobLength - fieldOffset);
243                         Array.Copy (value, (int) fieldOffset, buffer2,
244                                 bufferoffset, length);
245                         return length; // return actual read count
246                 }
247
248                 public
249 #if NET_2_0
250                 override
251 #endif
252                 char GetChar (int i)
253                 {
254                         throw new NotSupportedException ();
255                 }
256
257                 public
258 #if NET_2_0
259                 override
260 #endif
261                 long GetChars (int i, long fieldOffset, char[] buffer2, int bufferoffset, int length)
262                 {
263                         char [] value = (char[]) GetValue (i);
264                         Array.Copy (value, (int) fieldOffset, buffer2,
265                                 bufferoffset, length);
266                         return (value.Length - fieldOffset);
267                 }
268
269 #if !NET_2_0
270                 [MonoTODO]
271                 public IDataReader GetData (int i)
272                 {
273                         throw new NotImplementedException ();
274                 }
275 #endif
276
277                 public
278 #if NET_2_0
279                 override
280 #endif
281                 string GetDataTypeName (int i)
282                 {
283                         return dataTypeNames [i].ToString ().ToUpper ();
284                 }
285
286                 public
287 #if NET_2_0
288                 override
289 #endif
290                 DateTime GetDateTime (int i)
291                 {
292                         IConvertible c = (IConvertible) GetValue (i);
293                         return c.ToDateTime (CultureInfo.CurrentCulture);
294                 }
295
296                 public
297 #if NET_2_0
298                 override
299 #endif
300                 decimal GetDecimal (int i)
301                 {
302                         IConvertible c = (IConvertible) GetValue (i);
303                         return c.ToDecimal (CultureInfo.CurrentCulture);
304                 }
305
306                 public
307 #if NET_2_0
308                 override
309 #endif
310                 double GetDouble (int i)
311                 {
312                         IConvertible c = (IConvertible) GetValue (i);
313                         return c.ToDouble (CultureInfo.CurrentCulture);
314                 }
315
316                 public
317 #if NET_2_0
318                 override
319 #endif
320                 Type GetFieldType (int i)
321                 {
322                         OciDefineHandle defineHandle = (OciDefineHandle) statement.Values [i];
323                         return defineHandle.FieldType;
324                 }
325
326                 public
327 #if NET_2_0
328                 override
329 #endif
330                 float GetFloat (int i)
331                 {
332                         IConvertible c = (IConvertible) GetValue (i);
333                         return c.ToSingle (CultureInfo.CurrentCulture);
334                 }
335
336                 public
337 #if NET_2_0
338                 override
339 #endif
340                 Guid GetGuid (int i)
341                 {
342                         throw new NotSupportedException ();
343                 }
344
345                 public
346 #if NET_2_0
347                 override
348 #endif
349                 short GetInt16 (int i)
350                 {
351                         throw new NotSupportedException ();
352                 }
353
354                 public
355 #if NET_2_0
356                 override
357 #endif
358                 int GetInt32 (int i)
359                 {
360                         IConvertible c = (IConvertible) GetValue (i);
361                         return c.ToInt32 (CultureInfo.CurrentCulture);
362                 }
363
364                 public
365 #if NET_2_0
366                 override
367 #endif
368                 long GetInt64 (int i)
369                 {
370                         IConvertible c = (IConvertible) GetValue (i);
371                         return c.ToInt64 (CultureInfo.CurrentCulture);
372                 }
373
374                 public
375 #if NET_2_0
376                 override
377 #endif
378                 string GetName (int i)
379                 {
380                         return statement.GetParameter (i).GetName ();
381                 }
382
383                 [MonoTODO]
384                 public OracleBFile GetOracleBFile (int i)
385                 {
386                         throw new NotImplementedException ();
387                 }
388
389                 [MonoTODO]
390                 public OracleBinary GetOracleBinary (int i)
391                 {
392                         if (IsDBNull (i))
393                                 throw new InvalidOperationException("The value is null");
394
395                         return new OracleBinary ((byte[]) GetValue (i));
396                 }
397
398                 public OracleLob GetOracleLob (int i)
399                 {
400                         if (IsDBNull (i))
401                                 throw new InvalidOperationException("The value is null");
402
403                         OracleLob output = (OracleLob) ((OciDefineHandle) statement.Values [i]).GetValue (
404                                 command.Connection.SessionFormatProvider, command.Connection);
405                         output.connection = command.Connection;
406                         return output;
407                 }
408
409                 public OracleNumber GetOracleNumber (int i)
410                 {
411                         if (IsDBNull (i))
412                                 throw new InvalidOperationException("The value is null");
413
414                         return new OracleNumber (GetDecimal (i));
415                 }
416
417                 public OracleDateTime GetOracleDateTime (int i)
418                 {
419                         if (IsDBNull (i))
420                                 throw new InvalidOperationException("The value is null");
421
422                         return new OracleDateTime (GetDateTime (i));
423                 }
424
425                 public OracleMonthSpan GetOracleMonthSpan (int i)
426                 {
427                         if (IsDBNull (i))
428                                 throw new InvalidOperationException("The value is null");
429
430                         OracleMonthSpan output = (OracleMonthSpan) ((OciDefineHandle) statement.Values [i]).GetValue (
431                                 command.Connection.SessionFormatProvider, command.Connection);
432                         return output;
433                 }
434
435                 public OracleString GetOracleString (int i)
436                 {
437                         if (IsDBNull (i))
438                                 throw new InvalidOperationException("The value is null");
439
440                         return new OracleString (GetString (i));
441                 }
442
443                 public object GetOracleValue (int i)
444                 {
445                         OciDefineHandle defineHandle = (OciDefineHandle) statement.Values [i];
446
447                         switch (defineHandle.DataType) {
448                         case OciDataType.Raw:
449                                 return GetOracleBinary (i);
450                         case OciDataType.Date:
451                                 return GetOracleDateTime (i);
452                         case OciDataType.Clob:
453                         case OciDataType.Blob:
454                                 return GetOracleLob (i);
455                         case OciDataType.Integer:
456                         case OciDataType.Number:
457                         case OciDataType.Float:
458                                 return GetOracleNumber (i);
459                         case OciDataType.VarChar2:
460                         case OciDataType.String:
461                         case OciDataType.VarChar:
462                         case OciDataType.Char:
463                         case OciDataType.CharZ:
464                         case OciDataType.OciString:
465                         case OciDataType.LongVarChar:
466                         case OciDataType.Long:
467                         case OciDataType.RowIdDescriptor:
468                                 return GetOracleString (i);
469                         case OciDataType.IntervalDayToSecond:
470                                 return GetOracleTimeSpan (i);
471                         case OciDataType.IntervalYearToMonth:
472                                 return GetOracleMonthSpan (i);
473                         default:
474                                 throw new NotImplementedException ();
475                         }
476                 }
477
478                 public int GetOracleValues (object[] values)
479                 {
480                         int len = values.Length;
481                         int count = statement.ColumnCount;
482                         int retval = 0;
483
484                         if (len > count)
485                                 retval = count;
486                         else
487                                 retval = len;
488
489                         for (int i = 0; i < retval; i += 1) 
490                                 values [i] = GetOracleValue (i);
491
492                         return retval;
493                 }
494
495                 public OracleTimeSpan GetOracleTimeSpan (int i)
496                 {
497                         return new OracleTimeSpan (GetTimeSpan (i));
498                 }
499
500                 public
501 #if NET_2_0
502                 override
503 #endif
504                 int GetOrdinal (string name)
505                 {
506                         int i = GetOrdinalInternal (name);
507                         if (i == -1)
508                                 throw new IndexOutOfRangeException ();
509                         return i;
510                 }
511
512                 private int GetOrdinalInternal (string name)
513                 {
514                         int i;
515                         
516                         for (i = 0; i < statement.ColumnCount; i += 1) {
517                                 if (String.Compare (statement.GetParameter(i).GetName(), name, false) == 0)
518                                         return i;
519                         }
520
521                         for (i = 0; i < statement.ColumnCount; i += 1) {
522                                 if (String.Compare (statement.GetParameter(i).GetName(), name, true) == 0)
523                                         return i;
524                         }
525
526                         return -1;
527                 }
528
529                 private int GetRecordsAffected ()
530                 {
531                         if (statementType == OciStatementType.Select)
532                                 return -1;
533                         else {
534                                 if (!isClosed) {
535                                         if (recordsAffected == -1)
536                                                 if (statement != null)
537                                                         recordsAffected = statement.GetAttributeInt32 (OciAttributeType.RowCount, command.ErrorHandle);
538                                 }
539                         }
540                         
541                         return recordsAffected;
542                 }
543
544                 // get the KeyInfo about table columns (primary key)
545                 private StringCollection GetKeyInfo (out string ownerName, out string tableName) 
546                 {
547                         ArrayList tables = new ArrayList ();
548                         ParseSql (command.CommandText, ref tables);
549                         // TODO: handle multiple tables
550                         GetOwnerAndName ((string)tables[0], out ownerName, out tableName);
551                         return GetKeyColumns (ownerName, tableName);
552                 }
553
554                 // get the columns in a table that have a primary key
555                 private StringCollection GetKeyColumns(string owner, string table) 
556                 {
557                         OracleCommand cmd = command.Connection.CreateCommand ();
558
559                         StringCollection columns = new StringCollection ();
560
561                         if (command.Transaction != null)
562                                 cmd.Transaction = command.Transaction;
563
564                         cmd.CommandText = "select col.column_name " +
565                                 "from all_constraints pk, all_cons_columns col " +
566                                 "where pk.owner = '" + owner + "' " +
567                                 "and pk.table_name = '" + table + "' " +
568                                 "and pk.constraint_type = 'P' " +
569                                 "and pk.owner = col.owner " +
570                                 "and pk.table_name = col.table_name " +
571                                 "and pk.constraint_name = col.constraint_name";
572
573                         OracleDataReader rdr = cmd.ExecuteReader ();
574                         while (rdr.Read ())
575                                 columns.Add (rdr.GetString (0));
576
577                         rdr.Close();
578                         rdr = null;
579                         cmd.Dispose();
580                         cmd = null;
581
582                         return columns;
583                 }
584
585                 // parse the list of table names in the SQL
586                 // TODO: parse the column aliases and table aliases too
587                 //       and determine if a column is a true table column
588                 //       or an expression
589                 private void ParseSql (string sql, ref ArrayList tables) {
590                         if (sql == String.Empty)
591                                 return;
592
593                         char[] chars = sql.ToCharArray ();
594                         StringBuilder wb = new StringBuilder ();
595
596                         bool bFromFound = false;
597                         bool bEnd = false;
598                         int i = 0;
599                         bool bTableFound = false;
600                 
601                         for (; !bEnd && i < chars.Length; i++) {
602                                 char ch = chars[i];
603                         
604                                 if (Char.IsLetter (ch)) {
605                                         wb.Append (ch);
606                                 } else if (Char.IsWhiteSpace (ch)) {
607                                         if (wb.Length > 0) {
608                                                 if (!bFromFound) {
609                                                         string word = wb.ToString ().ToUpper ();
610                                                         if (word.Equals ("FROM")) {
611                                                                 bFromFound = true;
612                                                         }
613                                                         wb = null;
614                                                         wb = new StringBuilder ();
615                                                         bTableFound = false;
616                                                 } else {
617                                                         switch (wb.ToString ().ToUpper ()) {
618                                                         case "WHERE":
619                                                         case "ORDER":
620                                                         case "GROUP":
621                                                                 bEnd = true;
622                                                                 bTableFound = false;
623                                                                 break;
624                                                         default:
625                                                                 if (bTableFound)
626                                                                         bTableFound = false; // this is done in case of a table alias
627                                                                 else {
628                                                                         bTableFound = true;
629                                                                         tables.Add (wb.ToString ().ToUpper ());
630                                                                 }
631                                                                 wb = null;
632                                                                 wb = new StringBuilder ();
633                                                                 break;
634                                                         }
635                                                 }
636                                         }
637                                 } else if (bFromFound) {
638                                         switch (ch) {
639                                         case ',': 
640                                                 if (bTableFound)
641                                                         bTableFound = false;
642                                                 else
643                                                         tables.Add (wb.ToString ().ToUpper ());
644                                                 wb = null;
645                                                 wb = new StringBuilder ();
646                                                 break;
647                                         case '$':
648                                         case '_':
649                                         case '.':
650                                                 wb.Append (ch);
651                                                 break;
652                                         }
653                                 }
654                         }
655                         if (!bEnd) {
656                                 if (wb.Length > 0) {
657                                         if (!bFromFound && wb.ToString ().ToUpper ().Equals ("FROM"))
658                                                 bFromFound = true;
659                                         if (bFromFound) {
660                                                 switch(wb.ToString ().ToUpper ()) {
661                                                 case "WHERE":
662                                                 case "ORDER":
663                                                 case "GROUP":
664                                                         bEnd = true;
665                                                         break;
666                                                 default:
667                                                         if (!bTableFound)
668                                                                 tables.Add (wb.ToString ().ToUpper ());
669                                                         break;
670                                                 }
671                                         }
672                                 }
673                         }
674                 }
675
676                 // takes a object name like "owner.name" and parses it into "owner" and "name" strings
677                 // if object name is only "name", then it gets the username as the owner and returns
678                 // the name
679                 private void GetOwnerAndName (string objectName, out string owner, out string name) 
680                 {
681                         int idx = objectName.IndexOf (".");
682                         if (idx == -1) {
683                                 OracleCommand cmd = command.Connection.CreateCommand ();
684                                 if (command.Transaction != null)
685                                         cmd.Transaction = command.Transaction;
686
687                                 cmd.CommandText = "SELECT USER FROM DUAL";
688                                 owner = (string) cmd.ExecuteScalar();
689                                 name = objectName;
690                                 cmd.Dispose();
691                                 cmd = null;
692                         } else {
693                                 owner = objectName.Substring (0, idx);
694                                 name = objectName.Substring (idx + 1);
695                         }
696                 }
697
698                 [MonoTODO("Implement this properly, with all needed information.")]
699                 public
700 #if NET_2_0
701                 override
702 #endif
703                 DataTable GetSchemaTable ()
704                 {
705                         StringCollection keyinfo = null;
706
707                         if (schemaTable.Rows != null && schemaTable.Rows.Count > 0)
708                                 return schemaTable;
709
710                         string owner = String.Empty;
711                         string table = String.Empty;
712                         if ((behavior & CommandBehavior.KeyInfo) != 0)
713                                 keyinfo = GetKeyInfo (out owner, out table);
714
715                         dataTypeNames = new ArrayList ();
716
717                         for (int i = 0; i < statement.ColumnCount; i += 1) {
718                                 DataRow row = schemaTable.NewRow ();
719
720                                 OciParameterDescriptor parameter = statement.GetParameter (i);
721
722                                 dataTypeNames.Add (parameter.GetDataTypeName ());
723
724                                 row ["ColumnName"]              = parameter.GetName ();
725                                 row ["ColumnOrdinal"]           = i + 1;
726                                 row ["ColumnSize"]              = parameter.GetDataSize ();
727                                 row ["NumericPrecision"]        = parameter.GetPrecision ();
728                                 row ["NumericScale"]            = parameter.GetScale ();
729                                 
730                                 string sDataTypeName            = parameter.GetDataTypeName ();
731                                 row ["DataType"]                = parameter.GetFieldType (sDataTypeName);
732                                 
733                                 OciDataType ociType = parameter.GetDataType();
734                                 OracleType oraType = OciParameterDescriptor.OciDataTypeToOracleType (ociType);
735                                 row ["ProviderType"]            = (int) oraType;
736                                 
737                                 if (ociType == OciDataType.Blob || ociType == OciDataType.Clob)
738                                         row ["IsLong"]          = true;
739                                 else
740                                         row ["IsLong"]          = false;
741
742                                 row ["AllowDBNull"]             = parameter.GetIsNull ();
743
744                                 row ["IsAliased"]               = DBNull.Value; // TODO:
745                                 row ["IsExpression"]            = DBNull.Value; // TODO:
746                                 
747                                 if ((behavior & CommandBehavior.KeyInfo) != 0) {
748                                         if (keyinfo.IndexOf ((string)row ["ColumnName"]) >= 0)
749                                                 row ["IsKey"] = true;
750                                         else
751                                                 row ["IsKey"] = false;
752
753                                         row ["IsUnique"]        = DBNull.Value; // TODO: only set this if CommandBehavior.KeyInfo, otherwise, null
754                                         row ["BaseSchemaName"]  = owner;
755                                         row ["BaseTableName"]   = table;
756                                         row ["BaseColumnName"]  = row ["ColumnName"];
757                                 } else {
758                                         row ["IsKey"]           = DBNull.Value; 
759                                         row ["IsUnique"]        = DBNull.Value; 
760                                         row ["BaseSchemaName"]  = DBNull.Value; 
761                                         row ["BaseTableName"]   = DBNull.Value; 
762                                         row ["BaseColumnName"]  = DBNull.Value; 
763                                 }
764
765                                 schemaTable.Rows.Add (row);
766                         }
767
768                         return schemaTable;
769                 }
770
771                 public
772 #if NET_2_0
773                 override
774 #endif
775                 string GetString (int i)
776                 {
777                         return (string) GetValue (i);
778                 }
779
780                 public TimeSpan GetTimeSpan (int i)
781                 {
782                         return (TimeSpan) GetValue (i);
783                 }
784
785                 public
786 #if NET_2_0
787                 override
788 #endif
789                 object GetValue (int i)
790                 {
791                         OciDefineHandle defineHandle = (OciDefineHandle) statement.Values [i];
792
793                         if (defineHandle.IsNull)
794                                 return DBNull.Value;
795
796                         switch (defineHandle.DataType) {
797                         case OciDataType.Blob:
798                         case OciDataType.Clob:
799                                 OracleLob lob = GetOracleLob (i);
800                                 object value = lob.Value;
801                                 lob.Close ();
802                                 return value;
803                         default:
804                                 return defineHandle.GetValue (command.Connection.SessionFormatProvider, command.Connection);
805                         }
806                 }
807
808                 public
809 #if NET_2_0
810                 override
811 #endif
812                 int GetValues (object [] values)
813                 {
814                         int len = values.Length;
815                         int count = statement.ColumnCount;
816                         int retval = 0;
817
818                         if (len > count)
819                                 retval = count;
820                         else
821                                 retval = len;
822
823                         for (int i = 0; i < retval; i += 1) 
824                                 values [i] = GetValue (i);
825
826                         return retval;
827                 }
828
829 #if NET_2_0
830                 public override IEnumerator GetEnumerator ()
831 #else
832                 IEnumerator IEnumerable.GetEnumerator ()
833 #endif
834                 {
835                         return new DbEnumerator (this);
836                 }
837
838                 public
839 #if NET_2_0
840                 override
841 #endif
842                 bool IsDBNull (int i)
843                 {
844                         OciDefineHandle defineHandle = (OciDefineHandle) statement.Values [i];
845                         return defineHandle.IsNull;
846                 }
847
848                 void ValidateState ()
849                 {
850                         if (IsClosed)
851                                 throw new InvalidOperationException ("Invalid attempt to read data when reader is closed");
852                 }
853
854                 public
855 #if NET_2_0
856                 override
857 #endif
858                 bool NextResult ()
859                 {
860                         ValidateState ();
861
862                         if (statement == null)
863                                 return false;
864
865                         statement.Dispose ();
866                         statement = null;
867                         
868                         statement = command.GetNextResult ();
869                         
870                         if (statement == null)
871                                 return false;
872
873                         return true; 
874                 }
875
876                 public
877 #if NET_2_0
878                 override
879 #endif
880                 bool Read ()
881                 {
882                         ValidateState ();
883
884                         if (hasRows) {
885                                 bool retval = statement.Fetch ();
886                                 hasRows = retval;
887                                 return retval;
888                         }
889                         return false;
890                 }
891
892 #if NET_2_0
893                 [MonoTODO]
894                 public override Type GetProviderSpecificFieldType (int i)
895                 {
896                         return GetOracleValue (i).GetType ();
897                 }
898
899                 [MonoTODO]
900                 public override object GetProviderSpecificValue (int i)
901                 {
902                         return GetOracleValue (i);
903                 }
904
905                 [MonoTODO]
906                 public override int GetProviderSpecificValues (object [] values)
907                 {
908                         return GetOracleValues (values);
909                 }
910 #endif
911
912                 #endregion // Methods
913         }
914 }