Decompiled source of AsyncLoggers Experimental v20.1.2
BepInEx/patchers/AsyncLoggers.dll
Decompiled 4 months ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using System.Xml; using AsyncLoggers.API; using AsyncLoggers.BepInExListeners; using AsyncLoggers.Buffer; using AsyncLoggers.Cecil; using AsyncLoggers.Cecil.Decompiler; using AsyncLoggers.Cecil.Decompiler.Implementation; using AsyncLoggers.Cecil.Decompiler.Implementation.Composite; using AsyncLoggers.Config; using AsyncLoggers.Patches; using AsyncLoggers.Proxy; using AsyncLoggers.Wrappers; using AsyncLoggers.Wrappers.EventArgs; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using BepInEx.Preloader; using HarmonyLib; using JetBrains.Annotations; using Microsoft.CodeAnalysis; using Mono.Cecil; using Mono.Cecil.Cil; using Mono.Collections.Generic; using SQLite; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: IgnoresAccessChecksTo("BepInEx")] [assembly: IgnoresAccessChecksTo("BepInEx.Preloader")] [assembly: IgnoresAccessChecksTo("UnityEngine.CoreModule")] [assembly: IgnoresAccessChecksTo("UnityEngine")] [assembly: AssemblyCompany("AsyncLoggers")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("2.1.2.0")] [assembly: AssemblyInformationalVersion("2.1.2+f9919e13ac1c672d33457af7afc1245c3637c144")] [assembly: AssemblyProduct("Async Loggers")] [assembly: AssemblyTitle("AsyncLoggers")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("2.1.2.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace SQLite { public class SQLiteException : Exception { public SQLite3.Result Result { get; private set; } protected SQLiteException(SQLite3.Result r, string message) : base(message) { Result = r; } public static SQLiteException New(SQLite3.Result r, string message) { return new SQLiteException(r, message); } } public class NotNullConstraintViolationException : SQLiteException { public IEnumerable<TableMapping.Column> Columns { get; protected set; } protected NotNullConstraintViolationException(SQLite3.Result r, string message) : this(r, message, null, null) { } protected NotNullConstraintViolationException(SQLite3.Result r, string message, TableMapping mapping, object obj) : base(r, message) { if (mapping != null && obj != null) { Columns = mapping.Columns.Where((TableMapping.Column c) => !c.IsNullable && c.GetValue(obj) == null); } } public new static NotNullConstraintViolationException New(SQLite3.Result r, string message) { return new NotNullConstraintViolationException(r, message); } public static NotNullConstraintViolationException New(SQLite3.Result r, string message, TableMapping mapping, object obj) { return new NotNullConstraintViolationException(r, message, mapping, obj); } public static NotNullConstraintViolationException New(SQLiteException exception, TableMapping mapping, object obj) { return new NotNullConstraintViolationException(exception.Result, exception.Message, mapping, obj); } } [Flags] public enum SQLiteOpenFlags { ReadOnly = 1, ReadWrite = 2, Create = 4, NoMutex = 0x8000, FullMutex = 0x10000, SharedCache = 0x20000, PrivateCache = 0x40000, ProtectionComplete = 0x100000, ProtectionCompleteUnlessOpen = 0x200000, ProtectionCompleteUntilFirstUserAuthentication = 0x300000, ProtectionNone = 0x400000 } [Flags] public enum CreateFlags { None = 0, ImplicitPK = 1, ImplicitIndex = 2, AllImplicit = 3, AutoIncPK = 4, FullTextSearch3 = 0x100, FullTextSearch4 = 0x200 } public interface ISQLiteConnection { IntPtr Handle { get; } string DatabasePath { get; } int LibVersionNumber { get; } bool TimeExecution { get; set; } bool Trace { get; set; } Action<string> Tracer { get; set; } bool StoreDateTimeAsTicks { get; } bool StoreTimeSpanAsTicks { get; } string DateTimeStringFormat { get; } TimeSpan BusyTimeout { get; set; } IEnumerable<TableMapping> TableMappings { get; } bool IsInTransaction { get; } event EventHandler<NotifyTableChangedEventArgs> TableChanged; void Backup(string destinationDatabasePath, string databaseName = "main"); void BeginTransaction(); void Close(); void Commit(); SQLiteCommand CreateCommand(string cmdText, params object[] ps); SQLiteCommand CreateCommand(string cmdText, Dictionary<string, object> args); int CreateIndex(string indexName, string tableName, string[] columnNames, bool unique = false); int CreateIndex(string indexName, string tableName, string columnName, bool unique = false); int CreateIndex(string tableName, string columnName, bool unique = false); int CreateIndex(string tableName, string[] columnNames, bool unique = false); int CreateIndex<T>(Expression<Func<T, object>> property, bool unique = false); CreateTableResult CreateTable<T>(CreateFlags createFlags = CreateFlags.None); CreateTableResult CreateTable(Type ty, CreateFlags createFlags = CreateFlags.None); CreateTablesResult CreateTables<T, T2>(CreateFlags createFlags = CreateFlags.None) where T : new() where T2 : new(); CreateTablesResult CreateTables<T, T2, T3>(CreateFlags createFlags = CreateFlags.None) where T : new() where T2 : new() where T3 : new(); CreateTablesResult CreateTables<T, T2, T3, T4>(CreateFlags createFlags = CreateFlags.None) where T : new() where T2 : new() where T3 : new() where T4 : new(); CreateTablesResult CreateTables<T, T2, T3, T4, T5>(CreateFlags createFlags = CreateFlags.None) where T : new() where T2 : new() where T3 : new() where T4 : new() where T5 : new(); CreateTablesResult CreateTables(CreateFlags createFlags = CreateFlags.None, params Type[] types); IEnumerable<T> DeferredQuery<T>(string query, params object[] args) where T : new(); IEnumerable<object> DeferredQuery(TableMapping map, string query, params object[] args); int Delete(object objectToDelete); int Delete<T>(object primaryKey); int Delete(object primaryKey, TableMapping map); int DeleteAll<T>(); int DeleteAll(TableMapping map); void Dispose(); int DropTable<T>(); int DropTable(TableMapping map); void EnableLoadExtension(bool enabled); void EnableWriteAheadLogging(); int Execute(string query, params object[] args); T ExecuteScalar<T>(string query, params object[] args); T Find<T>(object pk) where T : new(); object Find(object pk, TableMapping map); T Find<T>(Expression<Func<T, bool>> predicate) where T : new(); T FindWithQuery<T>(string query, params object[] args) where T : new(); object FindWithQuery(TableMapping map, string query, params object[] args); T Get<T>(object pk) where T : new(); object Get(object pk, TableMapping map); T Get<T>(Expression<Func<T, bool>> predicate) where T : new(); TableMapping GetMapping(Type type, CreateFlags createFlags = CreateFlags.None); TableMapping GetMapping<T>(CreateFlags createFlags = CreateFlags.None); List<SQLiteConnection.ColumnInfo> GetTableInfo(string tableName); int Insert(object obj); int Insert(object obj, Type objType); int Insert(object obj, string extra); int Insert(object obj, string extra, Type objType); int InsertAll(IEnumerable objects, bool runInTransaction = true); int InsertAll(IEnumerable objects, string extra, bool runInTransaction = true); int InsertAll(IEnumerable objects, Type objType, bool runInTransaction = true); int InsertOrReplace(object obj); int InsertOrReplace(object obj, Type objType); List<T> Query<T>(string query, params object[] args) where T : new(); List<object> Query(TableMapping map, string query, params object[] args); List<T> QueryScalars<T>(string query, params object[] args); void Release(string savepoint); void Rollback(); void RollbackTo(string savepoint); void RunInTransaction(Action action); string SaveTransactionPoint(); TableQuery<T> Table<T>() where T : new(); int Update(object obj); int Update(object obj, Type objType); int UpdateAll(IEnumerable objects, bool runInTransaction = true); } [Preserve(AllMembers = true)] public class SQLiteConnection : IDisposable, ISQLiteConnection { private struct IndexedColumn { public int Order; public string ColumnName; } private struct IndexInfo { public string IndexName; public string TableName; public bool Unique; public List<IndexedColumn> Columns; } [Preserve(AllMembers = true)] public class ColumnInfo { [Column("name")] public string Name { get; set; } public int notnull { get; set; } public override string ToString() { return Name; } } private bool _open; private TimeSpan _busyTimeout; private static readonly Dictionary<string, TableMapping> _mappings = new Dictionary<string, TableMapping>(); private Stopwatch _sw; private long _elapsedMilliseconds; private int _transactionDepth; private Random _rand = new Random(); private static readonly IntPtr NullHandle = default(IntPtr); private static readonly IntPtr NullBackupHandle = default(IntPtr); private readonly Dictionary<Tuple<string, string>, PreparedSqlLiteInsertCommand> _insertCommandMap = new Dictionary<Tuple<string, string>, PreparedSqlLiteInsertCommand>(); public IntPtr Handle { get; private set; } public string DatabasePath { get; private set; } public int LibVersionNumber { get; private set; } public bool TimeExecution { get; set; } public bool Trace { get; set; } public Action<string> Tracer { get; set; } public bool StoreDateTimeAsTicks { get; private set; } public bool StoreTimeSpanAsTicks { get; private set; } public string DateTimeStringFormat { get; private set; } internal DateTimeStyles DateTimeStyle { get; private set; } public TimeSpan BusyTimeout { get { return _busyTimeout; } set { _busyTimeout = value; if (Handle != NullHandle) { SQLite3.BusyTimeout(Handle, (int)_busyTimeout.TotalMilliseconds); } } } public IEnumerable<TableMapping> TableMappings { get { lock (_mappings) { return new List<TableMapping>(_mappings.Values); } } } public bool IsInTransaction => _transactionDepth > 0; public event EventHandler<NotifyTableChangedEventArgs> TableChanged; public SQLiteConnection(string databasePath, bool storeDateTimeAsTicks = true) : this(new SQLiteConnectionString(databasePath, SQLiteOpenFlags.ReadWrite | SQLiteOpenFlags.Create, storeDateTimeAsTicks)) { } public SQLiteConnection(string databasePath, SQLiteOpenFlags openFlags, bool storeDateTimeAsTicks = true) : this(new SQLiteConnectionString(databasePath, openFlags, storeDateTimeAsTicks)) { } public SQLiteConnection(SQLiteConnectionString connectionString) { if (connectionString == null) { throw new ArgumentNullException("connectionString"); } if (connectionString.DatabasePath == null) { throw new InvalidOperationException("DatabasePath must be specified"); } DatabasePath = connectionString.DatabasePath; LibVersionNumber = SQLite3.LibVersionNumber(); byte[] nullTerminatedUtf = GetNullTerminatedUtf8(connectionString.DatabasePath); IntPtr db; SQLite3.Result result = SQLite3.Open(nullTerminatedUtf, out db, (int)connectionString.OpenFlags, connectionString.VfsName); Handle = db; if (result != 0) { throw SQLiteException.New(result, $"Could not open database file: {DatabasePath} ({result})"); } _open = true; StoreDateTimeAsTicks = connectionString.StoreDateTimeAsTicks; StoreTimeSpanAsTicks = connectionString.StoreTimeSpanAsTicks; DateTimeStringFormat = connectionString.DateTimeStringFormat; DateTimeStyle = connectionString.DateTimeStyle; BusyTimeout = TimeSpan.FromSeconds(1.0); Tracer = delegate { }; connectionString.PreKeyAction?.Invoke(this); if (connectionString.Key is string key) { SetKey(key); } else if (connectionString.Key is byte[] key2) { SetKey(key2); } else if (connectionString.Key != null) { throw new InvalidOperationException("Encryption keys must be strings or byte arrays"); } connectionString.PostKeyAction?.Invoke(this); } public void EnableWriteAheadLogging() { ExecuteScalar<string>("PRAGMA journal_mode=WAL", Array.Empty<object>()); } private static string Quote(string unsafeString) { if (unsafeString == null) { return "NULL"; } string text = unsafeString.Replace("'", "''"); return "'" + text + "'"; } private void SetKey(string key) { if (key == null) { throw new ArgumentNullException("key"); } string text = Quote(key); ExecuteScalar<string>("pragma key = " + text, Array.Empty<object>()); } private void SetKey(byte[] key) { if (key == null) { throw new ArgumentNullException("key"); } if (key.Length != 32 && key.Length != 48) { throw new ArgumentException("Key must be 32 bytes (256-bit) or 48 bytes (384-bit)", "key"); } string text = string.Join("", key.Select((byte x) => x.ToString("X2"))); ExecuteScalar<string>("pragma key = \"x'" + text + "'\"", Array.Empty<object>()); } public void EnableLoadExtension(bool enabled) { SQLite3.Result result = SQLite3.EnableLoadExtension(Handle, enabled ? 1 : 0); if (result != 0) { string errmsg = SQLite3.GetErrmsg(Handle); throw SQLiteException.New(result, errmsg); } } private static byte[] GetNullTerminatedUtf8(string s) { int byteCount = Encoding.UTF8.GetByteCount(s); byte[] array = new byte[byteCount + 1]; byteCount = Encoding.UTF8.GetBytes(s, 0, s.Length, array, 0); return array; } public TableMapping GetMapping(Type type, CreateFlags createFlags = CreateFlags.None) { string fullName = type.FullName; TableMapping value; lock (_mappings) { if (_mappings.TryGetValue(fullName, out value)) { if (createFlags != 0 && createFlags != value.CreateFlags) { value = new TableMapping(type, createFlags); _mappings[fullName] = value; } } else { value = new TableMapping(type, createFlags); _mappings.Add(fullName, value); } } return value; } public TableMapping GetMapping<T>(CreateFlags createFlags = CreateFlags.None) { return GetMapping(typeof(T), createFlags); } public int DropTable<T>() { return DropTable(GetMapping(typeof(T))); } public int DropTable(TableMapping map) { string query = $"drop table if exists \"{map.TableName}\""; return Execute(query); } public CreateTableResult CreateTable<T>(CreateFlags createFlags = CreateFlags.None) { return CreateTable(typeof(T), createFlags); } public CreateTableResult CreateTable(Type ty, CreateFlags createFlags = CreateFlags.None) { TableMapping mapping = GetMapping(ty, createFlags); if (mapping.Columns.Length == 0) { throw new Exception($"Cannot create a table without columns (does '{ty.FullName}' have public properties?)"); } CreateTableResult result = CreateTableResult.Created; List<ColumnInfo> tableInfo = GetTableInfo(mapping.TableName); if (tableInfo.Count == 0) { bool flag = (createFlags & CreateFlags.FullTextSearch3) != 0; bool flag2 = (createFlags & CreateFlags.FullTextSearch4) != 0; string text = ((flag || flag2) ? "virtual " : string.Empty); string text2 = (flag ? "using fts3 " : (flag2 ? "using fts4 " : string.Empty)); string text3 = "create " + text + "table if not exists \"" + mapping.TableName + "\" " + text2 + "(\n"; IEnumerable<string> source = mapping.Columns.Select((TableMapping.Column p) => Orm.SqlDecl(p, StoreDateTimeAsTicks, StoreTimeSpanAsTicks)); string text4 = string.Join(",\n", source.ToArray()); text3 += text4; text3 += ")"; if (mapping.WithoutRowId) { text3 += " without rowid"; } Execute(text3); } else { result = CreateTableResult.Migrated; MigrateTable(mapping, tableInfo); } Dictionary<string, IndexInfo> dictionary = new Dictionary<string, IndexInfo>(); TableMapping.Column[] columns = mapping.Columns; foreach (TableMapping.Column column in columns) { foreach (IndexedAttribute index in column.Indices) { string text5 = index.Name ?? (mapping.TableName + "_" + column.Name); if (!dictionary.TryGetValue(text5, out var value)) { IndexInfo indexInfo = default(IndexInfo); indexInfo.IndexName = text5; indexInfo.TableName = mapping.TableName; indexInfo.Unique = index.Unique; indexInfo.Columns = new List<IndexedColumn>(); value = indexInfo; dictionary.Add(text5, value); } if (index.Unique != value.Unique) { throw new Exception("All the columns in an index must have the same value for their Unique property"); } value.Columns.Add(new IndexedColumn { Order = index.Order, ColumnName = column.Name }); } } foreach (string key in dictionary.Keys) { IndexInfo indexInfo2 = dictionary[key]; string[] columnNames = (from i in indexInfo2.Columns orderby i.Order select i.ColumnName).ToArray(); CreateIndex(key, indexInfo2.TableName, columnNames, indexInfo2.Unique); } return result; } public CreateTablesResult CreateTables<T, T2>(CreateFlags createFlags = CreateFlags.None) where T : new() where T2 : new() { return CreateTables(createFlags, typeof(T), typeof(T2)); } public CreateTablesResult CreateTables<T, T2, T3>(CreateFlags createFlags = CreateFlags.None) where T : new() where T2 : new() where T3 : new() { return CreateTables(createFlags, typeof(T), typeof(T2), typeof(T3)); } public CreateTablesResult CreateTables<T, T2, T3, T4>(CreateFlags createFlags = CreateFlags.None) where T : new() where T2 : new() where T3 : new() where T4 : new() { return CreateTables(createFlags, typeof(T), typeof(T2), typeof(T3), typeof(T4)); } public CreateTablesResult CreateTables<T, T2, T3, T4, T5>(CreateFlags createFlags = CreateFlags.None) where T : new() where T2 : new() where T3 : new() where T4 : new() where T5 : new() { return CreateTables(createFlags, typeof(T), typeof(T2), typeof(T3), typeof(T4), typeof(T5)); } public CreateTablesResult CreateTables(CreateFlags createFlags = CreateFlags.None, params Type[] types) { CreateTablesResult createTablesResult = new CreateTablesResult(); foreach (Type type in types) { CreateTableResult value = CreateTable(type, createFlags); createTablesResult.Results[type] = value; } return createTablesResult; } public int CreateIndex(string indexName, string tableName, string[] columnNames, bool unique = false) { string query = string.Format("create {2} index if not exists \"{3}\" on \"{0}\"(\"{1}\")", tableName, string.Join("\", \"", columnNames), unique ? "unique" : "", indexName); return Execute(query); } public int CreateIndex(string indexName, string tableName, string columnName, bool unique = false) { return CreateIndex(indexName, tableName, new string[1] { columnName }, unique); } public int CreateIndex(string tableName, string columnName, bool unique = false) { return CreateIndex(tableName + "_" + columnName, tableName, columnName, unique); } public int CreateIndex(string tableName, string[] columnNames, bool unique = false) { return CreateIndex(tableName + "_" + string.Join("_", columnNames), tableName, columnNames, unique); } public int CreateIndex<T>(Expression<Func<T, object>> property, bool unique = false) { MemberExpression memberExpression = ((property.Body.NodeType != ExpressionType.Convert) ? (property.Body as MemberExpression) : (((UnaryExpression)property.Body).Operand as MemberExpression)); PropertyInfo propertyInfo = memberExpression.Member as PropertyInfo; if (propertyInfo == null) { throw new ArgumentException("The lambda expression 'property' should point to a valid Property"); } string name = propertyInfo.Name; TableMapping mapping = GetMapping<T>(); string name2 = mapping.FindColumnWithPropertyName(name).Name; return CreateIndex(mapping.TableName, name2, unique); } public List<ColumnInfo> GetTableInfo(string tableName) { string query = "pragma table_info(\"" + tableName + "\")"; return Query<ColumnInfo>(query, Array.Empty<object>()); } private void MigrateTable(TableMapping map, List<ColumnInfo> existingCols) { List<TableMapping.Column> list = new List<TableMapping.Column>(); TableMapping.Column[] columns = map.Columns; foreach (TableMapping.Column column in columns) { bool flag = false; foreach (ColumnInfo existingCol in existingCols) { flag = string.Compare(column.Name, existingCol.Name, StringComparison.OrdinalIgnoreCase) == 0; if (flag) { break; } } if (!flag) { list.Add(column); } } foreach (TableMapping.Column item in list) { string query = "alter table \"" + map.TableName + "\" add column " + Orm.SqlDecl(item, StoreDateTimeAsTicks, StoreTimeSpanAsTicks); Execute(query); } } protected virtual SQLiteCommand NewCommand() { return new SQLiteCommand(this); } public SQLiteCommand CreateCommand(string cmdText, params object[] ps) { if (!_open) { throw SQLiteException.New(SQLite3.Result.Error, "Cannot create commands from unopened database"); } SQLiteCommand sQLiteCommand = NewCommand(); sQLiteCommand.CommandText = cmdText; foreach (object val in ps) { sQLiteCommand.Bind(val); } return sQLiteCommand; } public SQLiteCommand CreateCommand(string cmdText, Dictionary<string, object> args) { if (!_open) { throw SQLiteException.New(SQLite3.Result.Error, "Cannot create commands from unopened database"); } SQLiteCommand sQLiteCommand = NewCommand(); sQLiteCommand.CommandText = cmdText; foreach (KeyValuePair<string, object> arg in args) { sQLiteCommand.Bind(arg.Key, arg.Value); } return sQLiteCommand; } public int Execute(string query, params object[] args) { SQLiteCommand sQLiteCommand = CreateCommand(query, args); if (TimeExecution) { if (_sw == null) { _sw = new Stopwatch(); } _sw.Reset(); _sw.Start(); } int result = sQLiteCommand.ExecuteNonQuery(); if (TimeExecution) { _sw.Stop(); _elapsedMilliseconds += _sw.ElapsedMilliseconds; Tracer?.Invoke($"Finished in {_sw.ElapsedMilliseconds} ms ({(double)_elapsedMilliseconds / 1000.0:0.0} s total)"); } return result; } public T ExecuteScalar<T>(string query, params object[] args) { SQLiteCommand sQLiteCommand = CreateCommand(query, args); if (TimeExecution) { if (_sw == null) { _sw = new Stopwatch(); } _sw.Reset(); _sw.Start(); } T result = sQLiteCommand.ExecuteScalar<T>(); if (TimeExecution) { _sw.Stop(); _elapsedMilliseconds += _sw.ElapsedMilliseconds; Tracer?.Invoke($"Finished in {_sw.ElapsedMilliseconds} ms ({(double)_elapsedMilliseconds / 1000.0:0.0} s total)"); } return result; } public List<T> Query<T>(string query, params object[] args) where T : new() { SQLiteCommand sQLiteCommand = CreateCommand(query, args); return sQLiteCommand.ExecuteQuery<T>(); } public List<T> QueryScalars<T>(string query, params object[] args) { SQLiteCommand sQLiteCommand = CreateCommand(query, args); return sQLiteCommand.ExecuteQueryScalars<T>().ToList(); } public IEnumerable<T> DeferredQuery<T>(string query, params object[] args) where T : new() { SQLiteCommand sQLiteCommand = CreateCommand(query, args); return sQLiteCommand.ExecuteDeferredQuery<T>(); } public List<object> Query(TableMapping map, string query, params object[] args) { SQLiteCommand sQLiteCommand = CreateCommand(query, args); return sQLiteCommand.ExecuteQuery<object>(map); } public IEnumerable<object> DeferredQuery(TableMapping map, string query, params object[] args) { SQLiteCommand sQLiteCommand = CreateCommand(query, args); return sQLiteCommand.ExecuteDeferredQuery<object>(map); } public TableQuery<T> Table<T>() where T : new() { return new TableQuery<T>(this); } public T Get<T>(object pk) where T : new() { TableMapping mapping = GetMapping(typeof(T)); return Query<T>(mapping.GetByPrimaryKeySql, new object[1] { pk }).First(); } public object Get(object pk, TableMapping map) { return Query(map, map.GetByPrimaryKeySql, pk).First(); } public T Get<T>(Expression<Func<T, bool>> predicate) where T : new() { return Table<T>().Where(predicate).First(); } public T Find<T>(object pk) where T : new() { TableMapping mapping = GetMapping(typeof(T)); return Query<T>(mapping.GetByPrimaryKeySql, new object[1] { pk }).FirstOrDefault(); } public object Find(object pk, TableMapping map) { return Query(map, map.GetByPrimaryKeySql, pk).FirstOrDefault(); } public T Find<T>(Expression<Func<T, bool>> predicate) where T : new() { return Table<T>().Where(predicate).FirstOrDefault(); } public T FindWithQuery<T>(string query, params object[] args) where T : new() { return Query<T>(query, args).FirstOrDefault(); } public object FindWithQuery(TableMapping map, string query, params object[] args) { return Query(map, query, args).FirstOrDefault(); } public void BeginTransaction() { if (Interlocked.CompareExchange(ref _transactionDepth, 1, 0) == 0) { try { Execute("begin transaction"); return; } catch (Exception ex) { if (ex is SQLiteException ex2) { switch (ex2.Result) { case SQLite3.Result.Busy: case SQLite3.Result.NoMem: case SQLite3.Result.Interrupt: case SQLite3.Result.IOError: case SQLite3.Result.Full: RollbackTo(null, noThrow: true); break; } } else { Interlocked.Decrement(ref _transactionDepth); } throw; } } throw new InvalidOperationException("Cannot begin a transaction while already in a transaction."); } public string SaveTransactionPoint() { int num = Interlocked.Increment(ref _transactionDepth) - 1; string text = "S" + _rand.Next(32767) + "D" + num; try { Execute("savepoint " + text); return text; } catch (Exception ex) { if (ex is SQLiteException ex2) { switch (ex2.Result) { case SQLite3.Result.Busy: case SQLite3.Result.NoMem: case SQLite3.Result.Interrupt: case SQLite3.Result.IOError: case SQLite3.Result.Full: RollbackTo(null, noThrow: true); break; } } else { Interlocked.Decrement(ref _transactionDepth); } throw; } } public void Rollback() { RollbackTo(null, noThrow: false); } public void RollbackTo(string savepoint) { RollbackTo(savepoint, noThrow: false); } private void RollbackTo(string savepoint, bool noThrow) { try { if (string.IsNullOrEmpty(savepoint)) { if (Interlocked.Exchange(ref _transactionDepth, 0) > 0) { Execute("rollback"); } } else { DoSavePointExecute(savepoint, "rollback to "); } } catch (SQLiteException) { if (!noThrow) { throw; } } } public void Release(string savepoint) { try { DoSavePointExecute(savepoint, "release "); } catch (SQLiteException ex) { if (ex.Result == SQLite3.Result.Busy) { try { Execute("rollback"); } catch { } } throw; } } private void DoSavePointExecute(string savepoint, string cmd) { int num = savepoint.IndexOf('D'); if (num >= 2 && savepoint.Length > num + 1 && int.TryParse(savepoint.Substring(num + 1), out var result) && 0 <= result && result < _transactionDepth) { Thread.VolatileWrite(ref _transactionDepth, result); Execute(cmd + savepoint); return; } throw new ArgumentException("savePoint is not valid, and should be the result of a call to SaveTransactionPoint.", "savePoint"); } public void Commit() { if (Interlocked.Exchange(ref _transactionDepth, 0) == 0) { return; } try { Execute("commit"); } catch { try { Execute("rollback"); } catch { } throw; } } public void RunInTransaction(Action action) { try { string savepoint = SaveTransactionPoint(); action(); Release(savepoint); } catch (Exception) { Rollback(); throw; } } public int InsertAll(IEnumerable objects, bool runInTransaction = true) { int c = 0; if (runInTransaction) { RunInTransaction(delegate { foreach (object @object in objects) { c += Insert(@object); } }); } else { foreach (object object2 in objects) { c += Insert(object2); } } return c; } public int InsertAll(IEnumerable objects, string extra, bool runInTransaction = true) { int c = 0; if (runInTransaction) { RunInTransaction(delegate { foreach (object @object in objects) { c += Insert(@object, extra); } }); } else { foreach (object object2 in objects) { c += Insert(object2, extra); } } return c; } public int InsertAll(IEnumerable objects, Type objType, bool runInTransaction = true) { int c = 0; if (runInTransaction) { RunInTransaction(delegate { foreach (object @object in objects) { c += Insert(@object, objType); } }); } else { foreach (object object2 in objects) { c += Insert(object2, objType); } } return c; } public int Insert(object obj) { if (obj == null) { return 0; } return Insert(obj, "", Orm.GetType(obj)); } public int InsertOrReplace(object obj) { if (obj == null) { return 0; } return Insert(obj, "OR REPLACE", Orm.GetType(obj)); } public int Insert(object obj, Type objType) { return Insert(obj, "", objType); } public int InsertOrReplace(object obj, Type objType) { return Insert(obj, "OR REPLACE", objType); } public int Insert(object obj, string extra) { if (obj == null) { return 0; } return Insert(obj, extra, Orm.GetType(obj)); } public int Insert(object obj, string extra, Type objType) { if (obj == null || objType == null) { return 0; } TableMapping mapping = GetMapping(objType); if (mapping.PK != null && mapping.PK.IsAutoGuid && mapping.PK.GetValue(obj).Equals(Guid.Empty)) { mapping.PK.SetValue(obj, Guid.NewGuid()); } TableMapping.Column[] array = ((string.Compare(extra, "OR REPLACE", StringComparison.OrdinalIgnoreCase) == 0) ? mapping.InsertOrReplaceColumns : mapping.InsertColumns); object[] array2 = new object[array.Length]; for (int i = 0; i < array2.Length; i++) { array2[i] = array[i].GetValue(obj); } PreparedSqlLiteInsertCommand insertCommand = GetInsertCommand(mapping, extra); int num; lock (insertCommand) { try { num = insertCommand.ExecuteNonQuery(array2); } catch (SQLiteException ex) { if (SQLite3.ExtendedErrCode(Handle) == SQLite3.ExtendedResult.ConstraintNotNull) { throw NotNullConstraintViolationException.New(ex.Result, ex.Message, mapping, obj); } throw; } if (mapping.HasAutoIncPK) { long id = SQLite3.LastInsertRowid(Handle); mapping.SetAutoIncPK(obj, id); } } if (num > 0) { OnTableChanged(mapping, NotifyTableChangedAction.Insert); } return num; } private PreparedSqlLiteInsertCommand GetInsertCommand(TableMapping map, string extra) { Tuple<string, string> key = Tuple.Create(map.MappedType.FullName, extra); PreparedSqlLiteInsertCommand value; lock (_insertCommandMap) { if (_insertCommandMap.TryGetValue(key, out value)) { return value; } } value = CreateInsertCommand(map, extra); lock (_insertCommandMap) { if (_insertCommandMap.TryGetValue(key, out var value2)) { value.Dispose(); return value2; } _insertCommandMap.Add(key, value); return value; } } private PreparedSqlLiteInsertCommand CreateInsertCommand(TableMapping map, string extra) { TableMapping.Column[] array = map.InsertColumns; string commandText; if (array.Length == 0 && map.Columns.Length == 1 && map.Columns[0].IsAutoInc) { commandText = string.Format("insert {1} into \"{0}\" default values", map.TableName, extra); } else { if (string.Compare(extra, "OR REPLACE", StringComparison.OrdinalIgnoreCase) == 0) { array = map.InsertOrReplaceColumns; } commandText = string.Format("insert {3} into \"{0}\"({1}) values ({2})", map.TableName, string.Join(",", array.Select((TableMapping.Column c) => "\"" + c.Name + "\"").ToArray()), string.Join(",", array.Select((TableMapping.Column c) => "?").ToArray()), extra); } return new PreparedSqlLiteInsertCommand(this, commandText); } public int Update(object obj) { if (obj == null) { return 0; } return Update(obj, Orm.GetType(obj)); } public int Update(object obj, Type objType) { int num = 0; if (obj == null || objType == null) { return 0; } TableMapping mapping = GetMapping(objType); TableMapping.Column pk = mapping.PK; if (pk == null) { throw new NotSupportedException("Cannot update " + mapping.TableName + ": it has no PK"); } IEnumerable<TableMapping.Column> source = mapping.Columns.Where((TableMapping.Column p) => p != pk); IEnumerable<object> collection = source.Select((TableMapping.Column c) => c.GetValue(obj)); List<object> list = new List<object>(collection); if (list.Count == 0) { source = mapping.Columns; collection = source.Select((TableMapping.Column c) => c.GetValue(obj)); list = new List<object>(collection); } list.Add(pk.GetValue(obj)); string query = string.Format("update \"{0}\" set {1} where \"{2}\" = ? ", mapping.TableName, string.Join(",", source.Select((TableMapping.Column c) => "\"" + c.Name + "\" = ? ").ToArray()), pk.Name); try { num = Execute(query, list.ToArray()); } catch (SQLiteException ex) { if (ex.Result == SQLite3.Result.Constraint && SQLite3.ExtendedErrCode(Handle) == SQLite3.ExtendedResult.ConstraintNotNull) { throw NotNullConstraintViolationException.New(ex, mapping, obj); } throw ex; } if (num > 0) { OnTableChanged(mapping, NotifyTableChangedAction.Update); } return num; } public int UpdateAll(IEnumerable objects, bool runInTransaction = true) { int c = 0; if (runInTransaction) { RunInTransaction(delegate { foreach (object @object in objects) { c += Update(@object); } }); } else { foreach (object object2 in objects) { c += Update(object2); } } return c; } public int Delete(object objectToDelete) { TableMapping mapping = GetMapping(Orm.GetType(objectToDelete)); TableMapping.Column pK = mapping.PK; if (pK == null) { throw new NotSupportedException("Cannot delete " + mapping.TableName + ": it has no PK"); } string query = $"delete from \"{mapping.TableName}\" where \"{pK.Name}\" = ?"; int num = Execute(query, pK.GetValue(objectToDelete)); if (num > 0) { OnTableChanged(mapping, NotifyTableChangedAction.Delete); } return num; } public int Delete<T>(object primaryKey) { return Delete(primaryKey, GetMapping(typeof(T))); } public int Delete(object primaryKey, TableMapping map) { TableMapping.Column pK = map.PK; if (pK == null) { throw new NotSupportedException("Cannot delete " + map.TableName + ": it has no PK"); } string query = $"delete from \"{map.TableName}\" where \"{pK.Name}\" = ?"; int num = Execute(query, primaryKey); if (num > 0) { OnTableChanged(map, NotifyTableChangedAction.Delete); } return num; } public int DeleteAll<T>() { TableMapping mapping = GetMapping(typeof(T)); return DeleteAll(mapping); } public int DeleteAll(TableMapping map) { string query = $"delete from \"{map.TableName}\""; int num = Execute(query); if (num > 0) { OnTableChanged(map, NotifyTableChangedAction.Delete); } return num; } public void Backup(string destinationDatabasePath, string databaseName = "main") { SQLite3.Result result = SQLite3.Open(destinationDatabasePath, out var db); if (result != 0) { throw SQLiteException.New(result, "Failed to open destination database"); } IntPtr intPtr = SQLite3.BackupInit(db, databaseName, Handle, databaseName); if (intPtr == NullBackupHandle) { SQLite3.Close(db); throw new Exception("Failed to create backup"); } SQLite3.BackupStep(intPtr, -1); SQLite3.BackupFinish(intPtr); result = SQLite3.GetResult(db); string message = ""; if (result != 0) { message = SQLite3.GetErrmsg(db); } SQLite3.Close(db); if (result != 0) { throw SQLiteException.New(result, message); } } ~SQLiteConnection() { Dispose(disposing: false); } public void Dispose() { Dispose(disposing: true); GC.SuppressFinalize(this); } public void Close() { Dispose(disposing: true); } protected virtual void Dispose(bool disposing) { bool flag = LibVersionNumber >= 3007014; if (!_open || !(Handle != NullHandle)) { return; } try { if (disposing) { lock (_insertCommandMap) { foreach (PreparedSqlLiteInsertCommand value in _insertCommandMap.Values) { value.Dispose(); } _insertCommandMap.Clear(); } SQLite3.Result result = (flag ? SQLite3.Close2(Handle) : SQLite3.Close(Handle)); if (result != 0) { string errmsg = SQLite3.GetErrmsg(Handle); throw SQLiteException.New(result, errmsg); } } else { SQLite3.Result result2 = (flag ? SQLite3.Close2(Handle) : SQLite3.Close(Handle)); } } finally { Handle = NullHandle; _open = false; } } private void OnTableChanged(TableMapping table, NotifyTableChangedAction action) { this.TableChanged?.Invoke(this, new NotifyTableChangedEventArgs(table, action)); } } public class NotifyTableChangedEventArgs : EventArgs { public TableMapping Table { get; private set; } public NotifyTableChangedAction Action { get; private set; } public NotifyTableChangedEventArgs(TableMapping table, NotifyTableChangedAction action) { Table = table; Action = action; } } public enum NotifyTableChangedAction { Insert, Update, Delete } public class SQLiteConnectionString { private const string DateTimeSqliteDefaultFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fff"; public string UniqueKey { get; } public string DatabasePath { get; } public bool StoreDateTimeAsTicks { get; } public bool StoreTimeSpanAsTicks { get; } public string DateTimeStringFormat { get; } public DateTimeStyles DateTimeStyle { get; } public object Key { get; } public SQLiteOpenFlags OpenFlags { get; } public Action<SQLiteConnection> PreKeyAction { get; } public Action<SQLiteConnection> PostKeyAction { get; } public string VfsName { get; } public SQLiteConnectionString(string databasePath, bool storeDateTimeAsTicks = true) : this(databasePath, SQLiteOpenFlags.ReadWrite | SQLiteOpenFlags.Create, storeDateTimeAsTicks) { } public SQLiteConnectionString(string databasePath, bool storeDateTimeAsTicks, object key = null, Action<SQLiteConnection> preKeyAction = null, Action<SQLiteConnection> postKeyAction = null, string vfsName = null) : this(databasePath, SQLiteOpenFlags.ReadWrite | SQLiteOpenFlags.Create, storeDateTimeAsTicks, key, preKeyAction, postKeyAction, vfsName) { } public SQLiteConnectionString(string databasePath, SQLiteOpenFlags openFlags, bool storeDateTimeAsTicks, object key = null, Action<SQLiteConnection> preKeyAction = null, Action<SQLiteConnection> postKeyAction = null, string vfsName = null, string dateTimeStringFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fff", bool storeTimeSpanAsTicks = true) { if (key != null && !(key is byte[]) && !(key is string)) { throw new ArgumentException("Encryption keys must be strings or byte arrays", "key"); } UniqueKey = $"{databasePath}_{(uint)openFlags:X8}"; StoreDateTimeAsTicks = storeDateTimeAsTicks; StoreTimeSpanAsTicks = storeTimeSpanAsTicks; DateTimeStringFormat = dateTimeStringFormat; DateTimeStyle = (("o".Equals(DateTimeStringFormat, StringComparison.OrdinalIgnoreCase) || "r".Equals(DateTimeStringFormat, StringComparison.OrdinalIgnoreCase)) ? DateTimeStyles.RoundtripKind : DateTimeStyles.None); Key = key; PreKeyAction = preKeyAction; PostKeyAction = postKeyAction; OpenFlags = openFlags; VfsName = vfsName; DatabasePath = databasePath; } } [AttributeUsage(AttributeTargets.Class)] public class TableAttribute : Attribute { public string Name { get; set; } public bool WithoutRowId { get; set; } public TableAttribute(string name) { Name = name; } } [AttributeUsage(AttributeTargets.Property)] public class ColumnAttribute : Attribute { public string Name { get; set; } public ColumnAttribute(string name) { Name = name; } } [AttributeUsage(AttributeTargets.Property)] public class PrimaryKeyAttribute : Attribute { } [AttributeUsage(AttributeTargets.Property)] public class AutoIncrementAttribute : Attribute { } [AttributeUsage(AttributeTargets.Property)] public class IndexedAttribute : Attribute { public string Name { get; set; } public int Order { get; set; } public virtual bool Unique { get; set; } public IndexedAttribute() { } public IndexedAttribute(string name, int order) { Name = name; Order = order; } } [AttributeUsage(AttributeTargets.Property)] public class IgnoreAttribute : Attribute { } [AttributeUsage(AttributeTargets.Property)] public class UniqueAttribute : IndexedAttribute { public override bool Unique { get { return true; } set { } } } [AttributeUsage(AttributeTargets.Property)] public class MaxLengthAttribute : Attribute { public int Value { get; private set; } public MaxLengthAttribute(int length) { Value = length; } } public sealed class PreserveAttribute : Attribute { public bool AllMembers; public bool Conditional; } [AttributeUsage(AttributeTargets.Property)] public class CollationAttribute : Attribute { public string Value { get; private set; } public CollationAttribute(string collation) { Value = collation; } } [AttributeUsage(AttributeTargets.Property)] public class NotNullAttribute : Attribute { } [AttributeUsage(AttributeTargets.Enum)] public class StoreAsTextAttribute : Attribute { } public class TableMapping { public class Column { private MemberInfo _member; public string Name { get; private set; } public PropertyInfo PropertyInfo => _member as PropertyInfo; public string PropertyName => _member.Name; public Type ColumnType { get; private set; } public string Collation { get; private set; } public bool IsAutoInc { get; private set; } public bool IsAutoGuid { get; private set; } public bool IsPK { get; private set; } public IEnumerable<IndexedAttribute> Indices { get; set; } public bool IsNullable { get; private set; } public int? MaxStringLength { get; private set; } public bool StoreAsText { get; private set; } public Column(MemberInfo member, CreateFlags createFlags = CreateFlags.None) { _member = member; Type memberType = GetMemberType(member); CustomAttributeData customAttributeData = member.CustomAttributes.FirstOrDefault((CustomAttributeData x) => x.AttributeType == typeof(ColumnAttribute)); Name = ((customAttributeData == null || customAttributeData.ConstructorArguments.Count <= 0) ? member.Name : customAttributeData.ConstructorArguments[0].Value?.ToString()); ColumnType = Nullable.GetUnderlyingType(memberType) ?? memberType; Collation = Orm.Collation(member); IsPK = Orm.IsPK(member) || ((createFlags & CreateFlags.ImplicitPK) == CreateFlags.ImplicitPK && string.Compare(member.Name, "Id", StringComparison.OrdinalIgnoreCase) == 0); bool flag = Orm.IsAutoInc(member) || (IsPK && (createFlags & CreateFlags.AutoIncPK) == CreateFlags.AutoIncPK); IsAutoGuid = flag && ColumnType == typeof(Guid); IsAutoInc = flag && !IsAutoGuid; Indices = Orm.GetIndices(member); if (!Indices.Any() && !IsPK && (createFlags & CreateFlags.ImplicitIndex) == CreateFlags.ImplicitIndex && Name.EndsWith("Id", StringComparison.OrdinalIgnoreCase)) { Indices = new IndexedAttribute[1] { new IndexedAttribute() }; } IsNullable = !IsPK && !Orm.IsMarkedNotNull(member); MaxStringLength = Orm.MaxStringLength(member); StoreAsText = memberType.GetTypeInfo().CustomAttributes.Any((CustomAttributeData x) => x.AttributeType == typeof(StoreAsTextAttribute)); } public Column(PropertyInfo member, CreateFlags createFlags = CreateFlags.None) : this((MemberInfo)member, createFlags) { } public void SetValue(object obj, object val) { if (_member is PropertyInfo propertyInfo) { if (val != null && ColumnType.GetTypeInfo().IsEnum) { propertyInfo.SetValue(obj, Enum.ToObject(ColumnType, val)); } else { propertyInfo.SetValue(obj, val); } return; } if (_member is FieldInfo fieldInfo) { if (val != null && ColumnType.GetTypeInfo().IsEnum) { fieldInfo.SetValue(obj, Enum.ToObject(ColumnType, val)); } else { fieldInfo.SetValue(obj, val); } return; } throw new InvalidProgramException("unreachable condition"); } public object GetValue(object obj) { if (_member is PropertyInfo propertyInfo) { return propertyInfo.GetValue(obj); } if (_member is FieldInfo fieldInfo) { return fieldInfo.GetValue(obj); } throw new InvalidProgramException("unreachable condition"); } private static Type GetMemberType(MemberInfo m) { return m.MemberType switch { MemberTypes.Property => ((PropertyInfo)m).PropertyType, MemberTypes.Field => ((FieldInfo)m).FieldType, _ => throw new InvalidProgramException("TableMapping supports properties or fields only."), }; } } internal enum MapMethod { ByName, ByPosition } private readonly Column _autoPk; private readonly Column[] _insertColumns; private readonly Column[] _insertOrReplaceColumns; public Type MappedType { get; private set; } public string TableName { get; private set; } public bool WithoutRowId { get; private set; } public Column[] Columns { get; private set; } public Column PK { get; private set; } public string GetByPrimaryKeySql { get; private set; } public CreateFlags CreateFlags { get; private set; } internal MapMethod Method { get; private set; } public bool HasAutoIncPK { get; private set; } public Column[] InsertColumns => _insertColumns; public Column[] InsertOrReplaceColumns => _insertOrReplaceColumns; public TableMapping(Type type, CreateFlags createFlags = CreateFlags.None) { MappedType = type; CreateFlags = createFlags; TypeInfo typeInfo = type.GetTypeInfo(); TableAttribute tableAttribute = (from x in typeInfo.CustomAttributes where x.AttributeType == typeof(TableAttribute) select (TableAttribute)Orm.InflateAttribute(x)).FirstOrDefault(); TableName = ((tableAttribute != null && !string.IsNullOrEmpty(tableAttribute.Name)) ? tableAttribute.Name : MappedType.Name); WithoutRowId = tableAttribute?.WithoutRowId ?? false; IReadOnlyCollection<MemberInfo> publicMembers = GetPublicMembers(type); List<Column> list = new List<Column>(publicMembers.Count); foreach (MemberInfo item in publicMembers) { if (!item.IsDefined(typeof(IgnoreAttribute), inherit: true)) { list.Add(new Column(item, createFlags)); } } Columns = list.ToArray(); Column[] columns = Columns; foreach (Column column in columns) { if (column.IsAutoInc && column.IsPK) { _autoPk = column; } if (column.IsPK) { PK = column; } } HasAutoIncPK = _autoPk != null; if (PK != null) { GetByPrimaryKeySql = $"select * from \"{TableName}\" where \"{PK.Name}\" = ?"; } else { GetByPrimaryKeySql = $"select * from \"{TableName}\" limit 1"; } _insertColumns = Columns.Where((Column c) => !c.IsAutoInc).ToArray(); _insertOrReplaceColumns = Columns.ToArray(); } private IReadOnlyCollection<MemberInfo> GetPublicMembers(Type type) { if (type.Name.StartsWith("ValueTuple`")) { return GetFieldsFromValueTuple(type); } List<MemberInfo> list = new List<MemberInfo>(); HashSet<string> memberNames = new HashSet<string>(); List<MemberInfo> list2 = new List<MemberInfo>(); do { TypeInfo typeInfo = type.GetTypeInfo(); list2.Clear(); list2.AddRange(typeInfo.DeclaredProperties.Where((PropertyInfo p) => !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)); list.AddRange(list2); foreach (MemberInfo item in list2) { memberNames.Add(item.Name); } type = typeInfo.BaseType; } while (type != typeof(object)); return list; } private IReadOnlyCollection<MemberInfo> GetFieldsFromValueTuple(Type type) { Method = MapMethod.ByPosition; FieldInfo[] fields = type.GetFields(); 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 (IReadOnlyCollection<MemberInfo>)(object)fields; } public void SetAutoIncPK(object obj, long id) { if (_autoPk != null) { _autoPk.SetValue(obj, Convert.ChangeType(id, _autoPk.ColumnType, null)); } } public Column FindColumnWithPropertyName(string propertyName) { return Columns.FirstOrDefault((Column c) => c.PropertyName == propertyName); } public Column FindColumn(string columnName) { if (Method != 0) { throw new InvalidOperationException(string.Format("This {0} is not mapped by name, but {1}.", "TableMapping", Method)); } return Columns.FirstOrDefault((Column c) => c.Name.ToLower() == columnName.ToLower()); } } internal class EnumCacheInfo { public bool IsEnum { get; private set; } public bool StoreAsText { get; private set; } public Dictionary<int, string> EnumValues { get; private set; } public EnumCacheInfo(Type type) { TypeInfo typeInfo = type.GetTypeInfo(); IsEnum = typeInfo.IsEnum; if (!IsEnum) { return; } StoreAsText = typeInfo.CustomAttributes.Any((CustomAttributeData x) => x.AttributeType == typeof(StoreAsTextAttribute)); if (!StoreAsText) { return; } EnumValues = new Dictionary<int, string>(); foreach (object value in Enum.GetValues(type)) { EnumValues[Convert.ToInt32(value)] = value.ToString(); } } } internal static class EnumCache { private static readonly Dictionary<Type, EnumCacheInfo> Cache = new Dictionary<Type, EnumCacheInfo>(); public static EnumCacheInfo GetInfo<T>() { return GetInfo(typeof(T)); } public static EnumCacheInfo GetInfo(Type type) { lock (Cache) { EnumCacheInfo value = null; if (!Cache.TryGetValue(type, out value)) { value = new EnumCacheInfo(type); Cache[type] = value; } return value; } } } public static class Orm { public const int DefaultMaxStringLength = 140; public const string ImplicitPkName = "Id"; public const string ImplicitIndexSuffix = "Id"; public static Type GetType(object obj) { if (obj == null) { return typeof(object); } if (obj is IReflectableType reflectableType) { return reflectableType.GetTypeInfo().AsType(); } return obj.GetType(); } public static string SqlDecl(TableMapping.Column p, bool storeDateTimeAsTicks, bool storeTimeSpanAsTicks) { string text = "\"" + p.Name + "\" " + SqlType(p, storeDateTimeAsTicks, storeTimeSpanAsTicks) + " "; if (p.IsPK) { text += "primary key "; } if (p.IsAutoInc) { text += "autoincrement "; } if (!p.IsNullable) { text += "not null "; } if (!string.IsNullOrEmpty(p.Collation)) { text = text + "collate " + p.Collation + " "; } return text; } public static string SqlType(TableMapping.Column p, bool storeDateTimeAsTicks, bool storeTimeSpanAsTicks) { Type columnType = p.ColumnType; if (columnType == typeof(bool) || columnType == typeof(byte) || columnType == typeof(ushort) || columnType == typeof(sbyte) || columnType == typeof(short) || columnType == typeof(int) || columnType == typeof(uint) || columnType == typeof(long)) { return "integer"; } if (columnType == typeof(float) || columnType == typeof(double) || columnType == typeof(decimal)) { return "float"; } if (columnType == typeof(string) || columnType == typeof(StringBuilder) || columnType == typeof(Uri) || columnType == typeof(UriBuilder)) { int? maxStringLength = p.MaxStringLength; if (maxStringLength.HasValue) { return "varchar(" + maxStringLength.Value + ")"; } return "varchar"; } if (columnType == typeof(TimeSpan)) { if (!storeTimeSpanAsTicks) { return "time"; } return "bigint"; } if (columnType == typeof(DateTime)) { if (!storeDateTimeAsTicks) { return "datetime"; } return "bigint"; } if (columnType == typeof(DateTimeOffset)) { return "bigint"; } if (columnType.GetTypeInfo().IsEnum) { if (p.StoreAsText) { return "varchar"; } return "integer"; } if (columnType == typeof(byte[])) { return "blob"; } if (columnType == typeof(Guid)) { return "varchar(36)"; } throw new NotSupportedException("Don't know about " + columnType); } public static bool IsPK(MemberInfo p) { return p.CustomAttributes.Any((CustomAttributeData x) => x.AttributeType == typeof(PrimaryKeyAttribute)); } public static string Collation(MemberInfo p) { return p.CustomAttributes.Where((CustomAttributeData x) => typeof(CollationAttribute) == x.AttributeType).Select(delegate(CustomAttributeData x) { IList<CustomAttributeTypedArgument> constructorArguments = x.ConstructorArguments; return (constructorArguments.Count <= 0) ? "" : ((constructorArguments[0].Value as string) ?? ""); }).FirstOrDefault() ?? ""; } public static bool IsAutoInc(MemberInfo p) { return p.CustomAttributes.Any((CustomAttributeData x) => x.AttributeType == typeof(AutoIncrementAttribute)); } public static FieldInfo GetField(TypeInfo t, string name) { FieldInfo declaredField = t.GetDeclaredField(name); if (declaredField != null) { return declaredField; } return GetField(t.BaseType.GetTypeInfo(), name); } public static PropertyInfo GetProperty(TypeInfo t, string name) { PropertyInfo declaredProperty = t.GetDeclaredProperty(name); if (declaredProperty != null) { return declaredProperty; } return GetProperty(t.BaseType.GetTypeInfo(), name); } public static object InflateAttribute(CustomAttributeData x) { Type attributeType = x.AttributeType; TypeInfo typeInfo = attributeType.GetTypeInfo(); object[] args = x.ConstructorArguments.Select((CustomAttributeTypedArgument a) => a.Value).ToArray(); object obj = Activator.CreateInstance(x.AttributeType, args); foreach (CustomAttributeNamedArgument namedArgument in x.NamedArguments) { if (namedArgument.IsField) { GetField(typeInfo, namedArgument.MemberName).SetValue(obj, namedArgument.TypedValue.Value); } else { GetProperty(typeInfo, namedArgument.MemberName).SetValue(obj, namedArgument.TypedValue.Value); } } return obj; } public static IEnumerable<IndexedAttribute> GetIndices(MemberInfo p) { TypeInfo indexedInfo = typeof(IndexedAttribute).GetTypeInfo(); return from x in p.CustomAttributes where indexedInfo.IsAssignableFrom(x.AttributeType.GetTypeInfo()) select (IndexedAttribute)InflateAttribute(x); } public static int? MaxStringLength(MemberInfo p) { CustomAttributeData customAttributeData = p.CustomAttributes.FirstOrDefault((CustomAttributeData x) => x.AttributeType == typeof(MaxLengthAttribute)); if (customAttributeData != null) { MaxLengthAttribute maxLengthAttribute = (MaxLengthAttribute)InflateAttribute(customAttributeData); return maxLengthAttribute.Value; } return null; } public static int? MaxStringLength(PropertyInfo p) { return MaxStringLength((MemberInfo)p); } public static bool IsMarkedNotNull(MemberInfo p) { return p.CustomAttributes.Any((CustomAttributeData x) => x.AttributeType == typeof(NotNullAttribute)); } } public class SQLiteCommand { private class Binding { public string Name { get; set; } public object Value { get; set; } public int Index { get; set; } } private SQLiteConnection _conn; private List<Binding> _bindings; private static IntPtr NegativePointer = new IntPtr(-1); public string CommandText { get; set; } public SQLiteCommand(SQLiteConnection conn) { _conn = conn; _bindings = new List<Binding>(); CommandText = ""; } public int ExecuteNonQuery() { if (_conn.Trace) { _conn.Tracer?.Invoke("Executing: " + this); } SQLite3.Result result = SQLite3.Result.OK; IntPtr stmt = Prepare(); result = SQLite3.Step(stmt); Finalize(stmt); switch (result) { case SQLite3.Result.Done: return SQLite3.Changes(_conn.Handle); case SQLite3.Result.Error: { string errmsg = SQLite3.GetErrmsg(_conn.Handle); throw SQLiteException.New(result, errmsg); } case SQLite3.Result.Constraint: if (SQLite3.ExtendedErrCode(_conn.Handle) == SQLite3.ExtendedResult.ConstraintNotNull) { throw NotNullConstraintViolationException.New(result, SQLite3.GetErrmsg(_conn.Handle)); } break; } throw SQLiteException.New(result, SQLite3.GetErrmsg(_conn.Handle)); } public IEnumerable<T> ExecuteDeferredQuery<T>() { return ExecuteDeferredQuery<T>(_conn.GetMapping(typeof(T))); } public List<T> ExecuteQuery<T>() { return ExecuteDeferredQuery<T>(_conn.GetMapping(typeof(T))).ToList(); } public List<T> ExecuteQuery<T>(TableMapping map) { return ExecuteDeferredQuery<T>(map).ToList(); } protected virtual void OnInstanceCreated(object obj) { } public IEnumerable<T> ExecuteDeferredQuery<T>(TableMapping map) { if (_conn.Trace) { _conn.Tracer?.Invoke("Executing Query: " + this); } IntPtr stmt = Prepare(); try { TableMapping.Column[] cols = new TableMapping.Column[SQLite3.ColumnCount(stmt)]; Action<object, IntPtr, int>[] fastColumnSetters = new Action<object, IntPtr, int>[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 methodInfo = null; if (typeof(T) != map.MappedType) { methodInfo = typeof(FastColumnSetter).GetMethod("GetFastSetter", BindingFlags.Static | BindingFlags.NonPublic).MakeGenericMethod(map.MappedType); } for (int i = 0; i < cols.Length; i++) { string columnName = SQLite3.ColumnName16(stmt, i); cols[i] = map.FindColumn(columnName); if (cols[i] != null) { if (methodInfo != null) { fastColumnSetters[i] = (Action<object, IntPtr, int>)methodInfo.Invoke(null, new object[2] { _conn, cols[i] }); } else { fastColumnSetters[i] = FastColumnSetter.GetFastSetter<T>(_conn, cols[i]); } } } } while (SQLite3.Step(stmt) == SQLite3.Result.Row) { object obj = Activator.CreateInstance(map.MappedType); for (int j = 0; j < cols.Length; j++) { if (cols[j] != null) { if (fastColumnSetters[j] != null) { fastColumnSetters[j](obj, stmt, j); continue; } SQLite3.ColType type = SQLite3.ColumnType(stmt, j); object val = ReadCol(stmt, j, type, cols[j].ColumnType); cols[j].SetValue(obj, val); } } OnInstanceCreated(obj); yield return (T)obj; } } finally { SQLite3.Finalize(stmt); } } public T ExecuteScalar<T>() { if (_conn.Trace) { _conn.Tracer?.Invoke("Executing Query: " + this); } T result = default(T); IntPtr stmt = Prepare(); try { SQLite3.Result result2 = SQLite3.Step(stmt); switch (result2) { case SQLite3.Result.Row: { SQLite3.ColType type = SQLite3.ColumnType(stmt, 0); object obj = ReadCol(stmt, 0, type, typeof(T)); if (obj != null) { result = (T)obj; return result; } break; } case SQLite3.Result.Done: break; default: throw SQLiteException.New(result2, SQLite3.GetErrmsg(_conn.Handle)); } } finally { Finalize(stmt); } return result; } public IEnumerable<T> ExecuteQueryScalars<T>() { if (_conn.Trace) { _conn.Tracer?.Invoke("Executing Query: " + this); } IntPtr 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) { SQLite3.ColType type = SQLite3.ColumnType(stmt, 0); object obj = ReadCol(stmt, 0, type, typeof(T)); if (obj == null) { yield return default(T); } else { yield return (T)obj; } } } finally { Finalize(stmt); } } public void Bind(string name, object val) { _bindings.Add(new Binding { Name = name, Value = val }); } public void Bind(object val) { Bind(null, val); } public override string ToString() { string[] array = new string[1 + _bindings.Count]; array[0] = CommandText; int num = 1; foreach (Binding binding in _bindings) { array[num] = $" {num - 1}: {binding.Value}"; num++; } return string.Join(Environment.NewLine, array); } private IntPtr Prepare() { IntPtr intPtr = SQLite3.Prepare2(_conn.Handle, CommandText); BindAll(intPtr); return intPtr; } private void Finalize(IntPtr stmt) { SQLite3.Finalize(stmt); } private void BindAll(IntPtr stmt) { int num = 1; foreach (Binding binding in _bindings) { if (binding.Name != null) { binding.Index = SQLite3.BindParameterIndex(stmt, binding.Name); } else { binding.Index = num++; } BindParameter(stmt, binding.Index, binding.Value, _conn.StoreDateTimeAsTicks, _conn.DateTimeStringFormat, _conn.StoreTimeSpanAsTicks); } } internal static void BindParameter(IntPtr stmt, int index, object value, bool storeDateTimeAsTicks, string dateTimeStringFormat, bool storeTimeSpanAsTicks) { if (value == null) { SQLite3.BindNull(stmt, index); return; } if (value is int) { SQLite3.BindInt(stmt, index, (int)value); return; } if (value is string) { SQLite3.BindText(stmt, index, (string)value, -1, NegativePointer); return; } if (value is byte || value is ushort || value is sbyte || value is short) { SQLite3.BindInt(stmt, index, Convert.ToInt32(value)); return; } if (value is bool) { SQLite3.BindInt(stmt, index, ((bool)value) ? 1 : 0); return; } if (value is uint || value is long) { SQLite3.BindInt64(stmt, index, Convert.ToInt64(value)); return; } if (value is float || value is double || value is decimal) { SQLite3.BindDouble(stmt, index, Convert.ToDouble(value)); return; } if (value is TimeSpan) { if (storeTimeSpanAsTicks) { SQLite3.BindInt64(stmt, index, ((TimeSpan)value).Ticks); } else { SQLite3.BindText(stmt, index, ((TimeSpan)value).ToString(), -1, NegativePointer); } return; } if (value is DateTime) { if (storeDateTimeAsTicks) { SQLite3.BindInt64(stmt, index, ((DateTime)value).Ticks); } else { SQLite3.BindText(stmt, index, ((DateTime)value).ToString(dateTimeStringFormat, CultureInfo.InvariantCulture), -1, NegativePointer); } return; } if (value is DateTimeOffset) { SQLite3.BindInt64(stmt, index, ((DateTimeOffset)value).UtcTicks); return; } if (value is byte[]) { SQLite3.BindBlob(stmt, index, (byte[])value, ((byte[])value).Length, NegativePointer); return; } if (value is Guid) { SQLite3.BindText(stmt, index, ((Guid)value).ToString(), 72, NegativePointer); return; } if (value is Uri) { SQLite3.BindText(stmt, index, ((Uri)value).ToString(), -1, NegativePointer); return; } if (value is StringBuilder) { SQLite3.BindText(stmt, index, ((StringBuilder)value).ToString(), -1, NegativePointer); return; } if (value is UriBuilder) { SQLite3.BindText(stmt, index, ((UriBuilder)value).ToString(), -1, NegativePointer); return; } Type type = value.GetType(); EnumCacheInfo info = EnumCache.GetInfo(type); if (info.IsEnum) { int num = Convert.ToInt32(value); if (info.StoreAsText) { SQLite3.BindText(stmt, index, info.EnumValues[num], -1, NegativePointer); } else { SQLite3.BindInt(stmt, index, num); } return; } throw new NotSupportedException("Cannot store type: " + Orm.GetType(value)); } private object ReadCol(IntPtr stmt, int index, SQLite3.ColType type, Type clrType) { if (type == SQLite3.ColType.Null) { return null; } TypeInfo typeInfo = clrType.GetTypeInfo(); if (typeInfo.IsGenericType && typeInfo.GetGenericTypeDefinition() == typeof(Nullable<>)) { clrType = typeInfo.GenericTypeArguments[0]; typeInfo = clrType.GetTypeInfo(); } if (clrType == typeof(string)) { return SQLite3.ColumnString(stmt, index); } if (clrType == typeof(int)) { return SQLite3.ColumnInt(stmt, index); } if (clrType == typeof(bool)) { return SQLite3.ColumnInt(stmt, index) == 1; } if (clrType == typeof(double)) { return SQLite3.ColumnDouble(stmt, index); } if (clrType == typeof(float)) { return (float)SQLite3.ColumnDouble(stmt, index); } if (clrType == typeof(TimeSpan)) { if (_conn.StoreTimeSpanAsTicks) { return new TimeSpan(SQLite3.ColumnInt64(stmt, index)); } string text = SQLite3.ColumnString(stmt, index); if (!TimeSpan.TryParseExact(text, "c", CultureInfo.InvariantCulture, TimeSpanStyles.None, out var result)) { result = TimeSpan.Parse(text); } return result; } if (clrType == typeof(DateTime)) { if (_conn.StoreDateTimeAsTicks) { return new DateTime(SQLite3.ColumnInt64(stmt, index)); } string s = SQLite3.ColumnString(stmt, index); if (!DateTime.TryParseExact(s, _conn.DateTimeStringFormat, CultureInfo.InvariantCulture, _conn.DateTimeStyle, out var result2)) { result2 = DateTime.Parse(s); } return result2; } if (clrType == typeof(DateTimeOffset)) { return new DateTimeOffset(SQLite3.ColumnInt64(stmt, index), TimeSpan.Zero); } if (typeInfo.IsEnum) { if (type == SQLite3.ColType.Text) { string text2 = SQLite3.ColumnString(stmt, index); return Enum.Parse(clrType, text2.ToString(), ignoreCase: true); } return SQLite3.ColumnInt(stmt, index); } if (clrType == typeof(long)) { return SQLite3.ColumnInt64(stmt, index); } if (clrType == typeof(uint)) { return (uint)SQLite3.ColumnInt64(stmt, index); } if (clrType == typeof(decimal)) { return (decimal)SQLite3.ColumnDouble(stmt, index); } if (clrType == typeof(byte)) { return (byte)SQLite3.ColumnInt(stmt, index); } if (clrType == typeof(ushort)) { return (ushort)SQLite3.ColumnInt(stmt, index); } if (clrType == typeof(short)) { return (short)SQLite3.ColumnInt(stmt, index); } if (clrType == typeof(sbyte)) { return (sbyte)SQLite3.ColumnInt(stmt, index); } if (clrType == typeof(byte[])) { return SQLite3.ColumnByteArray(stmt, index); } if (clrType == typeof(Guid)) { string g = SQLite3.ColumnString(stmt, index); return new Guid(g); } if (clrType == typeof(Uri)) { string uriString = SQLite3.ColumnString(stmt, index); return new Uri(uriString); } if (clrType == typeof(StringBuilder)) { string value = SQLite3.ColumnString(stmt, index); return new StringBuilder(value); } if (clrType == typeof(UriBuilder)) { string uri = SQLite3.ColumnString(stmt, index); return new UriBuilder(uri); } throw new NotSupportedException("Don't know how to read " + clrType); } } internal class FastColumnSetter { internal static Action<object, IntPtr, int> GetFastSetter<T>(SQLiteConnection conn, TableMapping.Column column) { Action<object, IntPtr, int> result = null; Type type = column.PropertyInfo.PropertyType; TypeInfo typeInfo = type.GetTypeInfo(); if (typeInfo.IsGenericType && typeInfo.GetGenericTypeDefinition() == typeof(Nullable<>)) { type = typeInfo.GenericTypeArguments[0]; typeInfo = type.GetTypeInfo(); } if (type == typeof(string)) { result = CreateTypedSetterDelegate<T, string>(column, (IntPtr stmt, int index) => SQLite3.ColumnString(stmt, index)); } else if (type == typeof(int)) { result = CreateNullableTypedSetterDelegate<T, int>(column, (IntPtr stmt, int index) => SQLite3.ColumnInt(stmt, index)); } else if (type == typeof(bool)) { result = CreateNullableTypedSetterDelegate<T, bool>(column, (IntPtr stmt, int index) => SQLite3.ColumnInt(stmt, index) == 1); } else if (type == typeof(double)) { result = CreateNullableTypedSetterDelegate<T, double>(column, (IntPtr stmt, int index) => SQLite3.ColumnDouble(stmt, index)); } else if (type == typeof(float)) { result = CreateNullableTypedSetterDelegate<T, float>(column, (IntPtr stmt, int index) => (float)SQLite3.ColumnDouble(stmt, index)); } else if (type == typeof(TimeSpan)) { result = ((!conn.StoreTimeSpanAsTicks) ? CreateNullableTypedSetterDelegate<T, TimeSpan>(column, delegate(IntPtr stmt, int index) { string text = SQLite3.ColumnString(stmt, index); TimeSpan result3; return (!TimeSpan.TryParseExact(text, "c", CultureInfo.InvariantCulture, TimeSpanStyles.None, out result3)) ? TimeSpan.Parse(text) : result3; }) : CreateNullableTypedSetterDelegate<T, TimeSpan>(column, (IntPtr stmt, int index) => new TimeSpan(SQLite3.ColumnInt64(stmt, index)))); } else if (type == typeof(DateTime)) { result = ((!conn.StoreDateTimeAsTicks) ? CreateNullableTypedSetterDelegate<T, DateTime>(column, delegate(IntPtr stmt, int index) { string s = SQLite3.ColumnString(stmt, index); DateTime result2; return (!DateTime.TryParseExact(s, conn.DateTimeStringFormat, CultureInfo.InvariantCulture, conn.DateTimeStyle, out result2)) ? DateTime.Parse(s) : result2; }) : CreateNullableTypedSetterDelegate<T, DateTime>(column, (IntPtr stmt, int index) => new DateTime(SQLite3.ColumnInt64(stmt, index)))); } else if (type == typeof(DateTimeOffset)) { result = CreateNullableTypedSetterDelegate<T, DateTimeOffset>(column, (IntPtr stmt, int index) => new DateTimeOffset(SQLite3.ColumnInt64(stmt, index), TimeSpan.Zero)); } else if (!typeInfo.IsEnum) { if (type == typeof(long)) { result = CreateNullableTypedSetterDelegate<T, long>(column, (IntPtr stmt, int index) => SQLite3.ColumnInt64(stmt, index)); } else if (type == typeof(uint)) { result = CreateNullableTypedSetterDelegate<T, uint>(column, (IntPtr stmt, int index) => (uint)SQLite3.ColumnInt64(stmt, index)); } else if (type == typeof(decimal)) { result = CreateNullableTypedSetterDelegate<T, decimal>(column, (IntPtr stmt, int index) => (decimal)SQLite3.ColumnDouble(stmt, index)); } else if (type == typeof(byte)) { result = CreateNullableTypedSetterDelegate<T, byte>(column, (IntPtr stmt, int index) => (byte)SQLite3.ColumnInt(stmt, index)); } else if (type == typeof(ushort)) { result = CreateNullableTypedSetterDelegate<T, ushort>(column, (IntPtr stmt, int index) => (ushort)SQLite3.ColumnInt(stmt, index)); } else if (type == typeof(short)) { result = CreateNullableTypedSetterDelegate<T, short>(column, (IntPtr stmt, int index) => (short)SQLite3.ColumnInt(stmt, index)); } else if (type == typeof(sbyte)) { result = CreateNullableTypedSetterDelegate<T, sbyte>(column, (IntPtr stmt, int index) => (sbyte)SQLite3.ColumnInt(stmt, index)); } else if (type == typeof(byte[])) { result = CreateTypedSetterDelegate<T, byte[]>(column, (IntPtr stmt, int index) => SQLite3.ColumnByteArray(stmt, index)); } else if (type == typeof(Guid)) { result = CreateNullableTypedSetterDelegate<T, Guid>(column, delegate(IntPtr stmt, int index) { string g = SQLite3.ColumnString(stmt, index); return new Guid(g); }); } else if (type == typeof(Uri)) { result = CreateTypedSetterDelegate<T, Uri>(column, delegate(IntPtr stmt, int index) { string uriString = SQLite3.ColumnString(stmt, index); return new Uri(uriString); }); } else if (type == typeof(StringBuilder)) { result = CreateTypedSetterDelegate<T, StringBuilder>(column, delegate(IntPtr stmt, int index) { string value = SQLite3.ColumnString(stmt, index); return new StringBuilder(value); }); } else if (type == typeof(UriBuilder)) { result = CreateTypedSetterDelegate<T, UriBuilder>(column, delegate(IntPtr stmt, int index) { string uri = SQLite3.ColumnString(stmt, index); return new UriBuilder(uri); }); } } return result; } private static Action<object, IntPtr, int> CreateNullableTypedSetterDelegate<ObjectType, ColumnMemberType>(TableMapping.Column column, Func<IntPtr, int, ColumnMemberType> getColumnValue) where ColumnMemberType : struct { TypeInfo typeInfo = column.PropertyInfo.PropertyType.GetTypeInfo(); bool flag = false; if (typeInfo.IsGenericType && typeInfo.GetGenericTypeDefinition() == typeof(Nullable<>)) { flag = true; } if (flag) { Action<ObjectType, ColumnMemberType?> setProperty = (Action<ObjectType, ColumnMemberType?>)Delegate.CreateDelegate(typeof(Action<ObjectType, ColumnMemberType?>), null, column.PropertyInfo.GetSetMethod()); return delegate(object o, IntPtr stmt, int i) { SQLite3.ColType colType = SQLite3.ColumnType(stmt, i); if (colType != SQLite3.ColType.Null) { setProperty((ObjectType)o, getColumnValue(stmt, i)); } }; } return CreateTypedSetterDelegate<ObjectType, ColumnMemberType>(column, getColumnValue); } private static Action<object, IntPtr, int> CreateTypedSetterDelegate<ObjectType, ColumnMemberType>(TableMapping.Column column, Func<IntPtr, int, ColumnMemberType> getColumnValue) { Action<ObjectType, ColumnMemberType> setProperty = (Action<ObjectType, ColumnMemberType>)Delegate.CreateDelegate(typeof(Action<ObjectType, ColumnMemberType>), null, column.PropertyInfo.GetSetMethod()); return delegate(object o, IntPtr stmt, int i) { SQLite3.ColType colType = SQLite3.ColumnType(stmt, i); if (colType != SQLite3.ColType.Null) { setProperty((ObjectType)o, getColumnValue(stmt, i)); } }; } } internal class PreparedSqlLiteInsertCommand : IDisposable { private bool Initialized; private SQLiteConnection Connection; private string CommandText; private IntPtr Statement; private static readonly IntPtr NullStatement; public PreparedSqlLiteInsertCommand(SQLiteConnection conn, string commandText) { Connection = conn; CommandText = commandText; } public int ExecuteNonQuery(object[] source) { if (Initialized && Statement == NullStatement) { throw new ObjectDisposedException("PreparedSqlLiteInsertCommand"); } if (Connection.Trace) { Connection.Tracer?.Invoke("Executing: " + CommandText); } SQLite3.Result result = SQLite3.Result.OK; if (!Initialized) { Statement = SQLite3.Prepare2(Connection.Handle, CommandText); Initialized = true; } if (source != null) { for (int i = 0; i < source.Length; i++) { SQLiteCommand.BindParameter(Statement, i + 1, source[i], Connection.StoreDateTimeAsTicks, Connection.DateTimeStringFormat, Connection.StoreTimeSpanAsTicks); } } result = SQLite3.Step(Statement); switch (result) { case SQLite3.Result.Done: { int result2 = SQLite3.Changes(Connection.Handle); SQLite3.Reset(Statement); return result2; } case SQLite3.Result.Error: { string errmsg = SQLite3.GetErrmsg(Connection.Handle); SQLite3.Reset(Statement); throw SQLiteException.New(result, errmsg); } case SQLite3.Result.Constraint: if (SQLite3.ExtendedErrCode(Connection.Handle) == SQLite3.ExtendedResult.ConstraintNotNull) { SQLite3.Reset(Statement); throw NotNullConstraintViolationException.New(result, SQLite3.GetErrmsg(Connection.Handle)); } break; } SQLite3.Reset(Statement); throw SQLiteException.New(result, SQLite3.GetErrmsg(Connection.Handle)); } public void Dispose() { Dispose(disposing: true); GC.SuppressFinalize(this); } private void Dispose(bool disposing) { IntPtr statement = Statement; Statement = NullStatement; Connection = null; if (statement != NullStatement) { SQLite3.Finalize(statement); } } ~PreparedSqlLiteInsertCommand() { Dispose(disposing: false); } } public enum CreateTableResult { Created, Migrated } public class CreateTablesResult { public Dictionary<Type, CreateTableResult> Results { get; private set; } public CreateTablesResult() { Results = new Dictionary<Type, CreateTableResult>(); } } public abstract class BaseTableQuery { protected class Ordering { public string ColumnName { get; set; } public bool Ascending { get; set; } } } public class TableQuery<T> : BaseTableQuery, IEnumerable<T>, IEnumerable { private class CompileResult { public string CommandText { get; set; } public object Value { get; set; } } private Expression _where; private List<Ordering> _orderBys; private int? _limit; private int? _offset; private BaseTableQuery _joinInner; private Expression _joinInnerKeySelector; private BaseTableQuery _joinOuter; private Expression _joinOuterKeySelector; private Expression _joinSelector; private Expression _selector; private bool _deferred; public SQLiteConnection Connection { get; private set; } public TableMapping Table { get; private set; } private TableQuery(SQLiteConnection conn, TableMapping table) { Connection = conn; Table = table; } public TableQuery(SQLiteConnection conn) { Connection = conn; Table = Connection.GetMapping(typeof(T)); } public TableQuery<U> Clone<U>() { TableQuery<U> tableQuery = new TableQuery<U>(Connection, Table); tableQuery._where = _where; tableQuery._deferred = _deferred; if (_orderBys != null) { tableQuery._orderBys = new List<Ordering>(_orderBys); } tableQuery._limit = _limit; tableQuery._offset = _offset; tableQuery._joinInner = _joinInner; tableQuery._joinInnerKeySelector = _joinInnerKeySelector; tableQuery._joinOuter = _joinOuter; tableQuery._joinOuterKeySelector = _joinOuterKeySelector; tableQuery._joinSelector = _joinSelector; tableQuery._selector = _selector; return tableQuery; } public TableQuery<T> Where(Expression<Func<T, bool>> predExpr) { if (predExpr.NodeType == ExpressionType.Lambda) { Expression body = predExpr.Body; TableQuery<T> tableQuery = Clone<T>(); tableQuery.AddWhere(body); return tableQuery; } throw new NotSupportedException("Must be a predicate"); } public int Delete() { return Delete(null); } public int Delete(Expression<Func<T, bool>> predExpr) { if (_limit.HasValue || _offset.HasValue) { throw new InvalidOperationException("Cannot delete with limits or offsets"); } if (_where == null && predExpr == null) { throw new InvalidOperationException("No condition specified"); } Expression expression = _where; if (predExpr != null && predExpr.NodeType == ExpressionType.Lambda) { expression = ((expression != null) ? Expression.AndAlso(expression, predExpr.Body) : predExpr.Body); } List<object> list = new List<object>(); string text = "delete from \"" + Table.TableName + "\""; CompileResult compileResult = CompileExpr(expression, list); text = text + " where " + compileResult.CommandText; SQLiteCommand sQLiteCommand = Connection.CreateCommand(text, list.ToArray()); return sQLiteCommand.ExecuteNonQuery(); } public TableQuery<T> Take(int n) { TableQuery<T> tableQuery = Clone<T>(); tableQuery._limit = n; return tableQuery; } public TableQuery<T> Skip(int n) { TableQuery<T> tableQuery = Clone<T>(); tableQuery._offset = n; return tableQuery; } public T ElementAt(int index) { return Skip(index).Take(1).First(); } public TableQuery<T> Deferred() { TableQuery<T> tableQuery = Clone<T>(); tableQuery._deferred = true; return tableQuery; } public TableQuery<T> OrderBy<U>(Expression<Func<T, U>> orderExpr) { return AddOrderBy(orderExpr, asc: true); } public TableQuery<T> OrderByDescending<U>(Expression<Func<T, U>> orderExpr) { return AddOrderBy(orderExpr, asc: false); } public TableQuery<T> ThenBy<U>(Expression<Func<T, U>> orderExpr) { return AddOrderBy(orderExpr, asc: true); } public TableQuery<T> ThenByDescending<U>(Expression<Func<T, U>> orderExpr) { return AddOrderBy(orderExpr, asc: false); } private TableQuery<T> AddOrderBy<U>(Expression<Func<T, U>> orderExpr, bool asc) { if (orderExpr.NodeType == ExpressionType.Lambda) { MemberExpression memberExpression = null; memberExpression = ((!(orderExpr.Body is UnaryExpression unaryExpression) || unaryExpression.NodeType != ExpressionType.Convert) ? (orderExpr.Body as MemberExpression) : (unaryExpression.Operand as MemberExpression)); if (memberExpression != null && memberExpression.Expression.NodeType == ExpressionType.Parameter) { TableQuery<T> tableQuery = Clone<T>(); if (tableQuery._orderBys == null) { tableQuery._orderBys = new List<Ordering>(); } tableQuery._orderBys.Add(new Ordering { ColumnName = Table.FindColumnWithPropertyName(memberExpression.Member.Name).Name, Ascending = asc }); return tableQuery; } throw new NotSupportedException("Order By does not support: " + orderExpr); } throw new NotSupportedException("Must be a predicate"); } private void AddWhere(Expression pred) { if (_where == null) { _where = pred; } else { _where = Expression.AndAlso(_where, pred); } } private SQLiteCommand GenerateCommand(string selectionList) { if (_joinInner != null && _joinOuter != null) { throw new NotSupportedException("Joins are not supported."); } string text = "select " + selectionList + " from \"" + Table.TableName + "\""; List<object> list = new List<object>(); if (_where != null) { CompileResult compileResult = CompileExpr(_where, list); text = text + " where " + compileResult.CommandText; } if (_orderBys != null && _orderBys.Count > 0) { string text2 = string.Join(", ", _orderBys.Select((Ordering o) => "\"" + o.ColumnName + "\"" + (o.Ascending ? "" : " desc")).ToArray()); text = text + " order by " + text2; } if (_limit.HasValue) { text = text + " limit " + _limit.Value; } if (_offset.HasValue) { if (!_limit.HasValue) { text += " limit -1 "; } text = text + " offset " + _offset.Value; } return Connection.CreateCommand(text, list.ToArray()); } private CompileResult CompileExpr(Expression expr, List<object> queryArgs) { if (expr == null) { throw new NotSupportedException("Expression is NULL"); } if (expr is BinaryExpression) { BinaryExpression binaryExpression = (BinaryExpression)expr; if (binaryExpression.Left.NodeType == ExpressionType.Call) { MethodCallExpression methodCallExpression = (MethodCallExpression)binaryExpression.Left; if (methodCallExpression.Method.DeclaringType.FullName == "Microsoft.VisualBasic.CompilerServices.Operators" && methodCallExpression.Method.Name == "CompareString") { binaryExpression = Expression.MakeBinary(binaryExpression.NodeType, methodCallExpression.Arguments[0], methodCallExpression.Arguments[1]); } } CompileResult compileResult = CompileExpr(binaryExpression.Left, queryArgs); CompileResult compileResult2 = CompileExpr(binaryExpression.Right, queryArgs); string commandText = ((compileResult.CommandText == "?" && compileResult.Value == null) ? CompileNullBinaryExpression(binaryExpression, compileResult2) : ((!(compileResult2.CommandText == "?") || compileResult2.Value != null) ? ("(" + compileResult.CommandText + " " + GetSqlName(binaryExpression) + " " + compileResult2.CommandText + ")") : CompileNullBinaryExpression(binaryExpression, compileResult))); return new CompileResult { CommandText = commandText }; } if (expr.NodeType == ExpressionType.Not) { Expression operand = ((UnaryExpression)expr).Operand; CompileResult compileResult3 = CompileExpr(operand, queryArgs); object obj = compileResult3.Value; if (obj is bool) { obj = !(bool)obj; } return new CompileResult { CommandText = "NOT(" + compileResult3.CommandText + ")", Value = obj }; } if (expr.NodeType == ExpressionType.Call) { MethodCallExpression methodCallExpression2 = (MethodCallExpression)expr; CompileResult[] array = new CompileResult[methodCallExpression2.Arguments.Count]; CompileResult compileResult4 = ((methodCallExpression2.Object != null) ? CompileExpr(methodCallExpression2.Object, queryArgs) : null); for (int i = 0; i < array.Length; i++) { array[i] = CompileExpr(methodCallExpression2.Arguments[i], queryArgs); } string commandText2 = ""; if (methodCallExpression2.Method.Name == "Like" && array.Length == 2) { commandText2 = "(" + array[0].CommandText + " like " + array[1].CommandText + ")"; } else if (methodCallExpression2.Method.Name == "Contains" && array.Length == 2) { commandText2 = "(" + array[1].CommandText + " in " + array[0].CommandText + ")"; } else if (methodCallExpression2.Method.Name == "Contains" && array.Length == 1) { commandText2 = ((methodCallExpression2.Object == null || !(methodCallExpression2.Object.Type == typeof(string))) ? ("(" + array[0].CommandText + " in " + compileResult4.CommandText + ")") : ("( instr(" + compileResult4.CommandText + "," + array[0].CommandText + ") >0 )")); } else if (methodCallExpression2.Method.Name == "StartsWith" && array.Length >= 1) { StringComparison stringComparison = StringComparison.CurrentCulture; if (array.Length == 2) { stringComparison = (StringComparison)array[1].Value; } switch (stringComparison) { case StringComparison.CurrentCulture: case StringComparison.Ordinal: commandText2 = "( substr(" + compileResult4.CommandText + ", 1, " + array[0].Value.ToString().Length + ") = " + array[0].CommandText + ")"; break; case StringComparison.CurrentCultureIgnoreCase: case StringComparison.OrdinalIgnoreCase: commandText2 = "(" + compileResult4.CommandText + " like (" + array[0].CommandText + " || '%'))"; break; } } else if (!(methodCallExpression2.Method.Name == "EndsWith") || array.Length < 1) { commandText2 = ((methodCallExpression2.Method.Name == "Equals" && array.Length == 1) ? ("(" + compileResult4.CommandText + " = (" + array[0].CommandText + "))") : ((methodCallExpression2.Method.Name == "ToLower") ? ("(lower(" + compileResult4.CommandText + "))") : ((methodCallExpression2.Method.Name == "ToUpper") ? ("(upper(" + compileResult4.CommandText + "))") : ((methodCallExpression2.Method.Name == "Replace" && array.Length == 2) ? ("(replace(" + compileResult4.CommandText + "," + array[0].CommandText + "," + array[1].CommandText + "))") : ((!(methodCallExpression2.Method.Name == "IsNullOrEmpty") || array.Length != 1) ? (methodCallExpression2.Method.Name.ToLower() + "(" + string.Join(",", array.Select((CompileResult a) => a.CommandText).ToArray()) + ")") : ("(" + array[0].CommandText + " is null or" + array[0].CommandText + " ='' )")))))); } else { StringComparison stringComparison2 = StringComparison.CurrentCulture; if (array.Length == 2) { stringComparison2 = (StringComparison)array[1].Value; } switch (stringComparison2) { case StringComparison.CurrentCulture: case StringComparison.Ordinal: commandText2 = "( substr(" + compileResult4.CommandText + ", length(" + compileResult4.CommandText + ") - " + array[0].Value.ToString().Length + "+1, " + array[0].Value.ToString().Length + ") = " + array[0].CommandText + ")"; break; case StringComparison.CurrentCultureIgnoreCase: case StringComparison.OrdinalIgnoreCase: commandText2 = "(" + compileResult4.CommandText + " like ('%' || " + array[0].CommandText + "))"; break; } } return new CompileResult { CommandText = commandText2 }; } if (expr.NodeType == ExpressionType.Constant) { ConstantExpression constantExpression = (ConstantExpression)expr; queryArgs.Add(constantExpression.Value); return new CompileResult { CommandText = "?", Value = constantExpression.Value }; } if (expr.NodeType == ExpressionType.Convert) { UnaryExpression unaryExpression = (UnaryExpression)expr; Type type = unaryExpression.Type; CompileResult compileResult5 = CompileExpr(unaryExpression.Operand, queryArgs); return new CompileResult { CommandText = compileResult5.CommandText, Value = ((compileResult5.Value != null) ? ConvertTo(compileResult5.Value, type) : null) }; } if (expr.NodeType == ExpressionType.MemberAccess) { MemberExpression memberExpression = (MemberExpression)expr; ParameterExpression parameterExpression = memberExpression.Expression as ParameterExpression; if (parameterExpression == null && memberExpression.Expression is UnaryExpression unaryExpression2 && unaryExpression2.NodeType == ExpressionType.Convert) { parameterExpression = unaryExpression2.Operand as ParameterExpression; } if (parameterExpression != null) { string name = Table.FindColumnWithPropertyName(memberExpression.Member.Name).Name; return new CompileResult { CommandText = "\"" + name + "\"" }; } object obj2 = null; if (memberExpression.Expression != null) { CompileResult compileResult6 = CompileExpr(memberExpression.Expression, queryArgs); if (compileResult6.Value == null) { throw new NotSupportedException("Member access failed to compile expression"); } if (compileResult6.CommandText == "?") { queryArgs.RemoveAt(queryArgs.Count - 1); } obj2 = compileResult6.Value; } object obj3 = null; if (memberExpression.Member is PropertyInfo) { PropertyInfo propertyInfo = (PropertyInfo)memberExpression.Member; obj3 = propertyInfo.GetValue(obj2, null); } else { if (!(memberExpression.Member is FieldInfo)) { throw new NotSupportedException("MemberExpr: " + memberExpression.Member.GetType()); } FieldInfo fieldInfo = (FieldInfo)memberExpression.Member; obj3 = fieldInfo.GetValue(obj2); } if (obj3 != null && obj3 is IEnumerable && !(obj3 is string) && !(obj3 is IEnumerable<byte>)) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append("("); string value = ""; foreach (object item in (IEnumerable)obj3) { queryArgs.Add(item); stringBuilder.Append(value); stringBuilder.Append("?"); value = ","; } stringBuilder.Append(")"); return new CompileResult { CommandText = stringBuilder.ToString(), Value = obj3 }; } queryArgs.Add(obj3); return new CompileResult { CommandText = "?", Value = obj3 }; } throw new NotSupportedException("Cannot compile: " + expr.NodeType); } private static object ConvertTo(object obj, Type t) { Type underlyingType = Nullable.GetUnderlyingType(t); if (underlyingType != null) { if (obj == null) { return null; } return Convert.ChangeType(obj, underlyingType); } return Convert.ChangeType(obj, t); } private string CompileNullBinaryExpression(BinaryExpression expression, CompileResult parameter) { if (expression.NodeType == ExpressionType.Equal) { return "(" + parameter.CommandText + " is ?)"; } if (expression.NodeType == ExpressionType.NotEqual) { return "(" + parameter.CommandText + " is not ?)"; } if (expression.NodeType == ExpressionType.GreaterThan || expression.NodeType == ExpressionType.GreaterThanOrEqual || expression.NodeType == ExpressionType.LessThan || expression.NodeType == ExpressionType.LessThanOrEqual) { return "(" + parameter.CommandText + " < ?)"; } throw new NotSupportedException("Cannot compile Null-BinaryExpression with type " + expression.NodeType); } private string GetSqlName(Expression expr) { ExpressionType nodeType = expr.NodeType; return nodeType switch { ExpressionType.GreaterThan => ">", ExpressionType.GreaterThanOrEqual => ">=", ExpressionType.LessThan => "<", ExpressionType.LessThanOrEqual => "<=", ExpressionType.And => "&", ExpressionType.AndAlso => "and", ExpressionType.Or => "|", ExpressionType.OrElse => "or", ExpressionType.Equal => "=", ExpressionType.NotEqual => "!=", _ => throw new NotSupportedException("Cannot get SQL for: " + nodeType), }; } public int Count() { return GenerateCommand("count(*)").ExecuteScalar<int>(); } public int Count(Expression<Func<T, bool>> predExpr) { return Where(predExpr).Count(); } public IEnumerator<T> GetEnumerator() { if (!_deferred) { return GenerateCommand("*").ExecuteQuery<T>().GetEnumerator(); } return GenerateCommand("*").ExecuteDeferredQuery<T>().GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } public List<T> ToList() { return GenerateCommand("*").ExecuteQuery<T>(); } public T[] ToArray() { return GenerateCommand("*").ExecuteQuery<T>().ToArray(); } public T First() { TableQuery<T> tableQuery = Take(1); return tableQuery.ToList().First(); } public T FirstOrDefault() { TableQuery<T> tableQuery = Take(1); return tableQuery.ToList().FirstOrDefault(); } public T First(Expression<Func<T, bool>> predExpr) { return Where(predExpr).First(); } public T FirstOrDefault(Expression<Func<T, bool>> predExpr) { return Where(predExpr).FirstOrDefault(); } } public static class SQLite3 { public enum Result { OK = 0, Error = 1, Internal = 2, Perm = 3, Abort = 4, Busy = 5, Locked = 6, NoMem = 7, ReadOnly = 8, Interrupt = 9, IOError = 10, Corrupt = 11, NotFound = 12, Full = 13, CannotOpen = 14, LockErr = 15, Empty = 16, SchemaChngd = 17, TooBig = 18, Constraint = 19, Mismatch = 20, Misuse = 21, NotImplementedLFS = 22, AccessDenied = 23, Format = 24, Range = 25, NonDBFile = 26, Notice = 27, Warning = 28, Row = 100, Done = 101 } public enum ExtendedResult { IOErrorRead = 266, IOErrorShortRead = 522, IOErrorWrite = 778, IOErrorFsync = 1034, IOErrorDirFSync = 1290, IOErrorTruncate = 1546, IOErrorFStat = 1802, IOErrorUnlock = 2058, IOErrorRdlock = 2314, IOErrorDelete = 2570, IOErrorBlocked = 2826, IOErrorNoMem = 3082, IOErrorAccess = 3338, IOErrorCheckReservedLock = 3594, IOErrorLock = 3850, IOErrorClose = 4106, IOErrorDirClose = 4362, IOErrorSHMOpen = 4618, IOErrorSHMSize = 4874, IOErrorSHMLock = 5130, IOErrorSHMMap = 5386, IOErrorSeek = 5642, IOErrorDeleteNoEnt = 5898, IOErrorMMap = 6154, LockedSharedcache = 262, BusyRecovery = 261, CannottOpenNoTempDir = 270, CannotOpenIsDir = 526, CannotOpenFullPath = 782, CorruptVTab = 267, ReadonlyRecovery = 264, ReadonlyCannotLock = 520, ReadonlyRollback = 776, AbortRollback = 516, ConstraintCheck = 275, ConstraintCommitHook = 531, ConstraintForeignKey = 787, ConstraintFunction = 1043, ConstraintNotNull = 1299, ConstraintPrimaryKey = 1555, ConstraintTrigger = 1811, ConstraintUnique = 2067, ConstraintVTab = 2323, NoticeRecoverWAL = 283, NoticeRecoverRollback = 539 } public enum ConfigOption { SingleThread = 1, MultiThread, Serialized } public enum ColType { Integer = 1, Float, Text, Blob, Null } private const string LibraryPath = "sqlite3"; [DllImport("sqlite3", CallingConvention = CallingConvention.Cdecl, EntryPoint = "sqlite3_threadsafe")] public static extern int Threadsafe(); [DllImport("sqlite3", CallingConvention = CallingConvention.Cdecl, EntryPoint = "sqlite3_open")] public static extern Result Open([MarshalAs(UnmanagedType.LPStr)] string filename, out IntPtr db); [DllImport("sqlite3", CallingConvention = CallingConvention.Cdecl, EntryPoint = "sqlite3_open_v2")] public static extern Result Open([MarshalAs(UnmanagedType.LPStr)] string filename, out IntPtr db, int flags, [MarshalAs(UnmanagedType.LPStr)] string zvfs); [DllImport("sqlite3", CallingConvention = CallingConvention.Cdecl, EntryPoint = "sqlite3_open_v2")] public static extern Result Open(byte[] filename, out IntPtr db, int flags, [MarshalAs(UnmanagedType.LPStr)] string zvfs); [DllImport("sqlite3", CallingConvention = CallingConvention.Cdecl, EntryPoint = "sqlite3_open16")] public static extern Result Open16([MarshalAs(UnmanagedType.LPWStr)] string filename, out IntPtr db); [DllImport("sqlite3", CallingConvention = CallingConvention.Cdecl, EntryPoint = "sqlite3_enable_load_extension")] public static extern Result EnableLoadExtension(IntPtr db, int onoff); [DllImport("sqlite3", CallingConvention = CallingConvention.Cdecl, EntryPoint = "sqlite3_close")] public static extern Result Close(IntPtr db); [DllImport("sqlite3", CallingConvention = CallingConvention.Cdecl, EntryPoint = "sqlite3_close_v2")] public static extern Result Close2(IntPtr db); [DllImport("sqlite3", CallingConvention = CallingConvention.Cdecl, EntryPoint = "sqlite3_initialize")] public static extern Result Initialize(); [DllImport("sqlite3", CallingConvention = CallingConvention.Cdecl, EntryPoint = "sqlite3_shutdown")] public static extern Result Shutdown(); [DllImport("sqlite3", CallingConvention = CallingConvention.Cdecl, EntryPoint = "sqlite3_config")] public static extern Result Config(ConfigOption option); [DllImport("sqlite3", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode, EntryPoint = "sqlite3_win32_set_directory")] public static extern int SetDirectory(uint directoryType, string directoryPath); [DllImport("sqlite3", CallingConvention = CallingConvention.Cdecl, EntryPoint = "sqlite3_busy_timeout")] public static extern Result BusyTimeou