2008-07-01 Atsushi Enomoto <atsushi@ximian.com>
[mono.git] / mcs / class / Mono.Data.Sqlite / Mono.Data.Sqlite / SqliteDataReader.cs
1 //
2 // Mono.Data.Sqlite.SqliteDataReader.cs
3 //
4 // Provides a means of reading a forward-only stream of rows from a Sqlite 
5 // database file.
6 //
7 // Author(s): Vladimir Vukicevic  <vladimir@pobox.com>
8 //            Everaldo Canuto  <everaldo_canuto@yahoo.com.br>
9 //            Joshua Tauberer <tauberer@for.net>
10 //
11 // Copyright (C) 2002  Vladimir Vukicevic
12 //
13 // Permission is hereby granted, free of charge, to any person obtaining
14 // a copy of this software and associated documentation files (the
15 // "Software"), to deal in the Software without restriction, including
16 // without limitation the rights to use, copy, modify, merge, publish,
17 // distribute, sublicense, and/or sell copies of the Software, and to
18 // permit persons to whom the Software is furnished to do so, subject to
19 // the following conditions:
20 // 
21 // The above copyright notice and this permission notice shall be
22 // included in all copies or substantial portions of the Software.
23 // 
24 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
25 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
26 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
27 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
28 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
29 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
30 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31 //
32 #if !NET_2_0
33 using System;
34 using System.Runtime.InteropServices;
35 using System.Collections;
36 using System.Data;
37 using System.Data.Common;
38
39 namespace Mono.Data.Sqlite
40 {
41         public class SqliteDataReader : MarshalByRefObject, IEnumerable, IDataReader, IDisposable, IDataRecord
42         {
43
44                 #region Fields
45                 
46                 private SqliteCommand command;
47                 private ArrayList rows;
48                 private string[] columns;
49                 private Hashtable column_names_sens, column_names_insens;
50                 private int current_row;
51                 private bool closed;
52                 private bool reading;
53                 private int records_affected;
54                 private string[] decltypes;
55                 
56                 #endregion
57
58                 #region Constructors and destructors
59                 
60                 internal SqliteDataReader (SqliteCommand cmd, IntPtr pVm, int version)
61                 {
62                         command = cmd;
63                         rows = new ArrayList ();
64                         column_names_sens = new Hashtable ();
65                         column_names_insens = new Hashtable (CaseInsensitiveHashCodeProvider.DefaultInvariant,
66                                                              CaseInsensitiveComparer.DefaultInvariant);
67                         closed = false;
68                         current_row = -1;
69                         reading = true;
70                         ReadpVm (pVm, version, cmd);
71                         ReadingDone ();
72                 }
73                 
74                 #endregion
75
76                 #region Properties              
77
78                 public int Depth {
79                         get { return 0; }
80                 }               
81
82                 public int FieldCount {
83                         get { return columns.Length; }
84                 }               
85
86                 public object this[string name] {
87                         get {
88                                 return GetValue (GetOrdinal (name));
89                         }
90                 }
91                 
92                 public object this[int i] {
93                         get { return GetValue (i); }
94                 }               
95
96                 public bool IsClosed {
97                         get { return closed; }
98                 }               
99
100                 public int RecordsAffected {
101                         get { return records_affected; }
102                 }
103                 
104                 #endregion
105
106                 #region Internal Methods
107                 
108                 internal void ReadpVm (IntPtr pVm, int version, SqliteCommand cmd)
109                 {
110                         int pN;
111                         IntPtr pazValue;
112                         IntPtr pazColName;
113                         bool first = true;
114                         
115                         int[] declmode = null;
116
117                         while (true) {
118                                 bool hasdata = cmd.ExecuteStatement(pVm, out pN, out pazValue, out pazColName);
119                         
120                                 // For the first row, get the column information
121                                 if (first) {
122                                         first = false;
123                                         
124                                         if (version == 3) {
125                                                 // A decltype might be null if the type is unknown to sqlite.
126                                                 decltypes = new string[pN];
127                                                 declmode = new int[pN]; // 1 == integer, 2 == datetime
128                                                 for (int i = 0; i < pN; i++) {
129                                                         IntPtr decl = Sqlite.sqlite3_column_decltype16 (pVm, i);
130                                                         if (decl != IntPtr.Zero) {
131                                                                 decltypes[i] = Marshal.PtrToStringUni (decl).ToLower(System.Globalization.CultureInfo.InvariantCulture);
132                                                                 if (decltypes[i] == "int" || decltypes[i] == "integer")
133                                                                         declmode[i] = 1;
134                                                                 else if (decltypes[i] == "date" || decltypes[i] == "datetime")
135                                                                         declmode[i] = 2;
136                                                         }
137                                                 }
138                                         }
139                                         
140                                         columns = new string[pN];       
141                                         for (int i = 0; i < pN; i++) {
142                                                 string colName;
143                                                 if (version == 2) {
144                                                         IntPtr fieldPtr = Marshal.ReadIntPtr (pazColName, i*IntPtr.Size);
145                                                         colName = Sqlite.HeapToString (fieldPtr, ((SqliteConnection)cmd.Connection).Encoding);
146                                                 } else {
147                                                         colName = Marshal.PtrToStringUni (Sqlite.sqlite3_column_name16 (pVm, i));
148                                                 }
149                                                 columns[i] = colName;
150                                                 column_names_sens [colName] = i;
151                                                 column_names_insens [colName] = i;
152                                         }
153                                 }
154
155                                 if (!hasdata) break;
156                                 
157                                 object[] data_row = new object [pN];
158                                 for (int i = 0; i < pN; i++) {
159                                         if (version == 2) {
160                                                 IntPtr fieldPtr = Marshal.ReadIntPtr (pazValue, i*IntPtr.Size);
161                                                 data_row[i] = Sqlite.HeapToString (fieldPtr, ((SqliteConnection)cmd.Connection).Encoding);
162                                         } else {
163                                                 switch (Sqlite.sqlite3_column_type (pVm, i)) {
164                                                         case 1:
165                                                                 long val = Sqlite.sqlite3_column_int64 (pVm, i);
166                                                         
167                                                                 // If the column was declared as an 'int' or 'integer', let's play
168                                                                 // nice and return an int (version 3 only).
169                                                                 if (declmode[i] == 1 && val >= int.MinValue && val <= int.MaxValue)
170                                                                         data_row[i] = (int)val;
171                                                                 
172                                                                 // Or if it was declared a date or datetime, do the reverse of what we
173                                                                 // do for DateTime parameters.
174                                                                 else if (declmode[i] == 2)
175                                                                         data_row[i] = DateTime.FromFileTime(val);
176                                                                 
177                                                                 else
178                                                                         data_row[i] = val;
179                                                                         
180                                                                 break;
181                                                         case 2:
182                                                                 data_row[i] = Sqlite.sqlite3_column_double (pVm, i);
183                                                                 break;
184                                                         case 3:
185                                                                 data_row[i] = Marshal.PtrToStringUni (Sqlite.sqlite3_column_text16 (pVm, i));
186                                                                 
187                                                                 // If the column was declared as a 'date' or 'datetime', let's play
188                                                                 // nice and return a DateTime (version 3 only).
189                                                                 if (declmode[i] == 2)
190                                                                         data_row[i] = DateTime.Parse((string)data_row[i]);
191                                                                 break;
192                                                         case 4:
193                                                                 int blobbytes = Sqlite.sqlite3_column_bytes16 (pVm, i);
194                                                                 IntPtr blobptr = Sqlite.sqlite3_column_blob (pVm, i);
195                                                                 byte[] blob = new byte[blobbytes];
196                                                                 Marshal.Copy (blobptr, blob, 0, blobbytes);
197                                                                 data_row[i] = blob;
198                                                                 break;
199                                                         case 5:
200                                                                 data_row[i] = null;
201                                                                 break;
202                                                         default:
203                                                                 throw new ApplicationException ("FATAL: Unknown sqlite3_column_type");
204                                                 }
205                                         }
206                                 }
207                                 
208                                 rows.Add (data_row);
209                         }
210                 }
211                 internal void ReadingDone ()
212                 {
213                         records_affected = command.NumChanges ();
214                         reading = false;
215                 }
216                 
217                 #endregion
218
219                 #region  Public Methods         
220
221                 public void Close ()
222                 {
223                         closed = true;
224                 }               
225
226                 public void Dispose ()
227                 {
228                         Close ();
229                 }
230
231                 IEnumerator IEnumerable.GetEnumerator () 
232                 {
233                         return new DbEnumerator (this);
234                 }
235                 
236                 public DataTable GetSchemaTable () 
237                 {
238                         DataTable dataTableSchema = new DataTable ();
239                         
240                         dataTableSchema.Columns.Add ("ColumnName", typeof (String));
241                         dataTableSchema.Columns.Add ("ColumnOrdinal", typeof (Int32));
242                         dataTableSchema.Columns.Add ("ColumnSize", typeof (Int32));
243                         dataTableSchema.Columns.Add ("NumericPrecision", typeof (Int32));
244                         dataTableSchema.Columns.Add ("NumericScale", typeof (Int32));
245                         dataTableSchema.Columns.Add ("IsUnique", typeof (Boolean));
246                         dataTableSchema.Columns.Add ("IsKey", typeof (Boolean));
247                         dataTableSchema.Columns.Add ("BaseCatalogName", typeof (String));
248                         dataTableSchema.Columns.Add ("BaseColumnName", typeof (String));
249                         dataTableSchema.Columns.Add ("BaseSchemaName", typeof (String));
250                         dataTableSchema.Columns.Add ("BaseTableName", typeof (String));
251                         dataTableSchema.Columns.Add ("DataType", typeof(Type));
252                         dataTableSchema.Columns.Add ("AllowDBNull", typeof (Boolean));
253                         dataTableSchema.Columns.Add ("ProviderType", typeof (Int32));
254                         dataTableSchema.Columns.Add ("IsAliased", typeof (Boolean));
255                         dataTableSchema.Columns.Add ("IsExpression", typeof (Boolean));
256                         dataTableSchema.Columns.Add ("IsIdentity", typeof (Boolean));
257                         dataTableSchema.Columns.Add ("IsAutoIncrement", typeof (Boolean));
258                         dataTableSchema.Columns.Add ("IsRowVersion", typeof (Boolean));
259                         dataTableSchema.Columns.Add ("IsHidden", typeof (Boolean));
260                         dataTableSchema.Columns.Add ("IsLong", typeof (Boolean));
261                         dataTableSchema.Columns.Add ("IsReadOnly", typeof (Boolean));
262                         
263                         dataTableSchema.BeginLoadData();
264                         for (int i = 0; i < this.FieldCount; i += 1 ) {
265                                 
266                                 DataRow schemaRow = dataTableSchema.NewRow ();
267                                 
268                                 schemaRow["ColumnName"] = columns[i];
269                                 schemaRow["ColumnOrdinal"] = i;
270                                 schemaRow["ColumnSize"] = 0;
271                                 schemaRow["NumericPrecision"] = 0;
272                                 schemaRow["NumericScale"] = 0;
273                                 schemaRow["IsUnique"] = false;
274                                 schemaRow["IsKey"] = false;
275                                 schemaRow["BaseCatalogName"] = "";
276                                 schemaRow["BaseColumnName"] = columns[i];
277                                 schemaRow["BaseSchemaName"] = "";
278                                 schemaRow["BaseTableName"] = "";
279                                 schemaRow["DataType"] = typeof(string);
280                                 schemaRow["AllowDBNull"] = true;
281                                 schemaRow["ProviderType"] = 0;
282                                 schemaRow["IsAliased"] = false;
283                                 schemaRow["IsExpression"] = false;
284                                 schemaRow["IsIdentity"] = false;
285                                 schemaRow["IsAutoIncrement"] = false;
286                                 schemaRow["IsRowVersion"] = false;
287                                 schemaRow["IsHidden"] = false;
288                                 schemaRow["IsLong"] = false;
289                                 schemaRow["IsReadOnly"] = false;
290                                 
291                                 dataTableSchema.Rows.Add (schemaRow);
292                                 schemaRow.AcceptChanges();
293                         }
294                         dataTableSchema.EndLoadData();
295                         
296                         return dataTableSchema;
297                 }
298                 
299                 public bool NextResult ()
300                 {
301                         current_row++;
302                         
303                         return (current_row < rows.Count);
304                 }
305                 
306                 public bool Read ()
307                 {
308                         return NextResult ();
309                 }
310
311                 #endregion
312                 
313                 #region IDataRecord getters
314                 
315                 public bool GetBoolean (int i)
316                 {
317                         return Convert.ToBoolean (((object[]) rows[current_row])[i]);
318                 }
319                 
320                 public byte GetByte (int i)
321                 {
322                         return Convert.ToByte (((object[]) rows[current_row])[i]);
323                 }
324                 
325                 public long GetBytes (int i, long fieldOffset, byte[] buffer, int bufferOffset, int length)
326                 {
327                         byte[] data = (byte[])(((object[]) rows[current_row])[i]);
328                         if (buffer != null)
329                                 Array.Copy (data, fieldOffset, buffer, bufferOffset, length);
330                         return data.LongLength - fieldOffset;
331                 }
332                 
333                 public char GetChar (int i)
334                 {
335                         return Convert.ToChar (((object[]) rows[current_row])[i]);
336                 }
337                 
338                 public long GetChars (int i, long fieldOffset, char[] buffer, int bufferOffset, int length)
339                 {
340                         char[] data = (char[])(((object[]) rows[current_row])[i]);
341                         if (buffer != null)
342                                 Array.Copy (data, fieldOffset, buffer, bufferOffset, length);
343                         return data.LongLength - fieldOffset;
344                 }
345                 
346                 public IDataReader GetData (int i)
347                 {
348                         return ((IDataReader) this [i]);
349                 }
350
351                 public string GetDataTypeName (int i)
352                 {
353                         if (decltypes != null && decltypes[i] != null)
354                                 return decltypes[i];
355                         return "text"; // SQL Lite data type
356                 }
357                 
358                 public DateTime GetDateTime (int i)
359                 {
360                         return Convert.ToDateTime (((object[]) rows[current_row])[i]);
361                 }
362                 
363                 public decimal GetDecimal (int i)
364                 {
365                         return Convert.ToDecimal (((object[]) rows[current_row])[i]);
366                 }
367                 
368                 public double GetDouble (int i)
369                 {
370                         return Convert.ToDouble (((object[]) rows[current_row])[i]);
371                 }
372                 
373                 public Type GetFieldType (int i)
374                 {
375                         int row = current_row;
376                         if (row == -1 && rows.Count == 0) return typeof(string);
377                         if (row == -1) row = 0;
378                         object element = ((object[]) rows[row])[i];
379                         if (element != null)
380                                 return element.GetType();
381                         else
382                                 return typeof (string);
383
384                         // Note that the return value isn't guaranteed to
385                         // be the same as the rows are read if different
386                         // types of information are stored in the column.
387                 }
388                 
389                 public float GetFloat (int i)
390                 {
391                         return Convert.ToSingle (((object[]) rows[current_row])[i]);
392                 }
393                 
394                 public Guid GetGuid (int i)
395                 {
396                         object value = GetValue (i);
397                         if (!(value is Guid)) {
398                                 if (value is DBNull)
399                                         throw new SqliteExecutionException ("Column value must not be null");
400                                 throw new InvalidCastException ("Type is " + value.GetType ().ToString ());
401                         }
402                         return ((Guid) value);
403                 }
404                 
405                 public short GetInt16 (int i)
406                 {
407                         return Convert.ToInt16 (((object[]) rows[current_row])[i]);
408                 }
409                 
410                 public int GetInt32 (int i)
411                 {
412                         return Convert.ToInt32 (((object[]) rows[current_row])[i]);
413                 }
414                 
415                 public long GetInt64 (int i)
416                 {
417                         return Convert.ToInt64 (((object[]) rows[current_row])[i]);
418                 }
419                 
420                 public string GetName (int i)
421                 {
422                         return columns[i];
423                 }
424                 
425                 public int GetOrdinal (string name)
426                 {
427                         object v = column_names_sens[name];
428                         if (v == null)
429                                 v = column_names_insens[name];
430                         if (v == null)
431                                 throw new ArgumentException("Column does not exist.");
432                         return (int) v;
433                 }
434                 
435                 public string GetString (int i)
436                 {
437                         return (((object[]) rows[current_row])[i]).ToString();
438                 }
439                 
440                 public object GetValue (int i)
441                 {
442                         return ((object[]) rows[current_row])[i];
443                 }
444                 
445                 public int GetValues (object[] values)
446                 {
447                         int num_to_fill = System.Math.Min (values.Length, columns.Length);
448                         for (int i = 0; i < num_to_fill; i++) {
449                                 if (((object[]) rows[current_row])[i] != null) {
450                                         values[i] = ((object[]) rows[current_row])[i];
451                                 } else {
452                                         values[i] = DBNull.Value;
453                                 }
454                         }
455                         return num_to_fill;
456                 }
457                 
458                 public bool IsDBNull (int i)
459                 {
460                         return (((object[]) rows[current_row])[i] == null);
461                 }
462
463                 #endregion
464         }
465 }
466 #endif