Index: Doc/Extra/Provider/version.html
==================================================================
--- Doc/Extra/Provider/version.html
+++ Doc/Extra/Provider/version.html
@@ -46,10 +46,13 @@
1.0.98.0 - August XX, 2015 (release scheduled)
- Updated to SQLite 3.8.11.
- Implement the Substring method for LINQ using the "substr" core SQL function. ** Potentially Incompatible Change **
- Remove errant semi-colons from the SQL used by LINQ to INSERT and then SELECT rows with composite primary keys. Fix for [9d353b0bd8].
+ - Change the base type for the SQLiteConnectionFlags enumeration to long integer. ** Potentially Incompatible Change **
+ - Improve exception handling in all native callbacks implemented in the SQLiteConnection class.
+ - Add Progress event and ProgressOps connection string property to enable raising progress events during long-running queries.
- Add VfsName connection string property to allow a non-default VFS to be used by the SQLite core library.
- Add BusyTimeout connection string property to set the busy timeout to be used by the SQLite core library.
- Enable integration with the ZipVFS extension.
1.0.97.0 - May 26, 2015
Index: System.Data.SQLite/SQLite3.cs
==================================================================
--- System.Data.SQLite/SQLite3.cs
+++ System.Data.SQLite/SQLite3.cs
@@ -819,10 +819,11 @@
n = UnsafeNativeMethods.sqlite3_step(stmt._sqlite_stmt);
}
if (n == SQLiteErrorCode.Row) return true;
if (n == SQLiteErrorCode.Done) return false;
+ if (n == SQLiteErrorCode.Interrupt) return false;
if (n != SQLiteErrorCode.Ok)
{
SQLiteErrorCode r;
@@ -955,12 +956,15 @@
using (SQLiteStatement tmp = Prepare(null, stmt._sqlStatement, null, (uint)(stmt._command._commandTimeout * 1000), ref str))
{
// Finalize the existing statement
stmt._sqlite_stmt.Dispose();
// Reassign a new statement pointer to the old statement and clear the temporary one
- stmt._sqlite_stmt = tmp._sqlite_stmt;
- tmp._sqlite_stmt = null;
+ if (tmp != null)
+ {
+ stmt._sqlite_stmt = tmp._sqlite_stmt;
+ tmp._sqlite_stmt = null;
+ }
// Reapply parameters
stmt.BindParameters();
}
return SQLiteErrorCode.Unknown; // Reset was OK, with schema change
@@ -1128,11 +1132,13 @@
SQLiteConnectionEventType.NewCriticalHandle, null, null,
null, null, statementHandle, strSql, new object[] { cnn,
strSql, previous, timeoutMS }));
}
- if (n == SQLiteErrorCode.Schema)
+ if (n == SQLiteErrorCode.Interrupt)
+ break;
+ else if (n == SQLiteErrorCode.Schema)
retries++;
else if (n == SQLiteErrorCode.Error)
{
if (String.Compare(GetLastError(), "near \"TYPES\": syntax error", StringComparison.OrdinalIgnoreCase) == 0)
{
@@ -1199,10 +1205,11 @@
System.Threading.Thread.Sleep(rnd.Next(1, 150));
}
}
}
+ if (n == SQLiteErrorCode.Interrupt) return null;
if (n != SQLiteErrorCode.Ok) throw new SQLiteException(n, GetLastError());
strRemain = UTF8ToString(ptr, len);
if (statementHandle != null) cmd = new SQLiteStatement(this, flags, statementHandle, strSql.Substring(0, strSql.Length - strRemain.Length), previous);
@@ -2406,10 +2413,15 @@
{
SQLiteErrorCode n = UnsafeNativeMethods.sqlite3_rekey(_sql, newPasswordBytes, (newPasswordBytes == null) ? 0 : newPasswordBytes.Length);
if (n != SQLiteErrorCode.Ok) throw new SQLiteException(n, GetLastError());
}
#endif
+
+ internal override void SetProgressHook(int nOps, SQLiteProgressCallback func)
+ {
+ UnsafeNativeMethods.sqlite3_progress_handler(_sql, nOps, func, IntPtr.Zero);
+ }
internal override void SetAuthorizerHook(SQLiteAuthorizerCallback func)
{
UnsafeNativeMethods.sqlite3_set_authorizer(_sql, func, IntPtr.Zero);
}
Index: System.Data.SQLite/SQLiteBase.cs
==================================================================
--- System.Data.SQLite/SQLiteBase.cs
+++ System.Data.SQLite/SQLiteBase.cs
@@ -385,10 +385,11 @@
#if INTEROP_CODEC || INTEROP_INCLUDE_SEE
internal abstract void SetPassword(byte[] passwordBytes);
internal abstract void ChangePassword(byte[] newPasswordBytes);
#endif
+ internal abstract void SetProgressHook(int nOps, SQLiteProgressCallback func);
internal abstract void SetAuthorizerHook(SQLiteAuthorizerCallback func);
internal abstract void SetUpdateHook(SQLiteUpdateCallback func);
internal abstract void SetCommitHook(SQLiteCommitCallback func);
internal abstract void SetTraceCallback(SQLiteTraceCallback func);
internal abstract void SetRollbackHook(SQLiteRollbackCallback func);
@@ -864,11 +865,11 @@
///
/// The extra behavioral flags that can be applied to a connection.
///
[Flags()]
- public enum SQLiteConnectionFlags
+ public enum SQLiteConnectionFlags : long
{
///
/// No extra flags.
///
None = 0x0,
@@ -1080,10 +1081,34 @@
/// type, take their into account as
/// well as that of the associated .
///
BindDateTimeWithKind = 0x10000000,
+ ///
+ /// If an exception is caught when raising the
+ /// event, the transaction
+ /// should be rolled back. If this is not specified, the transaction
+ /// will continue the commit process instead.
+ ///
+ RollbackOnException = 0x20000000,
+
+ ///
+ /// If an exception is caught when raising the
+ /// event, the action should
+ /// should be denied. If this is not specified, the action will be
+ /// allowed instead.
+ ///
+ DenyOnException = 0x40000000,
+
+ ///
+ /// If an exception is caught when raising the
+ /// event, the operation
+ /// should be interrupted. If this is not specified, the operation
+ /// will simply continue.
+ ///
+ InterruptOnException = 0x80000000,
+
///
/// 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).
///
Index: System.Data.SQLite/SQLiteConnection.cs
==================================================================
--- System.Data.SQLite/SQLiteConnection.cs
+++ System.Data.SQLite/SQLiteConnection.cs
@@ -407,10 +407,20 @@
/// schema being changed.
///
/// N
/// 3
///
+ /// -
+ /// ProgressOps
+ ///
+ /// The approximate number of virtual machine instructions between progress
+ /// events. In order for progress events to actually fire, the event handler
+ /// must be added to the event as well.
+ ///
+ /// N
+ /// 0
+ ///
///
///
public sealed partial class SQLiteConnection : DbConnection, ICloneable, IDisposable
{
#region Private Constants
@@ -465,10 +475,11 @@
private const bool DefaultForeignKeys = false;
private const bool DefaultEnlist = true;
private const bool DefaultSetDefaults = true;
internal const int DefaultPrepareRetries = 3;
private const string DefaultVfsName = null;
+ private const int DefaultProgressOps = 0;
#if INTEROP_INCLUDE_ZIPVFS
private const string ZipVfs_Automatic = "automatic";
private const string ZipVfs_V2 = "v2";
private const string ZipVfs_V3 = "v3";
@@ -635,10 +646,18 @@
/// normally only applies to preparation errors resulting from the database
/// schema being changed.
///
internal int _prepareRetries = DefaultPrepareRetries;
+ ///
+ /// The approximate number of virtual machine instructions between progress
+ /// events. In order for progress events to actually fire, the event handler
+ /// must be added to the event as
+ /// well. This value will only be used when opening the database.
+ ///
+ private int _progressOps = DefaultProgressOps;
+
///
/// Non-zero if the built-in (i.e. framework provided) connection string
/// parser should be used when opening the connection.
///
private bool _parseViaFramework;
@@ -645,16 +664,18 @@
internal bool _binaryGuid;
internal int _version;
+ private event SQLiteProgressEventHandler _progressHandler;
private event SQLiteAuthorizerEventHandler _authorizerHandler;
private event SQLiteUpdateEventHandler _updateHandler;
private event SQLiteCommitHandler _commitHandler;
private event SQLiteTraceEventHandler _traceHandler;
private event EventHandler _rollbackHandler;
+ private SQLiteProgressCallback _progressCallback;
private SQLiteAuthorizerCallback _authorizerCallback;
private SQLiteUpdateCallback _updateCallback;
private SQLiteCommitCallback _commitCallback;
private SQLiteTraceCallback _traceCallback;
private SQLiteRollbackCallback _rollbackCallback;
@@ -2700,10 +2721,11 @@
int maxPoolSize = Convert.ToInt32(FindKey(opts, "Max Pool Size", DefaultMaxPoolSize.ToString()), CultureInfo.InvariantCulture);
_defaultTimeout = Convert.ToInt32(FindKey(opts, "Default Timeout", DefaultConnectionTimeout.ToString()), CultureInfo.InvariantCulture);
_busyTimeout = Convert.ToInt32(FindKey(opts, "BusyTimeout", DefaultBusyTimeout.ToString()), CultureInfo.InvariantCulture);
_prepareRetries = Convert.ToInt32(FindKey(opts, "PrepareRetries", DefaultPrepareRetries.ToString()), CultureInfo.InvariantCulture);
+ _progressOps = Convert.ToInt32(FindKey(opts, "ProgressOps", DefaultProgressOps.ToString()), CultureInfo.InvariantCulture);
enumValue = TryParseEnum(typeof(IsolationLevel), FindKey(opts, "Default IsolationLevel", DefaultIsolationLevel.ToString()), true);
_defaultIsolation = (enumValue is IsolationLevel) ? (IsolationLevel)enumValue : DefaultIsolationLevel;
_defaultIsolation = GetEffectiveIsolationLevel(_defaultIsolation);
@@ -2866,10 +2888,13 @@
cmd.ExecuteNonQuery();
}
}
}
+ if (_progressHandler != null)
+ _sql.SetProgressHook(_progressOps, _progressCallback);
+
if (_authorizerHandler != null)
_sql.SetAuthorizerHook(_authorizerCallback);
if (_commitHandler != null)
_sql.SetCommitHook(_commitCallback);
@@ -2950,10 +2975,23 @@
public int PrepareRetries
{
get { CheckDisposed(); return _prepareRetries; }
set { CheckDisposed(); _prepareRetries = value; }
}
+
+ ///
+ /// The approximate number of virtual machine instructions between progress
+ /// events. In order for progress events to actually fire, the event handler
+ /// must be added to the event as
+ /// well. This value will only be used when the underlying native progress
+ /// callback needs to be changed.
+ ///
+ public int ProgressOps
+ {
+ get { CheckDisposed(); return _progressOps; }
+ set { CheckDisposed(); _progressOps = value; }
+ }
///
/// Non-zero if the built-in (i.e. framework provided) connection string
/// parser should be used when opening the connection.
///
@@ -4895,10 +4933,44 @@
tbl.EndLoadData();
tbl.AcceptChanges();
return tbl;
}
+
+ ///
+ /// This event is raised periodically during long running queries. Changing
+ /// the value of the property will
+ /// determine if the operation in progress will continue or be interrupted.
+ /// For the entire duration of the event, the associated connection and
+ /// statement objects must not be modified, either directly or indirectly, by
+ /// the called code.
+ ///
+ public event SQLiteProgressEventHandler Progress
+ {
+ add
+ {
+ CheckDisposed();
+
+ if (_progressHandler == null)
+ {
+ _progressCallback = new SQLiteProgressCallback(ProgressCallback);
+ if (_sql != null) _sql.SetProgressHook(_progressOps, _progressCallback);
+ }
+ _progressHandler += value;
+ }
+ remove
+ {
+ CheckDisposed();
+
+ _progressHandler -= value;
+ if (_progressHandler == null)
+ {
+ if (_sql != null) _sql.SetProgressHook(0, null);
+ _progressCallback = null;
+ }
+ }
+ }
///
/// This event is raised whenever SQLite encounters an action covered by the
/// authorizer during query preparation. Changing the value of the
/// property will determine if
@@ -4959,37 +5031,145 @@
if (_sql != null) _sql.SetUpdateHook(null);
_updateCallback = null;
}
}
}
+
+ private SQLiteProgressReturnCode ProgressCallback(
+ IntPtr pUserData /* NOT USED: Always IntPtr.Zero. */
+ )
+ {
+ try
+ {
+ ProgressEventArgs eventArgs = new ProgressEventArgs(
+ pUserData, SQLiteProgressReturnCode.Continue);
+
+ if (_progressHandler != null)
+ _progressHandler(this, eventArgs);
+
+ return eventArgs.ReturnCode;
+ }
+ catch (Exception e) /* NOTE: Must catch ALL. */
+ {
+ try
+ {
+ if ((_flags & SQLiteConnectionFlags.LogCallbackException) ==
+ SQLiteConnectionFlags.LogCallbackException)
+ {
+ SQLiteLog.LogMessage(SQLiteBase.COR_E_EXCEPTION,
+ String.Format(CultureInfo.CurrentCulture,
+ "Caught exception in \"Progress\" method: {1}",
+ e)); /* throw */
+ }
+ }
+ catch
+ {
+ // do nothing.
+ }
+ }
+
+ //
+ // NOTE: Should throwing an exception interrupt the operation?
+ //
+ if ((_flags & SQLiteConnectionFlags.InterruptOnException) ==
+ SQLiteConnectionFlags.InterruptOnException)
+ {
+ return SQLiteProgressReturnCode.Interrupt;
+ }
+ else
+ {
+ return SQLiteProgressReturnCode.Continue;
+ }
+ }
private SQLiteAuthorizerReturnCode AuthorizerCallback(
- IntPtr pUserData,
+ IntPtr pUserData, /* NOT USED: Always IntPtr.Zero. */
SQLiteAuthorizerActionCode actionCode,
IntPtr pArgument1,
IntPtr pArgument2,
IntPtr pDatabase,
IntPtr pAuthContext)
{
- AuthorizerEventArgs eventArgs = new AuthorizerEventArgs(pUserData, actionCode,
- SQLiteBase.UTF8ToString(pArgument1, -1), SQLiteBase.UTF8ToString(pArgument2, -1),
- SQLiteBase.UTF8ToString(pDatabase, -1), SQLiteBase.UTF8ToString(pAuthContext, -1),
- SQLiteAuthorizerReturnCode.Ok);
-
- if (_authorizerHandler != null)
- _authorizerHandler(this, eventArgs);
-
- return eventArgs.ReturnCode;
- }
-
- private void UpdateCallback(IntPtr puser, int type, IntPtr database, IntPtr table, Int64 rowid)
- {
- _updateHandler(this, new UpdateEventArgs(
- SQLiteBase.UTF8ToString(database, -1),
- SQLiteBase.UTF8ToString(table, -1),
- (UpdateEventType)type,
- rowid));
+ try
+ {
+ AuthorizerEventArgs eventArgs = new AuthorizerEventArgs(pUserData, actionCode,
+ SQLiteBase.UTF8ToString(pArgument1, -1), SQLiteBase.UTF8ToString(pArgument2, -1),
+ SQLiteBase.UTF8ToString(pDatabase, -1), SQLiteBase.UTF8ToString(pAuthContext, -1),
+ SQLiteAuthorizerReturnCode.Ok);
+
+ if (_authorizerHandler != null)
+ _authorizerHandler(this, eventArgs);
+
+ return eventArgs.ReturnCode;
+ }
+ catch (Exception e) /* NOTE: Must catch ALL. */
+ {
+ try
+ {
+ if ((_flags & SQLiteConnectionFlags.LogCallbackException) ==
+ SQLiteConnectionFlags.LogCallbackException)
+ {
+ SQLiteLog.LogMessage(SQLiteBase.COR_E_EXCEPTION,
+ String.Format(CultureInfo.CurrentCulture,
+ "Caught exception in \"Authorize\" method: {1}",
+ e)); /* throw */
+ }
+ }
+ catch
+ {
+ // do nothing.
+ }
+ }
+
+ //
+ // NOTE: Should throwing an exception deny the action?
+ //
+ if ((_flags & SQLiteConnectionFlags.DenyOnException) ==
+ SQLiteConnectionFlags.DenyOnException)
+ {
+ return SQLiteAuthorizerReturnCode.Deny;
+ }
+ else
+ {
+ return SQLiteAuthorizerReturnCode.Ok;
+ }
+ }
+
+ private void UpdateCallback(
+ IntPtr puser, /* NOT USED */
+ int type,
+ IntPtr database,
+ IntPtr table,
+ Int64 rowid
+ )
+ {
+ try
+ {
+ _updateHandler(this, new UpdateEventArgs(
+ SQLiteBase.UTF8ToString(database, -1),
+ SQLiteBase.UTF8ToString(table, -1),
+ (UpdateEventType)type,
+ rowid));
+ }
+ catch (Exception e) /* NOTE: Must catch ALL. */
+ {
+ try
+ {
+ if ((_flags & SQLiteConnectionFlags.LogCallbackException) ==
+ SQLiteConnectionFlags.LogCallbackException)
+ {
+ SQLiteLog.LogMessage(SQLiteBase.COR_E_EXCEPTION,
+ String.Format(CultureInfo.CurrentCulture,
+ "Caught exception in \"Update\" method: {1}",
+ e)); /* throw */
+ }
+ }
+ catch
+ {
+ // do nothing.
+ }
+ }
}
///
/// This event is raised whenever SQLite is committing a transaction.
/// Return non-zero to trigger a rollback.
@@ -5048,14 +5228,39 @@
_traceCallback = null;
}
}
}
- private void TraceCallback(IntPtr puser, IntPtr statement)
+ private void TraceCallback(
+ IntPtr puser, /* NOT USED */
+ IntPtr statement
+ )
{
- _traceHandler(this, new TraceEventArgs(
- SQLiteBase.UTF8ToString(statement, -1)));
+ try
+ {
+ if (_traceHandler != null)
+ _traceHandler(this, new TraceEventArgs(
+ SQLiteBase.UTF8ToString(statement, -1)));
+ }
+ catch (Exception e) /* NOTE: Must catch ALL. */
+ {
+ try
+ {
+ if ((_flags & SQLiteConnectionFlags.LogCallbackException) ==
+ SQLiteConnectionFlags.LogCallbackException)
+ {
+ SQLiteLog.LogMessage(SQLiteBase.COR_E_EXCEPTION,
+ String.Format(CultureInfo.CurrentCulture,
+ "Caught exception in \"Trace\" method: {1}",
+ e)); /* throw */
+ }
+ }
+ catch
+ {
+ // do nothing.
+ }
+ }
}
///
/// This event is raised whenever SQLite is rolling back a transaction.
///
@@ -5083,23 +5288,84 @@
_rollbackCallback = null;
}
}
}
-
- private int CommitCallback(IntPtr parg)
+ private int CommitCallback(
+ IntPtr parg /* NOT USED */
+ )
{
- CommitEventArgs e = new CommitEventArgs();
- _commitHandler(this, e);
- return (e.AbortTransaction == true) ? 1 : 0;
+ try
+ {
+ CommitEventArgs e = new CommitEventArgs();
+
+ if (_commitHandler != null)
+ _commitHandler(this, e);
+
+ return (e.AbortTransaction == true) ? 1 : 0;
+ }
+ catch (Exception e) /* NOTE: Must catch ALL. */
+ {
+ try
+ {
+ if ((_flags & SQLiteConnectionFlags.LogCallbackException) ==
+ SQLiteConnectionFlags.LogCallbackException)
+ {
+ SQLiteLog.LogMessage(SQLiteBase.COR_E_EXCEPTION,
+ String.Format(CultureInfo.CurrentCulture,
+ "Caught exception in \"Commit\" method: {1}",
+ e)); /* throw */
+ }
+ }
+ catch
+ {
+ // do nothing.
+ }
+ }
+
+ //
+ // NOTE: Should throwing an exception rollback the transaction?
+ //
+ if ((_flags & SQLiteConnectionFlags.RollbackOnException) ==
+ SQLiteConnectionFlags.RollbackOnException)
+ {
+ return 1; // rollback
+ }
+ else
+ {
+ return 0; // commit
+ }
}
- private void RollbackCallback(IntPtr parg)
+ private void RollbackCallback(
+ IntPtr parg /* NOT USED */
+ )
{
- _rollbackHandler(this, EventArgs.Empty);
+ try
+ {
+ if (_rollbackHandler != null)
+ _rollbackHandler(this, EventArgs.Empty);
+ }
+ catch (Exception e) /* NOTE: Must catch ALL. */
+ {
+ try
+ {
+ if ((_flags & SQLiteConnectionFlags.LogCallbackException) ==
+ SQLiteConnectionFlags.LogCallbackException)
+ {
+ SQLiteLog.LogMessage(SQLiteBase.COR_E_EXCEPTION,
+ String.Format(CultureInfo.CurrentCulture,
+ "Caught exception in \"Rollback\" method: {1}",
+ e)); /* throw */
+ }
+ }
+ catch
+ {
+ // do nothing.
+ }
+ }
}
-
}
///
/// The I/O file cache flushing behavior for the connection
///
@@ -5117,10 +5383,15 @@
/// Use the default operating system's file flushing, SQLite does not explicitly flush the file buffers after writing
///
Off = 2,
}
+#if !PLATFORM_COMPACTFRAMEWORK
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+#endif
+ internal delegate SQLiteProgressReturnCode SQLiteProgressCallback(IntPtr pUserData);
+
#if !PLATFORM_COMPACTFRAMEWORK
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
#endif
internal delegate SQLiteAuthorizerReturnCode SQLiteAuthorizerCallback(
IntPtr pUserData,
@@ -5149,10 +5420,20 @@
#if !PLATFORM_COMPACTFRAMEWORK
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
#endif
internal delegate void SQLiteRollbackCallback(IntPtr puser);
+ ///
+ /// Raised each time the number of virtual machine instructions is
+ /// approximately equal to the value of the
+ /// property.
+ ///
+ /// The connection performing the operation.
+ /// A that contains the
+ /// event data.
+ public delegate void SQLiteProgressEventHandler(object sender, ProgressEventArgs e);
+
///
/// Raised when authorization is required to perform an action contained
/// within a SQL query.
///
/// The connection performing the action.
@@ -5226,10 +5507,54 @@
int remainingPages,
int totalPages,
bool retry
);
#endregion
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+
+ public class ProgressEventArgs : EventArgs
+ {
+ ///
+ /// The user-defined native data associated with this event. Currently,
+ /// this will always contain the value of .
+ ///
+ public readonly IntPtr UserData;
+
+ ///
+ /// The return code for the current call into the progress callback.
+ ///
+ public SQLiteProgressReturnCode ReturnCode;
+
+ ///
+ /// Constructs an instance of this class with default property values.
+ ///
+ private ProgressEventArgs()
+ {
+ this.UserData = IntPtr.Zero;
+ this.ReturnCode = SQLiteProgressReturnCode.Continue;
+ }
+
+ ///
+ /// Constructs an instance of this class with specific property values.
+ ///
+ ///
+ /// The user-defined native data associated with this event.
+ ///
+ ///
+ /// The progress return code.
+ ///
+ internal ProgressEventArgs(
+ IntPtr pUserData,
+ SQLiteProgressReturnCode returnCode
+ )
+ : this()
+ {
+ this.UserData = pUserData;
+ this.ReturnCode = returnCode;
+ }
+ }
///////////////////////////////////////////////////////////////////////////////////////////////
///
/// The data associated with a call into the authorizer.
Index: System.Data.SQLite/SQLiteConnectionStringBuilder.cs
==================================================================
--- System.Data.SQLite/SQLiteConnectionStringBuilder.cs
+++ System.Data.SQLite/SQLiteConnectionStringBuilder.cs
@@ -291,10 +291,33 @@
set
{
this["prepareretries"] = value;
}
}
+
+ ///
+ /// Gets/sets the approximate number of virtual machine instructions between
+ /// progress events. In order for progress events to actually fire, the event
+ /// handler must be added to the event
+ /// as well.
+ ///
+ [DisplayName("Progress Ops")]
+ [Browsable(true)]
+ [DefaultValue(0)]
+ public int ProgressOps
+ {
+ get
+ {
+ object value;
+ TryGetValue("progressops", out value);
+ return Convert.ToInt32(value, CultureInfo.CurrentCulture);
+ }
+ set
+ {
+ this["progressops"] = value;
+ }
+ }
///
/// Determines whether or not the connection will automatically participate
/// in the current distributed transaction (if one exists)
///
Index: System.Data.SQLite/SQLiteConvert.cs
==================================================================
--- System.Data.SQLite/SQLiteConvert.cs
+++ System.Data.SQLite/SQLiteConvert.cs
@@ -2613,10 +2613,26 @@
/// A recursive query will be executed. The action-specific arguments
/// are two null values.
///
Recursive = 33
}
+
+ ///
+ /// The possible return codes for the progress callback.
+ ///
+ public enum SQLiteProgressReturnCode /* int */
+ {
+ ///
+ /// The operation should continue.
+ ///
+ Continue = 0,
+
+ ///
+ /// The operation should be interrupted.
+ ///
+ Interrupt = 1
+ }
///
/// The return code for the current call into the authorizer.
///
public enum SQLiteAuthorizerReturnCode
Index: System.Data.SQLite/UnsafeNativeMethods.cs
==================================================================
--- System.Data.SQLite/UnsafeNativeMethods.cs
+++ System.Data.SQLite/UnsafeNativeMethods.cs
@@ -2124,10 +2124,17 @@
[DllImport(SQLITE_DLL)]
#endif
internal static extern void zipvfsInit_v3(int regDflt);
#endif
+#if !PLATFORM_COMPACTFRAMEWORK
+ [DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)]
+#else
+ [DllImport(SQLITE_DLL)]
+#endif
+ internal static extern void sqlite3_progress_handler(IntPtr db, int ops, SQLiteProgressCallback func, IntPtr pvUser);
+
#if !PLATFORM_COMPACTFRAMEWORK
[DllImport(SQLITE_DLL, CallingConvention = CallingConvention.Cdecl)]
#else
[DllImport(SQLITE_DLL)]
#endif
Index: Tests/basic.eagle
==================================================================
--- Tests/basic.eagle
+++ Tests/basic.eagle
@@ -1052,20 +1052,20 @@
Password "Page Size" "Max Page Count" "Cache Size" \
DateTimeFormat DateTimeKind DateTimeFormatString \
BaseSchemaName "Journal Mode" "Default IsolationLevel" \
"Foreign Keys" Flags SetDefaults ToFullPath HexPassword \
DefaultDbType DefaultTypeName NoSharedFlags PrepareRetries \
- ZipVfsVersion VfsName BusyTimeout]
+ ZipVfsVersion VfsName BusyTimeout ProgressOps]
set values [list null 3 Normal True False \
True test.db test.db file:test.db 60 \
False True False True \
secret 4096 1024 8192 \
UnixEpoch Utc yyyy-MM-dd sqlite_schema \
Memory Serializable False \
Default False False 736563726574 String \
- TEXT True 20 v2 test 1000]
+ TEXT True 20 v2 test 1000 2000]
set propertyNames [list null Version SyncMode UseUTF16Encoding Pooling \
BinaryGUID DataSource Uri FullUri DefaultTimeout \
Enlist FailIfMissing LegacyFormat ReadOnly \
Password PageSize MaxPageCount CacheSize \
@@ -1072,11 +1072,11 @@
DateTimeFormat DateTimeKind DateTimeFormatString \
BaseSchemaName JournalMode DefaultIsolationLevel \
ForeignKeys Flags SetDefaults ToFullPath \
HexPassword DefaultDbType DefaultTypeName \
NoSharedFlags PrepareRetries ZipVfsVersion \
- VfsName BusyTimeout]
+ VfsName BusyTimeout ProgressOps]
foreach key $keys value $values propertyName $propertyNames {
set code [catch {
object invoke _Dynamic${id}.Test${id} GetConnectionString \
$key $value $propertyName
@@ -1107,11 +1107,12 @@
Keys=False\} 0 \{(?:Default|LogCallbackException),\
Flags=(?:Default|LogCallbackException)\} 0 \{False, SetDefaults=False\} 0\
\{False, ToFullPath=False\} 0 {736563726574, HexPassword=736563726574} 0\
\{String, DefaultDbType=String\} 0 \{TEXT, DefaultTypeName=TEXT\} 0 \{True,\
NoSharedFlags=True\} 0 \{20, PrepareRetries=20\} 0 \{v2, ZipVfsVersion=v2\} 0\
-\{test, VfsName=test\} 0 \{1000, BusyTimeout=1000\}$}}
+\{test, VfsName=test\} 0 \{1000, BusyTimeout=1000\} 0 \{2000,\
+ProgressOps=2000\}$}}
###############################################################################
runTest {test data-1.18 {SQLiteConvert ToDateTime (Julian Day)} -body {
set dateTime [object invoke -create System.Data.SQLite.SQLiteConvert \
ADDED Tests/progress.eagle
Index: Tests/progress.eagle
==================================================================
--- /dev/null
+++ Tests/progress.eagle
@@ -0,0 +1,327 @@
+###############################################################################
+#
+# progress.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
+
+###############################################################################
+
+runTest {test progress-1.1 {no progress without ProgressOps} -setup {
+ setupDb [set fileName progress-1.1.db]
+} -body {
+ set id [object invoke Interpreter.GetActive NextId]
+ set dataSource [file join [getDatabaseDirectory] $fileName]
+
+ set sql { \
+ CREATE TABLE t1(x INTEGER); \
+ INSERT INTO t1 (x) VALUES(1); \
+ INSERT INTO t1 (x) VALUES(2); \
+ INSERT INTO t1 (x) VALUES(3); \
+ INSERT INTO t1 (x) VALUES(4); \
+ SELECT x FROM t1 ORDER BY x; \
+ }
+
+ unset -nocomplain results errors
+
+ set code [compileCSharpWith [subst {
+ using System.Data.SQLite;
+
+ namespace _Dynamic${id}
+ {
+ public static class Test${id}
+ {
+ private static int count = 0;
+
+ ///////////////////////////////////////////////////////////////////////
+
+ public static void MyProgressHandler(
+ object sender,
+ ProgressEventArgs e
+ )
+ {
+ count++;
+ }
+
+ ///////////////////////////////////////////////////////////////////////
+
+ public static int Main()
+ {
+ using (SQLiteConnection connection = new SQLiteConnection(
+ "Data Source=${dataSource};[getFlagsProperty]"))
+ {
+ connection.Progress += MyProgressHandler;
+ connection.Open();
+
+ using (SQLiteCommand command = new SQLiteCommand("${sql}",
+ connection))
+ {
+ command.ExecuteNonQuery();
+ }
+ }
+
+ return count;
+ }
+ }
+ }
+ }] true true true results errors System.Data.SQLite.dll]
+
+ list $code $results \
+ [expr {[info exists errors] ? $errors : ""}] \
+ [expr {$code eq "Ok" ? [catch {
+ object invoke _Dynamic${id}.Test${id} Main
+ } result] : [set result ""]}] $result \
+ [expr {[string is integer -strict $result] && $result == 0 ? 1 : 0}]
+} -cleanup {
+ cleanupDb $fileName
+
+ unset -nocomplain result results errors code sql dataSource id db fileName
+} -constraints {eagle command.object monoBug28 command.sql compile.DATA SQLite\
+System.Data.SQLite compileCSharp} -match regexp -result {^Ok\
+System#CodeDom#Compiler#CompilerResults#\d+ \{\} 0 0 1$}}
+
+###############################################################################
+
+runTest {test progress-1.2 {simple progress counter} -setup {
+ setupDb [set fileName progress-1.2.db]
+} -body {
+ set id [object invoke Interpreter.GetActive NextId]
+ set dataSource [file join [getDatabaseDirectory] $fileName]
+
+ set sql { \
+ CREATE TABLE t1(x INTEGER); \
+ INSERT INTO t1 (x) VALUES(1); \
+ INSERT INTO t1 (x) VALUES(2); \
+ INSERT INTO t1 (x) VALUES(3); \
+ INSERT INTO t1 (x) VALUES(4); \
+ SELECT x FROM t1 ORDER BY x; \
+ }
+
+ unset -nocomplain results errors
+
+ set code [compileCSharpWith [subst {
+ using System.Data.SQLite;
+
+ namespace _Dynamic${id}
+ {
+ public static class Test${id}
+ {
+ private static int count = 0;
+
+ ///////////////////////////////////////////////////////////////////////
+
+ public static void MyProgressHandler(
+ object sender,
+ ProgressEventArgs e
+ )
+ {
+ count++;
+ }
+
+ ///////////////////////////////////////////////////////////////////////
+
+ public static int Main()
+ {
+ using (SQLiteConnection connection = new SQLiteConnection(
+ "Data Source=${dataSource};ProgressOps=1;[getFlagsProperty]"))
+ {
+ connection.Progress += MyProgressHandler;
+ connection.Open();
+
+ using (SQLiteCommand command = new SQLiteCommand("${sql}",
+ connection))
+ {
+ command.ExecuteNonQuery();
+ }
+ }
+
+ return count;
+ }
+ }
+ }
+ }] true true true results errors System.Data.SQLite.dll]
+
+ list $code $results \
+ [expr {[info exists errors] ? $errors : ""}] \
+ [expr {$code eq "Ok" ? [catch {
+ object invoke _Dynamic${id}.Test${id} Main
+ } result] : [set result ""]}] $result \
+ [expr {[string is integer -strict $result] && $result > 0 ? 1 : 0}]
+} -cleanup {
+ cleanupDb $fileName
+
+ unset -nocomplain result results errors code sql dataSource id db fileName
+} -constraints {eagle command.object monoBug28 command.sql compile.DATA SQLite\
+System.Data.SQLite compileCSharp} -match regexp -result {^Ok\
+System#CodeDom#Compiler#CompilerResults#\d+ \{\} 0 \d+ 1$}}
+
+###############################################################################
+
+runTest {test progress-1.3 {progress with interrupt} -setup {
+ setupDb [set fileName progress-1.3.db]
+} -body {
+ set id [object invoke Interpreter.GetActive NextId]
+ set dataSource [file join [getDatabaseDirectory] $fileName]
+
+ set sql { \
+ CREATE TABLE t1(x INTEGER); \
+ INSERT INTO t1 (x) VALUES(1); \
+ INSERT INTO t1 (x) VALUES(2); \
+ INSERT INTO t1 (x) VALUES(3); \
+ INSERT INTO t1 (x) VALUES(4); \
+ SELECT x FROM t1 ORDER BY x; \
+ }
+
+ unset -nocomplain results errors
+
+ set code [compileCSharpWith [subst {
+ using System.Data.SQLite;
+
+ namespace _Dynamic${id}
+ {
+ public static class Test${id}
+ {
+ private static int count = 0;
+
+ ///////////////////////////////////////////////////////////////////////
+
+ public static void MyProgressHandler(
+ object sender,
+ ProgressEventArgs e
+ )
+ {
+ count++;
+ e.ReturnCode = SQLiteProgressReturnCode.Interrupt;
+ }
+
+ ///////////////////////////////////////////////////////////////////////
+
+ public static int Main()
+ {
+ using (SQLiteConnection connection = new SQLiteConnection(
+ "Data Source=${dataSource};ProgressOps=1;[getFlagsProperty]"))
+ {
+ connection.Progress += MyProgressHandler;
+ connection.Open();
+
+ using (SQLiteCommand command = new SQLiteCommand("${sql}",
+ connection))
+ {
+ command.ExecuteNonQuery();
+ }
+ }
+
+ return count;
+ }
+ }
+ }
+ }] true true true results errors System.Data.SQLite.dll]
+
+ list $code $results \
+ [expr {[info exists errors] ? $errors : ""}] \
+ [expr {$code eq "Ok" ? [catch {
+ object invoke _Dynamic${id}.Test${id} Main
+ } result] : [set result ""]}] $result \
+ [expr {[string is integer -strict $result] && $result > 0 ? 1 : 0}]
+} -cleanup {
+ cleanupDb $fileName
+
+ unset -nocomplain result results errors code sql dataSource id db fileName
+} -constraints {eagle command.object monoBug28 command.sql compile.DATA SQLite\
+System.Data.SQLite compileCSharp} -match regexp -result {^Ok\
+System#CodeDom#Compiler#CompilerResults#\d+ \{\} 0 \d+ 1$}}
+
+###############################################################################
+
+runTest {test progress-1.4 {progress with exception} -setup {
+ setupDb [set fileName progress-1.4.db]
+} -body {
+ set id [object invoke Interpreter.GetActive NextId]
+ set dataSource [file join [getDatabaseDirectory] $fileName]
+
+ set sql { \
+ CREATE TABLE t1(x INTEGER); \
+ INSERT INTO t1 (x) VALUES(1); \
+ INSERT INTO t1 (x) VALUES(2); \
+ INSERT INTO t1 (x) VALUES(3); \
+ INSERT INTO t1 (x) VALUES(4); \
+ SELECT x FROM t1 ORDER BY x; \
+ }
+
+ unset -nocomplain results errors
+
+ set code [compileCSharpWith [subst {
+ using System;
+ using System.Data.SQLite;
+
+ namespace _Dynamic${id}
+ {
+ public static class Test${id}
+ {
+ private static int count = 0;
+
+ ///////////////////////////////////////////////////////////////////////
+
+ public static void MyProgressHandler(
+ object sender,
+ ProgressEventArgs e
+ )
+ {
+ count++;
+ throw new Exception();
+ }
+
+ ///////////////////////////////////////////////////////////////////////
+
+ public static int Main()
+ {
+ using (SQLiteConnection connection = new SQLiteConnection(
+ "Data Source=${dataSource};ProgressOps=1;[getFlagsProperty]"))
+ {
+ connection.Progress += MyProgressHandler;
+ connection.Open();
+
+ using (SQLiteCommand command = new SQLiteCommand("${sql}",
+ connection))
+ {
+ command.ExecuteNonQuery();
+ }
+ }
+
+ return count;
+ }
+ }
+ }
+ }] true true true results errors System.Data.SQLite.dll]
+
+ list $code $results \
+ [expr {[info exists errors] ? $errors : ""}] \
+ [expr {$code eq "Ok" ? [catch {
+ object invoke _Dynamic${id}.Test${id} Main
+ } result] : [set result ""]}] $result \
+ [expr {[string is integer -strict $result] && $result > 0 ? 1 : 0}]
+} -cleanup {
+ cleanupDb $fileName
+
+ unset -nocomplain result results errors code sql dataSource id db fileName
+} -constraints {eagle command.object monoBug28 command.sql compile.DATA SQLite\
+System.Data.SQLite compileCSharp} -match regexp -result {^Ok\
+System#CodeDom#Compiler#CompilerResults#\d+ \{\} 0 \d+ 1$}}
+
+###############################################################################
+
+runSQLiteTestEpilogue
+runTestEpilogue
Index: readme.htm
==================================================================
--- readme.htm
+++ readme.htm
@@ -213,10 +213,13 @@
- Updated to SQLite 3.8.11.
- Implement the Substring method for LINQ using the "substr" core SQL function. ** Potentially Incompatible Change **
- Remove errant semi-colons from the SQL used by LINQ to INSERT and then SELECT rows with composite primary keys. Fix for [9d353b0bd8].
+ - Change the base type for the SQLiteConnectionFlags enumeration to long integer. ** Potentially Incompatible Change **
+ - Improve exception handling in all native callbacks implemented in the SQLiteConnection class.
+ - Add Progress event and ProgressOps connection string property to enable raising progress events during long-running queries.
- Add VfsName connection string property to allow a non-default VFS to be used by the SQLite core library.
- Add BusyTimeout connection string property to set the busy timeout to be used by the SQLite core library.
- Enable integration with the ZipVFS extension.
Index: www/news.wiki
==================================================================
--- www/news.wiki
+++ www/news.wiki
@@ -7,10 +7,13 @@
- Updated to [https://www.sqlite.org/draft/releaselog/3_8_11.html|SQLite 3.8.11].
- Implement the Substring method for LINQ using the "substr" core SQL function. ** Potentially Incompatible Change **
- Remove errant semi-colons from the SQL used by LINQ to INSERT and then SELECT rows with composite primary keys. Fix for [9d353b0bd8].
+ - Change the base type for the SQLiteConnectionFlags enumeration to long integer. ** Potentially Incompatible Change **
+ - Improve exception handling in all native callbacks implemented in the SQLiteConnection class.
+ - Add Progress event and ProgressOps connection string property to enable raising progress events during long-running queries.
- Add VfsName connection string property to allow a non-default VFS to be used by the SQLite core library.
- Add BusyTimeout connection string property to set the busy timeout to be used by the SQLite core library.
- Enable integration with the [http://www.hwaci.com/sw/sqlite/zipvfs.html|ZipVFS] extension.