Copied remotely
[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
30 using NpgsqlTypes;
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 sealed 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             {
82                 throw new InvalidOperationException("Cannot read data. No result set.");
83             }
84         }
85
86         private void CheckHaveRow()
87         {
88             CheckHaveResultSet();
89
90             if (_rowIndex < 0)
91             {
92                 throw new InvalidOperationException("DataReader positioned before beginning of result set. Did you call Read()?");
93             }
94             else if (_rowIndex >= _currentResultset.Count)
95             {
96                 throw new InvalidOperationException("DataReader positioned beyond end of result set.");
97             }
98         }
99
100
101         /// <summary>
102         /// Releases the resources used by the <see cref="Npgsql.NpgsqlCommand">NpgsqlCommand</see>.
103         /// </summary>
104         public void Dispose()
105         {
106             Dispose(true);
107         }
108
109         /// <summary>
110         /// Releases the resources used by the <see cref="Npgsql.NpgsqlCommand">NpgsqlCommand</see>.
111         /// </summary>
112         protected void Dispose (bool disposing)
113         {
114             NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "Dispose");
115             if (disposing)
116             {
117                 this.Close();
118             }
119         }
120
121         /// <summary>
122         /// Gets a value indicating the depth of nesting for the current row.  Always returns zero.
123         /// </summary>
124         public Int32 Depth
125         {
126             get
127             {
128                 NpgsqlEventLog.LogPropertyGet(LogLevel.Debug, CLASSNAME, "Depth");
129                 return 0;
130             }
131         }
132
133         /// <summary>
134         /// Gets a value indicating whether the data reader is closed.
135         /// </summary>
136         public Boolean IsClosed
137         {
138             get
139             {
140                 NpgsqlEventLog.LogPropertyGet(LogLevel.Debug, CLASSNAME, "IsClosed");
141                 return _isClosed;
142             }
143         }
144
145         /// <summary>
146         /// Gets the number of rows changed, inserted, or deleted by execution of the SQL statement.
147         /// </summary>
148         public Int32 RecordsAffected
149         {
150             get
151             {
152                 NpgsqlEventLog.LogPropertyGet(LogLevel.Debug, CLASSNAME, "RecordsAffected");
153
154                 if (HaveResultSet())
155                 {
156                     return -1;
157                 }
158
159                 String[] _returnStringTokens = ((String)_responses[_resultsetIndex]).Split(null);       // whitespace separator.
160
161                 try
162                 {
163                     return Int32.Parse(_returnStringTokens[_returnStringTokens.Length - 1]);
164                 }
165                 catch (FormatException)
166                 {
167                     return -1;
168                 }
169             }
170         }
171
172         /// <summary>
173         /// Indicates if NpgsqlDatareader has rows to be read.
174         /// </summary>
175
176         public Boolean HasRows
177         {
178             get
179             {
180                 return _currentResultset.Count > 0;
181             }
182
183         }
184
185         /// <summary>
186         /// Closes the data reader object.
187         /// </summary>
188         public void Close()
189         {
190             if ((_behavior & CommandBehavior.CloseConnection) == CommandBehavior.CloseConnection)
191             {
192                 _connection.Close();
193
194             }
195
196             _isClosed = true;
197         }
198
199         /// <summary>
200         /// Advances the data reader to the next result, when multiple result sets were returned by the PostgreSQL backend.
201         /// </summary>
202         /// <returns>True if the reader was advanced, otherwise false.</returns>
203         public Boolean NextResult()
204         {
205             NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "NextResult");
206
207             if((_resultsetIndex + 1) < _resultsets.Count)
208             {
209                 _resultsetIndex++;
210                 _rowIndex = -1;
211                 _currentResultset = (NpgsqlResultSet)_resultsets[_resultsetIndex];
212                 return true;
213             }
214             else
215                 return false;
216
217         }
218
219         /// <summary>
220         /// Advances the data reader to the next row.
221         /// </summary>
222         /// <returns>True if the reader was advanced, otherwise false.</returns>
223         public Boolean Read()
224         {
225             NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "Read");
226
227             CheckHaveResultSet();
228
229             if (_rowIndex < _currentResultset.Count)
230             {
231                 _rowIndex++;
232                 return (_rowIndex < _currentResultset.Count);
233             }
234             else
235             {
236                 return false;
237             }
238         }
239
240         /// <summary>
241         /// Returns a System.Data.DataTable that describes the column metadata of the DataReader.
242         /// </summary>
243         public DataTable GetSchemaTable()
244         {
245             NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "GetSchemaTable");
246
247             if(_currentResultsetSchema == null)
248                 _currentResultsetSchema = GetResultsetSchema();
249
250             return _currentResultsetSchema;
251         }
252
253         /// <summary>
254         /// Gets the number of columns in the current row.
255         /// </summary>
256         public Int32 FieldCount
257         {
258             get
259             {
260
261                 NpgsqlEventLog.LogPropertyGet(LogLevel.Debug, CLASSNAME, "FieldCount");
262
263                 if (! HaveResultSet()) //Executed a non return rows query.
264                     return -1;
265                 else
266                     return _currentResultset.RowDescription.NumFields;
267
268
269             }
270
271         }
272
273         /// <summary>
274         /// Return the column name of the column at index <param name="Index"></param>.
275         /// </summary>
276         public String GetName(Int32 Index)
277         {
278             NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "GetName");
279
280             CheckHaveResultSet();
281
282             return _currentResultset.RowDescription[Index].name;
283         }
284
285         /// <summary>
286         /// Return the data type OID of the column at index <param name="Index"></param>.
287         /// </summary>
288         public String GetDataTypeOID(Int32 Index)
289         {
290             NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "GetDataTypeName");
291
292             CheckHaveResultSet();
293
294             NpgsqlBackendTypeInfo  TI = GetTypeInfo(Index);
295
296             return _currentResultset.RowDescription[Index].type_oid.ToString();
297         }
298
299         /// <summary>
300         /// Return the data type name of the column at index <param name="Index"></param>.
301         /// </summary>
302         public String GetDataTypeName(Int32 Index)
303         {
304             NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "GetDataTypeName");
305
306             CheckHaveResultSet();
307
308             NpgsqlBackendTypeInfo  TI = GetTypeInfo(Index);
309
310             if (TI == null)
311             {
312                 return _currentResultset.RowDescription[Index].type_oid.ToString();
313             }
314             else
315             {
316                 return TI.Name;
317             }
318         }
319
320         /// <summary>
321         /// Return the data type of the column at index <param name="Index"></param>.
322         /// </summary>
323         public Type GetFieldType(Int32 Index)
324         {
325             NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "GetFieldType");
326
327             CheckHaveResultSet();
328
329             NpgsqlBackendTypeInfo  TI = GetTypeInfo(Index);
330
331             if (TI == null)
332             {
333                 return typeof(String);  //Default type is string.
334             }
335             else
336             {
337                 return TI.Type;
338             }
339         }
340
341         /// <summary>
342         /// Return the data DbType of the column at index <param name="Index"></param>.
343         /// </summary>
344         public DbType GetFieldDbType(Int32 Index)
345         {
346             NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "GetFieldType");
347
348             CheckHaveResultSet();
349
350             NpgsqlBackendTypeInfo  TI = GetTypeInfo(Index);
351
352             if (TI == null)
353             {
354                 return DbType.String;
355             }
356             else
357             {
358                 //return TI.DBType;
359                 return DbType.String;
360             }
361         }
362
363         /// <summary>
364         /// Return the data NpgsqlDbType of the column at index <param name="Index"></param>.
365         /// </summary>
366         public NpgsqlDbType GetFieldNpgsqlDbType(Int32 Index)
367         {
368             NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "GetFieldType");
369
370             CheckHaveResultSet();
371
372             NpgsqlBackendTypeInfo  TI = GetTypeInfo(Index);
373
374             if (TI == null)
375             {
376                 return NpgsqlDbType.Text;
377             }
378             else
379             {
380                 return TI.NpgsqlDbType;
381
382             }
383         }
384
385
386         /// <summary>
387         /// Return the value of the column at index <param name="Index"></param>.
388         /// </summary>
389         public Object GetValue(Int32 Index)
390         {
391             NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "GetValue");
392
393             if (Index < 0 || Index >= _currentResultset.RowDescription.NumFields)
394             {
395                 throw new IndexOutOfRangeException("Column index out of range");
396             }
397
398             CheckHaveRow();
399
400             return ((NpgsqlAsciiRow)_currentResultset[_rowIndex])[Index];
401         }
402
403         /// <summary>
404         /// Copy values from each column in the current row into <param name="Values"></param>.
405         /// </summary>
406         /// <returns>The number of column values copied.</returns>
407         public Int32 GetValues(Object[] Values)
408         {
409             NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "GetValues");
410
411             CheckHaveRow();
412
413             // Only the number of elements in the array are filled.
414             // It's also possible to pass an array with more that FieldCount elements.
415             Int32 maxColumnIndex = (Values.Length < FieldCount) ? Values.Length : FieldCount;
416
417             for (Int32 i = 0; i < maxColumnIndex; i++)
418             {
419                 Values[i] = GetValue(i);
420             }
421
422             return maxColumnIndex;
423
424         }
425
426         /// <summary>
427         /// Return the column name of the column named <param name="Name"></param>.
428         /// </summary>
429         public Int32 GetOrdinal(String Name)
430         {
431             CheckHaveResultSet();
432             return _currentResultset.RowDescription.FieldIndex(Name);
433         }
434
435         /// <summary>
436         /// Gets the value of a column in its native format.
437         /// </summary>
438         public Object this [ Int32 i ]
439         {
440             get
441             {
442                 NpgsqlEventLog.LogIndexerGet(LogLevel.Debug, CLASSNAME, i);
443                 return GetValue(i);
444             }
445         }
446
447         /// <summary>
448         /// Gets the value of a column in its native format.
449         /// </summary>
450         public Object this [ String name ]
451         {
452             get
453             {
454                 NpgsqlEventLog.LogIndexerGet(LogLevel.Debug, CLASSNAME, name);
455                 return GetValue(_currentResultset.RowDescription.FieldIndex(name));
456             }
457         }
458
459         /// <summary>
460         /// Gets the value of a column converted to a Boolean.
461         /// </summary>
462         public Boolean GetBoolean(Int32 i)
463         {
464             // Should this be done using the GetValue directly and not by converting to String
465             // and parsing from there?
466             NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "GetBoolean");
467
468             return Convert.ToBoolean(GetValue(i));
469         }
470
471         /// <summary>
472         /// Gets the value of a column converted to a Byte.  Not implemented.
473         /// </summary>
474         public Byte GetByte(Int32 i)
475         {
476             throw new NotImplementedException();
477         }
478
479         /// <summary>
480         /// Gets raw data from a column.
481         /// </summary>
482         public Int64 GetBytes(Int32 i, Int64 fieldOffset, Byte[] buffer, Int32 bufferoffset, Int32 length)
483         {
484
485             Byte[] result;
486
487             result = (Byte[]) GetValue(i);
488
489             if (buffer == null)
490                 return result.Length;
491
492
493             // We just support read all the field for while. So, any fieldOffset value other than 0 will not read
494             // anything and return 0.
495
496             if (fieldOffset != 0)
497                 return 0;
498
499             // [TODO] Implement blob support.
500
501             result.CopyTo(buffer, 0);
502
503
504             return result.Length;
505
506         }
507
508         /// <summary>
509         /// Gets the value of a column converted to a Char.  Not implemented.
510         /// </summary>
511         public Char GetChar(Int32 i)
512         {
513             throw new NotImplementedException();
514         }
515
516         /// <summary>
517         /// Gets raw data from a column.
518         /// </summary>
519         public Int64 GetChars(Int32 i, Int64 fieldoffset, Char[] buffer, Int32 bufferoffset, Int32 length)
520         {
521             String              str;
522
523             str = GetString(i);
524             if (buffer == null)
525                 return str.Length;
526
527             str.ToCharArray(bufferoffset, length).CopyTo(buffer, 0);
528             return buffer.GetLength(0);
529         }
530
531         /// <summary>
532         /// Gets the value of a column converted to a Guid.  Not implemented.
533         /// </summary>
534         public Guid GetGuid(Int32 i)
535         {
536             throw new NotImplementedException();
537         }
538
539         /// <summary>
540         /// Gets the value of a column converted to Int16.
541         /// </summary>
542         public Int16 GetInt16(Int32 i)
543         {
544             NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "GetInt16");
545
546             return Convert.ToInt16(GetValue(i));
547         }
548
549         /// <summary>
550         /// Gets the value of a column converted to Int32.
551         /// </summary>
552         public Int32 GetInt32(Int32 i)
553         {
554             NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "GetInt32");
555
556             return Convert.ToInt32(GetValue(i));
557         }
558
559         /// <summary>
560         /// Gets the value of a column converted to Int64.
561         /// </summary>
562         public Int64 GetInt64(Int32 i)
563         {
564             NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "GetInt64");
565
566             return Convert.ToInt64(GetValue(i));
567         }
568
569         /// <summary>
570         /// Gets the value of a column converted to Single.
571         /// </summary>
572         public Single GetFloat(Int32 i)
573         {
574             NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "GetFloat");
575
576             return Convert.ToSingle(GetValue(i));
577         }
578
579         /// <summary>
580         /// Gets the value of a column converted to Double.
581         /// </summary>
582         public Double GetDouble(Int32 i)
583         {
584             NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "GetDouble");
585
586             return Convert.ToDouble(GetValue(i));
587         }
588
589         /// <summary>
590         /// Gets the value of a column converted to a String.
591         /// </summary>
592         public String GetString(Int32 i)
593         {
594             NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "GetString");
595
596             return Convert.ToString(GetValue(i));
597         }
598
599         /// <summary>
600         /// Gets the value of a column converted to Decimal.
601         /// </summary>
602         public Decimal GetDecimal(Int32 i)
603         {
604             NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "GetDecimal");
605
606             return Convert.ToDecimal(GetValue(i));
607         }
608
609         /// <summary>
610         /// Gets the value of a column converted to a DateTime.
611         /// </summary>
612         public DateTime GetDateTime(Int32 i)
613         {
614             NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "GetDateTime");
615
616             return Convert.ToDateTime(GetValue(i));
617         }
618
619         /// <summary>
620         /// Not implemented.
621         /// </summary>
622         public IDataReader GetData(Int32 i)
623         {
624             throw new NotImplementedException();
625         }
626
627         /// <summary>
628         /// Report whether the value in a column is DBNull.
629         /// </summary>
630         public Boolean IsDBNull(Int32 i)
631         {
632             NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "IsDBNull");
633
634             return (GetValue(i) == DBNull.Value);
635         }
636
637         internal NpgsqlBackendTypeInfo GetTypeInfo(Int32 FieldIndex)
638         {
639             return _currentResultset.RowDescription[FieldIndex].type_info;
640         }
641
642         private DataTable GetResultsetSchema()
643         {
644
645             NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "GetResultsetSchema");
646             DataTable result = null;
647
648             NpgsqlRowDescription rd = _currentResultset.RowDescription;
649             Int16 numFields = rd.NumFields;
650             if(numFields > 0)
651             {
652                 result = new DataTable("SchemaTable");
653
654                 result.Columns.Add ("ColumnName", typeof (string));
655                 result.Columns.Add ("ColumnOrdinal", typeof (int));
656                 result.Columns.Add ("ColumnSize", typeof (int));
657                 result.Columns.Add ("NumericPrecision", typeof (int));
658                 result.Columns.Add ("NumericScale", typeof (int));
659                 result.Columns.Add ("IsUnique", typeof (bool));
660                 result.Columns.Add ("IsKey", typeof (bool));
661                 DataColumn dc = result.Columns["IsKey"];
662                 dc.AllowDBNull = true; // IsKey can have a DBNull
663                 result.Columns.Add ("BaseCatalogName", typeof (string));
664                 result.Columns.Add ("BaseColumnName", typeof (string));
665                 result.Columns.Add ("BaseSchemaName", typeof (string));
666                 result.Columns.Add ("BaseTableName", typeof (string));
667                 result.Columns.Add ("DataType", typeof(Type));
668                 result.Columns.Add ("AllowDBNull", typeof (bool));
669                 result.Columns.Add ("ProviderType", typeof (int));
670                 result.Columns.Add ("IsAliased", typeof (bool));
671                 result.Columns.Add ("IsExpression", typeof (bool));
672                 result.Columns.Add ("IsIdentity", typeof (bool));
673                 result.Columns.Add ("IsAutoIncrement", typeof (bool));
674                 result.Columns.Add ("IsRowVersion", typeof (bool));
675                 result.Columns.Add ("IsHidden", typeof (bool));
676                 result.Columns.Add ("IsLong", typeof (bool));
677                 result.Columns.Add ("IsReadOnly", typeof (bool));
678
679                 DataRow row;
680
681                 for (Int16 i = 0; i < numFields; i++)
682                 {
683                     row = result.NewRow();
684
685                     row["ColumnName"] = GetName(i);
686                     row["ColumnOrdinal"] = i + 1;
687                     row["ColumnSize"] = (int) rd[i].type_size;
688                     row["NumericPrecision"] = 0;
689                     row["NumericScale"] = 0;
690                     row["IsUnique"] = false;
691                     row["IsKey"] = DBNull.Value;
692                     row["BaseCatalogName"] = "";
693                     row["BaseColumnName"] = GetName(i);
694                     row["BaseSchemaName"] = "";
695                     row["BaseTableName"] = "";
696                     row["DataType"] = GetFieldType(i);
697                     row["AllowDBNull"] = false;
698                     row["ProviderType"] = (int) rd[i].type_oid;
699                     row["IsAliased"] = false;
700                     row["IsExpression"] = false;
701                     row["IsIdentity"] = false;
702                     row["IsAutoIncrement"] = false;
703                     row["IsRowVersion"] = false;
704                     row["IsHidden"] = false;
705                     row["IsLong"] = false;
706                     row["IsReadOnly"] = false;
707
708                     result.Rows.Add(row);
709                 }
710             }
711
712             return result;
713
714         }
715
716         IEnumerator IEnumerable.GetEnumerator ()
717         {
718             return new System.Data.Common.DbEnumerator (this);
719         }
720     }
721 }