- Moved all classes to the Mono.Data.Sqlite namespace.
[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, 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         strColumn = _command.Connection._sql.ColumnOriginalName(_activeStatement, n);\r
619         if (String.IsNullOrEmpty(strColumn) == false) row[SchemaTableColumn.BaseColumnName] = strColumn;\r
620 \r
621         row[SchemaTableColumn.IsExpression] = String.IsNullOrEmpty(strColumn);\r
622         row[SchemaTableColumn.IsAliased] = (String.Compare(GetName(n), strColumn, true, CultureInfo.InvariantCulture) != 0);\r
623 \r
624         temp = _command.Connection._sql.ColumnTableName(_activeStatement, n);\r
625         if (String.IsNullOrEmpty(temp) == false) row[SchemaTableColumn.BaseTableName] = temp;\r
626 \r
627         temp = _command.Connection._sql.ColumnDatabaseName(_activeStatement, n);\r
628         if (String.IsNullOrEmpty(temp) == false) row[SchemaTableOptionalColumn.BaseCatalogName] = temp;\r
629 \r
630         string dataType = null;\r
631         // If we have a table-bound column, extract the extra information from it\r
632         if (String.IsNullOrEmpty(strColumn) == false)\r
633         {\r
634           string collSeq;\r
635           bool bNotNull;\r
636           bool bPrimaryKey;\r
637           bool bAutoIncrement;\r
638           string[] arSize;\r
639 \r
640           // Get the column meta data\r
641           _command.Connection._sql.ColumnMetaData(\r
642             (string)row[SchemaTableOptionalColumn.BaseCatalogName],\r
643             (string)row[SchemaTableColumn.BaseTableName],\r
644             strColumn,\r
645             out dataType, out collSeq, out bNotNull, out bPrimaryKey, out bAutoIncrement);\r
646 \r
647           if (bNotNull || bPrimaryKey) row[SchemaTableColumn.AllowDBNull] = false;\r
648 \r
649           row[SchemaTableColumn.IsKey] = bPrimaryKey;\r
650           row[SchemaTableOptionalColumn.IsAutoIncrement] = bAutoIncrement;\r
651           row["CollationType"] = collSeq;\r
652 \r
653           // For types like varchar(50) and such, extract the size\r
654           arSize = dataType.Split('(');\r
655           if (arSize.Length > 1)\r
656           {\r
657             dataType = arSize[0];\r
658             arSize = arSize[1].Split(')');\r
659             if (arSize.Length > 1)\r
660             {\r
661               arSize = arSize[0].Split(',', '.');\r
662               if (GetSQLiteType(n).Type == DbType.String || GetSQLiteType(n).Type == DbType.Binary)\r
663               {\r
664                 row[SchemaTableColumn.ColumnSize] = Convert.ToInt32(arSize[0], CultureInfo.InvariantCulture);\r
665               }\r
666               else\r
667               {\r
668                 row[SchemaTableColumn.NumericPrecision] = Convert.ToInt32(arSize[0], CultureInfo.InvariantCulture);\r
669                 if (arSize.Length > 1)\r
670                   row[SchemaTableColumn.NumericScale] = Convert.ToInt32(arSize[1], CultureInfo.InvariantCulture);\r
671               }\r
672             }\r
673           }\r
674 \r
675           if (wantDefaultValue)\r
676           {\r
677             // Determine the default value for the column, which sucks because we have to query the schema for each column\r
678             using (SQLiteCommand cmdTable = new SQLiteCommand(String.Format(CultureInfo.InvariantCulture, "PRAGMA [{0}].TABLE_INFO([{1}])",\r
679               row[SchemaTableOptionalColumn.BaseCatalogName],\r
680               row[SchemaTableColumn.BaseTableName]\r
681               ), _command.Connection))\r
682             using (DbDataReader rdTable = cmdTable.ExecuteReader())\r
683             {\r
684               // Find the matching column\r
685               while (rdTable.Read())\r
686               {\r
687                 if (String.Compare((string)row[SchemaTableColumn.BaseColumnName], rdTable.GetString(1), true, CultureInfo.InvariantCulture) == 0)\r
688                 {\r
689                   if (rdTable.IsDBNull(4) == false)\r
690                     row[SchemaTableOptionalColumn.DefaultValue] = rdTable[4];\r
691 \r
692                   break;\r
693                 }\r
694               }\r
695             }\r
696           }\r
697 \r
698           // Determine IsUnique properly, which is a pain in the butt!\r
699           if (wantUniqueInfo)\r
700           {\r
701             if ((string)row[SchemaTableOptionalColumn.BaseCatalogName] != strCatalog\r
702               || (string)row[SchemaTableColumn.BaseTableName] != strTable)\r
703             {\r
704               strCatalog = (string)row[SchemaTableOptionalColumn.BaseCatalogName];\r
705               strTable = (string)row[SchemaTableColumn.BaseTableName];\r
706 \r
707               tblIndexes = _command.Connection.GetSchema("Indexes", new string[] {\r
708                 (string)row[SchemaTableOptionalColumn.BaseCatalogName],\r
709                 null,\r
710                 (string)row[SchemaTableColumn.BaseTableName],\r
711                 null });\r
712             }\r
713 \r
714             foreach (DataRow rowIndexes in tblIndexes.Rows)\r
715             {\r
716               tblIndexColumns = _command.Connection.GetSchema("IndexColumns", new string[] {\r
717                 (string)row[SchemaTableOptionalColumn.BaseCatalogName],\r
718                 null,\r
719                 (string)row[SchemaTableColumn.BaseTableName],\r
720                 (string)rowIndexes["INDEX_NAME"],\r
721                 null\r
722                 });\r
723               foreach (DataRow rowColumnIndex in tblIndexColumns.Rows)\r
724               {\r
725                 if (String.Compare((string)rowColumnIndex["COLUMN_NAME"], strColumn, true, CultureInfo.InvariantCulture) == 0)\r
726                 {\r
727                   if (tblIndexColumns.Rows.Count == 1 && (bool)row[SchemaTableColumn.AllowDBNull] == false)\r
728                     row[SchemaTableColumn.IsUnique] = rowIndexes["UNIQUE"];\r
729 \r
730                   // If its an integer primary key and the only primary key in the table, then its a rowid alias and is autoincrement\r
731                   // NOTE:  Currently commented out because this is not always the desired behavior.  For example, a 1:1 relationship with\r
732                   //        another table, where the other table is autoincrement, but this one is not, and uses the rowid from the other.\r
733                   //        It is safer to only set Autoincrement on tables where we're SURE the user specified AUTOINCREMENT, even if its a rowid column.\r
734 \r
735                   if (tblIndexColumns.Rows.Count == 1 && (bool)rowIndexes["PRIMARY_KEY"] == true && String.IsNullOrEmpty(dataType) == false &&\r
736                     String.Compare(dataType, "integer", true, CultureInfo.InvariantCulture) == 0)\r
737                   {\r
738                     //  row[SchemaTableOptionalColumn.IsAutoIncrement] = true;\r
739                   }\r
740 \r
741                   break;\r
742                 }\r
743               }\r
744             }\r
745           }\r
746 \r
747           if (String.IsNullOrEmpty(dataType))\r
748           {\r
749             TypeAffinity affin;\r
750             dataType = _activeStatement._sql.ColumnType(_activeStatement, n, out affin);\r
751           }\r
752 \r
753           if (String.IsNullOrEmpty(dataType) == false)\r
754             row["DataTypeName"] = dataType;\r
755         }\r
756         tbl.Rows.Add(row);\r
757       }\r
758 \r
759       if (_keyInfo != null)\r
760         _keyInfo.AppendSchemaTable(tbl);\r
761 \r
762       tbl.AcceptChanges();\r
763       tbl.EndLoadData();\r
764 \r
765       return tbl;\r
766     }\r
767 \r
768     /// <summary>\r
769     /// Retrieves the column as a string\r
770     /// </summary>\r
771     /// <param name="i">The index of the column to retrieve</param>\r
772     /// <returns>string</returns>\r
773     public override string GetString(int i)\r
774     {\r
775       if (i >= VisibleFieldCount && _keyInfo != null)\r
776         return _keyInfo.GetString(i - VisibleFieldCount);\r
777 \r
778       VerifyType(i, DbType.String);\r
779       return _activeStatement._sql.GetText(_activeStatement, i);\r
780     }\r
781 \r
782     /// <summary>\r
783     /// Retrieves the column as an object corresponding to the underlying datatype of the column\r
784     /// </summary>\r
785     /// <param name="i">The index of the column to retrieve</param>\r
786     /// <returns>object</returns>\r
787     public override object GetValue(int i)\r
788     {\r
789       if (i >= VisibleFieldCount && _keyInfo != null)\r
790         return _keyInfo.GetValue(i - VisibleFieldCount);\r
791 \r
792       SQLiteType typ = GetSQLiteType(i);\r
793 \r
794       return _activeStatement._sql.GetValue(_activeStatement, i, typ);\r
795     }\r
796 \r
797     /// <summary>\r
798     /// Retreives the values of multiple columns, up to the size of the supplied array\r
799     /// </summary>\r
800     /// <param name="values">The array to fill with values from the columns in the current resultset</param>\r
801     /// <returns>The number of columns retrieved</returns>\r
802     public override int GetValues(object[] values)\r
803     {\r
804       int nMax = FieldCount;\r
805       if (values.Length < nMax) nMax = values.Length;\r
806 \r
807       for (int n = 0; n < nMax; n++)\r
808       {\r
809         values[n] = GetValue(n);\r
810       }\r
811 \r
812       return nMax;\r
813     }\r
814 \r
815     /// <summary>\r
816     /// Returns True if the resultset has rows that can be fetched\r
817     /// </summary>\r
818     public override bool HasRows\r
819     {\r
820       get\r
821       {\r
822         CheckClosed();\r
823         return (_readingState != 1);\r
824       }\r
825     }\r
826 \r
827     /// <summary>\r
828     /// Returns True if the data reader is closed\r
829     /// </summary>\r
830     public override bool IsClosed\r
831     {\r
832       get { return (_command == null); }\r
833     }\r
834 \r
835     /// <summary>\r
836     /// Returns True if the specified column is null\r
837     /// </summary>\r
838     /// <param name="i">The index of the column to retrieve</param>\r
839     /// <returns>True or False</returns>\r
840     public override bool IsDBNull(int i)\r
841     {\r
842       if (i >= VisibleFieldCount && _keyInfo != null)\r
843         return _keyInfo.IsDBNull(i - VisibleFieldCount);\r
844 \r
845       return _activeStatement._sql.IsNull(_activeStatement, i);\r
846     }\r
847 \r
848     /// <summary>\r
849     /// Moves to the next resultset in multiple row-returning SQL command.\r
850     /// </summary>\r
851     /// <returns>True if the command was successful and a new resultset is available, False otherwise.</returns>\r
852     public override bool NextResult()\r
853     {\r
854       CheckClosed();\r
855 \r
856       SQLiteStatement stmt = null;\r
857       int fieldCount;\r
858 \r
859       while (true)\r
860       {\r
861         if (_activeStatement != null && stmt == null)\r
862         {\r
863           // Reset the previously-executed statement\r
864           _activeStatement._sql.Reset(_activeStatement);\r
865 \r
866           // If we're only supposed to return a single rowset, step through all remaining statements once until\r
867           // they are all done and return false to indicate no more resultsets exist.\r
868           if ((_commandBehavior & CommandBehavior.SingleResult) != 0)\r
869           {\r
870             for (; ; )\r
871             {\r
872               stmt = _command.GetStatement(_activeStatementIndex + 1);\r
873               if (stmt == null) break;\r
874               _activeStatementIndex++;\r
875 \r
876               stmt._sql.Step(stmt);\r
877               if (stmt._sql.ColumnCount(stmt) == 0)\r
878               {\r
879                 if (_rowsAffected == -1) _rowsAffected = 0;\r
880                 _rowsAffected += stmt._sql.Changes;\r
881               }\r
882               stmt._sql.Reset(stmt); // Gotta reset after every step to release any locks and such!\r
883             }\r
884             return false;\r
885           }\r
886         }\r
887 \r
888         // Get the next statement to execute\r
889         stmt = _command.GetStatement(_activeStatementIndex + 1);\r
890 \r
891         // If we've reached the end of the statements, return false, no more resultsets\r
892         if (stmt == null)\r
893           return false;\r
894 \r
895         // If we were on a current resultset, set the state to "done reading" for it\r
896         if (_readingState < 1)\r
897           _readingState = 1;\r
898 \r
899         _activeStatementIndex++;\r
900 \r
901         fieldCount = stmt._sql.ColumnCount(stmt);\r
902 \r
903         // If the statement is not a select statement or we're not retrieving schema only, then perform the initial step\r
904         if ((_commandBehavior & CommandBehavior.SchemaOnly) == 0 || fieldCount == 0)\r
905         {\r
906           if (stmt._sql.Step(stmt))\r
907           {\r
908             _readingState = -1;\r
909           }\r
910           else if (fieldCount == 0) // No rows returned, if fieldCount is zero, skip to the next statement\r
911           {\r
912             if (_rowsAffected == -1) _rowsAffected = 0;\r
913             _rowsAffected += stmt._sql.Changes;\r
914             stmt._sql.Reset(stmt);\r
915             continue; // Skip this command and move to the next, it was not a row-returning resultset\r
916           }\r
917           else // No rows, fieldCount is non-zero so stop here\r
918           {\r
919             _readingState = 1; // This command returned columns but no rows, so return true, but HasRows = false and Read() returns false\r
920           }\r
921         }\r
922 \r
923         // Ahh, we found a row-returning resultset eligible to be returned!\r
924         _activeStatement = stmt;\r
925         _fieldCount = fieldCount;\r
926         _fieldTypeArray = null;\r
927 \r
928         if ((_commandBehavior & CommandBehavior.KeyInfo) != 0)\r
929           LoadKeyInfo();\r
930 \r
931         return true;\r
932       }\r
933     }\r
934 \r
935     /// <summary>\r
936     /// Retrieves the SQLiteType for a given column, and caches it to avoid repetetive interop calls.\r
937     /// </summary>\r
938     /// <param name="i">The index of the column to retrieve</param>\r
939     /// <returns>A SQLiteType structure</returns>\r
940     private SQLiteType GetSQLiteType(int i)\r
941     {\r
942       SQLiteType typ;\r
943 \r
944       // Initialize the field types array if not already initialized\r
945       if (_fieldTypeArray == null)\r
946         _fieldTypeArray = new SQLiteType[VisibleFieldCount];\r
947 \r
948       // Initialize this column's field type instance\r
949       if (_fieldTypeArray[i] == null) _fieldTypeArray[i] = new SQLiteType();\r
950 \r
951       typ = _fieldTypeArray[i];\r
952 \r
953       // If not initialized, then fetch the declared column datatype and attempt to convert it \r
954       // to a known DbType.\r
955       if (typ.Affinity == TypeAffinity.Uninitialized)\r
956         typ.Type = SQLiteConvert.TypeNameToDbType(_activeStatement._sql.ColumnType(_activeStatement, i, out typ.Affinity));\r
957       else\r
958         typ.Affinity = _activeStatement._sql.ColumnAffinity(_activeStatement, i);\r
959 \r
960       return typ;\r
961     }\r
962 \r
963     /// <summary>\r
964     /// Reads the next row from the resultset\r
965     /// </summary>\r
966     /// <returns>True if a new row was successfully loaded and is ready for processing</returns>\r
967     public override bool Read()\r
968     {\r
969       CheckClosed();\r
970 \r
971       if (_readingState == -1) // First step was already done at the NextResult() level, so don't step again, just return true.\r
972       {\r
973         _readingState = 0;\r
974         return true;\r
975       }\r
976       else if (_readingState == 0) // Actively reading rows\r
977       {\r
978         // Don't read a new row if the command behavior dictates SingleRow.  We've already read the first row.\r
979         if ((_commandBehavior & CommandBehavior.SingleRow) == 0)\r
980         {\r
981           if (_activeStatement._sql.Step(_activeStatement) == true)\r
982           {\r
983             if (_keyInfo != null)\r
984               _keyInfo.Reset();\r
985 \r
986             return true;\r
987           }\r
988         }\r
989 \r
990         _readingState = 1; // Finished reading rows\r
991       }\r
992 \r
993       return false;\r
994     }\r
995 \r
996     /// <summary>\r
997     /// Retrieve the count of records affected by an update/insert command.  Only valid once the data reader is closed!\r
998     /// </summary>\r
999     public override int RecordsAffected\r
1000     {\r
1001       get { return (_rowsAffected < 0) ? 0 : _rowsAffected; }\r
1002     }\r
1003 \r
1004     /// <summary>\r
1005     /// Indexer to retrieve data from a column given its name\r
1006     /// </summary>\r
1007     /// <param name="name">The name of the column to retrieve data for</param>\r
1008     /// <returns>The value contained in the column</returns>\r
1009     public override object this[string name]\r
1010     {\r
1011       get { return GetValue(GetOrdinal(name)); }\r
1012     }\r
1013 \r
1014     /// <summary>\r
1015     /// Indexer to retrieve data from a column given its i\r
1016     /// </summary>\r
1017     /// <param name="i">The index of the column to retrieve</param>\r
1018     /// <returns>The value contained in the column</returns>\r
1019     public override object this[int i]\r
1020     {\r
1021       get { return GetValue(i); }\r
1022     }\r
1023 \r
1024     private void LoadKeyInfo()\r
1025     {\r
1026       if (_keyInfo != null)\r
1027         _keyInfo.Dispose();\r
1028 \r
1029       _keyInfo = new SQLiteKeyReader(_command.Connection, this, _activeStatement);\r
1030     }\r
1031   }\r
1032 }\r