/********************************************************
* ADO.NET 2.0 Data Provider for SQLite Version 3.X
* Written by Robert Simpson (robert@blackcastlesoft.com)
*
* Released to the public domain, use at your own risk!
********************************************************/
namespace System.Data.SQLite
{
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Data;
using System.Data.Common;
using System.Globalization;
///
/// SQLite implementation of DbDataReader.
///
public sealed class SQLiteDataReader : DbDataReader
{
///
/// Underlying command this reader is attached to
///
private SQLiteCommand _command;
///
/// The flags pertaining to the associated connection (via the command).
///
private SQLiteConnectionFlags _flags;
///
/// Index of the current statement in the command being processed
///
private int _activeStatementIndex;
///
/// Current statement being Read()
///
private SQLiteStatement _activeStatement;
///
/// State of the current statement being processed.
/// -1 = First Step() executed, so the first Read() will be ignored
/// 0 = Actively reading
/// 1 = Finished reading
/// 2 = Non-row-returning statement, no records
///
private int _readingState;
///
/// Number of records affected by the insert/update statements executed on the command
///
private int _rowsAffected;
///
/// Count of fields (columns) in the row-returning statement currently being processed
///
private int _fieldCount;
///
/// The number of calls to Step() that have returned true (i.e. the number of rows that
/// have been read in the current result set).
///
private int _stepCount;
///
/// Maps the field (column) names to their corresponding indexes within the results.
///
private Dictionary _fieldIndexes;
///
/// Datatypes of active fields (columns) in the current statement, used for type-restricting data
///
private SQLiteType[] _fieldTypeArray;
///
/// The behavior of the datareader
///
private CommandBehavior _commandBehavior;
///
/// If set, then dispose of the command object when the reader is finished
///
internal bool _disposeCommand;
///
/// If set, then raise an exception when the object is accessed after being disposed.
///
internal bool _throwOnDisposed;
///
/// An array of rowid's for the active statement if CommandBehavior.KeyInfo is specified
///
private SQLiteKeyReader _keyInfo;
///
/// Matches the version of the connection.
///
internal int _version;
///
/// The "stub" (i.e. placeholder) base schema name to use when returning
/// column schema information. Matches the base schema name used by the
/// associated connection.
///
private string _baseSchemaName;
///
/// Internal constructor, initializes the datareader and sets up to begin executing statements
///
/// The SQLiteCommand this data reader is for
/// The expected behavior of the data reader
internal SQLiteDataReader(SQLiteCommand cmd, CommandBehavior behave)
{
_throwOnDisposed = true;
_command = cmd;
_version = _command.Connection._version;
_baseSchemaName = _command.Connection._baseSchemaName;
_commandBehavior = behave;
_activeStatementIndex = -1;
_rowsAffected = -1;
RefreshFlags();
SQLiteConnection.OnChanged(GetConnection(this),
new ConnectionEventArgs(SQLiteConnectionEventType.NewDataReader,
null, null, _command, this, null, null, new object[] { behave }));
if (_command != null)
NextResult();
}
///////////////////////////////////////////////////////////////////////////////////////////////
#region IDisposable "Pattern" Members
private bool disposed;
private void CheckDisposed() /* throw */
{
#if THROW_ON_DISPOSED
if (disposed && _throwOnDisposed)
throw new ObjectDisposedException(typeof(SQLiteDataReader).Name);
#endif
}
///////////////////////////////////////////////////////////////////////////////////////////////
///
/// Dispose of all resources used by this datareader.
///
///
protected override void Dispose(bool disposing)
{
SQLiteConnection.OnChanged(GetConnection(this),
new ConnectionEventArgs(SQLiteConnectionEventType.DisposingDataReader,
null, null, _command, this, null, null, new object[] { disposing,
disposed, _commandBehavior, _readingState, _rowsAffected, _stepCount,
_fieldCount, _disposeCommand, _throwOnDisposed }));
try
{
if (!disposed)
{
//if (disposing)
//{
// ////////////////////////////////////
// // dispose managed resources here...
// ////////////////////////////////////
//}
//////////////////////////////////////
// release unmanaged resources here...
//////////////////////////////////////
//
// NOTE: Fix for ticket [e1b2e0f769], do NOT throw exceptions
// while we are being disposed.
//
_throwOnDisposed = false;
}
}
finally
{
base.Dispose(disposing);
//
// NOTE: Everything should be fully disposed at this point.
//
disposed = true;
}
}
#endregion
///////////////////////////////////////////////////////////////////////////////////////////////
internal void Cancel()
{
_version = 0;
}
///
/// Closes the datareader, potentially closing the connection as well if CommandBehavior.CloseConnection was specified.
///
public override void Close()
{
CheckDisposed();
SQLiteConnection.OnChanged(GetConnection(this),
new ConnectionEventArgs(SQLiteConnectionEventType.ClosingDataReader,
null, null, _command, this, null, null, new object[] { _commandBehavior,
_readingState, _rowsAffected, _stepCount, _fieldCount, _disposeCommand,
_throwOnDisposed }));
try
{
if (_command != null)
{
try
{
try
{
// Make sure we've not been canceled
if (_version != 0)
{
try
{
while (NextResult())
{
}
}
catch(SQLiteException)
{
}
}
_command.ResetDataReader();
}
finally
{
// If the datareader's behavior includes closing the connection, then do so here.
if ((_commandBehavior & CommandBehavior.CloseConnection) != 0 && _command.Connection != null)
_command.Connection.Close();
}
}
finally
{
if (_disposeCommand)
_command.Dispose();
}
}
_command = null;
_activeStatement = null;
_fieldIndexes = null;
_fieldTypeArray = null;
}
finally
{
if (_keyInfo != null)
{
_keyInfo.Dispose();
_keyInfo = null;
}
}
}
///
/// Throw an error if the datareader is closed
///
private void CheckClosed()
{
if (!_throwOnDisposed)
return;
if (_command == null)
throw new InvalidOperationException("DataReader has been closed");
if (_version == 0)
throw new SQLiteException("Execution was aborted by the user");
SQLiteConnection connection = _command.Connection;
if (connection._version != _version || connection.State != ConnectionState.Open)
throw new InvalidOperationException("Connection was closed, statement was terminated");
}
///
/// Throw an error if a row is not loaded
///
private void CheckValidRow()
{
if (_readingState != 0)
throw new InvalidOperationException("No current row");
}
///
/// Enumerator support
///
/// Returns a DbEnumerator object.
public override Collections.IEnumerator GetEnumerator()
{
CheckDisposed();
return new DbEnumerator(this, ((_commandBehavior & CommandBehavior.CloseConnection) == CommandBehavior.CloseConnection));
}
///
/// Not implemented. Returns 0
///
public override int Depth
{
get
{
CheckDisposed();
CheckClosed();
return 0;
}
}
///
/// Returns the number of columns in the current resultset
///
public override int FieldCount
{
get
{
CheckDisposed();
CheckClosed();
if (_keyInfo == null)
return _fieldCount;
return _fieldCount + _keyInfo.Count;
}
}
///
/// Forces the connection flags cached by this data reader to be refreshed
/// from the underlying connection.
///
public void RefreshFlags()
{
CheckDisposed();
_flags = SQLiteCommand.GetFlags(_command);
}
///
/// Returns the number of rows seen so far in the current result set.
///
public int StepCount
{
get
{
CheckDisposed();
CheckClosed();
return _stepCount;
}
}
private int PrivateVisibleFieldCount
{
get { return _fieldCount; }
}
///
/// Returns the number of visible fields in the current resultset
///
public override int VisibleFieldCount
{
get
{
CheckDisposed();
CheckClosed();
return PrivateVisibleFieldCount;
}
}
///
/// This method is used to make sure the result set is open and a row is currently available.
///
private void VerifyForGet()
{
CheckClosed();
CheckValidRow();
}
///
/// SQLite is inherently un-typed. All datatypes in SQLite are natively strings. The definition of the columns of a table
/// and the affinity of returned types are all we have to go on to type-restrict data in the reader.
///
/// This function attempts to verify that the type of data being requested of a column matches the datatype of the column. In
/// 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)
/// to a set of known types that closely match that affinity. It's not an exact science, but its the best we can do.
///
///
/// This function throws an InvalidTypeCast() exception if the requested type doesn't match the column's definition or affinity.
///
/// The index of the column to type-check
/// The type we want to get out of the column
private TypeAffinity VerifyType(int i, DbType typ)
{
if ((_flags & SQLiteConnectionFlags.NoVerifyTypeAffinity) == SQLiteConnectionFlags.NoVerifyTypeAffinity)
return TypeAffinity.None;
TypeAffinity affinity = GetSQLiteType(_flags, i).Affinity;
switch (affinity)
{
case TypeAffinity.Int64:
if (typ == DbType.Int64) return affinity;
if (typ == DbType.Int32) return affinity;
if (typ == DbType.Int16) return affinity;
if (typ == DbType.Byte) return affinity;
if (typ == DbType.SByte) return affinity;
if (typ == DbType.Boolean) return affinity;
if (typ == DbType.DateTime) return affinity;
if (typ == DbType.Double) return affinity;
if (typ == DbType.Single) return affinity;
if (typ == DbType.Decimal) return affinity;
break;
case TypeAffinity.Double:
if (typ == DbType.Double) return affinity;
if (typ == DbType.Single) return affinity;
if (typ == DbType.Decimal) return affinity;
if (typ == DbType.DateTime) return affinity;
break;
case TypeAffinity.Text:
if (typ == DbType.String) return affinity;
if (typ == DbType.Guid) return affinity;
if (typ == DbType.DateTime) return affinity;
if (typ == DbType.Decimal) return affinity;
break;
case TypeAffinity.Blob:
if (typ == DbType.Guid) return affinity;
if (typ == DbType.Binary) return affinity;
if (typ == DbType.String) return affinity;
break;
}
throw new InvalidCastException();
}
///
/// Invokes the data reader value callback configured for the database
/// type name associated with the specified column. If no data reader
/// value callback is available for the database type name, do nothing.
///
///
/// The index of the column being read.
///
///
/// The extra event data to pass into the callback.
///
///
/// Non-zero if the default handling for the data reader call should be
/// skipped. If this is set to non-zero and the necessary return value
/// is unavailable or unsuitable, an exception will be thrown.
///
private void InvokeReadValueCallback(
int index,
SQLiteReadEventArgs eventArgs,
out bool complete
)
{
complete = false;
SQLiteConnectionFlags oldFlags = _flags;
_flags &= ~SQLiteConnectionFlags.UseConnectionReadValueCallbacks;
try
{
string typeName = GetDataTypeName(index);
if (typeName == null)
return;
SQLiteConnection connection = GetConnection(this);
if (connection == null)
return;
SQLiteTypeCallbacks callbacks;
if (!connection.TryGetTypeCallbacks(typeName, out callbacks) ||
(callbacks == null))
{
return;
}
SQLiteReadValueCallback callback = callbacks.ReadValueCallback;
if (callback == null)
return;
object userData = callbacks.ReadValueUserData;
callback(
_activeStatement._sql, this, oldFlags, eventArgs, typeName,
index, userData, out complete); /* throw */
}
finally
{
_flags |= SQLiteConnectionFlags.UseConnectionReadValueCallbacks;
}
}
///
/// Attempts to query the integer identifier for the current row. This
/// will not work for tables that were created WITHOUT ROWID -OR- if the
/// query does not include the "rowid" column or one of its aliases -OR-
/// if the was not created with the
/// flag.
///
///
/// The index of the BLOB column.
///
///
/// The integer identifier for the current row -OR- null if it could not
/// be determined.
///
internal long? GetRowId(
int i
)
{
// CheckDisposed();
VerifyForGet();
if (_keyInfo == null)
return null;
string databaseName = GetDatabaseName(i);
string tableName = GetTableName(i);
int iRowId = _keyInfo.GetRowIdIndex(databaseName, tableName);
if (iRowId != -1)
return GetInt64(iRowId);
return _keyInfo.GetRowId(databaseName, tableName);
}
///
/// Retrieves the column as a object.
/// This will not work for tables that were created WITHOUT ROWID
/// -OR- if the query does not include the "rowid" column or one
/// of its aliases -OR- if the was
/// not created with the
/// flag.
///
/// The index of the column.
///
/// Non-zero to open the blob object for read-only access.
///
/// A new object.
public SQLiteBlob GetBlob(int i, bool readOnly)
{
CheckDisposed();
VerifyForGet();
if ((_flags & SQLiteConnectionFlags.UseConnectionReadValueCallbacks) == SQLiteConnectionFlags.UseConnectionReadValueCallbacks)
{
SQLiteDataReaderValue value = new SQLiteDataReaderValue();
bool complete;
InvokeReadValueCallback(i, new SQLiteReadValueEventArgs(
"GetBlob", new SQLiteReadBlobEventArgs(readOnly), value),
out complete);
if (complete)
return (SQLiteBlob)value.BlobValue;
}
if (i >= PrivateVisibleFieldCount && _keyInfo != null)
return _keyInfo.GetBlob(i - PrivateVisibleFieldCount, readOnly);
return SQLiteBlob.Create(this, i, readOnly);
}
///
/// Retrieves the column as a boolean value
///
/// The index of the column.
/// bool
public override bool GetBoolean(int i)
{
CheckDisposed();
VerifyForGet();
if ((_flags & SQLiteConnectionFlags.UseConnectionReadValueCallbacks) == SQLiteConnectionFlags.UseConnectionReadValueCallbacks)
{
SQLiteDataReaderValue value = new SQLiteDataReaderValue();
bool complete;
InvokeReadValueCallback(i, new SQLiteReadValueEventArgs(
"GetBoolean", null, value), out complete);
if (complete)
{
if (value.BooleanValue == null)
throw new SQLiteException("missing boolean return value");
return (bool)value.BooleanValue;
}
}
if (i >= PrivateVisibleFieldCount && _keyInfo != null)
return _keyInfo.GetBoolean(i - PrivateVisibleFieldCount);
VerifyType(i, DbType.Boolean);
return Convert.ToBoolean(GetValue(i), CultureInfo.CurrentCulture);
}
///
/// Retrieves the column as a single byte value
///
/// The index of the column.
/// byte
public override byte GetByte(int i)
{
CheckDisposed();
VerifyForGet();
if ((_flags & SQLiteConnectionFlags.UseConnectionReadValueCallbacks) == SQLiteConnectionFlags.UseConnectionReadValueCallbacks)
{
SQLiteDataReaderValue value = new SQLiteDataReaderValue();
bool complete;
InvokeReadValueCallback(i, new SQLiteReadValueEventArgs(
"GetByte", null, value), out complete);
if (complete)
{
if (value.ByteValue == null)
throw new SQLiteException("missing byte return value");
return (byte)value.ByteValue;
}
}
if (i >= PrivateVisibleFieldCount && _keyInfo != null)
return _keyInfo.GetByte(i - PrivateVisibleFieldCount);
VerifyType(i, DbType.Byte);
return _activeStatement._sql.GetByte(_activeStatement, i);
}
///
/// Retrieves a column as an array of bytes (blob)
///
/// The index of the column.
/// The zero-based index of where to begin reading the data
/// The buffer to write the bytes into
/// The zero-based index of where to begin writing into the array
/// The number of bytes to retrieve
/// The actual number of bytes written into the array
///
/// To determine the number of bytes in the column, pass a null value for the buffer. The total length will be returned.
///
public override long GetBytes(int i, long fieldOffset, byte[] buffer, int bufferoffset, int length)
{
CheckDisposed();
VerifyForGet();
if ((_flags & SQLiteConnectionFlags.UseConnectionReadValueCallbacks) == SQLiteConnectionFlags.UseConnectionReadValueCallbacks)
{
SQLiteReadArrayEventArgs eventArgs = new SQLiteReadArrayEventArgs(
fieldOffset, buffer, bufferoffset, length);
SQLiteDataReaderValue value = new SQLiteDataReaderValue();
bool complete;
InvokeReadValueCallback(i, new SQLiteReadValueEventArgs(
"GetBytes", eventArgs, value), out complete);
if (complete)
{
byte[] bytes = value.BytesValue;
if (bytes != null)
{
#if !PLATFORM_COMPACTFRAMEWORK
Array.Copy(bytes, /* throw */
eventArgs.DataOffset, eventArgs.ByteBuffer,
eventArgs.BufferOffset, eventArgs.Length);
#else
Array.Copy(bytes, /* throw */
(int)eventArgs.DataOffset, eventArgs.ByteBuffer,
eventArgs.BufferOffset, eventArgs.Length);
#endif
return eventArgs.Length;
}
else
{
return -1;
}
}
}
if (i >= PrivateVisibleFieldCount && _keyInfo != null)
return _keyInfo.GetBytes(i - PrivateVisibleFieldCount, fieldOffset, buffer, bufferoffset, length);
VerifyType(i, DbType.Binary);
return _activeStatement._sql.GetBytes(_activeStatement, i, (int)fieldOffset, buffer, bufferoffset, length);
}
///
/// Returns the column as a single character
///
/// The index of the column.
/// char
public override char GetChar(int i)
{
CheckDisposed();
VerifyForGet();
if ((_flags & SQLiteConnectionFlags.UseConnectionReadValueCallbacks) == SQLiteConnectionFlags.UseConnectionReadValueCallbacks)
{
SQLiteDataReaderValue value = new SQLiteDataReaderValue();
bool complete;
InvokeReadValueCallback(i, new SQLiteReadValueEventArgs(
"GetChar", null, value), out complete);
if (complete)
{
if (value.CharValue == null)
throw new SQLiteException("missing character return value");
return (char)value.CharValue;
}
}
if (i >= PrivateVisibleFieldCount && _keyInfo != null)
return _keyInfo.GetChar(i - PrivateVisibleFieldCount);
VerifyType(i, DbType.SByte);
return _activeStatement._sql.GetChar(_activeStatement, i);
}
///
/// Retrieves a column as an array of chars (blob)
///
/// The index of the column.
/// The zero-based index of where to begin reading the data
/// The buffer to write the characters into
/// The zero-based index of where to begin writing into the array
/// The number of bytes to retrieve
/// The actual number of characters written into the array
///
/// To determine the number of characters in the column, pass a null value for the buffer. The total length will be returned.
///
public override long GetChars(int i, long fieldoffset, char[] buffer, int bufferoffset, int length)
{
CheckDisposed();
VerifyForGet();
if ((_flags & SQLiteConnectionFlags.UseConnectionReadValueCallbacks) == SQLiteConnectionFlags.UseConnectionReadValueCallbacks)
{
SQLiteReadArrayEventArgs eventArgs = new SQLiteReadArrayEventArgs(
fieldoffset, buffer, bufferoffset, length);
SQLiteDataReaderValue value = new SQLiteDataReaderValue();
bool complete;
InvokeReadValueCallback(i, new SQLiteReadValueEventArgs(
"GetChars", eventArgs, value), out complete);
if (complete)
{
char[] chars = value.CharsValue;
if (chars != null)
{
#if !PLATFORM_COMPACTFRAMEWORK
Array.Copy(chars, /* throw */
eventArgs.DataOffset, eventArgs.CharBuffer,
eventArgs.BufferOffset, eventArgs.Length);
#else
Array.Copy(chars, /* throw */
(int)eventArgs.DataOffset, eventArgs.CharBuffer,
eventArgs.BufferOffset, eventArgs.Length);
#endif
return eventArgs.Length;
}
else
{
return -1;
}
}
}
if (i >= PrivateVisibleFieldCount && _keyInfo != null)
return _keyInfo.GetChars(i - PrivateVisibleFieldCount, fieldoffset, buffer, bufferoffset, length);
if ((_flags & SQLiteConnectionFlags.NoVerifyTextAffinity) != SQLiteConnectionFlags.NoVerifyTextAffinity)
VerifyType(i, DbType.String);
return _activeStatement._sql.GetChars(_activeStatement, i, (int)fieldoffset, buffer, bufferoffset, length);
}
///
/// Retrieves the name of the back-end datatype of the column
///
/// The index of the column.
/// string
public override string GetDataTypeName(int i)
{
CheckDisposed();
if (i >= PrivateVisibleFieldCount && _keyInfo != null)
return _keyInfo.GetDataTypeName(i - PrivateVisibleFieldCount);
TypeAffinity affin = TypeAffinity.Uninitialized;
return _activeStatement._sql.ColumnType(_activeStatement, i, ref affin);
}
///
/// Retrieve the column as a date/time value
///
/// The index of the column.
/// DateTime
public override DateTime GetDateTime(int i)
{
CheckDisposed();
VerifyForGet();
if ((_flags & SQLiteConnectionFlags.UseConnectionReadValueCallbacks) == SQLiteConnectionFlags.UseConnectionReadValueCallbacks)
{
SQLiteDataReaderValue value = new SQLiteDataReaderValue();
bool complete;
InvokeReadValueCallback(i, new SQLiteReadValueEventArgs(
"GetDateTime", null, value), out complete);
if (complete)
{
if (value.DateTimeValue == null)
throw new SQLiteException("missing date/time return value");
return (DateTime)value.DateTimeValue;
}
}
if (i >= PrivateVisibleFieldCount && _keyInfo != null)
return _keyInfo.GetDateTime(i - PrivateVisibleFieldCount);
VerifyType(i, DbType.DateTime);
return _activeStatement._sql.GetDateTime(_activeStatement, i);
}
///
/// Retrieve the column as a decimal value
///
/// The index of the column.
/// decimal
public override decimal GetDecimal(int i)
{
CheckDisposed();
VerifyForGet();
if ((_flags & SQLiteConnectionFlags.UseConnectionReadValueCallbacks) == SQLiteConnectionFlags.UseConnectionReadValueCallbacks)
{
SQLiteDataReaderValue value = new SQLiteDataReaderValue();
bool complete;
InvokeReadValueCallback(i, new SQLiteReadValueEventArgs(
"GetDecimal", null, value), out complete);
if (complete)
{
if (value.DecimalValue == null)
throw new SQLiteException("missing decimal return value");
return (decimal)value.DecimalValue;
}
}
if (i >= PrivateVisibleFieldCount && _keyInfo != null)
return _keyInfo.GetDecimal(i - PrivateVisibleFieldCount);
VerifyType(i, DbType.Decimal);
CultureInfo cultureInfo = CultureInfo.CurrentCulture;
if ((_flags & SQLiteConnectionFlags.GetInvariantDecimal) == SQLiteConnectionFlags.GetInvariantDecimal)
cultureInfo = CultureInfo.InvariantCulture;
return Decimal.Parse(_activeStatement._sql.GetText(_activeStatement, i), NumberStyles.AllowDecimalPoint | NumberStyles.AllowExponent | NumberStyles.AllowLeadingSign, cultureInfo);
}
///
/// Returns the column as a double
///
/// The index of the column.
/// double
public override double GetDouble(int i)
{
CheckDisposed();
VerifyForGet();
if ((_flags & SQLiteConnectionFlags.UseConnectionReadValueCallbacks) == SQLiteConnectionFlags.UseConnectionReadValueCallbacks)
{
SQLiteDataReaderValue value = new SQLiteDataReaderValue();
bool complete;
InvokeReadValueCallback(i, new SQLiteReadValueEventArgs(
"GetDouble", null, value), out complete);
if (complete)
{
if (value.DoubleValue == null)
throw new SQLiteException("missing double return value");
return (double)value.DoubleValue;
}
}
if (i >= PrivateVisibleFieldCount && _keyInfo != null)
return _keyInfo.GetDouble(i - PrivateVisibleFieldCount);
VerifyType(i, DbType.Double);
return _activeStatement._sql.GetDouble(_activeStatement, i);
}
///
/// Determines and returns the of the
/// specified column.
///
///
/// The index of the column.
///
///
/// The associated with the specified
/// column, if any.
///
public TypeAffinity GetFieldAffinity(int i)
{
CheckDisposed();
if (i >= PrivateVisibleFieldCount && _keyInfo != null)
return _keyInfo.GetFieldAffinity(i - PrivateVisibleFieldCount);
return GetSQLiteType(_flags, i).Affinity;
}
///
/// Returns the .NET type of a given column
///
/// The index of the column.
/// Type
public override Type GetFieldType(int i)
{
CheckDisposed();
if (i >= PrivateVisibleFieldCount && _keyInfo != null)
return _keyInfo.GetFieldType(i - PrivateVisibleFieldCount);
return SQLiteConvert.SQLiteTypeToType(GetSQLiteType(_flags, i));
}
///
/// Returns a column as a float value
///
/// The index of the column.
/// float
public override float GetFloat(int i)
{
CheckDisposed();
VerifyForGet();
if ((_flags & SQLiteConnectionFlags.UseConnectionReadValueCallbacks) == SQLiteConnectionFlags.UseConnectionReadValueCallbacks)
{
SQLiteDataReaderValue value = new SQLiteDataReaderValue();
bool complete;
InvokeReadValueCallback(i, new SQLiteReadValueEventArgs(
"GetFloat", null, value), out complete);
if (complete)
{
if (value.FloatValue == null)
throw new SQLiteException("missing float return value");
return (float)value.FloatValue;
}
}
if (i >= PrivateVisibleFieldCount && _keyInfo != null)
return _keyInfo.GetFloat(i - PrivateVisibleFieldCount);
VerifyType(i, DbType.Single);
return Convert.ToSingle(_activeStatement._sql.GetDouble(_activeStatement, i));
}
///
/// Returns the column as a Guid
///
/// The index of the column.
/// Guid
public override Guid GetGuid(int i)
{
CheckDisposed();
VerifyForGet();
if ((_flags & SQLiteConnectionFlags.UseConnectionReadValueCallbacks) == SQLiteConnectionFlags.UseConnectionReadValueCallbacks)
{
SQLiteDataReaderValue value = new SQLiteDataReaderValue();
bool complete;
InvokeReadValueCallback(i, new SQLiteReadValueEventArgs(
"GetGuid", null, value), out complete);
if (complete)
{
if (value.GuidValue == null)
throw new SQLiteException("missing guid return value");
return (Guid)value.GuidValue;
}
}
if (i >= PrivateVisibleFieldCount && _keyInfo != null)
return _keyInfo.GetGuid(i - PrivateVisibleFieldCount);
TypeAffinity affinity = VerifyType(i, DbType.Guid);
if (affinity == TypeAffinity.Blob)
{
byte[] buffer = new byte[16];
_activeStatement._sql.GetBytes(_activeStatement, i, 0, buffer, 0, 16);
return new Guid(buffer);
}
else
return new Guid(_activeStatement._sql.GetText(_activeStatement, i));
}
///
/// Returns the column as a short
///
/// The index of the column.
/// Int16
public override Int16 GetInt16(int i)
{
CheckDisposed();
VerifyForGet();
if ((_flags & SQLiteConnectionFlags.UseConnectionReadValueCallbacks) == SQLiteConnectionFlags.UseConnectionReadValueCallbacks)
{
SQLiteDataReaderValue value = new SQLiteDataReaderValue();
bool complete;
InvokeReadValueCallback(i, new SQLiteReadValueEventArgs(
"GetInt16", null, value), out complete);
if (complete)
{
if (value.Int16Value == null)
throw new SQLiteException("missing int16 return value");
return (Int16)value.Int16Value;
}
}
if (i >= PrivateVisibleFieldCount && _keyInfo != null)
return _keyInfo.GetInt16(i - PrivateVisibleFieldCount);
VerifyType(i, DbType.Int16);
return _activeStatement._sql.GetInt16(_activeStatement, i);
}
///
/// Retrieves the column as an int
///
/// The index of the column.
/// Int32
public override Int32 GetInt32(int i)
{
CheckDisposed();
VerifyForGet();
if ((_flags & SQLiteConnectionFlags.UseConnectionReadValueCallbacks) == SQLiteConnectionFlags.UseConnectionReadValueCallbacks)
{
SQLiteDataReaderValue value = new SQLiteDataReaderValue();
bool complete;
InvokeReadValueCallback(i, new SQLiteReadValueEventArgs(
"GetInt32", null, value), out complete);
if (complete)
{
if (value.Int32Value == null)
throw new SQLiteException("missing int32 return value");
return (Int32)value.Int32Value;
}
}
if (i >= PrivateVisibleFieldCount && _keyInfo != null)
return _keyInfo.GetInt32(i - PrivateVisibleFieldCount);
VerifyType(i, DbType.Int32);
return _activeStatement._sql.GetInt32(_activeStatement, i);
}
///
/// Retrieves the column as a long
///
/// The index of the column.
/// Int64
public override Int64 GetInt64(int i)
{
CheckDisposed();
VerifyForGet();
if ((_flags & SQLiteConnectionFlags.UseConnectionReadValueCallbacks) == SQLiteConnectionFlags.UseConnectionReadValueCallbacks)
{
SQLiteDataReaderValue value = new SQLiteDataReaderValue();
bool complete;
InvokeReadValueCallback(i, new SQLiteReadValueEventArgs(
"GetInt64", null, value), out complete);
if (complete)
{
if (value.Int64Value == null)
throw new SQLiteException("missing int64 return value");
return (Int64)value.Int64Value;
}
}
if (i >= PrivateVisibleFieldCount && _keyInfo != null)
return _keyInfo.GetInt64(i - PrivateVisibleFieldCount);
VerifyType(i, DbType.Int64);
return _activeStatement._sql.GetInt64(_activeStatement, i);
}
///
/// Retrieves the name of the column
///
/// The index of the column.
/// string
public override string GetName(int i)
{
CheckDisposed();
if (i >= PrivateVisibleFieldCount && _keyInfo != null)
return _keyInfo.GetName(i - PrivateVisibleFieldCount);
return _activeStatement._sql.ColumnName(_activeStatement, i);
}
///
/// Returns the name of the database associated with the specified column.
///
/// The index of the column.
/// string
public string GetDatabaseName(int i)
{
CheckDisposed();
if (i >= PrivateVisibleFieldCount && _keyInfo != null)
return _keyInfo.GetDatabaseName(i - PrivateVisibleFieldCount);
return _activeStatement._sql.ColumnDatabaseName(_activeStatement, i);
}
///
/// Returns the name of the table associated with the specified column.
///
/// The index of the column.
/// string
public string GetTableName(int i)
{
CheckDisposed();
if (i >= PrivateVisibleFieldCount && _keyInfo != null)
return _keyInfo.GetTableName(i - PrivateVisibleFieldCount);
return _activeStatement._sql.ColumnTableName(_activeStatement, i);
}
///
/// Returns the original name of the specified column.
///
/// The index of the column.
/// string
public string GetOriginalName(int i)
{
CheckDisposed();
if (i >= PrivateVisibleFieldCount && _keyInfo != null)
return _keyInfo.GetName(i - PrivateVisibleFieldCount);
return _activeStatement._sql.ColumnOriginalName(_activeStatement, i);
}
///
/// Retrieves the i of a column, given its name
///
/// The name of the column to retrieve
/// The int i of the column
public override int GetOrdinal(string name)
{
CheckDisposed();
if (_throwOnDisposed) SQLiteCommand.Check(_command);
//
// NOTE: First, check if the column name cache has been initialized yet.
// If not, do it now.
//
if (_fieldIndexes == null)
{
_fieldIndexes = new Dictionary(
StringComparer.OrdinalIgnoreCase);
}
//
// NOTE: Next, see if the index for the requested column name has been
// cached already. If so, return the cached value. Otherwise,
// lookup the value and then cache the result for future use.
//
int r;
if (!_fieldIndexes.TryGetValue(name, out r))
{
r = _activeStatement._sql.ColumnIndex(_activeStatement, name);
if (r == -1 && _keyInfo != null)
{
r = _keyInfo.GetOrdinal(name);
if (r > -1) r += PrivateVisibleFieldCount;
}
_fieldIndexes.Add(name, r);
}
return r;
}
///
/// Schema information in SQLite is difficult to map into .NET conventions, so a lot of work must be done
/// to gather the necessary information so it can be represented in an ADO.NET manner.
///
/// Returns a DataTable containing the schema information for the active SELECT statement being processed.
public override DataTable GetSchemaTable()
{
CheckDisposed();
return GetSchemaTable(true, false);
}
///////////////////////////////////////////////////////////////////////////
#region ColumnParent Class
private sealed class ColumnParent : IEqualityComparer
{
#region Public Fields
public string DatabaseName;
public string TableName;
public string ColumnName;
#endregion
///////////////////////////////////////////////////////////////////////
#region Public Constructors
public ColumnParent()
{
// do nothing.
}
///////////////////////////////////////////////////////////////////////
public ColumnParent(
string databaseName,
string tableName,
string columnName
)
: this()
{
this.DatabaseName = databaseName;
this.TableName = tableName;
this.ColumnName = columnName;
}
#endregion
///////////////////////////////////////////////////////////////////////
#region IEqualityComparer Members
public bool Equals(ColumnParent x, ColumnParent y)
{
if ((x == null) && (y == null))
{
return true;
}
else if ((x == null) || (y == null))
{
return false;
}
else
{
if (!String.Equals(x.DatabaseName, y.DatabaseName,
StringComparison.OrdinalIgnoreCase))
{
return false;
}
if (!String.Equals(x.TableName, y.TableName,
StringComparison.OrdinalIgnoreCase))
{
return false;
}
if (!String.Equals(x.ColumnName, y.ColumnName,
StringComparison.OrdinalIgnoreCase))
{
return false;
}
return true;
}
}
///////////////////////////////////////////////////////////////////////
public int GetHashCode(ColumnParent obj)
{
int result = 0;
if ((obj != null) && (obj.DatabaseName != null))
result ^= obj.DatabaseName.GetHashCode();
if ((obj != null) && (obj.TableName != null))
result ^= obj.TableName.GetHashCode();
if ((obj != null) && (obj.ColumnName != null))
result ^= obj.ColumnName.GetHashCode();
return result;
}
#endregion
}
#endregion
///////////////////////////////////////////////////////////////////////////
private static void GetStatementColumnParents(
SQLiteBase sql,
SQLiteStatement stmt,
int fieldCount,
ref Dictionary> parentToColumns,
ref Dictionary columnToParent
)
{
if (parentToColumns == null)
parentToColumns = new Dictionary>(
new ColumnParent());
if (columnToParent == null)
columnToParent = new Dictionary();
for (int n = 0; n < fieldCount; n++)
{
string databaseName = sql.ColumnDatabaseName(stmt, n);
string tableName = sql.ColumnTableName(stmt, n);
string columnName = sql.ColumnOriginalName(stmt, n);
ColumnParent key = new ColumnParent(databaseName, tableName, null);
ColumnParent value = new ColumnParent(databaseName, tableName, columnName);
List indexList;
if (!parentToColumns.TryGetValue(key, out indexList))
parentToColumns.Add(key, new List(new int[] { n }));
else if (indexList != null)
indexList.Add(n);
else
parentToColumns[key] = new List(new int[] { n });
columnToParent.Add(n, value);
}
}
///////////////////////////////////////////////////////////////////////////
private static int CountParents(
Dictionary> parentToColumns
)
{
int result = 0;
if (parentToColumns != null)
{
foreach (ColumnParent key in parentToColumns.Keys)
{
if (key == null)
continue;
string tableName = key.TableName;
if (String.IsNullOrEmpty(tableName))
continue;
result++;
}
}
return result;
}
///////////////////////////////////////////////////////////////////////////
internal DataTable GetSchemaTable(bool wantUniqueInfo, bool wantDefaultValue)
{
CheckClosed();
if (_throwOnDisposed) SQLiteCommand.Check(_command);
//
// BUGFIX: We need to quickly scan all the fields in the current
// "result set" to see how many distinct tables are actually
// involved. This information is necessary so that some
// intelligent decisions can be made when constructing the
// metadata below. For example, we need to be very careful
// about flagging a particular column as "unique" just
// because it was in its original underlying database table
// if there are now multiple tables involved in the
// "result set". See ticket [7e3fa93744] for more detailed
// information.
//
Dictionary> parentToColumns = null;
Dictionary columnToParent = null;
SQLiteBase sql = _command.Connection._sql;
GetStatementColumnParents(
sql, _activeStatement, _fieldCount,
ref parentToColumns, ref columnToParent);
DataTable tbl = new DataTable("SchemaTable");
DataTable tblIndexes = null;
DataTable tblIndexColumns;
DataRow row;
string temp;
string strCatalog = String.Empty;
string strTable = String.Empty;
string strColumn = String.Empty;
tbl.Locale = CultureInfo.InvariantCulture;
tbl.Columns.Add(SchemaTableColumn.ColumnName, typeof(String));
tbl.Columns.Add(SchemaTableColumn.ColumnOrdinal, typeof(int));
tbl.Columns.Add(SchemaTableColumn.ColumnSize, typeof(int));
tbl.Columns.Add(SchemaTableColumn.NumericPrecision, typeof(int));
tbl.Columns.Add(SchemaTableColumn.NumericScale, typeof(int));
tbl.Columns.Add(SchemaTableColumn.IsUnique, typeof(Boolean));
tbl.Columns.Add(SchemaTableColumn.IsKey, typeof(Boolean));
tbl.Columns.Add(SchemaTableOptionalColumn.BaseServerName, typeof(string));
tbl.Columns.Add(SchemaTableOptionalColumn.BaseCatalogName, typeof(String));
tbl.Columns.Add(SchemaTableColumn.BaseColumnName, typeof(String));
tbl.Columns.Add(SchemaTableColumn.BaseSchemaName, typeof(String));
tbl.Columns.Add(SchemaTableColumn.BaseTableName, typeof(String));
tbl.Columns.Add(SchemaTableColumn.DataType, typeof(Type));
tbl.Columns.Add(SchemaTableColumn.AllowDBNull, typeof(Boolean));
tbl.Columns.Add(SchemaTableColumn.ProviderType, typeof(int));
tbl.Columns.Add(SchemaTableColumn.IsAliased, typeof(Boolean));
tbl.Columns.Add(SchemaTableColumn.IsExpression, typeof(Boolean));
tbl.Columns.Add(SchemaTableOptionalColumn.IsAutoIncrement, typeof(Boolean));
tbl.Columns.Add(SchemaTableOptionalColumn.IsRowVersion, typeof(Boolean));
tbl.Columns.Add(SchemaTableOptionalColumn.IsHidden, typeof(Boolean));
tbl.Columns.Add(SchemaTableColumn.IsLong, typeof(Boolean));
tbl.Columns.Add(SchemaTableOptionalColumn.IsReadOnly, typeof(Boolean));
tbl.Columns.Add(SchemaTableOptionalColumn.ProviderSpecificDataType, typeof(Type));
tbl.Columns.Add(SchemaTableOptionalColumn.DefaultValue, typeof(object));
tbl.Columns.Add("DataTypeName", typeof(string));
tbl.Columns.Add("CollationType", typeof(string));
tbl.BeginLoadData();
for (int n = 0; n < _fieldCount; n++)
{
SQLiteType sqlType = GetSQLiteType(_flags, n);
row = tbl.NewRow();
DbType typ = sqlType.Type;
// Default settings for the column
row[SchemaTableColumn.ColumnName] = GetName(n);
row[SchemaTableColumn.ColumnOrdinal] = n;
row[SchemaTableColumn.ColumnSize] = SQLiteConvert.DbTypeToColumnSize(typ);
row[SchemaTableColumn.NumericPrecision] = SQLiteConvert.DbTypeToNumericPrecision(typ);
row[SchemaTableColumn.NumericScale] = SQLiteConvert.DbTypeToNumericScale(typ);
row[SchemaTableColumn.ProviderType] = sqlType.Type;
row[SchemaTableColumn.IsLong] = false;
row[SchemaTableColumn.AllowDBNull] = true;
row[SchemaTableOptionalColumn.IsReadOnly] = false;
row[SchemaTableOptionalColumn.IsRowVersion] = false;
row[SchemaTableColumn.IsUnique] = false;
row[SchemaTableColumn.IsKey] = false;
row[SchemaTableOptionalColumn.IsAutoIncrement] = false;
row[SchemaTableColumn.DataType] = GetFieldType(n);
row[SchemaTableOptionalColumn.IsHidden] = false;
row[SchemaTableColumn.BaseSchemaName] = _baseSchemaName;
strColumn = columnToParent[n].ColumnName;
if (String.IsNullOrEmpty(strColumn) == false) row[SchemaTableColumn.BaseColumnName] = strColumn;
row[SchemaTableColumn.IsExpression] = String.IsNullOrEmpty(strColumn);
row[SchemaTableColumn.IsAliased] = (String.Compare(GetName(n), strColumn, StringComparison.OrdinalIgnoreCase) != 0);
temp = columnToParent[n].TableName;
if (String.IsNullOrEmpty(temp) == false) row[SchemaTableColumn.BaseTableName] = temp;
temp = columnToParent[n].DatabaseName;
if (String.IsNullOrEmpty(temp) == false) row[SchemaTableOptionalColumn.BaseCatalogName] = temp;
string dataType = null;
// If we have a table-bound column, extract the extra information from it
if (String.IsNullOrEmpty(strColumn) == false)
{
string baseCatalogName = String.Empty;
if (row[SchemaTableOptionalColumn.BaseCatalogName] != DBNull.Value)
baseCatalogName = (string)row[SchemaTableOptionalColumn.BaseCatalogName];
string baseTableName = String.Empty;
if (row[SchemaTableColumn.BaseTableName] != DBNull.Value)
baseTableName = (string)row[SchemaTableColumn.BaseTableName];
if (sql.DoesTableExist(baseCatalogName, baseTableName))
{
string baseColumnName = String.Empty;
if (row[SchemaTableColumn.BaseColumnName] != DBNull.Value)
baseColumnName = (string)row[SchemaTableColumn.BaseColumnName];
string collSeq = null;
bool bNotNull = false;
bool bPrimaryKey = false;
bool bAutoIncrement = false;
string[] arSize;
// Get the column meta data
_command.Connection._sql.ColumnMetaData(
baseCatalogName,
baseTableName,
strColumn,
true,
ref dataType, ref collSeq, ref bNotNull, ref bPrimaryKey, ref bAutoIncrement);
if (bNotNull || bPrimaryKey) row[SchemaTableColumn.AllowDBNull] = false;
bool allowDbNull = (bool)row[SchemaTableColumn.AllowDBNull];
row[SchemaTableColumn.IsKey] = bPrimaryKey && CountParents(parentToColumns) <= 1;
row[SchemaTableOptionalColumn.IsAutoIncrement] = bAutoIncrement;
row["CollationType"] = collSeq;
// For types like varchar(50) and such, extract the size
arSize = dataType.Split('(');
if (arSize.Length > 1)
{
dataType = arSize[0];
arSize = arSize[1].Split(')');
if (arSize.Length > 1)
{
arSize = arSize[0].Split(',', '.');
if (sqlType.Type == DbType.Binary || SQLiteConvert.IsStringDbType(sqlType.Type))
{
row[SchemaTableColumn.ColumnSize] = Convert.ToInt32(arSize[0], CultureInfo.InvariantCulture);
}
else
{
row[SchemaTableColumn.NumericPrecision] = Convert.ToInt32(arSize[0], CultureInfo.InvariantCulture);
if (arSize.Length > 1)
row[SchemaTableColumn.NumericScale] = Convert.ToInt32(arSize[1], CultureInfo.InvariantCulture);
}
}
}
if (wantDefaultValue)
{
// Determine the default value for the column, which sucks because we have to query the schema for each column
using (SQLiteCommand cmdTable = new SQLiteCommand(HelperMethods.StringFormat(CultureInfo.InvariantCulture, "PRAGMA [{0}].TABLE_INFO([{1}])",
baseCatalogName,
baseTableName
), _command.Connection))
using (DbDataReader rdTable = cmdTable.ExecuteReader())
{
// Find the matching column
while (rdTable.Read())
{
if (String.Compare(baseColumnName, rdTable.GetString(1), StringComparison.OrdinalIgnoreCase) == 0)
{
if (rdTable.IsDBNull(4) == false)
row[SchemaTableOptionalColumn.DefaultValue] = rdTable[4];
break;
}
}
}
}
// Determine IsUnique properly, which is a pain in the butt!
if (wantUniqueInfo)
{
if (baseCatalogName != strCatalog || baseTableName != strTable)
{
strCatalog = baseCatalogName;
strTable = baseTableName;
tblIndexes = _command.Connection.GetSchema("Indexes", new string[] {
baseCatalogName,
null,
baseTableName,
null
});
}
foreach (DataRow rowIndexes in tblIndexes.Rows)
{
tblIndexColumns = _command.Connection.GetSchema("IndexColumns", new string[] {
baseCatalogName,
null,
baseTableName,
(string)rowIndexes["INDEX_NAME"],
null
});
foreach (DataRow rowColumnIndex in tblIndexColumns.Rows)
{
if (String.Compare(SQLiteConvert.GetStringOrNull(rowColumnIndex["COLUMN_NAME"]), strColumn, StringComparison.OrdinalIgnoreCase) == 0)
{
//
// BUGFIX: Make sure that we only flag this column as "unique"
// if we are not processing of some kind of multi-table
// construct (i.e. a join) because in that case we must
// allow duplicate values (refer to ticket [7e3fa93744]).
//
if (parentToColumns.Count == 1 && tblIndexColumns.Rows.Count == 1 && allowDbNull == false)
row[SchemaTableColumn.IsUnique] = rowIndexes["UNIQUE"];
// If its an integer primary key and the only primary key in the table, then its a rowid alias and is autoincrement
// NOTE: Currently commented out because this is not always the desired behavior. For example, a 1:1 relationship with
// another table, where the other table is autoincrement, but this one is not, and uses the rowid from the other.
// It is safer to only set Autoincrement on tables where we're SURE the user specified AUTOINCREMENT, even if its a rowid column.
//if (tblIndexColumns.Rows.Count == 1 && (bool)rowIndexes["PRIMARY_KEY"] == true && String.IsNullOrEmpty(dataType) == false &&
// String.Compare(dataType, "integer", StringComparison.OrdinalIgnoreCase) == 0)
//{
// // row[SchemaTableOptionalColumn.IsAutoIncrement] = true;
//}
break;
}
}
}
}
}
if (String.IsNullOrEmpty(dataType))
{
TypeAffinity affin = TypeAffinity.Uninitialized;
dataType = _activeStatement._sql.ColumnType(_activeStatement, n, ref affin);
}
if (String.IsNullOrEmpty(dataType) == false)
row["DataTypeName"] = dataType;
}
tbl.Rows.Add(row);
}
if (_keyInfo != null)
_keyInfo.AppendSchemaTable(tbl);
tbl.AcceptChanges();
tbl.EndLoadData();
return tbl;
}
///
/// Retrieves the column as a string
///
/// The index of the column.
/// string
public override string GetString(int i)
{
CheckDisposed();
VerifyForGet();
if ((_flags & SQLiteConnectionFlags.UseConnectionReadValueCallbacks) == SQLiteConnectionFlags.UseConnectionReadValueCallbacks)
{
SQLiteDataReaderValue value = new SQLiteDataReaderValue();
bool complete;
InvokeReadValueCallback(i, new SQLiteReadValueEventArgs(
"GetString", null, value), out complete);
if (complete)
return value.StringValue;
}
if (i >= PrivateVisibleFieldCount && _keyInfo != null)
return _keyInfo.GetString(i - PrivateVisibleFieldCount);
if ((_flags & SQLiteConnectionFlags.NoVerifyTextAffinity) != SQLiteConnectionFlags.NoVerifyTextAffinity)
VerifyType(i, DbType.String);
return _activeStatement._sql.GetText(_activeStatement, i);
}
///
/// Retrieves the column as an object corresponding to the underlying datatype of the column
///
/// The index of the column.
/// object
public override object GetValue(int i)
{
CheckDisposed();
VerifyForGet();
if ((_flags & SQLiteConnectionFlags.UseConnectionReadValueCallbacks) == SQLiteConnectionFlags.UseConnectionReadValueCallbacks)
{
SQLiteDataReaderValue value = new SQLiteDataReaderValue();
bool complete;
InvokeReadValueCallback(i, new SQLiteReadValueEventArgs(
"GetValue", null, value), out complete);
if (complete)
return value.Value;
}
if (i >= PrivateVisibleFieldCount && _keyInfo != null)
return _keyInfo.GetValue(i - PrivateVisibleFieldCount);
SQLiteType typ = GetSQLiteType(_flags, i);
if (((_flags & SQLiteConnectionFlags.DetectTextAffinity) == SQLiteConnectionFlags.DetectTextAffinity) &&
((typ == null) || (typ.Affinity == TypeAffinity.Text)))
{
typ = GetSQLiteType(
typ, _activeStatement._sql.GetText(_activeStatement, i));
}
else if (((_flags & SQLiteConnectionFlags.DetectStringType) == SQLiteConnectionFlags.DetectStringType) &&
((typ == null) || SQLiteConvert.IsStringDbType(typ.Type)))
{
typ = GetSQLiteType(
typ, _activeStatement._sql.GetText(_activeStatement, i));
}
return _activeStatement._sql.GetValue(_activeStatement, _flags, i, typ);
}
///
/// Retreives the values of multiple columns, up to the size of the supplied array
///
/// The array to fill with values from the columns in the current resultset
/// The number of columns retrieved
public override int GetValues(object[] values)
{
CheckDisposed();
int nMax = FieldCount;
if (values.Length < nMax) nMax = values.Length;
for (int n = 0; n < nMax; n++)
{
values[n] = GetValue(n);
}
return nMax;
}
///
/// Returns a collection containing all the column names and values for the
/// current row of data in the current resultset, if any. If there is no
/// current row or no current resultset, an exception may be thrown.
///
///
/// The collection containing the column name and value information for the
/// current row of data in the current resultset or null if this information
/// cannot be obtained.
///
public NameValueCollection GetValues()
{
CheckDisposed();
if ((_activeStatement == null) || (_activeStatement._sql == null))
throw new InvalidOperationException();
int nMax = PrivateVisibleFieldCount;
NameValueCollection result = new NameValueCollection(nMax);
for (int n = 0; n < nMax; n++)
{
string name = _activeStatement._sql.ColumnName(_activeStatement, n);
string value = _activeStatement._sql.GetText(_activeStatement, n);
result.Add(name, value);
}
return result;
}
///
/// Returns True if the resultset has rows that can be fetched
///
public override bool HasRows
{
get
{
CheckDisposed();
CheckClosed();
//
// NOTE: If the "sticky" flag has been set, use the new behavior,
// which returns non-zero if there were ever any rows in
// the associated result sets. Generally, this flag is only
// useful when it is necessary to retain compatibility with
// other ADO.NET providers that use these same semantics for
// the HasRows property.
//
if ((_flags & SQLiteConnectionFlags.StickyHasRows) == SQLiteConnectionFlags.StickyHasRows)
return ((_readingState != 1) || (_stepCount > 0));
//
// NOTE: This is the default behavior. It returns non-zero only if
// more rows are available (i.e. a call to the Read method is
// expected to succeed). Prior to the introduction of the
// "sticky" flag, this is how this property has always worked.
//
return (_readingState != 1);
}
}
///
/// Returns True if the data reader is closed
///
public override bool IsClosed
{
get { CheckDisposed(); return (_command == null); }
}
///
/// Returns True if the specified column is null
///
/// The index of the column.
/// True or False
public override bool IsDBNull(int i)
{
CheckDisposed();
VerifyForGet();
if (i >= PrivateVisibleFieldCount && _keyInfo != null)
return _keyInfo.IsDBNull(i - PrivateVisibleFieldCount);
return _activeStatement._sql.IsNull(_activeStatement, i);
}
///
/// Moves to the next resultset in multiple row-returning SQL command.
///
/// True if the command was successful and a new resultset is available, False otherwise.
public override bool NextResult()
{
CheckDisposed();
CheckClosed();
if (_throwOnDisposed) SQLiteCommand.Check(_command);
SQLiteStatement stmt = null;
int fieldCount;
bool schemaOnly = ((_commandBehavior & CommandBehavior.SchemaOnly) != 0);
while (true)
{
if (stmt == null && _activeStatement != null && _activeStatement._sql != null && _activeStatement._sql.IsOpen())
{
// Reset the previously-executed statement
if (!schemaOnly) _activeStatement._sql.Reset(_activeStatement);
// If we're only supposed to return a single rowset, step through all remaining statements once until
// they are all done and return false to indicate no more resultsets exist.
if ((_commandBehavior & CommandBehavior.SingleResult) != 0)
{
for (; ; )
{
stmt = _command.GetStatement(_activeStatementIndex + 1);
if (stmt == null) break;
_activeStatementIndex++;
if (!schemaOnly && stmt._sql.Step(stmt)) _stepCount++;
if (stmt._sql.ColumnCount(stmt) == 0)
{
int changes = 0;
bool readOnly = false;
if (stmt.TryGetChanges(ref changes, ref readOnly))
{
if (!readOnly)
{
if (_rowsAffected == -1) _rowsAffected = 0;
_rowsAffected += changes;
}
}
else
{
return false;
}
}
if (!schemaOnly) stmt._sql.Reset(stmt); // Gotta reset after every step to release any locks and such!
}
return false;
}
}
// Get the next statement to execute
stmt = _command.GetStatement(_activeStatementIndex + 1);
// If we've reached the end of the statements, return false, no more resultsets
if (stmt == null)
return false;
// If we were on a current resultset, set the state to "done reading" for it
if (_readingState < 1)
_readingState = 1;
_activeStatementIndex++;
fieldCount = stmt._sql.ColumnCount(stmt);
// If the statement is not a select statement or we're not retrieving schema only, then perform the initial step
if (!schemaOnly || (fieldCount == 0))
{
if (!schemaOnly && stmt._sql.Step(stmt))
{
_stepCount++;
_readingState = -1;
}
else if (fieldCount == 0) // No rows returned, if fieldCount is zero, skip to the next statement
{
int changes = 0;
bool readOnly = false;
if (stmt.TryGetChanges(ref changes, ref readOnly))
{
if (!readOnly)
{
if (_rowsAffected == -1) _rowsAffected = 0;
_rowsAffected += changes;
}
}
else
{
return false;
}
if (!schemaOnly) stmt._sql.Reset(stmt);
continue; // Skip this command and move to the next, it was not a row-returning resultset
}
else // No rows, fieldCount is non-zero so stop here
{
_readingState = 1; // This command returned columns but no rows, so return true, but HasRows = false and Read() returns false
}
}
// Ahh, we found a row-returning resultset eligible to be returned!
_activeStatement = stmt;
_fieldCount = fieldCount;
_fieldIndexes = new Dictionary(StringComparer.OrdinalIgnoreCase);
_fieldTypeArray = new SQLiteType[PrivateVisibleFieldCount];
if ((_commandBehavior & CommandBehavior.KeyInfo) != 0)
LoadKeyInfo();
return true;
}
}
///
/// This method attempts to query the database connection associated with
/// the data reader in use. If the underlying command or connection is
/// unavailable, a null value will be returned.
///
///
/// The connection object -OR- null if it is unavailable.
///
internal static SQLiteConnection GetConnection(
SQLiteDataReader dataReader
)
{
try
{
if (dataReader != null)
{
SQLiteCommand command = dataReader._command;
if (command != null)
{
SQLiteConnection connection = command.Connection;
if (connection != null)
return connection;
}
}
}
catch (ObjectDisposedException)
{
// do nothing.
}
return null;
}
///
/// Retrieves the SQLiteType for a given column and row value.
///
///
/// The original SQLiteType structure, based only on the column.
///
///
/// The textual value of the column for a given row.
///
///
/// The SQLiteType structure.
///
private SQLiteType GetSQLiteType(
SQLiteType oldType, /* PASS-THROUGH */
string text
)
{
if (SQLiteConvert.LooksLikeNull(text))
return new SQLiteType(TypeAffinity.Null, DbType.Object);
if (SQLiteConvert.LooksLikeInt64(text))
return new SQLiteType(TypeAffinity.Int64, DbType.Int64);
if (SQLiteConvert.LooksLikeDouble(text))
return new SQLiteType(TypeAffinity.Double, DbType.Double);
if ((_activeStatement != null) &&
SQLiteConvert.LooksLikeDateTime(_activeStatement._sql, text))
{
return new SQLiteType(TypeAffinity.DateTime, DbType.DateTime);
}
return oldType;
}
///
/// Retrieves the SQLiteType for a given column, and caches it to avoid repetetive interop calls.
///
/// The flags associated with the parent connection object.
/// The index of the column.
/// A SQLiteType structure
private SQLiteType GetSQLiteType(SQLiteConnectionFlags flags, int i)
{
SQLiteType typ = _fieldTypeArray[i];
if (typ == null)
{
// Initialize this column's field type instance
typ = _fieldTypeArray[i] = new SQLiteType();
}
// If not initialized, then fetch the declared column datatype and attempt to convert it
// to a known DbType.
if (typ.Affinity == TypeAffinity.Uninitialized)
{
typ.Type = SQLiteConvert.TypeNameToDbType(
GetConnection(this), _activeStatement._sql.ColumnType(
_activeStatement, i, ref typ.Affinity), flags);
}
else
{
typ.Affinity = _activeStatement._sql.ColumnAffinity(
_activeStatement, i);
}
return typ;
}
///
/// Reads the next row from the resultset
///
/// True if a new row was successfully loaded and is ready for processing
public override bool Read()
{
CheckDisposed();
CheckClosed();
if (_throwOnDisposed) SQLiteCommand.Check(_command);
if ((_commandBehavior & CommandBehavior.SchemaOnly) != 0)
return false;
if (_readingState == -1) // First step was already done at the NextResult() level, so don't step again, just return true.
{
_readingState = 0;
return true;
}
else if (_readingState == 0) // Actively reading rows
{
// Don't read a new row if the command behavior dictates SingleRow. We've already read the first row.
if ((_commandBehavior & CommandBehavior.SingleRow) == 0)
{
if (_activeStatement._sql.Step(_activeStatement) == true)
{
_stepCount++;
if (_keyInfo != null)
_keyInfo.Reset();
return true;
}
}
_readingState = 1; // Finished reading rows
}
return false;
}
///
/// Returns the number of rows affected by the statement being executed.
/// The value returned may not be accurate for DDL statements. Also, it
/// will be -1 for any statement that does not modify the database (e.g.
/// SELECT). If an otherwise read-only statement modifies the database
/// indirectly (e.g. via a virtual table or user-defined function), the
/// value returned is undefined.
///
public override int RecordsAffected
{
get { CheckDisposed(); return _rowsAffected; }
}
///
/// Indexer to retrieve data from a column given its name
///
/// The name of the column to retrieve data for
/// The value contained in the column
public override object this[string name]
{
get { CheckDisposed(); return GetValue(GetOrdinal(name)); }
}
///
/// Indexer to retrieve data from a column given its i
///
/// The index of the column.
/// The value contained in the column
public override object this[int i]
{
get { CheckDisposed(); return GetValue(i); }
}
private void LoadKeyInfo()
{
if (_keyInfo != null)
{
_keyInfo.Dispose();
_keyInfo = null;
}
_keyInfo = new SQLiteKeyReader(_command.Connection, this, _activeStatement);
}
}
}