Index: Doc/Extra/Provider/version.html
==================================================================
--- Doc/Extra/Provider/version.html
+++ Doc/Extra/Provider/version.html
@@ -46,10 +46,12 @@
1.0.102.0 - June XX, 2016 (release scheduled)
- Updated to SQLite 3.13.0.
- Update the SQLiteConnection.EnableExtensions method to make use of the new SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION option, when available. ** Potentially Incompatible Change **
- Prevent the SQLiteCommand.ExecuteScalar method from throwing an exception when there are no result columns. ** Potentially Incompatible Change **
+ - Support per-connection customization for binding parameters and reading values, based on the database type name.
+ - Add TypeName property to the SQLiteParameter class.
- Add VerifyOnly method to the SQLiteCommand class.
- Add IsReadOnly method to the SQLiteConnection class.
1.0.101.0 - April 19, 2016
Index: Setup/data/verify.lst
==================================================================
--- Setup/data/verify.lst
+++ Setup/data/verify.lst
@@ -848,10 +848,11 @@
Tests/tkt-e47b3d8346.eagle
Tests/tkt-ef2216192d.eagle
Tests/tkt-f2c47a01eb.eagle
Tests/tkt-f8dbab8baf.eagle
Tests/tkt-fe50b8c2e8.eagle
+ Tests/types.eagle
Tests/version.eagle
Tests/vtab.eagle
tools/
tools/install/
tools/install/Installer.2005.csproj
Index: System.Data.SQLite/SQLiteBase.cs
==================================================================
--- System.Data.SQLite/SQLiteBase.cs
+++ System.Data.SQLite/SQLiteBase.cs
@@ -1158,10 +1158,41 @@
/// When returning column values as a , skip
/// verifying their affinity.
///
NoVerifyTextAffinity = 0x200000000,
+ ///
+ /// Enable using per-connection mappings between type names and
+ /// values. Also see the
+ /// ,
+ /// , and
+ /// methods.
+ ///
+ UseConnectionBindValueCallbacks = 0x400000000,
+
+ ///
+ /// Enable using per-connection mappings between type names and
+ /// values. Also see the
+ /// ,
+ /// , and
+ /// methods.
+ ///
+ UseConnectionReadValueCallbacks = 0x800000000,
+
+ ///
+ /// If the database type name has not been explicitly set for the
+ /// parameter specified, fallback to using the parameter name.
+ ///
+ UseParameterNameForTypeName = 0x1000000000,
+
+ ///
+ /// If the database type name has not been explicitly set for the
+ /// parameter specified, fallback to using the database type name
+ /// associated with the value.
+ ///
+ UseParameterDbTypeForTypeName = 0x2000000000,
+
///
/// When binding parameter values or returning column values, always
/// treat them as though they were plain text (i.e. no numeric,
/// date/time, or other conversions should be attempted).
///
@@ -1189,10 +1220,23 @@
/// or from strings.
///
ConvertAndBindAndGetAllAsInvariantText = BindAndGetAllAsText |
ConvertAndBindInvariantText,
+ ///
+ /// Enables use of all per-connection value handling callbacks.
+ ///
+ UseConnectionAllValueCallbacks = UseConnectionBindValueCallbacks |
+ UseConnectionReadValueCallbacks,
+
+ ///
+ /// Enables use of all applicable
+ /// properties as fallbacks for the database type name.
+ ///
+ UseParameterAnythingForTypeName = UseParameterNameForTypeName |
+ UseParameterDbTypeForTypeName,
+
///
/// Enable all logging.
///
#if INTEROP_VIRTUAL_TABLE
LogAll = LogPrepare | LogPreBind | LogBind |
Index: System.Data.SQLite/SQLiteConnection.cs
==================================================================
--- System.Data.SQLite/SQLiteConnection.cs
+++ System.Data.SQLite/SQLiteConnection.cs
@@ -19,10 +19,721 @@
using System.IO;
using System.Text;
/////////////////////////////////////////////////////////////////////////////////////////////////
+ ///
+ /// This class represents a single value to be returned
+ /// from the class via
+ /// its ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// , or
+ /// method. If the value of the
+ /// associated public field of this class is null upon returning from the
+ /// callback, the null value will only be used if the return type for the
+ /// method called is not a value type.
+ /// If the value to be returned from the
+ /// method is unsuitable (e.g. null with a value type), an exception will
+ /// be thrown.
+ ///
+ public sealed class SQLiteDataReaderValue
+ {
+ ///
+ /// The value to be returned from the
+ /// method -OR- null to
+ /// indicate an error.
+ ///
+ public bool? BooleanValue;
+
+ ///
+ /// The value to be returned from the
+ /// method -OR- null to
+ /// indicate an error.
+ ///
+ public byte? ByteValue;
+
+ ///
+ /// The value to be returned from the
+ /// method.
+ ///
+ public byte[] BytesValue;
+
+ ///
+ /// The value to be returned from the
+ /// method -OR- null to
+ /// indicate an error.
+ ///
+ public char? CharValue;
+
+ ///
+ /// The value to be returned from the
+ /// method.
+ ///
+ public char[] CharsValue;
+
+ ///
+ /// The value to be returned from the
+ /// method -OR- null to
+ /// indicate an error.
+ ///
+ public DateTime? DateTimeValue;
+
+ ///
+ /// The value to be returned from the
+ /// method -OR- null to
+ /// indicate an error.
+ ///
+ public decimal? DecimalValue;
+
+ ///
+ /// The value to be returned from the
+ /// method -OR- null to
+ /// indicate an error.
+ ///
+ public double? DoubleValue;
+
+ ///
+ /// The value to be returned from the
+ /// method -OR- null to
+ /// indicate an error.
+ ///
+ public float? FloatValue;
+
+ ///
+ /// The value to be returned from the
+ /// method -OR- null to
+ /// indicate an error.
+ ///
+ public Guid? GuidValue;
+
+ ///
+ /// The value to be returned from the
+ /// method -OR- null to
+ /// indicate an error.
+ ///
+ public short? Int16Value;
+
+ ///
+ /// The value to be returned from the
+ /// method -OR- null to
+ /// indicate an error.
+ ///
+ public int? Int32Value;
+
+ ///
+ /// The value to be returned from the
+ /// method -OR- null to
+ /// indicate an error.
+ ///
+ public long? Int64Value;
+
+ ///
+ /// The value to be returned from the
+ /// method.
+ ///
+ public string StringValue;
+
+ ///
+ /// The value to be returned from the
+ /// method.
+ ///
+ public object Value;
+ }
+
+ /////////////////////////////////////////////////////////////////////////////////////////////////
+
+ ///
+ /// This class represents the parameters that are provided
+ /// to the and
+ /// methods, with
+ /// the exception of the column index (provided separately).
+ ///
+ public class SQLiteReadArrayEventArgs : EventArgs
+ {
+ #region Private Data
+ ///
+ /// Provides the underlying storage for the
+ /// property.
+ ///
+ private long dataOffset;
+
+ ///
+ /// Provides the underlying storage for the
+ /// property.
+ ///
+ private byte[] byteBuffer;
+
+ ///
+ /// Provides the underlying storage for the
+ /// property.
+ ///
+ private char[] charBuffer;
+
+ ///
+ /// Provides the underlying storage for the
+ /// property.
+ ///
+ private int bufferOffset;
+
+ ///
+ /// Provides the underlying storage for the
+ /// property.
+ ///
+ private int length;
+ #endregion
+
+ /////////////////////////////////////////////////////////////////////////
+
+ #region Private Constructors
+ ///
+ /// Constructs an instance of this class to pass into a user-defined
+ /// callback associated with the
+ /// method.
+ ///
+ ///
+ /// The value that was originally specified for the "dataOffset"
+ /// parameter to the or
+ /// methods.
+ ///
+ ///
+ /// The value that was originally specified for the "buffer"
+ /// parameter to the
+ /// method.
+ ///
+ ///
+ /// The value that was originally specified for the "bufferOffset"
+ /// parameter to the or
+ /// methods.
+ ///
+ ///
+ /// The value that was originally specified for the "length"
+ /// parameter to the or
+ /// methods.
+ ///
+ internal SQLiteReadArrayEventArgs(
+ long dataOffset,
+ byte[] byteBuffer,
+ int bufferOffset,
+ int length
+ )
+ {
+ this.dataOffset = dataOffset;
+ this.byteBuffer = byteBuffer;
+ this.bufferOffset = bufferOffset;
+ this.length = length;
+ }
+
+ /////////////////////////////////////////////////////////////////////////
+
+ ///
+ /// Constructs an instance of this class to pass into a user-defined
+ /// callback associated with the
+ /// method.
+ ///
+ ///
+ /// The value that was originally specified for the "dataOffset"
+ /// parameter to the or
+ /// methods.
+ ///
+ ///
+ /// The value that was originally specified for the "buffer"
+ /// parameter to the
+ /// method.
+ ///
+ ///
+ /// The value that was originally specified for the "bufferOffset"
+ /// parameter to the or
+ /// methods.
+ ///
+ ///
+ /// The value that was originally specified for the "length"
+ /// parameter to the or
+ /// methods.
+ ///
+ internal SQLiteReadArrayEventArgs(
+ long dataOffset,
+ char[] charBuffer,
+ int bufferOffset,
+ int length
+ )
+ {
+ this.dataOffset = dataOffset;
+ this.charBuffer = charBuffer;
+ this.bufferOffset = bufferOffset;
+ this.length = length;
+ }
+ #endregion
+
+ /////////////////////////////////////////////////////////////////////////
+
+ #region Public Properties
+ ///
+ /// The value that was originally specified for the "dataOffset"
+ /// parameter to the or
+ /// methods.
+ ///
+ public long DataOffset
+ {
+ get { return dataOffset; }
+ set { dataOffset = value; }
+ }
+
+ /////////////////////////////////////////////////////////////////////////
+
+ ///
+ /// The value that was originally specified for the "buffer"
+ /// parameter to the
+ /// method.
+ ///
+ public byte[] ByteBuffer
+ {
+ get { return byteBuffer; }
+ }
+
+ /////////////////////////////////////////////////////////////////////////
+
+ ///
+ /// The value that was originally specified for the "buffer"
+ /// parameter to the
+ /// method.
+ ///
+ public char[] CharBuffer
+ {
+ get { return charBuffer; }
+ }
+
+ /////////////////////////////////////////////////////////////////////////
+
+ ///
+ /// The value that was originally specified for the "bufferOffset"
+ /// parameter to the or
+ /// methods.
+ ///
+ public int BufferOffset
+ {
+ get { return bufferOffset; }
+ set { bufferOffset = value; }
+ }
+
+ /////////////////////////////////////////////////////////////////////////
+
+ ///
+ /// The value that was originally specified for the "length"
+ /// parameter to the or
+ /// methods.
+ ///
+ public int Length
+ {
+ get { return length; }
+ set { length = value; }
+ }
+ #endregion
+ }
+
+ /////////////////////////////////////////////////////////////////////////////////////////////////
+
+ ///
+ /// This class represents the parameters and return values for the
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// , and
+ /// methods.
+ ///
+ public class SQLiteReadValueEventArgs : EventArgs
+ {
+ #region Private Data
+ ///
+ /// Provides the underlying storage for the
+ /// property.
+ ///
+ private string methodName;
+
+ ///
+ /// Provides the underlying storage for the
+ /// property.
+ ///
+ private SQLiteReadArrayEventArgs arrayEventArgs;
+
+ ///
+ /// Provides the underlying storage for the
+ /// property.
+ ///
+ private SQLiteDataReaderValue value;
+ #endregion
+
+ /////////////////////////////////////////////////////////////////////////
+
+ #region Private Constructors
+ ///
+ /// Constructs a new instance of this class. Depending on the method
+ /// being called, the and/or
+ /// parameters may be null.
+ ///
+ ///
+ /// The name of the method that was
+ /// responsible for invoking this callback.
+ ///
+ ///
+ /// If the or
+ /// method is being called,
+ /// this object will contain the array related parameters for that
+ /// method.
+ ///
+ ///
+ /// This may be used by the callback to set the return value for the
+ /// called method.
+ ///
+ internal SQLiteReadValueEventArgs(
+ string methodName,
+ SQLiteReadArrayEventArgs arrayEventArgs,
+ SQLiteDataReaderValue value
+ )
+ {
+ this.methodName = methodName;
+ this.arrayEventArgs = arrayEventArgs;
+ this.value = value;
+ }
+ #endregion
+
+ /////////////////////////////////////////////////////////////////////////
+
+ #region Public Properties
+ ///
+ /// The name of the method that was
+ /// responsible for invoking this callback.
+ ///
+ public string MethodName
+ {
+ get { return methodName; }
+ }
+
+ /////////////////////////////////////////////////////////////////////////
+
+ ///
+ /// If the or
+ /// method is being called,
+ /// this object will contain the array related parameters for that
+ /// method.
+ ///
+ public SQLiteReadArrayEventArgs ArrayEventArgs
+ {
+ get { return arrayEventArgs; }
+ }
+
+ /////////////////////////////////////////////////////////////////////////
+
+ ///
+ /// This may be used by the callback to set the return value for the
+ /// called method.
+ ///
+ public SQLiteDataReaderValue Value
+ {
+ get { return value; }
+ }
+ #endregion
+ }
+
+ /////////////////////////////////////////////////////////////////////////////////////////////////
+
+ ///
+ /// This represents a method that will be called in response to a request to
+ /// bind a parameter to a command. If an exception is thrown, it will cause
+ /// the parameter binding operation to fail -AND- it will continue to unwind
+ /// the call stack.
+ ///
+ ///
+ /// The instance in use.
+ ///
+ ///
+ /// The instance in use.
+ ///
+ ///
+ /// The flags associated with the instance
+ /// in use.
+ ///
+ ///
+ /// The instance being bound to the command.
+ ///
+ ///
+ /// The database type name associated with this callback.
+ ///
+ ///
+ /// The ordinal of the parameter being bound to the command.
+ ///
+ ///
+ /// The data originally used when registering this callback.
+ ///
+ ///
+ /// Non-zero if the default handling for the parameter binding call should
+ /// be skipped (i.e. the parameter should not be bound at all). Great care
+ /// should be used when setting this to non-zero.
+ ///
+ public delegate void SQLiteBindValueCallback(
+ SQLiteConvert convert,
+ SQLiteCommand command,
+ SQLiteConnectionFlags flags,
+ SQLiteParameter parameter,
+ string typeName,
+ int index,
+ object userData,
+ out bool complete
+ );
+
+ /////////////////////////////////////////////////////////////////////////////////////////////////
+
+ ///
+ /// This represents a method that will be called in response to a request
+ /// to read a value from a data reader. If an exception is thrown, it will
+ /// cause the data reader operation to fail -AND- it will continue to unwind
+ /// the call stack.
+ ///
+ ///
+ /// The instance in use.
+ ///
+ ///
+ /// The instance in use.
+ ///
+ ///
+ /// The flags associated with the instance
+ /// in use.
+ ///
+ ///
+ /// The parameter and return type data for the column being read from the
+ /// data reader.
+ ///
+ ///
+ /// The database type name associated with this callback.
+ ///
+ ///
+ /// The zero based index of the column being read from the data reader.
+ ///
+ ///
+ /// The data originally used when registering this 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.
+ ///
+ public delegate void SQLiteReadValueCallback(
+ SQLiteConvert convert,
+ SQLiteDataReader dataReader,
+ SQLiteConnectionFlags flags,
+ SQLiteReadValueEventArgs eventArgs,
+ string typeName,
+ int index,
+ object userData,
+ out bool complete
+ );
+
+ /////////////////////////////////////////////////////////////////////////////////////////////////
+
+ ///
+ /// This class represents the custom data type handling callbacks
+ /// for a single type name.
+ ///
+ public sealed class SQLiteTypeCallbacks
+ {
+ #region Private Data
+ ///
+ /// Provides the underlying storage for the
+ /// property.
+ ///
+ private string typeName;
+
+ ///
+ /// Provides the underlying storage for the
+ /// property.
+ ///
+ private SQLiteBindValueCallback bindValueCallback;
+
+ ///
+ /// Provides the underlying storage for the
+ /// property.
+ ///
+ private SQLiteReadValueCallback readValueCallback;
+
+ ///
+ /// Provides the underlying storage for the
+ /// property.
+ ///
+ private object bindValueUserData;
+
+ ///
+ /// Provides the underlying storage for the
+ /// property.
+ ///
+ private object readValueUserData;
+ #endregion
+
+ /////////////////////////////////////////////////////////////////////////
+
+ #region Private Constructors
+ ///
+ /// Constructs an instance of this class.
+ ///
+ ///
+ /// The custom paramater binding callback. This parameter may be null.
+ ///
+ ///
+ /// The custom data reader value callback. This parameter may be null.
+ ///
+ ///
+ /// The extra data to pass into the parameter binding callback. This
+ /// parameter may be null.
+ ///
+ ///
+ /// The extra data to pass into the data reader value callback. This
+ /// parameter may be null.
+ ///
+ private SQLiteTypeCallbacks(
+ SQLiteBindValueCallback bindValueCallback,
+ SQLiteReadValueCallback readValueCallback,
+ object bindValueUserData,
+ object readValueUserData
+ )
+ {
+ this.bindValueCallback = bindValueCallback;
+ this.readValueCallback = readValueCallback;
+ this.bindValueUserData = bindValueUserData;
+ this.readValueUserData = readValueUserData;
+ }
+ #endregion
+
+ /////////////////////////////////////////////////////////////////////////
+
+ #region Static "Factory" Methods
+ ///
+ /// Creates an instance of the class.
+ ///
+ ///
+ /// The custom paramater binding callback. This parameter may be null.
+ ///
+ ///
+ /// The custom data reader value callback. This parameter may be null.
+ ///
+ ///
+ /// The extra data to pass into the parameter binding callback. This
+ /// parameter may be null.
+ ///
+ ///
+ /// The extra data to pass into the data reader value callback. This
+ /// parameter may be null.
+ ///
+ public static SQLiteTypeCallbacks Create(
+ SQLiteBindValueCallback bindValueCallback,
+ SQLiteReadValueCallback readValueCallback,
+ object bindValueUserData,
+ object readValueUserData
+ )
+ {
+ return new SQLiteTypeCallbacks(
+ bindValueCallback, readValueCallback, bindValueUserData,
+ readValueUserData);
+ }
+ #endregion
+
+ /////////////////////////////////////////////////////////////////////////
+
+ #region Public Properties
+ ///
+ /// The database type name that the callbacks contained in this class
+ /// will apply to. This value may not be null.
+ ///
+ public string TypeName
+ {
+ get { return typeName; }
+ internal set { typeName = value; }
+ }
+
+ /////////////////////////////////////////////////////////////////////////
+
+ ///
+ /// The custom paramater binding callback. This value may be null.
+ ///
+ public SQLiteBindValueCallback BindValueCallback
+ {
+ get { return bindValueCallback; }
+ }
+
+ /////////////////////////////////////////////////////////////////////////
+
+ ///
+ /// The custom data reader value callback. This value may be null.
+ ///
+ public SQLiteReadValueCallback ReadValueCallback
+ {
+ get { return readValueCallback; }
+ }
+
+ /////////////////////////////////////////////////////////////////////////
+
+ ///
+ /// The extra data to pass into the parameter binding callback. This
+ /// value may be null.
+ ///
+ public object BindValueUserData
+ {
+ get { return bindValueUserData; }
+ }
+
+ /////////////////////////////////////////////////////////////////////////
+
+ ///
+ /// The extra data to pass into the data reader value callback. This
+ /// value may be null.
+ ///
+ public object ReadValueUserData
+ {
+ get { return readValueUserData; }
+ }
+ #endregion
+ }
+
+ /////////////////////////////////////////////////////////////////////////////////////////////////
+
+ ///
+ /// This class represents the mappings between database type names
+ /// and their associated custom data type handling callbacks.
+ ///
+ internal sealed class SQLiteTypeCallbacksMap
+ : Dictionary
+ {
+ ///
+ /// Constructs an (empty) instance of this class.
+ ///
+ public SQLiteTypeCallbacksMap()
+ : base(new TypeNameStringComparer())
+ {
+ // do nothing.
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////////////////////////////////
+
///
/// Event data for connection event handlers.
///
public class ConnectionEventArgs : EventArgs
{
@@ -676,10 +1387,16 @@
/// The per-connection mappings between type names and
/// values. These mappings override the corresponding global mappings.
///
internal SQLiteDbTypeMap _typeNames;
+ ///
+ /// The per-connection mappings between type names and optional callbacks
+ /// for parameter binding and value reading.
+ ///
+ private SQLiteTypeCallbacksMap _typeCallbacks;
+
///
/// The base SQLite object to interop with
///
internal SQLiteBase _sql;
///
@@ -900,10 +1617,11 @@
_cachedSettings = new Dictionary(
new TypeNameStringComparer());
_typeNames = new SQLiteDbTypeMap();
+ _typeCallbacks = new SQLiteTypeCallbacksMap();
_parseViaFramework = parseViaFramework;
_flags = SQLiteConnectionFlags.None;
_defaultDbType = null;
_defaultTypeName = null;
_vfsName = null;
@@ -1382,10 +2100,106 @@
}
return result;
}
#endregion
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+
+ #region Per-Connection Type Callbacks
+ ///
+ /// Clears the per-connection type callbacks.
+ ///
+ ///
+ /// The total number of per-connection type callbacks cleared.
+ ///
+ public int ClearTypeCallbacks()
+ {
+ CheckDisposed();
+
+ int result = -1; /* NO CALLBACKS */
+
+ if (_typeCallbacks != null)
+ {
+ result = _typeCallbacks.Count;
+ _typeCallbacks.Clear();
+ }
+
+ return result;
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+
+ ///
+ /// Attempts to get the per-connection type callbacks for the specified
+ /// database type name.
+ ///
+ ///
+ /// The database type name.
+ ///
+ ///
+ /// Upon success, this parameter will contain the object holding the
+ /// callbacks for the database type name. Upon failure, this parameter
+ /// will be null.
+ ///
+ ///
+ /// Non-zero upon success; otherwise, zero.
+ ///
+ public bool TryGetTypeCallbacks(
+ string typeName,
+ out SQLiteTypeCallbacks callbacks
+ )
+ {
+ if (typeName == null)
+ throw new ArgumentNullException("typeName");
+
+ if (_typeCallbacks == null)
+ {
+ callbacks = null;
+ return false;
+ }
+
+ return _typeCallbacks.TryGetValue(typeName, out callbacks);
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+
+ ///
+ /// Sets, resets, or clears the per-connection type callbacks for the
+ /// specified database type name.
+ ///
+ ///
+ /// The database type name.
+ ///
+ ///
+ /// The object holding the callbacks for the database type name. If
+ /// this parameter is null, any callbacks for the database type name
+ /// will be removed if they are present.
+ ///
+ ///
+ /// Non-zero if callbacks were set or removed; otherwise, zero.
+ ///
+ public bool SetTypeCallbacks(
+ string typeName,
+ SQLiteTypeCallbacks callbacks
+ )
+ {
+ if (typeName == null)
+ throw new ArgumentNullException("typeName");
+
+ if (_typeCallbacks == null)
+ return false;
+
+ if (callbacks == null)
+ return _typeCallbacks.Remove(typeName);
+
+ callbacks.TypeName = typeName;
+ _typeCallbacks[typeName] = callbacks;
+
+ return true;
+ }
+ #endregion
///////////////////////////////////////////////////////////////////////////////////////////////
///
/// Attempts to bind the specified object
Index: System.Data.SQLite/SQLiteDataReader.cs
==================================================================
--- System.Data.SQLite/SQLiteDataReader.cs
+++ System.Data.SQLite/SQLiteDataReader.cs
@@ -426,10 +426,73 @@
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,
+ SQLiteReadValueEventArgs 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;
+ }
+ }
///
/// Retrieves the column as a boolean value
///
/// The index of the column.
@@ -436,10 +499,27 @@
/// 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);
@@ -453,10 +533,27 @@
/// 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);
@@ -477,10 +574,46 @@
///
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);
@@ -494,10 +627,27 @@
/// 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);
@@ -518,10 +668,46 @@
///
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)
@@ -553,10 +739,27 @@
/// 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);
@@ -570,10 +773,27 @@
/// 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);
@@ -587,10 +807,27 @@
/// 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);
@@ -619,10 +856,27 @@
/// 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);
@@ -636,10 +890,27 @@
/// 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);
@@ -660,10 +931,27 @@
/// 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);
@@ -677,10 +965,27 @@
/// 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);
@@ -694,10 +999,27 @@
/// 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);
@@ -1230,10 +1552,22 @@
/// 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)
@@ -1249,10 +1583,22 @@
/// 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);
Index: System.Data.SQLite/SQLiteParameter.cs
==================================================================
--- System.Data.SQLite/SQLiteParameter.cs
+++ System.Data.SQLite/SQLiteParameter.cs
@@ -52,10 +52,15 @@
private int _dataSize;
private bool _nullable;
private bool _nullMapping;
+ ///
+ /// The database type name associated with this parameter, if any.
+ ///
+ private string _typeName;
+
///
/// Constructor used when creating for use with a specific command.
///
///
/// The command associated with this parameter.
@@ -474,10 +479,25 @@
_objValue = value;
if (_dbType == UnknownDbType && _objValue != null && _objValue != DBNull.Value) // If the DbType has never been assigned, try to glean one from the value's datatype
_dbType = SQLiteConvert.TypeToDbType(_objValue.GetType());
}
}
+
+ ///
+ /// The database type name associated with this parameter, if any.
+ ///
+ public string TypeName
+ {
+ get
+ {
+ return _typeName;
+ }
+ set
+ {
+ _typeName = value;
+ }
+ }
///
/// Clones a parameter
///
/// A new, unassociated SQLiteParameter
Index: System.Data.SQLite/SQLiteStatement.cs
==================================================================
--- System.Data.SQLite/SQLiteStatement.cs
+++ System.Data.SQLite/SQLiteStatement.cs
@@ -235,10 +235,145 @@
for (int n = 0; n < x; n++)
{
BindParameter(n + 1, _paramValues[n]);
}
}
+
+ ///
+ /// This method attempts to query the database connection associated with
+ /// the statement 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.
+ ///
+ private static SQLiteConnection GetConnection(
+ SQLiteStatement statement
+ )
+ {
+ try
+ {
+ if (statement != null)
+ {
+ SQLiteCommand command = statement._command;
+
+ if (command != null)
+ {
+ SQLiteConnection connection = command.Connection;
+
+ if (connection != null)
+ return connection;
+ }
+ }
+ }
+ catch (ObjectDisposedException)
+ {
+ // do nothing.
+ }
+
+ return null;
+ }
+
+ ///
+ /// Invokes the parameter binding callback configured for the database
+ /// type name associated with the specified column. If no parameter
+ /// binding callback is available for the database type name, do
+ /// nothing.
+ ///
+ ///
+ /// The index of the column being read.
+ ///
+ ///
+ /// The instance being bound to the
+ /// command.
+ ///
+ ///
+ /// Non-zero if the default handling for the parameter binding call
+ /// should be skipped (i.e. the parameter should not be bound at all).
+ /// Great care should be used when setting this to non-zero.
+ ///
+ private void InvokeBindValueCallback(
+ int index,
+ SQLiteParameter parameter,
+ out bool complete
+ )
+ {
+ complete = false;
+ SQLiteConnectionFlags oldFlags = _flags;
+ _flags &= ~SQLiteConnectionFlags.UseConnectionBindValueCallbacks;
+
+ try
+ {
+ if (parameter == null)
+ return;
+
+ SQLiteConnection connection = GetConnection(this);
+
+ if (connection == null)
+ return;
+
+ //
+ // NOTE: First, always look for an explicitly set database type
+ // name.
+ //
+ string typeName = parameter.TypeName;
+
+ if (typeName == null)
+ {
+ //
+ // NOTE: Are we allowed to fallback to using the parameter name
+ // as the basis for looking up the binding callback?
+ //
+ if ((_flags & SQLiteConnectionFlags.UseParameterNameForTypeName)
+ == SQLiteConnectionFlags.UseParameterNameForTypeName)
+ {
+ typeName = parameter.ParameterName;
+ }
+ }
+
+ if (typeName == null)
+ {
+ //
+ // NOTE: Are we allowed to fallback to using the database type
+ // name translated from the DbType as the basis for looking
+ // up the binding callback?
+ //
+ if ((_flags & SQLiteConnectionFlags.UseParameterDbTypeForTypeName)
+ == SQLiteConnectionFlags.UseParameterDbTypeForTypeName)
+ {
+ typeName = SQLiteConvert.DbTypeToTypeName(
+ connection, parameter.DbType, _flags);
+ }
+ }
+
+ if (typeName == null)
+ return;
+
+ SQLiteTypeCallbacks callbacks;
+
+ if (!connection.TryGetTypeCallbacks(typeName, out callbacks) ||
+ (callbacks == null))
+ {
+ return;
+ }
+
+ SQLiteBindValueCallback callback = callbacks.BindValueCallback;
+
+ if (callback == null)
+ return;
+
+ object userData = callbacks.BindValueUserData;
+
+ callback(
+ _sql, _command, oldFlags, parameter, typeName, index,
+ userData, out complete); /* throw */
+ }
+ finally
+ {
+ _flags |= SQLiteConnectionFlags.UseConnectionBindValueCallbacks;
+ }
+ }
///
/// Perform the bind operation for an individual parameter
///
/// The index of the parameter to bind
@@ -245,10 +380,20 @@
/// The parameter we're binding
private void BindParameter(int index, SQLiteParameter param)
{
if (param == null)
throw new SQLiteException("Insufficient parameters supplied to the command");
+
+ if ((_flags & SQLiteConnectionFlags.UseConnectionBindValueCallbacks) == SQLiteConnectionFlags.UseConnectionBindValueCallbacks)
+ {
+ bool complete;
+
+ InvokeBindValueCallback(index, param, out complete);
+
+ if (complete)
+ return;
+ }
object obj = param.Value;
DbType objType = param.DbType;
if ((obj != null) && (objType == DbType.Object))
ADDED Tests/types.eagle
Index: Tests/types.eagle
==================================================================
--- /dev/null
+++ Tests/types.eagle
@@ -0,0 +1,598 @@
+###############################################################################
+#
+# types.eagle --
+#
+# Written by Joe Mistachkin.
+# Released to the public domain, use at your own risk!
+#
+###############################################################################
+
+package require Eagle
+package require Eagle.Library
+package require Eagle.Test
+
+runTestPrologue
+
+###############################################################################
+
+package require System.Data.SQLite.Test
+runSQLiteTestPrologue
+
+###############################################################################
+
+proc bindValueCallback1 {
+ convert command flags parameter typeName index userData
+ completeVarName } {
+ lappend ::log(bind) [list convert [isObjectHandle $convert]]
+ lappend ::log(bind) [list command [isObjectHandle $command]]
+ lappend ::log(bind) [list flags [getStringFromObjectHandle $flags]]
+ lappend ::log(bind) [list parameter [isObjectHandle $parameter]]
+ lappend ::log(bind) [list typeName [getStringFromObjectHandle $typeName]]
+ lappend ::log(bind) [list index [getStringFromObjectHandle $index]]
+ lappend ::log(bind) [list userData [getStringFromObjectHandle $userData]]
+
+ if {[getStringFromObjectHandle $userData] == 3} then {
+ upvar 1 $completeVarName complete; unset complete
+ error "parameter binding canceled"
+ }
+
+ if {[getStringFromObjectHandle $userData] == 2} then {
+ $parameter DbType String
+ $parameter Value custom
+ }
+
+ if {[getStringFromObjectHandle $userData] == 1} then {
+ upvar 1 $completeVarName complete
+ set complete [object invoke -create System.Boolean Parse True]
+ }
+}
+
+###############################################################################
+
+proc readValueCallback1 {
+ convert dataReader flags eventArgs typeName index userData
+ completeVarName } {
+ lappend ::log(read) [list convert [isObjectHandle $convert]]
+ lappend ::log(read) [list dataReader [isObjectHandle $dataReader]]
+ lappend ::log(read) [list flags [getStringFromObjectHandle $flags]]
+ lappend ::log(read) [list eventArgs [isObjectHandle $eventArgs]]
+ lappend ::log(read) [list typeName [getStringFromObjectHandle $typeName]]
+ lappend ::log(read) [list index [getStringFromObjectHandle $index]]
+ lappend ::log(read) [list userData [getStringFromObjectHandle $userData]]
+
+ if {[getStringFromObjectHandle $userData] == 3} then {
+ upvar 1 $completeVarName complete; unset complete
+ error "reading of value canceled"
+ }
+
+ if {[getStringFromObjectHandle $userData] == 1} then {
+ upvar 1 $completeVarName complete
+ set complete [object invoke -create System.Boolean Parse True]
+ }
+}
+
+###############################################################################
+
+runTest {test types-1.1 {type callbacks management} -setup {
+ setupDb [set fileName types-1.1.db]
+} -body {
+ set connection [getDbConnection]
+
+ set result [list]
+
+ lappend result [$connection ClearTypeCallbacks]
+
+ set typeCallbacks(1) [object invoke \
+ System.Data.SQLite.SQLiteTypeCallbacks Create \
+ null null null null]
+
+ set typeCallbacks(2) null
+
+ lappend result [$connection SetTypeCallbacks TEST $typeCallbacks(1)]
+ lappend result [$connection TryGetTypeCallbacks TEST typeCallbacks(2)]
+ lappend result [$connection ClearTypeCallbacks]
+ lappend result [$connection SetTypeCallbacks TEST $typeCallbacks(1)]
+ lappend result [expr {$typeCallbacks(1) eq $typeCallbacks(2)}]
+
+ set result
+} -cleanup {
+ freeDbConnection
+
+ cleanupDb $fileName
+
+ unset -nocomplain typeCallbacks
+ unset -nocomplain result connection db fileName
+} -constraints {eagle command.object monoBug28 command.sql compile.DATA SQLite\
+System.Data.SQLite} -result {0 True True 1 True True}}
+
+###############################################################################
+
+set readArgs [list \
+ convert dataReader flags eventArgs typeName index userData \
+ completeVarName]
+
+set params [list \
+ [list Boolean GetBoolean false BooleanValue true 0] \
+ [list Byte GetByte false ByteValue true 0] \
+ [list Byte GetBytes true BytesValue false 0] \
+ [list Char GetChar false CharValue true 0] \
+ [list Char GetChars true CharsValue false 0] \
+ [list DateTime GetDateTime false DateTimeValue true 0] \
+ [list Decimal GetDecimal false DecimalValue true 0] \
+ [list Double GetDouble false DoubleValue true 0] \
+ [list Single GetFloat false FloatValue true 0] \
+ [list Guid GetGuid false GuidValue true \
+ 00000000-0000-0000-0000-000000000000] \
+ [list Int16 GetInt16 false Int16Value true 0] \
+ [list Int32 GetInt32 false Int32Value true 0] \
+ [list Int64 GetInt64 false Int64Value true 0] \
+ [list String GetString false StringValue false null] \
+ [list Object GetValue false Value false null]]
+
+###############################################################################
+
+set expectedResults [list \
+ {False False True False False False False False} \
+ {0 0 1 0 0 0 0 0} \
+ {0 1 48 1 49 3 {49 46 50} 5 {116 104 114\
+ 101 101} 1 4 27 {50 48 49 54 45 48 54\
+ 45 49 57 32 49 57 58 53 48 58 48 52 46\
+ 49 50 51 52 53 54 55} 36 {55 52 102 49\
+ 102 52 48 50 45 100 100 101 102 45 52\
+ 48 50 55 45 97 55 56 102 45 51 56 52 55\
+ 102 97 57 55 98 56 51 48}} \
+ " \x01 " \
+ "0 1 0 1 1 3 {1 . 2} 1 t 1 \x04 1 2 1 7" \
+ {{0001-01-01 00:00:00Z} {0001-01-01 00:00:00Z}\
+ {0001-01-01 00:00:00Z} {0001-01-01 00:00:00Z}\
+ {0001-01-01 00:00:00Z} {0001-01-01 00:00:00Z}\
+ {2016-06-19 19:50:04.1234567Z} {0001-01-01\
+ 00:00:00Z}} \
+ {0 0 1 1.2 0 0 0 0} \
+ {0 0 1 1.2 0 0 0 0} \
+ {0 0 1 1.2 0 0 0 0} \
+ {00000000-0000-0000-0000-000000000000\
+ 00000000-0000-0000-0000-000000000000\
+ 00000000-0000-0000-0000-000000000000\
+ 00000000-0000-0000-0000-000000000000\
+ 00000000-0000-0000-0000-000000000000\
+ 00060504-0000-0000-0000-000000000000\
+ 00000000-0000-0000-0000-000000000000\
+ 74f1f402-ddef-4027-a78f-3847fa97b830} \
+ {0 0 1 0 0 0 0 0} \
+ {0 0 1 0 0 0 0 0} \
+ {0 0 1 0 0 0 0 0} \
+ " three \x04\x05\x06\
+ {2016-06-19 19:50:04.1234567}\
+ 74f1f402-ddef-4027-a78f-3847fa97b830" \
+ {{} 0 1 1.2 three {4 5 6} {2016-06-19\
+ 19:50:04.1234567}\
+ 74f1f402-ddef-4027-a78f-3847fa97b830}]
+
+###############################################################################
+
+set savedDateTimeFormat [object invoke Interpreter.GetActive DateTimeFormat]
+if {![isObjectHandle $savedDateTimeFormat]} then {set savedDateTimeFormat null}
+object invoke Interpreter.GetActive DateTimeFormat [getDateTimeFormat]
+
+###############################################################################
+
+for {set i 0} {$i < [llength $params]} {incr i} {
+ foreach {
+ typeName methodName isArray propertyName isRequired value
+ } [lindex $params $i] break
+
+ set expectedResult [lindex $expectedResults $i]
+
+ #############################################################################
+
+ proc readValueCallback2 $readArgs [subst {
+ if {$isArray} then {
+ if {\[catch {
+ set dataOffset \[\$eventArgs ArrayEventArgs.DataOffset\]
+
+ set buffer \[\$eventArgs -create [appendArgs ArrayEventArgs. \
+ $typeName Buffer]\]
+
+ set bufferOffset \[\$eventArgs ArrayEventArgs.BufferOffset\]
+ set length \[\$eventArgs ArrayEventArgs.Length\]
+
+ set readValue \[\$dataReader \[\$eventArgs MethodName\] \
+ \$index \$dataOffset \$buffer \$bufferOffset \$length\]
+
+ \$eventArgs \[appendArgs Value. $propertyName\] \$readValue
+ } error\]} then {
+ set readValue \[\$dataReader -tostring GetValue \$index\]
+
+ if {"$typeName" eq "Char"} then {
+ set string \[object create -alias String \$readValue\]
+ set buffer \[\$string -create -alias ToCharArray]
+ } else {
+ set buffer \[object invoke -create -alias \
+ System.Text.Encoding.UTF8 GetBytes \$readValue\]
+ }
+
+ \$eventArgs \[appendArgs Value. $propertyName\] \$buffer
+ \$eventArgs ArrayEventArgs.Length \[\$buffer Length\]
+ } else {
+ set buffer \[\$eventArgs -create [appendArgs ArrayEventArgs. \
+ $typeName Buffer]\]
+
+ \$eventArgs \[appendArgs Value. $propertyName\] \$buffer
+ }
+ } else {
+ if {\[catch {
+ set readValue \[\$dataReader \[\$eventArgs MethodName\] \$index\]
+
+ if {"$typeName" eq "Char"} then {
+ set readValue \[object invoke -create Char Parse \$readValue\]
+ }
+
+ \$eventArgs \[appendArgs Value. $propertyName\] \$readValue
+ } error\]} then {
+ \$eventArgs \[appendArgs Value. $propertyName\] {$value}
+ }
+ }
+
+ upvar 1 \$completeVarName complete
+ set complete \[object invoke -create System.Boolean Parse True\]
+ }]
+
+ #############################################################################
+
+ runTest {test [appendArgs types-2. $i] [appendArgs $methodName " callback"] \
+ -setup [subst -nocommands {
+ set typeName {$typeName}
+ set methodName {$methodName}
+ set isArray {$isArray}
+
+ setupDb [set fileName [appendArgs types-2. $i .db]] "" "" "" \
+ UseConnectionReadValueCallbacks
+ }] -body {
+ sql execute $db {
+ CREATE TABLE t1(x INTEGER, y SPECIAL);
+ INSERT INTO t1 (x, y) VALUES(1, NULL);
+ INSERT INTO t1 (x, y) VALUES(2, 0);
+ INSERT INTO t1 (x, y) VALUES(3, 1);
+ INSERT INTO t1 (x, y) VALUES(4, 1.2);
+ INSERT INTO t1 (x, y) VALUES(5, 'three');
+ INSERT INTO t1 (x, y) VALUES(6, X'040506');
+ INSERT INTO t1 (x, y) VALUES(7, '2016-06-19 19:50:04.1234567');
+ INSERT INTO t1 (x, y) VALUES(8, '74f1f402-ddef-4027-a78f-3847fa97b830');
+ }
+
+ set callback {-callbackflags +Default readValueCallback2}
+ set connection [getDbConnection]
+
+ set result [list]
+
+ set typeCallbacks [object invoke -marshalflags +DynamicCallback \
+ System.Data.SQLite.SQLiteTypeCallbacks Create null $callback \
+ null null]
+
+ $connection SetTypeCallbacks SPECIAL $typeCallbacks
+
+ set dataReader [sql execute -execute reader -format datareader \
+ -alias $db "SELECT y FROM t1 ORDER BY x;"]
+
+ while {[$dataReader Read]} {
+ if {$isArray} then {
+ set buffer [object invoke \
+ -create Array CreateInstance $typeName 100]
+
+ if {[catch {
+ $dataReader $methodName 0 0 $buffer 0 1
+ } value] == 0} then {
+ lappend result $value
+
+ if {$value > 0} then {
+ set list [object create -alias StringList $buffer]
+
+ lappend result [object invoke StringList GetRange \
+ $list 0 [expr {$value - 1}] false]
+ }
+ } else {
+ lappend result [list error(array) $::errorCode]
+ }
+ } else {
+ if {[catch {
+ $dataReader $methodName 0
+ } value] == 0} then {
+ if {$value eq "\x00"} then {
+ lappend result
+ } else {
+ lappend result [getStringFromObjectHandle $value]
+ }
+ } else {
+ lappend result [list error(value) $::errorCode]
+ }
+ }
+ }
+
+ set result
+ } -cleanup {
+ catch {object removecallback $callback}
+
+ unset -nocomplain dataReader
+ freeDbConnection
+
+ cleanupDb $fileName
+
+ unset -nocomplain buffer typeCallbacks callback value list
+ unset -nocomplain result connection db fileName
+ unset -nocomplain typeName methodName isArray
+ } -constraints {eagle command.object monoBug28 command.sql compile.DATA\
+SQLite System.Data.SQLite} -result $expectedResult}
+
+ rename readValueCallback2 ""
+}
+
+###############################################################################
+
+object invoke Interpreter.GetActive DateTimeFormat $savedDateTimeFormat
+unset -nocomplain savedDateTimeFormat
+
+###############################################################################
+
+unset -nocomplain i readArgs params typeName methodName isArray propertyName \
+ isRequired expectedResults expectedResult
+
+###############################################################################
+
+runTest {test types-3.1 {bind callback (incomplete)} -setup {
+ unset -nocomplain log
+
+ setupDb [set fileName types-3.1.db] "" "" "" \
+ "UseConnectionBindValueCallbacks UseParameterNameForTypeName"
+} -body {
+ sql execute $db {
+ CREATE TABLE t1(x SPECIAL);
+ }
+
+ set callback {-callbackflags +Default bindValueCallback1}
+ set connection [getDbConnection]
+
+ set typeCallbacks [object invoke -marshalflags +DynamicCallback \
+ System.Data.SQLite.SQLiteTypeCallbacks Create $callback null \
+ 0 null]
+
+ $connection SetTypeCallbacks SPECIAL $typeCallbacks
+
+ sql execute $db {
+ INSERT INTO t1 (x) VALUES(?);
+ } [list Special Int64 1234]
+
+ set result [list]
+
+ lappend result [expr {
+ [info exists log(bind)] ? $log(bind) : ""
+ }]
+
+ lappend result [sql execute -execute reader -format list $db \
+ "SELECT * FROM t1 ORDER BY x;"]
+
+ set result
+} -cleanup {
+ catch {object removecallback $callback}
+
+ freeDbConnection
+
+ cleanupDb $fileName
+
+ unset -nocomplain result typeCallbacks callback log connection db fileName
+} -constraints {eagle command.object monoBug28 command.sql compile.DATA SQLite\
+System.Data.SQLite} -result {{{convert true} {command true} {flags\
+{UseConnectionBindValueCallbacks, UseParameterNameForTypeName}} {parameter\
+true} {typeName Special} {index 1} {userData 0}} 1234}}
+
+###############################################################################
+
+runTest {test types-3.2 {bind callback (complete)} -setup {
+ unset -nocomplain log
+
+ setupDb [set fileName types-3.2.db] "" "" "" \
+ "UseConnectionBindValueCallbacks UseParameterNameForTypeName"
+} -body {
+ sql execute $db {
+ CREATE TABLE t1(x SPECIAL);
+ }
+
+ set callback {-callbackflags +Default bindValueCallback1}
+ set connection [getDbConnection]
+
+ set typeCallbacks [object invoke -marshalflags +DynamicCallback \
+ System.Data.SQLite.SQLiteTypeCallbacks Create $callback null \
+ 1 null]
+
+ $connection SetTypeCallbacks SPECIAL $typeCallbacks
+
+ sql execute $db {
+ INSERT INTO t1 (x) VALUES(?);
+ } [list Special Int64 5678]
+
+ set result [list]
+
+ lappend result [expr {
+ [info exists log(bind)] ? $log(bind) : ""
+ }]
+
+ lappend result [sql execute -execute reader -format list $db \
+ "SELECT * FROM t1 ORDER BY x;"]
+
+ set result
+} -cleanup {
+ catch {object removecallback $callback}
+
+ freeDbConnection
+
+ cleanupDb $fileName
+
+ unset -nocomplain result typeCallbacks callback log connection db fileName
+} -constraints {eagle command.object monoBug28 command.sql compile.DATA SQLite\
+System.Data.SQLite} -result {{{convert true} {command true} {flags\
+{UseConnectionBindValueCallbacks, UseParameterNameForTypeName}} {parameter\
+true} {typeName Special} {index 1} {userData 1}} {}}}
+
+###############################################################################
+
+runTest {test types-3.3 {bind callback (modify/incomplete)} -setup {
+ unset -nocomplain log
+
+ setupDb [set fileName types-3.3.db] "" "" "" \
+ "UseConnectionBindValueCallbacks UseParameterNameForTypeName"
+} -body {
+ sql execute $db {
+ CREATE TABLE t1(x SPECIAL);
+ }
+
+ set callback {-callbackflags +Default bindValueCallback1}
+ set connection [getDbConnection]
+
+ set typeCallbacks [object invoke -marshalflags +DynamicCallback \
+ System.Data.SQLite.SQLiteTypeCallbacks Create $callback null \
+ 2 null]
+
+ $connection SetTypeCallbacks SPECIAL $typeCallbacks
+
+ sql execute $db {
+ INSERT INTO t1 (x) VALUES(?);
+ } [list Special Int64 9999]
+
+ set result [list]
+
+ lappend result [expr {
+ [info exists log(bind)] ? $log(bind) : ""
+ }]
+
+ lappend result [sql execute -execute reader -format list $db \
+ "SELECT * FROM t1 ORDER BY x;"]
+
+ set result
+} -cleanup {
+ catch {object removecallback $callback}
+
+ freeDbConnection
+
+ cleanupDb $fileName
+
+ unset -nocomplain result typeCallbacks callback log connection db fileName
+} -constraints {eagle command.object monoBug28 command.sql compile.DATA SQLite\
+System.Data.SQLite} -result {{{convert true} {command true} {flags\
+{UseConnectionBindValueCallbacks, UseParameterNameForTypeName}} {parameter\
+true} {typeName Special} {index 1} {userData 2}} custom}}
+
+###############################################################################
+
+runTest {test types-4.1 {read callback (exception)} -setup {
+ unset -nocomplain log
+
+ setupDb [set fileName types-4.1.db] "" "" "" \
+ UseConnectionReadValueCallbacks
+} -body {
+ sql execute $db {
+ CREATE TABLE t1(x SPECIAL);
+ INSERT INTO t1 (x) VALUES(8888);
+ }
+
+ set callback {-callbackflags {+Default ThrowOnError} readValueCallback1}
+ set connection [getDbConnection]
+
+ set typeCallbacks [object invoke -marshalflags +DynamicCallback \
+ System.Data.SQLite.SQLiteTypeCallbacks Create null $callback \
+ null 3]
+
+ $connection SetTypeCallbacks SPECIAL $typeCallbacks
+
+ set result [list]
+
+ lappend result [catch {
+ sql execute -execute reader -format list $db {SELECT * FROM t1 ORDER BY x;}
+ } error]
+
+ lappend result [extractSystemDataSQLiteExceptionMessage $error]
+
+ lappend result [catch {
+ sql execute -execute scalar $db {SELECT COUNT(*) FROM t1;}
+ } error]
+
+ lappend result [extractSystemDataSQLiteExceptionMessage $error]
+ lappend result [expr {[info exists log(read)] ? $log(read) : ""}]
+
+ set result
+} -cleanup {
+ catch {object removecallback $callback}
+
+ freeDbConnection
+
+ cleanupDb $fileName
+
+ unset -nocomplain error result typeCallbacks callback log connection db \
+ fileName
+} -constraints {eagle command.object monoBug28 command.sql compile.DATA SQLite\
+System.Data.SQLite} -result {1 {reading of value canceled} 0 1 {{convert true}\
+{dataReader true} {flags UseConnectionReadValueCallbacks} {eventArgs true}\
+{typeName SPECIAL} {index 0} {userData 3}}}}
+
+###############################################################################
+
+runTest {test types-5.1 {bind callback (exception)} -setup {
+ unset -nocomplain log
+
+ setupDb [set fileName types-5.1.db] "" "" "" \
+ "UseConnectionBindValueCallbacks UseParameterNameForTypeName"
+} -body {
+ sql execute $db {
+ CREATE TABLE t1(x SPECIAL);
+ }
+
+ set callback {-callbackflags {+Default ThrowOnError} bindValueCallback1}
+ set connection [getDbConnection]
+
+ set typeCallbacks [object invoke -marshalflags +DynamicCallback \
+ System.Data.SQLite.SQLiteTypeCallbacks Create $callback null \
+ 3 null]
+
+ $connection SetTypeCallbacks SPECIAL $typeCallbacks
+
+ catch {
+ sql execute $db {
+ INSERT INTO t1 (x) VALUES(?);
+ } [list Special Int64 4321]
+ }
+
+ set result [list]
+
+ lappend result [expr {
+ [info exists log(bind)] ? $log(bind) : ""
+ }]
+
+ lappend result [sql execute -execute reader -format list $db \
+ "SELECT * FROM t1 ORDER BY x;"]
+
+ set result
+} -cleanup {
+ catch {object removecallback $callback}
+
+ freeDbConnection
+
+ cleanupDb $fileName
+
+ unset -nocomplain result typeCallbacks callback log connection db fileName
+} -constraints {eagle command.object monoBug28 command.sql compile.DATA SQLite\
+System.Data.SQLite} -result {{{convert true} {command true} {flags\
+{UseConnectionBindValueCallbacks, UseParameterNameForTypeName}} {parameter\
+true} {typeName Special} {index 1} {userData 3}} {}}}
+
+###############################################################################
+
+catch {eval object dispose [info objects System#Boolean#*]}
+
+###############################################################################
+
+rename readValueCallback1 ""
+rename bindValueCallback1 ""
+
+###############################################################################
+
+runSQLiteTestEpilogue
+runTestEpilogue
Index: lib/System.Data.SQLite/common.eagle
==================================================================
--- lib/System.Data.SQLite/common.eagle
+++ lib/System.Data.SQLite/common.eagle
@@ -1825,10 +1825,117 @@
# various tests may fail.
#
return "yyyy-MM-dd HH:mm:ss.FFFFFFFK"
}
}
+
+ proc getProperties { object varName } {
+ upvar 1 $varName properties
+
+ set count 0
+ set names [list]
+
+ if {[isObjectHandle $object] && $object ne "null"} then {
+ eval lappend names [object members \
+ -membertypes Property -nameonly $object]
+
+ eval lappend names [object members \
+ -membertypes Field -nameonly $object]
+ }
+
+ foreach name $names {
+ if {[catch {
+ object invoke -objectflags +NoDispose $object $name
+ } value] == 0} then {
+ if {[isObjectHandle $value] && $value ne "null"} then {
+ set error null; object invoke -flags +NonPublic \
+ -marshalflags +NoHandle Interpreter.GetActive \
+ AddObjectReference Ok $value error
+
+ lappend properties(objects) [list $name $value]
+ } else {
+ lappend properties(values) [list $name $value]
+ }
+
+ incr count
+ } else {
+ lappend properties(errors) [list $name $::errorCode]
+ }
+ }
+
+ return $count
+ }
+
+ proc getAllProperties { object varName } {
+ upvar 1 $varName properties
+ set value $object
+
+ while {true} {
+ if {![info exists properties(seenObjects)] || \
+ $value ni $properties(seenObjects)} then {
+ getProperties $value properties
+ lappend properties(seenObjects) $value
+ }
+
+ if {![info exists properties(objects)]} then {
+ break
+ }
+
+ if {[llength $properties(objects)] == 0} then {
+ unset properties(objects); break
+ }
+
+ set value [lindex [lindex $properties(objects) 0] end]
+ set properties(objects) [lrange $properties(objects) 1 end]
+ }
+
+ if {[info exists properties(seenObjects)]} then {
+ foreach value $properties(seenObjects) {
+ if {$value eq $object} continue
+ catch {object dispose $value}
+ }
+
+ unset properties(seenObjects)
+ }
+ }
+
+ proc getVariables { varNames {objects false} } {
+ set result [list]
+
+ foreach varName $varNames {
+ if {[uplevel 1 [list array exists $varName]]} then {
+ set arrayName $varName
+
+ foreach elementName [uplevel 1 [list array names $arrayName]] {
+ set name [appendArgs $arrayName ( $elementName )]
+ set varValue [uplevel 1 [list set $name]]
+
+ if {$objects && [isObjectHandle $varValue]} then {
+ unset -nocomplain properties
+ getAllProperties $varValue properties
+
+ lappend result [list $name [array get properties]]
+ } else {
+ lappend result [list $name $varValue]
+ }
+ }
+ } else {
+ set varValue [uplevel 1 [list set $varName]]
+
+ if {$objects && [isObjectHandle $varValue]} then {
+ unset -nocomplain properties
+ getAllProperties $varValue properties
+
+ lappend result [list $varName [array get properties]]
+ } else {
+ lappend result [list $varName $varValue]
+ }
+ }
+ }
+
+ return $result
+ }
proc enumerableToList { enumerable } {
set result [list]
if {[string length $enumerable] == 0 || $enumerable eq "null"} then {
@@ -2008,11 +2115,12 @@
# error strings, extract and return only the error message
# portion itself.
#
set patterns [list \
{System\.Data\.SQLite\.SQLiteException \(0x80004005\): (.+?) (?: )?at} \
- {System\.Data\.SQLite\.SQLiteException: (.+?) (?: )?at}]
+ {System\.Data\.SQLite\.SQLiteException: (.+?) (?: )?at} \
+ {Eagle\._Components\.Public\.ScriptException: (.+?) (?: )?at}]
foreach pattern $patterns {
if {[regexp -- $pattern $value dummy message]} then {
set message [string map [list \r\n \n] [string trim $message]]
set lines [split $message \n]
@@ -2082,10 +2190,17 @@
} else {
set database
}
}
+ #
+ # NOTE: Even though there is only one source of flags so far, they
+ # must be combined using the correct syntax for enumerated
+ # flag values for the .NET Framework.
+ #
+ set flags [combineFlags $flags ""]
+
#
# NOTE: Show (and log) the local connection flags and the associated
# data source or file name.
#
if {!$quiet} then {
Index: readme.htm
==================================================================
--- readme.htm
+++ readme.htm
@@ -213,10 +213,12 @@
- Updated to SQLite 3.13.0.
- Update the SQLiteConnection.EnableExtensions method to make use of the new SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION option, when available. ** Potentially Incompatible Change **
- Prevent the SQLiteCommand.ExecuteScalar method from throwing an exception when there are no result columns. ** Potentially Incompatible Change **
+ - Support per-connection customization for binding parameters and reading values, based on the database type name.
+ - Add TypeName property to the SQLiteParameter class.
- Add VerifyOnly method to the SQLiteCommand class.
- Add IsReadOnly method to the SQLiteConnection class.
1.0.101.0 - April 19, 2016
Index: www/news.wiki
==================================================================
--- www/news.wiki
+++ www/news.wiki
@@ -7,10 +7,12 @@
- Updated to [https://www.sqlite.org/releaselog/3_13_0.html|SQLite 3.13.0].
- Update the SQLiteConnection.EnableExtensions method to make use of the new SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION option, when available. ** Potentially Incompatible Change **
- Prevent the SQLiteCommand.ExecuteScalar method from throwing an exception when there are no result columns. ** Potentially Incompatible Change **
+ - Support per-connection customization for binding parameters and reading values, based on the database type name.
+ - Add TypeName property to the SQLiteParameter class.
- Add VerifyOnly method to the SQLiteCommand class.
- Add IsReadOnly method to the SQLiteConnection class.
1.0.101.0 - April 19, 2016