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)

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.