// // Copyright (c) 2012-2024 Krueger Systems, Inc. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Threading; using System.Threading.Tasks; #pragma warning disable 1591 // XML Doc Comments namespace SQLite { public interface ISQLiteAsyncConnection { string DatabasePath { get; } int LibVersionNumber { get; } string DateTimeStringFormat { get; } bool StoreDateTimeAsTicks { get; } bool StoreTimeSpanAsTicks { get; } bool Trace { get; set; } Action Tracer { get; set; } bool TimeExecution { get; set; } IEnumerable TableMappings { get; } Task BackupAsync (string destinationDatabasePath, string databaseName = "main"); Task CloseAsync (); Task CreateIndexAsync (string tableName, string columnName, bool unique = false); Task CreateIndexAsync (string indexName, string tableName, string columnName, bool unique = false); Task CreateIndexAsync (string tableName, string[] columnNames, bool unique = false); Task CreateIndexAsync (string indexName, string tableName, string[] columnNames, bool unique = false); Task CreateIndexAsync (Expression> property, bool unique = false); Task CreateTableAsync (CreateFlags createFlags = CreateFlags.None,string tableName = "") where T : new(); Task CreateTableAsync (Type ty, CreateFlags createFlags = CreateFlags.None,string tableName = ""); Task CreateTablesAsync (CreateFlags createFlags = CreateFlags.None) where T : new() where T2 : new(); Task CreateTablesAsync (CreateFlags createFlags = CreateFlags.None) where T : new() where T2 : new() where T3 : new(); Task CreateTablesAsync (CreateFlags createFlags = CreateFlags.None) where T : new() where T2 : new() where T3 : new() where T4 : new(); Task CreateTablesAsync (CreateFlags createFlags = CreateFlags.None) where T : new() where T2 : new() where T3 : new() where T4 : new() where T5 : new(); Task CreateTablesAsync (CreateFlags createFlags = CreateFlags.None, params Type[] types); Task> DeferredQueryAsync (string query, params object[] args) where T : new(); Task> DeferredQueryAsync (TableMapping map, string query, params object[] args); Task DeleteAllAsync (string tableName = ""); Task DeleteAllAsync (TableMapping map); Task DeleteAsync (object objectToDelete,string tableName = ""); Task DeleteAsync (object primaryKey,string tableName = ""); Task DeleteAsync (object primaryKey, TableMapping map); Task DropTableAsync (string tableName = "") where T : new(); Task DropTableAsync (TableMapping map); Task EnableLoadExtensionAsync (bool enabled); Task EnableWriteAheadLoggingAsync (); Task ExecuteAsync (string query, params object[] args); Task ExecuteScalarAsync (string query, params object[] args); Task FindAsync (object pk,string tableName = "") where T : new(); Task FindAsync (object pk, TableMapping map); Task FindAsync (Expression> predicate,string tableName = "") where T : new(); Task FindWithQueryAsync (string query, params object[] args) where T : new(); Task FindWithQueryAsync (TableMapping map, string query, params object[] args); Task GetAsync (object pk,string tableName = "") where T : new(); Task GetAsync (object pk, TableMapping map); Task GetAsync (Expression> predicate,string tableName = "") where T : new(); TimeSpan GetBusyTimeout (); SQLiteConnectionWithLock GetConnection (); Task GetMappingAsync (Type type, CreateFlags createFlags = CreateFlags.None,string tableName = ""); Task GetMappingAsync (CreateFlags createFlags = CreateFlags.None,string tableName = "") where T : new(); Task> GetTableInfoAsync (string tableName); Task InsertAllAsync (IEnumerable objects, bool runInTransaction = true,string tableName = ""); Task InsertAllAsync (IEnumerable objects, string extra, bool runInTransaction = true,string tableName = ""); Task InsertAllAsync (IEnumerable objects, Type objType, bool runInTransaction = true,string tableName = ""); Task InsertAsync (object obj,string tableName = ""); Task InsertAsync (object obj, Type objType,string tableName = ""); Task InsertAsync (object obj, string extra,string tableName = ""); Task InsertAsync (object obj, string extra, Type objType,string tableName = ""); Task InsertOrReplaceAsync (object obj,string tableName = ""); Task InsertOrReplaceAsync (object obj, Type objType,string tableName = ""); Task> QueryAsync (string query, params object[] args) where T : new(); Task> QueryAsync (TableMapping map, string query, params object[] args); Task> QueryScalarsAsync (string query, params object[] args); Task ReKeyAsync (string key); Task ReKeyAsync (byte[] key); Task RunInTransactionAsync (Action action); Task SetBusyTimeoutAsync (TimeSpan value); AsyncTableQuery Table (string tableName = "") where T : new(); Task UpdateAllAsync (IEnumerable objects, bool runInTransaction = true,string tableName = ""); Task UpdateAsync (object obj,string tableName = ""); Task UpdateAsync (object obj, Type objType,string tableName = ""); } /// /// A pooled asynchronous connection to a SQLite database. /// public partial class SQLiteAsyncConnection : ISQLiteAsyncConnection { readonly SQLiteConnectionString _connectionString; /// /// Constructs a new SQLiteAsyncConnection and opens a pooled SQLite database specified by databasePath. /// /// /// Specifies the path to the database file. /// /// /// Specifies whether to store DateTime properties as ticks (true) or strings (false). You /// absolutely do want to store them as Ticks in all new projects. The value of false is /// only here for backwards compatibility. There is a *significant* speed advantage, with no /// down sides, when setting storeDateTimeAsTicks = true. /// If you use DateTimeOffset properties, it will be always stored as ticks regardingless /// the storeDateTimeAsTicks parameter. /// public SQLiteAsyncConnection (string databasePath, bool storeDateTimeAsTicks = true) : this (new SQLiteConnectionString (databasePath, SQLiteOpenFlags.Create | SQLiteOpenFlags.ReadWrite | SQLiteOpenFlags.FullMutex, storeDateTimeAsTicks)) { } /// /// Constructs a new SQLiteAsyncConnection and opens a pooled SQLite database specified by databasePath. /// /// /// Specifies the path to the database file. /// /// /// Flags controlling how the connection should be opened. /// Async connections should have the FullMutex flag set to provide best performance. /// /// /// Specifies whether to store DateTime properties as ticks (true) or strings (false). You /// absolutely do want to store them as Ticks in all new projects. The value of false is /// only here for backwards compatibility. There is a *significant* speed advantage, with no /// down sides, when setting storeDateTimeAsTicks = true. /// If you use DateTimeOffset properties, it will be always stored as ticks regardingless /// the storeDateTimeAsTicks parameter. /// public SQLiteAsyncConnection (string databasePath, SQLiteOpenFlags openFlags, bool storeDateTimeAsTicks = true) : this (new SQLiteConnectionString (databasePath, openFlags, storeDateTimeAsTicks)) { } /// /// Constructs a new SQLiteAsyncConnection and opens a pooled SQLite database /// using the given connection string. /// /// /// Details on how to find and open the database. /// public SQLiteAsyncConnection (SQLiteConnectionString connectionString) { _connectionString = connectionString; } /// /// Gets the database path used by this connection. /// public string DatabasePath => GetConnection ().DatabasePath; /// /// Gets the SQLite library version number. 3007014 would be v3.7.14 /// public int LibVersionNumber => GetConnection ().LibVersionNumber; /// /// The format to use when storing DateTime properties as strings. Ignored if StoreDateTimeAsTicks is true. /// /// The date time string format. public string DateTimeStringFormat => GetConnection ().DateTimeStringFormat; /// /// The amount of time to wait for a table to become unlocked. /// public TimeSpan GetBusyTimeout () { return GetConnection ().BusyTimeout; } /// /// Sets the amount of time to wait for a table to become unlocked. /// public Task SetBusyTimeoutAsync (TimeSpan value) { return ReadAsync (conn => { conn.BusyTimeout = value; return null; }); } /// /// Enables the write ahead logging. WAL is significantly faster in most scenarios /// by providing better concurrency and better disk IO performance than the normal /// journal mode. You only need to call this function once in the lifetime of the database. /// public Task EnableWriteAheadLoggingAsync () { return WriteAsync (conn => { conn.EnableWriteAheadLogging (); return null; }); } /// /// Whether to store DateTime properties as ticks (true) or strings (false). /// public bool StoreDateTimeAsTicks => GetConnection ().StoreDateTimeAsTicks; /// /// Whether to store TimeSpan properties as ticks (true) or strings (false). /// public bool StoreTimeSpanAsTicks => GetConnection ().StoreTimeSpanAsTicks; /// /// Whether to writer queries to during execution. /// /// The tracer. public bool Trace { get { return GetConnection ().Trace; } set { GetConnection ().Trace = value; } } /// /// The delegate responsible for writing trace lines. /// /// The tracer. public Action Tracer { get { return GetConnection ().Tracer; } set { GetConnection ().Tracer = value; } } /// /// Whether Trace lines should be written that show the execution time of queries. /// public bool TimeExecution { get { return GetConnection ().TimeExecution; } set { GetConnection ().TimeExecution = value; } } /// /// Returns the mappings from types to tables that the connection /// currently understands. /// public IEnumerable TableMappings => GetConnection ().TableMappings; /// /// Closes all connections to all async databases. /// You should *never* need to do this. /// This is a blocking operation that will return when all connections /// have been closed. /// public static void ResetPool () { SQLiteConnectionPool.Shared.Reset (); } /// /// Gets the pooled lockable connection used by this async connection. /// You should never need to use this. This is provided only to add additional /// functionality to SQLite-net. If you use this connection, you must use /// the Lock method on it while using it. /// public SQLiteConnectionWithLock GetConnection () { return SQLiteConnectionPool.Shared.GetConnection (_connectionString); } SQLiteConnectionWithLock GetConnectionAndTransactionLock (out object transactionLock) { return SQLiteConnectionPool.Shared.GetConnectionAndTransactionLock (_connectionString, out transactionLock); } /// /// Closes any pooled connections used by the database. /// public Task CloseAsync () { return Task.Factory.StartNew (() => { SQLiteConnectionPool.Shared.CloseConnection (_connectionString); }, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default); } Task ReadAsync (Func read) { return Task.Factory.StartNew (() => { var conn = GetConnection (); using (conn.Lock ()) { return read (conn); } }, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default); } Task WriteAsync (Func write) { return Task.Factory.StartNew (() => { var conn = GetConnection (); using (conn.Lock ()) { return write (conn); } }, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default); } Task TransactAsync (Func transact) { return Task.Factory.StartNew (() => { var conn = GetConnectionAndTransactionLock (out var transactionLock); lock (transactionLock) { using (conn.Lock ()) { return transact (conn); } } }, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default); } /// /// Enable or disable extension loading. /// public Task EnableLoadExtensionAsync (bool enabled) { return WriteAsync (conn => { conn.EnableLoadExtension (enabled); return null; }); } /// /// Executes a "create table if not exists" on the database. It also /// creates any specified indexes on the columns of the table. It uses /// a schema automatically generated from the specified type. You can /// later access this schema by calling GetMapping. /// /// /// Whether the table was created or migrated. /// public Task CreateTableAsync (CreateFlags createFlags = CreateFlags.None,string tableName = "") where T : new() { return WriteAsync (conn => conn.CreateTable (createFlags,tableName)); } /// /// Executes a "create table if not exists" on the database. It also /// creates any specified indexes on the columns of the table. It uses /// a schema automatically generated from the specified type. You can /// later access this schema by calling GetMapping. /// /// Type to reflect to a database table. /// Optional flags allowing implicit PK and indexes based on naming conventions. /// /// Whether the table was created or migrated. /// public Task CreateTableAsync (Type ty, CreateFlags createFlags = CreateFlags.None,string tableName = "") { return WriteAsync (conn => conn.CreateTable (ty, createFlags,tableName)); } /// /// Executes a "create table if not exists" on the database for each type. It also /// creates any specified indexes on the columns of the table. It uses /// a schema automatically generated from the specified type. You can /// later access this schema by calling GetMapping. /// /// /// Whether the table was created or migrated for each type. /// public Task CreateTablesAsync (CreateFlags createFlags = CreateFlags.None) where T : new() where T2 : new() { return CreateTablesAsync (createFlags, typeof (T), typeof (T2)); } /// /// Executes a "create table if not exists" on the database for each type. It also /// creates any specified indexes on the columns of the table. It uses /// a schema automatically generated from the specified type. You can /// later access this schema by calling GetMapping. /// /// /// Whether the table was created or migrated for each type. /// public Task CreateTablesAsync (CreateFlags createFlags = CreateFlags.None) where T : new() where T2 : new() where T3 : new() { return CreateTablesAsync (createFlags, typeof (T), typeof (T2), typeof (T3)); } /// /// Executes a "create table if not exists" on the database for each type. It also /// creates any specified indexes on the columns of the table. It uses /// a schema automatically generated from the specified type. You can /// later access this schema by calling GetMapping. /// /// /// Whether the table was created or migrated for each type. /// public Task CreateTablesAsync (CreateFlags createFlags = CreateFlags.None) where T : new() where T2 : new() where T3 : new() where T4 : new() { return CreateTablesAsync (createFlags, typeof (T), typeof (T2), typeof (T3), typeof (T4)); } /// /// Executes a "create table if not exists" on the database for each type. It also /// creates any specified indexes on the columns of the table. It uses /// a schema automatically generated from the specified type. You can /// later access this schema by calling GetMapping. /// /// /// Whether the table was created or migrated for each type. /// public Task CreateTablesAsync (CreateFlags createFlags = CreateFlags.None) where T : new() where T2 : new() where T3 : new() where T4 : new() where T5 : new() { return CreateTablesAsync (createFlags, typeof (T), typeof (T2), typeof (T3), typeof (T4), typeof (T5)); } /// /// Executes a "create table if not exists" on the database for each type. It also /// creates any specified indexes on the columns of the table. It uses /// a schema automatically generated from the specified type. You can /// later access this schema by calling GetMapping. /// /// /// Whether the table was created or migrated for each type. /// public Task CreateTablesAsync (CreateFlags createFlags = CreateFlags.None, params Type[] types) { return WriteAsync (conn => conn.CreateTables (createFlags, types)); } /// /// Executes a "drop table" on the database. This is non-recoverable. /// public Task DropTableAsync (string tableName = "") where T : new() { return WriteAsync (conn => conn.DropTable (tableName)); } /// /// Executes a "drop table" on the database. This is non-recoverable. /// /// /// The TableMapping used to identify the table. /// public Task DropTableAsync (TableMapping map) { return WriteAsync (conn => conn.DropTable (map)); } /// /// Creates an index for the specified table and column. /// /// Name of the database table /// Name of the column to index /// Whether the index should be unique /// Zero on success. public Task CreateIndexAsync (string tableName, string columnName, bool unique = false) { return WriteAsync (conn => conn.CreateIndex (tableName, columnName, unique)); } /// /// Creates an index for the specified table and column. /// /// Name of the index to create /// Name of the database table /// Name of the column to index /// Whether the index should be unique /// Zero on success. public Task CreateIndexAsync (string indexName, string tableName, string columnName, bool unique = false) { return WriteAsync (conn => conn.CreateIndex (indexName, tableName, columnName, unique)); } /// /// Creates an index for the specified table and columns. /// /// Name of the database table /// An array of column names to index /// Whether the index should be unique /// Zero on success. public Task CreateIndexAsync (string tableName, string[] columnNames, bool unique = false) { return WriteAsync (conn => conn.CreateIndex (tableName, columnNames, unique)); } /// /// Creates an index for the specified table and columns. /// /// Name of the index to create /// Name of the database table /// An array of column names to index /// Whether the index should be unique /// Zero on success. public Task CreateIndexAsync (string indexName, string tableName, string[] columnNames, bool unique = false) { return WriteAsync (conn => conn.CreateIndex (indexName, tableName, columnNames, unique)); } /// /// Creates an index for the specified object property. /// e.g. CreateIndex<Client>(c => c.Name); /// /// Type to reflect to a database table. /// Property to index /// Whether the index should be unique /// Zero on success. public Task CreateIndexAsync (Expression> property, bool unique = false) { return WriteAsync (conn => conn.CreateIndex (property, unique)); } /// /// Inserts the given object and (and updates its /// auto incremented primary key if it has one). /// /// /// The object to insert. /// /// /// The number of rows added to the table. /// public Task InsertAsync (object obj,string tableName = "") { return WriteAsync (conn => conn.Insert (obj,tableName)); } /// /// Inserts the given object (and updates its /// auto incremented primary key if it has one). /// The return value is the number of rows added to the table. /// /// /// The object to insert. /// /// /// The type of object to insert. /// /// /// The number of rows added to the table. /// public Task InsertAsync (object obj, Type objType,string tableName = "") { return WriteAsync (conn => conn.Insert (obj, objType,tableName)); } /// /// Inserts the given object (and updates its /// auto incremented primary key if it has one). /// The return value is the number of rows added to the table. /// /// /// The object to insert. /// /// /// Literal SQL code that gets placed into the command. INSERT {extra} INTO ... /// /// /// The number of rows added to the table. /// public Task InsertAsync (object obj, string extra,string tableName = "") { return WriteAsync (conn => conn.Insert (obj, extra,tableName)); } /// /// Inserts the given object (and updates its /// auto incremented primary key if it has one). /// The return value is the number of rows added to the table. /// /// /// The object to insert. /// /// /// Literal SQL code that gets placed into the command. INSERT {extra} INTO ... /// /// /// The type of object to insert. /// /// /// The number of rows added to the table. /// public Task InsertAsync (object obj, string extra, Type objType,string tableName = "") { return WriteAsync (conn => conn.Insert (obj, extra, objType,tableName)); } /// /// Inserts the given object (and updates its /// auto incremented primary key if it has one). /// The return value is the number of rows added to the table. /// If a UNIQUE constraint violation occurs with /// some pre-existing object, this function deletes /// the old object. /// /// /// The object to insert. /// /// /// The number of rows modified. /// public Task InsertOrReplaceAsync (object obj,string tableName = "") { return WriteAsync (conn => conn.InsertOrReplace (obj,tableName)); } /// /// Inserts the given object (and updates its /// auto incremented primary key if it has one). /// The return value is the number of rows added to the table. /// If a UNIQUE constraint violation occurs with /// some pre-existing object, this function deletes /// the old object. /// /// /// The object to insert. /// /// /// The type of object to insert. /// /// /// The number of rows modified. /// public Task InsertOrReplaceAsync (object obj, Type objType,string tableName = "") { return WriteAsync (conn => conn.InsertOrReplace (obj, objType,tableName)); } /// /// Updates all of the columns of a table using the specified object /// except for its primary key. /// The object is required to have a primary key. /// /// /// The object to update. It must have a primary key designated using the PrimaryKeyAttribute. /// /// /// The number of rows updated. /// public Task UpdateAsync (object obj,string tableName = "") { return WriteAsync (conn => conn.Update (obj,tableName)); } /// /// Updates all of the columns of a table using the specified object /// except for its primary key. /// The object is required to have a primary key. /// /// /// The object to update. It must have a primary key designated using the PrimaryKeyAttribute. /// /// /// The type of object to insert. /// /// /// The number of rows updated. /// public Task UpdateAsync (object obj, Type objType,string tableName = "") { return WriteAsync (conn => conn.Update (obj, objType,tableName)); } /// /// Updates all specified objects. /// /// /// An of the objects to insert. /// /// /// A boolean indicating if the inserts should be wrapped in a transaction /// /// /// The number of rows modified. /// public Task UpdateAllAsync (IEnumerable objects, bool runInTransaction = true,string tableName = "") { return WriteAsync (conn => conn.UpdateAll (objects, runInTransaction,tableName)); } /// /// Deletes the given object from the database using its primary key. /// /// /// The object to delete. It must have a primary key designated using the PrimaryKeyAttribute. /// /// /// The number of rows deleted. /// public Task DeleteAsync (object objectToDelete,string tableName = "") { return WriteAsync (conn => conn.Delete (objectToDelete,tableName)); } /// /// Deletes the object with the specified primary key. /// /// /// The primary key of the object to delete. /// /// /// The number of objects deleted. /// /// /// The type of object. /// public Task DeleteAsync (object primaryKey,string tableName = "") { return WriteAsync (conn => conn.Delete (primaryKey,tableName)); } /// /// Deletes the object with the specified primary key. /// /// /// The primary key of the object to delete. /// /// /// The TableMapping used to identify the table. /// /// /// The number of objects deleted. /// public Task DeleteAsync (object primaryKey, TableMapping map) { return WriteAsync (conn => conn.Delete (primaryKey, map)); } /// /// 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? /// /// /// The number of objects deleted. /// /// /// The type of objects to delete. /// public Task DeleteAllAsync (string tableName = "") { return WriteAsync (conn => conn.DeleteAll (tableName)); } /// /// 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? /// /// /// The TableMapping used to identify the table. /// /// /// The number of objects deleted. /// public Task DeleteAllAsync (TableMapping map) { return WriteAsync (conn => conn.DeleteAll (map)); } /// /// Backup the entire database to the specified path. /// /// Path to backup file. /// The name of the database to backup (usually "main"). public Task BackupAsync (string destinationDatabasePath, string databaseName = "main") { return WriteAsync (conn => { conn.Backup (destinationDatabasePath, databaseName); return 0; }); } /// /// Attempts to retrieve an object with the given primary key from the table /// associated with the specified type. Use of this method requires that /// the given type have a designated PrimaryKey (using the PrimaryKeyAttribute). /// /// /// The primary key. /// /// /// The object with the given primary key. Throws a not found exception /// if the object is not found. /// public Task GetAsync (object pk,string tableName = "") where T : new() { return ReadAsync (conn => conn.Get (pk,tableName)); } /// /// Attempts to retrieve an object with the given primary key from the table /// associated with the specified type. Use of this method requires that /// the given type have a designated PrimaryKey (using the PrimaryKeyAttribute). /// /// /// The primary key. /// /// /// The TableMapping used to identify the table. /// /// /// The object with the given primary key. Throws a not found exception /// if the object is not found. /// public Task GetAsync (object pk, TableMapping map) { return ReadAsync (conn => conn.Get (pk, map)); } /// /// Attempts to retrieve the first object that matches the predicate from the table /// associated with the specified type. /// /// /// A predicate for which object to find. /// /// /// The object that matches the given predicate. Throws a not found exception /// if the object is not found. /// public Task GetAsync (Expression> predicate,string tableName = "") where T : new() { return ReadAsync (conn => conn.Get (predicate,tableName)); } /// /// Attempts to retrieve an object with the given primary key from the table /// associated with the specified type. Use of this method requires that /// the given type have a designated PrimaryKey (using the PrimaryKeyAttribute). /// /// /// The primary key. /// /// /// The object with the given primary key or null /// if the object is not found. /// public Task FindAsync (object pk,string tableName = "") where T : new() { return ReadAsync (conn => conn.Find (pk,tableName)); } /// /// Attempts to retrieve an object with the given primary key from the table /// associated with the specified type. Use of this method requires that /// the given type have a designated PrimaryKey (using the PrimaryKeyAttribute). /// /// /// The primary key. /// /// /// The TableMapping used to identify the table. /// /// /// The object with the given primary key or null /// if the object is not found. /// public Task FindAsync (object pk, TableMapping map) { return ReadAsync (conn => conn.Find (pk, map)); } /// /// Attempts to retrieve the first object that matches the predicate from the table /// associated with the specified type. /// /// /// A predicate for which object to find. /// /// /// The object that matches the given predicate or null /// if the object is not found. /// public Task FindAsync (Expression> predicate,string tableName = "") where T : new() { return ReadAsync (conn => conn.Find (predicate,tableName)); } /// /// Attempts to retrieve the first object that matches the query from the table /// associated with the specified type. /// /// /// 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 (string query, params object[] args) where T : new() { 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. /// /// /// 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 (TableMapping map, string query, params object[] args) { return ReadAsync (conn => conn.FindWithQuery (map, query, args)); } /// /// Retrieves the mapping that is automatically generated for the given type. /// /// /// The type whose mapping to the database is returned. /// /// /// Optional flags allowing implicit PK and indexes based on naming conventions /// /// /// The mapping represents the schema of the columns of the database and contains /// methods to set and get properties of objects. /// public Task GetMappingAsync (Type type, CreateFlags createFlags = CreateFlags.None,string tableName = "") { return ReadAsync (conn => conn.GetMapping (type, createFlags,tableName)); } /// /// Retrieves the mapping that is automatically generated for the given type. /// /// /// Optional flags allowing implicit PK and indexes based on naming conventions /// /// /// The mapping represents the schema of the columns of the database and contains /// methods to set and get properties of objects. /// public Task GetMappingAsync (CreateFlags createFlags = CreateFlags.None,string tableName = "") where T : new() { return ReadAsync (conn => conn.GetMapping (createFlags,tableName)); } /// /// Query the built-in sqlite table_info table for a specific tables columns. /// /// The columns contains in the table. /// Table name. public Task> GetTableInfoAsync (string tableName) { return ReadAsync (conn => conn.GetTableInfo (tableName)); } /// /// 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. /// /// /// 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 (string query, params object[] args) { return WriteAsync (conn => conn.Execute (query, args)); } /// /// Inserts all specified objects. /// /// /// An of the objects to insert. /// /// A boolean indicating if the inserts should be wrapped in a transaction. /// /// /// The number of rows added to the table. /// public Task InsertAllAsync (IEnumerable objects, bool runInTransaction = true,string tableName = "") { return WriteAsync (conn => conn.InsertAll (objects, runInTransaction,tableName)); } /// /// Inserts all specified objects. /// /// /// An of the objects to insert. /// /// /// Literal SQL code that gets placed into the command. INSERT {extra} INTO ... /// /// /// A boolean indicating if the inserts should be wrapped in a transaction. /// /// /// The number of rows added to the table. /// public Task InsertAllAsync (IEnumerable objects, string extra, bool runInTransaction = true,string tableName = "") { return WriteAsync (conn => conn.InsertAll (objects, extra, runInTransaction,tableName)); } /// /// Inserts all specified objects. /// /// /// An of the objects to insert. /// /// /// The type of object to insert. /// /// /// A boolean indicating if the inserts should be wrapped in a transaction. /// /// /// The number of rows added to the table. /// public Task InsertAllAsync (IEnumerable objects, Type objType, bool runInTransaction = true,string tableName = "") { return WriteAsync (conn => conn.InsertAll (objects, objType, runInTransaction,tableName)); } /// /// Executes within a (possibly nested) transaction by wrapping it in a SAVEPOINT. If an /// exception occurs the whole transaction is rolled back, not just the current savepoint. The exception /// is rethrown. /// /// /// The to perform within a transaction. can contain any number /// of operations on the connection but should never call or /// . /// public Task RunInTransactionAsync (Action action) { return TransactAsync (conn => { conn.BeginTransaction (); try { action (conn); conn.Commit (); return null; } catch (Exception) { conn.Rollback (); throw; } }); } /// /// Returns a queryable interface to the table represented by the given type. /// /// /// A queryable object that is able to translate Where, OrderBy, and Take /// queries into native SQL. /// public AsyncTableQuery Table (string tableName = "") where T : new() { // // This isn't async as the underlying connection doesn't go out to the database // until the query is performed. The Async methods are on the query iteself. // var conn = GetConnection (); return new AsyncTableQuery (conn.Table (tableName)); } /// /// 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. /// /// /// 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 (string query, params object[] args) { return WriteAsync (conn => { var command = conn.CreateCommand (query, args); return command.ExecuteScalar (); }); } /// /// 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. /// /// /// 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 (string query, params object[] args) where T : new() { 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 the first column of each row of the result. /// /// /// 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 (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 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 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 Task> QueryAsync (TableMapping map, string query, params object[] args) { return ReadAsync (conn => conn.Query (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. /// It returns each row of the result using the mapping automatically generated for /// the given type. /// /// /// 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 (string query, params object[] args) where T : new() { 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 specified mapping. This function is /// only used by libraries in order to query the database via introspection. It is /// normally not used. /// /// /// 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 (TableMapping map, string query, params object[] args) { return ReadAsync (conn => (IEnumerable)conn.DeferredQuery (map, query, args).ToList ()); } /// /// Change the encryption key for a SQLCipher database with "pragma rekey = ...". /// /// Encryption key plain text that is converted to the real encryption key using PBKDF2 key derivation public Task ReKeyAsync (string key) { return WriteAsync (conn => { conn.ReKey (key); return null; }); } /// /// Change the encryption key for a SQLCipher database. /// /// 256-bit (32 byte) or 384-bit (48 bytes) encryption key data public Task ReKeyAsync (byte[] key) { return WriteAsync (conn => { conn.ReKey (key); return null; }); } } /// /// Query to an asynchronous database connection. /// public class AsyncTableQuery where T : new() { TableQuery _innerQuery; /// /// Creates a new async query that uses given the synchronous query. /// public AsyncTableQuery (TableQuery innerQuery) { _innerQuery = innerQuery; } Task ReadAsync (Func read) { return Task.Factory.StartNew (() => { var conn = (SQLiteConnectionWithLock)_innerQuery.Connection; using (conn.Lock ()) { return read (conn); } }, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default); } Task WriteAsync (Func write) { return Task.Factory.StartNew (() => { var conn = (SQLiteConnectionWithLock)_innerQuery.Connection; using (conn.Lock ()) { return write (conn); } }, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default); } /// /// Filters the query based on a predicate. /// public AsyncTableQuery Where (Expression> predExpr) { return new AsyncTableQuery (_innerQuery.Where (predExpr)); } /// /// Skips a given number of elements from the query and then yields the remainder. /// public AsyncTableQuery Skip (int n) { return new AsyncTableQuery (_innerQuery.Skip (n)); } /// /// Yields a given number of elements from the query and then skips the remainder. /// public AsyncTableQuery Take (int n) { return new AsyncTableQuery (_innerQuery.Take (n)); } /// /// Order the query results according to a key. /// public AsyncTableQuery OrderBy (Expression> orderExpr) { return new AsyncTableQuery (_innerQuery.OrderBy (orderExpr)); } /// /// Order the query results according to a key. /// public AsyncTableQuery OrderByDescending (Expression> orderExpr) { return new AsyncTableQuery (_innerQuery.OrderByDescending (orderExpr)); } /// /// Order the query results according to a key. /// public AsyncTableQuery ThenBy (Expression> orderExpr) { return new AsyncTableQuery (_innerQuery.ThenBy (orderExpr)); } /// /// Order the query results according to a key. /// public AsyncTableQuery ThenByDescending (Expression> orderExpr) { return new AsyncTableQuery (_innerQuery.ThenByDescending (orderExpr)); } /// /// Queries the database and returns the results as a List. /// public Task> ToListAsync () { return ReadAsync (conn => _innerQuery.ToList ()); } /// /// Queries the database and returns the results as an array. /// public Task ToArrayAsync () { return ReadAsync (conn => _innerQuery.ToArray ()); } /// /// Execute SELECT COUNT(*) on the query /// public Task CountAsync () { return ReadAsync (conn => _innerQuery.Count ()); } /// /// Execute SELECT COUNT(*) on the query with an additional WHERE clause. /// public Task CountAsync (Expression> predExpr) { return ReadAsync (conn => _innerQuery.Count (predExpr)); } /// /// Returns the element at a given index /// public Task ElementAtAsync (int index) { return ReadAsync (conn => _innerQuery.ElementAt (index)); } /// /// Returns the first element of this query. /// public Task FirstAsync () { return ReadAsync (conn => _innerQuery.First ()); } /// /// Returns the first element of this query, or null if no element is found. /// public Task FirstOrDefaultAsync () { return ReadAsync (conn => _innerQuery.FirstOrDefault ()); } /// /// Returns the first element of this query that matches the predicate. /// public Task FirstAsync (Expression> predExpr) { return ReadAsync (conn => _innerQuery.First (predExpr)); } /// /// Returns the first element of this query that matches the predicate. /// public Task FirstOrDefaultAsync (Expression> predExpr) { return ReadAsync (conn => _innerQuery.FirstOrDefault (predExpr)); } /// /// Delete all the rows that match this query and the given predicate. /// public Task DeleteAsync (Expression> predExpr) { return WriteAsync (conn => _innerQuery.Delete (predExpr)); } /// /// Delete all the rows that match this query. /// public Task DeleteAsync () { return WriteAsync (conn => _innerQuery.Delete ()); } } class SQLiteConnectionPool { class Entry { public SQLiteConnectionWithLock Connection { get; private set; } public SQLiteConnectionString ConnectionString { get; } public object TransactionLock { get; } = new object (); public Entry (SQLiteConnectionString connectionString) { ConnectionString = connectionString; Connection = new SQLiteConnectionWithLock (ConnectionString); // If the database is FullMutex, then we don't need to bother locking if (ConnectionString.OpenFlags.HasFlag (SQLiteOpenFlags.FullMutex)) { Connection.SkipLock = true; } } public void Close () { var wc = Connection; Connection = null; if (wc != null) { wc.Close (); } } } readonly Dictionary _entries = new Dictionary (); readonly object _entriesLock = new object (); static readonly SQLiteConnectionPool _shared = new SQLiteConnectionPool (); /// /// Gets the singleton instance of the connection tool. /// public static SQLiteConnectionPool Shared { get { return _shared; } } public SQLiteConnectionWithLock GetConnection (SQLiteConnectionString connectionString) { return GetConnectionAndTransactionLock (connectionString, out var _); } public SQLiteConnectionWithLock GetConnectionAndTransactionLock (SQLiteConnectionString connectionString, out object transactionLock) { var key = connectionString.UniqueKey; Entry entry; lock (_entriesLock) { if (!_entries.TryGetValue (key, out entry)) { // The opens the database while we're locked // This is to ensure another thread doesn't get an unopened database entry = new Entry (connectionString); _entries[key] = entry; } transactionLock = entry.TransactionLock; return entry.Connection; } } public void CloseConnection (SQLiteConnectionString connectionString) { var key = connectionString.UniqueKey; Entry entry; lock (_entriesLock) { if (_entries.TryGetValue (key, out entry)) { _entries.Remove (key); } } entry?.Close (); } /// /// Closes all connections managed by this pool. /// public void Reset () { List entries; lock (_entriesLock) { entries = new List (_entries.Values); _entries.Clear (); } foreach (var e in entries) { e.Close (); } } } /// /// This is a normal connection except it contains a Lock method that /// can be used to serialize access to the database /// in lieu of using the sqlite's FullMutex support. /// public class SQLiteConnectionWithLock : SQLiteConnection { readonly object _lockPoint = new object (); /// /// Initializes a new instance of the class. /// /// Connection string containing the DatabasePath. public SQLiteConnectionWithLock (SQLiteConnectionString connectionString) : base (connectionString) { } /// /// Gets or sets a value indicating whether this skip lock. /// /// true if skip lock; otherwise, false. public bool SkipLock { get; set; } /// /// Lock the database to serialize access to it. To unlock it, call Dispose /// on the returned object. /// /// The lock. public IDisposable Lock () { return SkipLock ? (IDisposable)new FakeLockWrapper() : new LockWrapper (_lockPoint); } class LockWrapper : IDisposable { object _lockPoint; public LockWrapper (object lockPoint) { _lockPoint = lockPoint; Monitor.Enter (_lockPoint); } public void Dispose () { Monitor.Exit (_lockPoint); } } class FakeLockWrapper : IDisposable { public void Dispose () { } } } }