1 /********************************************************
\r
2 * ADO.NET 2.0 Data Provider for SQLite Version 3.X
\r
3 * Written by Robert Simpson (robert@blackcastlesoft.com)
\r
5 * Released to the public domain, use at your own risk!
\r
6 ********************************************************/
\r
8 namespace Mono.Data.Sqlite
\r
12 using System.Data.Common;
\r
13 using System.Collections.Generic;
\r
14 using System.Globalization;
\r
15 using System.Reflection;
\r
18 /// SQLite implementation of DbDataReader.
\r
20 public sealed class SqliteDataReader : DbDataReader
\r
23 /// Underlying command this reader is attached to
\r
25 private SqliteCommand _command;
\r
27 /// Index of the current statement in the command being processed
\r
29 private int _activeStatementIndex;
\r
31 /// Current statement being Read()
\r
33 private SqliteStatement _activeStatement;
\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
41 private int _readingState;
\r
43 /// Number of records affected by the insert/update statements executed on the command
\r
45 private int _rowsAffected;
\r
47 /// Count of fields (columns) in the row-returning statement currently being processed
\r
49 private int _fieldCount;
\r
51 /// Datatypes of active fields (columns) in the current statement, used for type-restricting data
\r
53 private SQLiteType[] _fieldTypeArray;
\r
56 /// The behavior of the datareader
\r
58 private CommandBehavior _commandBehavior;
\r
61 /// If set, then dispose of the command object when the reader is finished
\r
63 internal bool _disposeCommand;
\r
66 /// An array of rowid's for the active statement if CommandBehavior.KeyInfo is specified
\r
68 private SqliteKeyReader _keyInfo;
\r
70 internal long _version; // Matches the version of the connection
\r
73 /// Internal constructor, initializes the datareader and sets up to begin executing statements
\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
80 _version = _command.Connection._version;
\r
82 _commandBehavior = behave;
\r
83 _activeStatementIndex = -1;
\r
84 _activeStatement = null;
\r
88 if (_command != null)
\r
92 internal void Cancel()
\r
98 /// Closes the datareader, potentially closing the connection as well if CommandBehavior.CloseConnection was specified.
\r
100 public override void Close()
\r
104 if (_command != null)
\r
110 // Make sure we've not been canceled
\r
115 while (NextResult())
\r
123 _command.ClearDataReader();
\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
134 if (_disposeCommand)
\r
135 _command.Dispose();
\r
140 _activeStatement = null;
\r
141 _fieldTypeArray = null;
\r
145 if (_keyInfo != null)
\r
147 _keyInfo.Dispose();
\r
154 /// Throw an error if the datareader is closed
\r
156 private void CheckClosed()
\r
158 if (_command == null)
\r
159 throw new InvalidOperationException("DataReader has been closed");
\r
162 throw new SqliteException((int)SQLiteErrorCode.Abort, "Execution was aborted by the user");
\r
164 if (_command.Connection.State != ConnectionState.Open || _command.Connection._version != _version)
\r
165 throw new InvalidOperationException("Connection was closed, statement was terminated");
\r
169 /// Throw an error if a row is not loaded
\r
171 private void CheckValidRow()
\r
173 if (_readingState != 0)
\r
174 throw new InvalidOperationException("No current row");
\r
178 /// Enumerator support
\r
180 /// <returns>Returns a DbEnumerator object.</returns>
\r
181 public override global::System.Collections.IEnumerator GetEnumerator()
\r
183 return new DbEnumerator(this, ((_commandBehavior & CommandBehavior.CloseConnection) == CommandBehavior.CloseConnection));
\r
187 /// Not implemented. Returns 0
\r
189 public override int Depth
\r
199 /// Returns the number of columns in the current resultset
\r
201 public override int FieldCount
\r
206 if (_keyInfo == null)
\r
207 return _fieldCount;
\r
209 return _fieldCount + _keyInfo.Count;
\r
214 /// Returns the number of visible fielsd in the current resultset
\r
216 public override int VisibleFieldCount
\r
221 return _fieldCount;
\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
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
234 /// This function throws an InvalidTypeCast() exception if the requested type doesn't match the column's definition or affinity.
\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
242 TypeAffinity affinity = GetSQLiteType(i).Affinity;
\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
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
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
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
278 throw new InvalidCastException();
\r
282 /// Retrieves the column as a boolean value
\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
288 if (i >= VisibleFieldCount && _keyInfo != null)
\r
289 return _keyInfo.GetBoolean(i - VisibleFieldCount);
\r
291 VerifyType(i, DbType.Boolean);
\r
292 return Convert.ToBoolean(GetValue(i), CultureInfo.CurrentCulture);
\r
296 /// Retrieves the column as a single byte value
\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
302 if (i >= VisibleFieldCount && _keyInfo != null)
\r
303 return _keyInfo.GetByte(i - VisibleFieldCount);
\r
305 VerifyType(i, DbType.Byte);
\r
306 return Convert.ToByte(_activeStatement._sql.GetInt32(_activeStatement, i));
\r
310 /// Retrieves a column as an array of bytes (blob)
\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
319 /// To determine the number of bytes in the column, pass a null value for the buffer. The total length will be returned.
\r
321 public override long GetBytes(int i, long fieldOffset, byte[] buffer, int bufferoffset, int length)
\r
323 if (i >= VisibleFieldCount && _keyInfo != null)
\r
324 return _keyInfo.GetBytes(i - VisibleFieldCount, fieldOffset, buffer, bufferoffset, length);
\r
326 VerifyType(i, DbType.Binary);
\r
327 return _activeStatement._sql.GetBytes(_activeStatement, i, (int)fieldOffset, buffer, bufferoffset, length);
\r
331 /// Returns the column as a single character
\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
337 if (i >= VisibleFieldCount && _keyInfo != null)
\r
338 return _keyInfo.GetChar(i - VisibleFieldCount);
\r
340 VerifyType(i, DbType.SByte);
\r
341 return Convert.ToChar(_activeStatement._sql.GetInt32(_activeStatement, i));
\r
345 /// Retrieves a column as an array of chars (blob)
\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
354 /// To determine the number of characters in the column, pass a null value for the buffer. The total length will be returned.
\r
356 public override long GetChars(int i, long fieldoffset, char[] buffer, int bufferoffset, int length)
\r
358 if (i >= VisibleFieldCount && _keyInfo != null)
\r
359 return _keyInfo.GetChars(i - VisibleFieldCount, fieldoffset, buffer, bufferoffset, length);
\r
361 VerifyType(i, DbType.String);
\r
362 return _activeStatement._sql.GetChars(_activeStatement, i, (int)fieldoffset, buffer, bufferoffset, length);
\r
366 /// Retrieves the name of the back-end datatype of the column
\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
372 if (i >= VisibleFieldCount && _keyInfo != null)
\r
373 return _keyInfo.GetDataTypeName(i - VisibleFieldCount);
\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
381 /// Retrieve the column as a date/time value
\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
387 if (i >= VisibleFieldCount && _keyInfo != null)
\r
388 return _keyInfo.GetDateTime(i - VisibleFieldCount);
\r
390 VerifyType(i, DbType.DateTime);
\r
391 return _activeStatement._sql.GetDateTime(_activeStatement, i);
\r
395 /// Retrieve the column as a decimal value
\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
401 if (i >= VisibleFieldCount && _keyInfo != null)
\r
402 return _keyInfo.GetDecimal(i - VisibleFieldCount);
\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
409 /// Returns the column as a double
\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
415 if (i >= VisibleFieldCount && _keyInfo != null)
\r
416 return _keyInfo.GetDouble(i - VisibleFieldCount);
\r
418 VerifyType(i, DbType.Double);
\r
419 return _activeStatement._sql.GetDouble(_activeStatement, i);
\r
423 /// Returns the .NET type of a given column
\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
429 if (i >= VisibleFieldCount && _keyInfo != null)
\r
430 return _keyInfo.GetFieldType(i - VisibleFieldCount);
\r
432 return SqliteConvert.SQLiteTypeToType(GetSQLiteType(i));
\r
436 /// Returns a column as a float value
\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
442 if (i >= VisibleFieldCount && _keyInfo != null)
\r
443 return _keyInfo.GetFloat(i - VisibleFieldCount);
\r
445 VerifyType(i, DbType.Single);
\r
446 return Convert.ToSingle(_activeStatement._sql.GetDouble(_activeStatement, i));
\r
450 /// Returns the column as a Guid
\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
456 if (i >= VisibleFieldCount && _keyInfo != null)
\r
457 return _keyInfo.GetGuid(i - VisibleFieldCount);
\r
459 TypeAffinity affinity = VerifyType(i, DbType.Guid);
\r
460 if (affinity == TypeAffinity.Blob)
\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
467 return new Guid(_activeStatement._sql.GetText(_activeStatement, i));
\r
471 /// Returns the column as a short
\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
477 if (i >= VisibleFieldCount && _keyInfo != null)
\r
478 return _keyInfo.GetInt16(i - VisibleFieldCount);
\r
480 VerifyType(i, DbType.Int16);
\r
481 return Convert.ToInt16(_activeStatement._sql.GetInt32(_activeStatement, i));
\r
485 /// Retrieves the column as an int
\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
491 if (i >= VisibleFieldCount && _keyInfo != null)
\r
492 return _keyInfo.GetInt32(i - VisibleFieldCount);
\r
494 VerifyType(i, DbType.Int32);
\r
495 return _activeStatement._sql.GetInt32(_activeStatement, i);
\r
499 /// Retrieves the column as a long
\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
505 if (i >= VisibleFieldCount && _keyInfo != null)
\r
506 return _keyInfo.GetInt64(i - VisibleFieldCount);
\r
508 VerifyType(i, DbType.Int64);
\r
509 return _activeStatement._sql.GetInt64(_activeStatement, i);
\r
513 /// Retrieves the name of the column
\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
519 if (i >= VisibleFieldCount && _keyInfo != null)
\r
520 return _keyInfo.GetName(i - VisibleFieldCount);
\r
522 return _activeStatement._sql.ColumnName(_activeStatement, i);
\r
526 /// Retrieves the i of a column, given its name
\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
533 int r = _activeStatement._sql.ColumnIndex(_activeStatement, name);
\r
534 if (r == -1 && _keyInfo != null)
\r
536 r = _keyInfo.GetOrdinal(name);
\r
537 if (r > -1) r += VisibleFieldCount;
\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
547 /// <returns>Returns a DataTable containing the schema information for the active SELECT statement being processed.</returns>
\r
548 public override DataTable GetSchemaTable()
\r
550 return GetSchemaTable(true, false);
\r
553 internal DataTable GetSchemaTable(bool wantUniqueInfo, bool wantDefaultValue)
\r
557 DataTable tbl = new DataTable("SchemaTable");
\r
558 DataTable tblIndexes = null;
\r
559 DataTable tblIndexColumns;
\r
562 string strCatalog = "";
\r
563 string strTable = "";
\r
564 string strColumn = "";
\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
595 for (int n = 0; n < _fieldCount; n++)
\r
597 row = tbl.NewRow();
\r
599 DbType typ = GetSQLiteType(n).Type;
\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
619 strColumn = _command.Connection._sql.ColumnOriginalName(_activeStatement, n);
\r
620 if (String.IsNullOrEmpty(strColumn) == false) row[SchemaTableColumn.BaseColumnName] = strColumn;
\r
622 row[SchemaTableColumn.IsExpression] = String.IsNullOrEmpty(strColumn);
\r
623 row[SchemaTableColumn.IsAliased] = (String.Compare(GetName(n), strColumn, true, CultureInfo.InvariantCulture) != 0);
\r
625 temp = _command.Connection._sql.ColumnTableName(_activeStatement, n);
\r
626 if (String.IsNullOrEmpty(temp) == false) row[SchemaTableColumn.BaseTableName] = temp;
\r
628 temp = _command.Connection._sql.ColumnDatabaseName(_activeStatement, n);
\r
629 if (String.IsNullOrEmpty(temp) == false) row[SchemaTableOptionalColumn.BaseCatalogName] = temp;
\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
639 bool bAutoIncrement;
\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
647 out dataType, out collSeq, out bNotNull, out bPrimaryKey, out bAutoIncrement);
\r
649 if (bNotNull || bPrimaryKey) row[SchemaTableColumn.AllowDBNull] = false;
\r
651 row[SchemaTableColumn.IsKey] = bPrimaryKey;
\r
652 row[SchemaTableOptionalColumn.IsAutoIncrement] = bAutoIncrement;
\r
653 row["CollationType"] = collSeq;
\r
655 // For types like varchar(50) and such, extract the size
\r
656 arSize = dataType.Split('(');
\r
657 if (arSize.Length > 1)
\r
659 dataType = arSize[0];
\r
660 arSize = arSize[1].Split(')');
\r
661 if (arSize.Length > 1)
\r
663 arSize = arSize[0].Split(',', '.');
\r
664 if (GetSQLiteType(n).Type == DbType.String || GetSQLiteType(n).Type == DbType.Binary)
\r
666 row[SchemaTableColumn.ColumnSize] = Convert.ToInt32(arSize[0], CultureInfo.InvariantCulture);
\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
677 if (wantDefaultValue)
\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
686 // Find the matching column
\r
687 while (rdTable.Read())
\r
689 if (String.Compare((string)row[SchemaTableColumn.BaseColumnName], rdTable.GetString(1), true, CultureInfo.InvariantCulture) == 0)
\r
691 if (rdTable.IsDBNull(4) == false)
\r
692 row[SchemaTableOptionalColumn.DefaultValue] = rdTable[4];
\r
700 // Determine IsUnique properly, which is a pain in the butt!
\r
701 if (wantUniqueInfo)
\r
703 if ((string)row[SchemaTableOptionalColumn.BaseCatalogName] != strCatalog
\r
704 || (string)row[SchemaTableColumn.BaseTableName] != strTable)
\r
706 strCatalog = (string)row[SchemaTableOptionalColumn.BaseCatalogName];
\r
707 strTable = (string)row[SchemaTableColumn.BaseTableName];
\r
709 tblIndexes = _command.Connection.GetSchema("Indexes", new string[] {
\r
710 (string)row[SchemaTableOptionalColumn.BaseCatalogName],
\r
712 (string)row[SchemaTableColumn.BaseTableName],
\r
716 foreach (DataRow rowIndexes in tblIndexes.Rows)
\r
718 tblIndexColumns = _command.Connection.GetSchema("IndexColumns", new string[] {
\r
719 (string)row[SchemaTableOptionalColumn.BaseCatalogName],
\r
721 (string)row[SchemaTableColumn.BaseTableName],
\r
722 (string)rowIndexes["INDEX_NAME"],
\r
725 foreach (DataRow rowColumnIndex in tblIndexColumns.Rows)
\r
727 if (String.Compare((string)rowColumnIndex["COLUMN_NAME"], strColumn, true, CultureInfo.InvariantCulture) == 0)
\r
729 if (tblIndexColumns.Rows.Count == 1 && (bool)row[SchemaTableColumn.AllowDBNull] == false)
\r
730 row[SchemaTableColumn.IsUnique] = rowIndexes["UNIQUE"];
\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
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
740 // row[SchemaTableOptionalColumn.IsAutoIncrement] = true;
\r
749 if (String.IsNullOrEmpty(dataType))
\r
751 TypeAffinity affin;
\r
752 dataType = _activeStatement._sql.ColumnType(_activeStatement, n, out affin);
\r
755 if (String.IsNullOrEmpty(dataType) == false)
\r
756 row["DataTypeName"] = dataType;
\r
761 if (_keyInfo != null)
\r
762 _keyInfo.AppendSchemaTable(tbl);
\r
764 tbl.AcceptChanges();
\r
771 /// Retrieves the column as a string
\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
777 if (i >= VisibleFieldCount && _keyInfo != null)
\r
778 return _keyInfo.GetString(i - VisibleFieldCount);
\r
780 VerifyType(i, DbType.String);
\r
781 return _activeStatement._sql.GetText(_activeStatement, i);
\r
785 /// Retrieves the column as an object corresponding to the underlying datatype of the column
\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
791 if (i >= VisibleFieldCount && _keyInfo != null)
\r
792 return _keyInfo.GetValue(i - VisibleFieldCount);
\r
794 SQLiteType typ = GetSQLiteType(i);
\r
796 return _activeStatement._sql.GetValue(_activeStatement, i, typ);
\r
800 /// Retreives the values of multiple columns, up to the size of the supplied array
\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
806 int nMax = FieldCount;
\r
807 if (values.Length < nMax) nMax = values.Length;
\r
809 for (int n = 0; n < nMax; n++)
\r
811 values[n] = GetValue(n);
\r
818 /// Returns True if the resultset has rows that can be fetched
\r
820 public override bool HasRows
\r
825 return (_readingState != 1);
\r
830 /// Returns True if the data reader is closed
\r
832 public override bool IsClosed
\r
834 get { return (_command == null); }
\r
838 /// Returns True if the specified column is null
\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
844 if (i >= VisibleFieldCount && _keyInfo != null)
\r
845 return _keyInfo.IsDBNull(i - VisibleFieldCount);
\r
847 return _activeStatement._sql.IsNull(_activeStatement, i);
\r
851 /// Moves to the next resultset in multiple row-returning SQL command.
\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
858 SqliteStatement stmt = null;
\r
863 if (_activeStatement != null && stmt == null)
\r
865 // Reset the previously-executed statement
\r
866 _activeStatement._sql.Reset(_activeStatement);
\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
874 stmt = _command.GetStatement(_activeStatementIndex + 1);
\r
875 if (stmt == null) break;
\r
876 _activeStatementIndex++;
\r
878 stmt._sql.Step(stmt);
\r
879 if (stmt._sql.ColumnCount(stmt) == 0)
\r
881 if (_rowsAffected == -1) _rowsAffected = 0;
\r
882 _rowsAffected += stmt._sql.Changes;
\r
884 stmt._sql.Reset(stmt); // Gotta reset after every step to release any locks and such!
\r
890 // Get the next statement to execute
\r
891 stmt = _command.GetStatement(_activeStatementIndex + 1);
\r
893 // If we've reached the end of the statements, return false, no more resultsets
\r
897 // If we were on a current resultset, set the state to "done reading" for it
\r
898 if (_readingState < 1)
\r
901 _activeStatementIndex++;
\r
903 fieldCount = stmt._sql.ColumnCount(stmt);
\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
908 if (stmt._sql.Step(stmt))
\r
910 _readingState = -1;
\r
912 else if (fieldCount == 0) // No rows returned, if fieldCount is zero, skip to the next statement
\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
919 else // No rows, fieldCount is non-zero so stop here
\r
921 _readingState = 1; // This command returned columns but no rows, so return true, but HasRows = false and Read() returns false
\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
930 if ((_commandBehavior & CommandBehavior.KeyInfo) != 0)
\r
938 /// Retrieves the SQLiteType for a given column, and caches it to avoid repetetive interop calls.
\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
946 // Initialize the field types array if not already initialized
\r
947 if (_fieldTypeArray == null)
\r
948 _fieldTypeArray = new SQLiteType[VisibleFieldCount];
\r
950 // Initialize this column's field type instance
\r
951 if (_fieldTypeArray[i] == null) _fieldTypeArray[i] = new SQLiteType();
\r
953 typ = _fieldTypeArray[i];
\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
960 typ.Affinity = _activeStatement._sql.ColumnAffinity(_activeStatement, i);
\r
966 /// Reads the next row from the resultset
\r
968 /// <returns>True if a new row was successfully loaded and is ready for processing</returns>
\r
969 public override bool Read()
\r
973 if (_readingState == -1) // First step was already done at the NextResult() level, so don't step again, just return true.
\r
978 else if (_readingState == 0) // Actively reading rows
\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
983 if (_activeStatement._sql.Step(_activeStatement) == true)
\r
985 if (_keyInfo != null)
\r
992 _readingState = 1; // Finished reading rows
\r
999 /// Retrieve the count of records affected by an update/insert command. Only valid once the data reader is closed!
\r
1001 public override int RecordsAffected
\r
1003 get { return (_rowsAffected < 0) ? 0 : _rowsAffected; }
\r
1007 /// Indexer to retrieve data from a column given its name
\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
1013 get { return GetValue(GetOrdinal(name)); }
\r
1017 /// Indexer to retrieve data from a column given its i
\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
1023 get { return GetValue(i); }
\r
1026 private void LoadKeyInfo()
\r
1028 if (_keyInfo != null)
\r
1029 _keyInfo.Dispose();
\r
1031 _keyInfo = new SqliteKeyReader(_command.Connection, this, _activeStatement);
\r