diff --git a/Example/Assets/Plugins/Android/libs/arm64-v8a/libsqlite3.so b/Example/Assets/Plugins/Android/libs/arm64-v8a/libsqlite3.so new file mode 100644 index 0000000..1e2ce2d Binary files /dev/null and b/Example/Assets/Plugins/Android/libs/arm64-v8a/libsqlite3.so differ diff --git a/Example/Assets/Plugins/Android/libs/armeabi-v7a/libsqlite3.so b/Example/Assets/Plugins/Android/libs/armeabi-v7a/libsqlite3.so index d69ba56..2c6e2bf 100644 Binary files a/Example/Assets/Plugins/Android/libs/armeabi-v7a/libsqlite3.so and b/Example/Assets/Plugins/Android/libs/armeabi-v7a/libsqlite3.so differ diff --git a/Example/Assets/Plugins/Android/libs/x86/libsqlite3.so b/Example/Assets/Plugins/Android/libs/x86/libsqlite3.so index d4d1203..9d08844 100644 Binary files a/Example/Assets/Plugins/Android/libs/x86/libsqlite3.so and b/Example/Assets/Plugins/Android/libs/x86/libsqlite3.so differ diff --git a/Example/Assets/Plugins/WSA.meta b/Example/Assets/Plugins/WSA.meta new file mode 100644 index 0000000..474c5be --- /dev/null +++ b/Example/Assets/Plugins/WSA.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 3e1310ff5b122f347a943bad24333d4f +folderAsset: yes +timeCreated: 1507670483 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Example/Assets/Plugins/WSA/ARM.meta b/Example/Assets/Plugins/WSA/ARM.meta new file mode 100644 index 0000000..925ac45 --- /dev/null +++ b/Example/Assets/Plugins/WSA/ARM.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: d5a4eb770a3e85941bd12132039ab956 +folderAsset: yes +timeCreated: 1507670500 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Example/Assets/Plugins/WSA/ARM/sqlite3.dll b/Example/Assets/Plugins/WSA/ARM/sqlite3.dll new file mode 100644 index 0000000..53aea09 Binary files /dev/null and b/Example/Assets/Plugins/WSA/ARM/sqlite3.dll differ diff --git a/Example/Assets/Plugins/WSA/ARM/sqlite3.dll.meta b/Example/Assets/Plugins/WSA/ARM/sqlite3.dll.meta new file mode 100644 index 0000000..af757b5 --- /dev/null +++ b/Example/Assets/Plugins/WSA/ARM/sqlite3.dll.meta @@ -0,0 +1,126 @@ +fileFormatVersion: 2 +guid: 0268b9f97df6a83468be4b296cf6af28 +timeCreated: 1507670500 +licenseType: Pro +PluginImporter: + serializedVersion: 2 + iconMap: {} + executionOrder: {} + isPreloaded: 0 + isOverridable: 0 + platformData: + data: + first: + '': Any + second: + enabled: 0 + settings: + Exclude Editor: 1 + Exclude Linux: 1 + Exclude Linux64: 1 + Exclude LinuxUniversal: 1 + Exclude OSXIntel: 1 + Exclude OSXIntel64: 1 + Exclude OSXUniversal: 1 + Exclude Win: 1 + Exclude Win64: 1 + Exclude WindowsStoreApps: 0 + data: + first: + Any: + second: + enabled: 0 + settings: {} + data: + first: + Editor: Editor + second: + enabled: 0 + settings: + CPU: AnyCPU + DefaultValueInitialized: true + OS: AnyOS + data: + first: + Facebook: Win + second: + enabled: 0 + settings: + CPU: AnyCPU + data: + first: + Facebook: Win64 + second: + enabled: 0 + settings: + CPU: AnyCPU + data: + first: + Standalone: Linux + second: + enabled: 0 + settings: + CPU: x86 + data: + first: + Standalone: Linux64 + second: + enabled: 0 + settings: + CPU: x86_64 + data: + first: + Standalone: LinuxUniversal + second: + enabled: 0 + settings: + CPU: None + data: + first: + Standalone: OSXIntel + second: + enabled: 0 + settings: + CPU: AnyCPU + data: + first: + Standalone: OSXIntel64 + second: + enabled: 0 + settings: + CPU: AnyCPU + data: + first: + Standalone: OSXUniversal + second: + enabled: 0 + settings: + CPU: None + data: + first: + Standalone: Win + second: + enabled: 0 + settings: + CPU: AnyCPU + data: + first: + Standalone: Win64 + second: + enabled: 0 + settings: + CPU: AnyCPU + data: + first: + Windows Store Apps: WindowsStoreApps + second: + enabled: 1 + settings: + CPU: ARM + DontProcess: False + PlaceholderPath: + SDK: AnySDK + ScriptingBackend: AnyScriptingBackend + userData: + assetBundleName: + assetBundleVariant: diff --git a/Example/Assets/Plugins/WSA/x64.meta b/Example/Assets/Plugins/WSA/x64.meta new file mode 100644 index 0000000..83b0b9d --- /dev/null +++ b/Example/Assets/Plugins/WSA/x64.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: e14810dc75ddd4e4f9bb7b92989cb58b +folderAsset: yes +timeCreated: 1507670500 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Example/Assets/Plugins/WSA/x64/sqlite3.dll b/Example/Assets/Plugins/WSA/x64/sqlite3.dll new file mode 100644 index 0000000..63096c1 Binary files /dev/null and b/Example/Assets/Plugins/WSA/x64/sqlite3.dll differ diff --git a/Example/Assets/Plugins/WSA/x64/sqlite3.dll.meta b/Example/Assets/Plugins/WSA/x64/sqlite3.dll.meta new file mode 100644 index 0000000..51ab767 --- /dev/null +++ b/Example/Assets/Plugins/WSA/x64/sqlite3.dll.meta @@ -0,0 +1,126 @@ +fileFormatVersion: 2 +guid: 1e46d058e8bdac745970fdfabe7e67ce +timeCreated: 1507670500 +licenseType: Pro +PluginImporter: + serializedVersion: 2 + iconMap: {} + executionOrder: {} + isPreloaded: 0 + isOverridable: 0 + platformData: + data: + first: + '': Any + second: + enabled: 0 + settings: + Exclude Editor: 1 + Exclude Linux: 1 + Exclude Linux64: 1 + Exclude LinuxUniversal: 1 + Exclude OSXIntel: 1 + Exclude OSXIntel64: 1 + Exclude OSXUniversal: 1 + Exclude Win: 1 + Exclude Win64: 1 + Exclude WindowsStoreApps: 0 + data: + first: + Any: + second: + enabled: 0 + settings: {} + data: + first: + Editor: Editor + second: + enabled: 0 + settings: + CPU: AnyCPU + DefaultValueInitialized: true + OS: AnyOS + data: + first: + Facebook: Win + second: + enabled: 0 + settings: + CPU: AnyCPU + data: + first: + Facebook: Win64 + second: + enabled: 0 + settings: + CPU: AnyCPU + data: + first: + Standalone: Linux + second: + enabled: 0 + settings: + CPU: x86 + data: + first: + Standalone: Linux64 + second: + enabled: 0 + settings: + CPU: x86_64 + data: + first: + Standalone: LinuxUniversal + second: + enabled: 0 + settings: + CPU: None + data: + first: + Standalone: OSXIntel + second: + enabled: 0 + settings: + CPU: AnyCPU + data: + first: + Standalone: OSXIntel64 + second: + enabled: 0 + settings: + CPU: AnyCPU + data: + first: + Standalone: OSXUniversal + second: + enabled: 0 + settings: + CPU: None + data: + first: + Standalone: Win + second: + enabled: 0 + settings: + CPU: AnyCPU + data: + first: + Standalone: Win64 + second: + enabled: 0 + settings: + CPU: AnyCPU + data: + first: + Windows Store Apps: WindowsStoreApps + second: + enabled: 1 + settings: + CPU: X64 + DontProcess: False + PlaceholderPath: + SDK: AnySDK + ScriptingBackend: AnyScriptingBackend + userData: + assetBundleName: + assetBundleVariant: diff --git a/Example/Assets/Plugins/WSA/x86.meta b/Example/Assets/Plugins/WSA/x86.meta new file mode 100644 index 0000000..7587a2b --- /dev/null +++ b/Example/Assets/Plugins/WSA/x86.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 9f579691124bfd24a81afafa4c8a09c1 +folderAsset: yes +timeCreated: 1507670500 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Example/Assets/Plugins/WSA/x86/sqlite3.dll b/Example/Assets/Plugins/WSA/x86/sqlite3.dll new file mode 100644 index 0000000..6667321 Binary files /dev/null and b/Example/Assets/Plugins/WSA/x86/sqlite3.dll differ diff --git a/Example/Assets/Plugins/WSA/x86/sqlite3.dll.meta b/Example/Assets/Plugins/WSA/x86/sqlite3.dll.meta new file mode 100644 index 0000000..963467f --- /dev/null +++ b/Example/Assets/Plugins/WSA/x86/sqlite3.dll.meta @@ -0,0 +1,126 @@ +fileFormatVersion: 2 +guid: a0bb909f5b8441e48a1632aef3d543a4 +timeCreated: 1507670500 +licenseType: Pro +PluginImporter: + serializedVersion: 2 + iconMap: {} + executionOrder: {} + isPreloaded: 0 + isOverridable: 0 + platformData: + data: + first: + '': Any + second: + enabled: 0 + settings: + Exclude Editor: 1 + Exclude Linux: 1 + Exclude Linux64: 1 + Exclude LinuxUniversal: 1 + Exclude OSXIntel: 1 + Exclude OSXIntel64: 1 + Exclude OSXUniversal: 1 + Exclude Win: 1 + Exclude Win64: 1 + Exclude WindowsStoreApps: 0 + data: + first: + Any: + second: + enabled: 0 + settings: {} + data: + first: + Editor: Editor + second: + enabled: 0 + settings: + CPU: AnyCPU + DefaultValueInitialized: true + OS: AnyOS + data: + first: + Facebook: Win + second: + enabled: 0 + settings: + CPU: AnyCPU + data: + first: + Facebook: Win64 + second: + enabled: 0 + settings: + CPU: AnyCPU + data: + first: + Standalone: Linux + second: + enabled: 0 + settings: + CPU: x86 + data: + first: + Standalone: Linux64 + second: + enabled: 0 + settings: + CPU: x86_64 + data: + first: + Standalone: LinuxUniversal + second: + enabled: 0 + settings: + CPU: None + data: + first: + Standalone: OSXIntel + second: + enabled: 0 + settings: + CPU: AnyCPU + data: + first: + Standalone: OSXIntel64 + second: + enabled: 0 + settings: + CPU: AnyCPU + data: + first: + Standalone: OSXUniversal + second: + enabled: 0 + settings: + CPU: None + data: + first: + Standalone: Win + second: + enabled: 0 + settings: + CPU: AnyCPU + data: + first: + Standalone: Win64 + second: + enabled: 0 + settings: + CPU: AnyCPU + data: + first: + Windows Store Apps: WindowsStoreApps + second: + enabled: 1 + settings: + CPU: X86 + DontProcess: False + PlaceholderPath: + SDK: AnySDK + ScriptingBackend: AnyScriptingBackend + userData: + assetBundleName: + assetBundleVariant: diff --git a/Example/Assets/Plugins/x64/sqlite3.dll b/Example/Assets/Plugins/x64/sqlite3.dll old mode 100644 new mode 100755 diff --git a/Example/Assets/Plugins/x86/sqlite3.dll b/Example/Assets/Plugins/x86/sqlite3.dll old mode 100644 new mode 100755 index 4cd5a11..d48a7ef Binary files a/Example/Assets/Plugins/x86/sqlite3.dll and b/Example/Assets/Plugins/x86/sqlite3.dll differ diff --git a/Example/Assets/Scripts/DataService.cs b/Example/Assets/Scripts/DataService.cs index 872f598..558ada1 100644 --- a/Example/Assets/Scripts/DataService.cs +++ b/Example/Assets/Scripts/DataService.cs @@ -42,6 +42,11 @@ public DataService(string DatabaseName){ var loadDb = Application.dataPath + "/StreamingAssets/" + DatabaseName; // this is the path to your StreamingAssets in iOS // then save to Application.persistentDataPath File.Copy(loadDb, filepath); + +#elif UNITY_STANDALONE_OSX + var loadDb = Application.dataPath + "/Resources/Data/StreamingAssets/" + DatabaseName; // this is the path to your StreamingAssets in iOS + // then save to Application.persistentDataPath + File.Copy(loadDb, filepath); #else var loadDb = Application.dataPath + "/StreamingAssets/" + DatabaseName; // this is the path to your StreamingAssets in iOS // then save to Application.persistentDataPath diff --git a/Example/Assets/Scripts/SQLite.cs b/Example/Assets/Scripts/SQLite.cs index 0ddd103..18ce6c3 100644 --- a/Example/Assets/Scripts/SQLite.cs +++ b/Example/Assets/Scripts/SQLite.cs @@ -77,7 +77,7 @@ protected NotNullConstraintViolationException (SQLite3.Result r, string message, { if (mapping != null && obj != null) { this.Columns = from c in mapping.Columns - where c.IsNullable == false && c.GetValue (obj) == null + where c.IsNullable == false && c.GetValue(obj) == null select c; } } @@ -109,16 +109,16 @@ public enum SQLiteOpenFlags { ProtectionNone = 0x00400000 } - [Flags] - public enum CreateFlags - { - None = 0, - ImplicitPK = 1, // create a primary key for field called 'Id' (Orm.ImplicitPkName) - ImplicitIndex = 2, // create an index for fields ending in 'Id' (Orm.ImplicitIndexSuffix) - AllImplicit = 3, // do both above + [Flags] + public enum CreateFlags + { + None = 0, + ImplicitPK = 1, // create a primary key for field called 'Id' (Orm.ImplicitPkName) + ImplicitIndex = 2, // create an index for fields ending in 'Id' (Orm.ImplicitIndexSuffix) + AllImplicit = 3, // do both above - AutoIncPK = 4 // force PK field to be auto inc - } + AutoIncPK = 4 // force PK field to be auto inc + } /// /// Represents an open connection to a SQLite database. @@ -130,7 +130,7 @@ public partial class SQLiteConnection : IDisposable private Dictionary _mappings = null; private Dictionary _tables = null; private System.Diagnostics.Stopwatch _sw; - private long _elapsedMilliseconds = 0; + private TimeSpan _elapsed = default(TimeSpan); private int _transactionDepth = 0; private Random _rand = new Random (); @@ -140,9 +140,47 @@ public partial class SQLiteConnection : IDisposable public string DatabasePath { get; private set; } - public bool TimeExecution { get; set; } + // Dictionary of synchronization objects. + // + // To prevent database disruption, a database file must be accessed *synchronously*. + // For the purpose we create synchronous objects for each database file and store in the + // static dictionary to share it among all connections. + // The key of the dictionary is database file path and its value is an object to be used + // by lock() statement. + // + // Use case: + // - database file lock is done implicitly and automatically. + // - To prepend deadlock, application may lock a database file explicity by either way: + // - RunInTransaction(Action) locks the database during the transaction (for insert/update) + // - RunInDatabaseLock(Action) similarly locks the database but no transaction (for query) + private static Dictionary syncObjects = new Dictionary(); + + #region debug tracing public bool Trace { get; set; } + public bool TimeExecution { get; set; } + + public delegate void TraceHandler (string message); + public event TraceHandler TraceEvent; + + internal void InvokeTrace (string message) + { + if (TraceEvent != null) { + TraceEvent(message); + } + } + + public delegate void TimeExecutionHandler (TimeSpan executionTime, TimeSpan totalExecutionTime); + public event TimeExecutionHandler TimeExecutionEvent; + + internal void InvokeTimeExecution(TimeSpan executionTime, TimeSpan totalExecutionTime) + { + if (TimeExecutionEvent != null) { + TimeExecutionEvent(executionTime, totalExecutionTime); + } + } + + #endregion public bool StoreDateTimeAsTicks { get; private set; } @@ -181,6 +219,7 @@ public SQLiteConnection (string databasePath, SQLiteOpenFlags openFlags, bool st throw new ArgumentException ("Must be specified", "databasePath"); DatabasePath = databasePath; + mayCreateSyncObject(databasePath); #if NETFX_CORE SQLite3.SetDirectory(/*temp directory type*/2, Windows.Storage.ApplicationData.Current.TemporaryFolder.Path); @@ -189,7 +228,7 @@ public SQLiteConnection (string databasePath, SQLiteOpenFlags openFlags, bool st Sqlite3DatabaseHandle handle; #if SILVERLIGHT || USE_CSHARP_SQLITE - var r = SQLite3.Open (databasePath, out handle, (int)openFlags, IntPtr.Zero); + var r = SQLite3.Open (databasePath, out handle, (int)openFlags, IntPtr.Zero); #else // open using the byte[] // in the case where the path may include Unicode @@ -217,14 +256,27 @@ static SQLiteConnection () } } - public void EnableLoadExtension(int onoff) - { - SQLite3.Result r = SQLite3.EnableLoadExtension(Handle, onoff); + void mayCreateSyncObject(string databasePath) + { + if (!syncObjects.ContainsKey(databasePath)) { + syncObjects[databasePath] = new object(); + } + } + + /// + /// Gets the synchronous object, to be lock the database file for updating. + /// + /// The sync object. + public object SyncObject { get { return syncObjects[DatabasePath];} } + + public void EnableLoadExtension(int onoff) + { + SQLite3.Result r = SQLite3.EnableLoadExtension(Handle, onoff); if (r != SQLite3.Result.OK) { string msg = SQLite3.GetErrmsg (Handle); throw SQLiteException.New (r, msg); } - } + } static byte[] GetNullTerminatedUtf8 (string s) { @@ -238,7 +290,9 @@ static byte[] GetNullTerminatedUtf8 (string s) /// Used to list some code that we want the MonoTouch linker /// to see, but that we never want to actually execute. /// + #pragma warning disable 649 static bool _preserveDuringLinkMagic; + #pragma warning restore 649 /// /// Sets a busy handler to sleep the specified amount of time when a table is locked. @@ -270,14 +324,14 @@ public IEnumerable TableMappings { /// /// 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 TableMapping GetMapping(Type type, CreateFlags createFlags = CreateFlags.None) + public TableMapping GetMapping(Type type, CreateFlags createFlags = CreateFlags.None) { if (_mappings == null) { _mappings = new Dictionary (); @@ -349,11 +403,11 @@ public int CreateTable(CreateFlags createFlags = CreateFlags.None) /// 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. + /// Optional flags allowing implicit PK and indexes based on naming conventions. /// /// The number of entries added to the database schema. /// - public int CreateTable(Type ty, CreateFlags createFlags = CreateFlags.None) + public int CreateTable(Type ty, CreateFlags createFlags = CreateFlags.None) { if (_tables == null) { _tables = new Dictionary (); @@ -404,92 +458,102 @@ public int CreateTable(Type ty, CreateFlags createFlags = CreateFlags.None) foreach (var indexName in indexes.Keys) { var index = indexes[indexName]; - var columns = index.Columns.OrderBy(i => i.Order).Select(i => i.ColumnName).ToArray(); - count += CreateIndex(indexName, index.TableName, columns, index.Unique); + string[] columnNames = new string[index.Columns.Count]; + if (index.Columns.Count == 1) { + columnNames[0] = index.Columns[0].ColumnName; + } else { + index.Columns.Sort((lhs, rhs) => { + return lhs.Order - rhs.Order; + }); + for (int i = 0, end = index.Columns.Count; i < end; ++i) { + columnNames[i] = index.Columns[i].ColumnName; + } + } + count += CreateIndex(indexName, index.TableName, columnNames, index.Unique); } return count; } - /// - /// 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 - public int CreateIndex(string indexName, string tableName, string[] columnNames, bool unique = false) - { - const string sqlFormat = "create {2} index if not exists \"{3}\" on \"{0}\"(\"{1}\")"; - var sql = String.Format(sqlFormat, tableName, string.Join ("\", \"", columnNames), unique ? "unique" : "", indexName); - return Execute(sql); - } - - /// - /// 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 - public int CreateIndex(string indexName, string tableName, string columnName, bool unique = false) - { - return CreateIndex(indexName, tableName, new string[] { columnName }, unique); - } - - /// - /// 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 - public int CreateIndex(string tableName, string columnName, bool unique = false) - { - return CreateIndex(tableName + "_" + columnName, 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 - public int CreateIndex(string tableName, string[] columnNames, bool unique = false) - { - return CreateIndex(tableName + "_" + string.Join ("_", columnNames), tableName, columnNames, unique); - } - - /// - /// Creates an index for the specified object property. - /// e.g. CreateIndex(c => c.Name); - /// - /// Type to reflect to a database table. - /// Property to index - /// Whether the index should be unique - public void CreateIndex(Expression> property, bool unique = false) - { - MemberExpression mx; - if (property.Body.NodeType == ExpressionType.Convert) - { - mx = ((UnaryExpression)property.Body).Operand as MemberExpression; - } - else - { - mx= (property.Body as MemberExpression); - } - var propertyInfo = mx.Member as PropertyInfo; - if (propertyInfo == null) - { - throw new ArgumentException("The lambda expression 'property' should point to a valid Property"); - } - - var propName = propertyInfo.Name; - - var map = GetMapping(); - var colName = map.FindColumnWithPropertyName(propName).Name; - - CreateIndex(map.TableName, colName, 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 + public int CreateIndex(string indexName, string tableName, string[] columnNames, bool unique = false) + { + const string sqlFormat = "create {2} index if not exists \"{3}\" on \"{0}\"(\"{1}\")"; + var sql = String.Format(sqlFormat, tableName, string.Join ("\", \"", columnNames), unique ? "unique" : "", indexName); + return Execute(sql); + } + + /// + /// 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 + public int CreateIndex(string indexName, string tableName, string columnName, bool unique = false) + { + return CreateIndex(indexName, tableName, new string[] { columnName }, unique); + } + + /// + /// 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 + public int CreateIndex(string tableName, string columnName, bool unique = false) + { + return CreateIndex(tableName + "_" + columnName, 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 + public int CreateIndex(string tableName, string[] columnNames, bool unique = false) + { + return CreateIndex(tableName + "_" + string.Join ("_", columnNames), tableName, columnNames, unique); + } + + /// + /// Creates an index for the specified object property. + /// e.g. CreateIndex(c => c.Name); + /// + /// Type to reflect to a database table. + /// Property to index + /// Whether the index should be unique + public void CreateIndex(Expression> property, bool unique = false) + { + MemberExpression mx; + if (property.Body.NodeType == ExpressionType.Convert) + { + mx = ((UnaryExpression)property.Body).Operand as MemberExpression; + } + else + { + mx= (property.Body as MemberExpression); + } + var propertyInfo = mx.Member as PropertyInfo; + if (propertyInfo == null) + { + throw new ArgumentException("The lambda expression 'property' should point to a valid Property"); + } + + var propName = propertyInfo.Name; + + var map = GetMapping(); + var colName = map.FindColumnWithPropertyName(propName).Name; + + CreateIndex(map.TableName, colName, unique); + } public class ColumnInfo { @@ -611,8 +675,8 @@ public int Execute (string query, params object[] args) if (TimeExecution) { _sw.Stop (); - _elapsedMilliseconds += _sw.ElapsedMilliseconds; - Debug.WriteLine (string.Format ("Finished in {0} ms ({1:0.0} s total)", _sw.ElapsedMilliseconds, _elapsedMilliseconds / 1000.0)); + _elapsed += _sw.Elapsed; + this.InvokeTimeExecution (_sw.Elapsed, _elapsed); } return r; @@ -634,8 +698,8 @@ public T ExecuteScalar (string query, params object[] args) if (TimeExecution) { _sw.Stop (); - _elapsedMilliseconds += _sw.ElapsedMilliseconds; - Debug.WriteLine (string.Format ("Finished in {0} ms ({1:0.0} s total)", _sw.ElapsedMilliseconds, _elapsedMilliseconds / 1000.0)); + _elapsed += _sw.Elapsed; + this.InvokeTimeExecution (_sw.Elapsed, _elapsed); } return r; @@ -769,21 +833,21 @@ public IEnumerable DeferredQuery(TableMapping map, string query, params return Query (map.GetByPrimaryKeySql, pk).First (); } - /// - /// 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 T Get (Expression> predicate) where T : new() - { - return Table ().Where (predicate).First (); - } + /// + /// 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 T Get (Expression> predicate) where T : new() + { + return Table ().Where (predicate).First (); + } /// /// Attempts to retrieve an object with the given primary key from the table @@ -824,20 +888,20 @@ public object Find (object pk, TableMapping 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 T Find (Expression> predicate) where T : new() - { - return Table ().Where (predicate).FirstOrDefault (); - } + /// 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 T Find (Expression> predicate) where T : new() + { + return Table ().Where (predicate).FirstOrDefault (); + } /// /// Whether has been called and the database is waiting for a . @@ -967,7 +1031,7 @@ void RollbackTo (string savepoint, bool noThrow) } catch (SQLiteException) { if (!noThrow) throw; - + } // No need to rollback if there are no transactions open. } @@ -995,13 +1059,13 @@ void DoSavePointExecute (string savepoint, string cmd) // TODO: Mild race here, but inescapable without locking almost everywhere. if (0 <= depth && depth < _transactionDepth) { #if NETFX_CORE - Volatile.Write (ref _transactionDepth, depth); + Volatile.Write (ref _transactionDepth, depth); #elif SILVERLIGHT _transactionDepth = depth; #else - Thread.VolatileWrite (ref _transactionDepth, depth); + Thread.VolatileWrite (ref _transactionDepth, depth); #endif - Execute (cmd + savepoint); + Execute (cmd + savepoint); return; } } @@ -1034,15 +1098,30 @@ public void Commit () public void RunInTransaction (Action action) { try { - var savePoint = SaveTransactionPoint (); - action (); - Release (savePoint); + lock (syncObjects[DatabasePath]) { + var savePoint = SaveTransactionPoint (); + action (); + Release (savePoint); + } } catch (Exception) { Rollback (); throw; } } + /// + /// Executes while blocking other threads to access the same database. + /// + /// + /// The to perform within a lock. + /// + public void RunInDatabaseLock (Action action) + { + lock (syncObjects[DatabasePath]) { + action (); + } + } + /// /// Inserts all specified objects. /// @@ -1208,61 +1287,63 @@ public int Insert (object obj, string extra) return Insert (obj, extra, obj.GetType ()); } - /// - /// Inserts the given object and retrieves its - /// auto incremented primary key if it has one. - /// - /// - /// 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 int Insert (object obj, string extra, Type objType) + /// + /// Inserts the given object and retrieves its + /// auto incremented primary key if it has one. + /// + /// + /// 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 int Insert (object obj, string extra, Type objType) { if (obj == null || objType == null) { return 0; } - + var map = GetMapping (objType); #if NETFX_CORE - if (map.PK != null && map.PK.IsAutoGuid) - { - // no GetProperty so search our way up the inheritance chain till we find it - PropertyInfo prop; - while (objType != null) - { - var info = objType.GetTypeInfo(); - prop = info.GetDeclaredProperty(map.PK.PropertyName); - if (prop != null) - { - if (prop.GetValue(obj, null).Equals(Guid.Empty)) - { - prop.SetValue(obj, Guid.NewGuid(), null); - } - break; - } - - objType = info.BaseType; - } - } + if (map.PK != null && map.PK.IsAutoGuid) + { + // no GetProperty so search our way up the inheritance chain till we find it + PropertyInfo prop; + while (objType != null) + { + var info = objType.GetTypeInfo(); + prop = info.GetDeclaredProperty(map.PK.PropertyName); + if (prop != null) + { + if (prop.GetValue(obj, null).Equals(Guid.Empty)) + { + prop.SetValue(obj, Guid.NewGuid(), null); + } + break; + } + + objType = info.BaseType; + } + } #else - if (map.PK != null && map.PK.IsAutoGuid) { - var prop = objType.GetProperty(map.PK.PropertyName); - if (prop != null) { - if (prop.GetValue(obj, null).Equals(Guid.Empty)) { - prop.SetValue(obj, Guid.NewGuid(), null); - } - } - } + if (map.PK != null && map.PK.IsAutoGuid) { + var prop = objType.GetProperty(map.PK.PropertyName); + if (prop != null) { + //if (prop.GetValue(obj, null).Equals(Guid.Empty)) { + if (prop.GetGetMethod().Invoke(obj, null).Equals(Guid.Empty)) + { + prop.SetValue(obj, Guid.NewGuid(), null); + } + } + } #endif @@ -1271,7 +1352,7 @@ public int Insert (object obj, string extra, Type objType) var cols = replacing ? map.InsertOrReplaceColumns : map.InsertColumns; var vals = new object[cols.Length]; for (var i = 0; i < vals.Length; i++) { - vals [i] = cols [i].GetValue (obj); + vals [i] = cols [i].GetValue(obj); } var insertCmd = map.GetInsertCommand (this, extra); @@ -1288,8 +1369,8 @@ public int Insert (object obj, string extra, Type objType) throw; } - if (map.HasAutoIncPK) - { + if (map.HasAutoIncPK) + { var id = SQLite3.LastInsertRowid (Handle); map.SetAutoIncPK (obj, id); } @@ -1516,7 +1597,7 @@ public SQLiteConnectionString (string databasePath, bool storeDateTimeAsTicks) } } - [AttributeUsage (AttributeTargets.Class)] + [AttributeUsage (AttributeTargets.Class)] public class TableAttribute : Attribute { public string Name { get; set; } @@ -1578,6 +1659,14 @@ public override bool Unique { get { return true; } set { /* throw? */ } } + + public UniqueAttribute() : base() + { + } + + public UniqueAttribute(string name, int order) : base(name, order) + { + } } [AttributeUsage (AttributeTargets.Property)] @@ -1623,13 +1712,13 @@ public class TableMapping Column[] _insertColumns; Column[] _insertOrReplaceColumns; - public TableMapping(Type type, CreateFlags createFlags = CreateFlags.None) + public TableMapping(Type type, CreateFlags createFlags = CreateFlags.None) { MappedType = type; #if NETFX_CORE var tableAttr = (TableAttribute)System.Reflection.CustomAttributeExtensions - .GetCustomAttribute(type.GetTypeInfo(), typeof(TableAttribute), true); + .GetCustomAttribute(type.GetTypeInfo(), typeof(TableAttribute), true); #else var tableAttr = (TableAttribute)type.GetCustomAttributes (typeof (TableAttribute), true).FirstOrDefault (); #endif @@ -1734,26 +1823,26 @@ public PreparedSqlLiteInsertCommand GetInsertCommand(SQLiteConnection conn, stri PreparedSqlLiteInsertCommand CreateInsertCommand(SQLiteConnection conn, string extra) { var cols = InsertColumns; - string insertSql; - if (!cols.Any() && Columns.Count() == 1 && Columns[0].IsAutoInc) - { - insertSql = string.Format("insert {1} into \"{0}\" default values", TableName, extra); - } - else - { + string insertSql; + if (!cols.Any() && Columns.Count() == 1 && Columns[0].IsAutoInc) + { + insertSql = string.Format("insert {1} into \"{0}\" default values", TableName, extra); + } + else + { var replacing = string.Compare (extra, "OR REPLACE", StringComparison.OrdinalIgnoreCase) == 0; if (replacing) { cols = InsertOrReplaceColumns; } - insertSql = string.Format("insert {3} into \"{0}\"({1}) values ({2})", TableName, - string.Join(",", (from c in cols - select "\"" + c.Name + "\"").ToArray()), - string.Join(",", (from c in cols - select "?").ToArray()), extra); - - } + insertSql = string.Format("insert {3} into \"{0}\"({1}) values ({2})", TableName, + string.Join(",", (from c in cols + select "\"" + c.Name + "\"").ToArray()), + string.Join(",", (from c in cols + select "?").ToArray()), extra); + + } var insertCommand = new PreparedSqlLiteInsertCommand(conn); insertCommand.CommandText = insertSql; @@ -1780,8 +1869,8 @@ public class Column public string Collation { get; private set; } - public bool IsAutoInc { get; private set; } - public bool IsAutoGuid { get; private set; } + public bool IsAutoInc { get; private set; } + public bool IsAutoGuid { get; private set; } public bool IsPK { get; private set; } @@ -1791,36 +1880,36 @@ public class Column public int? MaxStringLength { get; private set; } - public Column(PropertyInfo prop, CreateFlags createFlags = CreateFlags.None) - { - var colAttr = (ColumnAttribute)prop.GetCustomAttributes(typeof(ColumnAttribute), true).FirstOrDefault(); + public Column(PropertyInfo prop, CreateFlags createFlags = CreateFlags.None) + { + var colAttr = (ColumnAttribute)prop.GetCustomAttributes(typeof(ColumnAttribute), true).FirstOrDefault(); - _prop = prop; - Name = colAttr == null ? prop.Name : colAttr.Name; - //If this type is Nullable then Nullable.GetUnderlyingType returns the T, otherwise it returns null, so get the actual type instead - ColumnType = Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType; - Collation = Orm.Collation(prop); + _prop = prop; + Name = colAttr == null ? prop.Name : colAttr.Name; + //If this type is Nullable then Nullable.GetUnderlyingType returns the T, otherwise it returns null, so get the actual type instead + ColumnType = Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType; + Collation = Orm.Collation(prop); - IsPK = Orm.IsPK(prop) || + IsPK = Orm.IsPK(prop) || (((createFlags & CreateFlags.ImplicitPK) == CreateFlags.ImplicitPK) && - string.Compare (prop.Name, Orm.ImplicitPkName, StringComparison.OrdinalIgnoreCase) == 0); - - var isAuto = Orm.IsAutoInc(prop) || (IsPK && ((createFlags & CreateFlags.AutoIncPK) == CreateFlags.AutoIncPK)); - IsAutoGuid = isAuto && ColumnType == typeof(Guid); - IsAutoInc = isAuto && !IsAutoGuid; - - Indices = Orm.GetIndices(prop); - if (!Indices.Any() - && !IsPK - && ((createFlags & CreateFlags.ImplicitIndex) == CreateFlags.ImplicitIndex) - && Name.EndsWith (Orm.ImplicitIndexSuffix, StringComparison.OrdinalIgnoreCase) - ) - { - Indices = new IndexedAttribute[] { new IndexedAttribute() }; - } - IsNullable = !(IsPK || Orm.IsMarkedNotNull(prop)); - MaxStringLength = Orm.MaxStringLength(prop); - } + string.Compare (prop.Name, Orm.ImplicitPkName, StringComparison.OrdinalIgnoreCase) == 0); + + var isAuto = Orm.IsAutoInc(prop) || (IsPK && ((createFlags & CreateFlags.AutoIncPK) == CreateFlags.AutoIncPK)); + IsAutoGuid = isAuto && ColumnType == typeof(Guid); + IsAutoInc = isAuto && !IsAutoGuid; + + Indices = Orm.GetIndices(prop); + if (!Indices.Any() + && !IsPK + && ((createFlags & CreateFlags.ImplicitIndex) == CreateFlags.ImplicitIndex) + && Name.EndsWith (Orm.ImplicitIndexSuffix, StringComparison.OrdinalIgnoreCase) + ) + { + Indices = new IndexedAttribute[] { new IndexedAttribute() }; + } + IsNullable = !(IsPK || Orm.IsMarkedNotNull(prop)); + MaxStringLength = Orm.MaxStringLength(prop); + } public void SetValue (object obj, object val) { @@ -1829,16 +1918,16 @@ public void SetValue (object obj, object val) public object GetValue (object obj) { - return _prop.GetValue (obj, null); + return _prop.GetGetMethod().Invoke(obj, null); } } } public static class Orm { - public const int DefaultMaxStringLength = 140; - public const string ImplicitPkName = "Id"; - public const string ImplicitIndexSuffix = "Id"; + public const int DefaultMaxStringLength = 140; + public const string ImplicitPkName = "Id"; + public const string ImplicitIndexSuffix = "Id"; public static string SqlDecl (TableMapping.Column p, bool storeDateTimeAsTicks) { @@ -1877,7 +1966,7 @@ public static string SqlType (TableMapping.Column p, bool storeDateTimeAsTicks) return "varchar"; } else if (clrType == typeof(TimeSpan)) { - return "bigint"; + return "bigint"; } else if (clrType == typeof(DateTime)) { return storeDateTimeAsTicks ? "bigint" : "datetime"; } else if (clrType == typeof(DateTimeOffset)) { @@ -1890,9 +1979,9 @@ public static string SqlType (TableMapping.Column p, bool storeDateTimeAsTicks) return "integer"; } else if (clrType == typeof(byte[])) { return "blob"; - } else if (clrType == typeof(Guid)) { - return "varchar(36)"; - } else { + } else if (clrType == typeof(Guid)) { + return "varchar(36)"; + } else { throw new NotSupportedException ("Don't know about " + clrType); } } @@ -1915,7 +2004,7 @@ public static string Collation (MemberInfo p) return ((CollationAttribute)attrs [0]).Value; #else if (attrs.Count() > 0) { - return ((CollationAttribute)attrs.First()).Value; + return ((CollationAttribute)attrs.First()).Value; #endif } else { return string.Empty; @@ -1980,13 +2069,15 @@ internal SQLiteCommand (SQLiteConnection conn) public int ExecuteNonQuery () { if (_conn.Trace) { - Debug.WriteLine ("Executing: " + this); + _conn.InvokeTrace ("Executing: " + this); } var r = SQLite3.Result.OK; - var stmt = Prepare (); - r = SQLite3.Step (stmt); - Finalize (stmt); + lock (_conn.SyncObject) { + var stmt = Prepare (); + r = SQLite3.Step (stmt); + Finalize(stmt); + } if (r == SQLite3.Result.Done) { int rowsAffected = SQLite3.Changes (_conn.Handle); return rowsAffected; @@ -2038,66 +2129,64 @@ protected virtual void OnInstanceCreated (object obj) public IEnumerable ExecuteDeferredQuery (TableMapping map) { if (_conn.Trace) { - Debug.WriteLine ("Executing Query: " + this); + _conn.InvokeTrace ("Executing Query: " + this); } - var stmt = Prepare (); - try - { - var cols = new TableMapping.Column[SQLite3.ColumnCount (stmt)]; + lock (_conn.SyncObject) { + var stmt = Prepare (); + try { + var cols = new TableMapping.Column[SQLite3.ColumnCount (stmt)]; - for (int i = 0; i < cols.Length; i++) { - var name = SQLite3.ColumnName16 (stmt, i); - cols [i] = map.FindColumn (name); - } - - while (SQLite3.Step (stmt) == SQLite3.Result.Row) { - var obj = Activator.CreateInstance(map.MappedType); for (int i = 0; i < cols.Length; i++) { - if (cols [i] == null) - continue; - var colType = SQLite3.ColumnType (stmt, i); - var val = ReadCol (stmt, i, colType, cols [i].ColumnType); - cols [i].SetValue (obj, val); - } - OnInstanceCreated (obj); - yield return (T)obj; + var name = SQLite3.ColumnName16 (stmt, i); + cols [i] = map.FindColumn (name); + } + + while (SQLite3.Step (stmt) == SQLite3.Result.Row) { + var obj = Activator.CreateInstance(map.MappedType); + for (int i = 0; i < cols.Length; i++) { + if (cols [i] == null) + continue; + var colType = SQLite3.ColumnType (stmt, i); + var val = ReadCol (stmt, i, colType, cols [i].ColumnType); + cols [i].SetValue (obj, val); + } + OnInstanceCreated (obj); + yield return (T)obj; + } + } finally { + SQLite3.Finalize(stmt); } } - finally - { - SQLite3.Finalize(stmt); - } } public T ExecuteScalar () { if (_conn.Trace) { - Debug.WriteLine ("Executing Query: " + this); + _conn.InvokeTrace ("Executing Query: " + this); } T val = default(T); - var stmt = Prepare (); - - try - { - var r = SQLite3.Step (stmt); - if (r == SQLite3.Result.Row) { - var colType = SQLite3.ColumnType (stmt, 0); - val = (T)ReadCol (stmt, 0, colType, typeof(T)); - } - else if (r == SQLite3.Result.Done) { - } - else - { - throw SQLiteException.New (r, SQLite3.GetErrmsg (_conn.Handle)); - } - } - finally - { - Finalize (stmt); - } + lock (_conn.SyncObject) { + var stmt = Prepare(); + + try { + var r = SQLite3.Step (stmt); + if (r == SQLite3.Result.Row) { + var colType = SQLite3.ColumnType (stmt, 0); + val = (T)ReadCol (stmt, 0, colType, typeof(T)); + } + else if (r == SQLite3.Result.Done) { + } + else + { + throw SQLiteException.New (r, SQLite3.GetErrmsg (_conn.Handle)); + } + } finally { + Finalize (stmt); + } + } return val; } @@ -2189,13 +2278,13 @@ internal static void BindParameter (Sqlite3Statement stmt, int index, object val } else if (value.GetType().GetTypeInfo().IsEnum) { #endif SQLite3.BindInt (stmt, index, Convert.ToInt32 (value)); - } else if (value is byte[]){ - SQLite3.BindBlob(stmt, index, (byte[]) value, ((byte[]) value).Length, NegativePointer); - } else if (value is Guid) { - SQLite3.BindText(stmt, index, ((Guid)value).ToString(), 72, NegativePointer); - } else { - throw new NotSupportedException("Cannot store type: " + value.GetType()); - } + } else if (value is byte[]){ + SQLite3.BindBlob(stmt, index, (byte[]) value, ((byte[]) value).Length, NegativePointer); + } else if (value is Guid) { + SQLite3.BindText(stmt, index, ((Guid)value).ToString(), 72, NegativePointer); + } else { + throw new NotSupportedException("Cannot store type: " + value.GetType()); + } } } @@ -2258,9 +2347,9 @@ object ReadCol (Sqlite3Statement stmt, int index, SQLite3.ColType type, Type clr } else if (clrType == typeof(byte[])) { return SQLite3.ColumnByteArray (stmt, index); } else if (clrType == typeof(Guid)) { - var text = SQLite3.ColumnString(stmt, index); - return new Guid(text); - } else{ + var text = SQLite3.ColumnString(stmt, index); + return new Guid(text); + } else{ throw new NotSupportedException ("Don't know how to read " + clrType); } } @@ -2289,7 +2378,7 @@ internal PreparedSqlLiteInsertCommand (SQLiteConnection conn) public int ExecuteNonQuery (object[] source) { if (Connection.Trace) { - Debug.WriteLine ("Executing: " + CommandText); + Connection.InvokeTrace ("Executing: " + CommandText); } var r = SQLite3.Result.OK; @@ -2651,6 +2740,15 @@ private CompileResult CompileExpr (Expression expr, List queryArgs) CommandText = valr.CommandText, Value = valr.Value != null ? ConvertTo (valr.Value, ty) : null }; + } else if (expr.NodeType == ExpressionType.Not) { + var u = (UnaryExpression)expr; + var ty = u.Type; + var valr = CompileExpr (u.Operand, queryArgs); + + return new CompileResult { + CommandText = "NOT " + valr.CommandText, + Value = valr.Value != null ? valr.Value : null + }; } else if (expr.NodeType == ExpressionType.MemberAccess) { var mem = (MemberExpression)expr; @@ -2685,7 +2783,8 @@ private CompileResult CompileExpr (Expression expr, List queryArgs) if (mem.Member is PropertyInfo) { #endif var m = (PropertyInfo)mem.Member; - val = m.GetValue (obj, null); + //val = m.GetValue (obj, null); + val = m.GetGetMethod().Invoke(obj, null); #if !NETFX_CORE } else if (mem.Member.MemberType == MemberTypes.Field) { #else @@ -2823,7 +2922,7 @@ public T FirstOrDefault () var query = Take (1); return query.ToList().FirstOrDefault (); } - } + } public static class SQLite3 { @@ -2910,7 +3009,7 @@ public enum ExtendedResult : int NoticeRecoverWAL = (Result.Notice | (1 << 8)), NoticeRecoverRollback = (Result.Notice | (2 << 8)) } - + public enum ConfigOption : int { @@ -2968,10 +3067,10 @@ public static IntPtr Prepare2 (IntPtr db, string query) { IntPtr stmt; #if NETFX_CORE - byte[] queryBytes = System.Text.UTF8Encoding.UTF8.GetBytes (query); - var r = Prepare2 (db, queryBytes, queryBytes.Length, out stmt, IntPtr.Zero); + byte[] queryBytes = System.Text.UTF8Encoding.UTF8.GetBytes (query); + var r = Prepare2 (db, queryBytes, queryBytes.Length, out stmt, IntPtr.Zero); #else - var r = Prepare2 (db, query, System.Text.UTF8Encoding.UTF8.GetByteCount (query), out stmt, IntPtr.Zero); + var r = Prepare2 (db, query, System.Text.UTF8Encoding.UTF8.GetByteCount (query), out stmt, IntPtr.Zero); #endif if (r != Result.OK) { throw SQLiteException.New (r, GetErrmsg (db)); diff --git a/README.md b/README.md index 259de2a..56d1e38 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ # What's this? When I started with Unity3d development I needed to use SQLite in my project and it was very hard to me to find a place with simple instructions on how to make it work. All I got were links to paid solutions on the Unity3d's Assets Store and a lot of different and complicated tutorials. -At the end, I decided that there should be a simpler way and I created **SQLite4Unity3d**, a plugin that helps you to use SQLite in your Unity3d projects in a clear and easy way and works in **iOS, Android and Windows** projects. +At the end, I decided that there should be a simpler way and I created **SQLite4Unity3d**, a plugin that helps you to use SQLite in your Unity3d projects in a clear and easy way and works in **iOS, Mac, Android and Windows** projects. It uses the great [sqlite-net](https://github.com/praeclarum/sqlite-net) library as a base so you will have **Linq besides sql**. For a further reference on what possibilities you have available with this library I encourage you to visit [its github repository](https://github.com/praeclarum/sqlite-net). @@ -23,7 +23,7 @@ All you have to do to start using it in your project: # Example If you want to try it I've uploaded a small example that you will be able to find in the "Example" folder. Download the folder and open it with Unity3d to give it a try. It contains classes that will help you to start. -##Android particularities +## Android particularities Android configuration and deployment is very straight forward. Take a look at the **following video** if you want to get a picture on how to proceed: UWP diff --git a/SQLite4Unity3d.zip b/SQLite4Unity3d.zip index 9b2091f..d4452c2 100644 Binary files a/SQLite4Unity3d.zip and b/SQLite4Unity3d.zip differ