BindingFlags.Public needed here as Exception.HResult is now public in .NET 4.5. This...
[mono.git] / mcs / class / Mono.Data.Sqlite / Mono.Data.Sqlite_2.0 / SQLiteDataReader.cs
1 /********************************************************\r
2  * ADO.NET 2.0 Data Provider for SQLite Version 3.X\r
3  * Written by Robert Simpson (robert@blackcastlesoft.com)\r
4  * \r
5  * Released to the public domain, use at your own risk!\r
6  ********************************************************/\r
7 \r
8 namespace Mono.Data.Sqlite\r
9 {\r
10   using System;\r
11   using System.Data;\r
12   using System.Data.Common;\r
13   using System.Collections.Generic;\r
14   using System.Globalization;\r
15   using System.Reflection;\r
16 \r
17   /// <summary>\r
18   /// SQLite implementation of DbDataReader.\r
19   /// </summary>\r
20   public sealed class SqliteDataReader : DbDataReader\r
21   {\r
22     /// <summary>\r
23     /// Underlying command this reader is attached to\r
24     /// </summary>\r
25     private SqliteCommand _command;\r
26     /// <summary>\r
27     /// Index of the current statement in the command being processed\r
28     /// </summary>\r
29     private int _activeStatementIndex;\r
30     /// <summary>\r
31     /// Current statement being Read()\r
32     /// </summary>\r
33     private SqliteStatement _activeStatement;\r
34     /// <summary>\r
35     /// State of the current statement being processed.\r
36     /// -1 = First Step() executed, so the first Read() will be ignored\r
37     ///  0 = Actively reading\r
38     ///  1 = Finished reading\r
39     ///  2 = Non-row-returning statement, no records\r
40     /// </summary>\r
41     private int _readingState;\r
42     /// <summary>\r
43     /// Number of records affected by the insert/update statements executed on the command\r
44     /// </summary>\r
45     private int _rowsAffected;\r
46     /// <summary>\r
47     /// Count of fields (columns) in the row-returning statement currently being processed\r
48     /// </summary>\r
49     private int _fieldCount;\r
50     /// <summary>\r
51     /// Datatypes of active fields (columns) in the current statement, used for type-restricting data\r
52     /// </summary>\r
53     private SQLiteType[] _fieldTypeArray;\r
54 \r
55     /// <summary>\r
56     /// The behavior of the datareader\r
57     /// </summary>\r
58     private CommandBehavior _commandBehavior;\r
59 \r
60     /// <summary>\r
61     /// If set, then dispose of the command object when the reader is finished\r
62     /// </summary>\r
63     internal bool _disposeCommand;\r
64 \r
65     /// <summary>\r
66     /// An array of rowid's for the active statement if CommandBehavior.KeyInfo is specified\r
67     /// </summary>\r
68     private SqliteKeyReader _keyInfo;\r
69 \r
70     internal long _version; // Matches the version of the connection\r
71 \r
72     /// <summary>\r
73     /// Internal constructor, initializes the datareader and sets up to begin executing statements\r
74     /// </summary>\r
75     /// <param name="cmd">The SqliteCommand this data reader is for</param>\r
76     /// <param name="behave">The expected behavior of the data reader</param>\r
77     internal SqliteDataReader(SqliteCommand cmd, CommandBehavior behave)\r
78     {\r
79       _command = cmd;\r
80       _version = _command.Connection._version;\r
81 \r
82       _commandBehavior = behave;\r
83       _activeStatementIndex = -1;\r
84       _activeStatement = null;\r
85       _rowsAffected = -1;\r
86       _fieldCount = 0;\r
87 \r
88       if (_command != null)\r
89         NextResult();\r
90     }\r
91 \r
92     internal void Cancel()\r
93     {\r
94       _version = 0;\r
95     }\r
96 \r
97     /// <summary>\r
98     /// Closes the datareader, potentially closing the connection as well if CommandBehavior.CloseConnection was specified.\r
99     /// </summary>\r
100     public override void Close()\r
101     {\r
102       try\r
103       {\r
104         if (_command != null)\r
105         {\r
106           try\r
107           {\r
108             try\r
109             {\r
110               // Make sure we've not been canceled\r
111               if (_version != 0)\r
112               {\r
113                 try\r
114                 {\r
115                   while (NextResult())\r
116                   {\r
117                   }\r
118                 }\r
119                 catch\r
120                 {\r
121                 }\r
122               }\r
123               _command.ClearDataReader();\r
124             }\r
125             finally\r
126             {\r
127               // If the datareader's behavior includes closing the connection, then do so here.\r
128               if ((_commandBehavior & CommandBehavior.CloseConnection) != 0 && _command.Connection != null)\r
129                 _command.Connection.Close();\r
130             }\r
131           }\r
132           finally\r
133           {\r
134             if (_disposeCommand)\r
135               _command.Dispose();\r
136           }\r
137         }\r
138 \r
139         _command = null;\r
140         _activeStatement = null;\r
141         _fieldTypeArray = null;\r
142       }\r
143       finally\r
144       {\r
145         if (_keyInfo != null)\r
146         {\r
147           _keyInfo.Dispose();\r
148           _keyInfo = null;\r
149         }\r
150       }\r
151     }\r
152 \r
153     /// <summary>\r
154     /// Throw an error if the datareader is closed\r
155     /// </summary>\r
156     private void CheckClosed()\r
157     {\r
158       if (_command == null)\r
159         throw new InvalidOperationException("DataReader has been closed");\r
160 \r
161       if (_version == 0)\r
162         throw new SqliteException((int)SQLiteErrorCode.Abort, "Execution was aborted by the user");\r
163 \r
164       if (_command.Connection.State != ConnectionState.Open || _command.Connection._version != _version)\r
165         throw new InvalidOperationException("Connection was closed, statement was terminated");\r
166     }\r
167 \r
168     /// <summary>\r
169     /// Throw an error if a row is not loaded\r
170     /// </summary>\r
171     private void CheckValidRow()\r
172     {\r
173       if (_readingState != 0)\r
174         throw new InvalidOperationException("No current row");\r
175     }\r
176 \r
177     /// <summary>\r
178     /// Enumerator support\r
179     /// </summary>\r
180     /// <returns>Returns a DbEnumerator object.</returns>\r
181     public override global::System.Collections.IEnumerator GetEnumerator()\r
182     {\r
183       return new DbEnumerator(this, ((_commandBehavior & CommandBehavior.CloseConnection) == CommandBehavior.CloseConnection));\r
184     }\r
185 \r
186     /// <summary>\r
187     /// Not implemented.  Returns 0\r
188     /// </summary>\r
189     public override int Depth\r
190     {\r
191       get\r
192       {\r
193         CheckClosed();\r
194         return 0;\r
195       }\r
196     }\r
197 \r
198     /// <summary>\r
199     /// Returns the number of columns in the current resultset\r
200     /// </summary>\r
201     public override int FieldCount\r
202     {\r
203       get\r
204       {\r
205         CheckClosed();\r
206         if (_keyInfo == null)\r
207           return _fieldCount;\r
208 \r
209         return _fieldCount + _keyInfo.Count;\r
210       }\r
211     }\r
212 \r
213     /// <summary>\r
214     /// Returns the number of visible fielsd in the current resultset\r
215     /// </summary>\r
216     public override int VisibleFieldCount\r
217     {\r
218       get\r
219       {\r
220         CheckClosed();\r
221         return _fieldCount;\r
222       }\r
223     }\r
224 \r
225     /// <summary>\r
226     /// SQLite is inherently un-typed.  All datatypes in SQLite are natively strings.  The definition of the columns of a table\r
227     /// and the affinity of returned types are all we have to go on to type-restrict data in the reader.\r
228     /// \r
229     /// This function attempts to verify that the type of data being requested of a column matches the datatype of the column.  In\r
230     /// the case of columns that are not backed into a table definition, we attempt to match up the affinity of a column (int, double, string or blob)\r
231     /// to a set of known types that closely match that affinity.  It's not an exact science, but its the best we can do.\r
232     /// </summary>\r
233     /// <returns>\r
234     /// This function throws an InvalidTypeCast() exception if the requested type doesn't match the column's definition or affinity.\r
235     /// </returns>\r
236     /// <param name="i">The index of the column to type-check</param>\r
237     /// <param name="typ">The type we want to get out of the column</param>\r
238     private TypeAffinity VerifyType(int i, DbType typ)\r
239     {\r
240       CheckClosed();\r
241       CheckValidRow();\r
242       TypeAffinity affinity = GetSQLiteType(i).Affinity;\r
243 \r
244       switch (affinity)\r
245       {\r
246         case TypeAffinity.Int64:\r
247           if (typ == DbType.Int16) return affinity;\r
248           if (typ == DbType.Int32) return affinity;\r
249           if (typ == DbType.Int64) return affinity;\r
250           if (typ == DbType.Boolean) return affinity;\r
251           if (typ == DbType.Byte) return affinity;\r
252           if (typ == DbType.DateTime) return affinity;\r
253           if (typ == DbType.Single) return affinity;\r
254           if (typ == DbType.Double) return affinity;\r
255           if (typ == DbType.Decimal) return affinity;\r
256           break;\r
257         case TypeAffinity.Double:\r
258           if (typ == DbType.Single) return affinity;\r
259           if (typ == DbType.Double) return affinity;\r
260           if (typ == DbType.Decimal) return affinity;\r
261           if (typ == DbType.DateTime) return affinity;\r
262           break;\r
263         case TypeAffinity.Text:\r
264           if (typ == DbType.SByte) return affinity;\r
265           if (typ == DbType.String) return affinity;\r
266           if (typ == DbType.SByte) return affinity;\r
267           if (typ == DbType.Guid) return affinity;\r
268           if (typ == DbType.DateTime) return affinity;\r
269           if (typ == DbType.Decimal) return affinity;\r
270           break;\r
271         case TypeAffinity.Blob:\r
272           if (typ == DbType.Guid) return affinity;\r
273           if (typ == DbType.String) return affinity;\r
274           if (typ == DbType.Binary) return affinity;\r
275           break;\r
276       }\r
277 \r
278       throw new InvalidCastException();\r
279     }\r
280 \r
281     /// <summary>\r
282     /// Retrieves the column as a boolean value\r
283     /// </summary>\r
284     /// <param name="i">The index of the column to retrieve</param>\r
285     /// <returns>bool</returns>\r
286     public override bool GetBoolean(int i)\r
287     {\r
288       if (i >= VisibleFieldCount && _keyInfo != null)\r
289         return _keyInfo.GetBoolean(i - VisibleFieldCount);\r
290 \r
291       VerifyType(i, DbType.Boolean);\r
292       return Convert.ToBoolean(GetValue(i), CultureInfo.CurrentCulture);\r
293     }\r
294 \r
295     /// <summary>\r
296     /// Retrieves the column as a single byte value\r
297     /// </summary>\r
298     /// <param name="i">The index of the column to retrieve</param>\r
299     /// <returns>byte</returns>\r
300     public override byte GetByte(int i)\r
301     {\r
302       if (i >= VisibleFieldCount && _keyInfo != null)\r
303         return _keyInfo.GetByte(i - VisibleFieldCount);\r
304 \r
305       VerifyType(i, DbType.Byte);\r
306       return Convert.ToByte(_activeStatement._sql.GetInt32(_activeStatement, i));\r
307     }\r
308 \r
309     /// <summary>\r
310     /// Retrieves a column as an array of bytes (blob)\r
311     /// </summary>\r
312     /// <param name="i">The index of the column to retrieve</param>\r
313     /// <param name="fieldOffset">The zero-based index of where to begin reading the data</param>\r
314     /// <param name="buffer">The buffer to write the bytes into</param>\r
315     /// <param name="bufferoffset">The zero-based index of where to begin writing into the array</param>\r
316     /// <param name="length">The number of bytes to retrieve</param>\r
317     /// <returns>The actual number of bytes written into the array</returns>\r
318     /// <remarks>\r
319     /// To determine the number of bytes in the column, pass a null value for the buffer.  The total length will be returned.\r
320     /// </remarks>\r
321     public override long GetBytes(int i, long fieldOffset, byte[] buffer, int bufferoffset, int length)\r
322     {\r
323       if (i >= VisibleFieldCount && _keyInfo != null)\r
324         return _keyInfo.GetBytes(i - VisibleFieldCount, fieldOffset, buffer, bufferoffset, length);\r
325 \r
326       VerifyType(i, DbType.Binary);\r
327       return _activeStatement._sql.GetBytes(_activeStatement, i, (int)fieldOffset, buffer, bufferoffset, length);\r
328     }\r
329 \r
330     /// <summary>\r
331     /// Returns the column as a single character\r
332     /// </summary>\r
333     /// <param name="i">The index of the column to retrieve</param>\r
334     /// <returns>char</returns>\r
335     public override char GetChar(int i)\r
336     {\r
337       if (i >= VisibleFieldCount && _keyInfo != null)\r
338         return _keyInfo.GetChar(i - VisibleFieldCount);\r
339 \r
340       VerifyType(i, DbType.SByte);\r
341       return Convert.ToChar(_activeStatement._sql.GetInt32(_activeStatement, i));\r
342     }\r
343 \r
344     /// <summary>\r
345     /// Retrieves a column as an array of chars (blob)\r
346     /// </summary>\r
347     /// <param name="i">The index of the column to retrieve</param>\r
348     /// <param name="fieldoffset">The zero-based index of where to begin reading the data</param>\r
349     /// <param name="buffer">The buffer to write the characters into</param>\r
350     /// <param name="bufferoffset">The zero-based index of where to begin writing into the array</param>\r
351     /// <param name="length">The number of bytes to retrieve</param>\r
352     /// <returns>The actual number of characters written into the array</returns>\r
353     /// <remarks>\r
354     /// To determine the number of characters in the column, pass a null value for the buffer.  The total length will be returned.\r
355     /// </remarks>\r
356     public override long GetChars(int i, long fieldoffset, char[] buffer, int bufferoffset, int length)\r
357     {\r
358       if (i >= VisibleFieldCount && _keyInfo != null)\r
359         return _keyInfo.GetChars(i - VisibleFieldCount, fieldoffset, buffer, bufferoffset, length);\r
360 \r
361       VerifyType(i, DbType.String);\r
362       return _activeStatement._sql.GetChars(_activeStatement, i, (int)fieldoffset, buffer, bufferoffset, length);\r
363     }\r
364 \r
365     /// <summary>\r
366     /// Retrieves the name of the back-end datatype of the column\r
367     /// </summary>\r
368     /// <param name="i">The index of the column to retrieve</param>\r
369     /// <returns>string</returns>\r
370     public override string GetDataTypeName(int i)\r
371     {\r
372       if (i >= VisibleFieldCount && _keyInfo != null)\r
373         return _keyInfo.GetDataTypeName(i - VisibleFieldCount);\r
374 \r
375       SQLiteType typ = GetSQLiteType(i);\r
376       if (typ.Type == DbType.Object) return SqliteConvert.SQLiteTypeToType(typ).Name;\r
377       return _activeStatement._sql.ColumnType(_activeStatement, i, out typ.Affinity);\r
378     }\r
379 \r
380     /// <summary>\r
381     /// Retrieve the column as a date/time value\r
382     /// </summary>\r
383     /// <param name="i">The index of the column to retrieve</param>\r
384     /// <returns>DateTime</returns>\r
385     public override DateTime GetDateTime(int i)\r
386     {\r
387       if (i >= VisibleFieldCount && _keyInfo != null)\r
388         return _keyInfo.GetDateTime(i - VisibleFieldCount);\r
389 \r
390       VerifyType(i, DbType.DateTime);\r
391       return _activeStatement._sql.GetDateTime(_activeStatement, i);\r
392     }\r
393 \r
394     /// <summary>\r
395     /// Retrieve the column as a decimal value\r
396     /// </summary>\r
397     /// <param name="i">The index of the column to retrieve</param>\r
398     /// <returns>decimal</returns>\r
399     public override decimal GetDecimal(int i)\r
400     {\r
401       if (i >= VisibleFieldCount && _keyInfo != null)\r
402         return _keyInfo.GetDecimal(i - VisibleFieldCount);\r
403 \r
404       VerifyType(i, DbType.Decimal);\r
405       return Decimal.Parse(_activeStatement._sql.GetText(_activeStatement, i), NumberStyles.AllowDecimalPoint | NumberStyles.AllowExponent  | NumberStyles.AllowLeadingSign, CultureInfo.InvariantCulture);\r
406     }\r
407 \r
408     /// <summary>\r
409     /// Returns the column as a double\r
410     /// </summary>\r
411     /// <param name="i">The index of the column to retrieve</param>\r
412     /// <returns>double</returns>\r
413     public override double GetDouble(int i)\r
414     {\r
415       if (i >= VisibleFieldCount && _keyInfo != null)\r
416         return _keyInfo.GetDouble(i - VisibleFieldCount);\r
417 \r
418       VerifyType(i, DbType.Double);\r
419       return _activeStatement._sql.GetDouble(_activeStatement, i);\r
420     }\r
421 \r
422     /// <summary>\r
423     /// Returns the .NET type of a given column\r
424     /// </summary>\r
425     /// <param name="i">The index of the column to retrieve</param>\r
426     /// <returns>Type</returns>\r
427     public override Type GetFieldType(int i)\r
428     {\r
429       if (i >= VisibleFieldCount && _keyInfo != null)\r
430         return _keyInfo.GetFieldType(i - VisibleFieldCount);\r
431 \r
432       return SqliteConvert.SQLiteTypeToType(GetSQLiteType(i));\r
433     }\r
434 \r
435     /// <summary>\r
436     /// Returns a column as a float value\r
437     /// </summary>\r
438     /// <param name="i">The index of the column to retrieve</param>\r
439     /// <returns>float</returns>\r
440     public override float GetFloat(int i)\r
441     {\r
442       if (i >= VisibleFieldCount && _keyInfo != null)\r
443         return _keyInfo.GetFloat(i - VisibleFieldCount);\r
444 \r
445       VerifyType(i, DbType.Single);\r
446       return Convert.ToSingle(_activeStatement._sql.GetDouble(_activeStatement, i));\r
447     }\r
448 \r
449     /// <summary>\r
450     /// Returns the column as a Guid\r
451     /// </summary>\r
452     /// <param name="i">The index of the column to retrieve</param>\r
453     /// <returns>Guid</returns>\r
454     public override Guid GetGuid(int i)\r
455     {\r
456       if (i >= VisibleFieldCount && _keyInfo != null)\r
457         return _keyInfo.GetGuid(i - VisibleFieldCount);\r
458 \r
459       TypeAffinity affinity = VerifyType(i, DbType.Guid);\r
460       if (affinity == TypeAffinity.Blob)\r
461       {\r
462         byte[] buffer = new byte[16];\r
463         _activeStatement._sql.GetBytes(_activeStatement, i, 0, buffer, 0, 16);\r
464         return new Guid(buffer);\r
465       }\r
466       else\r
467         return new Guid(_activeStatement._sql.GetText(_activeStatement, i));\r
468     }\r
469 \r
470     /// <summary>\r
471     /// Returns the column as a short\r
472     /// </summary>\r
473     /// <param name="i">The index of the column to retrieve</param>\r
474     /// <returns>Int16</returns>\r
475     public override Int16 GetInt16(int i)\r
476     {\r
477       if (i >= VisibleFieldCount && _keyInfo != null)\r
478         return _keyInfo.GetInt16(i - VisibleFieldCount);\r
479 \r
480       VerifyType(i, DbType.Int16);\r
481       return Convert.ToInt16(_activeStatement._sql.GetInt32(_activeStatement, i));\r
482     }\r
483 \r
484     /// <summary>\r
485     /// Retrieves the column as an int\r
486     /// </summary>\r
487     /// <param name="i">The index of the column to retrieve</param>\r
488     /// <returns>Int32</returns>\r
489     public override Int32 GetInt32(int i)\r
490     {\r
491       if (i >= VisibleFieldCount && _keyInfo != null)\r
492         return _keyInfo.GetInt32(i - VisibleFieldCount);\r
493 \r
494       VerifyType(i, DbType.Int32);\r
495       return _activeStatement._sql.GetInt32(_activeStatement, i);\r
496     }\r
497 \r
498     /// <summary>\r
499     /// Retrieves the column as a long\r
500     /// </summary>\r
501     /// <param name="i">The index of the column to retrieve</param>\r
502     /// <returns>Int64</returns>\r
503     public override Int64 GetInt64(int i)\r
504     {\r
505       if (i >= VisibleFieldCount && _keyInfo != null)\r
506         return _keyInfo.GetInt64(i - VisibleFieldCount);\r
507 \r
508       VerifyType(i, DbType.Int64);\r
509       return _activeStatement._sql.GetInt64(_activeStatement, i);\r
510     }\r
511 \r
512     /// <summary>\r
513     /// Retrieves the name of the column\r
514     /// </summary>\r
515     /// <param name="i">The index of the column to retrieve</param>\r
516     /// <returns>string</returns>\r
517     public override string GetName(int i)\r
518     {\r
519       if (i >= VisibleFieldCount && _keyInfo != null)\r
520         return _keyInfo.GetName(i - VisibleFieldCount);\r
521 \r
522       return _activeStatement._sql.ColumnName(_activeStatement, i);\r
523     }\r
524 \r
525     /// <summary>\r
526     /// Retrieves the i of a column, given its name\r
527     /// </summary>\r
528     /// <param name="name">The name of the column to retrieve</param>\r
529     /// <returns>The int i of the column</returns>\r
530     public override int GetOrdinal(string name)\r
531     {\r
532       CheckClosed();\r
533       int r = _activeStatement._sql.ColumnIndex(_activeStatement, name);\r
534       if (r == -1 && _keyInfo != null)\r
535       {\r
536         r = _keyInfo.GetOrdinal(name);\r
537         if (r > -1) r += VisibleFieldCount;\r
538       }\r
539 \r
540       return r;\r
541     }\r
542 \r
543     /// <summary>\r
544     /// Schema information in SQLite is difficult to map into .NET conventions, so a lot of work must be done\r
545     /// to gather the necessary information so it can be represented in an ADO.NET manner.\r
546     /// </summary>\r
547     /// <returns>Returns a DataTable containing the schema information for the active SELECT statement being processed.</returns>\r
548     public override DataTable GetSchemaTable()\r
549     {\r
550       return GetSchemaTable(true, false);\r
551     }\r
552 \r
553     internal DataTable GetSchemaTable(bool wantUniqueInfo, bool wantDefaultValue)\r
554     {\r
555       CheckClosed();\r
556 \r
557       DataTable tbl = new DataTable("SchemaTable");\r
558       DataTable tblIndexes = null;\r
559       DataTable tblIndexColumns;\r
560       DataRow row;\r
561       string temp;\r
562       string strCatalog = "";\r
563       string strTable = "";\r
564       string strColumn = "";\r
565 \r
566       tbl.Locale = CultureInfo.InvariantCulture;\r
567       tbl.Columns.Add(SchemaTableColumn.ColumnName, typeof(String));\r
568       tbl.Columns.Add(SchemaTableColumn.ColumnOrdinal, typeof(int));\r
569       tbl.Columns.Add(SchemaTableColumn.ColumnSize, typeof(int));\r
570       tbl.Columns.Add(SchemaTableColumn.NumericPrecision, typeof(short));\r
571       tbl.Columns.Add(SchemaTableColumn.NumericScale, typeof(short));\r
572       tbl.Columns.Add(SchemaTableColumn.IsUnique, typeof(Boolean));\r
573       tbl.Columns.Add(SchemaTableColumn.IsKey, typeof(Boolean));\r
574       tbl.Columns.Add(SchemaTableOptionalColumn.BaseServerName, typeof(string));\r
575       tbl.Columns.Add(SchemaTableOptionalColumn.BaseCatalogName, typeof(String));\r
576       tbl.Columns.Add(SchemaTableColumn.BaseColumnName, typeof(String));\r
577       tbl.Columns.Add(SchemaTableColumn.BaseSchemaName, typeof(String));\r
578       tbl.Columns.Add(SchemaTableColumn.BaseTableName, typeof(String));\r
579       tbl.Columns.Add(SchemaTableColumn.DataType, typeof(Type));\r
580       tbl.Columns.Add(SchemaTableColumn.AllowDBNull, typeof(Boolean));\r
581       tbl.Columns.Add(SchemaTableColumn.ProviderType, typeof(int));\r
582       tbl.Columns.Add(SchemaTableColumn.IsAliased, typeof(Boolean));\r
583       tbl.Columns.Add(SchemaTableColumn.IsExpression, typeof(Boolean));\r
584       tbl.Columns.Add(SchemaTableOptionalColumn.IsAutoIncrement, typeof(Boolean));\r
585       tbl.Columns.Add(SchemaTableOptionalColumn.IsRowVersion, typeof(Boolean));\r
586       tbl.Columns.Add(SchemaTableOptionalColumn.IsHidden, typeof(Boolean));\r
587       tbl.Columns.Add(SchemaTableColumn.IsLong, typeof(Boolean));\r
588       tbl.Columns.Add(SchemaTableOptionalColumn.IsReadOnly, typeof(Boolean));\r
589       tbl.Columns.Add(SchemaTableOptionalColumn.ProviderSpecificDataType, typeof(Type));\r
590       tbl.Columns.Add(SchemaTableOptionalColumn.DefaultValue, typeof(object));\r
591       tbl.Columns.Add("DataTypeName", typeof(string));\r
592       tbl.Columns.Add("CollationType", typeof(string));\r
593       tbl.BeginLoadData();\r
594 \r
595       for (int n = 0; n < _fieldCount; n++)\r
596       {\r
597         row = tbl.NewRow();\r
598 \r
599         DbType typ = GetSQLiteType(n).Type;\r
600 \r
601         // Default settings for the column\r
602         row[SchemaTableColumn.ColumnName] = GetName(n);\r
603         row[SchemaTableColumn.ColumnOrdinal] = n;\r
604         row[SchemaTableColumn.ColumnSize] = SqliteConvert.DbTypeToColumnSize(typ);\r
605         row[SchemaTableColumn.NumericPrecision] = SqliteConvert.DbTypeToNumericPrecision(typ);\r
606         row[SchemaTableColumn.NumericScale] = SqliteConvert.DbTypeToNumericScale(typ);\r
607         row[SchemaTableColumn.ProviderType] = GetSQLiteType(n).Type;\r
608         row[SchemaTableColumn.IsLong] = false;\r
609         row[SchemaTableColumn.AllowDBNull] = true;\r
610         row[SchemaTableOptionalColumn.IsReadOnly] = false;\r
611         row[SchemaTableOptionalColumn.IsRowVersion] = false;\r
612         row[SchemaTableColumn.IsUnique] = false;\r
613         row[SchemaTableColumn.IsKey] = false;\r
614         row[SchemaTableOptionalColumn.IsAutoIncrement] = false;\r
615         row[SchemaTableColumn.DataType] = GetFieldType(n);\r
616         row[SchemaTableOptionalColumn.IsHidden] = false;\r
617 \r
618 #if !MONOTOUCH\r
619         strColumn = _command.Connection._sql.ColumnOriginalName(_activeStatement, n);\r
620         if (String.IsNullOrEmpty(strColumn) == false) row[SchemaTableColumn.BaseColumnName] = strColumn;\r
621 \r
622         row[SchemaTableColumn.IsExpression] = String.IsNullOrEmpty(strColumn);\r
623         row[SchemaTableColumn.IsAliased] = (String.Compare(GetName(n), strColumn, true, CultureInfo.InvariantCulture) != 0);\r
624 \r
625         temp = _command.Connection._sql.ColumnTableName(_activeStatement, n);\r
626         if (String.IsNullOrEmpty(temp) == false) row[SchemaTableColumn.BaseTableName] = temp;\r
627 \r
628         temp = _command.Connection._sql.ColumnDatabaseName(_activeStatement, n);\r
629         if (String.IsNullOrEmpty(temp) == false) row[SchemaTableOptionalColumn.BaseCatalogName] = temp;\r
630 #endif\r
631 \r
632         string dataType = null;\r
633         // If we have a table-bound column, extract the extra information from it\r
634         if (String.IsNullOrEmpty(strColumn) == false)\r
635         {\r
636           string collSeq;\r
637           bool bNotNull;\r
638           bool bPrimaryKey;\r
639           bool bAutoIncrement;\r
640           string[] arSize;\r
641 \r
642           // Get the column meta data\r
643           _command.Connection._sql.ColumnMetaData(\r
644             (string)row[SchemaTableOptionalColumn.BaseCatalogName],\r
645             (string)row[SchemaTableColumn.BaseTableName],\r
646             strColumn,\r
647             out dataType, out collSeq, out bNotNull, out bPrimaryKey, out bAutoIncrement);\r
648 \r
649           if (bNotNull || bPrimaryKey) row[SchemaTableColumn.AllowDBNull] = false;\r
650 \r
651           row[SchemaTableColumn.IsKey] = bPrimaryKey;\r
652           row[SchemaTableOptionalColumn.IsAutoIncrement] = bAutoIncrement;\r
653           row["CollationType"] = collSeq;\r
654 \r
655           // For types like varchar(50) and such, extract the size\r
656           arSize = dataType.Split('(');\r
657           if (arSize.Length > 1)\r
658           {\r
659             dataType = arSize[0];\r
660             arSize = arSize[1].Split(')');\r
661             if (arSize.Length > 1)\r
662             {\r
663               arSize = arSize[0].Split(',', '.');\r
664               if (GetSQLiteType(n).Type == DbType.String || GetSQLiteType(n).Type == DbType.Binary)\r
665               {\r
666                 row[SchemaTableColumn.ColumnSize] = Convert.ToInt32(arSize[0], CultureInfo.InvariantCulture);\r
667               }\r
668               else\r
669               {\r
670                 row[SchemaTableColumn.NumericPrecision] = Convert.ToInt32(arSize[0], CultureInfo.InvariantCulture);\r
671                 if (arSize.Length > 1)\r
672                   row[SchemaTableColumn.NumericScale] = Convert.ToInt32(arSize[1], CultureInfo.InvariantCulture);\r
673               }\r
674             }\r
675           }\r
676 \r
677           if (wantDefaultValue)\r
678           {\r
679             // Determine the default value for the column, which sucks because we have to query the schema for each column\r
680             using (SqliteCommand cmdTable = new SqliteCommand(String.Format(CultureInfo.InvariantCulture, "PRAGMA [{0}].TABLE_INFO([{1}])",\r
681               row[SchemaTableOptionalColumn.BaseCatalogName],\r
682               row[SchemaTableColumn.BaseTableName]\r
683               ), _command.Connection))\r
684             using (DbDataReader rdTable = cmdTable.ExecuteReader())\r
685             {\r
686               // Find the matching column\r
687               while (rdTable.Read())\r
688               {\r
689                 if (String.Compare((string)row[SchemaTableColumn.BaseColumnName], rdTable.GetString(1), true, CultureInfo.InvariantCulture) == 0)\r
690                 {\r
691                   if (rdTable.IsDBNull(4) == false)\r
692                     row[SchemaTableOptionalColumn.DefaultValue] = rdTable[4];\r
693 \r
694                   break;\r
695                 }\r
696               }\r
697             }\r
698           }\r
699 \r
700           // Determine IsUnique properly, which is a pain in the butt!\r
701           if (wantUniqueInfo)\r
702           {\r
703             if ((string)row[SchemaTableOptionalColumn.BaseCatalogName] != strCatalog\r
704               || (string)row[SchemaTableColumn.BaseTableName] != strTable)\r
705             {\r
706               strCatalog = (string)row[SchemaTableOptionalColumn.BaseCatalogName];\r
707               strTable = (string)row[SchemaTableColumn.BaseTableName];\r
708 \r
709               tblIndexes = _command.Connection.GetSchema("Indexes", new string[] {\r
710                 (string)row[SchemaTableOptionalColumn.BaseCatalogName],\r
711                 null,\r
712                 (string)row[SchemaTableColumn.BaseTableName],\r
713                 null });\r
714             }\r
715 \r
716             foreach (DataRow rowIndexes in tblIndexes.Rows)\r
717             {\r
718               tblIndexColumns = _command.Connection.GetSchema("IndexColumns", new string[] {\r
719                 (string)row[SchemaTableOptionalColumn.BaseCatalogName],\r
720                 null,\r
721                 (string)row[SchemaTableColumn.BaseTableName],\r
722                 (string)rowIndexes["INDEX_NAME"],\r
723                 null\r
724                 });\r
725               foreach (DataRow rowColumnIndex in tblIndexColumns.Rows)\r
726               {\r
727                 if (String.Compare((string)rowColumnIndex["COLUMN_NAME"], strColumn, true, CultureInfo.InvariantCulture) == 0)\r
728                 {\r
729                   if (tblIndexColumns.Rows.Count == 1 && (bool)row[SchemaTableColumn.AllowDBNull] == false)\r
730                     row[SchemaTableColumn.IsUnique] = rowIndexes["UNIQUE"];\r
731 \r
732                   // If its an integer primary key and the only primary key in the table, then its a rowid alias and is autoincrement\r
733                   // NOTE:  Currently commented out because this is not always the desired behavior.  For example, a 1:1 relationship with\r
734                   //        another table, where the other table is autoincrement, but this one is not, and uses the rowid from the other.\r
735                   //        It is safer to only set Autoincrement on tables where we're SURE the user specified AUTOINCREMENT, even if its a rowid column.\r
736 \r
737                   if (tblIndexColumns.Rows.Count == 1 && (bool)rowIndexes["PRIMARY_KEY"] == true && String.IsNullOrEmpty(dataType) == false &&\r
738                     String.Compare(dataType, "integer", true, CultureInfo.InvariantCulture) == 0)\r
739                   {\r
740                     //  row[SchemaTableOptionalColumn.IsAutoIncrement] = true;\r
741                   }\r
742 \r
743                   break;\r
744                 }\r
745               }\r
746             }\r
747           }\r
748 \r
749           if (String.IsNullOrEmpty(dataType))\r
750           {\r
751             TypeAffinity affin;\r
752             dataType = _activeStatement._sql.ColumnType(_activeStatement, n, out affin);\r
753           }\r
754 \r
755           if (String.IsNullOrEmpty(dataType) == false)\r
756             row["DataTypeName"] = dataType;\r
757         }\r
758         tbl.Rows.Add(row);\r
759       }\r
760 \r
761       if (_keyInfo != null)\r
762         _keyInfo.AppendSchemaTable(tbl);\r
763 \r
764       tbl.AcceptChanges();\r
765       tbl.EndLoadData();\r
766 \r
767       return tbl;\r
768     }\r
769 \r
770     /// <summary>\r
771     /// Retrieves the column as a string\r
772     /// </summary>\r
773     /// <param name="i">The index of the column to retrieve</param>\r
774     /// <returns>string</returns>\r
775     public override string GetString(int i)\r
776     {\r
777       if (i >= VisibleFieldCount && _keyInfo != null)\r
778         return _keyInfo.GetString(i - VisibleFieldCount);\r
779 \r
780       VerifyType(i, DbType.String);\r
781       return _activeStatement._sql.GetText(_activeStatement, i);\r
782     }\r
783 \r
784     /// <summary>\r
785     /// Retrieves the column as an object corresponding to the underlying datatype of the column\r
786     /// </summary>\r
787     /// <param name="i">The index of the column to retrieve</param>\r
788     /// <returns>object</returns>\r
789     public override object GetValue(int i)\r
790     {\r
791       if (i >= VisibleFieldCount && _keyInfo != null)\r
792         return _keyInfo.GetValue(i - VisibleFieldCount);\r
793 \r
794       SQLiteType typ = GetSQLiteType(i);\r
795 \r
796       return _activeStatement._sql.GetValue(_activeStatement, i, typ);\r
797     }\r
798 \r
799     /// <summary>\r
800     /// Retreives the values of multiple columns, up to the size of the supplied array\r
801     /// </summary>\r
802     /// <param name="values">The array to fill with values from the columns in the current resultset</param>\r
803     /// <returns>The number of columns retrieved</returns>\r
804     public override int GetValues(object[] values)\r
805     {\r
806       int nMax = FieldCount;\r
807       if (values.Length < nMax) nMax = values.Length;\r
808 \r
809       for (int n = 0; n < nMax; n++)\r
810       {\r
811         values[n] = GetValue(n);\r
812       }\r
813 \r
814       return nMax;\r
815     }\r
816 \r
817     /// <summary>\r
818     /// Returns True if the resultset has rows that can be fetched\r
819     /// </summary>\r
820     public override bool HasRows\r
821     {\r
822       get\r
823       {\r
824         CheckClosed();\r
825         return (_readingState != 1);\r
826       }\r
827     }\r
828 \r
829     /// <summary>\r
830     /// Returns True if the data reader is closed\r
831     /// </summary>\r
832     public override bool IsClosed\r
833     {\r
834       get { return (_command == null); }\r
835     }\r
836 \r
837     /// <summary>\r
838     /// Returns True if the specified column is null\r
839     /// </summary>\r
840     /// <param name="i">The index of the column to retrieve</param>\r
841     /// <returns>True or False</returns>\r
842     public override bool IsDBNull(int i)\r
843     {\r
844       if (i >= VisibleFieldCount && _keyInfo != null)\r
845         return _keyInfo.IsDBNull(i - VisibleFieldCount);\r
846 \r
847       return _activeStatement._sql.IsNull(_activeStatement, i);\r
848     }\r
849 \r
850     /// <summary>\r
851     /// Moves to the next resultset in multiple row-returning SQL command.\r
852     /// </summary>\r
853     /// <returns>True if the command was successful and a new resultset is available, False otherwise.</returns>\r
854     public override bool NextResult()\r
855     {\r
856       CheckClosed();\r
857 \r
858       SqliteStatement stmt = null;\r
859       int fieldCount;\r
860 \r
861       while (true)\r
862       {\r
863         if (_activeStatement != null && stmt == null)\r
864         {\r
865           // Reset the previously-executed statement\r
866           _activeStatement._sql.Reset(_activeStatement);\r
867 \r
868           // If we're only supposed to return a single rowset, step through all remaining statements once until\r
869           // they are all done and return false to indicate no more resultsets exist.\r
870           if ((_commandBehavior & CommandBehavior.SingleResult) != 0)\r
871           {\r
872             for (; ; )\r
873             {\r
874               stmt = _command.GetStatement(_activeStatementIndex + 1);\r
875               if (stmt == null) break;\r
876               _activeStatementIndex++;\r
877 \r
878               stmt._sql.Step(stmt);\r
879               if (stmt._sql.ColumnCount(stmt) == 0)\r
880               {\r
881                 if (_rowsAffected == -1) _rowsAffected = 0;\r
882                 _rowsAffected += stmt._sql.Changes;\r
883               }\r
884               stmt._sql.Reset(stmt); // Gotta reset after every step to release any locks and such!\r
885             }\r
886             return false;\r
887           }\r
888         }\r
889 \r
890         // Get the next statement to execute\r
891         stmt = _command.GetStatement(_activeStatementIndex + 1);\r
892 \r
893         // If we've reached the end of the statements, return false, no more resultsets\r
894         if (stmt == null)\r
895           return false;\r
896 \r
897         // If we were on a current resultset, set the state to "done reading" for it\r
898         if (_readingState < 1)\r
899           _readingState = 1;\r
900 \r
901         _activeStatementIndex++;\r
902 \r
903         fieldCount = stmt._sql.ColumnCount(stmt);\r
904 \r
905         // If the statement is not a select statement or we're not retrieving schema only, then perform the initial step\r
906         if ((_commandBehavior & CommandBehavior.SchemaOnly) == 0 || fieldCount == 0)\r
907         {\r
908           if (stmt._sql.Step(stmt))\r
909           {\r
910             _readingState = -1;\r
911           }\r
912           else if (fieldCount == 0) // No rows returned, if fieldCount is zero, skip to the next statement\r
913           {\r
914             if (_rowsAffected == -1) _rowsAffected = 0;\r
915             _rowsAffected += stmt._sql.Changes;\r
916             stmt._sql.Reset(stmt);\r
917             continue; // Skip this command and move to the next, it was not a row-returning resultset\r
918           }\r
919           else // No rows, fieldCount is non-zero so stop here\r
920           {\r
921             _readingState = 1; // This command returned columns but no rows, so return true, but HasRows = false and Read() returns false\r
922           }\r
923         }\r
924 \r
925         // Ahh, we found a row-returning resultset eligible to be returned!\r
926         _activeStatement = stmt;\r
927         _fieldCount = fieldCount;\r
928         _fieldTypeArray = null;\r
929 \r
930         if ((_commandBehavior & CommandBehavior.KeyInfo) != 0)\r
931           LoadKeyInfo();\r
932 \r
933         return true;\r
934       }\r
935     }\r
936 \r
937     /// <summary>\r
938     /// Retrieves the SQLiteType for a given column, and caches it to avoid repetetive interop calls.\r
939     /// </summary>\r
940     /// <param name="i">The index of the column to retrieve</param>\r
941     /// <returns>A SQLiteType structure</returns>\r
942     private SQLiteType GetSQLiteType(int i)\r
943     {\r
944       SQLiteType typ;\r
945 \r
946       // Initialize the field types array if not already initialized\r
947       if (_fieldTypeArray == null)\r
948         _fieldTypeArray = new SQLiteType[VisibleFieldCount];\r
949 \r
950       // Initialize this column's field type instance\r
951       if (_fieldTypeArray[i] == null) _fieldTypeArray[i] = new SQLiteType();\r
952 \r
953       typ = _fieldTypeArray[i];\r
954 \r
955       // If not initialized, then fetch the declared column datatype and attempt to convert it \r
956       // to a known DbType.\r
957       if (typ.Affinity == TypeAffinity.Uninitialized)\r
958         typ.Type = SqliteConvert.TypeNameToDbType(_activeStatement._sql.ColumnType(_activeStatement, i, out typ.Affinity));\r
959       else\r
960         typ.Affinity = _activeStatement._sql.ColumnAffinity(_activeStatement, i);\r
961 \r
962       return typ;\r
963     }\r
964 \r
965     /// <summary>\r
966     /// Reads the next row from the resultset\r
967     /// </summary>\r
968     /// <returns>True if a new row was successfully loaded and is ready for processing</returns>\r
969     public override bool Read()\r
970     {\r
971       CheckClosed();\r
972 \r
973       if (_readingState == -1) // First step was already done at the NextResult() level, so don't step again, just return true.\r
974       {\r
975         _readingState = 0;\r
976         return true;\r
977       }\r
978       else if (_readingState == 0) // Actively reading rows\r
979       {\r
980         // Don't read a new row if the command behavior dictates SingleRow.  We've already read the first row.\r
981         if ((_commandBehavior & CommandBehavior.SingleRow) == 0)\r
982         {\r
983           if (_activeStatement._sql.Step(_activeStatement) == true)\r
984           {\r
985             if (_keyInfo != null)\r
986               _keyInfo.Reset();\r
987 \r
988             return true;\r
989           }\r
990         }\r
991 \r
992         _readingState = 1; // Finished reading rows\r
993       }\r
994 \r
995       return false;\r
996     }\r
997 \r
998     /// <summary>\r
999     /// Retrieve the count of records affected by an update/insert command.  Only valid once the data reader is closed!\r
1000     /// </summary>\r
1001     public override int RecordsAffected\r
1002     {\r
1003       get { return (_rowsAffected < 0) ? 0 : _rowsAffected; }\r
1004     }\r
1005 \r
1006     /// <summary>\r
1007     /// Indexer to retrieve data from a column given its name\r
1008     /// </summary>\r
1009     /// <param name="name">The name of the column to retrieve data for</param>\r
1010     /// <returns>The value contained in the column</returns>\r
1011     public override object this[string name]\r
1012     {\r
1013       get { return GetValue(GetOrdinal(name)); }\r
1014     }\r
1015 \r
1016     /// <summary>\r
1017     /// Indexer to retrieve data from a column given its i\r
1018     /// </summary>\r
1019     /// <param name="i">The index of the column to retrieve</param>\r
1020     /// <returns>The value contained in the column</returns>\r
1021     public override object this[int i]\r
1022     {\r
1023       get { return GetValue(i); }\r
1024     }\r
1025 \r
1026     private void LoadKeyInfo()\r
1027     {\r
1028       if (_keyInfo != null)\r
1029         _keyInfo.Dispose();\r
1030 \r
1031       _keyInfo = new SqliteKeyReader(_command.Connection, this, _activeStatement);\r
1032     }\r
1033   }\r
1034 }\r