This commit was manufactured by cvs2svn to create branch 'mono-1-0'.
[mono.git] / mcs / class / Npgsql / Npgsql / NpgsqlDataReader.cs
1
2 // Npgsql.NpgsqlDataReader.cs
3 //
4 // Author:
5 //      Francisco Jr. (fxjrlists@yahoo.com.br)
6 //
7 //      Copyright (C) 2002 The Npgsql Development Team
8 //      npgsql-general@gborg.postgresql.org
9 //      http://gborg.postgresql.org/project/npgsql/projdisplay.php
10 //
11 //
12 // This library is free software; you can redistribute it and/or
13 // modify it under the terms of the GNU Lesser General Public
14 // License as published by the Free Software Foundation; either
15 // version 2.1 of the License, or (at your option) any later version.
16 //
17 // This library is distributed in the hope that it will be useful,
18 // but WITHOUT ANY WARRANTY; without even the implied warranty of
19 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
20 // Lesser General Public License for more details.
21 //
22 // You should have received a copy of the GNU Lesser General Public
23 // License along with this library; if not, write to the Free Software
24 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
25
26 using System;
27 using System.Data;
28 using System.Collections;
29 using NpgsqlTypes;
30
31
32 namespace Npgsql
33 {
34     /// <summary>
35     /// Provides a means of reading a forward-only stream of rows from a PostgreSQL backend.  This class cannot be inherited.
36     /// </summary>
37     public class NpgsqlDataReader : IDataReader, IEnumerable
38     {
39         private NpgsqlConnection        _connection;
40         private ArrayList                       _resultsets;
41         private ArrayList                       _responses;
42         private Int32                           _rowIndex;
43         private Int32                           _resultsetIndex;
44         private NpgsqlResultSet         _currentResultset;
45         private DataTable                       _currentResultsetSchema;
46         private CommandBehavior     _behavior;
47         private Boolean             _isClosed;
48
49
50         // Logging related values
51         private static readonly String CLASSNAME = "NpgsqlDataReader";
52
53         internal NpgsqlDataReader( ArrayList resultsets, ArrayList responses, NpgsqlConnection connection, CommandBehavior behavior)
54         {
55             _resultsets = resultsets;
56             _responses = responses;
57             _connection = connection;
58             _rowIndex = -1;
59             _resultsetIndex = 0;
60
61             if (_resultsets.Count > 0)
62                 _currentResultset = (NpgsqlResultSet)_resultsets[_resultsetIndex];
63
64             _behavior = behavior;
65             _isClosed = false;
66         }
67
68         private Boolean HaveResultSet()
69         {
70             return (_currentResultset != null);
71         }
72
73         private Boolean HaveRow()
74         {
75             return (HaveResultSet() && _rowIndex >= 0 && _rowIndex < _currentResultset.Count);
76         }
77
78         private void CheckHaveResultSet()
79         {
80             if (! HaveResultSet()) {
81                 throw new InvalidOperationException("Cannot read data. No result set.");
82             }
83         }
84
85         private void CheckHaveRow()
86         {
87             CheckHaveResultSet();
88
89             if (_rowIndex < 0) {
90                 throw new InvalidOperationException("DataReader positioned before beginning of result set. Did you call Read()?");
91             } else if (_rowIndex >= _currentResultset.Count) {
92                 throw new InvalidOperationException("DataReader positioned beyond end of result set.");
93             }
94         }
95
96
97         /// <summary>
98         /// Releases the resources used by the <see cref="Npgsql.NpgsqlCommand">NpgsqlCommand</see>.
99         /// </summary>
100         public void Dispose()
101         {
102             Dispose(true);
103         }
104
105         /// <summary>
106         /// Releases the resources used by the <see cref="Npgsql.NpgsqlCommand">NpgsqlCommand</see>.
107         /// </summary>
108         protected void Dispose (bool disposing)
109         {
110             NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "Dispose");
111             if (disposing)
112             {
113                 this.Close();
114             }
115         }
116
117         /// <summary>
118         /// Gets a value indicating the depth of nesting for the current row.  Always returns zero.
119         /// </summary>
120         public Int32 Depth
121         {
122             get
123             {
124                 NpgsqlEventLog.LogPropertyGet(LogLevel.Debug, CLASSNAME, "Depth");
125                 return 0;
126             }
127         }
128
129         /// <summary>
130         /// Gets a value indicating whether the data reader is closed.
131         /// </summary>
132         public Boolean IsClosed
133         {
134             get
135             {
136                 NpgsqlEventLog.LogPropertyGet(LogLevel.Debug, CLASSNAME, "IsClosed");
137                 return _isClosed;
138             }
139         }
140
141         /// <summary>
142         /// Gets the number of rows changed, inserted, or deleted by execution of the SQL statement.
143         /// </summary>
144         public Int32 RecordsAffected
145         {
146             get
147             {
148                 NpgsqlEventLog.LogPropertyGet(LogLevel.Debug, CLASSNAME, "RecordsAffected");
149
150                 if (HaveResultSet()) {
151                     return -1;
152                 }
153
154                 String[] _returnStringTokens = ((String)_responses[_resultsetIndex]).Split(null);       // whitespace separator.
155
156                 try {
157                     return Int32.Parse(_returnStringTokens[_returnStringTokens.Length - 1]);
158                 }
159                 catch (FormatException) {
160                     return -1;
161                 }
162             }
163         }
164
165         /// <summary>
166         /// Closes the data reader object.
167         /// </summary>
168         public void Close()
169         {
170             if ((_behavior & CommandBehavior.CloseConnection) == CommandBehavior.CloseConnection)
171             {
172                 _connection.Close();
173                 _isClosed = true;
174             }
175         }
176
177         /// <summary>
178         /// Advances the data reader to the next result, when multiple result sets were returned by the PostgreSQL backend.
179         /// </summary>
180         /// <returns>True if the reader was advanced, otherwise false.</returns>
181         public Boolean NextResult()
182         {
183             NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "NextResult");
184
185             if((_resultsetIndex + 1) < _resultsets.Count)
186             {
187                 _resultsetIndex++;
188                 _rowIndex = -1;
189                 _currentResultset = (NpgsqlResultSet)_resultsets[_resultsetIndex];
190                 return true;
191             }
192             else
193                 return false;
194
195         }
196
197         /// <summary>
198         /// Advances the data reader to the next row.
199         /// </summary>
200         /// <returns>True if the reader was advanced, otherwise false.</returns>
201         public Boolean Read()
202         {
203             NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "Read");
204
205             CheckHaveResultSet();
206
207             if (_rowIndex < _currentResultset.Count) {
208                 _rowIndex++;
209                 return (_rowIndex < _currentResultset.Count);
210             } else {
211                 return false;
212             }
213         }
214
215         /// <summary>
216         /// Returns a System.Data.DataTable that describes the column metadata of the DataReader.
217         /// </summary>
218         public DataTable GetSchemaTable()
219         {
220             NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "GetSchemaTable");
221
222             if(_currentResultsetSchema == null)
223                 _currentResultsetSchema = GetResultsetSchema();
224
225             return _currentResultsetSchema;
226         }
227
228         /// <summary>
229         /// Gets the number of columns in the current row.
230         /// </summary>
231         public Int32 FieldCount
232         {
233             get
234             {
235
236                 NpgsqlEventLog.LogPropertyGet(LogLevel.Debug, CLASSNAME, "FieldCount");
237
238                 if (! HaveResultSet()) //Executed a non return rows query.
239                     return -1;
240                 else
241                     return _currentResultset.RowDescription.NumFields;
242
243
244             }
245
246         }
247
248         /// <summary>
249         /// Return the column name of the column at index <param name="Index"></param>.
250         /// </summary>
251         public String GetName(Int32 Index)
252         {
253             NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "GetName");
254
255             if (! HaveResultSet())
256                 return String.Empty;
257             else
258                 return _currentResultset.RowDescription[Index].name;
259         }
260
261         /// <summary>
262         /// Return the data type name of the column at index <param name="Index"></param>.
263         /// </summary>
264         public String GetDataTypeName(Int32 Index)
265         {
266             // FIXME: have a type name instead of the oid
267             if (! HaveResultSet())
268                 return String.Empty;
269             else
270                 return (_currentResultset.RowDescription[Index].type_oid).ToString();
271         }
272
273         /// <summary>
274         /// Return the data type of the column at index <param name="Index"></param>.
275         /// </summary>
276         public Type GetFieldType(Int32 Index)
277         {
278             NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "GetFieldType");
279
280
281             if (! HaveResultSet())
282                 return null;
283             else
284                 return NpgsqlTypesHelper.GetSystemTypeFromTypeOid(_connection.OidToNameMapping, _currentResultset.RowDescription[Index].type_oid);
285         }
286
287         /// <summary>
288         /// Return the value of the column at index <param name="Index"></param>.
289         /// </summary>
290         public Object GetValue(Int32 Index)
291         {
292             NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "GetValue");
293
294             if (Index < 0 || Index >= _currentResultset.RowDescription.NumFields) {
295                 throw new IndexOutOfRangeException("Column index out of range");
296             }
297
298             CheckHaveRow();
299
300             return ((NpgsqlAsciiRow)_currentResultset[_rowIndex])[Index];
301         }
302
303         /// <summary>
304         /// Copy values from each column in the current row into <param name="Values"></param>.
305         /// </summary>
306         /// <returns>The number of column values copied.</returns>
307         public Int32 GetValues(Object[] Values)
308         {
309             NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "GetValues");
310
311             CheckHaveRow();
312
313             // Only the number of elements in the array are filled.
314             // It's also possible to pass an array with more that FieldCount elements.
315             Int32 maxColumnIndex = (Values.Length < FieldCount) ? Values.Length : FieldCount;
316
317             for (Int32 i = 0; i < maxColumnIndex; i++) {
318                 Values[i] = GetValue(i);
319             }
320
321             return maxColumnIndex;
322
323         }
324
325         /// <summary>
326         /// Return the column name of the column named <param name="Name"></param>.
327         /// </summary>
328         public Int32 GetOrdinal(String Name)
329         {
330             CheckHaveResultSet();
331             return _currentResultset.RowDescription.FieldIndex(Name);
332         }
333
334         /// <summary>
335         /// Gets the value of a column in its native format.
336         /// </summary>
337         public Object this [ Int32 i ]
338         {
339             get
340             {
341                 NpgsqlEventLog.LogIndexerGet(LogLevel.Debug, CLASSNAME, i);
342                 return GetValue(i);
343             }
344         }
345
346         /// <summary>
347         /// Gets the value of a column in its native format.
348         /// </summary>
349         public Object this [ String name ]
350         {
351             get
352             {
353                 NpgsqlEventLog.LogIndexerGet(LogLevel.Debug, CLASSNAME, name);
354                 return GetValue(_currentResultset.RowDescription.FieldIndex(name));
355             }
356         }
357
358         /// <summary>
359         /// Gets the value of a column converted to a Boolean.
360         /// </summary>
361         public Boolean GetBoolean(Int32 i)
362         {
363             // Should this be done using the GetValue directly and not by converting to String
364             // and parsing from there?
365             NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "GetBoolean");
366
367             return Convert.ToBoolean(GetValue(i));
368         }
369
370         /// <summary>
371         /// Gets the value of a column converted to a Byte.  Not implemented.
372         /// </summary>
373         public Byte GetByte(Int32 i)
374         {
375             throw new NotImplementedException();
376         }
377
378         /// <summary>
379         /// Gets raw data from a column.
380         /// </summary>
381         public Int64 GetBytes(Int32 i, Int64 fieldOffset, Byte[] buffer, Int32 bufferoffset, Int32 length)
382         {
383
384             Byte[] result;
385
386             result = (Byte[]) GetValue(i);
387
388             // [TODO] Implement blob support.
389             if (buffer != null)
390             {
391                 result.CopyTo(buffer, 0);
392             }
393
394             return result.Length;
395
396         }
397
398         /// <summary>
399         /// Gets the value of a column converted to a Char.  Not implemented.
400         /// </summary>
401         public Char GetChar(Int32 i)
402         {
403             throw new NotImplementedException();
404         }
405
406         /// <summary>
407         /// Gets raw data from a column.
408         /// </summary>
409         public Int64 GetChars(Int32 i, Int64 fieldoffset, Char[] buffer, Int32 bufferoffset, Int32 length)
410         {
411             String              str;
412
413             str = GetString(i);
414             if (buffer == null)
415                 return str.Length;
416
417             str.ToCharArray(bufferoffset, length).CopyTo(buffer, 0);
418             return buffer.GetLength(0);
419         }
420
421         /// <summary>
422         /// Gets the value of a column converted to a Guid.  Not implemented.
423         /// </summary>
424         public Guid GetGuid(Int32 i)
425         {
426             throw new NotImplementedException();
427         }
428
429         /// <summary>
430         /// Gets the value of a column converted to Int16.
431         /// </summary>
432         public Int16 GetInt16(Int32 i)
433         {
434             NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "GetInt16");
435
436             return Convert.ToInt16(GetValue(i));
437         }
438
439         /// <summary>
440         /// Gets the value of a column converted to Int32.
441         /// </summary>
442         public Int32 GetInt32(Int32 i)
443         {
444             NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "GetInt32");
445
446             return Convert.ToInt32(GetValue(i));
447         }
448
449         /// <summary>
450         /// Gets the value of a column converted to Int64.
451         /// </summary>
452         public Int64 GetInt64(Int32 i)
453         {
454             NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "GetInt64");
455
456             return Convert.ToInt64(GetValue(i));
457         }
458
459         /// <summary>
460         /// Gets the value of a column converted to Single.
461         /// </summary>
462         public Single GetFloat(Int32 i)
463         {
464             NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "GetFloat");
465
466             return Convert.ToSingle(GetValue(i));
467         }
468
469         /// <summary>
470         /// Gets the value of a column converted to Double.
471         /// </summary>
472         public Double GetDouble(Int32 i)
473         {
474             NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "GetDouble");
475
476             return Convert.ToDouble(GetValue(i));
477         }
478
479         /// <summary>
480         /// Gets the value of a column converted to a String.
481         /// </summary>
482         public String GetString(Int32 i)
483         {
484             NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "GetString");
485
486             return Convert.ToString(GetValue(i));
487         }
488
489         /// <summary>
490         /// Gets the value of a column converted to Decimal.
491         /// </summary>
492         public Decimal GetDecimal(Int32 i)
493         {
494             NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "GetDecimal");
495
496             return Convert.ToDecimal(GetValue(i));
497         }
498
499         /// <summary>
500         /// Gets the value of a column converted to a DateTime.
501         /// </summary>
502         public DateTime GetDateTime(Int32 i)
503         {
504             NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "GetDateTime");
505
506             return Convert.ToDateTime(GetValue(i));
507         }
508
509         /// <summary>
510         /// Not implemented.
511         /// </summary>
512         public IDataReader GetData(Int32 i)
513         {
514             throw new NotImplementedException();
515         }
516
517         /// <summary>
518         /// Report whether the value in a column is DBNull.
519         /// </summary>
520         public Boolean IsDBNull(Int32 i)
521         {
522             NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "IsDBNull");
523
524             return (GetValue(i) == DBNull.Value);
525         }
526
527         private DataTable GetResultsetSchema()
528         {
529
530             NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "GetResultsetSchema");
531             DataTable result = null;
532
533             NpgsqlRowDescription rd = _currentResultset.RowDescription;
534             Int16 numFields = rd.NumFields;
535             if(numFields > 0)
536             {
537                 result = new DataTable("SchemaTable");
538
539                 result.Columns.Add ("ColumnName", typeof (string));
540                 result.Columns.Add ("ColumnOrdinal", typeof (int));
541                 result.Columns.Add ("ColumnSize", typeof (int));
542                 result.Columns.Add ("NumericPrecision", typeof (int));
543                 result.Columns.Add ("NumericScale", typeof (int));
544                 result.Columns.Add ("IsUnique", typeof (bool));
545                 result.Columns.Add ("IsKey", typeof (bool));
546                 DataColumn dc = result.Columns["IsKey"];
547                 dc.AllowDBNull = true; // IsKey can have a DBNull
548                 result.Columns.Add ("BaseCatalogName", typeof (string));
549                 result.Columns.Add ("BaseColumnName", typeof (string));
550                 result.Columns.Add ("BaseSchemaName", typeof (string));
551                 result.Columns.Add ("BaseTableName", typeof (string));
552                 result.Columns.Add ("DataType", typeof(Type));
553                 result.Columns.Add ("AllowDBNull", typeof (bool));
554                 result.Columns.Add ("ProviderType", typeof (int));
555                 result.Columns.Add ("IsAliased", typeof (bool));
556                 result.Columns.Add ("IsExpression", typeof (bool));
557                 result.Columns.Add ("IsIdentity", typeof (bool));
558                 result.Columns.Add ("IsAutoIncrement", typeof (bool));
559                 result.Columns.Add ("IsRowVersion", typeof (bool));
560                 result.Columns.Add ("IsHidden", typeof (bool));
561                 result.Columns.Add ("IsLong", typeof (bool));
562                 result.Columns.Add ("IsReadOnly", typeof (bool));
563
564                 DataRow row;
565
566                 for (Int16 i = 0; i < numFields; i++)
567                 {
568                     row = result.NewRow();
569
570                     row["ColumnName"] = GetName(i);
571                     row["ColumnOrdinal"] = i + 1;
572                     row["ColumnSize"] = (int) rd[i].type_size;
573                     row["NumericPrecision"] = 0;
574                     row["NumericScale"] = 0;
575                     row["IsUnique"] = false;
576                     row["IsKey"] = DBNull.Value;
577                     row["BaseCatalogName"] = "";
578                     row["BaseColumnName"] = GetName(i);
579                     row["BaseSchemaName"] = "";
580                     row["BaseTableName"] = "";
581                     row["DataType"] = GetFieldType(i);
582                     row["AllowDBNull"] = false;
583                     row["ProviderType"] = (int) rd[i].type_oid;
584                     row["IsAliased"] = false;
585                     row["IsExpression"] = false;
586                     row["IsIdentity"] = false;
587                     row["IsAutoIncrement"] = false;
588                     row["IsRowVersion"] = false;
589                     row["IsHidden"] = false;
590                     row["IsLong"] = false;
591                     row["IsReadOnly"] = false;
592
593                     result.Rows.Add(row);
594                 }
595             }
596
597             return result;
598
599         }
600
601         IEnumerator IEnumerable.GetEnumerator ()
602         {
603             return new System.Data.Common.DbEnumerator (this);
604         }
605     }
606 }