diff --git a/.gitignore b/.gitignore index ce044810b..5d7b5efd5 100644 --- a/.gitignore +++ b/.gitignore @@ -265,3 +265,5 @@ paket-files/ # CodeRush .cr/ +*.ncrunchsolution +*.ncrunchproject diff --git a/src/SQLite.cs b/src/SQLite.cs index 5b6941c3b..c3ac0819d 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,27 +215,32 @@ 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 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); @@ -243,8 +254,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 +394,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) @@ -504,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 @@ -1034,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 (); @@ -1073,7 +1132,7 @@ public T ExecuteScalar (string query, params object[] args) _sw.Start (); } - var r = cmd.ExecuteScalar (); + var r = cmd.ExecuteScalar (null); if (TimeExecution) { _sw.Stop (); @@ -1084,6 +1143,50 @@ 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. + /// 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 (); + _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. @@ -1105,6 +1208,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 +1231,30 @@ 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 (); + } + + /// + /// 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 (cancTok).ToList (); } /// @@ -1149,6 +1281,33 @@ public List QueryScalars (string query, params object[] args) return cmd.ExecuteDeferredQuery (); } + /// + /// 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 (cancTok); + } + /// /// 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 +1333,35 @@ public List Query (TableMapping map, string query, params object[] args) var cmd = CreateCommand (query, args); return cmd.ExecuteQuery (map); } + /// + /// 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, cancTok); + } /// /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?' @@ -1203,6 +1391,37 @@ public IEnumerable DeferredQuery (TableMapping map, string query, params var cmd = CreateCommand (query, args); return cmd.ExecuteDeferredQuery (map); } + /// + /// 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 (cancTok, map); + } /// /// Returns a queryable interface to the table represented by the given type. @@ -1261,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 (); } /// @@ -1315,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 (); } /// @@ -1343,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. @@ -1364,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 . @@ -1550,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; @@ -2112,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); } /// @@ -2132,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; @@ -2563,13 +2851,13 @@ 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) - { - 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) { @@ -2595,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; } @@ -2669,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; @@ -2706,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 @@ -2744,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."); } } } @@ -3014,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) { @@ -3022,13 +3309,13 @@ public static bool IsMarkedNotNull (MemberInfo p) } } + public partial class SQLiteCommand { SQLiteConnection _conn; private List _bindings; public string CommandText { get; set; } - public SQLiteCommand (SQLiteConnection conn) { _conn = conn; @@ -3036,37 +3323,57 @@ 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))) { + 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; + } + cancTok?.ThrowIfCancellationRequested (); - 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)); + 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 () { 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 +3385,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,120 +3405,192 @@ 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); } - var stmt = Prepare (); - try { - 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); - } + cancTok?.ThrowIfCancellationRequested (); + using (var interruptCallbackRegistration = cancTok?.Register (() => SQLite3.Interrupt (_conn.Handle))) { + 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)]; - 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]}); + 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); } - 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) { + object obj = null; + try { + var r = SQLite3.Step (stmt); + if (r == SQLite3.Result.Done) + break; + + cancTok?.ThrowIfCancellationRequested (); - while (SQLite3.Step (stmt) == SQLite3.Result.Row) { - var obj = Activator.CreateInstance (map.MappedType); - for (int i = 0; i < cols.Length; i++) { - if (cols[i] == null) - continue; + if (r != SQLite3.Result.Row) + throw SQLiteException.New (r, SQLite3.GetErrmsg (_conn.Handle)); - if (fastColumnSetters[i] != null) { - fastColumnSetters[i].Invoke (obj, stmt, i); + 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); + } + } + OnInstanceCreated (obj); } - else { - var colType = SQLite3.ColumnType (stmt, i); - var val = ReadCol (stmt, i, colType, cols[i].ColumnType); - cols[i].SetValue (obj, val); + catch (SQLiteException ex) { + if (ex.Result == SQLite3.Result.Interrupt && cancTok?.IsCancellationRequested == true) + throw new OperationCanceledException (cancTok.Value); + throw; } + + yield return (T)obj; } - OnInstanceCreated (obj); - yield return (T)obj; } - } - finally { - SQLite3.Finalize (stmt); + finally { + if (stmt != default) + SQLite3.Finalize (stmt); + } } } - 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))) { + Sqlite3Statement stmt = default; + try { + stmt = Prepare (); + var r = SQLite3.Step (stmt); + if (r == SQLite3.Result.Done) + return default (T); + + 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); + } + cancTok?.ThrowIfCancellationRequested (); - var stmt = Prepare (); + throw SQLiteException.New (r, SQLite3.GetErrmsg (_conn.Handle)); - try { - 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; - } } - else if (r == SQLite3.Result.Done) { + catch (SQLiteException ex) { + if (ex.Result == SQLite3.Result.Interrupt && cancTok?.IsCancellationRequested == true) + throw new OperationCanceledException (cancTok.Value); + throw; } - else { - throw SQLiteException.New (r, SQLite3.GetErrmsg (_conn.Handle)); + finally { + if (stmt != default) + Finalize (stmt); } } - finally { - Finalize (stmt); - } - - return val; } - public IEnumerable ExecuteQueryScalars () + 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"); - } - while (SQLite3.Step (stmt) == SQLite3.Result.Row) { - var colType = SQLite3.ColumnType (stmt, 0); - var val = ReadCol (stmt, 0, colType, typeof (T)); - if (val == null) { - yield return default (T); + cancTok?.ThrowIfCancellationRequested (); + using (var interruptCallbackRegistration = cancTok?.Register (() => SQLite3.Interrupt (_conn.Handle))) { + Sqlite3Statement stmt = default; + try { + try { + stmt = Prepare (); + if (SQLite3.ColumnCount (stmt) < 1) { + throw new InvalidOperationException ("QueryScalars should return at least one column"); + } } - else { - yield return (T)val; + catch (SQLiteException ex) { + if (ex.Result == SQLite3.Result.Interrupt && cancTok?.IsCancellationRequested == true) + throw new OperationCanceledException (cancTok.Value); + throw; + } + + while (true) { + 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); + 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); + } + else { + yield return (T)val; + } } } - } - finally { - Finalize (stmt); + finally { + if (stmt != default) + Finalize (stmt); + } } } @@ -3495,7 +3878,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); }); } @@ -3511,7 +3894,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)) { @@ -3638,7 +4021,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<>)) { @@ -3812,6 +4195,7 @@ public class TableQuery : BaseTableQuery, IEnumerable Expression _joinSelector; Expression _selector; + CancellationToken? _cancelToken; TableQuery (SQLiteConnection conn, TableMapping table) { @@ -3841,6 +4225,7 @@ public TableQuery Clone () q._joinOuterKeySelector = _joinOuterKeySelector; q._joinSelector = _joinSelector; q._selector = _selector; + q._cancelToken = _cancelToken; return q; } @@ -3861,6 +4246,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. /// @@ -3894,7 +4286,7 @@ public int Delete (Expression> predExpr) var command = Connection.CreateCommand (cmdText, args.ToArray ()); - int result = command.ExecuteNonQuery (); + int result = command.ExecuteNonQuery (_cancelToken); return result; } @@ -4370,7 +4762,7 @@ string GetSqlName (Expression expr) /// public int Count () { - return GenerateCommand ("count(*)").ExecuteScalar (); + return GenerateCommand ("count(*)").ExecuteScalar (_cancelToken); } /// @@ -4383,10 +4775,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 +4799,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 +4811,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 (); } /// @@ -4543,50 +4951,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)] @@ -4600,7 +5008,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)); @@ -4608,19 +5016,22 @@ 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)] + [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) @@ -4628,62 +5039,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) @@ -4772,6 +5183,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/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/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..e73326d71 --- /dev/null +++ b/tests/SQLite.Tests/CancelableTest.cs @@ -0,0 +1,322 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using NUnit.Framework; + +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"); + } + + 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; + + var task = Task.Run (() => { + var longArray = conn.QueryScalars (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 ()); + } + } + + [Test] + 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 recs = conn.Query (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 ()); + } + } + + + [Test] + public async Task CancelableTable_Test () + { + using (var conn = CreateSqliteInMemoryDb()) { + // + conn.Execute (BIGVIEW_DEFCMD); + + var CancTokSource = new CancellationTokenSource (); + var tok = CancTokSource.Token; + + var task = Task.Run(() => { + + var result = + conn.Table() + .Where(x => x.fld1!=x.fld2) + .CancelToken (tok) + .ToList (); + }).ConfigureAwait(false); + + Exception e = null; + try { + await Task.Delay (10); + CancTokSource.Cancel (); + await task; + } + catch (Exception ex) { + e = ex; + } + + Assert.That (e, Is.InstanceOf ()); + + } + } + #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 + } +}