From 49ba18ea0fb25dd99bbd8041b3b1d6f8409e2e1c Mon Sep 17 00:00:00 2001 From: Carlo Sirna Date: Tue, 20 Jun 2023 22:12:28 +0200 Subject: [PATCH 1/4] First implementation (for SqliteConnection. async queries are still to be done) --- .gitignore | 1 + src/SQLite.cs | 145 ++++++++++++++++++++++++--- tests/ApiDiff/ApiDiff.csproj | 6 +- tests/ApiDiff/app.config | 3 + tests/SQLite.Tests/CancelableTest.cs | 116 +++++++++++++++++++++ 5 files changed, 257 insertions(+), 14 deletions(-) create mode 100644 tests/ApiDiff/app.config create mode 100644 tests/SQLite.Tests/CancelableTest.cs diff --git a/.gitignore b/.gitignore index ce044810b..80a1309ec 100644 --- a/.gitignore +++ b/.gitignore @@ -265,3 +265,4 @@ paket-files/ # CodeRush .cr/ +*.ncrunchsolution diff --git a/src/SQLite.cs b/src/SQLite.cs index 5b6941c3b..893a07add 100644 --- a/src/SQLite.cs +++ b/src/SQLite.cs @@ -49,6 +49,8 @@ using Sqlite3BackupHandle = SQLitePCL.sqlite3_backup; using Sqlite3Statement = SQLitePCL.sqlite3_stmt; using Sqlite3 = SQLitePCL.raw; +using System.Threading.Tasks; +using System.Runtime.CompilerServices; #else using Sqlite3DatabaseHandle = System.IntPtr; using Sqlite3BackupHandle = System.IntPtr; @@ -56,6 +58,10 @@ #endif #pragma warning disable 1591 // XML Doc Comments +#pragma warning disable IDE0034 // Simplify 'default' expression +#pragma warning disable IDE0011 // Add braces +#pragma warning disable IDE0032 // Use auto property +#pragma warning disable IDE0075 // Simplify conditional expression namespace SQLite { @@ -159,7 +165,7 @@ public enum CreateFlags FullTextSearch4 = 0x200 } - public interface ISQLiteConnection + public interface ISQLiteConnection : IDisposable { Sqlite3DatabaseHandle Handle { get; } string DatabasePath { get; } @@ -209,13 +215,14 @@ CreateTablesResult CreateTables (CreateFlags createFlags = Cr where T5 : new(); CreateTablesResult CreateTables (CreateFlags createFlags = CreateFlags.None, params Type[] types); IEnumerable DeferredQuery (string query, params object[] args) where T : new(); + IEnumerable DeferredQuery (CancellationToken tok, string query, params object[] args) where T : new(); IEnumerable DeferredQuery (TableMapping map, string query, params object[] args); + IEnumerable DeferredQuery (CancellationToken cancellationToken, TableMapping map, string query, params object[] args); int Delete (object objectToDelete); int Delete (object primaryKey); int Delete (object primaryKey, TableMapping map); int DeleteAll (); int DeleteAll (TableMapping map); - void Dispose (); int DropTable (); int DropTable (TableMapping map); void EnableLoadExtension (bool enabled); @@ -243,8 +250,11 @@ CreateTablesResult CreateTables (CreateFlags createFlags = Cr int InsertOrReplace (object obj); int InsertOrReplace (object obj, Type objType); List Query (string query, params object[] args) where T : new(); + List Query (CancellationToken cancellationToken, string query, params object[] args) where T : new(); List Query (TableMapping map, string query, params object[] args); + List Query (CancellationToken cancellationToken, TableMapping map, string query, params object[] args); List QueryScalars (string query, params object[] args); + List QueryScalars (CancellationToken cancellationToken, string query, params object[] args); void Release (string savepoint); void Rollback (); void RollbackTo (string savepoint); @@ -380,6 +390,7 @@ public SQLiteConnection (string databasePath, SQLiteOpenFlags openFlags, bool st /// public SQLiteConnection (SQLiteConnectionString connectionString) { + if (connectionString == null) throw new ArgumentNullException (nameof (connectionString)); if (connectionString.DatabasePath == null) @@ -1105,6 +1116,12 @@ public T ExecuteScalar (string query, params object[] args) return cmd.ExecuteQuery (); } + public List Query (CancellationToken cancellationToken, string query, params object[] args) where T : new() + { + var cmd = CreateCommand (query, args); + return cmd.CancelableExecuteQuery (cancellationToken); + } + /// /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?' /// in the command text for each of the arguments and then executes that command. @@ -1122,7 +1139,13 @@ public T ExecuteScalar (string query, params object[] args) public List QueryScalars (string query, params object[] args) { var cmd = CreateCommand (query, args); - return cmd.ExecuteQueryScalars ().ToList (); + return cmd.ExecuteQueryScalars (null).ToList (); + } + + public List QueryScalars (CancellationToken cancellationToken, string query, params object[] args) + { + var cmd = CreateCommand (query, args); + return cmd.ExecuteQueryScalars (cancellationToken).ToList (); } /// @@ -1149,6 +1172,12 @@ public List QueryScalars (string query, params object[] args) return cmd.ExecuteDeferredQuery (); } + public IEnumerable DeferredQuery (CancellationToken tok, string query, params object[] args) where T : new() + { + var cmd = CreateCommand (query, args); + return cmd.CancelableExecuteDeferredQuery (tok); + } + /// /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?' /// in the command text for each of the arguments and then executes that command. @@ -1174,6 +1203,11 @@ public List Query (TableMapping map, string query, params object[] args) var cmd = CreateCommand (query, args); return cmd.ExecuteQuery (map); } + public List Query (CancellationToken cancellationToken, TableMapping map, string query, params object[] args) + { + var cmd = CreateCommand (query, args); + return cmd.CancelableExecuteQuery (map,cancellationToken); + } /// /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?' @@ -1203,6 +1237,11 @@ public IEnumerable DeferredQuery (TableMapping map, string query, params var cmd = CreateCommand (query, args); return cmd.ExecuteDeferredQuery (map); } + public IEnumerable DeferredQuery (CancellationToken cancellationToken, TableMapping map, string query, params object[] args) + { + var cmd = CreateCommand (query, args); + return cmd.CancelableExecuteDeferredQuery (cancellationToken,map); + } /// /// Returns a queryable interface to the table represented by the given type. @@ -2563,6 +2602,7 @@ public TableMapping (Type type, CreateFlags createFlags = CreateFlags.None) TableName = (tableAttr != null && !string.IsNullOrEmpty (tableAttr.Name)) ? tableAttr.Name : MappedType.Name; WithoutRowId = tableAttr != null ? tableAttr.WithoutRowId : false; + var members = GetPublicMembers(type); var cols = new List(members.Count); foreach(var m in members) @@ -3028,7 +3068,6 @@ public partial class SQLiteCommand private List _bindings; public string CommandText { get; set; } - public SQLiteCommand (SQLiteConnection conn) { _conn = conn; @@ -3067,6 +3106,15 @@ public IEnumerable ExecuteDeferredQuery () { return ExecuteDeferredQuery (_conn.GetMapping (typeof (T))); } + public IEnumerable CancelableExecuteDeferredQuery (CancellationToken cancellationToken) + { + return CancelableExecuteDeferredQuery (cancellationToken, _conn.GetMapping (typeof (T))); + } + + public List CancelableExecuteQuery (CancellationToken cancellationToken) + { + return CancelableExecuteDeferredQuery (cancellationToken, _conn.GetMapping (typeof (T))).ToList (); + } public List ExecuteQuery () { @@ -3078,6 +3126,10 @@ public List ExecuteQuery (TableMapping map) return ExecuteDeferredQuery (map).ToList (); } + public List CancelableExecuteQuery (TableMapping map, CancellationToken cancellationToken) + { + return CancelableExecuteDeferredQuery (cancellationToken, map).ToList (); + } /// /// Invoked every time an instance is loaded from the database. /// @@ -3094,6 +3146,11 @@ protected virtual void OnInstanceCreated (object obj) } public IEnumerable ExecuteDeferredQuery (TableMapping map) + { + return CancelableExecuteDeferredQuery (null, map); + } + + public IEnumerable CancelableExecuteDeferredQuery (CancellationToken? cancTok, TableMapping map) { if (_conn.Trace) { _conn.Tracer?.Invoke ("Executing Query: " + this); @@ -3101,6 +3158,10 @@ public IEnumerable ExecuteDeferredQuery (TableMapping map) var stmt = Prepare (); try { + if (cancTok != null) + cancTok.Value.Register (() => { + SQLite3.Interrupt (_conn.Handle); + }); var cols = new TableMapping.Column[SQLite3.ColumnCount (stmt)]; var fastColumnSetters = new Action[SQLite3.ColumnCount (stmt)]; @@ -3129,7 +3190,17 @@ public IEnumerable ExecuteDeferredQuery (TableMapping map) } } - while (SQLite3.Step (stmt) == SQLite3.Result.Row) { + while (true) { + var r = SQLite3.Step (stmt); + if (r == SQLite3.Result.Done) + break; + + if (cancTok != null) + cancTok.Value.ThrowIfCancellationRequested (); + + if (r != SQLite3.Result.Row) + throw SQLiteException.New (r, SQLite3.GetErrmsg (_conn.Handle)); + var obj = Activator.CreateInstance (map.MappedType); for (int i = 0; i < cols.Length; i++) { if (cols[i] == null) @@ -3164,6 +3235,7 @@ public T ExecuteScalar () var stmt = Prepare (); try { + var r = SQLite3.Step (stmt); if (r == SQLite3.Result.Row) { var colType = SQLite3.ColumnType (stmt, 0); @@ -3185,7 +3257,7 @@ public T ExecuteScalar () return val; } - public IEnumerable ExecuteQueryScalars () + public IEnumerable ExecuteQueryScalars (CancellationToken? cancTok) { if (_conn.Trace) { _conn.Tracer?.Invoke ("Executing Query: " + this); @@ -3195,7 +3267,21 @@ public IEnumerable ExecuteQueryScalars () if (SQLite3.ColumnCount (stmt) < 1) { throw new InvalidOperationException ("QueryScalars should return at least one column"); } - while (SQLite3.Step (stmt) == SQLite3.Result.Row) { + + if (cancTok != null) + cancTok.Value.Register (() => { + SQLite3.Interrupt (_conn.Handle); + }); + + while (true) { + var r = SQLite3.Step (stmt); + if (r == SQLite3.Result.Done) + break; + if (cancTok != null) + cancTok.Value.ThrowIfCancellationRequested (); + if (r != SQLite3.Result.Row) + throw SQLiteException.New (r, SQLite3.GetErrmsg (_conn.Handle)); + var colType = SQLite3.ColumnType (stmt, 0); var val = ReadCol (stmt, 0, colType, typeof (T)); if (val == null) { @@ -3812,6 +3898,7 @@ public class TableQuery : BaseTableQuery, IEnumerable Expression _joinSelector; Expression _selector; + CancellationToken? _cancelToken; TableQuery (SQLiteConnection conn, TableMapping table) { @@ -3841,6 +3928,7 @@ public TableQuery Clone () q._joinOuterKeySelector = _joinOuterKeySelector; q._joinSelector = _joinSelector; q._selector = _selector; + q._cancelToken = _cancelToken; return q; } @@ -3861,6 +3949,13 @@ public TableQuery Where (Expression> predExpr) } } + public TableQuery CancelToken(CancellationToken? tok) + { + var q = Clone (); + q._cancelToken = tok; + return q; + } + /// /// Delete all the rows that match this query. /// @@ -4383,10 +4478,18 @@ public int Count (Expression> predExpr) public IEnumerator GetEnumerator () { + var cmd = GenerateCommand ("*"); + if (_cancelToken != null) { + if (!_deferred) + return cmd.CancelableExecuteQuery (_cancelToken.Value).GetEnumerator (); + + return cmd.CancelableExecuteDeferredQuery (_cancelToken.Value).GetEnumerator (); + } + if (!_deferred) - return GenerateCommand ("*").ExecuteQuery ().GetEnumerator (); + return cmd.ExecuteQuery ().GetEnumerator (); - return GenerateCommand ("*").ExecuteDeferredQuery ().GetEnumerator (); + return cmd.ExecuteDeferredQuery ().GetEnumerator (); } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator () @@ -4399,7 +4502,11 @@ System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator () /// public List ToList () { - return GenerateCommand ("*").ExecuteQuery (); + var cmd =GenerateCommand ("*"); + if (_cancelToken != null) + return cmd.CancelableExecuteQuery (_cancelToken.Value); + else + return cmd.ExecuteQuery(); } /// @@ -4407,7 +4514,11 @@ public List ToList () /// public T[] ToArray () { - return GenerateCommand ("*").ExecuteQuery ().ToArray (); + var cmd = GenerateCommand ("*"); + if (_cancelToken != null) + return cmd.CancelableExecuteQuery (_cancelToken.Value).ToArray (); + else + return cmd.ExecuteQuery ().ToArray (); } /// @@ -4608,7 +4719,10 @@ public static IntPtr Prepare2 (IntPtr db, string query) return stmt; } - [DllImport(LibraryPath, EntryPoint = "sqlite3_step", CallingConvention=CallingConvention.Cdecl)] + [DllImport (LibraryPath, EntryPoint = "sqlite3_interrupt", CallingConvention = CallingConvention.Cdecl)] + public static extern void Interrupt(IntPtr db); + + [DllImport (LibraryPath, EntryPoint = "sqlite3_step", CallingConvention=CallingConvention.Cdecl)] public static extern Result Step (IntPtr stmt); [DllImport(LibraryPath, EntryPoint = "sqlite3_reset", CallingConvention=CallingConvention.Cdecl)] @@ -4772,6 +4886,13 @@ public static Result Step (Sqlite3Statement stmt) return (Result)Sqlite3.sqlite3_step (stmt); } + public static void Interrupt(Sqlite3DatabaseHandle db) + { + Sqlite3.sqlite3_interrupt(db); + } + + + public static Result Reset (Sqlite3Statement stmt) { return (Result)Sqlite3.sqlite3_reset (stmt); diff --git a/tests/ApiDiff/ApiDiff.csproj b/tests/ApiDiff/ApiDiff.csproj index 577d53938..841d33eb9 100644 --- a/tests/ApiDiff/ApiDiff.csproj +++ b/tests/ApiDiff/ApiDiff.csproj @@ -1,4 +1,4 @@ - + Debug @@ -9,7 +9,8 @@ Exe ApiDiff ApiDiff - v4.6.1 + v4.8 + true @@ -45,6 +46,7 @@ + diff --git a/tests/ApiDiff/app.config b/tests/ApiDiff/app.config new file mode 100644 index 000000000..3e0e37cfc --- /dev/null +++ b/tests/ApiDiff/app.config @@ -0,0 +1,3 @@ + + + diff --git a/tests/SQLite.Tests/CancelableTest.cs b/tests/SQLite.Tests/CancelableTest.cs new file mode 100644 index 000000000..1383488cc --- /dev/null +++ b/tests/SQLite.Tests/CancelableTest.cs @@ -0,0 +1,116 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using NUnit.Framework; + +namespace SQLite.Tests +{ + public class CancelableTest + { + [Test] + public async Task CancelableQueryQueryScalarsTest() + { + using (var conn = new SQLiteConnection (":memory:") as ISQLiteConnection) { + var CancTokSource = new CancellationTokenSource (); + var tok = CancTokSource.Token; + // here I am launching the query in a separate task. + // this query is extremely slow: Its execution time is way beyond my patience limit + var task = Task.Run (() => { + var extremelySlowQuery = + @"WITH RECURSIVE qry(n) AS ( + SELECT 1 + UNION ALL + SELECT n + 1 FROM qry WHERE n < 10000000 + ) + SELECT * FROM qry where n = 100000000"; + + var result = conn.QueryScalars (tok, extremelySlowQuery); + }); + + Exception e = null; + try { + await Task.Delay (1000); // let the query run for a bit + CancTokSource.Cancel (); // and then we ask the query to stop (this will make a OperationCanceledException to be raised in the context of the task) + await task; // then we wait the task to be completed + } + catch (Exception ex) { + e = ex; + } + Assert.That (e, Is.InstanceOf ()); + } + } + + [Test] + public async Task CancelableQueryTest () + { + using (var conn = new SQLiteConnection (":memory:")) { + var CancTokSource = new CancellationTokenSource (); + var tok = CancTokSource.Token; + // here I am launching the query in a separate task. + // this query is extremely slow: Its execution time is way beyond my patience limit + var task = Task.Run (() => { + var extremelySlowQuery = + @"WITH RECURSIVE qry(n) AS ( + SELECT 1 as n + UNION ALL + SELECT n + 1 as n FROM qry WHERE n < 100000000 + ) + SELECT n as fld1, n as fld2 FROM qry where n = 100000"; + var result = conn.Query<(long fld1,long fld2)>(tok, extremelySlowQuery); + }); + + Exception e = null; + try { + await Task.Delay (1000); // let the query run for a bit + CancTokSource.Cancel (); // and then we ask the query to stop (this will make a OperationCanceledException to be raised in the context of the task) + await task; // then we wait the task to be completed + } + catch (Exception ex) { + e = ex; + } + Assert.That (e, Is.InstanceOf ()); + } + } + + + public class MyRecType + { + public long fld1 { get; set; } + public long fld2 { get; set; } + } + + [Test] + public async Task CancelableTableTest () + { + using (var conn = new SQLiteConnection (":memory:")) { + var CancTokSource = new CancellationTokenSource (); + var tok = CancTokSource.Token; + // here I am launching the query in a separate task. + // this query is extremely slow: Its execution time is way beyond my patience limit + + conn.CreateTable (); + + for (var i= 0; i< 1000000; i++) + conn.Insert (new MyRecType { fld1 = i, fld2 = i }); + + var task = Task.Run (() => { + var result = conn.Table().CancelToken (tok).ToList(); + }); + + Exception e = null; + try { + await Task.Delay (10); // let the query run for a bit + CancTokSource.Cancel (); // and then we ask the query to stop (this will make a OperationCanceledException to be raised in the context of the task) + await task; // then we wait the task to be completed + } + catch (Exception ex) { + e = ex; + } + Assert.That (e, Is.InstanceOf ()); + } + } + + } +} From 84e861fba911064378aa81ec10ced90c4db20b61 Mon Sep 17 00:00:00 2001 From: Carlo Sirna Date: Wed, 21 Jun 2023 14:04:05 +0200 Subject: [PATCH 2/4] i wasn't unregistering the sqlite3_interrupt call from the cancellation token. improved test cases --- .gitignore | 1 + src/SQLite.cs | 158 +++++++++++++-------------- tests/SQLite.Tests/CancelableTest.cs | 90 ++++++++++----- 3 files changed, 139 insertions(+), 110 deletions(-) diff --git a/.gitignore b/.gitignore index 80a1309ec..5d7b5efd5 100644 --- a/.gitignore +++ b/.gitignore @@ -266,3 +266,4 @@ paket-files/ .cr/ *.ncrunchsolution +*.ncrunchproject diff --git a/src/SQLite.cs b/src/SQLite.cs index 893a07add..faf161ebd 100644 --- a/src/SQLite.cs +++ b/src/SQLite.cs @@ -3155,72 +3155,70 @@ public IEnumerable CancelableExecuteDeferredQuery (CancellationToken? canc if (_conn.Trace) { _conn.Tracer?.Invoke ("Executing Query: " + this); } + + cancTok?.ThrowIfCancellationRequested (); + using (var interruptCallbackRegistration = cancTok?.Register (() => SQLite3.Interrupt (_conn.Handle))) { + var stmt = Prepare (); + try { - var stmt = Prepare (); - try { - if (cancTok != null) - cancTok.Value.Register (() => { - SQLite3.Interrupt (_conn.Handle); - }); - var cols = new TableMapping.Column[SQLite3.ColumnCount (stmt)]; - var fastColumnSetters = new Action[SQLite3.ColumnCount (stmt)]; + var cols = new TableMapping.Column[SQLite3.ColumnCount (stmt)]; + var fastColumnSetters = new Action[SQLite3.ColumnCount (stmt)]; - if (map.Method == TableMapping.MapMethod.ByPosition) - { - Array.Copy(map.Columns, cols, Math.Min(cols.Length, map.Columns.Length)); - } - else if (map.Method == TableMapping.MapMethod.ByName) { - MethodInfo getSetter = null; - if (typeof(T) != map.MappedType) { - getSetter = typeof(FastColumnSetter) - .GetMethod (nameof(FastColumnSetter.GetFastSetter), - BindingFlags.NonPublic | BindingFlags.Static).MakeGenericMethod (map.MappedType); + if (map.Method == TableMapping.MapMethod.ByPosition) { + Array.Copy (map.Columns, cols, Math.Min (cols.Length, map.Columns.Length)); } + else if (map.Method == TableMapping.MapMethod.ByName) { + MethodInfo getSetter = null; + if (typeof (T) != map.MappedType) { + getSetter = typeof (FastColumnSetter) + .GetMethod (nameof (FastColumnSetter.GetFastSetter), + BindingFlags.NonPublic | BindingFlags.Static).MakeGenericMethod (map.MappedType); + } - for (int i = 0; i < cols.Length; i++) { - var name = SQLite3.ColumnName16 (stmt, i); - cols[i] = map.FindColumn (name); - if (cols[i] != null) - if (getSetter != null) { - fastColumnSetters[i] = (Action)getSetter.Invoke(null, new object[]{ _conn, cols[i]}); - } - else { - fastColumnSetters[i] = FastColumnSetter.GetFastSetter(_conn, cols[i]); - } + for (int i = 0; i < cols.Length; i++) { + var name = SQLite3.ColumnName16 (stmt, i); + cols[i] = map.FindColumn (name); + if (cols[i] != null) + if (getSetter != null) { + fastColumnSetters[i] = (Action)getSetter.Invoke (null, new object[] { _conn, cols[i] }); + } + else { + fastColumnSetters[i] = FastColumnSetter.GetFastSetter (_conn, cols[i]); + } + } } - } - while (true) { - var r = SQLite3.Step (stmt); - if (r == SQLite3.Result.Done) - break; + while (true) { + var r = SQLite3.Step (stmt); + if (r == SQLite3.Result.Done) + break; - if (cancTok != null) - cancTok.Value.ThrowIfCancellationRequested (); + cancTok?.ThrowIfCancellationRequested (); - if (r != SQLite3.Result.Row) - throw SQLiteException.New (r, SQLite3.GetErrmsg (_conn.Handle)); + if (r != SQLite3.Result.Row) + throw SQLiteException.New (r, SQLite3.GetErrmsg (_conn.Handle)); - var obj = Activator.CreateInstance (map.MappedType); - for (int i = 0; i < cols.Length; i++) { - if (cols[i] == null) - continue; + var obj = Activator.CreateInstance (map.MappedType); + for (int i = 0; i < cols.Length; i++) { + if (cols[i] == null) + continue; - if (fastColumnSetters[i] != null) { - fastColumnSetters[i].Invoke (obj, stmt, i); - } - else { - var colType = SQLite3.ColumnType (stmt, i); - var val = ReadCol (stmt, i, colType, cols[i].ColumnType); - cols[i].SetValue (obj, val); + if (fastColumnSetters[i] != null) { + fastColumnSetters[i].Invoke (obj, stmt, i); + } + else { + var colType = SQLite3.ColumnType (stmt, i); + var val = ReadCol (stmt, i, colType, cols[i].ColumnType); + cols[i].SetValue (obj, val); + } } + OnInstanceCreated (obj); + yield return (T)obj; } - OnInstanceCreated (obj); - yield return (T)obj; } - } - finally { - SQLite3.Finalize (stmt); + finally { + SQLite3.Finalize (stmt); + } } } @@ -3262,38 +3260,36 @@ public IEnumerable ExecuteQueryScalars (CancellationToken? cancTok) if (_conn.Trace) { _conn.Tracer?.Invoke ("Executing Query: " + this); } - var stmt = Prepare (); - try { - if (SQLite3.ColumnCount (stmt) < 1) { - throw new InvalidOperationException ("QueryScalars should return at least one column"); - } + cancTok?.ThrowIfCancellationRequested (); - if (cancTok != null) - cancTok.Value.Register (() => { - SQLite3.Interrupt (_conn.Handle); - }); - - while (true) { - var r = SQLite3.Step (stmt); - if (r == SQLite3.Result.Done) - break; - if (cancTok != null) - cancTok.Value.ThrowIfCancellationRequested (); - if (r != SQLite3.Result.Row) - throw SQLiteException.New (r, SQLite3.GetErrmsg (_conn.Handle)); - - var colType = SQLite3.ColumnType (stmt, 0); - var val = ReadCol (stmt, 0, colType, typeof (T)); - if (val == null) { - yield return default (T); + using (var interruptCallbackRegistration = cancTok?.Register (() => SQLite3.Interrupt (_conn.Handle))) { + var stmt = Prepare (); + try { + if (SQLite3.ColumnCount (stmt) < 1) { + throw new InvalidOperationException ("QueryScalars should return at least one column"); } - else { - yield return (T)val; + + while (true) { + var r = SQLite3.Step (stmt); + if (r == SQLite3.Result.Done) + break; + cancTok?.ThrowIfCancellationRequested (); + if (r != SQLite3.Result.Row) + throw SQLiteException.New (r, SQLite3.GetErrmsg (_conn.Handle)); + + var colType = SQLite3.ColumnType (stmt, 0); + var val = ReadCol (stmt, 0, colType, typeof (T)); + if (val == null) { + yield return default (T); + } + else { + yield return (T)val; + } } } - } - finally { - Finalize (stmt); + finally { + Finalize (stmt); + } } } diff --git a/tests/SQLite.Tests/CancelableTest.cs b/tests/SQLite.Tests/CancelableTest.cs index 1383488cc..de9125ca5 100644 --- a/tests/SQLite.Tests/CancelableTest.cs +++ b/tests/SQLite.Tests/CancelableTest.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -7,16 +8,31 @@ namespace SQLite.Tests { + // all these test do run some heavy queries in a background task. + // these queries would take quite some time if they would be allowed to be run to complation, + // but each test, after having launched the query, it stops it by setting the CancellationToken. public class CancelableTest { + public static ISQLiteConnection CreateSqliteInMemoryDb() + { + //return new SQLiteConnection (":memory:"); this simpler version would make all tests run using the very same memory database, so test would not run in parallel + // this more complex way of creating a memory database allows to give a separate name to different memory databases + return new SQLiteConnection ($"file:memdb{Guid.NewGuid ().ToString ("N")}?mode=memory&cache=private"); + } + + [Test] - public async Task CancelableQueryQueryScalarsTest() + public async Task CancelableQueryQueryScalars_Test() { - using (var conn = new SQLiteConnection (":memory:") as ISQLiteConnection) { + using (var conn = CreateSqliteInMemoryDb()) { var CancTokSource = new CancellationTokenSource (); var tok = CancTokSource.Token; - // here I am launching the query in a separate task. - // this query is extremely slow: Its execution time is way beyond my patience limit + + // notice that this query takes ages before returning the first record. + // here I am actually testing that the execution is stopped at the "server side" + // by the sqlite3_interrupt api call, since we never enter in the internal + // "fetch next row" c# loop + var task = Task.Run (() => { var extremelySlowQuery = @"WITH RECURSIVE qry(n) AS ( @@ -31,9 +47,9 @@ SELECT n + 1 FROM qry WHERE n < 10000000 Exception e = null; try { - await Task.Delay (1000); // let the query run for a bit - CancTokSource.Cancel (); // and then we ask the query to stop (this will make a OperationCanceledException to be raised in the context of the task) - await task; // then we wait the task to be completed + await Task.Delay (300); // wait some time to be sure that the query has started + CancTokSource.Cancel (); + await task; } catch (Exception ex) { e = ex; @@ -43,13 +59,11 @@ SELECT n + 1 FROM qry WHERE n < 10000000 } [Test] - public async Task CancelableQueryTest () + public async Task CancelableQuery_Test () { - using (var conn = new SQLiteConnection (":memory:")) { + using (var conn = CreateSqliteInMemoryDb()) { var CancTokSource = new CancellationTokenSource (); var tok = CancTokSource.Token; - // here I am launching the query in a separate task. - // this query is extremely slow: Its execution time is way beyond my patience limit var task = Task.Run (() => { var extremelySlowQuery = @"WITH RECURSIVE qry(n) AS ( @@ -63,9 +77,9 @@ SELECT n + 1 as n FROM qry WHERE n < 100000000 Exception e = null; try { - await Task.Delay (1000); // let the query run for a bit - CancTokSource.Cancel (); // and then we ask the query to stop (this will make a OperationCanceledException to be raised in the context of the task) - await task; // then we wait the task to be completed + await Task.Delay (1000); + CancTokSource.Cancel (); + await task; } catch (Exception ex) { e = ex; @@ -75,6 +89,21 @@ SELECT n + 1 as n FROM qry WHERE n < 100000000 } + /// + /// this view will return millions of records, by using a recursive "with" + /// + public const string BIGVIEW_DEFCMD= + @"create view BIGVIEW as + WITH RECURSIVE qry(n) + AS ( + SELECT 1 as n + UNION ALL + SELECT n + 1 as n FROM qry WHERE n < 100000000 + ) + SELECT n as fld1, n as fld2 FROM qry"; + + // this entity maps bigview + [Table("BIGVIEW")] public class MyRecType { public long fld1 { get; set; } @@ -82,33 +111,36 @@ public class MyRecType } [Test] - public async Task CancelableTableTest () + public async Task CancelableTable_Test () { - using (var conn = new SQLiteConnection (":memory:")) { + using (var conn = CreateSqliteInMemoryDb()) { + // + conn.Execute (BIGVIEW_DEFCMD); + var CancTokSource = new CancellationTokenSource (); var tok = CancTokSource.Token; - // here I am launching the query in a separate task. - // this query is extremely slow: Its execution time is way beyond my patience limit - conn.CreateTable (); - - for (var i= 0; i< 1000000; i++) - conn.Insert (new MyRecType { fld1 = i, fld2 = i }); - - var task = Task.Run (() => { - var result = conn.Table().CancelToken (tok).ToList(); - }); + var task = Task.Run(() => { + // this query would take forever if we couldn't stop it + var result = + conn.Table() + .Where(x => x.fld1!=x.fld2) + .CancelToken (tok) + .ToList (); + }).ConfigureAwait(false); Exception e = null; try { - await Task.Delay (10); // let the query run for a bit - CancTokSource.Cancel (); // and then we ask the query to stop (this will make a OperationCanceledException to be raised in the context of the task) - await task; // then we wait the task to be completed + await Task.Delay (10); + CancTokSource.Cancel (); + await task; } catch (Exception ex) { e = ex; } + Assert.That (e, Is.InstanceOf ()); + } } From cba2f7e7cde9de6dc74550f51e5a0f666f292bca Mon Sep 17 00:00:00 2001 From: Carlo Sirna Date: Wed, 21 Jun 2023 19:17:32 +0200 Subject: [PATCH 3/4] Now also SqliteAsyncConnection is cancelable. made cancelable also other methods (not only queries) --- src/SQLite.cs | 603 +++++++++++++++++++-------- src/SQLiteAsync.cs | 265 +++++++++++- tests/SQLite.Tests/CancelableTest.cs | 266 ++++++++++-- 3 files changed, 897 insertions(+), 237 deletions(-) diff --git a/src/SQLite.cs b/src/SQLite.cs index faf161ebd..f810fe769 100644 --- a/src/SQLite.cs +++ b/src/SQLite.cs @@ -221,22 +221,26 @@ CreateTablesResult CreateTables (CreateFlags createFlags = Cr int Delete (object objectToDelete); int Delete (object primaryKey); int Delete (object primaryKey, TableMapping map); - int DeleteAll (); - int DeleteAll (TableMapping map); + int DeleteAll (CancellationToken? cancTok = null); + int DeleteAll (TableMapping map, CancellationToken? cancTok = null); int DropTable (); int DropTable (TableMapping map); void EnableLoadExtension (bool enabled); void EnableWriteAheadLogging (); int Execute (string query, params object[] args); + int Execute (CancellationToken canTok, string query, params object[] args); T ExecuteScalar (string query, params object[] args); + T ExecuteScalar (CancellationToken canTok, string query, params object[] args); T Find (object pk) where T : new(); object Find (object pk, TableMapping map); - T Find (Expression> predicate) where T : new(); + T Find (Expression> predicate, CancellationToken? cancTok = null) where T : new(); T FindWithQuery (string query, params object[] args) where T : new(); + T FindWithQuery (CancellationToken cancTok, string query, params object[] args) where T : new(); object FindWithQuery (TableMapping map, string query, params object[] args); + object FindWithQuery (CancellationToken cancTok, TableMapping map, string query, params object[] args); T Get (object pk) where T : new(); object Get (object pk, TableMapping map); - T Get (Expression> predicate) where T : new(); + T Get (Expression> predicate, CancellationToken? cancTok = null) where T : new(); TableMapping GetMapping (Type type, CreateFlags createFlags = CreateFlags.None); TableMapping GetMapping (CreateFlags createFlags = CreateFlags.None); List GetTableInfo (string tableName); @@ -515,8 +519,8 @@ public void EnableLoadExtension (bool enabled) static byte[] GetNullTerminatedUtf8 (string s) { var utf8Length = System.Text.Encoding.UTF8.GetByteCount (s); - var bytes = new byte [utf8Length + 1]; - utf8Length = System.Text.Encoding.UTF8.GetBytes(s, 0, s.Length, bytes, 0); + var bytes = new byte[utf8Length + 1]; + utf8Length = System.Text.Encoding.UTF8.GetBytes (s, 0, s.Length, bytes, 0); return bytes; } #endif @@ -1045,7 +1049,51 @@ public int Execute (string query, params object[] args) _sw.Start (); } - var r = cmd.ExecuteNonQuery (); + var r = cmd.ExecuteNonQuery (null); + + if (TimeExecution) { + _sw.Stop (); + _elapsedMilliseconds += _sw.ElapsedMilliseconds; + Tracer?.Invoke (string.Format ("Finished in {0} ms ({1:0.0} s total)", _sw.ElapsedMilliseconds, _elapsedMilliseconds / 1000.0)); + } + + return r; + } + + + /// + /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?' + /// in the command text for each of the arguments and then executes that command. + /// Use this method instead of Query when you don't expect rows back. Such cases include + /// INSERTs, UPDATEs, and DELETEs. + /// You can set the Trace or TimeExecution properties of the connection + /// to profile execution. + /// + /// + /// a cancellation token that can be used to stop the execution of the query + /// + /// + /// The fully escaped SQL. + /// + /// + /// Arguments to substitute for the occurences of '?' in the query. + /// + /// + /// The number of rows modified in the database as a result of this execution. + /// + public int Execute (CancellationToken cancTok, string query, params object[] args) + { + var cmd = CreateCommand (query, args); + + if (TimeExecution) { + if (_sw == null) { + _sw = new Stopwatch (); + } + _sw.Reset (); + _sw.Start (); + } + + var r = cmd.ExecuteNonQuery (cancTok); if (TimeExecution) { _sw.Stop (); @@ -1084,7 +1132,50 @@ public T ExecuteScalar (string query, params object[] args) _sw.Start (); } - var r = cmd.ExecuteScalar (); + var r = cmd.ExecuteScalar (null); + + if (TimeExecution) { + _sw.Stop (); + _elapsedMilliseconds += _sw.ElapsedMilliseconds; + Tracer?.Invoke (string.Format ("Finished in {0} ms ({1:0.0} s total)", _sw.ElapsedMilliseconds, _elapsedMilliseconds / 1000.0)); + } + + return r; + } + + /// + /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?' + /// in the command text for each of the arguments and then executes that command. + /// Use this method when return primitive values. + /// You can set the Trace or TimeExecution properties of the connection + /// to profile execution. + /// + /// + /// a cancellation token that can be used to stop the execution of the query + /// + /// + /// The fully escaped SQL. + /// + /// + /// Arguments to substitute for the occurences of '?' in the query. + /// + /// + /// The number of rows modified in the database as a result of this execution. + /// + + public T ExecuteScalar (CancellationToken cancTok, string query, params object[] args) + { + var cmd = CreateCommand (query, args); + + if (TimeExecution) { + if (_sw == null) { + _sw = new Stopwatch (); + } + _sw.Reset (); + _sw.Start (); + } + + var r = cmd.ExecuteScalar (cancTok); if (TimeExecution) { _sw.Stop (); @@ -1095,6 +1186,7 @@ public T ExecuteScalar (string query, params object[] args) return r; } + /// /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?' /// in the command text for each of the arguments and then executes that command. @@ -1142,10 +1234,27 @@ public List QueryScalars (string query, params object[] args) return cmd.ExecuteQueryScalars (null).ToList (); } - public List QueryScalars (CancellationToken cancellationToken, string query, params object[] args) + /// + /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?' + /// in the command text for each of the arguments and then executes that command. + /// It returns the first column of each row of the result. + /// + /// + /// a cancellation token that can be used to stop the execution of the query + /// + /// + /// The fully escaped SQL. + /// + /// + /// Arguments to substitute for the occurences of '?' in the query. + /// + /// + /// An enumerable with one result for the first column of each row returned by the query. + /// + public List QueryScalars (CancellationToken cancTok, string query, params object[] args) { var cmd = CreateCommand (query, args); - return cmd.ExecuteQueryScalars (cancellationToken).ToList (); + return cmd.ExecuteQueryScalars (cancTok).ToList (); } /// @@ -1172,10 +1281,31 @@ public List QueryScalars (CancellationToken cancellationToken, string quer return cmd.ExecuteDeferredQuery (); } - public IEnumerable DeferredQuery (CancellationToken tok, string query, params object[] args) where T : new() + /// + /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?' + /// in the command text for each of the arguments and then executes that command. + /// It returns each row of the result using the mapping automatically generated for + /// the given type. + /// + /// + /// a cancellation token that can be used to stop the execution of the query + /// + /// + /// The fully escaped SQL. + /// + /// + /// Arguments to substitute for the occurences of '?' in the query. + /// + /// + /// An enumerable with one result for each row returned by the query. + /// The enumerator (retrieved by calling GetEnumerator() on the result of this method) + /// will call sqlite3_step on each call to MoveNext, so the database + /// connection must remain open for the lifetime of the enumerator. + /// + public IEnumerable DeferredQuery (CancellationToken cancTok, string query, params object[] args) where T : new() { var cmd = CreateCommand (query, args); - return cmd.CancelableExecuteDeferredQuery (tok); + return cmd.CancelableExecuteDeferredQuery (cancTok); } /// @@ -1203,10 +1333,34 @@ public List Query (TableMapping map, string query, params object[] args) var cmd = CreateCommand (query, args); return cmd.ExecuteQuery (map); } - public List Query (CancellationToken cancellationToken, TableMapping map, string query, params object[] args) + /// + /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?' + /// in the command text for each of the arguments and then executes that command. + /// It returns each row of the result using the specified mapping. This function is + /// only used by libraries in order to query the database via introspection. It is + /// normally not used. + /// + /// + /// a cancellation token that can be used to stop the execution of the query + /// + /// + /// A to use to convert the resulting rows + /// into objects. + /// + /// + /// The fully escaped SQL. + /// + /// + /// Arguments to substitute for the occurences of '?' in the query. + /// + /// + /// An enumerable with one result for each row returned by the query. + /// + + public List Query (CancellationToken cancTok, TableMapping map, string query, params object[] args) { var cmd = CreateCommand (query, args); - return cmd.CancelableExecuteQuery (map,cancellationToken); + return cmd.CancelableExecuteQuery (map, cancTok); } /// @@ -1237,10 +1391,36 @@ public IEnumerable DeferredQuery (TableMapping map, string query, params var cmd = CreateCommand (query, args); return cmd.ExecuteDeferredQuery (map); } - public IEnumerable DeferredQuery (CancellationToken cancellationToken, TableMapping map, string query, params object[] args) + /// + /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?' + /// in the command text for each of the arguments and then executes that command. + /// It returns each row of the result using the specified mapping. This function is + /// only used by libraries in order to query the database via introspection. It is + /// normally not used. + /// + /// + /// a cancellation token that can be used to stop the execution of the query + /// + /// + /// A to use to convert the resulting rows + /// into objects. + /// + /// + /// The fully escaped SQL. + /// + /// + /// Arguments to substitute for the occurences of '?' in the query. + /// + /// + /// An enumerable with one result for each row returned by the query. + /// The enumerator (retrieved by calling GetEnumerator() on the result of this method) + /// will call sqlite3_step on each call to MoveNext, so the database + /// connection must remain open for the lifetime of the enumerator. + /// + public IEnumerable DeferredQuery (CancellationToken cancTok, TableMapping map, string query, params object[] args) { var cmd = CreateCommand (query, args); - return cmd.CancelableExecuteDeferredQuery (cancellationToken,map); + return cmd.CancelableExecuteDeferredQuery (cancTok, map); } /// @@ -1300,13 +1480,19 @@ public object Get (object pk, TableMapping map) /// /// A predicate for which object to find. /// + /// + /// a cancellation token that can be used to stop the execution of the query + /// /// /// The object that matches the given predicate. Throws a not found exception /// if the object is not found. /// - public T Get (Expression> predicate) where T : new() + public T Get (Expression> predicate, CancellationToken? cancTok = null) where T : new() { - return Table ().Where (predicate).First (); + var qry = Table ().Where (predicate); + if (cancTok != null) + qry = qry.CancelToken (cancTok.Value); + return qry.First (); } /// @@ -1354,13 +1540,19 @@ public object Find (object pk, TableMapping map) /// /// A predicate for which object to find. /// + /// + /// an optional cancellation token to stop the execution of the query + /// /// /// The object that matches the given predicate or null /// if the object is not found. /// - public T Find (Expression> predicate) where T : new() + public T Find (Expression> predicate, CancellationToken? cancTok = null) where T : new() { - return Table ().Where (predicate).FirstOrDefault (); + var qry = Table ().Where (predicate); + if (cancTok != null) + qry = qry.CancelToken (cancTok.Value); + return qry.FirstOrDefault (); } /// @@ -1382,6 +1574,28 @@ public object Find (object pk, TableMapping map) return Query (query, args).FirstOrDefault (); } + /// + /// Attempts to retrieve the first object that matches the query from the table + /// associated with the specified type. + /// + /// + /// a cancellation token that can be used to stop the execution of the query + /// + /// + /// The fully escaped SQL. + /// + /// + /// Arguments to substitute for the occurences of '?' in the query. + /// + /// + /// The object that matches the given predicate or null + /// if the object is not found. + /// + public T FindWithQuery (CancellationToken cancTok, string query, params object[] args) where T : new() + { + return Query (cancTok, query, args).FirstOrDefault (); + } + /// /// Attempts to retrieve the first object that matches the query from the table /// associated with the specified type. @@ -1403,6 +1617,30 @@ public object FindWithQuery (TableMapping map, string query, params object[] arg { return Query (map, query, args).FirstOrDefault (); } + /// + /// Attempts to retrieve the first object that matches the query from the table + /// associated with the specified type. + /// + /// + /// a cancellation token that can be used to stop the execution of the query + /// + /// + /// The TableMapping used to identify the table. + /// + /// + /// The fully escaped SQL. + /// + /// + /// Arguments to substitute for the occurences of '?' in the query. + /// + /// + /// The object that matches the given predicate or null + /// if the object is not found. + /// + public object FindWithQuery (CancellationToken cancTok, TableMapping map, string query, params object[] args) + { + return Query (cancTok, map, query, args).FirstOrDefault (); + } /// /// Whether has been called and the database is waiting for a . @@ -1589,7 +1827,7 @@ void DoSavePointExecute (string savepoint, string cmd) #elif SILVERLIGHT _transactionDepth = depth; #else - Thread.VolatileWrite (ref _transactionDepth, depth); + Thread.VolatileWrite (ref _transactionDepth, depth); #endif Execute (cmd + savepoint); return; @@ -2151,16 +2389,19 @@ public int Delete (object primaryKey, TableMapping map) /// WARNING WARNING: Let me repeat. It deletes ALL the objects from the /// specified table. Do you really want to do that? /// + /// + /// an optional cancellation token to stop the execution of the query + /// /// /// The number of objects deleted. /// /// /// The type of objects to delete. /// - public int DeleteAll () + public int DeleteAll (CancellationToken? cancTok = null) { var map = GetMapping (typeof (T)); - return DeleteAll (map); + return DeleteAll (map, cancTok); } /// @@ -2171,13 +2412,21 @@ public int DeleteAll () /// /// The TableMapping used to identify the table. /// + /// + /// an optional cancellation token to stop the execution of the query + /// /// /// The number of objects deleted. /// - public int DeleteAll (TableMapping map) + public int DeleteAll (TableMapping map, CancellationToken? cancTok = null) { var query = string.Format ("delete from \"{0}\"", map.TableName); - var count = Execute (query); + int count; + if (cancTok != null) + count = Execute (cancTok.Value, query); + else + count = Execute (query); + if (count > 0) OnTableChanged (map, NotifyTableChangedAction.Delete); return count; @@ -2603,13 +2852,12 @@ public TableMapping (Type type, CreateFlags createFlags = CreateFlags.None) WithoutRowId = tableAttr != null ? tableAttr.WithoutRowId : false; - var members = GetPublicMembers(type); - var cols = new List(members.Count); - foreach(var m in members) - { - var ignore = m.IsDefined(typeof(IgnoreAttribute), true); - if(!ignore) - cols.Add(new Column(m, createFlags)); + var members = GetPublicMembers (type); + var cols = new List (members.Count); + foreach (var m in members) { + var ignore = m.IsDefined (typeof (IgnoreAttribute), true); + if (!ignore) + cols.Add (new Column (m, createFlags)); } Columns = cols.ToArray (); foreach (var c in Columns) { @@ -2635,47 +2883,46 @@ public TableMapping (Type type, CreateFlags createFlags = CreateFlags.None) _insertOrReplaceColumns = Columns.ToArray (); } - private IReadOnlyCollection GetPublicMembers(Type type) + private IReadOnlyCollection GetPublicMembers (Type type) { - if(type.Name.StartsWith("ValueTuple`")) - return GetFieldsFromValueTuple(type); + if (type.Name.StartsWith ("ValueTuple`")) + return GetFieldsFromValueTuple (type); - var members = new List(); - var memberNames = new HashSet(); - var newMembers = new List(); - do - { - var ti = type.GetTypeInfo(); - newMembers.Clear(); + var members = new List (); + var memberNames = new HashSet (); + var newMembers = new List (); + do { + var ti = type.GetTypeInfo (); + newMembers.Clear (); - newMembers.AddRange( + newMembers.AddRange ( from p in ti.DeclaredProperties - where !memberNames.Contains(p.Name) && + where !memberNames.Contains (p.Name) && p.CanRead && p.CanWrite && p.GetMethod != null && p.SetMethod != null && p.GetMethod.IsPublic && p.SetMethod.IsPublic && !p.GetMethod.IsStatic && !p.SetMethod.IsStatic select p); - members.AddRange(newMembers); - foreach(var m in newMembers) - memberNames.Add(m.Name); + members.AddRange (newMembers); + foreach (var m in newMembers) + memberNames.Add (m.Name); type = ti.BaseType; } - while(type != typeof(object)); + while (type != typeof (object)); return members; } - private IReadOnlyCollection GetFieldsFromValueTuple(Type type) + private IReadOnlyCollection GetFieldsFromValueTuple (Type type) { Method = MapMethod.ByPosition; - var fields = type.GetFields(); + var fields = type.GetFields (); // https://docs.microsoft.com/en-us/dotnet/api/system.valuetuple-8.rest - if(fields.Length >= 8) - throw new NotSupportedException("ValueTuple with more than 7 members not supported due to nesting; see https://docs.microsoft.com/en-us/dotnet/api/system.valuetuple-8.rest"); + if (fields.Length >= 8) + throw new NotSupportedException ("ValueTuple with more than 7 members not supported due to nesting; see https://docs.microsoft.com/en-us/dotnet/api/system.valuetuple-8.rest"); return fields; } @@ -2709,8 +2956,8 @@ public Column FindColumnWithPropertyName (string propertyName) public Column FindColumn (string columnName) { - if(Method != MapMethod.ByName) - throw new InvalidOperationException($"This {nameof(TableMapping)} is not mapped by name, but {Method}."); + if (Method != MapMethod.ByName) + throw new InvalidOperationException ($"This {nameof (TableMapping)} is not mapped by name, but {Method}."); var exact = Columns.FirstOrDefault (c => c.Name.ToLower () == columnName.ToLower ()); return exact; @@ -2746,7 +2993,7 @@ public class Column public Column (MemberInfo member, CreateFlags createFlags = CreateFlags.None) { _member = member; - var memberType = GetMemberType(member); + var memberType = GetMemberType (member); var colAttr = member.CustomAttributes.FirstOrDefault (x => x.AttributeType == typeof (ColumnAttribute)); #if ENABLE_IL2CPP @@ -2784,46 +3031,46 @@ public Column (MemberInfo member, CreateFlags createFlags = CreateFlags.None) } public Column (PropertyInfo member, CreateFlags createFlags = CreateFlags.None) - : this((MemberInfo)member, createFlags) + : this ((MemberInfo)member, createFlags) { } public void SetValue (object obj, object val) { - if(_member is PropertyInfo propy) - { + if (_member is PropertyInfo propy) { if (val != null && ColumnType.GetTypeInfo ().IsEnum) propy.SetValue (obj, Enum.ToObject (ColumnType, val)); else propy.SetValue (obj, val); } - else if(_member is FieldInfo field) - { + else if (_member is FieldInfo field) { if (val != null && ColumnType.GetTypeInfo ().IsEnum) field.SetValue (obj, Enum.ToObject (ColumnType, val)); else field.SetValue (obj, val); } else - throw new InvalidProgramException("unreachable condition"); + throw new InvalidProgramException ("unreachable condition"); } public object GetValue (object obj) { - if(_member is PropertyInfo propy) - return propy.GetValue(obj); - else if(_member is FieldInfo field) - return field.GetValue(obj); + if (_member is PropertyInfo propy) + return propy.GetValue (obj); + else if (_member is FieldInfo field) + return field.GetValue (obj); else - throw new InvalidProgramException("unreachable condition"); + throw new InvalidProgramException ("unreachable condition"); } - private static Type GetMemberType(MemberInfo m) + private static Type GetMemberType (MemberInfo m) { - switch(m.MemberType) - { - case MemberTypes.Property: return ((PropertyInfo)m).PropertyType; - case MemberTypes.Field: return ((FieldInfo)m).FieldType; - default: throw new InvalidProgramException($"{nameof(TableMapping)} supports properties or fields only."); + switch (m.MemberType) { + case MemberTypes.Property: + return ((PropertyInfo)m).PropertyType; + case MemberTypes.Field: + return ((FieldInfo)m).FieldType; + default: + throw new InvalidProgramException ($"{nameof (TableMapping)} supports properties or fields only."); } } } @@ -3054,7 +3301,7 @@ public static IEnumerable GetIndices (MemberInfo p) #endif } - public static int? MaxStringLength (PropertyInfo p) => MaxStringLength((MemberInfo)p); + public static int? MaxStringLength (PropertyInfo p) => MaxStringLength ((MemberInfo)p); public static bool IsMarkedNotNull (MemberInfo p) { @@ -3075,31 +3322,36 @@ public SQLiteCommand (SQLiteConnection conn) CommandText = ""; } - public int ExecuteNonQuery () + public int ExecuteNonQuery (CancellationToken? cancTok = null) { if (_conn.Trace) { _conn.Tracer?.Invoke ("Executing: " + this); } + cancTok?.ThrowIfCancellationRequested (); + using (var interruptCallbackRegistration = cancTok?.Register (() => SQLite3.Interrupt (_conn.Handle))) { - var r = SQLite3.Result.OK; - var stmt = Prepare (); - r = SQLite3.Step (stmt); - Finalize (stmt); - if (r == SQLite3.Result.Done) { - int rowsAffected = SQLite3.Changes (_conn.Handle); - return rowsAffected; - } - else if (r == SQLite3.Result.Error) { - string msg = SQLite3.GetErrmsg (_conn.Handle); - throw SQLiteException.New (r, msg); - } - else if (r == SQLite3.Result.Constraint) { - if (SQLite3.ExtendedErrCode (_conn.Handle) == SQLite3.ExtendedResult.ConstraintNotNull) { - throw NotNullConstraintViolationException.New (r, SQLite3.GetErrmsg (_conn.Handle)); + var r = SQLite3.Result.OK; + var stmt = Prepare (); + r = SQLite3.Step (stmt); + Finalize (stmt); + if (r == SQLite3.Result.Done) { + int rowsAffected = SQLite3.Changes (_conn.Handle); + return rowsAffected; + } + cancTok?.ThrowIfCancellationRequested (); + + if (r == SQLite3.Result.Error) { + string msg = SQLite3.GetErrmsg (_conn.Handle); + throw SQLiteException.New (r, msg); + } + else if (r == SQLite3.Result.Constraint) { + if (SQLite3.ExtendedErrCode (_conn.Handle) == SQLite3.ExtendedResult.ConstraintNotNull) { + throw NotNullConstraintViolationException.New (r, SQLite3.GetErrmsg (_conn.Handle)); + } } - } - throw SQLiteException.New (r, SQLite3.GetErrmsg (_conn.Handle)); + throw SQLiteException.New (r, SQLite3.GetErrmsg (_conn.Handle)); + } } public IEnumerable ExecuteDeferredQuery () @@ -3155,7 +3407,7 @@ public IEnumerable CancelableExecuteDeferredQuery (CancellationToken? canc if (_conn.Trace) { _conn.Tracer?.Invoke ("Executing Query: " + this); } - + cancTok?.ThrowIfCancellationRequested (); using (var interruptCallbackRegistration = cancTok?.Register (() => SQLite3.Interrupt (_conn.Handle))) { var stmt = Prepare (); @@ -3222,37 +3474,39 @@ public IEnumerable CancelableExecuteDeferredQuery (CancellationToken? canc } } - public T ExecuteScalar () + public T ExecuteScalar (CancellationToken? cancTok = null) { if (_conn.Trace) { _conn.Tracer?.Invoke ("Executing Query: " + this); } - T val = default (T); + cancTok?.ThrowIfCancellationRequested (); + using (var interruptCallbackRegistration = cancTok?.Register (() => SQLite3.Interrupt (_conn.Handle))) { - var stmt = Prepare (); + var stmt = Prepare (); + try { - try { + var r = SQLite3.Step (stmt); + if (r == SQLite3.Result.Done) + return default (T); - var r = SQLite3.Step (stmt); - if (r == SQLite3.Result.Row) { - var colType = SQLite3.ColumnType (stmt, 0); - var colval = ReadCol (stmt, 0, colType, typeof (T)); - if (colval != null) { - val = (T)colval; + if (r == SQLite3.Result.Row) { + var colType = SQLite3.ColumnType (stmt, 0); + var colval = ReadCol (stmt, 0, colType, typeof (T)); + if (colval != null) + return (T)colval; + else + return default (T); } - } - else if (r == SQLite3.Result.Done) { - } - else { + cancTok?.ThrowIfCancellationRequested (); + throw SQLiteException.New (r, SQLite3.GetErrmsg (_conn.Handle)); + + } + finally { + Finalize (stmt); } } - finally { - Finalize (stmt); - } - - return val; } public IEnumerable ExecuteQueryScalars (CancellationToken? cancTok) @@ -3261,7 +3515,6 @@ public IEnumerable ExecuteQueryScalars (CancellationToken? cancTok) _conn.Tracer?.Invoke ("Executing Query: " + this); } cancTok?.ThrowIfCancellationRequested (); - using (var interruptCallbackRegistration = cancTok?.Register (() => SQLite3.Interrupt (_conn.Handle))) { var stmt = Prepare (); try { @@ -3577,7 +3830,7 @@ internal static Action GetFastSetter (SQLiteCo }); } else if (clrType == typeof (Int32)) { - fastSetter = CreateNullableTypedSetterDelegate (column, (stmt, index)=>{ + fastSetter = CreateNullableTypedSetterDelegate (column, (stmt, index) => { return SQLite3.ColumnInt (stmt, index); }); } @@ -3593,7 +3846,7 @@ internal static Action GetFastSetter (SQLiteCo } else if (clrType == typeof (float)) { fastSetter = CreateNullableTypedSetterDelegate (column, (stmt, index) => { - return (float) SQLite3.ColumnDouble (stmt, index); + return (float)SQLite3.ColumnDouble (stmt, index); }); } else if (clrType == typeof (TimeSpan)) { @@ -3720,7 +3973,7 @@ internal static Action GetFastSetter (SQLiteCo /// A strongly-typed delegate private static Action CreateNullableTypedSetterDelegate (TableMapping.Column column, Func getColumnValue) where ColumnMemberType : struct { - var clrTypeInfo = column.PropertyInfo.PropertyType.GetTypeInfo(); + var clrTypeInfo = column.PropertyInfo.PropertyType.GetTypeInfo (); bool isNullable = false; if (clrTypeInfo.IsGenericType && clrTypeInfo.GetGenericTypeDefinition () == typeof (Nullable<>)) { @@ -3945,7 +4198,7 @@ public TableQuery Where (Expression> predExpr) } } - public TableQuery CancelToken(CancellationToken? tok) + public TableQuery CancelToken (CancellationToken? tok) { var q = Clone (); q._cancelToken = tok; @@ -3985,7 +4238,7 @@ public int Delete (Expression> predExpr) var command = Connection.CreateCommand (cmdText, args.ToArray ()); - int result = command.ExecuteNonQuery (); + int result = command.ExecuteNonQuery (_cancelToken); return result; } @@ -4461,7 +4714,7 @@ string GetSqlName (Expression expr) /// public int Count () { - return GenerateCommand ("count(*)").ExecuteScalar (); + return GenerateCommand ("count(*)").ExecuteScalar (_cancelToken); } /// @@ -4498,11 +4751,11 @@ System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator () /// public List ToList () { - var cmd =GenerateCommand ("*"); + var cmd = GenerateCommand ("*"); if (_cancelToken != null) - return cmd.CancelableExecuteQuery (_cancelToken.Value); + return cmd.CancelableExecuteQuery (_cancelToken.Value); else - return cmd.ExecuteQuery(); + return cmd.ExecuteQuery (); } /// @@ -4512,9 +4765,9 @@ public T[] ToArray () { var cmd = GenerateCommand ("*"); if (_cancelToken != null) - return cmd.CancelableExecuteQuery (_cancelToken.Value).ToArray (); + return cmd.CancelableExecuteQuery (_cancelToken.Value).ToArray (); else - return cmd.ExecuteQuery ().ToArray (); + return cmd.ExecuteQuery ().ToArray (); } /// @@ -4650,50 +4903,50 @@ public enum ConfigOption : int const string LibraryPath = "sqlite3"; #if !USE_CSHARP_SQLITE && !USE_WP8_NATIVE_SQLITE && !USE_SQLITEPCL_RAW - [DllImport(LibraryPath, EntryPoint = "sqlite3_threadsafe", CallingConvention=CallingConvention.Cdecl)] + [DllImport (LibraryPath, EntryPoint = "sqlite3_threadsafe", CallingConvention = CallingConvention.Cdecl)] public static extern int Threadsafe (); - [DllImport(LibraryPath, EntryPoint = "sqlite3_open", CallingConvention=CallingConvention.Cdecl)] - public static extern Result Open ([MarshalAs(UnmanagedType.LPStr)] string filename, out IntPtr db); + [DllImport (LibraryPath, EntryPoint = "sqlite3_open", CallingConvention = CallingConvention.Cdecl)] + public static extern Result Open ([MarshalAs (UnmanagedType.LPStr)] string filename, out IntPtr db); - [DllImport(LibraryPath, EntryPoint = "sqlite3_open_v2", CallingConvention=CallingConvention.Cdecl)] - public static extern Result Open ([MarshalAs(UnmanagedType.LPStr)] string filename, out IntPtr db, int flags, [MarshalAs (UnmanagedType.LPStr)] string zvfs); + [DllImport (LibraryPath, EntryPoint = "sqlite3_open_v2", CallingConvention = CallingConvention.Cdecl)] + public static extern Result Open ([MarshalAs (UnmanagedType.LPStr)] string filename, out IntPtr db, int flags, [MarshalAs (UnmanagedType.LPStr)] string zvfs); - [DllImport(LibraryPath, EntryPoint = "sqlite3_open_v2", CallingConvention = CallingConvention.Cdecl)] - public static extern Result Open(byte[] filename, out IntPtr db, int flags, [MarshalAs (UnmanagedType.LPStr)] string zvfs); + [DllImport (LibraryPath, EntryPoint = "sqlite3_open_v2", CallingConvention = CallingConvention.Cdecl)] + public static extern Result Open (byte[] filename, out IntPtr db, int flags, [MarshalAs (UnmanagedType.LPStr)] string zvfs); - [DllImport(LibraryPath, EntryPoint = "sqlite3_open16", CallingConvention = CallingConvention.Cdecl)] - public static extern Result Open16([MarshalAs(UnmanagedType.LPWStr)] string filename, out IntPtr db); + [DllImport (LibraryPath, EntryPoint = "sqlite3_open16", CallingConvention = CallingConvention.Cdecl)] + public static extern Result Open16 ([MarshalAs (UnmanagedType.LPWStr)] string filename, out IntPtr db); - [DllImport(LibraryPath, EntryPoint = "sqlite3_enable_load_extension", CallingConvention=CallingConvention.Cdecl)] + [DllImport (LibraryPath, EntryPoint = "sqlite3_enable_load_extension", CallingConvention = CallingConvention.Cdecl)] public static extern Result EnableLoadExtension (IntPtr db, int onoff); - [DllImport(LibraryPath, EntryPoint = "sqlite3_close", CallingConvention=CallingConvention.Cdecl)] + [DllImport (LibraryPath, EntryPoint = "sqlite3_close", CallingConvention = CallingConvention.Cdecl)] public static extern Result Close (IntPtr db); - [DllImport(LibraryPath, EntryPoint = "sqlite3_close_v2", CallingConvention = CallingConvention.Cdecl)] - public static extern Result Close2(IntPtr db); + [DllImport (LibraryPath, EntryPoint = "sqlite3_close_v2", CallingConvention = CallingConvention.Cdecl)] + public static extern Result Close2 (IntPtr db); - [DllImport(LibraryPath, EntryPoint = "sqlite3_initialize", CallingConvention=CallingConvention.Cdecl)] - public static extern Result Initialize(); + [DllImport (LibraryPath, EntryPoint = "sqlite3_initialize", CallingConvention = CallingConvention.Cdecl)] + public static extern Result Initialize (); - [DllImport(LibraryPath, EntryPoint = "sqlite3_shutdown", CallingConvention=CallingConvention.Cdecl)] - public static extern Result Shutdown(); + [DllImport (LibraryPath, EntryPoint = "sqlite3_shutdown", CallingConvention = CallingConvention.Cdecl)] + public static extern Result Shutdown (); - [DllImport(LibraryPath, EntryPoint = "sqlite3_config", CallingConvention=CallingConvention.Cdecl)] + [DllImport (LibraryPath, EntryPoint = "sqlite3_config", CallingConvention = CallingConvention.Cdecl)] public static extern Result Config (ConfigOption option); - [DllImport(LibraryPath, EntryPoint = "sqlite3_win32_set_directory", CallingConvention=CallingConvention.Cdecl, CharSet=CharSet.Unicode)] + [DllImport (LibraryPath, EntryPoint = "sqlite3_win32_set_directory", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)] public static extern int SetDirectory (uint directoryType, string directoryPath); - [DllImport(LibraryPath, EntryPoint = "sqlite3_busy_timeout", CallingConvention=CallingConvention.Cdecl)] + [DllImport (LibraryPath, EntryPoint = "sqlite3_busy_timeout", CallingConvention = CallingConvention.Cdecl)] public static extern Result BusyTimeout (IntPtr db, int milliseconds); - [DllImport(LibraryPath, EntryPoint = "sqlite3_changes", CallingConvention=CallingConvention.Cdecl)] + [DllImport (LibraryPath, EntryPoint = "sqlite3_changes", CallingConvention = CallingConvention.Cdecl)] public static extern int Changes (IntPtr db); - [DllImport(LibraryPath, EntryPoint = "sqlite3_prepare_v2", CallingConvention=CallingConvention.Cdecl)] - public static extern Result Prepare2 (IntPtr db, [MarshalAs(UnmanagedType.LPStr)] string sql, int numBytes, out IntPtr stmt, IntPtr pzTail); + [DllImport (LibraryPath, EntryPoint = "sqlite3_prepare_v2", CallingConvention = CallingConvention.Cdecl)] + public static extern Result Prepare2 (IntPtr db, [MarshalAs (UnmanagedType.LPStr)] string sql, int numBytes, out IntPtr stmt, IntPtr pzTail); #if NETFX_CORE [DllImport (LibraryPath, EntryPoint = "sqlite3_prepare_v2", CallingConvention = CallingConvention.Cdecl)] @@ -4707,7 +4960,7 @@ public static IntPtr Prepare2 (IntPtr db, string query) byte[] queryBytes = System.Text.UTF8Encoding.UTF8.GetBytes (query); var r = Prepare2 (db, queryBytes, queryBytes.Length, out stmt, IntPtr.Zero); #else - var r = Prepare2 (db, query, System.Text.UTF8Encoding.UTF8.GetByteCount (query), out stmt, IntPtr.Zero); + var r = Prepare2 (db, query, System.Text.UTF8Encoding.UTF8.GetByteCount (query), out stmt, IntPtr.Zero); #endif if (r != Result.OK) { throw SQLiteException.New (r, GetErrmsg (db)); @@ -4716,21 +4969,21 @@ public static IntPtr Prepare2 (IntPtr db, string query) } [DllImport (LibraryPath, EntryPoint = "sqlite3_interrupt", CallingConvention = CallingConvention.Cdecl)] - public static extern void Interrupt(IntPtr db); + public static extern void Interrupt (IntPtr db); - [DllImport (LibraryPath, EntryPoint = "sqlite3_step", CallingConvention=CallingConvention.Cdecl)] + [DllImport (LibraryPath, EntryPoint = "sqlite3_step", CallingConvention = CallingConvention.Cdecl)] public static extern Result Step (IntPtr stmt); - [DllImport(LibraryPath, EntryPoint = "sqlite3_reset", CallingConvention=CallingConvention.Cdecl)] + [DllImport (LibraryPath, EntryPoint = "sqlite3_reset", CallingConvention = CallingConvention.Cdecl)] public static extern Result Reset (IntPtr stmt); - [DllImport(LibraryPath, EntryPoint = "sqlite3_finalize", CallingConvention=CallingConvention.Cdecl)] + [DllImport (LibraryPath, EntryPoint = "sqlite3_finalize", CallingConvention = CallingConvention.Cdecl)] public static extern Result Finalize (IntPtr stmt); - [DllImport(LibraryPath, EntryPoint = "sqlite3_last_insert_rowid", CallingConvention=CallingConvention.Cdecl)] + [DllImport (LibraryPath, EntryPoint = "sqlite3_last_insert_rowid", CallingConvention = CallingConvention.Cdecl)] public static extern long LastInsertRowid (IntPtr db); - [DllImport(LibraryPath, EntryPoint = "sqlite3_errmsg16", CallingConvention=CallingConvention.Cdecl)] + [DllImport (LibraryPath, EntryPoint = "sqlite3_errmsg16", CallingConvention = CallingConvention.Cdecl)] public static extern IntPtr Errmsg (IntPtr db); public static string GetErrmsg (IntPtr db) @@ -4738,62 +4991,62 @@ public static string GetErrmsg (IntPtr db) return Marshal.PtrToStringUni (Errmsg (db)); } - [DllImport(LibraryPath, EntryPoint = "sqlite3_bind_parameter_index", CallingConvention=CallingConvention.Cdecl)] - public static extern int BindParameterIndex (IntPtr stmt, [MarshalAs(UnmanagedType.LPStr)] string name); + [DllImport (LibraryPath, EntryPoint = "sqlite3_bind_parameter_index", CallingConvention = CallingConvention.Cdecl)] + public static extern int BindParameterIndex (IntPtr stmt, [MarshalAs (UnmanagedType.LPStr)] string name); - [DllImport(LibraryPath, EntryPoint = "sqlite3_bind_null", CallingConvention=CallingConvention.Cdecl)] + [DllImport (LibraryPath, EntryPoint = "sqlite3_bind_null", CallingConvention = CallingConvention.Cdecl)] public static extern int BindNull (IntPtr stmt, int index); - [DllImport(LibraryPath, EntryPoint = "sqlite3_bind_int", CallingConvention=CallingConvention.Cdecl)] + [DllImport (LibraryPath, EntryPoint = "sqlite3_bind_int", CallingConvention = CallingConvention.Cdecl)] public static extern int BindInt (IntPtr stmt, int index, int val); - [DllImport(LibraryPath, EntryPoint = "sqlite3_bind_int64", CallingConvention=CallingConvention.Cdecl)] + [DllImport (LibraryPath, EntryPoint = "sqlite3_bind_int64", CallingConvention = CallingConvention.Cdecl)] public static extern int BindInt64 (IntPtr stmt, int index, long val); - [DllImport(LibraryPath, EntryPoint = "sqlite3_bind_double", CallingConvention=CallingConvention.Cdecl)] + [DllImport (LibraryPath, EntryPoint = "sqlite3_bind_double", CallingConvention = CallingConvention.Cdecl)] public static extern int BindDouble (IntPtr stmt, int index, double val); - [DllImport(LibraryPath, EntryPoint = "sqlite3_bind_text16", CallingConvention=CallingConvention.Cdecl, CharSet = CharSet.Unicode)] - public static extern int BindText (IntPtr stmt, int index, [MarshalAs(UnmanagedType.LPWStr)] string val, int n, IntPtr free); + [DllImport (LibraryPath, EntryPoint = "sqlite3_bind_text16", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)] + public static extern int BindText (IntPtr stmt, int index, [MarshalAs (UnmanagedType.LPWStr)] string val, int n, IntPtr free); - [DllImport(LibraryPath, EntryPoint = "sqlite3_bind_blob", CallingConvention=CallingConvention.Cdecl)] + [DllImport (LibraryPath, EntryPoint = "sqlite3_bind_blob", CallingConvention = CallingConvention.Cdecl)] public static extern int BindBlob (IntPtr stmt, int index, byte[] val, int n, IntPtr free); - [DllImport(LibraryPath, EntryPoint = "sqlite3_column_count", CallingConvention=CallingConvention.Cdecl)] + [DllImport (LibraryPath, EntryPoint = "sqlite3_column_count", CallingConvention = CallingConvention.Cdecl)] public static extern int ColumnCount (IntPtr stmt); - [DllImport(LibraryPath, EntryPoint = "sqlite3_column_name", CallingConvention=CallingConvention.Cdecl)] + [DllImport (LibraryPath, EntryPoint = "sqlite3_column_name", CallingConvention = CallingConvention.Cdecl)] public static extern IntPtr ColumnName (IntPtr stmt, int index); - [DllImport(LibraryPath, EntryPoint = "sqlite3_column_name16", CallingConvention=CallingConvention.Cdecl)] + [DllImport (LibraryPath, EntryPoint = "sqlite3_column_name16", CallingConvention = CallingConvention.Cdecl)] static extern IntPtr ColumnName16Internal (IntPtr stmt, int index); - public static string ColumnName16(IntPtr stmt, int index) + public static string ColumnName16 (IntPtr stmt, int index) { - return Marshal.PtrToStringUni(ColumnName16Internal(stmt, index)); + return Marshal.PtrToStringUni (ColumnName16Internal (stmt, index)); } - [DllImport(LibraryPath, EntryPoint = "sqlite3_column_type", CallingConvention=CallingConvention.Cdecl)] + [DllImport (LibraryPath, EntryPoint = "sqlite3_column_type", CallingConvention = CallingConvention.Cdecl)] public static extern ColType ColumnType (IntPtr stmt, int index); - [DllImport(LibraryPath, EntryPoint = "sqlite3_column_int", CallingConvention=CallingConvention.Cdecl)] + [DllImport (LibraryPath, EntryPoint = "sqlite3_column_int", CallingConvention = CallingConvention.Cdecl)] public static extern int ColumnInt (IntPtr stmt, int index); - [DllImport(LibraryPath, EntryPoint = "sqlite3_column_int64", CallingConvention=CallingConvention.Cdecl)] + [DllImport (LibraryPath, EntryPoint = "sqlite3_column_int64", CallingConvention = CallingConvention.Cdecl)] public static extern long ColumnInt64 (IntPtr stmt, int index); - [DllImport(LibraryPath, EntryPoint = "sqlite3_column_double", CallingConvention=CallingConvention.Cdecl)] + [DllImport (LibraryPath, EntryPoint = "sqlite3_column_double", CallingConvention = CallingConvention.Cdecl)] public static extern double ColumnDouble (IntPtr stmt, int index); - [DllImport(LibraryPath, EntryPoint = "sqlite3_column_text", CallingConvention=CallingConvention.Cdecl)] + [DllImport (LibraryPath, EntryPoint = "sqlite3_column_text", CallingConvention = CallingConvention.Cdecl)] public static extern IntPtr ColumnText (IntPtr stmt, int index); - [DllImport(LibraryPath, EntryPoint = "sqlite3_column_text16", CallingConvention=CallingConvention.Cdecl)] + [DllImport (LibraryPath, EntryPoint = "sqlite3_column_text16", CallingConvention = CallingConvention.Cdecl)] public static extern IntPtr ColumnText16 (IntPtr stmt, int index); - [DllImport(LibraryPath, EntryPoint = "sqlite3_column_blob", CallingConvention=CallingConvention.Cdecl)] + [DllImport (LibraryPath, EntryPoint = "sqlite3_column_blob", CallingConvention = CallingConvention.Cdecl)] public static extern IntPtr ColumnBlob (IntPtr stmt, int index); - [DllImport(LibraryPath, EntryPoint = "sqlite3_column_bytes", CallingConvention=CallingConvention.Cdecl)] + [DllImport (LibraryPath, EntryPoint = "sqlite3_column_bytes", CallingConvention = CallingConvention.Cdecl)] public static extern int ColumnBytes (IntPtr stmt, int index); public static string ColumnString (IntPtr stmt, int index) diff --git a/src/SQLiteAsync.cs b/src/SQLiteAsync.cs index e30676194..101adbdf5 100644 --- a/src/SQLiteAsync.cs +++ b/src/SQLiteAsync.cs @@ -73,9 +73,11 @@ Task CreateTablesAsync (CreateFlags creat where T5 : new(); Task CreateTablesAsync (CreateFlags createFlags = CreateFlags.None, params Type[] types); Task> DeferredQueryAsync (string query, params object[] args) where T : new(); + Task> DeferredQueryAsync (CancellationToken tok, string query, params object[] args) where T : new(); Task> DeferredQueryAsync (TableMapping map, string query, params object[] args); - Task DeleteAllAsync (); - Task DeleteAllAsync (TableMapping map); + Task> DeferredQueryAsync (CancellationToken tok, TableMapping map, string query, params object[] args); + Task DeleteAllAsync (CancellationToken? cancTok = null); + Task DeleteAllAsync (TableMapping map, CancellationToken? cancTok = null); Task DeleteAsync (object objectToDelete); Task DeleteAsync (object primaryKey); Task DeleteAsync (object primaryKey, TableMapping map); @@ -84,15 +86,19 @@ Task CreateTablesAsync (CreateFlags creat Task EnableLoadExtensionAsync (bool enabled); Task EnableWriteAheadLoggingAsync (); Task ExecuteAsync (string query, params object[] args); + Task ExecuteAsync (CancellationToken cancTok, string query, params object[] args); Task ExecuteScalarAsync (string query, params object[] args); + Task ExecuteScalarAsync (CancellationToken cancTok, string query, params object[] args); Task FindAsync (object pk) where T : new(); Task FindAsync (object pk, TableMapping map); - Task FindAsync (Expression> predicate) where T : new(); + Task FindAsync (Expression> predicate, CancellationToken? cancTok = null) where T : new(); Task FindWithQueryAsync (string query, params object[] args) where T : new(); + Task FindWithQueryAsync (CancellationToken cancTok, string query, params object[] args) where T : new(); Task FindWithQueryAsync (TableMapping map, string query, params object[] args); + Task FindWithQueryAsync (CancellationToken cancTok, TableMapping map, string query, params object[] args); Task GetAsync (object pk) where T : new(); Task GetAsync (object pk, TableMapping map); - Task GetAsync (Expression> predicate) where T : new(); + Task GetAsync (Expression> predicate, CancellationToken? cancTok = null) where T : new(); TimeSpan GetBusyTimeout (); SQLiteConnectionWithLock GetConnection (); Task GetMappingAsync (Type type, CreateFlags createFlags = CreateFlags.None); @@ -108,8 +114,11 @@ Task CreateTablesAsync (CreateFlags creat Task InsertOrReplaceAsync (object obj); Task InsertOrReplaceAsync (object obj, Type objType); Task> QueryAsync (string query, params object[] args) where T : new(); + Task> QueryAsync (CancellationToken cancellationToken, string query, params object[] args) where T : new(); Task> QueryAsync (TableMapping map, string query, params object[] args); + Task> QueryAsync (CancellationToken cancellationToken, TableMapping map, string query, params object[] args); Task> QueryScalarsAsync (string query, params object[] args); + Task> QueryScalarsAsync (CancellationToken cancellationToken, string query, params object[] args); Task RunInTransactionAsync (Action action); Task SetBusyTimeoutAsync (TimeSpan value); AsyncTableQuery Table () where T : new(); @@ -769,31 +778,37 @@ public Task DeleteAsync (object primaryKey, TableMapping map) /// WARNING WARNING: Let me repeat. It deletes ALL the objects from the /// specified table. Do you really want to do that? /// + /// + /// optional cancellation token to stop the execution of the deletion + /// /// /// The number of objects deleted. /// /// /// The type of objects to delete. /// - public Task DeleteAllAsync () + public Task DeleteAllAsync (CancellationToken? cancTok = null) { - return WriteAsync (conn => conn.DeleteAll ()); + return WriteAsync (conn => conn.DeleteAll (cancTok)); } /// /// Deletes all the objects from the specified table. /// WARNING WARNING: Let me repeat. It deletes ALL the objects from the - /// specified table. Do you really want to do that? + /// specified table. Do you really want to do that§ /// /// /// The TableMapping used to identify the table. /// + /// + /// optional cancellation token to stop the execution of the deletion + /// /// /// The number of objects deleted. /// - public Task DeleteAllAsync (TableMapping map) + public Task DeleteAllAsync (TableMapping map, CancellationToken? cancTok = null) { - return WriteAsync (conn => conn.DeleteAll (map)); + return WriteAsync (conn => conn.DeleteAll (map, cancTok)); } /// @@ -854,14 +869,17 @@ public Task GetAsync (object pk, TableMapping map) /// /// A predicate for which object to find. /// + /// + /// a cancellation token that can be used to stop the execution of the query + /// /// /// The object that matches the given predicate. Throws a not found exception /// if the object is not found. /// - public Task GetAsync (Expression> predicate) + public Task GetAsync (Expression> predicate, CancellationToken? cancellationToken = null) where T : new() { - return ReadAsync (conn => conn.Get (predicate)); + return ReadAsync (conn => conn.Get (predicate, cancellationToken)); } /// @@ -909,14 +927,17 @@ public Task FindAsync (object pk, TableMapping map) /// /// A predicate for which object to find. /// + /// + /// an optional cancellation token to stop the execution of the query + /// /// /// The object that matches the given predicate or null /// if the object is not found. /// - public Task FindAsync (Expression> predicate) + public Task FindAsync (Expression> predicate, CancellationToken? cancTok = null) where T : new() { - return ReadAsync (conn => conn.Find (predicate)); + return ReadAsync (conn => conn.Find (predicate, cancTok)); } /// @@ -938,6 +959,29 @@ public Task FindWithQueryAsync (string query, params object[] args) { return ReadAsync (conn => conn.FindWithQuery (query, args)); } + /// + /// Attempts to retrieve the first object that matches the query from the table + /// associated with the specified type. + /// + /// + /// an optional cancellation token to stop the execution of the query + /// + /// + /// The fully escaped SQL. + /// + /// + /// Arguments to substitute for the occurences of '?' in the query. + /// + /// + /// The object that matches the given predicate or null + /// if the object is not found. + /// + public Task FindWithQueryAsync (CancellationToken cancTok, string query, params object[] args) + where T : new() + { + return ReadAsync (conn => conn.FindWithQuery (cancTok, query, args)); + } + /// /// Attempts to retrieve the first object that matches the query from the table @@ -960,6 +1004,31 @@ public Task FindWithQueryAsync (TableMapping map, string query, params o { return ReadAsync (conn => conn.FindWithQuery (map, query, args)); } + /// + /// Attempts to retrieve the first object that matches the query from the table + /// associated with the specified type. + /// + /// + /// an optional cancellation token to stop the execution of the query + /// + /// + /// The TableMapping used to identify the table. + /// + /// + /// The fully escaped SQL. + /// + /// + /// Arguments to substitute for the occurences of '?' in the query. + /// + /// + /// The object that matches the given predicate or null + /// if the object is not found. + /// + public Task FindWithQueryAsync (CancellationToken cancTok, TableMapping map, string query, params object[] args) + { + return ReadAsync (conn => conn.FindWithQuery (cancTok, map, query, args)); + } + /// /// Retrieves the mapping that is automatically generated for the given type. @@ -1026,6 +1095,30 @@ public Task ExecuteAsync (string query, params object[] args) { return WriteAsync (conn => conn.Execute (query, args)); } + /// + /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?' + /// in the command text for each of the arguments and then executes that command. + /// Use this method instead of Query when you don't expect rows back. Such cases include + /// INSERTs, UPDATEs, and DELETEs. + /// You can set the Trace or TimeExecution properties of the connection + /// to profile execution. + /// + /// + /// a cancellation token you can use to stop the execution of the query + /// + /// + /// The fully escaped SQL. + /// + /// + /// Arguments to substitute for the occurences of '?' in the query. + /// + /// + /// The number of rows modified in the database as a result of this execution. + /// + public Task ExecuteAsync (CancellationToken cancTok, string query, params object[] args) + { + return WriteAsync (conn => conn.Execute (cancTok, query, args)); + } /// /// Inserts all specified objects. @@ -1147,7 +1240,34 @@ public Task ExecuteScalarAsync (string query, params object[] args) { return WriteAsync (conn => { var command = conn.CreateCommand (query, args); - return command.ExecuteScalar (); + return command.ExecuteScalar (null); + }); + } + + /// + /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?' + /// in the command text for each of the arguments and then executes that command. + /// Use this method when return primitive values. + /// You can set the Trace or TimeExecution properties of the connection + /// to profile execution. + /// + /// + /// a cancellation token you can use to stop the execution of the query + /// + /// + /// The fully escaped SQL. + /// + /// + /// Arguments to substitute for the occurences of '?' in the query. + /// + /// + /// The number of rows modified in the database as a result of this execution. + /// + public Task ExecuteScalarAsync (CancellationToken cancTok, string query, params object[] args) + { + return WriteAsync (conn => { + var command = conn.CreateCommand (query, args); + return command.ExecuteScalar (cancTok); }); } @@ -1172,6 +1292,30 @@ public Task> QueryAsync (string query, params object[] args) return ReadAsync (conn => conn.Query (query, args)); } + /// + /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?' + /// in the command text for each of the arguments and then executes that command. + /// It returns each row of the result using the mapping automatically generated for + /// the given type. + /// + /// + /// a cancellation token you can use to stop the execution of the query + /// + /// + /// The fully escaped SQL. + /// + /// + /// Arguments to substitute for the occurences of '?' in the query. + /// + /// + /// A list with one result for each row returned by the query. + /// + public Task> QueryAsync (CancellationToken cancTok, string query, params object[] args) + where T : new() + { + return ReadAsync (conn => conn.Query (cancTok, query, args)); + } + /// /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?' /// in the command text for each of the arguments and then executes that command. @@ -1190,6 +1334,27 @@ public Task> QueryScalarsAsync (string query, params object[] args) { return ReadAsync (conn => conn.QueryScalars (query, args)); } + /// + /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?' + /// in the command text for each of the arguments and then executes that command. + /// It returns the first column of each row of the result. + /// + /// + /// a cancellation token you can use to stop the execution of the query + /// + /// + /// The fully escaped SQL. + /// + /// + /// Arguments to substitute for the occurences of '?' in the query. + /// + /// + /// A list with one result for the first column of each row returned by the query. + /// + public Task> QueryScalarsAsync (CancellationToken cancTok, string query, params object[] args) + { + return ReadAsync (conn => conn.QueryScalars (cancTok, query, args)); + } /// /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?' @@ -1215,7 +1380,10 @@ public Task> QueryAsync (TableMapping map, string query, params obj { return ReadAsync (conn => conn.Query (map, query, args)); } - + public Task> QueryAsync (CancellationToken cancellationToken, TableMapping map, string query, params object[] args) + { + return ReadAsync (conn => conn.Query (cancellationToken, map, query, args)); + } /// /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?' /// in the command text for each of the arguments and then executes that command. @@ -1238,6 +1406,31 @@ public Task> DeferredQueryAsync (string query, params object[] { return ReadAsync (conn => (IEnumerable)conn.DeferredQuery (query, args).ToList ()); } + /// + /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?' + /// in the command text for each of the arguments and then executes that command. + /// It returns each row of the result using the mapping automatically generated for + /// the given type. + /// + /// + /// a cancellation token you can use to stop the execution of the query + /// + /// + /// The fully escaped SQL. + /// + /// + /// Arguments to substitute for the occurences of '?' in the query. + /// + /// + /// An enumerable with one result for each row returned by the query. + /// The enumerator will call sqlite3_step on each call to MoveNext, so the database + /// connection must remain open for the lifetime of the enumerator. + /// + public Task> DeferredQueryAsync (CancellationToken cancTok, string query, params object[] args) + where T : new() + { + return ReadAsync (conn => (IEnumerable)conn.DeferredQuery (cancTok, query, args).ToList ()); + } /// /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?' @@ -1265,6 +1458,38 @@ public Task> DeferredQueryAsync (TableMapping map, string qu { return ReadAsync (conn => (IEnumerable)conn.DeferredQuery (map, query, args).ToList ()); } + /// + /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?' + /// in the command text for each of the arguments and then executes that command. + /// It returns each row of the result using the specified mapping. This function is + /// only used by libraries in order to query the database via introspection. It is + /// normally not used. + /// + /// + /// a cancellation token you can use to stop the execution of the query + /// + /// + /// A to use to convert the resulting rows + /// into objects. + /// + /// + /// The fully escaped SQL. + /// + /// + /// Arguments to substitute for the occurences of '?' in the query. + /// + /// + /// An enumerable with one result for each row returned by the query. + /// The enumerator will call sqlite3_step on each call to MoveNext, so the database + /// connection must remain open for the lifetime of the enumerator. + /// + public Task> DeferredQueryAsync (CancellationToken cancTok, TableMapping map, string query, params object[] args) + { + return ReadAsync (conn => (IEnumerable)conn.DeferredQuery (cancTok, map, query, args).ToList ()); + } + + + } /// @@ -1359,6 +1584,12 @@ public AsyncTableQuery ThenByDescending (Expression> orderExpr) return new AsyncTableQuery (_innerQuery.ThenByDescending (orderExpr)); } + public AsyncTableQuery CancelToken (CancellationToken tok) + { + return new AsyncTableQuery (_innerQuery.CancelToken (tok)); + } + + /// /// Queries the database and returns the results as a List. /// @@ -1446,6 +1677,8 @@ public Task DeleteAsync () { return WriteAsync (conn => _innerQuery.Delete ()); } + + } class SQLiteConnectionPool @@ -1574,7 +1807,7 @@ public SQLiteConnectionWithLock (SQLiteConnectionString connectionString) /// The lock. public IDisposable Lock () { - return SkipLock ? (IDisposable)new FakeLockWrapper() : new LockWrapper (_lockPoint); + return SkipLock ? (IDisposable)new FakeLockWrapper () : new LockWrapper (_lockPoint); } class LockWrapper : IDisposable diff --git a/tests/SQLite.Tests/CancelableTest.cs b/tests/SQLite.Tests/CancelableTest.cs index de9125ca5..e73326d71 100644 --- a/tests/SQLite.Tests/CancelableTest.cs +++ b/tests/SQLite.Tests/CancelableTest.cs @@ -20,34 +20,104 @@ public static ISQLiteConnection CreateSqliteInMemoryDb() return new SQLiteConnection ($"file:memdb{Guid.NewGuid ().ToString ("N")}?mode=memory&cache=private"); } + public static ISQLiteAsyncConnection CreateAsyncSqliteInMemoryDb () + { + //return new SQLiteConnection (":memory:"); this simpler version would make all tests run using the very same memory database, so test would not run in parallel + // this more complex way of creating a memory database allows to give a separate name to different memory databases + return new SQLiteAsyncConnection ($"file:memdb{Guid.NewGuid ().ToString ("N")}?mode=memory&cache=private"); + } + + + /// + /// this is a "create view" command that creates a view that will return 100 millions of records + /// by using a recursive "with". I use it for having some slow queries whose execution I can cancel + /// + public const string BIGVIEW_DEFCMD = + @"create view BIGVIEW as + WITH RECURSIVE qry(n) + AS ( + SELECT 1 as n + UNION ALL + SELECT n + 1 as n FROM qry WHERE n < 100000000 + ) + SELECT n as fld1, n as fld2 FROM qry"; + + // this entity maps bigview + [Table ("BIGVIEW")] + public class MyRecType + { + public long fld1 { get; set; } + public long fld2 { get; set; } + } + + #region SQLiteConnection + + [Test] + public async Task CancelableExecute_Test () + { + using (var conn = CreateSqliteInMemoryDb ()) { + conn.Execute (BIGVIEW_DEFCMD); + var CancTokSource = new CancellationTokenSource (); + var tok = CancTokSource.Token; + + var task = Task.Run (() => { + conn.Execute (tok, "create table bigtable as select * from bigview"); + }); + + Exception e = null; + try { + await Task.Delay (300); + CancTokSource.Cancel (); + await task; + } + catch (Exception ex) { + e = ex; + } + Assert.That (e, Is.InstanceOf ()); + } + } + + + [Test] + public async Task CancelableCount_Test () + { + using (var conn = CreateSqliteInMemoryDb ()) { + conn.Execute (BIGVIEW_DEFCMD); + var CancTokSource = new CancellationTokenSource (); + var tok = CancTokSource.Token; + + var task = Task.Run (() => { + var cnt = conn.Table ().CancelToken (tok).Count (); + }); + + Exception e = null; + try { + await Task.Delay (300); + CancTokSource.Cancel (); + await task; + } + catch (Exception ex) { + e = ex; + } + Assert.That (e, Is.InstanceOf ()); + } + } [Test] public async Task CancelableQueryQueryScalars_Test() { using (var conn = CreateSqliteInMemoryDb()) { + conn.Execute (BIGVIEW_DEFCMD); var CancTokSource = new CancellationTokenSource (); var tok = CancTokSource.Token; - // notice that this query takes ages before returning the first record. - // here I am actually testing that the execution is stopped at the "server side" - // by the sqlite3_interrupt api call, since we never enter in the internal - // "fetch next row" c# loop - var task = Task.Run (() => { - var extremelySlowQuery = - @"WITH RECURSIVE qry(n) AS ( - SELECT 1 - UNION ALL - SELECT n + 1 FROM qry WHERE n < 10000000 - ) - SELECT * FROM qry where n = 100000000"; - - var result = conn.QueryScalars (tok, extremelySlowQuery); + var longArray = conn.QueryScalars (tok, "select fld1 from BIGVIEW where fld1 = 1000000"); }); Exception e = null; try { - await Task.Delay (300); // wait some time to be sure that the query has started + await Task.Delay (300); CancTokSource.Cancel (); await task; } @@ -62,17 +132,12 @@ SELECT n + 1 FROM qry WHERE n < 10000000 public async Task CancelableQuery_Test () { using (var conn = CreateSqliteInMemoryDb()) { + conn.Execute (BIGVIEW_DEFCMD); + var CancTokSource = new CancellationTokenSource (); var tok = CancTokSource.Token; var task = Task.Run (() => { - var extremelySlowQuery = - @"WITH RECURSIVE qry(n) AS ( - SELECT 1 as n - UNION ALL - SELECT n + 1 as n FROM qry WHERE n < 100000000 - ) - SELECT n as fld1, n as fld2 FROM qry where n = 100000"; - var result = conn.Query<(long fld1,long fld2)>(tok, extremelySlowQuery); + var recs = conn.Query (tok, "select * from BIGVIEW where fld1 = 1000000"); }); Exception e = null; @@ -89,27 +154,6 @@ SELECT n + 1 as n FROM qry WHERE n < 100000000 } - /// - /// this view will return millions of records, by using a recursive "with" - /// - public const string BIGVIEW_DEFCMD= - @"create view BIGVIEW as - WITH RECURSIVE qry(n) - AS ( - SELECT 1 as n - UNION ALL - SELECT n + 1 as n FROM qry WHERE n < 100000000 - ) - SELECT n as fld1, n as fld2 FROM qry"; - - // this entity maps bigview - [Table("BIGVIEW")] - public class MyRecType - { - public long fld1 { get; set; } - public long fld2 { get; set; } - } - [Test] public async Task CancelableTable_Test () { @@ -121,11 +165,11 @@ public async Task CancelableTable_Test () var tok = CancTokSource.Token; var task = Task.Run(() => { - // this query would take forever if we couldn't stop it + var result = conn.Table() .Where(x => x.fld1!=x.fld2) - .CancelToken (tok) + .CancelToken (tok) .ToList (); }).ConfigureAwait(false); @@ -143,6 +187,136 @@ public async Task CancelableTable_Test () } } + #endregion + + #region SQLiteAyncConnection + + [Test] + public async Task CancelableExecuteAsync_Test () + { + var conn = CreateAsyncSqliteInMemoryDb (); + try { + await conn.ExecuteAsync (BIGVIEW_DEFCMD); + var CancTokSource = new CancellationTokenSource (); + var tok = CancTokSource.Token; + + var task = Task.Run (async () => { + await conn.ExecuteAsync (tok, "create table bigtable as select * from bigview"); + }); + + Exception e = null; + try { + await Task.Delay (300); + CancTokSource.Cancel (); + await task; + } + catch (Exception ex) { + e = ex; + } + Assert.That (e, Is.InstanceOf ()); + } + finally { + await conn.CloseAsync (); + } + } + + [Test] + public async Task CancelableAsyncQueryQueryScalars_Test () + { + var conn = CreateAsyncSqliteInMemoryDb (); + try { + await conn.ExecuteAsync (BIGVIEW_DEFCMD); + var CancTokSource = new CancellationTokenSource (); + var tok = CancTokSource.Token; + + var task = Task.Run (async () => { + var longArray = await conn.QueryScalarsAsync (tok, "select fld1 from BIGVIEW where fld1 = 1000000"); + }); + + Exception e = null; + try { + await Task.Delay (300); + CancTokSource.Cancel (); + await task; + } + catch (Exception ex) { + e = ex; + } + Assert.That (e, Is.InstanceOf ()); + } + finally { + await conn.CloseAsync (); + } + } + + [Test] + public async Task CancelableAsyncQuery_Test () + { + var conn = CreateAsyncSqliteInMemoryDb (); + try + { + await conn.ExecuteAsync (BIGVIEW_DEFCMD); + + var CancTokSource = new CancellationTokenSource (); + var tok = CancTokSource.Token; + var task = Task.Run (async () => { + var recs = await conn.QueryAsync (tok, "select * from BIGVIEW where fld1 = 1000000"); + }); + + Exception e = null; + try { + await Task.Delay (1000); + CancTokSource.Cancel (); + await task; + } + catch (Exception ex) { + e = ex; + } + Assert.That (e, Is.InstanceOf ()); + } + finally { + await conn.CloseAsync (); + } + } + + + [Test] + public async Task CancelableAsyncTable_Test () + { + var conn = CreateAsyncSqliteInMemoryDb (); + try { + await conn.ExecuteAsync(BIGVIEW_DEFCMD); + + var CancTokSource = new CancellationTokenSource (); + var tok = CancTokSource.Token; + + var task = Task.Run (async () => { + + var result = + await conn.Table () + .Where (x => x.fld1 != x.fld2) + .CancelToken (tok) + .ToListAsync (); + }).ConfigureAwait (false); + + Exception e = null; + try { + await Task.Delay (10); + CancTokSource.Cancel (); + await task; + } + catch (Exception ex) { + e = ex; + } + + Assert.That (e, Is.InstanceOf ()); + + } + finally { + await conn.CloseAsync (); + } + } + #endregion } } From e1e4d5fe50bc597b2626f5dbc09142135cdf5d07 Mon Sep 17 00:00:00 2001 From: Carlo Sirna Date: Fri, 22 Sep 2023 10:08:33 +0200 Subject: [PATCH 4/4] if the CancelToken was set during a Prepare() call, a SQLite3Exception was thrown instead of a OperationCanceledException --- src/SQLite.cs | 176 ++++++++++++++++++++++++++++++++------------------ 1 file changed, 112 insertions(+), 64 deletions(-) diff --git a/src/SQLite.cs b/src/SQLite.cs index f810fe769..c3ac0819d 100644 --- a/src/SQLite.cs +++ b/src/SQLite.cs @@ -3309,6 +3309,7 @@ public static bool IsMarkedNotNull (MemberInfo p) } } + public partial class SQLiteCommand { SQLiteConnection _conn; @@ -3329,11 +3330,17 @@ public int ExecuteNonQuery (CancellationToken? cancTok = null) } cancTok?.ThrowIfCancellationRequested (); using (var interruptCallbackRegistration = cancTok?.Register (() => SQLite3.Interrupt (_conn.Handle))) { - - var r = SQLite3.Result.OK; - var stmt = Prepare (); - r = SQLite3.Step (stmt); - Finalize (stmt); + SQLite3.Result r = SQLite3.Result.OK; + try { + var stmt = Prepare (); + r = SQLite3.Step (stmt); + Finalize (stmt); + } + catch (SQLiteException ex) { + if (ex.Result == SQLite3.Result.Interrupt && cancTok?.IsCancellationRequested == true) + throw new OperationCanceledException (cancTok.Value); + throw; + } if (r == SQLite3.Result.Done) { int rowsAffected = SQLite3.Changes (_conn.Handle); return rowsAffected; @@ -3410,66 +3417,85 @@ public IEnumerable CancelableExecuteDeferredQuery (CancellationToken? canc cancTok?.ThrowIfCancellationRequested (); using (var interruptCallbackRegistration = cancTok?.Register (() => SQLite3.Interrupt (_conn.Handle))) { - var stmt = Prepare (); - try { - - var cols = new TableMapping.Column[SQLite3.ColumnCount (stmt)]; - var fastColumnSetters = new Action[SQLite3.ColumnCount (stmt)]; + Sqlite3Statement stmt = default; + TableMapping.Column[] cols = null; + Action[] fastColumnSetters = null; + try { + try { + stmt = Prepare (); + cols = new TableMapping.Column[SQLite3.ColumnCount (stmt)]; + fastColumnSetters = new Action[SQLite3.ColumnCount (stmt)]; - if (map.Method == TableMapping.MapMethod.ByPosition) { - Array.Copy (map.Columns, cols, Math.Min (cols.Length, map.Columns.Length)); - } - else if (map.Method == TableMapping.MapMethod.ByName) { - MethodInfo getSetter = null; - if (typeof (T) != map.MappedType) { - getSetter = typeof (FastColumnSetter) - .GetMethod (nameof (FastColumnSetter.GetFastSetter), - BindingFlags.NonPublic | BindingFlags.Static).MakeGenericMethod (map.MappedType); + if (map.Method == TableMapping.MapMethod.ByPosition) { + Array.Copy (map.Columns, cols, Math.Min (cols.Length, map.Columns.Length)); } + else if (map.Method == TableMapping.MapMethod.ByName) { + MethodInfo getSetter = null; + if (typeof (T) != map.MappedType) { + getSetter = typeof (FastColumnSetter) + .GetMethod (nameof (FastColumnSetter.GetFastSetter), + BindingFlags.NonPublic | BindingFlags.Static).MakeGenericMethod (map.MappedType); + } - for (int i = 0; i < cols.Length; i++) { - var name = SQLite3.ColumnName16 (stmt, i); - cols[i] = map.FindColumn (name); - if (cols[i] != null) - if (getSetter != null) { - fastColumnSetters[i] = (Action)getSetter.Invoke (null, new object[] { _conn, cols[i] }); - } - else { - fastColumnSetters[i] = FastColumnSetter.GetFastSetter (_conn, cols[i]); - } + for (int i = 0; i < cols.Length; i++) { + var name = SQLite3.ColumnName16 (stmt, i); + cols[i] = map.FindColumn (name); + if (cols[i] != null) + if (getSetter != null) { + fastColumnSetters[i] = (Action)getSetter.Invoke (null, new object[] { _conn, cols[i] }); + } + else { + fastColumnSetters[i] = FastColumnSetter.GetFastSetter (_conn, cols[i]); + } + } } } + catch (SQLiteException ex) { + if (ex.Result == SQLite3.Result.Interrupt && cancTok?.IsCancellationRequested == true) + throw new OperationCanceledException (cancTok.Value); + throw; + } while (true) { - var r = SQLite3.Step (stmt); - if (r == SQLite3.Result.Done) - break; + object obj = null; + try { + var r = SQLite3.Step (stmt); + if (r == SQLite3.Result.Done) + break; - cancTok?.ThrowIfCancellationRequested (); + cancTok?.ThrowIfCancellationRequested (); - if (r != SQLite3.Result.Row) - throw SQLiteException.New (r, SQLite3.GetErrmsg (_conn.Handle)); + if (r != SQLite3.Result.Row) + throw SQLiteException.New (r, SQLite3.GetErrmsg (_conn.Handle)); - var obj = Activator.CreateInstance (map.MappedType); - for (int i = 0; i < cols.Length; i++) { - if (cols[i] == null) - continue; + obj = Activator.CreateInstance (map.MappedType); + for (int i = 0; i < cols.Length; i++) { + if (cols[i] == null) + continue; - if (fastColumnSetters[i] != null) { - fastColumnSetters[i].Invoke (obj, stmt, i); - } - else { - var colType = SQLite3.ColumnType (stmt, i); - var val = ReadCol (stmt, i, colType, cols[i].ColumnType); - cols[i].SetValue (obj, val); + if (fastColumnSetters[i] != null) { + fastColumnSetters[i].Invoke (obj, stmt, i); + } + else { + var colType = SQLite3.ColumnType (stmt, i); + var val = ReadCol (stmt, i, colType, cols[i].ColumnType); + cols[i].SetValue (obj, val); + } } + OnInstanceCreated (obj); } - OnInstanceCreated (obj); + catch (SQLiteException ex) { + if (ex.Result == SQLite3.Result.Interrupt && cancTok?.IsCancellationRequested == true) + throw new OperationCanceledException (cancTok.Value); + throw; + } + yield return (T)obj; } } finally { - SQLite3.Finalize (stmt); + if (stmt != default) + SQLite3.Finalize (stmt); } } } @@ -3482,10 +3508,9 @@ public T ExecuteScalar (CancellationToken? cancTok = null) cancTok?.ThrowIfCancellationRequested (); using (var interruptCallbackRegistration = cancTok?.Register (() => SQLite3.Interrupt (_conn.Handle))) { - - var stmt = Prepare (); + Sqlite3Statement stmt = default; try { - + stmt = Prepare (); var r = SQLite3.Step (stmt); if (r == SQLite3.Result.Done) return default (T); @@ -3503,8 +3528,14 @@ public T ExecuteScalar (CancellationToken? cancTok = null) throw SQLiteException.New (r, SQLite3.GetErrmsg (_conn.Handle)); } + catch (SQLiteException ex) { + if (ex.Result == SQLite3.Result.Interrupt && cancTok?.IsCancellationRequested == true) + throw new OperationCanceledException (cancTok.Value); + throw; + } finally { - Finalize (stmt); + if (stmt != default) + Finalize (stmt); } } } @@ -3516,22 +3547,38 @@ public IEnumerable ExecuteQueryScalars (CancellationToken? cancTok) } cancTok?.ThrowIfCancellationRequested (); using (var interruptCallbackRegistration = cancTok?.Register (() => SQLite3.Interrupt (_conn.Handle))) { - var stmt = Prepare (); + Sqlite3Statement stmt = default; try { - if (SQLite3.ColumnCount (stmt) < 1) { - throw new InvalidOperationException ("QueryScalars should return at least one column"); + try { + stmt = Prepare (); + if (SQLite3.ColumnCount (stmt) < 1) { + throw new InvalidOperationException ("QueryScalars should return at least one column"); + } + } + catch (SQLiteException ex) { + if (ex.Result == SQLite3.Result.Interrupt && cancTok?.IsCancellationRequested == true) + throw new OperationCanceledException (cancTok.Value); + throw; } while (true) { - var r = SQLite3.Step (stmt); - if (r == SQLite3.Result.Done) - break; - cancTok?.ThrowIfCancellationRequested (); - if (r != SQLite3.Result.Row) - throw SQLiteException.New (r, SQLite3.GetErrmsg (_conn.Handle)); + object val = null; + try { + var r = SQLite3.Step (stmt); + if (r == SQLite3.Result.Done) + break; + cancTok?.ThrowIfCancellationRequested (); + if (r != SQLite3.Result.Row) + throw SQLiteException.New (r, SQLite3.GetErrmsg (_conn.Handle)); - var colType = SQLite3.ColumnType (stmt, 0); - var val = ReadCol (stmt, 0, colType, typeof (T)); + var colType = SQLite3.ColumnType (stmt, 0); + val = ReadCol (stmt, 0, colType, typeof (T)); + } + catch (SQLiteException ex) { + if (ex.Result == SQLite3.Result.Interrupt && cancTok?.IsCancellationRequested == true) + throw new OperationCanceledException (cancTok.Value); + throw; + } if (val == null) { yield return default (T); } @@ -3541,7 +3588,8 @@ public IEnumerable ExecuteQueryScalars (CancellationToken? cancTok) } } finally { - Finalize (stmt); + if (stmt != default) + Finalize (stmt); } } }