Removed debugging CWL that shows during normal execution.
[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         public sealed class OracleDataReader : MarshalByRefObject, IDataReader, IDisposable, IDataRecord, IEnumerable
32         {
33                 #region Fields
34
35                 OracleCommand command;
36                 ArrayList dataTypeNames;
37                 bool disposed = false;
38                 bool isClosed;
39                 bool hasRows;
40                 DataTable schemaTable;
41                 CommandBehavior behavior;
42
43                 int recordsAffected = -1;
44                 OciStatementType statementType;
45                 OciStatementHandle statement;
46
47                 #endregion // Fields
48
49                 #region Constructors
50
51                 internal OracleDataReader (OracleCommand command, OciStatementHandle statement, bool extHasRows, CommandBehavior behavior) 
52                 {
53                         this.command = command;
54                         this.hasRows = extHasRows;
55                         this.isClosed = false;
56                         this.schemaTable = ConstructSchemaTable ();
57                         this.statement = statement;
58                         this.statementType = statement.GetStatementType ();
59                         this.behavior = behavior;
60                 }
61
62                 ~OracleDataReader ()
63                 {
64                         Dispose (false);
65                 }
66
67                 #endregion // Constructors
68
69                 #region Properties
70
71                 public int Depth {
72                         get { return 0; }
73                 }
74
75                 public int FieldCount {
76                         get { return statement.ColumnCount; }
77                 }
78
79                 public bool HasRows {
80                         get { return hasRows; }
81                 }
82
83                 public bool IsClosed {
84                         get { return isClosed; }
85                 }
86
87                 public object this [string name] {
88                         get { return GetValue (GetOrdinal (name)); }
89                 }
90
91                 public object this [int i] {
92                         get { return GetValue (i); }
93                 }
94
95                 public int RecordsAffected {
96                         get { 
97                                 return GetRecordsAffected ();                           
98                         }
99                 }
100
101                 #endregion // Properties
102
103                 #region Methods
104
105                 public void Close ()
106                 {       
107                         if (!isClosed) {
108                                 GetRecordsAffected ();
109                                 if (command != null)
110                                         command.CloseDataReader ();
111                         }
112                         if (statement != null) {
113                                 statement.Dispose();
114                                 statement = null;
115                         }
116                         isClosed = true;
117                 }
118
119                 private static DataTable ConstructSchemaTable ()
120                 {
121                         Type booleanType = Type.GetType ("System.Boolean");
122                         Type stringType = Type.GetType ("System.String");
123                         Type intType = Type.GetType ("System.Int32");
124                         Type typeType = Type.GetType ("System.Type");
125                         Type shortType = Type.GetType ("System.Int16");
126
127                         DataTable schemaTable = new DataTable ("SchemaTable");
128                         schemaTable.Columns.Add ("ColumnName", stringType);
129                         schemaTable.Columns.Add ("ColumnOrdinal", intType);
130                         schemaTable.Columns.Add ("ColumnSize", intType);
131                         schemaTable.Columns.Add ("NumericPrecision", shortType);
132                         schemaTable.Columns.Add ("NumericScale", shortType);
133                         schemaTable.Columns.Add ("DataType", typeType);
134                         schemaTable.Columns.Add ("ProviderType", intType);
135                         schemaTable.Columns.Add ("IsLong", booleanType);
136                         schemaTable.Columns.Add ("AllowDBNull", booleanType);
137                         schemaTable.Columns.Add ("IsAliased", booleanType);\r
138                         schemaTable.Columns.Add ("IsExpression", booleanType);
139                         schemaTable.Columns.Add ("IsKey", booleanType);
140                         schemaTable.Columns.Add ("IsUnique", booleanType);
141                         schemaTable.Columns.Add ("BaseSchemaName", stringType);
142                         schemaTable.Columns.Add ("BaseTableName", stringType);
143                         schemaTable.Columns.Add ("BaseColumnName", stringType);
144
145                         return schemaTable;
146                 }
147
148                 private void Dispose (bool disposing)
149                 {
150                         if (!disposed) {
151                                 if (disposing) {
152                                         schemaTable.Dispose ();
153                                         Close ();
154                                 }
155                                 disposed = true;
156                         }
157                 }
158
159                 public void Dispose ()
160                 {
161                         Dispose (true);
162                         GC.SuppressFinalize (this);
163                 }
164
165                 public bool GetBoolean (int i)
166                 {
167                         throw new NotSupportedException ();
168                 }
169
170                 public byte GetByte (int i)
171                 {
172                         throw new NotSupportedException ();
173                 }
174
175                 public long GetBytes (int i, long fieldOffset, byte[] buffer2, int bufferoffset, int length)
176                 {
177                         object value = GetValue (i);
178                         if (!(value is byte[]))
179                                 throw new InvalidCastException ();
180
181                         if ( buffer2 == null )
182                                 return ((byte []) value).Length; // Return length of data
183
184                         // Copy data into buffer
185                         long lobLength = ((byte []) value).Length;
186                         if ( (lobLength - fieldOffset) < length)
187                                 length = (int) (lobLength - fieldOffset);
188                         Array.Copy ( (byte[]) value, (int) fieldOffset, buffer2, bufferoffset, length);
189                         return length; // return actual read count
190                 }
191
192                 public char GetChar (int i)
193                 {
194                         throw new NotSupportedException ();
195                 }
196
197                 public long GetChars (int i, long fieldOffset, char[] buffer2, int bufferoffset, int length)
198                 {
199                         object value = GetValue (i);
200                         if (!(value is char[]))
201                                 throw new InvalidCastException ();
202                         Array.Copy ((char[]) value, (int) fieldOffset, buffer2, bufferoffset, length);
203                         return ((char[]) value).Length - fieldOffset;
204                 }
205
206                 [MonoTODO]
207                 public IDataReader GetData (int i)
208                 {
209                         throw new NotImplementedException ();
210                 }
211
212                 public string GetDataTypeName (int i)
213                 {
214                         return dataTypeNames [i].ToString ().ToUpper ();
215                 }
216
217                 public DateTime GetDateTime (int i)
218                 {
219                         IConvertible c = (IConvertible) GetValue (i);
220                         return c.ToDateTime (CultureInfo.CurrentCulture);
221                 }
222
223                 public decimal GetDecimal (int i)
224                 {
225                         IConvertible c = (IConvertible) GetValue (i);
226                         return c.ToDecimal (CultureInfo.CurrentCulture);
227                 }
228
229                 public double GetDouble (int i)
230                 {
231                         IConvertible c = (IConvertible) GetValue (i);
232                         return c.ToDouble (CultureInfo.CurrentCulture);
233                 }
234
235                 public Type GetFieldType (int i)
236                 {
237                         OciDefineHandle defineHandle = (OciDefineHandle) statement.Values [i];
238                         return defineHandle.FieldType;
239                 }
240
241                 public float GetFloat (int i)
242                 {
243                         IConvertible c = (IConvertible) GetValue (i);
244                         return c.ToSingle (CultureInfo.CurrentCulture);
245                 }
246
247                 public Guid GetGuid (int i)
248                 {
249                         throw new NotSupportedException ();
250                 }
251
252                 public short GetInt16 (int i)
253                 {
254                         throw new NotSupportedException ();
255                 }
256
257                 public int GetInt32 (int i)
258                 {
259                         IConvertible c = (IConvertible) GetValue (i);
260                         return c.ToInt32 (CultureInfo.CurrentCulture);
261                 }
262
263                 public long GetInt64 (int i)
264                 {
265                         IConvertible c = (IConvertible) GetValue (i);
266                         return c.ToInt64 (CultureInfo.CurrentCulture);
267                 }
268
269                 public string GetName (int i)
270                 {
271                         return statement.GetParameter (i).GetName ();
272                 }
273
274                 [MonoTODO]
275                 public OracleBFile GetOracleBFile (int i)
276                 {
277                         throw new NotImplementedException ();
278                 }
279
280                 [MonoTODO]
281                 public OracleBinary GetOracleBinary (int i)
282                 {
283                         if (IsDBNull (i))
284                                 throw new InvalidOperationException("The value is null");
285
286                         return new OracleBinary ((byte[]) GetValue (i));
287                 }
288
289                 public OracleLob GetOracleLob (int i)
290                 {
291                         if (IsDBNull (i))
292                                 throw new InvalidOperationException("The value is null");
293
294                         OracleLob output = (OracleLob) ((OciDefineHandle) statement.Values [i]).GetValue();
295                         output.connection = command.Connection;
296                         return output;
297                 }
298
299                 public OracleNumber GetOracleNumber (int i)
300                 {
301                         if (IsDBNull (i))
302                                 throw new InvalidOperationException("The value is null");
303
304                         return new OracleNumber (GetDecimal (i));
305                 }
306
307                 public OracleDateTime GetOracleDateTime (int i)
308                 {
309                         if (IsDBNull (i))
310                                 throw new InvalidOperationException("The value is null");
311
312                         return new OracleDateTime (GetDateTime (i));
313                 }
314
315                 [MonoTODO]
316                 public OracleMonthSpan GetOracleMonthSpan (int i)
317                 {
318                         throw new NotImplementedException ();
319                 }
320
321                 public OracleString GetOracleString (int i)
322                 {
323                         if (IsDBNull (i))
324                                 throw new InvalidOperationException("The value is null");
325
326                         return new OracleString (GetString (i));
327                 }
328
329                 public object GetOracleValue (int i)
330                 {
331                         OciDefineHandle defineHandle = (OciDefineHandle) statement.Values [i];
332
333                         switch (defineHandle.DataType) {
334                         case OciDataType.Raw:
335                                 return GetOracleBinary (i);
336                         case OciDataType.Date:
337                                 return GetOracleDateTime (i);
338                         case OciDataType.Clob:
339                         case OciDataType.Blob:
340                                 return GetOracleLob (i);
341                         case OciDataType.Integer:
342                         case OciDataType.Number:
343                         case OciDataType.Float:
344                                 return GetOracleNumber (i);
345                         case OciDataType.VarChar2:
346                         case OciDataType.String:
347                         case OciDataType.VarChar:
348                         case OciDataType.Char:
349                         case OciDataType.CharZ:
350                         case OciDataType.OciString:
351                         case OciDataType.LongVarChar:
352                         case OciDataType.Long:
353                         case OciDataType.RowIdDescriptor:
354                                 return GetOracleString (i);
355                         default:
356                                 throw new NotImplementedException ();
357                         }
358                 }
359
360                 public int GetOracleValues (object[] values)
361                 {
362                         int len = values.Length;
363                         int count = statement.ColumnCount;
364                         int retval = 0;
365
366                         if (len > count)
367                                 retval = count;
368                         else
369                                 retval = len;
370
371                         for (int i = 0; i < retval; i += 1) 
372                                 values [i] = GetOracleValue (i);
373
374                         return retval;
375                 }
376
377                 [MonoTODO]
378                 public OracleTimeSpan GetOracleTimeSpan (int i)
379                 {
380                         return new OracleTimeSpan (GetTimeSpan (i));
381                 }
382
383                 public int GetOrdinal (string name)
384                 {
385                         int i = GetOrdinalInternal (name);
386                         if (i == -1)
387                                 throw new IndexOutOfRangeException ();
388                         return i;
389                 }
390
391                 private int GetOrdinalInternal (string name)
392                 {
393                         int i;
394                         
395                         for (i = 0; i < statement.ColumnCount; i += 1) {
396                                 if (String.Compare (statement.GetParameter(i).GetName(), name, false) == 0)
397                                         return i;
398                         }
399
400                         for (i = 0; i < statement.ColumnCount; i += 1) {
401                                 if (String.Compare (statement.GetParameter(i).GetName(), name, true) == 0)
402                                         return i;
403                         }
404
405                         return -1;
406                 }
407
408                 private int GetRecordsAffected ()
409                 {
410                         if (statementType == OciStatementType.Select)
411                                 return -1;
412                         else {
413                                 if (this.isClosed == false) {
414                                         if (recordsAffected == -1)
415                                                 if (statement != null)
416                                                         recordsAffected = statement.GetAttributeInt32 (OciAttributeType.RowCount, command.ErrorHandle);
417                                 }
418                         }
419                         
420                         return recordsAffected;
421                 }
422
423                 // get the KeyInfo about table columns (primary key)
424                 private StringCollection GetKeyInfo (out string ownerName, out string tableName) 
425                 {
426                         ArrayList tables = new ArrayList ();
427                         ParseSql (command.CommandText, ref tables);
428                         // TODO: handle multiple tables
429                         GetOwnerAndName ((string)tables[0], out ownerName, out tableName);
430                         return GetKeyColumns (ownerName, tableName);
431                 }
432
433                 // get the columns in a table that have a primary key
434                 private StringCollection GetKeyColumns(string owner, string table) 
435                 {
436                         OracleCommand cmd = command.Connection.CreateCommand ();
437                         
438                         StringCollection columns = new StringCollection ();
439                                                 \r
440                         if (command.Transaction != null)\r
441                                 cmd.Transaction = command.Transaction;\r
442 \r
443                         cmd.CommandText = "select col.column_name " +\r
444                                 "from all_constraints pk, all_cons_columns col " +\r
445                                 "where pk.owner = '" + owner + "' " +\r
446                                 "and pk.table_name = '" + table + "' " +\r
447                                 "and pk.constraint_type = 'P' " +\r
448                                 "and pk.owner = col.owner " +\r
449                                 "and pk.table_name = col.table_name " +\r
450                                 "and pk.constraint_name = col.constraint_name";\r
451
452                         OracleDataReader rdr = cmd.ExecuteReader ();
453                         while (rdr.Read ())
454                                 columns.Add (rdr.GetString (0));
455
456                         rdr.Close();
457                         rdr = null;
458                         cmd.Dispose();
459                         cmd = null;
460
461                         return columns;
462                 }
463
464                 // parse the list of table names in the SQL
465                 // TODO: parse the column aliases and table aliases too
466                 //       and determine if a column is a true table column
467                 //       or an expression
468                 private void ParseSql (string sql, ref ArrayList tables) {\r
469                         if (sql == String.Empty)\r
470                                 return;\r
471 \r
472                         char[] chars = sql.ToCharArray ();\r
473                         StringBuilder wb = new StringBuilder ();\r
474 \r
475                         bool bFromFound = false;\r
476                         bool bEnd = false;\r
477                         int i = 0;\r
478                         bool bTableFound = false;\r
479                 \r
480                         for (; bEnd == false && i < chars.Length; i++) {\r
481                                 char ch = chars[i];\r
482                         \r
483                                 if (Char.IsLetter (ch)) {\r
484                                         wb.Append (ch);\r
485                                 }\r
486                                 else if (Char.IsWhiteSpace (ch)) {\r
487                                         if (wb.Length > 0) {\r
488                                                 if (bFromFound == false) {\r
489                                                         string word = wb.ToString ().ToUpper ();\r
490                                                         if (word.Equals ("FROM")) {\r
491                                                                 bFromFound = true;\r
492                                                         }\r
493                                                         wb = null;\r
494                                                         wb = new StringBuilder ();\r
495                                                         bTableFound = false;\r
496                                                 }\r
497                                                 else {\r
498                                                         switch (wb.ToString ().ToUpper ()) {\r
499                                                         case "WHERE":\r
500                                                         case "ORDER":\r
501                                                         case "GROUP":\r
502                                                                 bEnd = true;\r
503                                                                 bTableFound = false;\r
504                                                                 break;\r
505                                                         default:\r
506                                                                 if (bTableFound == true)\r
507                                                                         bTableFound = false; // this is done in case of a table alias\r
508                                                                 else {\r
509                                                                         bTableFound = true;\r
510                                                                         tables.Add (wb.ToString ().ToUpper ());\r
511                                                                 }\r
512                                                                 wb = null;\r
513                                                                 wb = new StringBuilder ();      \r
514                                                                 break;\r
515                                                         }\r
516                                                 }\r
517                                         }\r
518                                 }\r
519                                 else if (bFromFound == true) {\r
520                                         switch (ch) {\r
521                                         case ',': \r
522                                                 if (bTableFound == true)\r
523                                                         bTableFound = false;\r
524                                                 else {\r
525                                                         tables.Add (wb.ToString ().ToUpper ());\r
526                                                 }\r
527                                                 wb = null;\r
528                                                 wb = new StringBuilder ();\r
529                                                 break;\r
530                                         case '$':\r
531                                         case '_':\r
532                                         case '.':\r
533                                                 wb.Append (ch);\r
534                                                 break;\r
535                                         }\r
536                                 }\r
537                         }\r
538                         if (bEnd == false) {\r
539                                 if (wb.Length > 0) {\r
540                                         if (bFromFound == false && wb.ToString ().ToUpper ().Equals ("FROM"))\r
541                                                 bFromFound = true;\r
542                                         if (bFromFound == true) {\r
543                                                 switch(wb.ToString ().ToUpper ()) {\r
544                                                 case "WHERE":\r
545                                                 case "ORDER":\r
546                                                 case "GROUP":\r
547                                                         bEnd = true;\r
548                                                         break;\r
549                                                 default:\r
550                                                         if (bTableFound == false) {\r
551                                                                 tables.Add (wb.ToString ().ToUpper ());\r
552                                                         }\r
553                                                         break;\r
554                                                 }\r
555                                         }\r
556                                 }\r
557                         }\r
558                 }\r
559 \r
560                 // takes a object name like "owner.name" and parses it into "owner" and "name" strings\r
561                 // if object name is only "name", then it gets the username as the owner and returns\r
562                 // the name\r
563                 private void GetOwnerAndName (string objectName, out string owner, out string name) \r
564                 {\r
565                         int idx = objectName.IndexOf (".");\r
566                         if (idx == -1) {\r
567                                 OracleCommand cmd = command.Connection.CreateCommand ();
568                                 if (command.Transaction != null)\r
569                                         cmd.Transaction = command.Transaction;\r
570 \r
571                                 cmd.CommandText = "SELECT USER FROM DUAL";
572                                 owner = (string) cmd.ExecuteScalar();
573                                 name = objectName;\r
574                                 cmd.Dispose();\r
575                                 cmd = null;\r
576                         }\r
577                         else {\r
578                                 owner = objectName.Substring (0, idx);
579                                 name = objectName.Substring (idx + 1);\r
580                         }\r
581                 }\r
582 \r
583                 public DataTable GetSchemaTable ()
584                 {
585                         StringCollection keyinfo = null;
586
587                         if (schemaTable.Rows != null && schemaTable.Rows.Count > 0)
588                                 return schemaTable;
589
590                         string owner = String.Empty;
591                         string table = String.Empty;
592                         if ((behavior & CommandBehavior.KeyInfo) != 0)
593                                 keyinfo = GetKeyInfo (out owner, out table);
594
595                         dataTypeNames = new ArrayList ();
596
597                         for (int i = 0; i < statement.ColumnCount; i += 1) {
598                                 DataRow row = schemaTable.NewRow ();
599
600                                 OciParameterDescriptor parameter = statement.GetParameter (i);
601
602                                 dataTypeNames.Add (parameter.GetDataTypeName ());
603
604                                 row ["ColumnName"]              = parameter.GetName ();
605                                 row ["ColumnOrdinal"]           = i + 1;
606                                 row ["ColumnSize"]              = parameter.GetDataSize ();
607                                 row ["NumericPrecision"]        = parameter.GetPrecision ();
608                                 row ["NumericScale"]            = parameter.GetScale ();
609                                 
610                                 string sDataTypeName            = parameter.GetDataTypeName ();
611                                 row ["DataType"]                = parameter.GetFieldType (sDataTypeName);
612                                 
613                                 OciDataType ociType = parameter.GetDataType();
614                                 OracleType oraType = OciParameterDescriptor.OciDataTypeToOracleType (ociType);
615                                 row ["ProviderType"]            = (int) oraType;
616                                 
617                                 if (ociType == OciDataType.Blob || ociType == OciDataType.Clob)
618                                         row ["IsLong"]          = true;
619                                 else
620                                         row ["IsLong"]          = false;
621
622                                 row ["AllowDBNull"]             = parameter.GetIsNull ();
623
624                                 row ["IsAliased"]               = DBNull.Value; // TODO:\r
625                                 row ["IsExpression"]            = DBNull.Value; // TODO:
626                                 
627                                 if ((behavior & CommandBehavior.KeyInfo) != 0) {
628                                         if (keyinfo.IndexOf ((string)row ["ColumnName"]) >= 0)
629                                                 row ["IsKey"] = true;
630                                         else
631                                                 row ["IsKey"] = false;
632
633                                         row ["IsUnique"]        = DBNull.Value; // TODO: only set this if CommandBehavior.KeyInfo, otherwise, null
634                                         row ["BaseSchemaName"]  = owner;
635                                         row ["BaseTableName"]   = table;
636                                         row ["BaseColumnName"]  = row ["ColumnName"];
637                                 }       
638                                 else {
639                                         row ["IsKey"]           = DBNull.Value; 
640                                         row ["IsUnique"]        = DBNull.Value; 
641                                         row ["BaseSchemaName"]  = DBNull.Value; 
642                                         row ["BaseTableName"]   = DBNull.Value; 
643                                         row ["BaseColumnName"]  = DBNull.Value; 
644                                 }
645
646                                 schemaTable.Rows.Add (row);
647                         }
648
649                         return schemaTable;
650                 }
651
652                 public string GetString (int i)
653                 {
654                         object value = GetValue (i);
655                         if (!(value is string))
656                                 throw new InvalidCastException ();
657                         return (string) value;
658                 }
659
660                 public TimeSpan GetTimeSpan (int i)
661                 {
662                         object value = GetValue (i);
663                         if (!(value is TimeSpan))
664                                 throw new InvalidCastException ();
665                         return (TimeSpan) value;
666                 }
667
668                 public object GetValue (int i)
669                 {
670                         OciDefineHandle defineHandle = (OciDefineHandle) statement.Values [i];
671
672                         if (defineHandle.IsNull)
673                                 return DBNull.Value;
674
675                         switch (defineHandle.DataType) {
676                         case OciDataType.Blob:
677                         case OciDataType.Clob:
678                                 OracleLob lob = GetOracleLob (i);
679                                 object value = lob.Value;
680                                 lob.Close ();
681                                 return value;
682                         default:
683                                 return defineHandle.GetValue ();
684                         }
685                 }
686
687                 public int GetValues (object[] values)
688                 {
689                         int len = values.Length;
690                         int count = statement.ColumnCount;
691                         int retval = 0;
692
693                         if (len > count)
694                                 retval = count;
695                         else
696                                 retval = len;
697
698                         for (int i = 0; i < retval; i += 1) 
699                                 values [i] = GetValue (i);
700
701                         return retval;
702                 }
703
704                 IEnumerator IEnumerable.GetEnumerator ()
705                 {
706                         return new DbEnumerator (this);
707                 }
708
709                 public bool IsDBNull (int i)
710                 {
711                         OciDefineHandle defineHandle = (OciDefineHandle) statement.Values [i];
712                         return defineHandle.IsNull;
713                 }
714
715                 [MonoTODO]
716                 public bool NextResult ()
717                 {
718                         // FIXME: get next result
719                         return false; 
720                 }
721
722                 public bool Read ()
723                 {
724                         if (hasRows) {
725                                 bool retval = statement.Fetch ();
726                                 hasRows = retval;
727                                 return retval;
728                         }
729                         return false;
730                 }
731
732                 #endregion // Methods
733         }
734 }