diff --git a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/Plugins/macOS/sqlcipher.bundle.meta b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/Plugins/macOS/sqlcipher.bundle.meta
index 5981ee3..dbe4d26 100644
--- a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/Plugins/macOS/sqlcipher.bundle.meta
+++ b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/Plugins/macOS/sqlcipher.bundle.meta
@@ -20,6 +20,7 @@ PluginImporter:
Exclude Editor: 0
Exclude Linux64: 1
Exclude OSXUniversal: 0
+ Exclude WebGL: 1
Exclude Win: 1
Exclude Win64: 1
Exclude iOS: 1
@@ -40,7 +41,7 @@ PluginImporter:
second:
enabled: 1
settings:
- CPU: x86_64
+ CPU: ARM64
DefaultValueInitialized: true
OS: OSX
- first:
diff --git a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/Plugins/x64/libcrypto-1_1-x64.dll b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/Plugins/x64/libcrypto-1_1-x64.dll
deleted file mode 100644
index f746f0e..0000000
Binary files a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/Plugins/x64/libcrypto-1_1-x64.dll and /dev/null differ
diff --git a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/Plugins/x64/libcrypto-1_1-x64.dll.meta b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/Plugins/x64/libcrypto-1_1-x64.dll.meta
deleted file mode 100644
index e0d2f93..0000000
--- a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/Plugins/x64/libcrypto-1_1-x64.dll.meta
+++ /dev/null
@@ -1,91 +0,0 @@
-fileFormatVersion: 2
-guid: 4905f87dcd14491469d1286d50ef52c4
-PluginImporter:
- externalObjects: {}
- serializedVersion: 2
- iconMap: {}
- executionOrder: {}
- defineConstraints: []
- isPreloaded: 0
- isOverridable: 0
- isExplicitlyReferenced: 0
- validateReferences: 1
- platformData:
- - first:
- : Any
- second:
- enabled: 0
- settings:
- Exclude Android: 1
- Exclude Editor: 0
- Exclude Linux64: 0
- Exclude OSXUniversal: 0
- Exclude Win: 1
- Exclude Win64: 0
- Exclude WindowsStoreApps: 0
- Exclude iOS: 1
- - first:
- Android: Android
- second:
- enabled: 0
- settings:
- CPU: ARMv7
- - first:
- Any:
- second:
- enabled: 0
- settings: {}
- - first:
- Editor: Editor
- second:
- enabled: 1
- settings:
- CPU: x86_64
- DefaultValueInitialized: true
- OS: Windows
- - first:
- Standalone: Linux64
- second:
- enabled: 1
- settings:
- CPU: AnyCPU
- - first:
- Standalone: OSXUniversal
- second:
- enabled: 1
- settings:
- CPU: x86_64
- - first:
- Standalone: Win
- second:
- enabled: 0
- settings:
- CPU: None
- - first:
- Standalone: Win64
- second:
- enabled: 1
- settings:
- CPU: AnyCPU
- - first:
- Windows Store Apps: WindowsStoreApps
- second:
- enabled: 1
- settings:
- CPU: X64
- DontProcess: false
- PlaceholderPath:
- SDK: UWP
- ScriptingBackend: AnyScriptingBackend
- - first:
- iPhone: iOS
- second:
- enabled: 0
- settings:
- AddToEmbeddedBinaries: false
- CPU: AnyCPU
- CompileFlags:
- FrameworkDependencies:
- userData:
- assetBundleName:
- assetBundleVariant:
diff --git a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/Plugins/x64/libcrypto.so b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/Plugins/x64/libcrypto.so
deleted file mode 100644
index dd28eec..0000000
Binary files a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/Plugins/x64/libcrypto.so and /dev/null differ
diff --git a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/Plugins/x64/libcrypto.so.meta b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/Plugins/x64/libcrypto.so.meta
deleted file mode 100644
index e5d8761..0000000
--- a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/Plugins/x64/libcrypto.so.meta
+++ /dev/null
@@ -1,115 +0,0 @@
-fileFormatVersion: 2
-guid: df3b1f3ce94281c459ecdc390f8ae257
-PluginImporter:
- externalObjects: {}
- serializedVersion: 2
- iconMap: {}
- executionOrder: {}
- defineConstraints: []
- isPreloaded: 0
- isOverridable: 0
- isExplicitlyReferenced: 0
- validateReferences: 1
- platformData:
- - first:
- : Any
- second:
- enabled: 0
- settings:
- Exclude Android: 1
- Exclude Editor: 0
- Exclude Linux: 0
- Exclude Linux64: 0
- Exclude LinuxUniversal: 0
- Exclude OSXUniversal: 0
- Exclude Win: 0
- Exclude Win64: 0
- Exclude iOS: 1
- Exclude tvOS: 1
- - first:
- Android: Android
- second:
- enabled: 0
- settings:
- CPU: ARMv7
- - first:
- Any:
- second:
- enabled: 0
- settings: {}
- - first:
- Editor: Editor
- second:
- enabled: 1
- settings:
- CPU: x86_64
- DefaultValueInitialized: true
- OS: Linux
- - first:
- Facebook: Win
- second:
- enabled: 0
- settings:
- CPU: None
- - first:
- Facebook: Win64
- second:
- enabled: 1
- settings:
- CPU: AnyCPU
- - first:
- Standalone: Linux
- second:
- enabled: 1
- settings:
- CPU: None
- - first:
- Standalone: Linux64
- second:
- enabled: 1
- settings:
- CPU: x86_64
- - first:
- Standalone: LinuxUniversal
- second:
- enabled: 1
- settings:
- CPU: x86_64
- - first:
- Standalone: OSXUniversal
- second:
- enabled: 1
- settings:
- CPU: x86_64
- - first:
- Standalone: Win
- second:
- enabled: 1
- settings:
- CPU: None
- - first:
- Standalone: Win64
- second:
- enabled: 1
- settings:
- CPU: AnyCPU
- - first:
- iPhone: iOS
- second:
- enabled: 0
- settings:
- AddToEmbeddedBinaries: false
- CPU: AnyCPU
- CompileFlags:
- FrameworkDependencies:
- - first:
- tvOS: tvOS
- second:
- enabled: 0
- settings:
- CPU: AnyCPU
- CompileFlags:
- FrameworkDependencies:
- userData:
- assetBundleName:
- assetBundleVariant:
diff --git a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/Plugins/x64/libsqlcipher.so b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/Plugins/x64/libsqlcipher.so
deleted file mode 100644
index ea44784..0000000
Binary files a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/Plugins/x64/libsqlcipher.so and /dev/null differ
diff --git a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/Plugins/x64/libsqlcipher.so.meta b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/Plugins/x64/libsqlcipher.so.meta
deleted file mode 100644
index d7459f2..0000000
--- a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/Plugins/x64/libsqlcipher.so.meta
+++ /dev/null
@@ -1,115 +0,0 @@
-fileFormatVersion: 2
-guid: 25342efbb63fb5346b0100dc754b2c91
-PluginImporter:
- externalObjects: {}
- serializedVersion: 2
- iconMap: {}
- executionOrder: {}
- defineConstraints: []
- isPreloaded: 0
- isOverridable: 0
- isExplicitlyReferenced: 0
- validateReferences: 1
- platformData:
- - first:
- : Any
- second:
- enabled: 0
- settings:
- Exclude Android: 1
- Exclude Editor: 0
- Exclude Linux: 0
- Exclude Linux64: 0
- Exclude LinuxUniversal: 0
- Exclude OSXUniversal: 0
- Exclude Win: 0
- Exclude Win64: 0
- Exclude iOS: 1
- Exclude tvOS: 1
- - first:
- Android: Android
- second:
- enabled: 0
- settings:
- CPU: ARMv7
- - first:
- Any:
- second:
- enabled: 0
- settings: {}
- - first:
- Editor: Editor
- second:
- enabled: 1
- settings:
- CPU: x86_64
- DefaultValueInitialized: true
- OS: Linux
- - first:
- Facebook: Win
- second:
- enabled: 0
- settings:
- CPU: None
- - first:
- Facebook: Win64
- second:
- enabled: 1
- settings:
- CPU: AnyCPU
- - first:
- Standalone: Linux
- second:
- enabled: 1
- settings:
- CPU: None
- - first:
- Standalone: Linux64
- second:
- enabled: 1
- settings:
- CPU: x86_64
- - first:
- Standalone: LinuxUniversal
- second:
- enabled: 1
- settings:
- CPU: x86_64
- - first:
- Standalone: OSXUniversal
- second:
- enabled: 1
- settings:
- CPU: x86_64
- - first:
- Standalone: Win
- second:
- enabled: 1
- settings:
- CPU: None
- - first:
- Standalone: Win64
- second:
- enabled: 1
- settings:
- CPU: AnyCPU
- - first:
- iPhone: iOS
- second:
- enabled: 0
- settings:
- AddToEmbeddedBinaries: false
- CPU: AnyCPU
- CompileFlags:
- FrameworkDependencies:
- - first:
- tvOS: tvOS
- second:
- enabled: 0
- settings:
- CPU: AnyCPU
- CompileFlags:
- FrameworkDependencies:
- userData:
- assetBundleName:
- assetBundleVariant:
diff --git a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/Plugins/x64/sqlcipher.dll b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/Plugins/x64/sqlcipher.dll
deleted file mode 100644
index 143413a..0000000
Binary files a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/Plugins/x64/sqlcipher.dll and /dev/null differ
diff --git a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/Plugins/x64/sqlcipher.dll.meta b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/Plugins/x64/sqlcipher.dll.meta
deleted file mode 100644
index 9a5af6a..0000000
--- a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/Plugins/x64/sqlcipher.dll.meta
+++ /dev/null
@@ -1,91 +0,0 @@
-fileFormatVersion: 2
-guid: 553c4748fe7ee42cda362ee103818e60
-PluginImporter:
- externalObjects: {}
- serializedVersion: 2
- iconMap: {}
- executionOrder: {}
- defineConstraints: []
- isPreloaded: 0
- isOverridable: 0
- isExplicitlyReferenced: 0
- validateReferences: 1
- platformData:
- - first:
- : Any
- second:
- enabled: 0
- settings:
- Exclude Android: 1
- Exclude Editor: 0
- Exclude Linux64: 0
- Exclude OSXUniversal: 0
- Exclude Win: 1
- Exclude Win64: 0
- Exclude WindowsStoreApps: 0
- Exclude iOS: 1
- - first:
- Android: Android
- second:
- enabled: 0
- settings:
- CPU: ARMv7
- - first:
- Any:
- second:
- enabled: 0
- settings: {}
- - first:
- Editor: Editor
- second:
- enabled: 1
- settings:
- CPU: x86_64
- DefaultValueInitialized: true
- OS: Windows
- - first:
- Standalone: Linux64
- second:
- enabled: 1
- settings:
- CPU: AnyCPU
- - first:
- Standalone: OSXUniversal
- second:
- enabled: 1
- settings:
- CPU: x86_64
- - first:
- Standalone: Win
- second:
- enabled: 0
- settings:
- CPU: None
- - first:
- Standalone: Win64
- second:
- enabled: 1
- settings:
- CPU: AnyCPU
- - first:
- Windows Store Apps: WindowsStoreApps
- second:
- enabled: 1
- settings:
- CPU: X64
- DontProcess: false
- PlaceholderPath:
- SDK: UWP
- ScriptingBackend: AnyScriptingBackend
- - first:
- iPhone: iOS
- second:
- enabled: 0
- settings:
- AddToEmbeddedBinaries: false
- CPU: AnyCPU
- CompileFlags:
- FrameworkDependencies:
- userData:
- assetBundleName:
- assetBundleVariant:
diff --git a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/SQLiteAsync.cs b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/SQLiteAsync.cs
index 2d4254d..a722ff3 100644
--- a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/SQLiteAsync.cs
+++ b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/SQLiteAsync.cs
@@ -33,6 +33,9 @@ namespace SqlCipher4Unity3D
///
/// A pooled asynchronous connection to a SQLite database.
///
+ #if !SQLITEASYNC_UNITASK
+
+
public partial class SQLiteAsyncConnection
{
readonly SQLiteConnectionString _connectionString;
@@ -1492,7 +1495,7 @@ public void Reset()
}
}
}
-
+ #endif
///
/// This is a normal connection except it contains a Lock method that
/// can be used to serialize access to the database
diff --git a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/Plugins/x64.meta b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration.meta
similarity index 57%
rename from SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/Plugins/x64.meta
rename to SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration.meta
index 049d532..b59a2d4 100644
--- a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/Plugins/x64.meta
+++ b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration.meta
@@ -1,9 +1,8 @@
fileFormatVersion: 2
-guid: d080ad26255213a459eabfbd537e41b5
+guid: fa0c4fa0c60734926b50bc48e9c4deae
folderAsset: yes
-timeCreated: 1445131378
-licenseType: Free
DefaultImporter:
+ externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
diff --git a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Exceptions.meta b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Exceptions.meta
new file mode 100644
index 0000000..9a6b33a
--- /dev/null
+++ b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Exceptions.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: d54b9df3016c409bb2673af7b56d4329
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Exceptions/IncorrectRelationshipExtension.cs b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Exceptions/IncorrectRelationshipExtension.cs
new file mode 100644
index 0000000..00df57d
--- /dev/null
+++ b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Exceptions/IncorrectRelationshipExtension.cs
@@ -0,0 +1,15 @@
+namespace SqlCipher4Unity3D.UniTaskIntegration.Exceptions
+{
+ using System;
+
+ public class IncorrectRelationshipException : Exception
+ {
+ public string PropertyName { get; set; }
+ public string TypeName { get; set; }
+
+ public IncorrectRelationshipException(string typeName, string propertyName, string message)
+ : base(string.Format("{0}.{1}: {2}", typeName, propertyName, message))
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Exceptions/IncorrectRelationshipExtension.cs.meta b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Exceptions/IncorrectRelationshipExtension.cs.meta
new file mode 100644
index 0000000..dc3bfc6
--- /dev/null
+++ b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Exceptions/IncorrectRelationshipExtension.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: f302f7ffa17740e8ad476b0e9490f399
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions.meta b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions.meta
new file mode 100644
index 0000000..bc75c37
--- /dev/null
+++ b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 8ded37c3131a435f8a66de2ed83494fe
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/AsyncExtensions.meta b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/AsyncExtensions.meta
new file mode 100644
index 0000000..99e7535
--- /dev/null
+++ b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/AsyncExtensions.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 05aab3a30e854ae3aa8ec48eabb62ae8
+timeCreated: 1644966093
\ No newline at end of file
diff --git a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/AsyncExtensions/ReadOperations.cs b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/AsyncExtensions/ReadOperations.cs
new file mode 100644
index 0000000..2e9c0c4
--- /dev/null
+++ b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/AsyncExtensions/ReadOperations.cs
@@ -0,0 +1,204 @@
+using SQLite.Attributes;
+
+[assembly: Preserve]
+namespace SqlCipher4Unity3D.UniTaskIntegration.Extensions.AsyncExtensions
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Linq.Expressions;
+ using System.Reflection;
+ using System.Threading;
+ using Cysharp.Threading.Tasks;
+ using SQLite.Attributes;
+
+
+ [Preserve]
+ public static class ReadOperations
+ {
+ #region Public API
+
+ ///
+ /// Fetches all the entities of the specified type with the filter and fetches all the relationship
+ /// properties of all the returned elements.
+ ///
+ /// List of all the elements of the type T that matches the filter with the children already loaded
+ /// SQLite Net connection object
+ /// Filter that will be passed to the Where clause when fetching
+ /// objects from the database. No relationship properties are allowed in this filter as they
+ /// are loaded afterwards
+ /// If set to true all the relationships with
+ /// CascadeOperation.CascadeRead will be loaded recusively.
+ /// Cancellation token
+ /// Entity type where the object should be fetched from
+ public static UniTask> GetAllWithChildrenAsync(this SQLiteAsyncConnection conn, Expression> filter = null, bool recursive = false, CancellationToken cancellationToken = default(CancellationToken))
+ where T : new()
+ {
+ return UniTask.RunOnThreadPool(() =>
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+
+ var connectionWithLock = SqliteAsyncConnectionWrapper.Lock(conn);
+ using (connectionWithLock.Lock())
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ return connectionWithLock.GetAllWithChildren(filter, recursive);
+ }
+ }, false, cancellationToken);
+ }
+
+ ///
+ /// Obtains the object from the database and fetches all the properties annotated with
+ /// any subclass of RelationshipAttribute . If the object with the specified primary key doesn't
+ /// exist in the database, an exception will be raised.
+ ///
+ /// The object with all the children loaded
+ /// SQLite Net connection object
+ /// Primary key for the object to search in the database
+ /// If set to true all the relationships with
+ /// CascadeOperation.CascadeRead will be loaded recusively.
+ /// Cancellation token
+ /// Entity type where the object should be fetched from
+ public static UniTask GetWithChildrenAsync(this SQLiteAsyncConnection conn, object pk, bool recursive = false, CancellationToken cancellationToken = default(CancellationToken))
+ where T : new()
+ {
+ return UniTask.RunOnThreadPool(() =>
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+
+ var connectionWithLock = SqliteAsyncConnectionWrapper.Lock(conn);
+ using (connectionWithLock.Lock())
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ return connectionWithLock.GetWithChildren(pk, recursive);
+ }
+ },false, cancellationToken);
+ }
+
+ ///
+ /// The behavior is the same that GetWithChildren but it returns null if the object doesn't
+ /// exist in the database instead of throwing an exception
+ /// Obtains the object from the database and fetch all the properties annotated with
+ /// any subclass of RelationshipAttribute . If the object with the specified primary key doesn't
+ /// exist in the database, it will return null
+ ///
+ /// The object with all the children loaded or null if it doesn't exist
+ /// SQLite Net connection object
+ /// Primary key for the object to search in the database
+ /// If set to true all the relationships with
+ /// CascadeOperation.CascadeRead will be loaded recusively.
+ /// Cancellation token
+ /// Entity type where the object should be fetched from
+ public static UniTask FindWithChildrenAsync(this SQLiteAsyncConnection conn, object pk, bool recursive = false, CancellationToken cancellationToken = default(CancellationToken))
+ where T : new()
+ {
+ return UniTask.RunOnThreadPool(() =>
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+
+ var connectionWithLock = SqliteAsyncConnectionWrapper.Lock(conn);
+ using (connectionWithLock.Lock())
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ return connectionWithLock.FindWithChildren(pk, recursive);
+ }
+ }, false , cancellationToken);
+ }
+
+ ///
+ /// Fetches all the properties annotated with any subclass of RelationshipAttribute of the current
+ /// object and keeps fetching recursively if the recursive flag has been set.
+ ///
+ /// SQLite Net connection object
+ /// Element used to load all the relationship properties
+ /// If set to true all the relationships with
+ /// CascadeOperation.CascadeRead will be loaded recusively.
+ /// Cancellation token
+ /// Entity type where the object should be fetched from
+ public static UniTask GetChildrenAsync(this SQLiteAsyncConnection conn, T element, bool recursive = false, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ return UniTask.RunOnThreadPool(() =>
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+
+ var connectionWithLock = SqliteAsyncConnectionWrapper.Lock(conn);
+ using (connectionWithLock.Lock())
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ connectionWithLock.GetChildren(element, recursive);
+ }
+ }, false, cancellationToken);
+ }
+
+ ///
+ /// Fetches a specific property of the current object and keeps fetching recursively if the
+ /// recursive flag has been set.
+ ///
+ /// SQLite Net connection object
+ /// Element used to load all the relationship properties
+ /// Name of the property to fetch from the database
+ /// If set to true all the relationships with
+ /// CascadeOperation.CascadeRead will be loaded recusively.
+ /// Cancellation token
+ /// Entity type where the object should be fetched from
+ public static UniTask GetChildAsync(this SQLiteAsyncConnection conn, T element, string relationshipProperty, bool recursive = false, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ return UniTask.RunOnThreadPool(() =>
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+
+ var connectionWithLock = SqliteAsyncConnectionWrapper.Lock(conn);
+ using (connectionWithLock.Lock())
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ connectionWithLock.GetChild(element, element.GetType().GetRuntimeProperty(relationshipProperty), recursive);
+ }
+ }, false, cancellationToken);
+ }
+
+ ///
+ /// Fetches a specific property of the current object and keeps fetching recursively if the
+ /// recursive flag has been set.
+ ///
+ /// SQLite Net connection object
+ /// Element used to load all the relationship properties
+ /// Expression that returns the property to be loaded from the database.
+ /// This variant is useful to avoid spelling mistakes and make the code refactor-safe.
+ /// If set to true all the relationships with
+ /// CascadeOperation.CascadeRead will be loaded recusively.
+ /// Cancellation token
+ /// Entity type where the object should be fetched from
+ public static UniTask GetChildAsync(this SQLiteAsyncConnection conn, T element, Expression> propertyExpression, bool recursive = false, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ return conn.GetChildAsync(element, ReflectionExtensions.GetProperty(propertyExpression), recursive, cancellationToken);
+ }
+
+ ///
+ /// Fetches a specific property of the current object and keeps fetching recursively if the
+ /// recursive flag has been set.
+ ///
+ /// SQLite Net connection object
+ /// Element used to load all the relationship properties
+ /// Property to load from the database
+ /// If set to true all the relationships with
+ /// CascadeOperation.CascadeRead will be loaded recusively.
+ /// Cancellation token
+ /// Entity type where the object should be fetched from
+ public static UniTask GetChildAsync(this SQLiteAsyncConnection conn, T element, PropertyInfo relationshipProperty, bool recursive = false, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ return UniTask.RunOnThreadPool(() =>
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+
+ var connectionWithLock = SqliteAsyncConnectionWrapper.Lock(conn);
+ using (connectionWithLock.Lock())
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ connectionWithLock.GetChild(element, relationshipProperty, recursive);
+ }
+ }, false, cancellationToken);
+ }
+
+ #endregion
+
+ }
+}
\ No newline at end of file
diff --git a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/AsyncExtensions/ReadOperations.cs.meta b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/AsyncExtensions/ReadOperations.cs.meta
new file mode 100644
index 0000000..0022221
--- /dev/null
+++ b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/AsyncExtensions/ReadOperations.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 4a0eaf3efaf44ae3b0090c1728a45c6f
+timeCreated: 1644966093
\ No newline at end of file
diff --git a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/AsyncExtensions/SqliteAsyncConnectionWrapper.cs b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/AsyncExtensions/SqliteAsyncConnectionWrapper.cs
new file mode 100644
index 0000000..f164c0c
--- /dev/null
+++ b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/AsyncExtensions/SqliteAsyncConnectionWrapper.cs
@@ -0,0 +1,30 @@
+namespace SqlCipher4Unity3D.UniTaskIntegration.Extensions.AsyncExtensions
+{
+ using System;
+ using System.Reflection;
+ using SqlCipher4Unity3D;
+
+ public static class SqliteAsyncConnectionWrapper
+ {
+ private static readonly MethodInfo GetConnectionMethodInfo = typeof(SQLiteAsyncConnection).GetTypeInfo().GetDeclaredMethod("GetConnection");
+
+ private static SQLiteConnectionWithLock GetConnectionWithLock(SQLiteAsyncConnection asyncConnection)
+ {
+ return (SQLiteConnectionWithLock) GetConnectionMethodInfo.Invoke(asyncConnection, null);
+ }
+
+ public static SQLiteConnectionWithLock Lock(SQLiteAsyncConnection asyncConnection)
+ {
+ return GetConnectionWithLock(asyncConnection);
+ }
+ }
+
+ public static class SqliteConnectionExtensions
+ {
+ public static IDisposable Lock(this SQLiteConnectionWithLock connection)
+ {
+ var lockMethod = connection.GetType().GetTypeInfo().GetDeclaredMethod("Lock");
+ return (IDisposable)lockMethod.Invoke(connection, null);
+ }
+ }
+}
\ No newline at end of file
diff --git a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/AsyncExtensions/SqliteAsyncConnectionWrapper.cs.meta b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/AsyncExtensions/SqliteAsyncConnectionWrapper.cs.meta
new file mode 100644
index 0000000..e97e384
--- /dev/null
+++ b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/AsyncExtensions/SqliteAsyncConnectionWrapper.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 112042533cd14407a281cf58324c697e
+timeCreated: 1644966093
\ No newline at end of file
diff --git a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/AsyncExtensions/WriteOperations.cs b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/AsyncExtensions/WriteOperations.cs
new file mode 100644
index 0000000..8769df9
--- /dev/null
+++ b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/AsyncExtensions/WriteOperations.cs
@@ -0,0 +1,194 @@
+namespace SqlCipher4Unity3D.UniTaskIntegration.Extensions.AsyncExtensions
+{
+ using System.Collections;
+ using System.Collections.Generic;
+ using Cysharp.Threading.Tasks;
+ using SqlCipher4Unity3D;
+
+ public static class WriteOperations
+ {
+ ///
+ /// Updates the with foreign keys of the current object and save changes to the database and
+ /// updates the inverse foreign keys of the defined relationships so the relationships are
+ /// stored correctly in the database. This operation will create or delete the required intermediate
+ /// objects for ManyToMany relationships. All related objects must have a primary key assigned in order
+ /// to work correctly. This also implies that any object with 'AutoIncrement' primary key must've been
+ /// inserted in the database previous to this call.
+ /// This method will also update inverse relationships of objects that currently exist in the object tree,
+ /// but it won't update inverse relationships of objects that are not reachable through this object
+ /// properties. For example, objects removed from a 'ToMany' relationship won't be updated in memory.
+ ///
+ /// SQLite Net connection object
+ /// Object to be updated. Must already have been inserted in the database
+ public static UniTask UpdateWithChildrenAsync(this SQLiteAsyncConnection conn, object element)
+ {
+ return UniTask.RunOnThreadPool(() =>
+ {
+ var connectionWithLock = SqliteAsyncConnectionWrapper.Lock(conn);
+ using (connectionWithLock.Lock())
+ {
+ connectionWithLock.UpdateWithChildren(element);
+ }
+ });
+ }
+
+ ///
+ /// Inserts the element and all the relationships that are annotated with CascadeOperation.CascadeInsert
+ /// into the database. If any element already exists in the database a 'Constraint' exception will be raised.
+ /// Elements with a primary key that it's not AutoIncrement will need a valid identifier before calling
+ /// this method.
+ /// If the recursive flag is set to true, all the relationships annotated with
+ /// CascadeOperation.CascadeInsert are inserted recursively in the database. This method will handle
+ /// loops and inverse relationships correctly. ReadOnly properties will be omitted.
+ ///
+ /// SQLite Net connection object
+ /// Object to be inserted.
+ /// If set to true all the insert-cascade properties will be inserted
+ public static UniTask InsertWithChildrenAsync(this SQLiteAsyncConnection conn, object element, bool recursive = false)
+ {
+ return UniTask.RunOnThreadPool(() =>
+ {
+ var connectionWithLock = SqliteAsyncConnectionWrapper.Lock(conn);
+ using (connectionWithLock.Lock())
+ {
+ connectionWithLock.InsertWithChildren(element, recursive);
+ }
+ });
+ }
+
+ ///
+ /// Inserts or replace the element and all the relationships that are annotated with
+ /// CascadeOperation.CascadeInsert into the database. If any element already exists in the database
+ /// it will be replaced. Elements with AutoIncrement primary keys that haven't been assigned will
+ /// be always inserted instead of replaced.
+ /// If the recursive flag is set to true, all the relationships annotated with
+ /// CascadeOperation.CascadeInsert are inserted recursively in the database. This method will handle
+ /// loops and inverse relationships correctly. ReadOnly properties will be omitted.
+ ///
+ /// SQLite Net connection object
+ /// Object to be inserted.
+ /// If set to true all the insert-cascade properties will be inserted
+ public static UniTask InsertOrReplaceWithChildrenAsync(this SQLiteAsyncConnection conn, object element, bool recursive = false)
+ {
+ return UniTask.RunOnThreadPool(() =>
+ {
+ var connectionWithLock = SqliteAsyncConnectionWrapper.Lock(conn);
+ using (connectionWithLock.Lock())
+ {
+ connectionWithLock.InsertOrReplaceWithChildren(element, recursive);
+ }
+ });
+ }
+
+ ///
+ /// Inserts all the elements and all the relationships that are annotated with CascadeOperation.CascadeInsert
+ /// into the database. If any element already exists in the database a 'Constraint' exception will be raised.
+ /// Elements with a primary key that it's not AutoIncrement will need a valid identifier before calling
+ /// this method.
+ /// If the recursive flag is set to true, all the relationships annotated with
+ /// CascadeOperation.CascadeInsert are inserted recursively in the database. This method will handle
+ /// loops and inverse relationships correctly. ReadOnly properties will be omitted.
+ ///
+ /// SQLite Net connection object
+ /// Objects to be inserted.
+ /// If set to true all the insert-cascade properties will be inserted
+ public static UniTask InsertAllWithChildrenAsync(this SQLiteAsyncConnection conn, IEnumerable elements, bool recursive = false)
+ {
+ return UniTask.RunOnThreadPool(() =>
+ {
+ var connectionWithLock = SqliteAsyncConnectionWrapper.Lock(conn);
+ using (connectionWithLock.Lock())
+ {
+ connectionWithLock.InsertAllWithChildren(elements, recursive);
+ }
+ });
+ }
+
+ ///
+ /// Inserts or replace all the elements and all the relationships that are annotated with
+ /// CascadeOperation.CascadeInsert into the database. If any element already exists in the database
+ /// it will be replaced. Elements with AutoIncrement primary keys that haven't been assigned will
+ /// be always inserted instead of replaced.
+ /// If the recursive flag is set to true, all the relationships annotated with
+ /// CascadeOperation.CascadeInsert are inserted recursively in the database. This method will handle
+ /// loops and inverse relationships correctly. ReadOnly properties will be omitted.
+ ///
+ /// SQLite Net connection object
+ /// Objects to be inserted.
+ /// If set to true all the insert-cascade properties will be inserted
+ public static UniTask InsertOrReplaceAllWithChildrenAsync(this SQLiteAsyncConnection conn, IEnumerable elements, bool recursive = false)
+ {
+ return UniTask.RunOnThreadPool(() =>
+ {
+ var connectionWithLock = SqliteAsyncConnectionWrapper.Lock(conn);
+ using (connectionWithLock.Lock())
+ {
+ connectionWithLock.InsertOrReplaceAllWithChildren(elements, recursive);
+ }
+ });
+ }
+
+ ///
+ /// Deletes all the objects passed as parameters from the database.
+ /// If recursive flag is set to true, all relationships marked with 'CascadeDelete' will be
+ /// deleted from the database recursively. Inverse relationships and closed entity loops are handled
+ /// correctly to avoid endless loops
+ ///
+ /// SQLite Net connection object
+ /// If set to true all relationships marked with 'CascadeDelete' will be
+ /// deleted from the database recursively
+ /// Objects to be deleted from the database
+ public static UniTask DeleteAllAsync(this SQLiteAsyncConnection conn, IEnumerable objects, bool recursive = false)
+ {
+ return UniTask.RunOnThreadPool(() =>
+ {
+ var connectionWithLock = SqliteAsyncConnectionWrapper.Lock(conn);
+ using (connectionWithLock.Lock())
+ {
+ connectionWithLock.DeleteAll(objects, recursive);
+ }
+ });
+ }
+
+ ///
+ /// Deletes all the objects passed as parameters from the database.
+ /// If recursive flag is set to true, all relationships marked with 'CascadeDelete' will be
+ /// deleted from the database recursively. Inverse relationships and closed entity loops are handled
+ /// correctly to avoid endless loops
+ ///
+ /// SQLite Net connection object
+ /// If set to true all relationships marked with 'CascadeDelete' will be
+ /// deleted from the database recursively
+ /// Object to be deleted from the database
+ public static UniTask DeleteAsync(this SQLiteAsyncConnection conn, object element, bool recursive)
+ {
+ return UniTask.RunOnThreadPool(() =>
+ {
+ var connectionWithLock = SqliteAsyncConnectionWrapper.Lock(conn);
+ using (connectionWithLock.Lock())
+ {
+ connectionWithLock.Delete(element, recursive);
+ }
+ });
+ }
+
+ ///
+ /// Deletes all the objects passed with IDs equal to the passed parameters from the database.
+ /// Relationships are not taken into account in this method
+ ///
+ /// SQLite Net connection object
+ /// Primary keys of the objects to be deleted from the database
+ /// The Entity type, it should match de database entity type
+ public static UniTask DeleteAllIdsAsync(this SQLiteAsyncConnection conn, IEnumerable primaryKeyValues)
+ {
+ return UniTask.RunOnThreadPool(() =>
+ {
+ var connectionWithLock = SqliteAsyncConnectionWrapper.Lock(conn);
+ using (connectionWithLock.Lock())
+ {
+ connectionWithLock.DeleteAllIds(primaryKeyValues);
+ }
+ });
+ }
+ }
+}
\ No newline at end of file
diff --git a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/AsyncExtensions/WriteOperations.cs.meta b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/AsyncExtensions/WriteOperations.cs.meta
new file mode 100644
index 0000000..47a16e8
--- /dev/null
+++ b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/AsyncExtensions/WriteOperations.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: d3e6bd28399e46b3b8372d2968966e6e
+timeCreated: 1644966093
\ No newline at end of file
diff --git a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/Attributes.meta b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/Attributes.meta
new file mode 100644
index 0000000..c97d653
--- /dev/null
+++ b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/Attributes.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 1c1cda8f4ffcd43da9355cb6d7449d9f
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/Attributes/CascadeOperation.cs b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/Attributes/CascadeOperation.cs
new file mode 100644
index 0000000..7e18b7a
--- /dev/null
+++ b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/Attributes/CascadeOperation.cs
@@ -0,0 +1,13 @@
+namespace SqlCipher4Unity3D.UniTaskIntegration.Extensions.Attributes
+{
+ using System;
+
+ [Flags]
+ public enum CascadeOperation {
+ None = 0,
+ CascadeRead = 1 << 1,
+ CascadeInsert = 1 << 2,
+ CascadeDelete = 1 << 3,
+ All = CascadeRead | CascadeInsert | CascadeDelete
+ }
+}
\ No newline at end of file
diff --git a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/Attributes/CascadeOperation.cs.meta b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/Attributes/CascadeOperation.cs.meta
new file mode 100644
index 0000000..50bec37
--- /dev/null
+++ b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/Attributes/CascadeOperation.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 110862a11e154157b2ed8ec0afad4160
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/Attributes/ForeignKeyAttribute.cs b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/Attributes/ForeignKeyAttribute.cs
new file mode 100644
index 0000000..258c603
--- /dev/null
+++ b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/Attributes/ForeignKeyAttribute.cs
@@ -0,0 +1,16 @@
+namespace SqlCipher4Unity3D.UniTaskIntegration.Extensions.Attributes
+{
+ using System;
+ using global::SQLite.Attributes;
+
+ [AttributeUsage(AttributeTargets.Property)]
+ public class ForeignKeyAttribute : IndexedAttribute
+ {
+ public ForeignKeyAttribute(Type foreignType)
+ {
+ ForeignType = foreignType;
+ }
+
+ public Type ForeignType { get; private set; }
+ }
+}
\ No newline at end of file
diff --git a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/Attributes/ForeignKeyAttribute.cs.meta b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/Attributes/ForeignKeyAttribute.cs.meta
new file mode 100644
index 0000000..30bc65c
--- /dev/null
+++ b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/Attributes/ForeignKeyAttribute.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 9ef096ad72b346a1bb9a0e7de211fa3c
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/Attributes/ManyToManyAttribute.cs b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/Attributes/ManyToManyAttribute.cs
new file mode 100644
index 0000000..7309c3c
--- /dev/null
+++ b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/Attributes/ManyToManyAttribute.cs
@@ -0,0 +1,15 @@
+namespace SqlCipher4Unity3D.UniTaskIntegration.Extensions.Attributes
+{
+ using System;
+
+ public class ManyToManyAttribute : RelationshipAttribute
+ {
+ public ManyToManyAttribute(Type intermediateType, string inverseForeignKey = null, string inverseProperty = null)
+ : base(null, inverseForeignKey, inverseProperty)
+ {
+ IntermediateType = intermediateType;
+ }
+
+ public Type IntermediateType { get; private set; }
+ }
+}
\ No newline at end of file
diff --git a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/Attributes/ManyToManyAttribute.cs.meta b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/Attributes/ManyToManyAttribute.cs.meta
new file mode 100644
index 0000000..fd05f39
--- /dev/null
+++ b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/Attributes/ManyToManyAttribute.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: e3aab18c045d43249f867f6a28f29004
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/Attributes/ManyToOneAttribute.cs b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/Attributes/ManyToOneAttribute.cs
new file mode 100644
index 0000000..022d638
--- /dev/null
+++ b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/Attributes/ManyToOneAttribute.cs
@@ -0,0 +1,11 @@
+namespace SqlCipher4Unity3D.UniTaskIntegration.Extensions.Attributes
+{
+ public class ManyToOneAttribute : RelationshipAttribute
+ {
+ public ManyToOneAttribute(string foreignKey = null, string inverseProperty = null)
+ : base(foreignKey, null, inverseProperty)
+ {
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/Attributes/ManyToOneAttribute.cs.meta b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/Attributes/ManyToOneAttribute.cs.meta
new file mode 100644
index 0000000..26d824f
--- /dev/null
+++ b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/Attributes/ManyToOneAttribute.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: d0053e0437db4744b43eb596aa477ad2
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/Attributes/OneToManyAttribute.cs b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/Attributes/OneToManyAttribute.cs
new file mode 100644
index 0000000..37be8f1
--- /dev/null
+++ b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/Attributes/OneToManyAttribute.cs
@@ -0,0 +1,10 @@
+namespace SqlCipher4Unity3D.UniTaskIntegration.Extensions.Attributes
+{
+ public class OneToManyAttribute : RelationshipAttribute
+ {
+ public OneToManyAttribute(string inverseForeignKey = null, string inverseProperty = null)
+ : base(null, inverseForeignKey, inverseProperty)
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/Attributes/OneToManyAttribute.cs.meta b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/Attributes/OneToManyAttribute.cs.meta
new file mode 100644
index 0000000..01d71cf
--- /dev/null
+++ b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/Attributes/OneToManyAttribute.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 279764d3f49548568e34922f3db1b2d9
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/Attributes/OneToOneAttribute.cs b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/Attributes/OneToOneAttribute.cs
new file mode 100644
index 0000000..07f9643
--- /dev/null
+++ b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/Attributes/OneToOneAttribute.cs
@@ -0,0 +1,10 @@
+namespace SqlCipher4Unity3D.UniTaskIntegration.Extensions.Attributes
+{
+ public class OneToOneAttribute : RelationshipAttribute
+ {
+ public OneToOneAttribute(string foreignKey = null, string inverseProperty = null)
+ : base(foreignKey, null, inverseProperty)
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/Attributes/OneToOneAttribute.cs.meta b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/Attributes/OneToOneAttribute.cs.meta
new file mode 100644
index 0000000..9e31167
--- /dev/null
+++ b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/Attributes/OneToOneAttribute.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 50cfdc265dca4803ae8b75df3cfecde3
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/Attributes/RelationshipAttribute.cs b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/Attributes/RelationshipAttribute.cs
new file mode 100644
index 0000000..633091b
--- /dev/null
+++ b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/Attributes/RelationshipAttribute.cs
@@ -0,0 +1,26 @@
+namespace SqlCipher4Unity3D.UniTaskIntegration.Extensions.Attributes
+{
+ using System;
+ using global::SQLite.Attributes;
+
+ [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
+ public abstract class RelationshipAttribute : IgnoreAttribute
+ {
+ protected RelationshipAttribute(string foreignKey, string inverseForeignKey, string inverseProperty)
+ {
+ InverseForeignKey = inverseForeignKey;
+ InverseProperty = inverseProperty;
+ ForeignKey = foreignKey;
+ }
+
+ public string ForeignKey { get; private set; }
+ public string InverseProperty { get; private set; }
+ public string InverseForeignKey { get; private set; }
+ public virtual CascadeOperation CascadeOperations { get; set; }
+ public bool ReadOnly { get; set; }
+
+ public bool IsCascadeRead { get { return CascadeOperations.HasFlag(CascadeOperation.CascadeRead); } }
+ public bool IsCascadeInsert { get { return CascadeOperations.HasFlag(CascadeOperation.CascadeInsert); } }
+ public bool IsCascadeDelete { get { return CascadeOperations.HasFlag(CascadeOperation.CascadeDelete); } }
+ }
+}
\ No newline at end of file
diff --git a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/Attributes/RelationshipAttribute.cs.meta b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/Attributes/RelationshipAttribute.cs.meta
new file mode 100644
index 0000000..4cab30c
--- /dev/null
+++ b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/Attributes/RelationshipAttribute.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 83de8f69ff0f42d09e4f53594df33d73
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/Attributes/TextBlobAttribute.cs b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/Attributes/TextBlobAttribute.cs
new file mode 100644
index 0000000..e833e42
--- /dev/null
+++ b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/Attributes/TextBlobAttribute.cs
@@ -0,0 +1,18 @@
+namespace SqlCipher4Unity3D.UniTaskIntegration.Extensions.Attributes
+{
+ using System;
+
+ [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
+ public class TextBlobAttribute : RelationshipAttribute
+ {
+ public TextBlobAttribute(string textProperty) : base(null, null, null)
+ {
+ TextProperty = textProperty;
+ }
+
+ public string TextProperty { get; private set; }
+
+ // No cascade operations allowed on TextBlob properties
+ public override CascadeOperation CascadeOperations { get { return CascadeOperation.None; } }
+ }
+}
\ No newline at end of file
diff --git a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/Attributes/TextBlobAttribute.cs.meta b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/Attributes/TextBlobAttribute.cs.meta
new file mode 100644
index 0000000..30c9a71
--- /dev/null
+++ b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/Attributes/TextBlobAttribute.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 5a8a97dff8f244ca8769fdb7eeb09f58
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/ReadOperations.cs b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/ReadOperations.cs
new file mode 100644
index 0000000..142fb8f
--- /dev/null
+++ b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/ReadOperations.cs
@@ -0,0 +1,782 @@
+using ObjectCache =
+ System.Collections.Generic.Dictionary>;
+
+
+namespace SqlCipher4Unity3D.UniTaskIntegration.Extensions
+{
+ using System;
+ using System.Collections;
+ using System.Collections.Generic;
+ using System.Collections.ObjectModel;
+ using System.Linq;
+ using System.Linq.Expressions;
+ using System.Reflection;
+ using Attributes;
+ using Exceptions;
+ using SqlCipher4Unity3D;
+ using TextBlob;
+
+ public static class ReadOperations
+ {
+ #region Public API
+
+ ///
+ /// Enable to allow descriptive error descriptions on incorrect relationships. Enabled by default.
+ /// Disable for production environments to remove the checks and reduce performance penalty
+ ///
+ public static bool EnableRuntimeAssertions = true;
+
+ ///
+ /// Fetches all the entities of the specified type with the filter and fetches all the relationship
+ /// properties of all the returned elements.
+ ///
+ /// List of all the elements of the type T that matches the filter with the children already loaded
+ /// SQLite Net connection object
+ /// Filter that will be passed to the Where clause when fetching
+ /// objects from the database. No relationship properties are allowed in this filter as they
+ /// are loaded afterwards
+ /// If set to true all the relationships with
+ /// CascadeOperation.CascadeRead will be loaded recusively.
+ /// Entity type where the object should be fetched from
+ public static List GetAllWithChildren(this SQLiteConnection conn, Expression> filter = null,
+ bool recursive = false)
+ where T : new()
+ {
+ var elements = conn.Table();
+ if (filter != null)
+ {
+ elements = elements.Where(filter);
+ }
+
+ var list = elements.ToList();
+
+ foreach (T element in list)
+ {
+ conn.GetChildren(element, recursive);
+ }
+
+ return list;
+ }
+
+ ///
+ /// Obtains the object from the database and fetches all the properties annotated with
+ /// any subclass of RelationshipAttribute . If the object with the specified primary key doesn't
+ /// exist in the database, an exception will be raised.
+ ///
+ /// The object with all the children loaded
+ /// SQLite Net connection object
+ /// Primary key for the object to search in the database
+ /// If set to true all the relationships with
+ /// CascadeOperation.CascadeRead will be loaded recusively.
+ /// Entity type where the object should be fetched from
+ public static T GetWithChildren(this SQLiteConnection conn, object pk, bool recursive = false)
+ where T : new()
+ {
+ var element = conn.Get(pk);
+ conn.GetChildren(element, recursive);
+ return element;
+ }
+
+ ///
+ /// The behavior is the same that GetWithChildren but it returns null if the object doesn't
+ /// exist in the database instead of throwing an exception
+ /// Obtains the object from the database and fetch all the properties annotated with
+ /// any subclass of RelationshipAttribute . If the object with the specified primary key doesn't
+ /// exist in the database, it will return null
+ ///
+ /// The object with all the children loaded or null if it doesn't exist
+ /// SQLite Net connection object
+ /// Primary key for the object to search in the database
+ /// If set to true all the relationships with
+ /// CascadeOperation.CascadeRead will be loaded recusively.
+ /// Entity type where the object should be fetched from
+ public static T FindWithChildren(this SQLiteConnection conn, object pk, bool recursive = false)
+ where T : new()
+ {
+ var element = conn.Find(pk);
+ if (!EqualityComparer.Default.Equals(element, default(T)))
+ conn.GetChildren(element, recursive);
+ return element;
+ }
+
+ ///
+ /// Fetches all the properties annotated with any subclass of RelationshipAttribute of the current
+ /// object and keeps fetching recursively if the recursive flag has been set.
+ ///
+ /// SQLite Net connection object
+ /// Element used to load all the relationship properties
+ /// If set to true all the relationships with
+ /// CascadeOperation.CascadeRead will be loaded recusively.
+ /// Entity type where the object should be fetched from
+ public static void GetChildren(this SQLiteConnection conn, T element, bool recursive = false)
+ {
+ GetChildrenRecursive(conn, element, false, recursive);
+ }
+
+ ///
+ /// Fetches a specific property of the current object and keeps fetching recursively if the
+ /// recursive flag has been set.
+ ///
+ /// SQLite Net connection object
+ /// Element used to load all the relationship properties
+ /// Name of the property to fetch from the database
+ /// If set to true all the relationships with
+ /// CascadeOperation.CascadeRead will be loaded recusively.
+ /// Entity type where the object should be fetched from
+ public static void GetChild(this SQLiteConnection conn, T element, string relationshipProperty,
+ bool recursive = false)
+ {
+ conn.GetChild(element, element.GetType().GetRuntimeProperty(relationshipProperty), recursive);
+ }
+
+ ///
+ /// Fetches a specific property of the current object and keeps fetching recursively if the
+ /// recursive flag has been set.
+ ///
+ /// SQLite Net connection object
+ /// Element used to load all the relationship properties
+ /// Expression that returns the property to be loaded from the database.
+ /// This variant is useful to avoid spelling mistakes and make the code refactor-safe.
+ /// If set to true all the relationships with
+ /// CascadeOperation.CascadeRead will be loaded recusively.
+ /// Entity type where the object should be fetched from
+ public static void GetChild(this SQLiteConnection conn, T element,
+ Expression> propertyExpression, bool recursive = false)
+ {
+ conn.GetChild(element, ReflectionExtensions.GetProperty(propertyExpression), recursive);
+ }
+
+ ///
+ /// Fetches a specific property of the current object and keeps fetching recursively if the
+ /// recursive flag has been set.
+ ///
+ /// SQLite Net connection object
+ /// Element used to load all the relationship properties
+ /// Property to load from the database
+ /// If set to true all the relationships with
+ /// CascadeOperation.CascadeRead will be loaded recusively.
+ /// Entity type where the object should be fetched from
+ public static void GetChild(this SQLiteConnection conn, T element, PropertyInfo relationshipProperty,
+ bool recursive = false)
+ {
+ conn.GetChildRecursive(element, relationshipProperty, recursive, new ObjectCache());
+ }
+
+ #endregion
+
+ #region Private methods
+
+ private static void GetChildrenRecursive(this SQLiteConnection conn, object element, bool onlyCascadeChildren,
+ bool recursive, ObjectCache objectCache = null)
+ {
+ objectCache = objectCache ?? new ObjectCache();
+
+ foreach (var relationshipProperty in element.GetType().GetRelationshipProperties())
+ {
+ var relationshipAttribute = relationshipProperty.GetAttribute();
+ if (!onlyCascadeChildren || relationshipAttribute.IsCascadeRead)
+ {
+ conn.GetChildRecursive(element, relationshipProperty, recursive, objectCache);
+ }
+ else if (relationshipAttribute is TextBlobAttribute)
+ {
+ conn.GetChildRecursive(element, relationshipProperty, false, objectCache);
+ }
+ }
+ }
+
+ private static void GetChildRecursive(this SQLiteConnection conn, object element,
+ PropertyInfo relationshipProperty, bool recursive, ObjectCache objectCache)
+ {
+ var relationshipAttribute = relationshipProperty.GetAttribute();
+
+ switch (relationshipAttribute)
+ {
+ case OneToOneAttribute _:
+ conn.GetOneToOneChildren(new List { element }, relationshipProperty, recursive, objectCache);
+ break;
+ case OneToManyAttribute _:
+ conn.GetOneToManyChildren(element, relationshipProperty, recursive, objectCache);
+ break;
+ case ManyToOneAttribute _:
+ conn.GetManyToOneChildren(new List { element }, relationshipProperty, recursive,
+ objectCache);
+ break;
+ case ManyToManyAttribute _:
+ conn.GetManyToManyChildren(element, relationshipProperty, recursive, objectCache);
+ break;
+ case TextBlobAttribute _:
+ TextBlobOperations.GetTextBlobChild(element, relationshipProperty);
+ break;
+ }
+ }
+
+ private static object GetOneToOneChildren(this SQLiteConnection conn, IList elements,
+ PropertyInfo relationshipProperty,
+ bool recursive, ObjectCache objectCache)
+ {
+ var primaryKeys = new Dictionary>();
+ var type = elements[0].GetType();
+
+ var entityType = relationshipProperty.GetEntityType(out var enclosedType);
+
+ Assert(enclosedType == EnclosedType.None, type, relationshipProperty,
+ "OneToOne relationship cannot be of type List or Array");
+
+ var currentEntityPrimaryKeyProperty = type.GetPrimaryKey();
+ var otherEntityPrimaryKeyProperty = entityType.GetPrimaryKey();
+
+ Assert(currentEntityPrimaryKeyProperty != null || otherEntityPrimaryKeyProperty != null, type,
+ relationshipProperty,
+ "At least one entity in a OneToOne relationship must have Primary Key");
+
+ var currentEntityForeignKeyProperty = type.GetForeignKeyProperty(relationshipProperty);
+ var otherEntityForeignKeyProperty = type.GetForeignKeyProperty(relationshipProperty, inverse: true);
+
+ Assert(currentEntityForeignKeyProperty != null || otherEntityForeignKeyProperty != null, type,
+ relationshipProperty,
+ "At least one entity in a OneToOne relationship must have Foreign Key");
+
+ var hasForeignKey = otherEntityPrimaryKeyProperty != null && currentEntityForeignKeyProperty != null;
+ var hasInverseForeignKey = currentEntityPrimaryKeyProperty != null && otherEntityForeignKeyProperty != null;
+
+ Assert(hasForeignKey || hasInverseForeignKey, type, relationshipProperty,
+ "Missing either ForeignKey or PrimaryKey for a complete OneToOne relationship");
+
+ var tableMapping = conn.GetMapping(entityType);
+
+ Assert(tableMapping != null, type, relationshipProperty,
+ "There's no mapping table for OneToOne relationship");
+
+ var inverseProperty = type.GetInverseProperty(relationshipProperty);
+
+ foreach (var element in elements)
+ {
+ var isLoadedFromCache = false;
+ var value = default(T);
+ object keyValue = null;
+
+ if (hasForeignKey)
+ {
+ keyValue = currentEntityForeignKeyProperty.GetValue(element, null);
+ if (keyValue != null)
+ {
+ // Try to load from cache when possible
+ if (recursive)
+ {
+ value = (T)GetObjectFromCache(entityType, keyValue, objectCache);
+ }
+
+ if (value != null)
+ {
+ isLoadedFromCache = true;
+ }
+ }
+ }
+ else
+ {
+ keyValue = currentEntityPrimaryKeyProperty?.GetValue(element, null);
+ // Try to replace the loaded entity with the same object from the cache whenever possible
+ value = recursive
+ ? (T)ReplaceWithCacheObjectIfPossible(value, otherEntityPrimaryKeyProperty, objectCache,
+ out isLoadedFromCache)
+ : value;
+ }
+
+ if (isLoadedFromCache)
+ {
+ relationshipProperty.SetValue(element, value, null);
+ if (value != null && inverseProperty != null)
+ {
+ inverseProperty.SetValue(value, element, null);
+ }
+ }
+ else
+ {
+ if (keyValue != null)
+ {
+ AddPrimaryKeyToDictionary(keyValue, element, primaryKeys);
+ }
+ }
+ }
+
+ if (primaryKeys.Count <= 0) return elements[0];
+ {
+ string columnName;
+ if (otherEntityForeignKeyProperty != null)
+ {
+ columnName = otherEntityForeignKeyProperty.GetColumnName();
+ }
+ else
+ {
+ columnName = tableMapping?.PK.Name;
+ }
+
+ var placeHolders = string.Join(",", Enumerable.Repeat("?", primaryKeys.Count));
+
+ var query = string.Format("select * from [{0}] where [{1}] in ({2})", tableMapping?.TableName,
+ columnName, placeHolders);
+
+ IList values = conn.Query(tableMapping, query, primaryKeys.Keys.ToArray());
+
+ if (values.Count <= 0) return elements[0];
+
+ var keyProperty = otherEntityForeignKeyProperty ?? values[0].GetType().GetPrimaryKey();
+
+ foreach (var value in values)
+ {
+ var keyValue = keyProperty.GetValue(value);
+
+ if (!primaryKeys.TryGetValue(keyValue, out var keyElements)) continue;
+
+ foreach (var keyElement in keyElements)
+ {
+ relationshipProperty.SetValue(keyElement, value, null);
+ if (value != null && inverseProperty != null)
+ {
+ inverseProperty.SetValue(value, keyElement, null);
+ }
+
+ if (value != null && recursive)
+ {
+ SaveObjectToCache(value, otherEntityPrimaryKeyProperty.GetValue(value, null), objectCache);
+ conn.GetChildrenRecursive(value, true, recursive, objectCache);
+ }
+ }
+ }
+ }
+ return elements[0];
+ }
+
+
+ private static object GetManyToOneChildren(this SQLiteConnection conn, IList elements,
+ PropertyInfo relationshipProperty,
+ bool recursive, ObjectCache objectCache)
+ {
+ var primaryKeys = new Dictionary>();
+ var type = elements[0].GetType();
+
+ var entityType = relationshipProperty.GetEntityType(out var enclosedType);
+
+ Assert(enclosedType == EnclosedType.None, type, relationshipProperty,
+ "ManyToOne relationship cannot be of type List or Array");
+
+ var otherEntityPrimaryKeyProperty = entityType.GetPrimaryKey();
+ Assert(otherEntityPrimaryKeyProperty != null, type, relationshipProperty,
+ "ManyToOne relationship destination must have Primary Key");
+
+ var currentEntityForeignKeyProperty = type.GetForeignKeyProperty(relationshipProperty);
+ Assert(currentEntityForeignKeyProperty != null, type, relationshipProperty,
+ "ManyToOne relationship origin must have Foreign Key");
+
+ var tableMapping = conn.GetMapping(entityType);
+ Assert(tableMapping != null, type, relationshipProperty,
+ "There's no mapping table for OneToMany relationship destination");
+
+ foreach (var element in elements)
+ {
+ object value = null;
+ var isLoadedFromCache = false;
+ var foreignKeyValue = currentEntityForeignKeyProperty?.GetValue(element, null);
+ if (foreignKeyValue != null)
+ {
+ // Try to load from cache when possible
+ if (recursive)
+ {
+ value = GetObjectFromCache(entityType, foreignKeyValue, objectCache);
+ }
+
+ if (value == null)
+ {
+ AddPrimaryKeyToDictionary(foreignKeyValue, element, primaryKeys);
+ }
+
+ else
+ {
+ isLoadedFromCache = true;
+ }
+ }
+
+ if (isLoadedFromCache)
+ {
+ relationshipProperty.SetValue(element, value, null);
+ }
+ }
+
+ if (primaryKeys.Count <= 0) return elements[0];
+ {
+ var placeHolders = string.Join(",", Enumerable.Repeat("?", primaryKeys.Count));
+
+ var query = string.Format("select * from [{0}] where [{1}] in ({2})", tableMapping?.TableName,
+ tableMapping?.PK.Name, placeHolders);
+
+ IList values = conn.Query(tableMapping, query, primaryKeys.Keys.ToArray());
+
+ if (values.Count <= 0) return elements[0];
+
+ var keyProperty = values[0].GetType().GetPrimaryKey();
+
+ foreach (var value in values)
+ {
+ var keyValue = keyProperty.GetValue(value);
+
+ if (!primaryKeys.TryGetValue(keyValue, out var keyElements)) continue;
+
+ foreach (var keyElement in keyElements)
+ {
+ relationshipProperty.SetValue(keyElement, value, null);
+ if (value == null || !recursive) continue;
+
+ SaveObjectToCache(value, otherEntityPrimaryKeyProperty?.GetValue(value, null),
+ objectCache);
+
+ conn.GetChildrenRecursive(value, true, recursive, objectCache);
+ }
+ }
+ }
+
+ return elements[0];
+ }
+
+ private static void AddPrimaryKeyToDictionary(object key, T element,
+ Dictionary> primaryKeys)
+ {
+ if (!primaryKeys.TryGetValue(key, out var list))
+ {
+ list = new List() { element };
+ primaryKeys.Add(key, list);
+ }
+ else
+ {
+ list.Add(element);
+ }
+ }
+
+ private static IEnumerable GetOneToManyChildren(this SQLiteConnection conn, T element,
+ PropertyInfo relationshipProperty,
+ bool recursive, ObjectCache objectCache)
+ {
+ var type = element.GetType();
+
+ var entityType = relationshipProperty.GetEntityType(out var enclosedType);
+
+ Assert(enclosedType != EnclosedType.None, type, relationshipProperty,
+ "OneToMany relationship must be a List or Array");
+
+ var currentEntityPrimaryKeyProperty = type.GetPrimaryKey();
+ Assert(currentEntityPrimaryKeyProperty != null, type, relationshipProperty,
+ "OneToMany relationship origin must have Primary Key");
+
+ var otherEntityForeignKeyProperty = type.GetForeignKeyProperty(relationshipProperty, inverse: true);
+ Assert(otherEntityForeignKeyProperty != null, type, relationshipProperty,
+ "OneToMany relationship destination must have Foreign Key to the origin class");
+
+ var otherEntityPrimaryKeyProperty = entityType.GetPrimaryKey();
+
+ var tableMapping = conn.GetMapping(entityType);
+ Assert(tableMapping != null, type, relationshipProperty,
+ "There's no mapping table for OneToMany relationship destination");
+
+ var inverseProperty = type.GetInverseProperty(relationshipProperty);
+
+ IList cascadeElements = new List();
+ IList values = null;
+
+ var primaryKeyValue = currentEntityPrimaryKeyProperty?.GetValue(element, null);
+ if (primaryKeyValue != null)
+ {
+ var query = string.Format("select * from [{0}] where [{1}] = ?", entityType.GetTableName(),
+ otherEntityForeignKeyProperty.GetColumnName());
+
+ var queryResults = conn.Query(tableMapping, query, primaryKeyValue);
+
+ Array array = null;
+
+ values = enclosedType switch
+ {
+ // Create a generic list of the expected type
+ EnclosedType.List => (IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(entityType)),
+ EnclosedType.ObservableCollection => (IList)Activator.CreateInstance(
+ typeof(ObservableCollection<>).MakeGenericType(entityType)),
+ _ => array = Array.CreateInstance(entityType, queryResults.Count)
+ };
+
+ var i = 0;
+
+ foreach (T result in queryResults)
+ {
+ // Replace obtained value with a cached one whenever possible
+ var loadedFromCache = false;
+ var value = recursive
+ ? ReplaceWithCacheObjectIfPossible(result, otherEntityPrimaryKeyProperty, objectCache,
+ out loadedFromCache)
+ : result;
+
+ if (array != null)
+ {
+ array.SetValue(value, i);
+ }
+ else
+ {
+ values.Add(value);
+ }
+
+ if (!loadedFromCache)
+ {
+ cascadeElements.Add(result);
+ }
+
+ i++;
+ }
+ }
+
+ relationshipProperty.SetValue(element, values, null);
+
+ if (inverseProperty != null && values != null)
+ {
+ // Stablish inverse relationships (we already have that object anyway)
+ foreach (var value in values)
+ {
+ inverseProperty.SetValue(value, element, null);
+ }
+ }
+
+ if (recursive)
+ {
+ if (cascadeElements.Count > 0)
+ {
+ conn.GetChildrenRecursiveBatched(cascadeElements, objectCache);
+ }
+ }
+
+ return values;
+ }
+
+ private static void GetChildrenRecursiveBatched(this SQLiteConnection conn, IList elements,
+ ObjectCache objectCache)
+ {
+ var element = elements[0];
+ foreach (var relationshipProperty in element.GetType().GetRelationshipProperties())
+ {
+ var relationshipAttribute = relationshipProperty.GetAttribute();
+ if (relationshipAttribute.IsCascadeRead)
+ {
+ if (relationshipAttribute is OneToOneAttribute)
+ {
+ conn.GetOneToOneChildren(elements, relationshipProperty, true, objectCache);
+ }
+ else if (relationshipAttribute is OneToManyAttribute)
+ {
+ foreach (var e in elements)
+ {
+ conn.GetOneToManyChildren(e, relationshipProperty, true, objectCache);
+ }
+ }
+ else if (relationshipAttribute is ManyToOneAttribute)
+ {
+ conn.GetManyToOneChildren(elements, relationshipProperty, true, objectCache);
+ }
+ else if (relationshipAttribute is ManyToManyAttribute)
+ {
+ foreach (var e in elements)
+ {
+ conn.GetManyToManyChildren(e, relationshipProperty, true, objectCache);
+ }
+ }
+ else if (relationshipAttribute is TextBlobAttribute)
+ {
+ TextBlobOperations.GetTextBlobChild(element, relationshipProperty);
+ }
+ }
+ else if (relationshipAttribute is TextBlobAttribute)
+ {
+ foreach (var e in elements)
+ {
+ conn.GetChildRecursive(e, relationshipProperty, false, objectCache);
+ }
+ }
+ }
+ }
+
+ private static IEnumerable GetManyToManyChildren(this SQLiteConnection conn, T element,
+ PropertyInfo relationshipProperty,
+ bool recursive, ObjectCache objectCache)
+ {
+ var type = element.GetType();
+ EnclosedType enclosedType;
+ var entityType = relationshipProperty.GetEntityType(out enclosedType);
+
+ var currentEntityPrimaryKeyProperty = type.GetPrimaryKey();
+ var otherEntityPrimaryKeyProperty = entityType.GetPrimaryKey();
+ var manyToManyMetaInfo = type.GetManyToManyMetaInfo(relationshipProperty);
+ var currentEntityForeignKeyProperty = manyToManyMetaInfo.OriginProperty;
+ var otherEntityForeignKeyProperty = manyToManyMetaInfo.DestinationProperty;
+ var intermediateType = manyToManyMetaInfo.IntermediateType;
+ var tableMapping = conn.GetMapping(entityType);
+
+ Assert(enclosedType != EnclosedType.None, type, relationshipProperty,
+ "ManyToMany relationship must be a List or Array");
+ Assert(currentEntityPrimaryKeyProperty != null, type, relationshipProperty,
+ "ManyToMany relationship origin must have Primary Key");
+ Assert(otherEntityPrimaryKeyProperty != null, type, relationshipProperty,
+ "ManyToMany relationship destination must have Primary Key");
+ Assert(intermediateType != null, type, relationshipProperty,
+ "ManyToMany relationship intermediate type cannot be null");
+ Assert(currentEntityForeignKeyProperty != null, type, relationshipProperty,
+ "ManyToMany relationship origin must have a foreign key defined in the intermediate type");
+ Assert(otherEntityForeignKeyProperty != null, type, relationshipProperty,
+ "ManyToMany relationship destination must have a foreign key defined in the intermediate type");
+ Assert(tableMapping != null, type, relationshipProperty,
+ "There's no mapping table defined for ManyToMany relationship origin");
+
+ IList cascadeElements = new List();
+ IList values = null;
+
+ var primaryKeyValue = currentEntityPrimaryKeyProperty?.GetValue(element, null);
+ if (primaryKeyValue != null)
+ {
+ // Obtain the relationship keys
+ var keysQuery = string.Format("select [{0}] from [{1}] where [{2}] = ?",
+ otherEntityForeignKeyProperty.GetColumnName(),
+ intermediateType.GetTableName(), currentEntityForeignKeyProperty.GetColumnName());
+
+ var query = string.Format("select * from [{0}] where [{1}] in ({2})", entityType.GetTableName(),
+ otherEntityPrimaryKeyProperty.GetColumnName(), keysQuery);
+
+ var queryResults = conn.Query(tableMapping, query, primaryKeyValue);
+
+ Array array = null;
+
+ values = enclosedType switch
+ {
+ // Create a generic list of the expected type
+ EnclosedType.List => (IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(entityType)),
+ EnclosedType.ObservableCollection => (IList)Activator.CreateInstance(
+ typeof(ObservableCollection<>).MakeGenericType(entityType)),
+ _ => array = Array.CreateInstance(entityType, queryResults.Count)
+ };
+
+ var i = 0;
+ foreach (var result in queryResults)
+ {
+ // Replace obtained value with a cached one whenever possible
+ var loadedFromCache = false;
+ var value = recursive
+ ? ReplaceWithCacheObjectIfPossible(result, otherEntityPrimaryKeyProperty, objectCache,
+ out loadedFromCache)
+ : result;
+
+ if (array != null)
+ {
+ array.SetValue(value, i);
+ }
+ else
+ {
+ values.Add(value);
+ }
+
+ if (!loadedFromCache)
+ {
+ cascadeElements.Add(result);
+ }
+
+ i++;
+ }
+ }
+
+
+ relationshipProperty.SetValue(element, values, null);
+
+ if (recursive)
+ {
+ foreach (var child in cascadeElements)
+ {
+ conn.GetChildrenRecursive(child, true, recursive, objectCache);
+ }
+ }
+
+ return values;
+ }
+
+ static object ReplaceWithCacheObjectIfPossible(object element, PropertyInfo primaryKeyProperty,
+ ObjectCache objectCache, out bool isLoadedFromCache)
+ {
+ isLoadedFromCache = false;
+
+ if (element == null || primaryKeyProperty == null || objectCache == null)
+ {
+ return element;
+ }
+
+ object primaryKey = null;
+ primaryKey = primaryKeyProperty.GetValue(element, null);
+
+ if (primaryKey == null)
+ {
+ return element;
+ }
+
+ var cachedElement = GetObjectFromCache(element.GetType(), primaryKey, objectCache);
+ object result;
+ if (cachedElement != null)
+ {
+ result = cachedElement;
+ isLoadedFromCache = true;
+ }
+ else
+ {
+ result = element;
+ SaveObjectToCache(element, primaryKey, objectCache);
+ }
+
+ return result;
+ }
+
+ static void Assert(bool assertion, Type type, PropertyInfo property, string message)
+ {
+ if (EnableRuntimeAssertions && !assertion)
+ {
+ throw new IncorrectRelationshipException(type.Name, property.Name, message);
+ }
+ }
+
+ static object GetObjectFromCache(Type objectType, object primaryKey, ObjectCache objectCache)
+ {
+ if (objectCache == null)
+ {
+ return null;
+ }
+
+ var typeName = objectType.FullName;
+ Dictionary typeDict = null;
+ object value = null;
+
+ if (typeName != null && objectCache.TryGetValue(typeName, out typeDict))
+ {
+ typeDict.TryGetValue(primaryKey, out value);
+ }
+
+ return value;
+ }
+
+ static void SaveObjectToCache(object element, object primaryKey, ObjectCache objectCache)
+ {
+ if (objectCache == null || primaryKey == null || element == null)
+ {
+ return;
+ }
+
+ var typeName = element.GetType().FullName;
+ Dictionary typeDict = null;
+ if (!objectCache.TryGetValue(typeName, out typeDict))
+ {
+ typeDict = new Dictionary();
+ objectCache[typeName] = typeDict;
+ }
+
+ typeDict[primaryKey] = element;
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/ReadOperations.cs.meta b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/ReadOperations.cs.meta
new file mode 100644
index 0000000..9071645
--- /dev/null
+++ b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/ReadOperations.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: f7a3550b60d04dc699c29b95d6f9b501
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/ReflectionExtensions.cs b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/ReflectionExtensions.cs
new file mode 100644
index 0000000..d146fd6
--- /dev/null
+++ b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/ReflectionExtensions.cs
@@ -0,0 +1,254 @@
+namespace SqlCipher4Unity3D.UniTaskIntegration.Extensions
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Collections.ObjectModel;
+ using System.Linq;
+ using System.Linq.Expressions;
+ using System.Reflection;
+ using Attributes;
+ using SQLite.Attributes;
+
+ public enum EnclosedType
+ {
+ None,
+ Array,
+ List,
+ ObservableCollection
+ }
+
+ public class ManyToManyMetaInfo
+ {
+ public Type IntermediateType { get; set; }
+ public PropertyInfo OriginProperty { get; set; }
+ public PropertyInfo DestinationProperty { get; set; }
+ }
+
+ public static class ReflectionExtensions
+ {
+ public static T GetAttribute(this Type type) where T : Attribute {
+ T attribute = null;
+ var attributes = (T[])type.GetTypeInfo().GetCustomAttributes(typeof(T), true);
+ if (attributes.Length > 0)
+ {
+ attribute = attributes[0];
+ }
+ return attribute;
+ }
+
+ public static T GetAttribute(this PropertyInfo property) where T : Attribute
+ {
+ T attribute = null;
+ var attributes = (T[])property.GetCustomAttributes(typeof(T), true);
+ if (attributes.Length > 0)
+ {
+ attribute = attributes[0];
+ }
+ return attribute;
+ }
+
+ public static Type GetEntityType(this PropertyInfo property, out EnclosedType enclosedType)
+ {
+ var type = property.PropertyType;
+ enclosedType = EnclosedType.None;
+
+ var typeInfo = type.GetTypeInfo();
+ if (type.IsArray)
+ {
+ type = type.GetElementType();
+ enclosedType = EnclosedType.Array;
+ }
+ else if (typeInfo.IsGenericType && typeof(List<>).GetTypeInfo().IsAssignableFrom(type.GetGenericTypeDefinition().GetTypeInfo()))
+ {
+ type = typeInfo.GenericTypeArguments[0];
+ enclosedType = EnclosedType.List;
+ }
+ else if (typeInfo.IsGenericType && typeof(ObservableCollection<>).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo().GetGenericTypeDefinition().GetTypeInfo()))
+ {
+ type = typeInfo.GenericTypeArguments[0];
+ enclosedType = EnclosedType.ObservableCollection;
+ }
+ return type;
+ }
+
+ public static object GetDefault(this Type type)
+ {
+ return type.GetTypeInfo().IsValueType ? Activator.CreateInstance(type) : null;
+ }
+
+ private static PropertyInfo GetExplicitForeignKeyProperty(this Type type, Type destinationType)
+ {
+ return (from property in type.GetRuntimeProperties() where property.IsPublicInstance()
+ let foreignKeyAttribute = property.GetAttribute()
+ where foreignKeyAttribute != null && foreignKeyAttribute.ForeignType.GetTypeInfo().IsAssignableFrom(destinationType.GetTypeInfo())
+ select property)
+ .FirstOrDefault();
+ }
+
+ private static PropertyInfo GetConventionForeignKeyProperty(this Type type, string destinationTypeName)
+ {
+ var conventionFormats = new List { "{0}Id", "{0}Key", "{0}ForeignKey" };
+
+ var conventionNames = conventionFormats.Select(format => string.Format(format, destinationTypeName)).ToList();
+
+ // No explicit declaration, search for convention names
+ return (from property in type.GetRuntimeProperties()
+ where property.IsPublicInstance() && conventionNames.Contains(property.Name, StringComparer.OrdinalIgnoreCase)
+ select property)
+ .FirstOrDefault();
+ }
+
+ public static PropertyInfo GetForeignKeyProperty(this Type type, PropertyInfo relationshipProperty, Type intermediateType = null, bool inverse = false)
+ {
+ PropertyInfo result;
+ var attribute = relationshipProperty.GetAttribute();
+ RelationshipAttribute inverseAttribute = null;
+
+ EnclosedType enclosedType;
+ var propertyType = relationshipProperty.GetEntityType(out enclosedType);
+
+ var originType = intermediateType ?? (inverse ? propertyType : type);
+ var destinationType = inverse ? type : propertyType;
+
+ // Inverse relationships may have the foreign key declared in the inverse property relationship attribute
+ var inverseProperty = type.GetInverseProperty(relationshipProperty);
+ if (inverseProperty != null)
+ {
+ inverseAttribute = inverseProperty.GetAttribute();
+ }
+
+ if (!inverse && !string.IsNullOrEmpty(attribute.ForeignKey))
+ {
+ // Explicitly declared foreign key name
+ result = originType.GetRuntimeProperty(attribute.ForeignKey);
+ }
+ else if (!inverse && inverseAttribute != null && !string.IsNullOrEmpty(inverseAttribute.InverseForeignKey))
+ {
+ // Explicitly declared inverse foreign key name in inverse property (double inverse refers to current entity foreign key)
+ result = originType.GetRuntimeProperty(inverseAttribute.InverseForeignKey);
+ }
+ else if (inverse && !string.IsNullOrEmpty(attribute.InverseForeignKey))
+ {
+ // Explicitly declared inverse foreign key name
+ result = originType.GetRuntimeProperty(attribute.InverseForeignKey);
+ }
+ else if (inverse && inverseAttribute != null && !string.IsNullOrEmpty(inverseAttribute.ForeignKey))
+ {
+ // Explicitly declared foreign key name in inverse property
+ result = originType.GetRuntimeProperty(inverseAttribute.ForeignKey);
+ }
+ else
+ {
+ // Explicitly declared attribute
+ result = originType.GetExplicitForeignKeyProperty(destinationType) ??
+ originType.GetConventionForeignKeyProperty(destinationType.Name);
+ }
+
+ return result;
+ }
+
+
+ public static PropertyInfo GetInverseProperty(this Type elementType, PropertyInfo property)
+ {
+
+ var attribute = property.GetAttribute();
+ if (attribute == null || (attribute.InverseProperty != null && attribute.InverseProperty.Equals("")))
+ {
+ // Relationship not reversible
+ return null;
+ }
+
+ EnclosedType enclosedType;
+ var propertyType = property.GetEntityType(out enclosedType);
+
+ PropertyInfo result = null;
+ if (attribute.InverseProperty != null)
+ {
+ result = propertyType.GetRuntimeProperty(attribute.InverseProperty);
+ }
+ else
+ {
+ var properties = (from p in propertyType.GetRuntimeProperties() where p.IsPublicInstance() select p);
+ foreach (var inverseProperty in properties)
+ {
+ var inverseAttribute = inverseProperty.GetAttribute();
+ EnclosedType enclosedInverseType;
+ var inverseType = inverseProperty.GetEntityType(out enclosedInverseType);
+ if (inverseAttribute != null && inverseType.GetTypeInfo().Equals(elementType.GetTypeInfo()))
+ {
+ result = inverseProperty;
+ break;
+ }
+ }
+ }
+
+ return result;
+ }
+
+ public static PropertyInfo GetProperty(Expression> expression) {
+ var type = typeof(T);
+ var body = expression.Body as MemberExpression;
+ // Debug.Assert(body != null, "Expression should be a property member expression");
+
+ var propertyName = body.Member.Name;
+ return type.GetRuntimeProperty(propertyName);
+ }
+
+ public static ManyToManyMetaInfo GetManyToManyMetaInfo(this Type type, PropertyInfo relationship)
+ {
+ var manyToManyAttribute = relationship.GetAttribute();
+ // Debug.Assert(manyToManyAttribute != null, "Unable to find ManyToMany attribute");
+
+ var intermediateType = manyToManyAttribute.IntermediateType;
+ var destinationKeyProperty = type.GetForeignKeyProperty(relationship, intermediateType);
+ var inverseKeyProperty = type.GetForeignKeyProperty(relationship, intermediateType, true);
+
+ return new ManyToManyMetaInfo
+ {
+ IntermediateType = intermediateType,
+ OriginProperty = inverseKeyProperty,
+ DestinationProperty = destinationKeyProperty
+ };
+ }
+
+ public static List GetRelationshipProperties(this Type type)
+ {
+ return (from property in type.GetRuntimeProperties()
+ where property.IsPublicInstance() && property.GetAttribute() != null
+ select property).ToList();
+ }
+
+ public static PropertyInfo GetPrimaryKey(this Type type)
+ {
+ return (from property in type.GetRuntimeProperties()
+ where property.IsPublicInstance() && property.GetAttribute() != null
+ select property).FirstOrDefault();
+ }
+
+ public static string GetTableName(this Type type) {
+ var tableName = type.Name;
+ var tableAttribute = type.GetAttribute();
+ if (tableAttribute != null && tableAttribute.Name != null)
+ tableName = tableAttribute.Name;
+
+ return tableName;
+ }
+
+ public static string GetColumnName(this PropertyInfo property) {
+ var column = property.Name;
+ var columnAttribute = property.GetAttribute();
+ if (columnAttribute != null && columnAttribute.Name != null)
+ column = columnAttribute.Name;
+
+ return column;
+ }
+
+ // Equivalent to old GetProperties(BindingFlags.Public | BindingFlags.Instance)
+ private static bool IsPublicInstance(this PropertyInfo propertyInfo)
+ {
+ return propertyInfo != null &&
+ ((propertyInfo.GetMethod != null && !propertyInfo.GetMethod.IsStatic && propertyInfo.GetMethod.IsPublic) &&
+ (propertyInfo.SetMethod != null && !propertyInfo.SetMethod.IsStatic && propertyInfo.SetMethod.IsPublic));
+ }
+ }
+}
\ No newline at end of file
diff --git a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/ReflectionExtensions.cs.meta b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/ReflectionExtensions.cs.meta
new file mode 100644
index 0000000..3fa39e3
--- /dev/null
+++ b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/ReflectionExtensions.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: eba6e03f0d6f48dc849505805fbddb4d
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/TextBlob.meta b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/TextBlob.meta
new file mode 100644
index 0000000..6f89fb7
--- /dev/null
+++ b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/TextBlob.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 63e5749fb28d4022b5e591cb6e6f679f
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/TextBlob/ITextBlobSerializer.cs b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/TextBlob/ITextBlobSerializer.cs
new file mode 100644
index 0000000..289c86a
--- /dev/null
+++ b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/TextBlob/ITextBlobSerializer.cs
@@ -0,0 +1,10 @@
+namespace SqlCipher4Unity3D.UniTaskIntegration.Extensions.TextBlob
+{
+ using System;
+
+ public interface ITextBlobSerializer
+ {
+ string Serialize(object element);
+ object Deserialize(string text, Type type);
+ }
+}
\ No newline at end of file
diff --git a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/TextBlob/ITextBlobSerializer.cs.meta b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/TextBlob/ITextBlobSerializer.cs.meta
new file mode 100644
index 0000000..b4367d7
--- /dev/null
+++ b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/TextBlob/ITextBlobSerializer.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 44158ca43fe441058a86d18033345def
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/TextBlob/Serializers.meta b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/TextBlob/Serializers.meta
new file mode 100644
index 0000000..7e9f7bc
--- /dev/null
+++ b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/TextBlob/Serializers.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: adce313dae1f4476bc4f8c52454d2372
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/TextBlob/Serializers/JsonBlobSerializer.cs b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/TextBlob/Serializers/JsonBlobSerializer.cs
new file mode 100644
index 0000000..005f92f
--- /dev/null
+++ b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/TextBlob/Serializers/JsonBlobSerializer.cs
@@ -0,0 +1,18 @@
+namespace SqlCipher4Unity3D.UniTaskIntegration.Extensions.TextBlob.Serializers
+{
+ using System;
+ using Newtonsoft.Json;
+
+ public class JsonBlobSerializer : ITextBlobSerializer
+ {
+ public string Serialize(object element)
+ {
+ return JsonConvert.SerializeObject(element);
+ }
+
+ public object Deserialize(string text, Type type)
+ {
+ return JsonConvert.DeserializeObject(text, type);
+ }
+ }
+}
\ No newline at end of file
diff --git a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/TextBlob/Serializers/JsonBlobSerializer.cs.meta b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/TextBlob/Serializers/JsonBlobSerializer.cs.meta
new file mode 100644
index 0000000..d8b0c95
--- /dev/null
+++ b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/TextBlob/Serializers/JsonBlobSerializer.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 2c86db91d41144c2ba600afdd1ec27a5
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/TextBlob/TextBlobOperations.cs b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/TextBlob/TextBlobOperations.cs
new file mode 100644
index 0000000..24f11d9
--- /dev/null
+++ b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/TextBlob/TextBlobOperations.cs
@@ -0,0 +1,56 @@
+namespace SqlCipher4Unity3D.UniTaskIntegration.Extensions.TextBlob
+{
+ using System.Reflection;
+ using Attributes;
+ using Serializers;
+
+ public static class TextBlobOperations
+ {
+ private static ITextBlobSerializer _serializer;
+
+ public static void SetTextSerializer(ITextBlobSerializer serializer)
+ {
+ _serializer = serializer;
+ }
+
+ public static ITextBlobSerializer GetTextSerializer()
+ {
+ // If not specified, use default JSON serializer
+ return _serializer ??= new JsonBlobSerializer();
+ }
+
+ public static void GetTextBlobChild(object element, PropertyInfo relationshipProperty)
+ {
+ var type = element.GetType();
+ var relationshipType = relationshipProperty.PropertyType;
+
+ // Debug.Assert(relationshipType != typeof(string), "TextBlob property is already a string");
+
+ var textblobAttribute = relationshipProperty.GetAttribute();
+ var textProperty = type.GetRuntimeProperty(textblobAttribute.TextProperty);
+ // Debug.Assert(textProperty != null && textProperty.PropertyType == typeof(string), "Text property for TextBlob relationship not found");
+
+ var textValue = (string)textProperty.GetValue(element, null);
+ var value = textValue != null ? GetTextSerializer().Deserialize(textValue, relationshipType) : null;
+
+ relationshipProperty.SetValue(element, value, null);
+ }
+
+ public static void UpdateTextBlobProperty(object element, PropertyInfo relationshipProperty)
+ {
+ var type = element.GetType();
+ var relationshipType = relationshipProperty.PropertyType;
+
+ // Debug.Assert(relationshipType != typeof(string), "TextBlob property is already a string");
+
+ var textblobAttribute = relationshipProperty.GetAttribute();
+ var textProperty = type.GetRuntimeProperty(textblobAttribute.TextProperty);
+ // Debug.Assert(textProperty != null && textProperty.PropertyType == typeof(string), "Text property for TextBlob relationship not found");
+
+ var value = relationshipProperty.GetValue(element, null);
+ var textValue = value != null ? GetTextSerializer().Serialize(value) : null;
+
+ textProperty.SetValue(element, textValue, null);
+ }
+ }
+}
\ No newline at end of file
diff --git a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/TextBlob/TextBlobOperations.cs.meta b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/TextBlob/TextBlobOperations.cs.meta
new file mode 100644
index 0000000..9a5bcf4
--- /dev/null
+++ b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/TextBlob/TextBlobOperations.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: cc4b6569787545e5b9f6f3759485f240
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/WriteOperations.cs b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/WriteOperations.cs
new file mode 100644
index 0000000..e24de27
--- /dev/null
+++ b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/WriteOperations.cs
@@ -0,0 +1,638 @@
+namespace SqlCipher4Unity3D.UniTaskIntegration.Extensions
+{
+ using System;
+ using System.Collections;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Reflection;
+ using Attributes;
+ using Exceptions;
+ using SqlCipher4Unity3D;
+ using SQLite.Attributes;
+ using TextBlob;
+
+ public static class WriteOperations
+ {
+ const int queryLimit = 990; //Make room for extra keys added by the code
+
+ ///
+ /// Enable to allow descriptive error descriptions on incorrect relationships. Enabled by default.
+ /// Disable for production environments to remove the checks and reduce performance penalty
+ ///
+ public static bool EnableRuntimeAssertions = true;
+
+ ///
+ /// Updates the with foreign keys of the current object and save changes to the database and
+ /// updates the inverse foreign keys of the defined relationships so the relationships are
+ /// stored correctly in the database. This operation will create or delete the required intermediate
+ /// objects for ManyToMany relationships. All related objects must have a primary key assigned in order
+ /// to work correctly. This also implies that any object with 'AutoIncrement' primary key must've been
+ /// inserted in the database previous to this call.
+ /// This method will also update inverse relationships of objects that currently exist in the object tree,
+ /// but it won't update inverse relationships of objects that are not reachable through this object
+ /// properties. For example, objects removed from a 'ToMany' relationship won't be updated in memory.
+ ///
+ /// SQLite Net connection object
+ /// Object to be updated. Must already have been inserted in the database
+ public static void UpdateWithChildren(this SQLiteConnection conn, object element)
+ {
+ // Update the current element
+ RefreshForeignKeys(element);
+ conn.Update(element);
+
+ // Update inverse foreign keys
+ conn.UpdateInverseForeignKeys(element);
+ }
+
+ ///
+ /// Inserts the element and all the relationships that are annotated with CascadeOperation.CascadeInsert
+ /// into the database. If any element already exists in the database a 'Constraint' exception will be raised.
+ /// Elements with a primary key that it's not AutoIncrement will need a valid identifier before calling
+ /// this method.
+ /// If the recursive flag is set to true, all the relationships annotated with
+ /// CascadeOperation.CascadeInsert are inserted recursively in the database. This method will handle
+ /// loops and inverse relationships correctly. ReadOnly properties will be omitted.
+ ///
+ /// SQLite Net connection object
+ /// Object to be inserted.
+ /// If set to true all the insert-cascade properties will be inserted
+ public static void InsertWithChildren(this SQLiteConnection conn, object element, bool recursive = false) {
+ conn.InsertWithChildrenRecursive(element, false, recursive);
+ }
+
+ ///
+ /// Inserts or replace the element and all the relationships that are annotated with
+ /// CascadeOperation.CascadeInsert into the database. If any element already exists in the database
+ /// it will be replaced. Elements with AutoIncrement primary keys that haven't been assigned will
+ /// be always inserted instead of replaced.
+ /// If the recursive flag is set to true, all the relationships annotated with
+ /// CascadeOperation.CascadeInsert are inserted recursively in the database. This method will handle
+ /// loops and inverse relationships correctly. ReadOnly properties will be omitted.
+ ///
+ /// SQLite Net connection object
+ /// Object to be inserted.
+ /// If set to true all the insert-cascade properties will be inserted
+ public static void InsertOrReplaceWithChildren(this SQLiteConnection conn, object element, bool recursive = false) {
+ conn.InsertWithChildrenRecursive(element, true, recursive);
+ }
+
+ ///
+ /// Inserts all the elements and all the relationships that are annotated with CascadeOperation.CascadeInsert
+ /// into the database. If any element already exists in the database a 'Constraint' exception will be raised.
+ /// Elements with a primary key that it's not AutoIncrement will need a valid identifier before calling
+ /// this method.
+ /// If the recursive flag is set to true, all the relationships annotated with
+ /// CascadeOperation.CascadeInsert are inserted recursively in the database. This method will handle
+ /// loops and inverse relationships correctly. ReadOnly properties will be omitted.
+ ///
+ /// SQLite Net connection object
+ /// Objects to be inserted.
+ /// If set to true all the insert-cascade properties will be inserted
+ public static void InsertAllWithChildren(this SQLiteConnection conn, IEnumerable elements, bool recursive = false) {
+ conn.InsertAllWithChildrenRecursive(elements, false, recursive);
+ }
+
+ ///
+ /// Inserts or replace all the elements and all the relationships that are annotated with
+ /// CascadeOperation.CascadeInsert into the database. If any element already exists in the database
+ /// it will be replaced. Elements with AutoIncrement primary keys that haven't been assigned will
+ /// be always inserted instead of replaced.
+ /// If the recursive flag is set to true, all the relationships annotated with
+ /// CascadeOperation.CascadeInsert are inserted recursively in the database. This method will handle
+ /// loops and inverse relationships correctly. ReadOnly properties will be omitted.
+ ///
+ /// SQLite Net connection object
+ /// Objects to be inserted.
+ /// If set to true all the insert-cascade properties will be inserted
+ public static void InsertOrReplaceAllWithChildren(this SQLiteConnection conn, IEnumerable elements, bool recursive = false) {
+ conn.InsertAllWithChildrenRecursive(elements, true, recursive);
+ }
+
+ ///
+ /// Deletes all the objects passed as parameters from the database.
+ /// If recursive flag is set to true, all relationships marked with 'CascadeDelete' will be
+ /// deleted from the database recursively. Inverse relationships and closed entity loops are handled
+ /// correctly to avoid endless loops
+ ///
+ /// SQLite Net connection object
+ /// If set to true all relationships marked with 'CascadeDelete' will be
+ /// deleted from the database recursively
+ /// Objects to be deleted from the database
+ public static void DeleteAll(this SQLiteConnection conn, IEnumerable objects, bool recursive = false) {
+ conn.DeleteAllRecursive(objects, recursive);
+ }
+
+ ///
+ /// Deletes all the objects passed as parameters from the database.
+ /// If recursive flag is set to true, all relationships marked with 'CascadeDelete' will be
+ /// deleted from the database recursively. Inverse relationships and closed entity loops are handled
+ /// correctly to avoid endless loops
+ ///
+ /// SQLite Net connection object
+ /// If set to true all relationships marked with 'CascadeDelete' will be
+ /// deleted from the database recursively
+ /// Object to be deleted from the database
+ public static void Delete(this SQLiteConnection conn, object element, bool recursive) {
+ if (recursive)
+ conn.DeleteAll(new []{ element }, recursive);
+ else
+ conn.Delete(element);
+ }
+
+ ///
+ /// Deletes all the objects passed with IDs equal to the passed parameters from the database.
+ /// Relationships are not taken into account in this method
+ ///
+ /// SQLite Net connection object
+ /// Primary keys of the objects to be deleted from the database
+ /// The Entity type, it should match de database entity type
+ public static void DeleteAllIds(this SQLiteConnection conn, IEnumerable primaryKeyValues) {
+ var type = typeof(T);
+ var primaryKeyProperty = type.GetPrimaryKey();
+
+ conn.DeleteAllIds(primaryKeyValues.ToArray(), type.GetTableName(), primaryKeyProperty.GetColumnName());
+ }
+
+
+ #region Private methods
+ static void InsertAllWithChildrenRecursive(this SQLiteConnection conn, IEnumerable elements, bool replace, bool recursive, ISet objectCache = null) {
+ if (elements == null)
+ return;
+
+ objectCache = objectCache ?? new HashSet();
+ var insertedElements = conn.InsertElements(elements, replace, objectCache).Cast().ToList();
+
+ foreach (var element in insertedElements) {
+ conn.InsertChildrenRecursive(element, replace, recursive, objectCache);
+ }
+
+ foreach (var element in insertedElements) {
+ conn.UpdateWithChildren(element);
+ }
+ }
+
+ static void InsertWithChildrenRecursive(this SQLiteConnection conn, object element, bool replace, bool recursive, ISet objectCache = null) {
+ objectCache = objectCache ?? new HashSet();
+ if (objectCache.Contains(element))
+ return;
+
+ conn.InsertElement(element, replace, objectCache);
+
+ objectCache.Add(element);
+ conn.InsertChildrenRecursive(element, replace, recursive, objectCache);
+
+ conn.UpdateWithChildren(element);
+ }
+
+ static void InsertChildrenRecursive(this SQLiteConnection conn, object element, bool replace, bool recursive, ISet objectCache = null) {
+ if (element == null)
+ return;
+
+ objectCache = objectCache ?? new HashSet();
+ foreach (var relationshipProperty in element.GetType().GetRelationshipProperties())
+ {
+ var relationshipAttribute = relationshipProperty.GetAttribute();
+
+ // Ignore read-only attributes and process only 'CascadeInsert' attributes
+ if (relationshipAttribute.ReadOnly || !relationshipAttribute.IsCascadeInsert)
+ continue;
+
+ var value = relationshipProperty.GetValue(element, null);
+ conn.InsertValue(value, replace, recursive, objectCache);
+ }
+ }
+
+ static void InsertValue(this SQLiteConnection conn, object value, bool replace, bool recursive, ISet objectCache) {
+ if (value == null)
+ return;
+
+ var enumerable = value as IEnumerable;
+ if (recursive)
+ {
+ if (enumerable != null)
+ conn.InsertAllWithChildrenRecursive(enumerable, replace, recursive, objectCache);
+ else
+ conn.InsertWithChildrenRecursive(value, replace, recursive, objectCache);
+ }
+ else
+ {
+ if (enumerable != null)
+ conn.InsertElements(enumerable, replace, objectCache);
+ else
+ conn.InsertElement(value, replace, objectCache);
+ }
+ }
+
+ static IEnumerable InsertElements(this SQLiteConnection conn, IEnumerable elements, bool replace, ISet objectCache) {
+ if (elements == null)
+ return Enumerable.Empty();
+
+ objectCache = objectCache ?? new HashSet();
+ var elementsToInsert = elements.Cast().Except(objectCache).ToList();
+ if (elementsToInsert.Count == 0)
+ return Enumerable.Empty();
+
+ var primaryKeyProperty = elementsToInsert[0].GetType().GetPrimaryKey();
+ var isAutoIncrementPrimaryKey = primaryKeyProperty != null && primaryKeyProperty.GetAttribute() != null;
+
+ foreach (var element in elementsToInsert) {
+ conn.InsertElement(element, replace, primaryKeyProperty, isAutoIncrementPrimaryKey, objectCache);
+ objectCache.Add(element);
+ }
+
+ return elementsToInsert;
+ }
+
+ static void InsertElement(this SQLiteConnection conn, object element, bool replace, ISet objectCache) {
+ var primaryKeyProperty = element.GetType().GetPrimaryKey();
+ var isAutoIncrementPrimaryKey = primaryKeyProperty != null && primaryKeyProperty.GetAttribute() != null;
+
+ conn.InsertElement(element, replace, primaryKeyProperty, isAutoIncrementPrimaryKey, objectCache);
+ }
+
+ static void InsertElement(this SQLiteConnection conn, object element, bool replace, PropertyInfo primaryKeyProperty, bool isAutoIncrementPrimaryKey, ISet objectCache) {
+ if (element == null || (objectCache != null && objectCache.Contains(element)))
+ return;
+
+ bool isPrimaryKeySet = false;
+ if (replace && isAutoIncrementPrimaryKey)
+ {
+ var primaryKeyValue = primaryKeyProperty.GetValue(element, null);
+ var defaultPrimaryKeyValue = primaryKeyProperty.PropertyType.GetDefault();
+ isPrimaryKeySet = primaryKeyValue != null && !primaryKeyValue.Equals(defaultPrimaryKeyValue);
+ }
+
+ bool shouldReplace = replace && (!isAutoIncrementPrimaryKey || isPrimaryKeySet);
+
+ // Only replace elements that have an assigned primary key
+ if (shouldReplace)
+ conn.InsertOrReplace(element);
+ else
+ conn.Insert(element);
+ }
+
+ static void DeleteAllRecursive(this SQLiteConnection conn, IEnumerable elements, bool recursive, ISet objectCache = null) {
+ if (elements == null)
+ return;
+
+ var isRootElement = objectCache == null;
+ objectCache = objectCache ?? new HashSet();
+
+ var elementList = elements.Cast().Except(objectCache).ToList();
+
+ // Mark the objects for deletion
+ foreach (var element in elementList)
+ objectCache.Add(element);
+
+ if (recursive)
+ {
+ foreach (var element in elementList)
+ {
+ var type = element.GetType();
+ foreach (var relationshipProperty in type.GetRelationshipProperties())
+ {
+ var relationshipAttribute = relationshipProperty.GetAttribute();
+
+ // Ignore read-only attributes or those that are not marked as CascadeDelete
+ if (!relationshipAttribute.IsCascadeDelete || relationshipAttribute.ReadOnly)
+ continue;
+
+ var value = relationshipProperty.GetValue(element, null);
+ conn.DeleteValueRecursive(value, recursive, objectCache);
+ }
+ }
+ }
+
+ // To improve performance, the root method call will delete all the objects at once
+ if (isRootElement) {
+ conn.DeleteAllObjects(objectCache);
+ }
+ }
+
+ static void DeleteValueRecursive(this SQLiteConnection conn, object value, bool recursive, ISet objectCache) {
+ if (value == null)
+ return;
+
+ var enumerable = value as IEnumerable ?? new [] { value };
+ conn.DeleteAllRecursive(enumerable, recursive, objectCache);
+ }
+
+ static void DeleteAllObjects(this SQLiteConnection conn, IEnumerable elements) {
+ if (elements == null)
+ return;
+
+ var groupedElements = elements.Cast().GroupBy(o => o.GetType());
+ foreach (var groupElement in groupedElements)
+ {
+ var type = groupElement.Key;
+ var primaryKeyProperty = type.GetPrimaryKey();
+ Assert(primaryKeyProperty != null, type, null, "Cannot delete objects without primary key");
+ var primaryKeyValues = (from element in groupElement
+ select primaryKeyProperty.GetValue(element, null)).ToArray();
+ conn.DeleteAllIds(primaryKeyValues, type.GetTableName(), primaryKeyProperty.GetColumnName());
+ }
+ }
+
+ private static void RefreshForeignKeys(object element)
+ {
+ var type = element.GetType();
+ foreach (var relationshipProperty in type.GetRelationshipProperties())
+ {
+ var relationshipAttribute = relationshipProperty.GetAttribute();
+
+ // Ignore read-only attributes
+ if (relationshipAttribute.ReadOnly)
+ continue;
+
+ if (relationshipAttribute is OneToOneAttribute || relationshipAttribute is ManyToOneAttribute)
+ {
+ var foreignKeyProperty = type.GetForeignKeyProperty(relationshipProperty);
+ if (foreignKeyProperty != null)
+ {
+ EnclosedType enclosedType;
+ var entityType = relationshipProperty.GetEntityType(out enclosedType);
+ var destinationPrimaryKeyProperty = entityType.GetPrimaryKey();
+ Assert(enclosedType == EnclosedType.None, type, relationshipProperty, "ToOne relationships cannot be lists or arrays");
+ Assert(destinationPrimaryKeyProperty != null, type, relationshipProperty, "Found foreign key but destination Type doesn't have primary key");
+
+ var relationshipValue = relationshipProperty.GetValue(element, null);
+ object foreignKeyValue = null;
+ if (relationshipValue != null)
+ {
+ foreignKeyValue = destinationPrimaryKeyProperty.GetValue(relationshipValue, null);
+ }
+ foreignKeyProperty.SetValue(element, foreignKeyValue, null);
+ }
+ }
+ else if (relationshipAttribute is TextBlobAttribute)
+ {
+ TextBlobOperations.UpdateTextBlobProperty(element, relationshipProperty);
+ }
+ }
+ }
+
+
+ private static void UpdateInverseForeignKeys(this SQLiteConnection conn, object element)
+ {
+ foreach (var relationshipProperty in element.GetType().GetRelationshipProperties())
+ {
+ var relationshipAttribute = relationshipProperty.GetAttribute();
+
+ // Ignore read-only attributes
+ if (relationshipAttribute.ReadOnly)
+ continue;
+
+ if (relationshipAttribute is OneToManyAttribute)
+ {
+ conn.UpdateOneToManyInverseForeignKey(element, relationshipProperty);
+ }
+ else if (relationshipAttribute is OneToOneAttribute)
+ {
+ conn.UpdateOneToOneInverseForeignKey(element, relationshipProperty);
+ }
+ else if (relationshipAttribute is ManyToManyAttribute)
+ {
+ conn.UpdateManyToManyForeignKeys(element, relationshipProperty);
+ }
+ }
+ }
+
+ private static void UpdateOneToManyInverseForeignKey(this SQLiteConnection conn, object element, PropertyInfo relationshipProperty)
+ {
+ var type = element.GetType();
+
+ EnclosedType enclosedType;
+ var entityType = relationshipProperty.GetEntityType(out enclosedType);
+
+ var originPrimaryKeyProperty = type.GetPrimaryKey();
+ var inversePrimaryKeyProperty = entityType.GetPrimaryKey();
+ var inverseForeignKeyProperty = type.GetForeignKeyProperty(relationshipProperty, inverse: true);
+
+ Assert(enclosedType != EnclosedType.None, type, relationshipProperty, "OneToMany relationships must be List or Array of entities");
+ Assert(originPrimaryKeyProperty != null, type, relationshipProperty, "OneToMany relationships require Primary Key in the origin entity");
+ Assert(inversePrimaryKeyProperty != null, type, relationshipProperty, "OneToMany relationships require Primary Key in the destination entity");
+ Assert(inverseForeignKeyProperty != null, type, relationshipProperty, "Unable to find foreign key for OneToMany relationship");
+
+ var inverseProperty = type.GetInverseProperty(relationshipProperty);
+ if (inverseProperty != null)
+ {
+ EnclosedType inverseEnclosedType;
+ var inverseEntityType = inverseProperty.GetEntityType(out inverseEnclosedType);
+ Assert(inverseEnclosedType == EnclosedType.None, type, relationshipProperty, "OneToMany inverse relationship shouldn't be List or Array");
+ Assert(inverseEntityType == type, type, relationshipProperty, "OneToMany inverse relationship is not the expected type");
+ }
+
+ var keyValue = originPrimaryKeyProperty.GetValue(element, null);
+ var children = (IEnumerable)relationshipProperty.GetValue(element, null);
+ var childrenKeyList = new List();
+ if (children != null)
+ {
+ foreach (var child in children)
+ {
+ var childKey = inversePrimaryKeyProperty.GetValue(child, null);
+ childrenKeyList.Add(childKey);
+
+ inverseForeignKeyProperty.SetValue(child, keyValue, null);
+ if (inverseProperty != null)
+ {
+ inverseProperty.SetValue(child, element, null);
+ }
+ }
+ }
+
+
+ // Delete previous relationships
+ var deleteQuery = string.Format("update [{0}] set [{1}] = NULL where [{1}] == ?",
+ entityType.GetTableName(), inverseForeignKeyProperty.GetColumnName());
+ var deleteParamaters = new List { keyValue };
+ conn.Execute(deleteQuery, deleteParamaters.ToArray());
+
+ var chunks = Split(childrenKeyList, queryLimit);
+ var loopTo = chunks.Count == 0 ? 1 : chunks.Count;
+ for (int i = 0; i < loopTo; i++) {
+ var chunk = chunks.Count > i ? chunks[i] :new List();
+ // Objects already updated, now change the database
+ var childrenPlaceHolders = string.Join(",", Enumerable.Repeat("?", chunk.Count));
+ var query = string.Format("update [{0}] set [{1}] = ? where [{2}] in ({3})",
+ entityType.GetTableName(), inverseForeignKeyProperty.GetColumnName(), inversePrimaryKeyProperty.GetColumnName(), childrenPlaceHolders);
+
+ var parameters = new List { keyValue };
+ parameters.AddRange(chunk);
+ conn.Execute(query, parameters.ToArray());
+ }
+ }
+
+ private static void UpdateOneToOneInverseForeignKey(this SQLiteConnection conn, object element, PropertyInfo relationshipProperty)
+ {
+ var type = element.GetType();
+
+ EnclosedType enclosedType;
+ var entityType = relationshipProperty.GetEntityType(out enclosedType);
+
+ var originPrimaryKeyProperty = type.GetPrimaryKey();
+ var inversePrimaryKeyProperty = entityType.GetPrimaryKey();
+ var inverseForeignKeyProperty = type.GetForeignKeyProperty(relationshipProperty, inverse: true);
+
+ Assert(enclosedType == EnclosedType.None, type, relationshipProperty, "OneToOne relationships cannot be List or Array of entities");
+
+ var inverseProperty = type.GetInverseProperty(relationshipProperty);
+ if (inverseProperty != null)
+ {
+ EnclosedType inverseEnclosedType;
+ var inverseEntityType = inverseProperty.GetEntityType(out inverseEnclosedType);
+ Assert(inverseEnclosedType == EnclosedType.None, type, relationshipProperty, "OneToOne inverse relationship shouldn't be List or Array");
+ Assert(inverseEntityType == type, type, relationshipProperty, "OneToOne inverse relationship is not the expected type");
+ }
+
+ object keyValue = null;
+ if (originPrimaryKeyProperty != null && inverseForeignKeyProperty != null)
+ {
+ keyValue = originPrimaryKeyProperty.GetValue(element, null);
+ }
+
+ object childKey = null;
+ var child = relationshipProperty.GetValue(element, null);
+ if (child != null)
+ {
+ if (inverseForeignKeyProperty != null && keyValue != null)
+ {
+ inverseForeignKeyProperty.SetValue(child, keyValue, null);
+ }
+ if (inverseProperty != null)
+ {
+ inverseProperty.SetValue(child, element, null);
+ }
+ if (inversePrimaryKeyProperty != null)
+ {
+ childKey = inversePrimaryKeyProperty.GetValue(child, null);
+ }
+ }
+
+
+ // Objects already updated, now change the database
+ if (inverseForeignKeyProperty != null && inversePrimaryKeyProperty != null)
+ {
+ var query = string.Format("update [{0}] set [{1}] = ? where [{2}] == ?",
+ entityType.GetTableName(), inverseForeignKeyProperty.GetColumnName(), inversePrimaryKeyProperty.GetColumnName());
+ conn.Execute(query, keyValue, childKey);
+
+ // Delete previous relationships
+ var deleteQuery = string.Format("update [{0}] set [{1}] = NULL where [{1}] == ? and [{2}] not in (?)",
+ entityType.GetTableName(), inverseForeignKeyProperty.GetColumnName(), inversePrimaryKeyProperty.GetColumnName());
+ conn.Execute(deleteQuery, keyValue, childKey ?? "");
+ }
+ }
+
+ private static void UpdateManyToManyForeignKeys(this SQLiteConnection conn, object element, PropertyInfo relationshipProperty)
+ {
+ var type = element.GetType();
+
+ EnclosedType enclosedType;
+ var entityType = relationshipProperty.GetEntityType(out enclosedType);
+
+ var currentEntityPrimaryKeyProperty = type.GetPrimaryKey();
+ var otherEntityPrimaryKeyProperty = entityType.GetPrimaryKey();
+ var manyToManyMetaInfo = type.GetManyToManyMetaInfo(relationshipProperty);
+ var currentEntityForeignKeyProperty = manyToManyMetaInfo.OriginProperty;
+ var otherEntityForeignKeyProperty = manyToManyMetaInfo.DestinationProperty;
+ var intermediateType = manyToManyMetaInfo.IntermediateType;
+
+ Assert(enclosedType != EnclosedType.None, type, relationshipProperty, "ManyToMany relationship must be a List or Array");
+ Assert(currentEntityPrimaryKeyProperty != null, type, relationshipProperty, "ManyToMany relationship origin must have Primary Key");
+ Assert(otherEntityPrimaryKeyProperty != null, type, relationshipProperty, "ManyToMany relationship destination must have Primary Key");
+ Assert(intermediateType != null, type, relationshipProperty, "ManyToMany relationship intermediate type cannot be null");
+ Assert(currentEntityForeignKeyProperty != null, type, relationshipProperty, "ManyToMany relationship origin must have a foreign key defined in the intermediate type");
+ Assert(otherEntityForeignKeyProperty != null, type, relationshipProperty, "ManyToMany relationship destination must have a foreign key defined in the intermediate type");
+
+ var primaryKey = currentEntityPrimaryKeyProperty.GetValue(element, null);
+
+ // Obtain the list of children keys
+ var childList = (IEnumerable)relationshipProperty.GetValue(element, null);
+ var childKeyList = (from object child in childList ?? new List()
+ select otherEntityPrimaryKeyProperty.GetValue(child, null)).ToList();
+
+ List currentChildKeyList = new List();
+ var chunks = Split(childKeyList, queryLimit);
+ var loopTo = chunks.Count == 0 ? 1 : chunks.Count;
+ for (int i = 0; i < loopTo; i++) {
+ var chunk = chunks.Count > i ? chunks[i] : new List();
+ // Check for already existing relationships
+ var childrenPlaceHolders = string.Join(",", Enumerable.Repeat("?", chunk.Count));
+ var currentChildrenQuery = string.Format("select [{0}] from [{1}] where [{2}] == ? and [{0}] in ({3})",
+ otherEntityForeignKeyProperty.GetColumnName(), intermediateType.GetTableName(), currentEntityForeignKeyProperty.GetColumnName(), childrenPlaceHolders);
+
+ var parameters = new List { primaryKey };
+ parameters.AddRange(chunk);
+ currentChildKeyList.AddRange(
+ from object child in
+ conn.Query(conn.GetMapping(intermediateType), currentChildrenQuery, parameters.ToArray())
+ select otherEntityForeignKeyProperty.GetValue(child, null));
+ }
+
+ // Insert missing relationships in the intermediate table
+ var missingChildKeyList = childKeyList.Where(o => !currentChildKeyList.Contains(o)).ToList();
+ var missingIntermediateObjects = new List(missingChildKeyList.Count);
+ foreach (var missingChildKey in missingChildKeyList)
+ {
+ var intermediateObject = Activator.CreateInstance(intermediateType);
+ currentEntityForeignKeyProperty.SetValue(intermediateObject, primaryKey, null);
+ otherEntityForeignKeyProperty.SetValue(intermediateObject, missingChildKey, null);
+
+ missingIntermediateObjects.Add(intermediateObject);
+ }
+
+ conn.InsertAll(missingIntermediateObjects);
+
+
+
+ for (int i = 0; i < loopTo; i++)
+ {
+ var chunk = chunks.Count > i ? chunks[i] : new List();
+ var childrenPlaceHolders = string.Join(",", Enumerable.Repeat("?", chunk.Count));
+
+ // Delete any other pending relationship
+ var deleteQuery = string.Format("delete from [{0}] where [{1}] == ? and [{2}] not in ({3})",
+ intermediateType.GetTableName(), currentEntityForeignKeyProperty.GetColumnName(),
+ otherEntityForeignKeyProperty.GetColumnName(), childrenPlaceHolders);
+
+ var parameters = new List { primaryKey };
+ parameters.AddRange(chunk);
+ conn.Execute(deleteQuery, parameters.ToArray());
+ }
+
+ }
+
+ private static void DeleteAllIds(this SQLiteConnection conn, object[] primaryKeyValues, string entityName, string primaryKeyName) {
+ if (primaryKeyValues == null || primaryKeyValues.Length == 0)
+ return;
+
+ if (primaryKeyValues.Length <= queryLimit)
+ {
+ var placeholdersString = string.Join(",", Enumerable.Repeat("?", primaryKeyValues.Length));
+ var deleteQuery = string.Format("delete from [{0}] where [{1}] in ({2})", entityName, primaryKeyName, placeholdersString);
+
+ conn.Execute(deleteQuery, primaryKeyValues);
+ }
+ else {
+ foreach (var primaryKeys in Split(primaryKeyValues.ToList(), queryLimit)) {
+ conn.DeleteAllIds(primaryKeys.ToArray(), entityName, primaryKeyName);
+ }
+
+ }
+ }
+
+ static List> Split(List items, int sliceSize = 30)
+ {
+ List> list = new List>();
+ for (int i = 0; i < items.Count; i += sliceSize)
+ list.Add(items.GetRange(i, Math.Min(sliceSize, items.Count - i)));
+ return list;
+ }
+
+ static void Assert(bool assertion, Type type, PropertyInfo property, string message) {
+ if (EnableRuntimeAssertions && !assertion)
+ throw new IncorrectRelationshipException(type.Name, property != null ? property.Name : string.Empty , message);
+ }
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/WriteOperations.cs.meta b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/WriteOperations.cs.meta
new file mode 100644
index 0000000..44f57d2
--- /dev/null
+++ b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/Extensions/WriteOperations.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 62ef0acd4a6442679d5fa4c4e602157c
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/SQLiteAsyncUnitask.cs b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/SQLiteAsyncUnitask.cs
new file mode 100644
index 0000000..b76414b
--- /dev/null
+++ b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/UniTaskIntegration/SQLiteAsyncUnitask.cs
@@ -0,0 +1 @@
+#if SQLITEASYNC_UNITASK
namespace SqlCipher4Unity3D.UniTaskIntegration
{
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading;
using Cysharp.Threading.Tasks;
///
/// A pooled asynchronous connection to a SQLite database.
///
public partial class SQLiteAsyncConnection
{
readonly SQLiteConnectionString _connectionString;
// NOTE(pyoung): added for SqlCipher4Unity3D
public SQLiteAsyncConnection(string databasePath, string password, bool storeDateTimeAsTicks = true)
: this(new SQLiteConnectionString(databasePath, SQLiteOpenFlags.Create | SQLiteOpenFlags.ReadWrite | SQLiteOpenFlags.FullMutex, storeDateTimeAsTicks, password))
{
}
///
/// Constructs a new SQLiteAsyncConnection and opens a pooled SQLite database specified by databasePath.
///
///
/// Specifies the path to the database file.
///
///
/// Specifies whether to store DateTime properties as ticks (true) or strings (false). You
/// absolutely do want to store them as Ticks in all new projects. The value of false is
/// only here for backwards compatibility. There is a *significant* speed advantage, with no
/// down sides, when setting storeDateTimeAsTicks = true.
/// If you use DateTimeOffset properties, it will be always stored as ticks regardingless
/// the storeDateTimeAsTicks parameter.
///
public SQLiteAsyncConnection(string databasePath, bool storeDateTimeAsTicks = true)
: this(new SQLiteConnectionString(databasePath, SQLiteOpenFlags.Create | SQLiteOpenFlags.ReadWrite | SQLiteOpenFlags.FullMutex, storeDateTimeAsTicks))
{
}
///
/// Constructs a new SQLiteAsyncConnection and opens a pooled SQLite database specified by databasePath.
///
///
/// Specifies the path to the database file.
///
///
/// Flags controlling how the connection should be opened.
/// Async connections should have the FullMutex flag set to provide best performance.
///
///
/// Specifies whether to store DateTime properties as ticks (true) or strings (false). You
/// absolutely do want to store them as Ticks in all new projects. The value of false is
/// only here for backwards compatibility. There is a *significant* speed advantage, with no
/// down sides, when setting storeDateTimeAsTicks = true.
/// If you use DateTimeOffset properties, it will be always stored as ticks regardingless
/// the storeDateTimeAsTicks parameter.
///
public SQLiteAsyncConnection(string databasePath, SQLiteOpenFlags openFlags, bool storeDateTimeAsTicks = true)
: this(new SQLiteConnectionString(databasePath, openFlags, storeDateTimeAsTicks))
{
}
///
/// Constructs a new SQLiteAsyncConnection and opens a pooled SQLite database
/// using the given connection string.
///
///
/// Details on how to find and open the database.
///
public SQLiteAsyncConnection(SQLiteConnectionString connectionString)
{
_connectionString = connectionString;
}
///
/// Gets the database path used by this connection.
///
public string DatabasePath => GetConnection().DatabasePath;
///
/// Gets the SQLite library version number. 3007014 would be v3.7.14
///
public int LibVersionNumber => GetConnection().LibVersionNumber;
///
/// The format to use when storing DateTime properties as strings. Ignored if StoreDateTimeAsTicks is true.
///
/// The date time string format.
public string DateTimeStringFormat => GetConnection().DateTimeStringFormat;
///
/// The amount of time to wait for a table to become unlocked.
///
public TimeSpan GetBusyTimeout()
{
return GetConnection().BusyTimeout;
}
///
/// Sets the amount of time to wait for a table to become unlocked.
///
public UniTask SetBusyTimeoutAsync(TimeSpan value)
{
return ReadAsync(conn =>
{
conn.BusyTimeout = value;
return null;
});
}
///
/// Enables the write ahead logging. WAL is significantly faster in most scenarios
/// by providing better concurrency and better disk IO performance than the normal
/// journal mode. You only need to call this function once in the lifetime of the database.
///
public UniTask EnableWriteAheadLoggingAsync()
{
return WriteAsync(conn =>
{
conn.EnableWriteAheadLogging();
return null;
});
}
///
/// Whether to store DateTime properties as ticks (true) or strings (false).
///
public bool StoreDateTimeAsTicks => GetConnection().StoreDateTimeAsTicks;
///
/// Whether to store TimeSpan properties as ticks (true) or strings (false).
///
public bool StoreTimeSpanAsTicks => GetConnection().StoreTimeSpanAsTicks;
///
/// Whether to writer queries to during execution.
///
/// The tracer.
public bool Trace
{
get { return GetConnection().Trace; }
set { GetConnection().Trace = value; }
}
///
/// The delegate responsible for writing trace lines.
///
/// The tracer.
public Action Tracer
{
get { return GetConnection().Tracer; }
set { GetConnection().Tracer = value; }
}
///
/// Whether Trace lines should be written that show the execution time of queries.
///
public bool TimeExecution
{
get { return GetConnection().TimeExecution; }
set { GetConnection().TimeExecution = value; }
}
///
/// Returns the mappings from types to tables that the connection
/// currently understands.
///
public IEnumerable TableMappings => GetConnection().TableMappings;
///
/// Closes all connections to all async databases.
/// You should *never* need to do this.
/// This is a blocking operation that will return when all connections
/// have been closed.
///
public static void ResetPool()
{
SQLiteConnectionPool.Shared.Reset();
}
///
/// Gets the pooled lockable connection used by this async connection.
/// You should never need to use this. This is provided only to add additional
/// functionality to SQLite-net. If you use this connection, you must use
/// the Lock method on it while using it.
///
public SQLiteConnectionWithLock GetConnection()
{
return SQLiteConnectionPool.Shared.GetConnection(_connectionString);
}
SQLiteConnectionWithLock GetConnectionAndTransactionLock(out object transactionLock)
{
return SQLiteConnectionPool.Shared.GetConnectionAndTransactionLock(_connectionString, out transactionLock);
}
///
/// Closes any pooled connections used by the database.
///
public UniTask CloseAsync()
{
return UniTask.RunOnThreadPool(() => SQLiteConnectionPool.Shared.CloseConnection (_connectionString), true, CancellationToken.None);
}
UniTask ReadAsync(Func read)
{
return UniTask.RunOnThreadPool(() =>
{
var conn = GetConnection();
using (conn.Lock())
{
return read(conn);
}
}, false, CancellationToken.None);
}
UniTask WriteAsync(Func write)
{
return UniTask.RunOnThreadPool(() =>
{
var conn = GetConnection();
using (conn.Lock())
{
return write(conn);
}
},false, CancellationToken.None);
}
UniTask TransactAsync(Func transact)
{
return UniTask.RunOnThreadPool(() =>
{
var conn = GetConnectionAndTransactionLock(out var transactionLock);
lock (transactionLock)
{
using (conn.Lock())
{
return transact(conn);
}
}
},false, CancellationToken.None);
}
///
/// Enable or disable extension loading.
///
public UniTask EnableLoadExtensionAsync(bool enabled)
{
return WriteAsync(conn =>
{
conn.EnableLoadExtension(enabled);
return null;
});
}
///
/// Executes a "create table if not exists" on the database. It also
/// creates any specified indexes on the columns of the table. It uses
/// a schema automatically generated from the specified type. You can
/// later access this schema by calling GetMapping.
///
///
/// Whether the table was created or migrated.
///
public UniTask CreateTableAsync(CreateFlags createFlags = CreateFlags.None)
where T : new()
{
return WriteAsync(conn => conn.CreateTable(createFlags));
}
///
/// Executes a "create table if not exists" on the database. It also
/// creates any specified indexes on the columns of the table. It uses
/// a schema automatically generated from the specified type. You can
/// later access this schema by calling GetMapping.
///
/// Type to reflect to a database table.
/// Optional flags allowing implicit PK and indexes based on naming conventions.
///
/// Whether the table was created or migrated.
///
public UniTask CreateTableAsync(Type ty, CreateFlags createFlags = CreateFlags.None)
{
return WriteAsync(conn => conn.CreateTable(ty, createFlags));
}
///
/// Executes a "create table if not exists" on the database for each type. It also
/// creates any specified indexes on the columns of the table. It uses
/// a schema automatically generated from the specified type. You can
/// later access this schema by calling GetMapping.
///
///
/// Whether the table was created or migrated for each type.
///
public UniTask CreateTablesAsync(CreateFlags createFlags = CreateFlags.None)
where T : new()
where T2 : new()
{
return CreateTablesAsync(createFlags, typeof(T), typeof(T2));
}
///
/// Executes a "create table if not exists" on the database for each type. It also
/// creates any specified indexes on the columns of the table. It uses
/// a schema automatically generated from the specified type. You can
/// later access this schema by calling GetMapping.
///
///
/// Whether the table was created or migrated for each type.
///
public UniTask CreateTablesAsync(CreateFlags createFlags = CreateFlags.None)
where T : new()
where T2 : new()
where T3 : new()
{
return CreateTablesAsync(createFlags, typeof(T), typeof(T2), typeof(T3));
}
///
/// Executes a "create table if not exists" on the database for each type. It also
/// creates any specified indexes on the columns of the table. It uses
/// a schema automatically generated from the specified type. You can
/// later access this schema by calling GetMapping.
///
///
/// Whether the table was created or migrated for each type.
///
public UniTask CreateTablesAsync(CreateFlags createFlags = CreateFlags.None)
where T : new()
where T2 : new()
where T3 : new()
where T4 : new()
{
return CreateTablesAsync(createFlags, typeof(T), typeof(T2), typeof(T3), typeof(T4));
}
///
/// Executes a "create table if not exists" on the database for each type. It also
/// creates any specified indexes on the columns of the table. It uses
/// a schema automatically generated from the specified type. You can
/// later access this schema by calling GetMapping.
///
///
/// Whether the table was created or migrated for each type.
///
public UniTask CreateTablesAsync(CreateFlags createFlags = CreateFlags.None)
where T : new()
where T2 : new()
where T3 : new()
where T4 : new()
where T5 : new()
{
return CreateTablesAsync(createFlags, typeof(T), typeof(T2), typeof(T3), typeof(T4), typeof(T5));
}
///
/// Executes a "create table if not exists" on the database for each type. It also
/// creates any specified indexes on the columns of the table. It uses
/// a schema automatically generated from the specified type. You can
/// later access this schema by calling GetMapping.
///
///
/// Whether the table was created or migrated for each type.
///
public UniTask CreateTablesAsync(CreateFlags createFlags = CreateFlags.None, params Type[] types)
{
return WriteAsync(conn => conn.CreateTables(createFlags, types));
}
///
/// Executes a "drop table" on the database. This is non-recoverable.
///
public UniTask DropTableAsync()
where T : new()
{
return WriteAsync(conn => conn.DropTable());
}
///
/// Executes a "drop table" on the database. This is non-recoverable.
///
///
/// The TableMapping used to identify the table.
///
public UniTask DropTableAsync(TableMapping map)
{
return WriteAsync(conn => conn.DropTable(map));
}
///
/// Creates an index for the specified table and column.
///
/// Name of the database table
/// Name of the column to index
/// Whether the index should be unique
/// Zero on success.
public UniTask CreateIndexAsync(string tableName, string columnName, bool unique = false)
{
return WriteAsync(conn => conn.CreateIndex(tableName, columnName, unique));
}
///
/// Creates an index for the specified table and column.
///
/// Name of the index to create
/// Name of the database table
/// Name of the column to index
/// Whether the index should be unique
/// Zero on success.
public UniTask CreateIndexAsync(string indexName, string tableName, string columnName, bool unique = false)
{
return WriteAsync(conn => conn.CreateIndex(indexName, tableName, columnName, unique));
}
///
/// Creates an index for the specified table and columns.
///
/// Name of the database table
/// An array of column names to index
/// Whether the index should be unique
/// Zero on success.
public UniTask CreateIndexAsync(string tableName, string[] columnNames, bool unique = false)
{
return WriteAsync(conn => conn.CreateIndex(tableName, columnNames, unique));
}
///
/// Creates an index for the specified table and columns.
///
/// Name of the index to create
/// Name of the database table
/// An array of column names to index
/// Whether the index should be unique
/// Zero on success.
public UniTask CreateIndexAsync(string indexName, string tableName, string[] columnNames, bool unique = false)
{
return WriteAsync(conn => conn.CreateIndex(indexName, tableName, columnNames, unique));
}
///
/// Creates an index for the specified object property.
/// e.g. CreateIndex<Client>(c => c.Name);
///
/// Type to reflect to a database table.
/// Property to index
/// Whether the index should be unique
/// Zero on success.
public UniTask CreateIndexAsync(Expression> property, bool unique = false)
{
return WriteAsync(conn => conn.CreateIndex(property, unique));
}
///
/// Inserts the given object and retrieves its
/// auto incremented primary key if it has one.
///
///
/// The object to insert.
///
///
/// The number of rows added to the table.
///
public UniTask InsertAsync(object obj)
{
return WriteAsync(conn => conn.Insert(obj));
}
///
/// Inserts the given object (and updates its
/// auto incremented primary key if it has one).
/// The return value is the number of rows added to the table.
///
///
/// The object to insert.
///
///
/// The type of object to insert.
///
///
/// The number of rows added to the table.
///
public UniTask InsertAsync(object obj, Type objType)
{
return WriteAsync(conn => conn.Insert(obj, objType));
}
///
/// Inserts the given object (and updates its
/// auto incremented primary key if it has one).
/// The return value is the number of rows added to the table.
///
///
/// The object to insert.
///
///
/// Literal SQL code that gets placed into the command. INSERT {extra} INTO ...
///
///
/// The number of rows added to the table.
///
public UniTask InsertAsync(object obj, string extra)
{
return WriteAsync(conn => conn.Insert(obj, extra));
}
///
/// Inserts the given object (and updates its
/// auto incremented primary key if it has one).
/// The return value is the number of rows added to the table.
///
///
/// The object to insert.
///
///
/// Literal SQL code that gets placed into the command. INSERT {extra} INTO ...
///
///
/// The type of object to insert.
///
///
/// The number of rows added to the table.
///
public UniTask InsertAsync(object obj, string extra, Type objType)
{
return WriteAsync(conn => conn.Insert(obj, extra, objType));
}
///
/// Inserts the given object (and updates its
/// auto incremented primary key if it has one).
/// The return value is the number of rows added to the table.
/// If a UNIQUE constraint violation occurs with
/// some pre-existing object, this function deletes
/// the old object.
///
///
/// The object to insert.
///
///
/// The number of rows modified.
///
public UniTask InsertOrReplaceAsync(object obj)
{
return WriteAsync(conn => conn.InsertOrReplace(obj));
}
///
/// Inserts the given object (and updates its
/// auto incremented primary key if it has one).
/// The return value is the number of rows added to the table.
/// If a UNIQUE constraint violation occurs with
/// some pre-existing object, this function deletes
/// the old object.
///
///
/// The object to insert.
///
///
/// The type of object to insert.
///
///
/// The number of rows modified.
///
public UniTask InsertOrReplaceAsync(object obj, Type objType)
{
return WriteAsync(conn => conn.InsertOrReplace(obj, objType));
}
///
/// Updates all of the columns of a table using the specified object
/// except for its primary key.
/// The object is required to have a primary key.
///
///
/// The object to update. It must have a primary key designated using the PrimaryKeyAttribute.
///
///
/// The number of rows updated.
///
public UniTask UpdateAsync(object obj)
{
return WriteAsync(conn => conn.Update(obj));
}
///
/// Updates all of the columns of a table using the specified object
/// except for its primary key.
/// The object is required to have a primary key.
///
///
/// The object to update. It must have a primary key designated using the PrimaryKeyAttribute.
///
///
/// The type of object to insert.
///
///
/// The number of rows updated.
///
public UniTask UpdateAsync(object obj, Type objType)
{
return WriteAsync(conn => conn.Update(obj, objType));
}
///
/// Updates all specified objects.
///
///
/// An of the objects to insert.
///
///
/// A boolean indicating if the inserts should be wrapped in a transaction
///
///
/// The number of rows modified.
///
public UniTask UpdateAllAsync(IEnumerable objects, bool runInTransaction = true)
{
return WriteAsync(conn => conn.UpdateAll(objects, runInTransaction));
}
///
/// Deletes the given object from the database using its primary key.
///
///
/// The object to delete. It must have a primary key designated using the PrimaryKeyAttribute.
///
///
/// The number of rows deleted.
///
public UniTask DeleteAsync(object objectToDelete)
{
return WriteAsync(conn => conn.Delete(objectToDelete));
}
///
/// Deletes the object with the specified primary key.
///
///
/// The primary key of the object to delete.
///
///
/// The number of objects deleted.
///
///
/// The type of object.
///
public UniTask DeleteAsync(object primaryKey)
{
return WriteAsync(conn => conn.Delete(primaryKey));
}
///
/// Deletes the object with the specified primary key.
///
///
/// The primary key of the object to delete.
///
///
/// The TableMapping used to identify the table.
///
///
/// The number of objects deleted.
///
public UniTask DeleteAsync(object primaryKey, TableMapping map)
{
return WriteAsync(conn => conn.Delete(primaryKey, map));
}
///
/// Deletes all the objects from the specified table.
/// WARNING WARNING: Let me repeat. It deletes ALL the objects from the
/// specified table. Do you really want to do that?
///
///
/// The number of objects deleted.
///
///
/// The type of objects to delete.
///
public UniTask DeleteAllAsync()
{
return WriteAsync(conn => conn.DeleteAll());
}
///
/// Deletes all the objects from the specified table.
/// WARNING WARNING: Let me repeat. It deletes ALL the objects from the
/// specified table. Do you really want to do that?
///
///
/// The TableMapping used to identify the table.
///
///
/// The number of objects deleted.
///
public UniTask DeleteAllAsync(TableMapping map)
{
return WriteAsync(conn => conn.DeleteAll(map));
}
///
/// Backup the entire database to the specified path.
///
/// Path to backup file.
/// The name of the database to backup (usually "main").
public UniTask BackupAsync(string destinationDatabasePath, string databaseName = "main")
{
return WriteAsync(conn =>
{
conn.Backup(destinationDatabasePath, databaseName);
return 0;
});
}
///
/// Attempts to retrieve an object with the given primary key from the table
/// associated with the specified type. Use of this method requires that
/// the given type have a designated PrimaryKey (using the PrimaryKeyAttribute).
///
///
/// The primary key.
///
///
/// The object with the given primary key. Throws a not found exception
/// if the object is not found.
///
public UniTask GetAsync(object pk)
where T : new()
{
return ReadAsync(conn => conn.Get(pk));
}
///
/// Attempts to retrieve an object with the given primary key from the table
/// associated with the specified type. Use of this method requires that
/// the given type have a designated PrimaryKey (using the PrimaryKeyAttribute).
///
///
/// The primary key.
///
///
/// The TableMapping used to identify the table.
///
///
/// The object with the given primary key. Throws a not found exception
/// if the object is not found.
///
public UniTask GetAsync(object pk, TableMapping map)
{
return ReadAsync(conn => conn.Get(pk, map));
}
///
/// Attempts to retrieve the first object that matches the predicate from the table
/// associated with the specified type.
///
///
/// A predicate for which object to find.
///
///
/// The object that matches the given predicate. Throws a not found exception
/// if the object is not found.
///
public UniTask GetAsync(Expression> predicate)
where T : new()
{
return ReadAsync(conn => conn.Get(predicate));
}
///
/// Attempts to retrieve an object with the given primary key from the table
/// associated with the specified type. Use of this method requires that
/// the given type have a designated PrimaryKey (using the PrimaryKeyAttribute).
///
///
/// The primary key.
///
///
/// The object with the given primary key or null
/// if the object is not found.
///
public UniTask FindAsync(object pk)
where T : new()
{
return ReadAsync(conn => conn.Find(pk));
}
///
/// Attempts to retrieve an object with the given primary key from the table
/// associated with the specified type. Use of this method requires that
/// the given type have a designated PrimaryKey (using the PrimaryKeyAttribute).
///
///
/// The primary key.
///
///
/// The TableMapping used to identify the table.
///
///
/// The object with the given primary key or null
/// if the object is not found.
///
public UniTask FindAsync(object pk, TableMapping map)
{
return ReadAsync(conn => conn.Find(pk, map));
}
///
/// Attempts to retrieve the first object that matches the predicate from the table
/// associated with the specified type.
///
///
/// A predicate for which object to find.
///
///
/// The object that matches the given predicate or null
/// if the object is not found.
///
public UniTask FindAsync(Expression> predicate)
where T : new()
{
return ReadAsync(conn => conn.Find(predicate));
}
///
/// Attempts to retrieve the first object that matches the query from the table
/// associated with the specified type.
///
///
/// The fully escaped SQL.
///
///
/// Arguments to substitute for the occurences of '?' in the query.
///
///
/// The object that matches the given predicate or null
/// if the object is not found.
///
public UniTask FindWithQueryAsync(string query, params object[] args)
where T : new()
{
return ReadAsync(conn => conn.FindWithQuery(query, args));
}
///
/// Attempts to retrieve the first object that matches the query from the table
/// associated with the specified type.
///
///
/// The TableMapping used to identify the table.
///
///
/// The fully escaped SQL.
///
///
/// Arguments to substitute for the occurences of '?' in the query.
///
///
/// The object that matches the given predicate or null
/// if the object is not found.
///
public UniTask FindWithQueryAsync(TableMapping map, string query, params object[] args)
{
return ReadAsync(conn => conn.FindWithQuery(map, query, args));
}
///
/// Retrieves the mapping that is automatically generated for the given type.
///
///
/// The type whose mapping to the database is returned.
///
///
/// Optional flags allowing implicit PK and indexes based on naming conventions
///
///
/// The mapping represents the schema of the columns of the database and contains
/// methods to set and get properties of objects.
///
public UniTask GetMappingAsync(Type type, CreateFlags createFlags = CreateFlags.None)
{
return ReadAsync(conn => conn.GetMapping(type, createFlags));
}
///
/// Retrieves the mapping that is automatically generated for the given type.
///
///
/// Optional flags allowing implicit PK and indexes based on naming conventions
///
///
/// The mapping represents the schema of the columns of the database and contains
/// methods to set and get properties of objects.
///
public UniTask GetMappingAsync(CreateFlags createFlags = CreateFlags.None)
where T : new()
{
return ReadAsync(conn => conn.GetMapping(createFlags));
}
///
/// Query the built-in sqlite table_info table for a specific tables columns.
///
/// The columns contains in the table.
/// Table name.
public UniTask> GetTableInfoAsync(string tableName)
{
return ReadAsync(conn => conn.GetTableInfo(tableName));
}
///
/// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?'
/// in the command text for each of the arguments and then executes that command.
/// Use this method instead of Query when you don't expect rows back. Such cases include
/// INSERTs, UPDATEs, and DELETEs.
/// You can set the Trace or TimeExecution properties of the connection
/// to profile execution.
///
///
/// The fully escaped SQL.
///
///
/// Arguments to substitute for the occurences of '?' in the query.
///
///
/// The number of rows modified in the database as a result of this execution.
///
public UniTask ExecuteAsync(string query, params object[] args)
{
return WriteAsync(conn => conn.Execute(query, args));
}
///
/// Inserts all specified objects.
///
///
/// An of the objects to insert.
///
/// A boolean indicating if the inserts should be wrapped in a transaction.
///
///
/// The number of rows added to the table.
///
public UniTask InsertAllAsync(IEnumerable objects, bool runInTransaction = true)
{
return WriteAsync(conn => conn.InsertAll(objects, runInTransaction));
}
///
/// Inserts all specified objects.
///
///
/// An of the objects to insert.
///
///
/// Literal SQL code that gets placed into the command. INSERT {extra} INTO ...
///
///
/// A boolean indicating if the inserts should be wrapped in a transaction.
///
///
/// The number of rows added to the table.
///
public UniTask InsertAllAsync(IEnumerable objects, string extra, bool runInTransaction = true)
{
return WriteAsync(conn => conn.InsertAll(objects, extra, runInTransaction));
}
///
/// Inserts all specified objects.
///
///
/// An of the objects to insert.
///
///
/// The type of object to insert.
///
///
/// A boolean indicating if the inserts should be wrapped in a transaction.
///
///
/// The number of rows added to the table.
///
public UniTask InsertAllAsync(IEnumerable objects, Type objType, bool runInTransaction = true)
{
return WriteAsync(conn => conn.InsertAll(objects, objType, runInTransaction));
}
///
/// Executes within a (possibly nested) transaction by wrapping it in a SAVEPOINT. If an
/// exception occurs the whole transaction is rolled back, not just the current savepoint. The exception
/// is rethrown.
///
///
/// The to perform within a transaction. can contain any number
/// of operations on the connection but should never call or
/// .
///
public UniTask RunInTransactionAsync(Action action)
{
return TransactAsync(conn =>
{
conn.BeginTransaction();
try
{
action(conn);
conn.Commit();
return null;
}
catch (Exception)
{
conn.Rollback();
throw;
}
});
}
///
/// Returns a queryable interface to the table represented by the given type.
///
///
/// A queryable object that is able to translate Where, OrderBy, and Take
/// queries into native SQL.
///
public AsyncTableQuery Table()
where T : new()
{
//
// This isn't async as the underlying connection doesn't go out to the database
// until the query is performed. The Async methods are on the query itself.
//
var conn = GetConnection();
return new AsyncTableQuery(conn.Table());
}
///
/// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?'
/// in the command text for each of the arguments and then executes that command.
/// Use this method when return primitive values.
/// You can set the Trace or TimeExecution properties of the connection
/// to profile execution.
///
///
/// The fully escaped SQL.
///
///
/// Arguments to substitute for the occurences of '?' in the query.
///
///
/// The number of rows modified in the database as a result of this execution.
///
public UniTask ExecuteScalarAsync(string query, params object[] args)
{
return WriteAsync(conn =>
{
var command = conn.CreateCommand(query, args);
return command.ExecuteScalar();
});
}
///
/// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?'
/// in the command text for each of the arguments and then executes that command.
/// It returns each row of the result using the mapping automatically generated for
/// the given type.
///
///
/// The fully escaped SQL.
///
///
/// Arguments to substitute for the occurences of '?' in the query.
///
///
/// A list with one result for each row returned by the query.
///
public UniTask> QueryAsync(string query, params object[] args)
where T : new()
{
return ReadAsync(conn => conn.Query(query, args));
}
///
/// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?'
/// in the command text for each of the arguments and then executes that command.
/// It returns the first column of each row of the result.
///
///
/// The fully escaped SQL.
///
///
/// Arguments to substitute for the occurences of '?' in the query.
///
///
/// A list with one result for the first column of each row returned by the query.
///
public UniTask> QueryScalarsAsync(string query, params object[] args)
{
return ReadAsync(conn => conn.QueryScalars(query, args));
}
///
/// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?'
/// in the command text for each of the arguments and then executes that command.
/// It returns each row of the result using the specified mapping. This function is
/// only used by libraries in order to query the database via introspection. It is
/// normally not used.
///
///
/// A to use to convert the resulting rows
/// into objects.
///
///
/// The fully escaped SQL.
///
///
/// Arguments to substitute for the occurences of '?' in the query.
///
///
/// An enumerable with one result for each row returned by the query.
///
public UniTask> QueryAsync(TableMapping map, string query, params object[] args)
{
return ReadAsync(conn => conn.Query(map, query, args));
}
///
/// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?'
/// in the command text for each of the arguments and then executes that command.
/// It returns each row of the result using the mapping automatically generated for
/// the given type.
///
///
/// The fully escaped SQL.
///
///
/// Arguments to substitute for the occurences of '?' in the query.
///
///
/// An enumerable with one result for each row returned by the query.
/// The enumerator will call sqlite3_step on each call to MoveNext, so the database
/// connection must remain open for the lifetime of the enumerator.
///
public UniTask> DeferredQueryAsync(string query, params object[] args)
where T : new()
{
return ReadAsync(conn => (IEnumerable)conn.DeferredQuery(query, args).ToList());
}
///
/// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?'
/// in the command text for each of the arguments and then executes that command.
/// It returns each row of the result using the specified mapping. This function is
/// only used by libraries in order to query the database via introspection. It is
/// normally not used.
///
///
/// A to use to convert the resulting rows
/// into objects.
///
///
/// The fully escaped SQL.
///
///
/// Arguments to substitute for the occurences of '?' in the query.
///
///
/// An enumerable with one result for each row returned by the query.
/// The enumerator will call sqlite3_step on each call to MoveNext, so the database
/// connection must remain open for the lifetime of the enumerator.
///
public UniTask> DeferredQueryAsync(TableMapping map, string query, params object[] args)
{
return ReadAsync(conn => (IEnumerable)conn.DeferredQuery(map, query, args).ToList());
}
}
///
/// Query to an asynchronous database connection.
///
public class AsyncTableQuery
where T : new()
{
TableQuery _innerQuery;
///
/// Creates a new async query that uses given the synchronous query.
///
public AsyncTableQuery(TableQuery innerQuery)
{
_innerQuery = innerQuery;
}
UniTask ReadAsync(Func read)
{
return UniTask.RunOnThreadPool(() =>
{
var conn = (SQLiteConnectionWithLock)_innerQuery.Connection;
using (conn.Lock())
{
return read(conn);
}
}, false, CancellationToken.None);
}
UniTask WriteAsync(Func write)
{
return UniTask.RunOnThreadPool(() =>
{
var conn = (SQLiteConnectionWithLock)_innerQuery.Connection;
using (conn.Lock())
{
return write(conn);
}
}, false, CancellationToken.None);
}
///
/// Filters the query based on a predicate.
///
public AsyncTableQuery Where(Expression> predExpr)
{
return new AsyncTableQuery(_innerQuery.Where(predExpr));
}
///
/// Skips a given number of elements from the query and then yields the remainder.
///
public AsyncTableQuery Skip(int n)
{
return new AsyncTableQuery(_innerQuery.Skip(n));
}
///
/// Yields a given number of elements from the query and then skips the remainder.
///
public AsyncTableQuery Take(int n)
{
return new AsyncTableQuery(_innerQuery.Take(n));
}
///
/// Order the query results according to a key.
///
public AsyncTableQuery OrderBy(Expression> orderExpr)
{
return new AsyncTableQuery(_innerQuery.OrderBy(orderExpr));
}
///
/// Order the query results according to a key.
///
public AsyncTableQuery OrderByDescending(Expression> orderExpr)
{
return new AsyncTableQuery(_innerQuery.OrderByDescending(orderExpr));
}
///
/// Order the query results according to a key.
///
public AsyncTableQuery ThenBy(Expression> orderExpr)
{
return new AsyncTableQuery