diff --git a/.gitignore b/.gitignore index 9554bc2..f68c05c 100644 --- a/.gitignore +++ b/.gitignore @@ -20,4 +20,7 @@ *.pidb.meta # Unity3D Generated File On Crash Reports -sysinfo.txt \ No newline at end of file +sysinfo.txt + +.DS_Store +.idea/ diff --git a/README.md b/README.md index a551751..f1d57c7 100644 --- a/README.md +++ b/README.md @@ -17,18 +17,28 @@ # Watchout * If You are on Windows, need to `libeay32.dll` from https://wiki.openssl.org/index.php/Binaries * If You are on iOS, need to modify [link.xml](https://docs.unity3d.com/Manual/iphone-playerSizeOptimization.html) for prevent stripping by Unity. +* Check [Issues](https://github.com/netpyoung/SqlCipher4Unity3D/issues) # The fast track All you have to do to start using it in your project: -1. [Download this .unitypackage](https://github.com/netpyoung/SqlCipher4Unity3D/raw/master/SqlCipher4Unity3D.unitypackage), extract its content on your Unity3D Project. It contains the dlls that Unity3d will need to access sqlite. +1. [Download this .unitypackage](https://github.com/netpyoung/SqlCipher4Unity3D/raw/master/SqlCipher4Unity3D-v1.0.0.unitypackage), extract its content on your Unity3D Project. It contains the dlls that Unity3d will need to access sqlite. 4. **You’re done!** +# Sqlchipher lib version +* android : 3.5.9 + - https://www.zetetic.net/sqlcipher/sqlcipher-for-android/ + - precompiled : https://github.com/sqlcipher/sqlcipher-android-tests/tree/master/app/libs + + + # Example If you want to try it I've uploaded a small example that you will be able to find in the "Example" folder. Download the folder and open it with Unity3d to give it a try. It contains classes that will help you to start. +# codefix +https://github.com/Leopotam/vscode-csharpfixformat # Acknowledgements This project is based on the work of: diff --git a/SqlCipher4Unity3D.unitypackage b/SqlCipher4Unity3D-v1.0.0.unitypackage similarity index 52% rename from SqlCipher4Unity3D.unitypackage rename to SqlCipher4Unity3D-v1.0.0.unitypackage index ace659a..4a030d3 100644 Binary files a/SqlCipher4Unity3D.unitypackage and b/SqlCipher4Unity3D-v1.0.0.unitypackage differ diff --git a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/Plugins/Android/libs/x86.meta b/SqlCipher4Unity3D/Assets/Plugins.meta similarity index 60% rename from SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/Plugins/Android/libs/x86.meta rename to SqlCipher4Unity3D/Assets/Plugins.meta index dcdd9b7..6bfb4bb 100644 --- a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/Plugins/Android/libs/x86.meta +++ b/SqlCipher4Unity3D/Assets/Plugins.meta @@ -1,9 +1,10 @@ fileFormatVersion: 2 -guid: 127f0aaf74b9540b4ad306d147930d41 +guid: b1b1fa6bb2d7a404a94caf3899990815 folderAsset: yes -timeCreated: 1474990725 +timeCreated: 1529076095 licenseType: Free DefaultImporter: + externalObjects: {} userData: assetBundleName: assetBundleVariant: diff --git a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/Plugins/Android/libs/armeabi-v7a.meta b/SqlCipher4Unity3D/Assets/Plugins/Editor.meta similarity index 60% rename from SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/Plugins/Android/libs/armeabi-v7a.meta rename to SqlCipher4Unity3D/Assets/Plugins/Editor.meta index f834ad2..d11d2bf 100644 --- a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/Plugins/Android/libs/armeabi-v7a.meta +++ b/SqlCipher4Unity3D/Assets/Plugins/Editor.meta @@ -1,9 +1,10 @@ fileFormatVersion: 2 -guid: 6b2e0bb4f78bd4b2ca44c545632b0699 +guid: f4a705c98a92b40c7b46d4396b3c71b3 folderAsset: yes -timeCreated: 1474993115 +timeCreated: 1529076095 licenseType: Free DefaultImporter: + externalObjects: {} userData: assetBundleName: assetBundleVariant: diff --git a/SqlCipher4Unity3D/Assets/example/Scenes.meta b/SqlCipher4Unity3D/Assets/Plugins/Editor/JetBrains.meta similarity index 60% rename from SqlCipher4Unity3D/Assets/example/Scenes.meta rename to SqlCipher4Unity3D/Assets/Plugins/Editor/JetBrains.meta index df90c30..45d3071 100644 --- a/SqlCipher4Unity3D/Assets/example/Scenes.meta +++ b/SqlCipher4Unity3D/Assets/Plugins/Editor/JetBrains.meta @@ -1,9 +1,10 @@ fileFormatVersion: 2 -guid: b8ab4eabea1d94b49808216caa3ed5d0 +guid: 50395a360f242446aa26c190bebebe26 folderAsset: yes -timeCreated: 1453576960 +timeCreated: 1529076095 licenseType: Free DefaultImporter: + externalObjects: {} userData: assetBundleName: assetBundleVariant: diff --git a/SqlCipher4Unity3D/Assets/Plugins/Editor/JetBrains/JetBrains.Rider.Unity.Editor.Plugin.Repacked.dll b/SqlCipher4Unity3D/Assets/Plugins/Editor/JetBrains/JetBrains.Rider.Unity.Editor.Plugin.Repacked.dll new file mode 100644 index 0000000..7a5211f Binary files /dev/null and b/SqlCipher4Unity3D/Assets/Plugins/Editor/JetBrains/JetBrains.Rider.Unity.Editor.Plugin.Repacked.dll differ diff --git a/SqlCipher4Unity3D/Assets/Plugins/Editor/JetBrains/JetBrains.Rider.Unity.Editor.Plugin.Repacked.dll.meta b/SqlCipher4Unity3D/Assets/Plugins/Editor/JetBrains/JetBrains.Rider.Unity.Editor.Plugin.Repacked.dll.meta new file mode 100644 index 0000000..51a257e --- /dev/null +++ b/SqlCipher4Unity3D/Assets/Plugins/Editor/JetBrains/JetBrains.Rider.Unity.Editor.Plugin.Repacked.dll.meta @@ -0,0 +1,32 @@ +fileFormatVersion: 2 +guid: 1e4b845353e8f4746a99f67bd4576954 +timeCreated: 1529076122 +licenseType: Free +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + isPreloaded: 0 + isOverridable: 0 + platformData: + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 1 + settings: + DefaultValueInitialized: true + - first: + Windows Store Apps: WindowsStoreApps + second: + enabled: 0 + settings: + CPU: AnyCPU + userData: + assetBundleName: + assetBundleVariant: diff --git a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/Plugins/.DS_Store b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/Plugins/.DS_Store new file mode 100644 index 0000000..0b9632c Binary files /dev/null and b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/Plugins/.DS_Store differ diff --git a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/Plugins/Android/.DS_Store b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/Plugins/Android/.DS_Store new file mode 100644 index 0000000..8e08b6a Binary files /dev/null and b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/Plugins/Android/.DS_Store differ diff --git a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/Plugins/Android/libs/android-database-sqlcipher-3.5.9.aar b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/Plugins/Android/libs/android-database-sqlcipher-3.5.9.aar new file mode 100644 index 0000000..dbe692a Binary files /dev/null and b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/Plugins/Android/libs/android-database-sqlcipher-3.5.9.aar differ diff --git a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/Plugins/Android/libs/android-database-sqlcipher-3.5.9.aar.meta b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/Plugins/Android/libs/android-database-sqlcipher-3.5.9.aar.meta new file mode 100644 index 0000000..34d7532 --- /dev/null +++ b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/Plugins/Android/libs/android-database-sqlcipher-3.5.9.aar.meta @@ -0,0 +1,31 @@ +fileFormatVersion: 2 +guid: 19954fdde7bc74f71b4faa2b958e62eb +timeCreated: 1529075790 +licenseType: Free +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + isPreloaded: 0 + isOverridable: 0 + platformData: + - first: + Android: Android + second: + enabled: 1 + settings: {} + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + DefaultValueInitialized: true + userData: + assetBundleName: + assetBundleVariant: diff --git a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/Plugins/Android/libs/armeabi-v7a/libsqlcipher.so b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/Plugins/Android/libs/armeabi-v7a/libsqlcipher.so deleted file mode 100755 index 3a66a2f..0000000 Binary files a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/Plugins/Android/libs/armeabi-v7a/libsqlcipher.so and /dev/null differ diff --git a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/Plugins/Android/libs/armeabi-v7a/libsqlcipher.so.meta b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/Plugins/Android/libs/armeabi-v7a/libsqlcipher.so.meta deleted file mode 100644 index c546f05..0000000 --- a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/Plugins/Android/libs/armeabi-v7a/libsqlcipher.so.meta +++ /dev/null @@ -1,55 +0,0 @@ -fileFormatVersion: 2 -guid: f933ca8a3455e488495ae127ffbe92a3 -timeCreated: 1474993115 -licenseType: Free -PluginImporter: - serializedVersion: 1 - iconMap: {} - executionOrder: {} - isPreloaded: 0 - platformData: - Android: - enabled: 1 - settings: - CPU: ARMv7 - Any: - enabled: 0 - settings: {} - Editor: - enabled: 0 - settings: - CPU: AnyCPU - DefaultValueInitialized: true - OS: AnyOS - Linux: - enabled: 0 - settings: - CPU: x86 - Linux64: - enabled: 0 - settings: - CPU: x86_64 - OSXIntel: - enabled: 0 - settings: - CPU: AnyCPU - OSXIntel64: - enabled: 0 - settings: - CPU: AnyCPU - Win: - enabled: 0 - settings: - CPU: AnyCPU - Win64: - enabled: 0 - settings: - CPU: AnyCPU - iOS: - enabled: 0 - settings: - CompileFlags: - FrameworkDependencies: - userData: - assetBundleName: - assetBundleVariant: diff --git a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/Plugins/Android/libs/x86/libsqlcipher.so b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/Plugins/Android/libs/x86/libsqlcipher.so deleted file mode 100755 index fe16038..0000000 Binary files a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/Plugins/Android/libs/x86/libsqlcipher.so and /dev/null differ diff --git a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/Plugins/Android/libs/x86/libsqlcipher.so.meta b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/Plugins/Android/libs/x86/libsqlcipher.so.meta deleted file mode 100644 index 6b661f3..0000000 --- a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/Plugins/Android/libs/x86/libsqlcipher.so.meta +++ /dev/null @@ -1,55 +0,0 @@ -fileFormatVersion: 2 -guid: 301805b21c6614d5b82c1d5b9a17f965 -timeCreated: 1474990725 -licenseType: Free -PluginImporter: - serializedVersion: 1 - iconMap: {} - executionOrder: {} - isPreloaded: 0 - platformData: - Android: - enabled: 1 - settings: - CPU: x86 - Any: - enabled: 0 - settings: {} - Editor: - enabled: 0 - settings: - CPU: AnyCPU - DefaultValueInitialized: true - OS: AnyOS - Linux: - enabled: 0 - settings: - CPU: x86 - Linux64: - enabled: 0 - settings: - CPU: x86_64 - OSXIntel: - enabled: 0 - settings: - CPU: AnyCPU - OSXIntel64: - enabled: 0 - settings: - CPU: AnyCPU - Win: - enabled: 0 - settings: - CPU: AnyCPU - Win64: - enabled: 0 - settings: - CPU: AnyCPU - iOS: - enabled: 0 - settings: - CompileFlags: - FrameworkDependencies: - userData: - assetBundleName: - assetBundleVariant: diff --git a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/Sqlite3.cs b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/Sqlite3.cs index 822c5ff..e931e56 100644 --- a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/Sqlite3.cs +++ b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/Sqlite3.cs @@ -1,491 +1,280 @@ -// -// Copyright (c) 2009-2012 Krueger Systems, Inc. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// - -#if WINDOWS_PHONE && !USE_WP8_NATIVE_SQLITE -#define USE_CSHARP_SQLITE -#endif - -using System; -using System.Diagnostics; using System.Runtime.InteropServices; -using System.Collections.Generic; -using System.Reflection; -using System.Linq; -using System.Linq.Expressions; -using System.Threading; - -#if USE_CSHARP_SQLITE -using Sqlite3 = Community.CsharpSqlite.Sqlite3; -using Sqlite3DatabaseHandle = Community.CsharpSqlite.Sqlite3.sqlite3; -using Sqlite3Statement = Community.CsharpSqlite.Sqlite3.Vdbe; -#elif USE_WP8_NATIVE_SQLITE -using Sqlite3 = Sqlite.Sqlite3; -using Sqlite3DatabaseHandle = Sqlite.Database; -using Sqlite3Statement = Sqlite.Statement; -#else +using System.Text; using Sqlite3DatabaseHandle = System.IntPtr; using Sqlite3Statement = System.IntPtr; -#endif - namespace SqlCipher4Unity3D { - public static partial class SQLite3 - { - public enum Result : int { - OK = 0, - Error = 1, - Internal = 2, - Perm = 3, - Abort = 4, - Busy = 5, - Locked = 6, - NoMem = 7, - ReadOnly = 8, - Interrupt = 9, - IOError = 10, - Corrupt = 11, - NotFound = 12, - Full = 13, - CannotOpen = 14, - LockErr = 15, - Empty = 16, - SchemaChngd = 17, - TooBig = 18, - Constraint = 19, - Mismatch = 20, - Misuse = 21, - NotImplementedLFS = 22, - AccessDenied = 23, - Format = 24, - Range = 25, - NonDBFile = 26, - Notice = 27, - Warning = 28, - Row = 100, - Done = 101 - } - - public enum ExtendedResult : int { - IOErrorRead = (Result.IOError | (1 << 8)), - IOErrorShortRead = (Result.IOError | (2 << 8)), - IOErrorWrite = (Result.IOError | (3 << 8)), - IOErrorFsync = (Result.IOError | (4 << 8)), - IOErrorDirFSync = (Result.IOError | (5 << 8)), - IOErrorTruncate = (Result.IOError | (6 << 8)), - IOErrorFStat = (Result.IOError | (7 << 8)), - IOErrorUnlock = (Result.IOError | (8 << 8)), - IOErrorRdlock = (Result.IOError | (9 << 8)), - IOErrorDelete = (Result.IOError | (10 << 8)), - IOErrorBlocked = (Result.IOError | (11 << 8)), - IOErrorNoMem = (Result.IOError | (12 << 8)), - IOErrorAccess = (Result.IOError | (13 << 8)), - IOErrorCheckReservedLock = (Result.IOError | (14 << 8)), - IOErrorLock = (Result.IOError | (15 << 8)), - IOErrorClose = (Result.IOError | (16 << 8)), - IOErrorDirClose = (Result.IOError | (17 << 8)), - IOErrorSHMOpen = (Result.IOError | (18 << 8)), - IOErrorSHMSize = (Result.IOError | (19 << 8)), - IOErrorSHMLock = (Result.IOError | (20 << 8)), - IOErrorSHMMap = (Result.IOError | (21 << 8)), - IOErrorSeek = (Result.IOError | (22 << 8)), - IOErrorDeleteNoEnt = (Result.IOError | (23 << 8)), - IOErrorMMap = (Result.IOError | (24 << 8)), - LockedSharedcache = (Result.Locked | (1 << 8)), - BusyRecovery = (Result.Busy | (1 << 8)), - CannottOpenNoTempDir = (Result.CannotOpen | (1 << 8)), - CannotOpenIsDir = (Result.CannotOpen | (2 << 8)), - CannotOpenFullPath = (Result.CannotOpen | (3 << 8)), - CorruptVTab = (Result.Corrupt | (1 << 8)), - ReadonlyRecovery = (Result.ReadOnly | (1 << 8)), - ReadonlyCannotLock = (Result.ReadOnly | (2 << 8)), - ReadonlyRollback = (Result.ReadOnly | (3 << 8)), - AbortRollback = (Result.Abort | (2 << 8)), - ConstraintCheck = (Result.Constraint | (1 << 8)), - ConstraintCommitHook = (Result.Constraint | (2 << 8)), - ConstraintForeignKey = (Result.Constraint | (3 << 8)), - ConstraintFunction = (Result.Constraint | (4 << 8)), - ConstraintNotNull = (Result.Constraint | (5 << 8)), - ConstraintPrimaryKey = (Result.Constraint | (6 << 8)), - ConstraintTrigger = (Result.Constraint | (7 << 8)), - ConstraintUnique = (Result.Constraint | (8 << 8)), - ConstraintVTab = (Result.Constraint | (9 << 8)), - NoticeRecoverWAL = (Result.Notice | (1 << 8)), - NoticeRecoverRollback = (Result.Notice | (2 << 8)) - } - - - public enum ConfigOption : int { - SingleThread = 1, - MultiThread = 2, - Serialized = 3 - } - - public enum ColType : int { - Integer = 1, - Float = 2, - Text = 3, - Blob = 4, - Null = 5 - } - } - - - public static partial class SQLite3 { + public static partial class SQLite3 + { + public enum ColType + { + Integer = 1, + Float = 2, + Text = 3, + Blob = 4, + Null = 5 + } + + public enum ConfigOption + { + SingleThread = 1, + MultiThread = 2, + Serialized = 3 + } + + public enum ExtendedResult + { + IOErrorRead = Result.IOError | (1 << 8), + IOErrorShortRead = Result.IOError | (2 << 8), + IOErrorWrite = Result.IOError | (3 << 8), + IOErrorFsync = Result.IOError | (4 << 8), + IOErrorDirFSync = Result.IOError | (5 << 8), + IOErrorTruncate = Result.IOError | (6 << 8), + IOErrorFStat = Result.IOError | (7 << 8), + IOErrorUnlock = Result.IOError | (8 << 8), + IOErrorRdlock = Result.IOError | (9 << 8), + IOErrorDelete = Result.IOError | (10 << 8), + IOErrorBlocked = Result.IOError | (11 << 8), + IOErrorNoMem = Result.IOError | (12 << 8), + IOErrorAccess = Result.IOError | (13 << 8), + IOErrorCheckReservedLock = Result.IOError | (14 << 8), + IOErrorLock = Result.IOError | (15 << 8), + IOErrorClose = Result.IOError | (16 << 8), + IOErrorDirClose = Result.IOError | (17 << 8), + IOErrorSHMOpen = Result.IOError | (18 << 8), + IOErrorSHMSize = Result.IOError | (19 << 8), + IOErrorSHMLock = Result.IOError | (20 << 8), + IOErrorSHMMap = Result.IOError | (21 << 8), + IOErrorSeek = Result.IOError | (22 << 8), + IOErrorDeleteNoEnt = Result.IOError | (23 << 8), + IOErrorMMap = Result.IOError | (24 << 8), + LockedSharedcache = Result.Locked | (1 << 8), + BusyRecovery = Result.Busy | (1 << 8), + CannottOpenNoTempDir = Result.CannotOpen | (1 << 8), + CannotOpenIsDir = Result.CannotOpen | (2 << 8), + CannotOpenFullPath = Result.CannotOpen | (3 << 8), + CorruptVTab = Result.Corrupt | (1 << 8), + ReadonlyRecovery = Result.ReadOnly | (1 << 8), + ReadonlyCannotLock = Result.ReadOnly | (2 << 8), + ReadonlyRollback = Result.ReadOnly | (3 << 8), + AbortRollback = Result.Abort | (2 << 8), + ConstraintCheck = Result.Constraint | (1 << 8), + ConstraintCommitHook = Result.Constraint | (2 << 8), + ConstraintForeignKey = Result.Constraint | (3 << 8), + ConstraintFunction = Result.Constraint | (4 << 8), + ConstraintNotNull = Result.Constraint | (5 << 8), + ConstraintPrimaryKey = Result.Constraint | (6 << 8), + ConstraintTrigger = Result.Constraint | (7 << 8), + ConstraintUnique = Result.Constraint | (8 << 8), + ConstraintVTab = Result.Constraint | (9 << 8), + NoticeRecoverWAL = Result.Notice | (1 << 8), + NoticeRecoverRollback = Result.Notice | (2 << 8) + } + + public enum Result + { + OK = 0, + Error = 1, + Internal = 2, + Perm = 3, + Abort = 4, + Busy = 5, + Locked = 6, + NoMem = 7, + ReadOnly = 8, + Interrupt = 9, + IOError = 10, + Corrupt = 11, + NotFound = 12, + Full = 13, + CannotOpen = 14, + LockErr = 15, + Empty = 16, + SchemaChngd = 17, + TooBig = 18, + Constraint = 19, + Mismatch = 20, + Misuse = 21, + NotImplementedLFS = 22, + AccessDenied = 23, + Format = 24, + Range = 25, + NonDBFile = 26, + Notice = 27, + Warning = 28, + Row = 100, + Done = 101 + } + } + + public static partial class SQLite3 + { #if UNITY_EDITOR - const string DLL_NAME = "sqlcipher"; + private const string DLL_NAME = "sqlcipher"; #elif UNITY_ANDROID const string DLL_NAME = "sqlcipher"; #elif UNITY_IOS const string DLL_NAME = "__Internal"; #endif -#if !USE_CSHARP_SQLITE && !USE_WP8_NATIVE_SQLITE - - [DllImport(DLL_NAME, EntryPoint = "sqlite3_open", CallingConvention=CallingConvention.Cdecl)] - public static extern Result Open ([MarshalAs(UnmanagedType.LPStr)] string filename, out IntPtr db); - - [DllImport(DLL_NAME, EntryPoint = "sqlite3_open_v2", CallingConvention=CallingConvention.Cdecl)] - public static extern Result Open ([MarshalAs(UnmanagedType.LPStr)] string filename, out IntPtr db, int flags, IntPtr zvfs); - - [DllImport(DLL_NAME, EntryPoint = "sqlite3_open_v2", CallingConvention = CallingConvention.Cdecl)] - public static extern Result Open(byte[] filename, out IntPtr db, int flags, IntPtr zvfs); - - [DllImport(DLL_NAME, EntryPoint = "sqlite3_key", CallingConvention = CallingConvention.Cdecl)] - public static extern Result Key(IntPtr db, [MarshalAs(UnmanagedType.LPStr)]string key, int keylen); - - [DllImport(DLL_NAME, EntryPoint = "sqlite3_open16", CallingConvention = CallingConvention.Cdecl)] - public static extern Result Open16([MarshalAs(UnmanagedType.LPWStr)] string filename, out IntPtr db); - - [DllImport(DLL_NAME, EntryPoint = "sqlite3_enable_load_extension", CallingConvention=CallingConvention.Cdecl)] - public static extern Result EnableLoadExtension (IntPtr db, int onoff); + [DllImport(DLL_NAME, EntryPoint = "sqlite3_open", CallingConvention = CallingConvention.Cdecl)] + public static extern Result Open([MarshalAs(UnmanagedType.LPStr)] string filename, + out Sqlite3DatabaseHandle db); - [DllImport(DLL_NAME, EntryPoint = "sqlite3_close", CallingConvention=CallingConvention.Cdecl)] - public static extern Result Close (IntPtr db); + [DllImport(DLL_NAME, EntryPoint = "sqlite3_open_v2", CallingConvention = CallingConvention.Cdecl)] + public static extern Result Open([MarshalAs(UnmanagedType.LPStr)] string filename, out Sqlite3DatabaseHandle db, + int flags, Sqlite3DatabaseHandle zvfs); - [DllImport(DLL_NAME, EntryPoint = "sqlite3_initialize", CallingConvention=CallingConvention.Cdecl)] - public static extern Result Initialize(); + [DllImport(DLL_NAME, EntryPoint = "sqlite3_open_v2", CallingConvention = CallingConvention.Cdecl)] + public static extern Result Open(byte[] filename, out Sqlite3DatabaseHandle db, int flags, + Sqlite3DatabaseHandle zvfs); - [DllImport(DLL_NAME, EntryPoint = "sqlite3_shutdown", CallingConvention=CallingConvention.Cdecl)] - public static extern Result Shutdown(); + [DllImport(DLL_NAME, EntryPoint = "sqlite3_key", CallingConvention = CallingConvention.Cdecl)] + public static extern Result Key(Sqlite3DatabaseHandle db, [MarshalAs(UnmanagedType.LPStr)] string key, + int keylen); - [DllImport(DLL_NAME, EntryPoint = "sqlite3_config", CallingConvention=CallingConvention.Cdecl)] - public static extern Result Config (ConfigOption option); + [DllImport(DLL_NAME, EntryPoint = "sqlite3_open16", CallingConvention = CallingConvention.Cdecl)] + public static extern Result Open16([MarshalAs(UnmanagedType.LPWStr)] string filename, + out Sqlite3DatabaseHandle db); - //[DllImport(DLL_NAME, EntryPoint = "sqlite3_win32_set_directory", CallingConvention=CallingConvention.Cdecl, CharSet=CharSet.Unicode)] - //public static extern int SetDirectory (uint directoryType, string directoryPath); + [DllImport(DLL_NAME, EntryPoint = "sqlite3_enable_load_extension", CallingConvention = CallingConvention.Cdecl)] + public static extern Result EnableLoadExtension(Sqlite3DatabaseHandle db, int onoff); - [DllImport(DLL_NAME, EntryPoint = "sqlite3_busy_timeout", CallingConvention=CallingConvention.Cdecl)] - public static extern Result BusyTimeout (IntPtr db, int milliseconds); + [DllImport(DLL_NAME, EntryPoint = "sqlite3_close", CallingConvention = CallingConvention.Cdecl)] + public static extern Result Close(Sqlite3DatabaseHandle db); - [DllImport(DLL_NAME, EntryPoint = "sqlite3_changes", CallingConvention=CallingConvention.Cdecl)] - public static extern int Changes (IntPtr db); + [DllImport(DLL_NAME, EntryPoint = "sqlite3_initialize", CallingConvention = CallingConvention.Cdecl)] + public static extern Result Initialize(); - [DllImport(DLL_NAME, EntryPoint = "sqlite3_prepare_v2", CallingConvention=CallingConvention.Cdecl)] - public static extern Result Prepare2 (IntPtr db, [MarshalAs(UnmanagedType.LPStr)] string sql, int numBytes, out IntPtr stmt, IntPtr pzTail); + [DllImport(DLL_NAME, EntryPoint = "sqlite3_shutdown", CallingConvention = CallingConvention.Cdecl)] + public static extern Result Shutdown(); - [DllImport(DLL_NAME, EntryPoint = "sqlite3_step", CallingConvention=CallingConvention.Cdecl)] - public static extern Result Step (IntPtr stmt); + [DllImport(DLL_NAME, EntryPoint = "sqlite3_config", CallingConvention = CallingConvention.Cdecl)] + public static extern Result Config(ConfigOption option); - [DllImport(DLL_NAME, EntryPoint = "sqlite3_reset", CallingConvention=CallingConvention.Cdecl)] - public static extern Result Reset (IntPtr stmt); + //[DllImport(DLL_NAME, EntryPoint = "sqlite3_win32_set_directory", CallingConvention=CallingConvention.Cdecl, CharSet=CharSet.Unicode)] + //public static extern int SetDirectory (uint directoryType, string directoryPath); - [DllImport(DLL_NAME, EntryPoint = "sqlite3_finalize", CallingConvention=CallingConvention.Cdecl)] - public static extern Result Finalize (IntPtr stmt); + [DllImport(DLL_NAME, EntryPoint = "sqlite3_busy_timeout", CallingConvention = CallingConvention.Cdecl)] + public static extern Result BusyTimeout(Sqlite3DatabaseHandle db, int milliseconds); - [DllImport(DLL_NAME, EntryPoint = "sqlite3_last_insert_rowid", CallingConvention=CallingConvention.Cdecl)] - public static extern long LastInsertRowid (IntPtr db); + [DllImport(DLL_NAME, EntryPoint = "sqlite3_changes", CallingConvention = CallingConvention.Cdecl)] + public static extern int Changes(Sqlite3DatabaseHandle db); - [DllImport(DLL_NAME, EntryPoint = "sqlite3_errmsg16", CallingConvention=CallingConvention.Cdecl)] - public static extern IntPtr Errmsg (IntPtr db); - public static string GetErrmsg (IntPtr db) { - return Marshal.PtrToStringUni (Errmsg (db)); - } + [DllImport(DLL_NAME, EntryPoint = "sqlite3_prepare_v2", CallingConvention = CallingConvention.Cdecl)] + public static extern Result Prepare2(Sqlite3DatabaseHandle db, [MarshalAs(UnmanagedType.LPStr)] string sql, + int numBytes, out Sqlite3DatabaseHandle stmt, Sqlite3DatabaseHandle pzTail); - [DllImport(DLL_NAME, EntryPoint = "sqlite3_bind_parameter_index", CallingConvention=CallingConvention.Cdecl)] - public static extern int BindParameterIndex (IntPtr stmt, [MarshalAs(UnmanagedType.LPStr)] string name); + [DllImport(DLL_NAME, EntryPoint = "sqlite3_step", CallingConvention = CallingConvention.Cdecl)] + public static extern Result Step(Sqlite3DatabaseHandle stmt); - [DllImport(DLL_NAME, EntryPoint = "sqlite3_bind_null", CallingConvention=CallingConvention.Cdecl)] - public static extern int BindNull (IntPtr stmt, int index); + [DllImport(DLL_NAME, EntryPoint = "sqlite3_reset", CallingConvention = CallingConvention.Cdecl)] + public static extern Result Reset(Sqlite3DatabaseHandle stmt); - [DllImport(DLL_NAME, EntryPoint = "sqlite3_bind_int", CallingConvention=CallingConvention.Cdecl)] - public static extern int BindInt (IntPtr stmt, int index, int val); + [DllImport(DLL_NAME, EntryPoint = "sqlite3_finalize", CallingConvention = CallingConvention.Cdecl)] + public static extern Result Finalize(Sqlite3DatabaseHandle stmt); - [DllImport(DLL_NAME, EntryPoint = "sqlite3_bind_int64", CallingConvention=CallingConvention.Cdecl)] - public static extern int BindInt64 (IntPtr stmt, int index, long val); + [DllImport(DLL_NAME, EntryPoint = "sqlite3_last_insert_rowid", CallingConvention = CallingConvention.Cdecl)] + public static extern long LastInsertRowid(Sqlite3DatabaseHandle db); - [DllImport(DLL_NAME, EntryPoint = "sqlite3_bind_double", CallingConvention=CallingConvention.Cdecl)] - public static extern int BindDouble (IntPtr stmt, int index, double val); + [DllImport(DLL_NAME, EntryPoint = "sqlite3_errmsg16", CallingConvention = CallingConvention.Cdecl)] + public static extern Sqlite3DatabaseHandle Errmsg(Sqlite3DatabaseHandle db); - [DllImport(DLL_NAME, EntryPoint = "sqlite3_bind_text16", CallingConvention=CallingConvention.Cdecl, CharSet = CharSet.Unicode)] - public static extern int BindText (IntPtr stmt, int index, [MarshalAs(UnmanagedType.LPWStr)] string val, int n, IntPtr free); + public static string GetErrmsg(Sqlite3DatabaseHandle db) + { + return Marshal.PtrToStringUni(Errmsg(db)); + } - [DllImport(DLL_NAME, EntryPoint = "sqlite3_bind_blob", CallingConvention=CallingConvention.Cdecl)] - public static extern int BindBlob (IntPtr stmt, int index, byte[] val, int n, IntPtr free); + [DllImport(DLL_NAME, EntryPoint = "sqlite3_bind_parameter_index", CallingConvention = CallingConvention.Cdecl)] + public static extern int BindParameterIndex(Sqlite3DatabaseHandle stmt, + [MarshalAs(UnmanagedType.LPStr)] string name); - [DllImport(DLL_NAME, EntryPoint = "sqlite3_column_count", CallingConvention=CallingConvention.Cdecl)] - public static extern int ColumnCount (IntPtr stmt); + [DllImport(DLL_NAME, EntryPoint = "sqlite3_bind_null", CallingConvention = CallingConvention.Cdecl)] + public static extern int BindNull(Sqlite3DatabaseHandle stmt, int index); - [DllImport(DLL_NAME, EntryPoint = "sqlite3_column_name", CallingConvention=CallingConvention.Cdecl)] - public static extern IntPtr ColumnName (IntPtr stmt, int index); + [DllImport(DLL_NAME, EntryPoint = "sqlite3_bind_int", CallingConvention = CallingConvention.Cdecl)] + public static extern int BindInt(Sqlite3DatabaseHandle stmt, int index, int val); - [DllImport(DLL_NAME, EntryPoint = "sqlite3_column_name16", CallingConvention=CallingConvention.Cdecl)] - static extern IntPtr ColumnName16Internal (IntPtr stmt, int index); - public static string ColumnName16(IntPtr stmt, int index) { - return Marshal.PtrToStringUni(ColumnName16Internal(stmt, index)); - } + [DllImport(DLL_NAME, EntryPoint = "sqlite3_bind_int64", CallingConvention = CallingConvention.Cdecl)] + public static extern int BindInt64(Sqlite3DatabaseHandle stmt, int index, long val); - [DllImport(DLL_NAME, EntryPoint = "sqlite3_column_type", CallingConvention=CallingConvention.Cdecl)] - public static extern ColType ColumnType (IntPtr stmt, int index); + [DllImport(DLL_NAME, EntryPoint = "sqlite3_bind_double", CallingConvention = CallingConvention.Cdecl)] + public static extern int BindDouble(Sqlite3DatabaseHandle stmt, int index, double val); - [DllImport(DLL_NAME, EntryPoint = "sqlite3_column_int", CallingConvention=CallingConvention.Cdecl)] - public static extern int ColumnInt (IntPtr stmt, int index); + [DllImport(DLL_NAME, EntryPoint = "sqlite3_bind_text16", CallingConvention = CallingConvention.Cdecl, + CharSet = CharSet.Unicode)] + public static extern int BindText(Sqlite3DatabaseHandle stmt, int index, + [MarshalAs(UnmanagedType.LPWStr)] string val, int n, Sqlite3DatabaseHandle free); - [DllImport(DLL_NAME, EntryPoint = "sqlite3_column_int64", CallingConvention=CallingConvention.Cdecl)] - public static extern long ColumnInt64 (IntPtr stmt, int index); + [DllImport(DLL_NAME, EntryPoint = "sqlite3_bind_blob", CallingConvention = CallingConvention.Cdecl)] + public static extern int BindBlob(Sqlite3DatabaseHandle stmt, int index, byte[] val, int n, + Sqlite3DatabaseHandle free); - [DllImport(DLL_NAME, EntryPoint = "sqlite3_column_double", CallingConvention=CallingConvention.Cdecl)] - public static extern double ColumnDouble (IntPtr stmt, int index); + [DllImport(DLL_NAME, EntryPoint = "sqlite3_column_count", CallingConvention = CallingConvention.Cdecl)] + public static extern int ColumnCount(Sqlite3DatabaseHandle stmt); - [DllImport(DLL_NAME, EntryPoint = "sqlite3_column_text", CallingConvention=CallingConvention.Cdecl)] - public static extern IntPtr ColumnText (IntPtr stmt, int index); + [DllImport(DLL_NAME, EntryPoint = "sqlite3_column_name", CallingConvention = CallingConvention.Cdecl)] + public static extern Sqlite3DatabaseHandle ColumnName(Sqlite3DatabaseHandle stmt, int index); - [DllImport(DLL_NAME, EntryPoint = "sqlite3_column_text16", CallingConvention=CallingConvention.Cdecl)] - public static extern IntPtr ColumnText16 (IntPtr stmt, int index); + [DllImport(DLL_NAME, EntryPoint = "sqlite3_column_name16", CallingConvention = CallingConvention.Cdecl)] + private static extern Sqlite3DatabaseHandle ColumnName16Internal(Sqlite3DatabaseHandle stmt, int index); - [DllImport(DLL_NAME, EntryPoint = "sqlite3_column_blob", CallingConvention=CallingConvention.Cdecl)] - public static extern IntPtr ColumnBlob (IntPtr stmt, int index); + public static string ColumnName16(Sqlite3DatabaseHandle stmt, int index) + { + return Marshal.PtrToStringUni(ColumnName16Internal(stmt, index)); + } - [DllImport(DLL_NAME, EntryPoint = "sqlite3_column_bytes", CallingConvention=CallingConvention.Cdecl)] - public static extern int ColumnBytes (IntPtr stmt, int index); - public static string ColumnString (IntPtr stmt, int index) { - return Marshal.PtrToStringUni (SQLite3.ColumnText16 (stmt, index)); - } + [DllImport(DLL_NAME, EntryPoint = "sqlite3_column_type", CallingConvention = CallingConvention.Cdecl)] + public static extern ColType ColumnType(Sqlite3DatabaseHandle stmt, int index); - public static byte[] ColumnByteArray (IntPtr stmt, int index) { - int length = ColumnBytes (stmt, index); - var result = new byte[length]; - if (length > 0) - Marshal.Copy (ColumnBlob (stmt, index), result, 0, length); - return result; - } + [DllImport(DLL_NAME, EntryPoint = "sqlite3_column_int", CallingConvention = CallingConvention.Cdecl)] + public static extern int ColumnInt(Sqlite3DatabaseHandle stmt, int index); - [DllImport (DLL_NAME, EntryPoint = "sqlite3_extended_errcode", CallingConvention = CallingConvention.Cdecl)] - public static extern ExtendedResult ExtendedErrCode (IntPtr db); - - [DllImport (DLL_NAME, EntryPoint = "sqlite3_libversion_number", CallingConvention = CallingConvention.Cdecl)] - public static extern int LibVersionNumber (); - - - #if NETFX_CORE - [DllImport (DLL_NAME, EntryPoint = "sqlite3_prepare_v2", CallingConvention = CallingConvention.Cdecl)] - static extern Result Prepare2 (IntPtr db, byte[] queryBytes, int numBytes, out IntPtr stmt, IntPtr pzTail); - #endif - - public static IntPtr Prepare2 (IntPtr db, string query) { - IntPtr stmt; - #if NETFX_CORE - byte[] queryBytes = System.Text.UTF8Encoding.UTF8.GetBytes (query); - var r = Prepare2 (db, queryBytes, queryBytes.Length, out stmt, IntPtr.Zero); - #else - var r = Prepare2 (db, query, System.Text.UTF8Encoding.UTF8.GetByteCount (query), out stmt, IntPtr.Zero); - #endif - if (r != Result.OK) { - throw SQLiteException.New (r, GetErrmsg (db)); - } - return stmt; - } - - -#endif - - - } - - - public static partial class SQLite3 - { -#if USE_CSHARP_SQLITE - public static Result Open(string filename, out Sqlite3DatabaseHandle db, int flags, IntPtr zVfs) { - return (Result)Sqlite3.sqlite3_open_v2(filename, out db, flags, null); - } - - public static int BindText(Sqlite3Statement stmt, int index, string val, int n, IntPtr free) { - return Sqlite3.sqlite3_bind_text(stmt, index, val, n, null); - } - - public static int BindBlob(Sqlite3Statement stmt, int index, byte[] val, int n, IntPtr free) { - return Sqlite3.sqlite3_bind_blob(stmt, index, val, n, null); - } - - public static Sqlite3Statement Prepare2(Sqlite3DatabaseHandle db, string query) { - Sqlite3Statement stmt = new Sqlite3Statement(); - var r = Sqlite3.sqlite3_prepare_v2(db, query, -1, ref stmt, 0); - if (r != 0) { - throw SQLiteException.New((Result)r, GetErrmsg(db)); - } - return stmt; - } -#elif USE_WP8_NATIVE_SQLITE - public static Result Open(string filename, out Sqlite3DatabaseHandle db, int flags, IntPtr zVfs) { - return (Result)Sqlite3.sqlite3_open_v2(filename, out db, flags, ""); - } - - public static int BindText(Sqlite3Statement stmt, int index, string val, int n, IntPtr free) { - return Sqlite3.sqlite3_bind_text(stmt, index, val, n); - } - - public static int BindBlob(Sqlite3Statement stmt, int index, byte[] val, int n, IntPtr free) { - return Sqlite3.sqlite3_bind_blob(stmt, index, val, n, null); - } - - public static Sqlite3Statement Prepare2(Sqlite3DatabaseHandle db, string query) { - Sqlite3Statement stmt = default(Sqlite3Statement); - var r = Sqlite3.sqlite3_prepare_v2(db, query, out stmt); - - if (r != 0) { - throw SQLiteException.New((Result)r, GetErrmsg(db)); - } - return stmt; - } -#endif + [DllImport(DLL_NAME, EntryPoint = "sqlite3_column_int64", CallingConvention = CallingConvention.Cdecl)] + public static extern long ColumnInt64(Sqlite3DatabaseHandle stmt, int index); -#if USE_CSHARP_SQLITE || USE_WP8_NATIVE_SQLITE - public static Result Open(string filename, out Sqlite3DatabaseHandle db) { - return (Result) Sqlite3.sqlite3_open(filename, out db); - } + [DllImport(DLL_NAME, EntryPoint = "sqlite3_column_double", CallingConvention = CallingConvention.Cdecl)] + public static extern double ColumnDouble(Sqlite3DatabaseHandle stmt, int index); - public static Result Close(Sqlite3DatabaseHandle db) { - return (Result)Sqlite3.sqlite3_close(db); - } + [DllImport(DLL_NAME, EntryPoint = "sqlite3_column_text", CallingConvention = CallingConvention.Cdecl)] + public static extern Sqlite3DatabaseHandle ColumnText(Sqlite3DatabaseHandle stmt, int index); - public static Result BusyTimeout(Sqlite3DatabaseHandle db, int milliseconds) { - return (Result)Sqlite3.sqlite3_busy_timeout(db, milliseconds); - } + [DllImport(DLL_NAME, EntryPoint = "sqlite3_column_text16", CallingConvention = CallingConvention.Cdecl)] + public static extern Sqlite3DatabaseHandle ColumnText16(Sqlite3DatabaseHandle stmt, int index); - public static int Changes(Sqlite3DatabaseHandle db) { - return Sqlite3.sqlite3_changes(db); - } + [DllImport(DLL_NAME, EntryPoint = "sqlite3_column_blob", CallingConvention = CallingConvention.Cdecl)] + public static extern Sqlite3DatabaseHandle ColumnBlob(Sqlite3DatabaseHandle stmt, int index); - public static Result Step(Sqlite3Statement stmt) { - return (Result)Sqlite3.sqlite3_step(stmt); - } + [DllImport(DLL_NAME, EntryPoint = "sqlite3_column_bytes", CallingConvention = CallingConvention.Cdecl)] + public static extern int ColumnBytes(Sqlite3DatabaseHandle stmt, int index); - public static Result Reset(Sqlite3Statement stmt) { - return (Result)Sqlite3.sqlite3_reset(stmt); - } + public static string ColumnString(Sqlite3DatabaseHandle stmt, int index) + { + return Marshal.PtrToStringUni(ColumnText16(stmt, index)); + } - public static Result Finalize(Sqlite3Statement stmt) { - return (Result)Sqlite3.sqlite3_finalize(stmt); - } + public static byte[] ColumnByteArray(Sqlite3DatabaseHandle stmt, int index) + { + int length = ColumnBytes(stmt, index); + byte[] result = new byte[length]; + if (length > 0) + Marshal.Copy(ColumnBlob(stmt, index), result, 0, length); + return result; + } - public static long LastInsertRowid(Sqlite3DatabaseHandle db) { - return Sqlite3.sqlite3_last_insert_rowid(db); - } + [DllImport(DLL_NAME, EntryPoint = "sqlite3_extended_errcode", CallingConvention = CallingConvention.Cdecl)] + public static extern ExtendedResult ExtendedErrCode(Sqlite3DatabaseHandle db); - public static string GetErrmsg(Sqlite3DatabaseHandle db) { - return Sqlite3.sqlite3_errmsg(db); - } - - public static int BindParameterIndex(Sqlite3Statement stmt, string name) { - return Sqlite3.sqlite3_bind_parameter_index(stmt, name); - } - - public static int BindNull(Sqlite3Statement stmt, int index) { - return Sqlite3.sqlite3_bind_null(stmt, index); - } - - public static int BindInt(Sqlite3Statement stmt, int index, int val) { - return Sqlite3.sqlite3_bind_int(stmt, index, val); - } - - public static int BindInt64(Sqlite3Statement stmt, int index, long val) { - return Sqlite3.sqlite3_bind_int64(stmt, index, val); - } - - public static int BindDouble(Sqlite3Statement stmt, int index, double val) { - return Sqlite3.sqlite3_bind_double(stmt, index, val); - } - - public static int ColumnCount(Sqlite3Statement stmt) { - return Sqlite3.sqlite3_column_count(stmt); - } - - public static string ColumnName(Sqlite3Statement stmt, int index) { - return Sqlite3.sqlite3_column_name(stmt, index); - } - - public static string ColumnName16(Sqlite3Statement stmt, int index) { - return Sqlite3.sqlite3_column_name(stmt, index); - } - - public static ColType ColumnType(Sqlite3Statement stmt, int index) { - return (ColType)Sqlite3.sqlite3_column_type(stmt, index); - } - - public static int ColumnInt(Sqlite3Statement stmt, int index) { - return Sqlite3.sqlite3_column_int(stmt, index); - } - - public static long ColumnInt64(Sqlite3Statement stmt, int index) { - return Sqlite3.sqlite3_column_int64(stmt, index); - } - - public static double ColumnDouble(Sqlite3Statement stmt, int index) { - return Sqlite3.sqlite3_column_double(stmt, index); - } - - public static string ColumnText(Sqlite3Statement stmt, int index) { - return Sqlite3.sqlite3_column_text(stmt, index); - } - - public static string ColumnText16(Sqlite3Statement stmt, int index) { - return Sqlite3.sqlite3_column_text(stmt, index); - } - - public static byte[] ColumnBlob(Sqlite3Statement stmt, int index) { - return Sqlite3.sqlite3_column_blob(stmt, index); - } - - public static int ColumnBytes(Sqlite3Statement stmt, int index) { - return Sqlite3.sqlite3_column_bytes(stmt, index); - } - - public static string ColumnString(Sqlite3Statement stmt, int index) { - return Sqlite3.sqlite3_column_text(stmt, index); - } - - public static byte[] ColumnByteArray(Sqlite3Statement stmt, int index) { - return ColumnBlob(stmt, index); - } - - public static Result EnableLoadExtension(Sqlite3DatabaseHandle db, int onoff) { - return (Result)Sqlite3.sqlite3_enable_load_extension(db, onoff); - } - - public static ExtendedResult ExtendedErrCode(Sqlite3DatabaseHandle db) { - return (ExtendedResult)Sqlite3.sqlite3_extended_errcode(db); - } -#endif - } -} + [DllImport(DLL_NAME, EntryPoint = "sqlite3_libversion_number", CallingConvention = CallingConvention.Cdecl)] + public static extern int LibVersionNumber(); + public static Sqlite3DatabaseHandle Prepare2(Sqlite3DatabaseHandle db, string query) + { + Sqlite3DatabaseHandle stmt; + Result r = Prepare2(db, query, Encoding.UTF8.GetByteCount(query), out stmt, Sqlite3DatabaseHandle.Zero); + if (r != Result.OK) throw SQLiteException.New(r, GetErrmsg(db)); + return stmt; + } + } +} \ No newline at end of file diff --git a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/Sqlite3Connection.cs b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/Sqlite3Connection.cs index 7637a87..9e96e8e 100644 --- a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/Sqlite3Connection.cs +++ b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/Sqlite3Connection.cs @@ -1,2616 +1,2568 @@ -using System.Collections; -using System; +using System; +using System.Collections; using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; using System.Linq.Expressions; using System.Reflection; -using System.Linq; -using System.Diagnostics; +using System.Text; using System.Threading; - - -#if USE_CSHARP_SQLITE -using Sqlite3 = Community.CsharpSqlite.Sqlite3; -using Sqlite3DatabaseHandle = Community.CsharpSqlite.Sqlite3.sqlite3; -using Sqlite3Statement = Community.CsharpSqlite.Sqlite3.Vdbe; -#elif USE_WP8_NATIVE_SQLITE -using Sqlite3 = Sqlite.Sqlite3; -using Sqlite3DatabaseHandle = Sqlite.Database; -using Sqlite3Statement = Sqlite.Statement; -#else +using SQLite.Attribute; using Sqlite3DatabaseHandle = System.IntPtr; using Sqlite3Statement = System.IntPtr; -#endif namespace SqlCipher4Unity3D { - using SQLite.Attribute; - /// - /// Represents an open connection to a SQLite database. - /// - public partial class SQLiteConnection : IDisposable { - private bool _open; - private TimeSpan _busyTimeout; - private Dictionary _mappings = null; - private Dictionary _tables = null; - private System.Diagnostics.Stopwatch _sw; - private long _elapsedMilliseconds = 0; - - private int _transactionDepth = 0; - private System.Random _rand = new System.Random (); - - public Sqlite3DatabaseHandle Handle { get; private set; } - internal static readonly Sqlite3DatabaseHandle NullHandle = default(Sqlite3DatabaseHandle); - - public string DatabasePath { get; private set; } - - public bool TimeExecution { get; set; } - - public bool Trace { get; set; } - - public bool StoreDateTimeAsTicks { get; private set; } - - /// - /// Constructs a new SQLiteConnection and opens a 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 default of false is - /// only here for backwards compatibility. There is a *significant* speed advantage, with no - /// down sides, when setting storeDateTimeAsTicks = true. - /// - public SQLiteConnection (string databasePath, string password = null, bool storeDateTimeAsTicks = false) - : this (databasePath, password, SQLiteOpenFlags.ReadWrite | SQLiteOpenFlags.Create, storeDateTimeAsTicks) - { - } - - /// - /// Constructs a new SQLiteConnection and opens a 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 default of false is - /// only here for backwards compatibility. There is a *significant* speed advantage, with no - /// down sides, when setting storeDateTimeAsTicks = true. - /// - public SQLiteConnection (string databasePath, string password, SQLiteOpenFlags openFlags, bool storeDateTimeAsTicks = false) { - if (string.IsNullOrEmpty (databasePath)) - throw new ArgumentException ("Must be specified", "databasePath"); - - DatabasePath = databasePath; - - #if NETFX_CORE - SQLite3.SetDirectory(/*temp directory type*/2, Windows.Storage.ApplicationData.Current.TemporaryFolder.Path); - #endif - - Sqlite3DatabaseHandle handle; - - #if SILVERLIGHT || USE_CSHARP_SQLITE - var r = SQLite3.Open (databasePath, out handle, (int)openFlags, IntPtr.Zero); - #else - // open using the byte[] - // in the case where the path may include Unicode - // force open to using UTF-8 using sqlite3_open_v2 - var databasePathAsBytes = GetNullTerminatedUtf8 (DatabasePath); - var r = SQLite3.Open (databasePathAsBytes, out handle, (int) openFlags, IntPtr.Zero); - #endif - - Handle = handle; - if (r != SQLite3.Result.OK) { - throw SQLiteException.New (r, String.Format ("Could not open database file: {0} ({1})", DatabasePath, r)); - } - - if (!string.IsNullOrEmpty(password)) { - var result = SQLite3.Key(handle, password, password.Length); - if (result != SQLite3.Result.OK) { - throw SQLiteException.New (r, String.Format ("Could not open database file: {0} ({1})", DatabasePath, r)); - } - } - - _open = true; - - StoreDateTimeAsTicks = storeDateTimeAsTicks; - - BusyTimeout = TimeSpan.FromSeconds (0.1); - } - - static SQLiteConnection () { - if (_preserveDuringLinkMagic) { - var ti = new ColumnInfo (); - ti.Name = "magic"; - } - } - - public void EnableLoadExtension(int onoff) { - SQLite3.Result r = SQLite3.EnableLoadExtension(Handle, onoff); - if (r != SQLite3.Result.OK) { - string msg = SQLite3.GetErrmsg (Handle); - throw SQLiteException.New (r, msg); - } - } - - static byte[] GetNullTerminatedUtf8 (string s) { - var utf8Length = System.Text.Encoding.UTF8.GetByteCount (s); - var bytes = new byte [utf8Length + 1]; - utf8Length = System.Text.Encoding.UTF8.GetBytes(s, 0, s.Length, bytes, 0); - return bytes; - } - - /// - /// Used to list some code that we want the MonoTouch linker - /// to see, but that we never want to actually execute. - /// - static bool _preserveDuringLinkMagic; - - /// - /// Sets a busy handler to sleep the specified amount of time when a table is locked. - /// The handler will sleep multiple times until a total time of has accumulated. - /// - public TimeSpan BusyTimeout { - get { return _busyTimeout; } - set { - _busyTimeout = value; - if (Handle != NullHandle) { - SQLite3.BusyTimeout (Handle, (int)_busyTimeout.TotalMilliseconds); - } - } - } - - /// - /// Returns the mappings from types to tables that the connection - /// currently understands. - /// - public IEnumerable TableMappings { - get { - return _tables != null ? _tables.Values : Enumerable.Empty (); - } - } - - /// - /// 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 TableMapping GetMapping(Type type, CreateFlags createFlags = CreateFlags.None) - { - if (_mappings == null) { - _mappings = new Dictionary (); - } - TableMapping map; - if (!_mappings.TryGetValue (type.FullName, out map)) { - map = new TableMapping (type, createFlags); - _mappings [type.FullName] = map; - } - return map; - } - - /// - /// Retrieves the mapping that is automatically generated for the given type. - /// - /// - /// The mapping represents the schema of the columns of the database and contains - /// methods to set and get properties of objects. - /// - public TableMapping GetMapping () - { - return GetMapping (typeof (T)); - } - - private struct IndexedColumn - { - public int Order; - public string ColumnName; - } - - private struct IndexInfo - { - public string IndexName; - public string TableName; - public bool Unique; - public List Columns; - } - - /// - /// Executes a "drop table" on the database. This is non-recoverable. - /// - public int DropTable() - { - var map = GetMapping (typeof (T)); - - var query = string.Format("drop table if exists \"{0}\"", map.TableName); - - return Execute (query); - } - - public int DropTable(Type t) - { - var map = GetMapping(t); - - var query = string.Format("drop table if exists \"{0}\"", map.TableName); - - return Execute(query); - } - - /// - /// 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. - /// - /// - /// The number of entries added to the database schema. - /// - public int CreateTable(CreateFlags createFlags = CreateFlags.None) - { - return CreateTable(typeof (T), 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. - /// - /// The number of entries added to the database schema. - /// - public int CreateTable(Type ty, CreateFlags createFlags = CreateFlags.None) - { - if (_tables == null) { - _tables = new Dictionary (); - } - TableMapping map; - if (!_tables.TryGetValue (ty.FullName, out map)) { - map = GetMapping (ty, createFlags); - _tables.Add (ty.FullName, map); - } - var query = "create table if not exists \"" + map.TableName + "\"(\n"; - - var decls = map.Columns.Select (p => Orm.SqlDecl (p, StoreDateTimeAsTicks)); - var decl = string.Join (",\n", decls.ToArray ()); - query += decl; - query += ")"; - - var count = Execute (query); - - if (count == 0) { //Possible bug: This always seems to return 0? - // Table already exists, migrate it - MigrateTable (map); - } - - var indexes = new Dictionary (); - foreach (var c in map.Columns) { - foreach (var i in c.Indices) { - var iname = i.Name ?? map.TableName + "_" + c.Name; - IndexInfo iinfo; - if (!indexes.TryGetValue (iname, out iinfo)) { - iinfo = new IndexInfo { - IndexName = iname, - TableName = map.TableName, - Unique = i.Unique, - Columns = new List () - }; - indexes.Add (iname, iinfo); - } - - if (i.Unique != iinfo.Unique) - throw new Exception ("All the columns in an index must have the same value for their Unique property"); - - iinfo.Columns.Add (new IndexedColumn { - Order = i.Order, - ColumnName = c.Name - }); - } - } - - foreach (var indexName in indexes.Keys) { - var index = indexes[indexName]; - var columns = index.Columns.OrderBy(i => i.Order).Select(i => i.ColumnName).ToArray(); - count += CreateIndex(indexName, index.TableName, columns, index.Unique); - } - - return count; - } - - /// - /// Creates an index for the specified table and columns. - /// - /// Name of the index to create - /// Name of the database table - /// An array of column names to index - /// Whether the index should be unique - public int CreateIndex(string indexName, string tableName, string[] columnNames, bool unique = false) - { - const string sqlFormat = "create {2} index if not exists \"{3}\" on \"{0}\"(\"{1}\")"; - var sql = String.Format(sqlFormat, tableName, string.Join ("\", \"", columnNames), unique ? "unique" : "", indexName); - return Execute(sql); - } - - /// - /// Creates an index for the specified table and column. - /// - /// Name of the index to create - /// Name of the database table - /// Name of the column to index - /// Whether the index should be unique - public int CreateIndex(string indexName, string tableName, string columnName, bool unique = false) - { - return CreateIndex(indexName, tableName, new string[] { columnName }, unique); - } - - /// - /// Creates an index for the specified table and column. - /// - /// Name of the database table - /// Name of the column to index - /// Whether the index should be unique - public int CreateIndex(string tableName, string columnName, bool unique = false) - { - return CreateIndex(tableName + "_" + columnName, tableName, columnName, unique); - } - - /// - /// Creates an index for the specified table and columns. - /// - /// Name of the database table - /// An array of column names to index - /// Whether the index should be unique - public int CreateIndex(string tableName, string[] columnNames, bool unique = false) - { - return CreateIndex(tableName + "_" + string.Join ("_", columnNames), tableName, columnNames, unique); - } - - /// - /// Creates an index for the specified object property. - /// e.g. CreateIndex(c => c.Name); - /// - /// Type to reflect to a database table. - /// Property to index - /// Whether the index should be unique - public void CreateIndex(Expression> property, bool unique = false) - { - MemberExpression mx; - if (property.Body.NodeType == ExpressionType.Convert) - { - mx = ((UnaryExpression)property.Body).Operand as MemberExpression; - } - else - { - mx= (property.Body as MemberExpression); - } - var propertyInfo = mx.Member as PropertyInfo; - if (propertyInfo == null) - { - throw new ArgumentException("The lambda expression 'property' should point to a valid Property"); - } - - var propName = propertyInfo.Name; - - var map = GetMapping(); - var colName = map.FindColumnWithPropertyName(propName).Name; - - CreateIndex(map.TableName, colName, unique); - } - - public class ColumnInfo - { - // public int cid { get; set; } - - [Column ("name")] - public string Name { get; set; } - - // [Column ("type")] - // public string ColumnType { get; set; } - - public int notnull { get; set; } - - // public string dflt_value { get; set; } - - // public int pk { get; set; } - - public override string ToString () - { - return Name; - } - } - - public List GetTableInfo (string tableName) - { - var query = "pragma table_info(\"" + tableName + "\")"; - return Query (query); - } - - void MigrateTable (TableMapping map) - { - var existingCols = GetTableInfo (map.TableName); - - var toBeAdded = new List (); - - foreach (var p in map.Columns) { - var found = false; - foreach (var c in existingCols) { - found = (string.Compare (p.Name, c.Name, StringComparison.OrdinalIgnoreCase) == 0); - if (found) - break; - } - if (!found) { - toBeAdded.Add (p); - } - } - - foreach (var p in toBeAdded) { - var addCol = "alter table \"" + map.TableName + "\" add column " + Orm.SqlDecl (p, StoreDateTimeAsTicks); - Execute (addCol); - } - } - - /// - /// Creates a new SQLiteCommand. Can be overridden to provide a sub-class. - /// - /// - protected virtual SQLiteCommand NewCommand () - { - return new SQLiteCommand (this); - } - - /// - /// Creates a new SQLiteCommand given the command text with arguments. Place a '?' - /// in the command text for each of the arguments. - /// - /// - /// The fully escaped SQL. - /// - /// - /// Arguments to substitute for the occurences of '?' in the command text. - /// - /// - /// A - /// - public SQLiteCommand CreateCommand (string cmdText, params object[] ps) - { - if (!_open) - throw SQLiteException.New (SQLite3.Result.Error, "Cannot create commands from unopened database"); - - var cmd = NewCommand (); - cmd.CommandText = cmdText; - foreach (var o in ps) { - cmd.Bind (o); - } - return cmd; - } - - /// - /// 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 int Execute (string query, params object[] args) - { - var cmd = CreateCommand (query, args); - - if (TimeExecution) { - if (_sw == null) { - _sw = new Stopwatch (); - } - _sw.Reset (); - _sw.Start (); - } - - var r = cmd.ExecuteNonQuery (); - - if (TimeExecution) { - _sw.Stop (); - _elapsedMilliseconds += _sw.ElapsedMilliseconds; - System.Diagnostics.Debug.WriteLine (string.Format ("Finished in {0} ms ({1:0.0} s total)", _sw.ElapsedMilliseconds, _elapsedMilliseconds / 1000.0)); - } - - return r; - } - - public T ExecuteScalar (string query, params object[] args) - { - var cmd = CreateCommand (query, args); - - if (TimeExecution) { - if (_sw == null) { - _sw = new Stopwatch (); - } - _sw.Reset (); - _sw.Start (); - } - - var r = cmd.ExecuteScalar (); - - if (TimeExecution) { - _sw.Stop (); - _elapsedMilliseconds += _sw.ElapsedMilliseconds; - System.Diagnostics.Debug.WriteLine (string.Format ("Finished in {0} ms ({1:0.0} s total)", _sw.ElapsedMilliseconds, _elapsedMilliseconds / 1000.0)); - } - - return r; - } - - /// - /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?' - /// in the command text for each of the arguments and then executes that command. - /// 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. - /// - public List Query (string query, params object[] args) where T : new() - { - var cmd = CreateCommand (query, args); - return cmd.ExecuteQuery (); - } - - /// - /// 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 IEnumerable DeferredQuery(string query, params object[] args) where T : new() - { - var cmd = CreateCommand(query, args); - return cmd.ExecuteDeferredQuery(); - } - - /// - /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?' - /// in the command text for each of the arguments and then executes that command. - /// It returns each row of the result using the 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 List Query (TableMapping map, string query, params object[] args) - { - var cmd = CreateCommand (query, args); - return cmd.ExecuteQuery (map); - } - - /// - /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?' - /// in the command text for each of the arguments and then executes that command. - /// It returns each row of the result using the specified mapping. This function is - /// only used by libraries in order to query the database via introspection. It is - /// normally not used. - /// - /// - /// A 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 IEnumerable DeferredQuery(TableMapping map, string query, params object[] args) - { - var cmd = CreateCommand(query, args); - return cmd.ExecuteDeferredQuery(map); - } - - /// - /// 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 TableQuery Table () where T : new() - { - return new TableQuery (this); - } - - /// - /// 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 T Get (object pk) where T : new() - { - var map = GetMapping (typeof(T)); - return Query (map.GetByPrimaryKeySql, pk).First (); - } - - /// - /// Attempts to retrieve the first object that matches the predicate from the table - /// associated with the specified type. - /// - /// - /// A predicate for which object to find. - /// - /// - /// The object that matches the given predicate. Throws a not found exception - /// if the object is not found. - /// - public T Get (Expression> predicate) where T : new() - { - return Table ().Where (predicate).First (); - } - - /// - /// Attempts to retrieve 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 T Find (object pk) where T : new () - { - var map = GetMapping (typeof (T)); - return Query (map.GetByPrimaryKeySql, pk).FirstOrDefault (); - } - - /// - /// 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 object type. - /// - /// - /// The object with the given primary key or null - /// if the object is not found. - /// - public object Find (object pk, TableMapping map) - { - return Query (map, map.GetByPrimaryKeySql, pk).FirstOrDefault (); - } - - /// - /// Attempts to retrieve the first object that matches the predicate from the table - /// associated with the specified type. - /// - /// - /// A predicate for which object to find. - /// - /// - /// The object that matches the given predicate or null - /// if the object is not found. - /// - public T Find (Expression> predicate) where T : new() - { - return Table ().Where (predicate).FirstOrDefault (); - } - - /// - /// Whether has been called and the database is waiting for a . - /// - public bool IsInTransaction { - get { return _transactionDepth > 0; } - } - - /// - /// Begins a new transaction. Call to end the transaction. - /// - /// Throws if a transaction has already begun. - public void BeginTransaction () - { - // The BEGIN command only works if the transaction stack is empty, - // or in other words if there are no pending transactions. - // If the transaction stack is not empty when the BEGIN command is invoked, - // then the command fails with an error. - // Rather than crash with an error, we will just ignore calls to BeginTransaction - // that would result in an error. - if (Interlocked.CompareExchange (ref _transactionDepth, 1, 0) == 0) { - try { - Execute ("begin transaction"); - } catch (Exception ex) { - var sqlExp = ex as SQLiteException; - if (sqlExp != null) { - // It is recommended that applications respond to the errors listed below - // by explicitly issuing a ROLLBACK command. - // TODO: This rollback failsafe should be localized to all throw sites. - switch (sqlExp.Result) { - case SQLite3.Result.IOError: - case SQLite3.Result.Full: - case SQLite3.Result.Busy: - case SQLite3.Result.NoMem: - case SQLite3.Result.Interrupt: - RollbackTo (null, true); - break; - } - } else { - // Call decrement and not VolatileWrite in case we've already - // created a transaction point in SaveTransactionPoint since the catch. - Interlocked.Decrement (ref _transactionDepth); - } - - throw; - } - } else { - // Calling BeginTransaction on an already open transaction is invalid - throw new InvalidOperationException ("Cannot begin a transaction while already in a transaction."); - } - } - - /// - /// Creates a savepoint in the database at the current point in the transaction timeline. - /// Begins a new transaction if one is not in progress. - /// - /// Call to undo transactions since the returned savepoint. - /// Call to commit transactions after the savepoint returned here. - /// Call to end the transaction, committing all changes. - /// - /// A string naming the savepoint. - public string SaveTransactionPoint () - { - int depth = Interlocked.Increment (ref _transactionDepth) - 1; - string retVal = "S" + _rand.Next (short.MaxValue) + "D" + depth; - - try { - Execute ("savepoint " + retVal); - } catch (Exception ex) { - var sqlExp = ex as SQLiteException; - if (sqlExp != null) { - // It is recommended that applications respond to the errors listed below - // by explicitly issuing a ROLLBACK command. - // TODO: This rollback failsafe should be localized to all throw sites. - switch (sqlExp.Result) { - case SQLite3.Result.IOError: - case SQLite3.Result.Full: - case SQLite3.Result.Busy: - case SQLite3.Result.NoMem: - case SQLite3.Result.Interrupt: - RollbackTo (null, true); - break; - } - } else { - Interlocked.Decrement (ref _transactionDepth); - } - - throw; - } - - return retVal; - } - - /// - /// Rolls back the transaction that was begun by or . - /// - public void Rollback () - { - RollbackTo (null, false); - } - - /// - /// Rolls back the savepoint created by or SaveTransactionPoint. - /// - /// The name of the savepoint to roll back to, as returned by . If savepoint is null or empty, this method is equivalent to a call to - public void RollbackTo (string savepoint) - { - RollbackTo (savepoint, false); - } - - /// - /// Rolls back the transaction that was begun by . - /// - /// true to avoid throwing exceptions, false otherwise - void RollbackTo (string savepoint, bool noThrow) - { - // Rolling back without a TO clause rolls backs all transactions - // and leaves the transaction stack empty. - try { - if (String.IsNullOrEmpty (savepoint)) { - if (Interlocked.Exchange (ref _transactionDepth, 0) > 0) { - Execute ("rollback"); - } - } else { - DoSavePointExecute (savepoint, "rollback to "); - } - } catch (SQLiteException) { - if (!noThrow) - throw; - - } - // No need to rollback if there are no transactions open. - } - - /// - /// Releases a savepoint returned from . Releasing a savepoint - /// makes changes since that savepoint permanent if the savepoint began the transaction, - /// or otherwise the changes are permanent pending a call to . - /// - /// The RELEASE command is like a COMMIT for a SAVEPOINT. - /// - /// The name of the savepoint to release. The string should be the result of a call to - public void Release (string savepoint) - { - DoSavePointExecute (savepoint, "release "); - } - - void DoSavePointExecute (string savepoint, string cmd) - { - // Validate the savepoint - int firstLen = savepoint.IndexOf ('D'); - if (firstLen >= 2 && savepoint.Length > firstLen + 1) { - int depth; - if (Int32.TryParse (savepoint.Substring (firstLen + 1), out depth)) { - // TODO: Mild race here, but inescapable without locking almost everywhere. - if (0 <= depth && depth < _transactionDepth) { - #if NETFX_CORE + /// + /// Represents an open connection to a SQLite database. + /// + public class SQLiteConnection : IDisposable + { + internal static readonly Sqlite3DatabaseHandle NullHandle = default(Sqlite3DatabaseHandle); + + /// + /// Used to list some code that we want the MonoTouch linker + /// to see, but that we never want to actually execute. + /// + private static bool _preserveDuringLinkMagic; + + private readonly Random _rand = new Random(); + + private TimeSpan _busyTimeout; + private long _elapsedMilliseconds; + private Dictionary _mappings; + private bool _open; + private Stopwatch _sw; + private Dictionary _tables; + + private int _transactionDepth; + + static SQLiteConnection() + { + if (_preserveDuringLinkMagic) + { + ColumnInfo ti = new ColumnInfo(); + ti.Name = "magic"; + } + } + + /// + /// Constructs a new SQLiteConnection and opens a 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 default of false is + /// only here for backwards compatibility. There is a *significant* speed advantage, with no + /// down sides, when setting storeDateTimeAsTicks = true. + /// + public SQLiteConnection(string databasePath, string password = null, bool storeDateTimeAsTicks = false) : this( + databasePath, password, SQLiteOpenFlags.ReadWrite | SQLiteOpenFlags.Create, storeDateTimeAsTicks) { } + + /// + /// Constructs a new SQLiteConnection and opens a 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 default of false is + /// only here for backwards compatibility. There is a *significant* speed advantage, with no + /// down sides, when setting storeDateTimeAsTicks = true. + /// + public SQLiteConnection(string databasePath, string password, SQLiteOpenFlags openFlags, + bool storeDateTimeAsTicks = false) + { + if (string.IsNullOrEmpty(databasePath)) + throw new ArgumentException("Must be specified", "databasePath"); + + this.DatabasePath = databasePath; + + Sqlite3DatabaseHandle handle; + + // open using the byte[] + // in the case where the path may include Unicode + // force open to using UTF-8 using sqlite3_open_v2 + byte[] databasePathAsBytes = GetNullTerminatedUtf8(this.DatabasePath); + SQLite3.Result r = SQLite3.Open(databasePathAsBytes, out handle, (int) openFlags, IntPtr.Zero); + + this.Handle = handle; + if (r != SQLite3.Result.OK) + throw SQLiteException.New(r, + string.Format("Could not open database file: {0} ({1})", this.DatabasePath, r)); + + if (!string.IsNullOrEmpty(password)) + { + SQLite3.Result result = SQLite3.Key(handle, password, password.Length); + if (result != SQLite3.Result.OK) + throw SQLiteException.New(r, + string.Format("Could not open database file: {0} ({1})", this.DatabasePath, r)); + } + + this._open = true; + + this.StoreDateTimeAsTicks = storeDateTimeAsTicks; + + this.BusyTimeout = TimeSpan.FromSeconds(0.1); + } + + public Sqlite3DatabaseHandle Handle { get; private set; } + + public string DatabasePath { get; private set; } + + public bool TimeExecution { get; set; } + + public bool Trace { get; set; } + + public bool StoreDateTimeAsTicks { get; private set; } + + /// + /// Sets a busy handler to sleep the specified amount of time when a table is locked. + /// The handler will sleep multiple times until a total time of has accumulated. + /// + public TimeSpan BusyTimeout + { + get { return this._busyTimeout; } + set + { + this._busyTimeout = value; + if (this.Handle != NullHandle) + SQLite3.BusyTimeout(this.Handle, (int) this._busyTimeout.TotalMilliseconds); + } + } + + /// + /// Returns the mappings from types to tables that the connection + /// currently understands. + /// + public IEnumerable TableMappings + { + get { return this._tables != null ? this._tables.Values : Enumerable.Empty(); } + } + + /// + /// Whether has been called and the database is waiting for a . + /// + public bool IsInTransaction + { + get { return this._transactionDepth > 0; } + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + public void EnableLoadExtension(int onoff) + { + SQLite3.Result r = SQLite3.EnableLoadExtension(this.Handle, onoff); + if (r != SQLite3.Result.OK) + { + string msg = SQLite3.GetErrmsg(this.Handle); + throw SQLiteException.New(r, msg); + } + } + + private static byte[] GetNullTerminatedUtf8(string s) + { + int utf8Length = Encoding.UTF8.GetByteCount(s); + byte[] bytes = new byte[utf8Length + 1]; + utf8Length = Encoding.UTF8.GetBytes(s, 0, s.Length, bytes, 0); + return bytes; + } + + /// + /// 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 TableMapping GetMapping(Type type, CreateFlags createFlags = CreateFlags.None) + { + if (this._mappings == null) this._mappings = new Dictionary(); + TableMapping map; + if (!this._mappings.TryGetValue(type.FullName, out map)) + { + map = new TableMapping(type, createFlags); + this._mappings[type.FullName] = map; + } + + return map; + } + + /// + /// Retrieves the mapping that is automatically generated for the given type. + /// + /// + /// The mapping represents the schema of the columns of the database and contains + /// methods to set and get properties of objects. + /// + public TableMapping GetMapping() + { + return GetMapping(typeof(T)); + } + + /// + /// Executes a "drop table" on the database. This is non-recoverable. + /// + public int DropTable() + { + TableMapping map = GetMapping(typeof(T)); + + string query = string.Format("drop table if exists \"{0}\"", map.TableName); + + return Execute(query); + } + + public int DropTable(Type t) + { + TableMapping map = GetMapping(t); + + string query = string.Format("drop table if exists \"{0}\"", map.TableName); + + return Execute(query); + } + + /// + /// 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. + /// + /// + /// The number of entries added to the database schema. + /// + public int CreateTable(CreateFlags createFlags = CreateFlags.None) + { + return CreateTable(typeof(T), 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. + /// + /// The number of entries added to the database schema. + /// + public int CreateTable(Type ty, CreateFlags createFlags = CreateFlags.None) + { + if (this._tables == null) this._tables = new Dictionary(); + TableMapping map; + if (!this._tables.TryGetValue(ty.FullName, out map)) + { + map = GetMapping(ty, createFlags); + this._tables.Add(ty.FullName, map); + } + + string query = "create table if not exists \"" + map.TableName + "\"(\n"; + + IEnumerable decls = map.Columns.Select(p => Orm.SqlDecl(p, this.StoreDateTimeAsTicks)); + string decl = string.Join(",\n", decls.ToArray()); + query += decl; + query += ")"; + + int count = Execute(query); + + if (count == 0) MigrateTable(map); + + Dictionary indexes = new Dictionary(); + foreach (TableMapping.Column c in map.Columns) + foreach (IndexedAttribute i in c.Indices) + { + string iname = i.Name ?? map.TableName + "_" + c.Name; + IndexInfo iinfo; + if (!indexes.TryGetValue(iname, out iinfo)) + { + iinfo = new IndexInfo + { + IndexName = iname, + TableName = map.TableName, + Unique = i.Unique, + Columns = new List() + }; + indexes.Add(iname, iinfo); + } + + if (i.Unique != iinfo.Unique) + throw new Exception( + "All the columns in an index must have the same value for their Unique property"); + + iinfo.Columns.Add(new IndexedColumn + { + Order = i.Order, + ColumnName = c.Name + }); + } + + foreach (string indexName in indexes.Keys) + { + IndexInfo index = indexes[indexName]; + string[] columns = index.Columns.OrderBy(i => i.Order).Select(i => i.ColumnName).ToArray(); + count += CreateIndex(indexName, index.TableName, columns, index.Unique); + } + + return count; + } + + /// + /// Creates an index for the specified table and columns. + /// + /// Name of the index to create + /// Name of the database table + /// An array of column names to index + /// Whether the index should be unique + public int CreateIndex(string indexName, string tableName, string[] columnNames, bool unique = false) + { + const string sqlFormat = "create {2} index if not exists \"{3}\" on \"{0}\"(\"{1}\")"; + string sql = string.Format(sqlFormat, tableName, string.Join("\", \"", columnNames), unique ? "unique" : "", + indexName); + return Execute(sql); + } + + /// + /// Creates an index for the specified table and column. + /// + /// Name of the index to create + /// Name of the database table + /// Name of the column to index + /// Whether the index should be unique + public int CreateIndex(string indexName, string tableName, string columnName, bool unique = false) + { + return CreateIndex(indexName, tableName, new[] {columnName}, unique); + } + + /// + /// Creates an index for the specified table and column. + /// + /// Name of the database table + /// Name of the column to index + /// Whether the index should be unique + public int CreateIndex(string tableName, string columnName, bool unique = false) + { + return CreateIndex(tableName + "_" + columnName, tableName, columnName, unique); + } + + /// + /// Creates an index for the specified table and columns. + /// + /// Name of the database table + /// An array of column names to index + /// Whether the index should be unique + public int CreateIndex(string tableName, string[] columnNames, bool unique = false) + { + return CreateIndex(tableName + "_" + string.Join("_", columnNames), tableName, columnNames, unique); + } + + /// + /// Creates an index for the specified object property. + /// e.g. CreateIndex(c => c.Name); + /// + /// Type to reflect to a database table. + /// Property to index + /// Whether the index should be unique + public void CreateIndex(Expression> property, bool unique = false) + { + MemberExpression mx; + if (property.Body.NodeType == ExpressionType.Convert) + mx = ((UnaryExpression) property.Body).Operand as MemberExpression; + else + mx = property.Body as MemberExpression; + PropertyInfo propertyInfo = mx.Member as PropertyInfo; + if (propertyInfo == null) + throw new ArgumentException("The lambda expression 'property' should point to a valid Property"); + + string propName = propertyInfo.Name; + + TableMapping map = GetMapping(); + string colName = map.FindColumnWithPropertyName(propName).Name; + + CreateIndex(map.TableName, colName, unique); + } + + public List GetTableInfo(string tableName) + { + string query = "pragma table_info(\"" + tableName + "\")"; + return Query(query); + } + + private void MigrateTable(TableMapping map) + { + List existingCols = GetTableInfo(map.TableName); + + List toBeAdded = new List(); + + foreach (TableMapping.Column p in map.Columns) + { + bool found = false; + foreach (ColumnInfo c in existingCols) + { + found = string.Compare(p.Name, c.Name, StringComparison.OrdinalIgnoreCase) == 0; + if (found) + break; + } + + if (!found) toBeAdded.Add(p); + } + + foreach (TableMapping.Column p in toBeAdded) + { + string addCol = "alter table \"" + map.TableName + "\" add column " + + Orm.SqlDecl(p, this.StoreDateTimeAsTicks); + Execute(addCol); + } + } + + /// + /// Creates a new SQLiteCommand. Can be overridden to provide a sub-class. + /// + /// + protected virtual SQLiteCommand NewCommand() + { + return new SQLiteCommand(this); + } + + /// + /// Creates a new SQLiteCommand given the command text with arguments. Place a '?' + /// in the command text for each of the arguments. + /// + /// + /// The fully escaped SQL. + /// + /// + /// Arguments to substitute for the occurences of '?' in the command text. + /// + /// + /// A + /// + public SQLiteCommand CreateCommand(string cmdText, params object[] ps) + { + if (!this._open) + throw SQLiteException.New(SQLite3.Result.Error, "Cannot create commands from unopened database"); + + SQLiteCommand cmd = NewCommand(); + cmd.CommandText = cmdText; + foreach (object o in ps) cmd.Bind(o); + return cmd; + } + + /// + /// 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 int Execute(string query, params object[] args) + { + SQLiteCommand cmd = CreateCommand(query, args); + + if (this.TimeExecution) + { + if (this._sw == null) this._sw = new Stopwatch(); + this._sw.Reset(); + this._sw.Start(); + } + + int r = cmd.ExecuteNonQuery(); + + if (this.TimeExecution) + { + this._sw.Stop(); + this._elapsedMilliseconds += this._sw.ElapsedMilliseconds; + Debug.WriteLine("Finished in {0} ms ({1:0.0} s total)", this._sw.ElapsedMilliseconds, + this._elapsedMilliseconds / 1000.0); + } + + return r; + } + + public T ExecuteScalar(string query, params object[] args) + { + SQLiteCommand cmd = CreateCommand(query, args); + + if (this.TimeExecution) + { + if (this._sw == null) this._sw = new Stopwatch(); + this._sw.Reset(); + this._sw.Start(); + } + + T r = cmd.ExecuteScalar(); + + if (this.TimeExecution) + { + this._sw.Stop(); + this._elapsedMilliseconds += this._sw.ElapsedMilliseconds; + Debug.WriteLine("Finished in {0} ms ({1:0.0} s total)", this._sw.ElapsedMilliseconds, + this._elapsedMilliseconds / 1000.0); + } + + return r; + } + + /// + /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?' + /// in the command text for each of the arguments and then executes that command. + /// 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. + /// + public List Query(string query, params object[] args) where T : new() + { + SQLiteCommand cmd = CreateCommand(query, args); + return cmd.ExecuteQuery(); + } + + /// + /// 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 IEnumerable DeferredQuery(string query, params object[] args) where T : new() + { + SQLiteCommand cmd = CreateCommand(query, args); + return cmd.ExecuteDeferredQuery(); + } + + /// + /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?' + /// in the command text for each of the arguments and then executes that command. + /// It returns each row of the result using the 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 List Query(TableMapping map, string query, params object[] args) + { + SQLiteCommand cmd = CreateCommand(query, args); + return cmd.ExecuteQuery(map); + } + + /// + /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?' + /// in the command text for each of the arguments and then executes that command. + /// It returns each row of the result using the specified mapping. This function is + /// only used by libraries in order to query the database via introspection. It is + /// normally not used. + /// + /// + /// A 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 IEnumerable DeferredQuery(TableMapping map, string query, params object[] args) + { + SQLiteCommand cmd = CreateCommand(query, args); + return cmd.ExecuteDeferredQuery(map); + } + + /// + /// 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 TableQuery Table() where T : new() + { + return new TableQuery(this); + } + + /// + /// 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 T Get(object pk) where T : new() + { + TableMapping map = GetMapping(typeof(T)); + return Query(map.GetByPrimaryKeySql, pk).First(); + } + + /// + /// Attempts to retrieve the first object that matches the predicate from the table + /// associated with the specified type. + /// + /// + /// A predicate for which object to find. + /// + /// + /// The object that matches the given predicate. Throws a not found exception + /// if the object is not found. + /// + public T Get(Expression> predicate) where T : new() + { + return Table().Where(predicate).First(); + } + + /// + /// Attempts to retrieve 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 T Find(object pk) where T : new() + { + TableMapping map = GetMapping(typeof(T)); + return Query(map.GetByPrimaryKeySql, pk).FirstOrDefault(); + } + + /// + /// 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 object type. + /// + /// + /// The object with the given primary key or null + /// if the object is not found. + /// + public object Find(object pk, TableMapping map) + { + return Query(map, map.GetByPrimaryKeySql, pk).FirstOrDefault(); + } + + /// + /// Attempts to retrieve the first object that matches the predicate from the table + /// associated with the specified type. + /// + /// + /// A predicate for which object to find. + /// + /// + /// The object that matches the given predicate or null + /// if the object is not found. + /// + public T Find(Expression> predicate) where T : new() + { + return Table().Where(predicate).FirstOrDefault(); + } + + /// + /// Begins a new transaction. Call to end the transaction. + /// + /// Throws if a transaction has already begun. + public void BeginTransaction() + { + // The BEGIN command only works if the transaction stack is empty, + // or in other words if there are no pending transactions. + // If the transaction stack is not empty when the BEGIN command is invoked, + // then the command fails with an error. + // Rather than crash with an error, we will just ignore calls to BeginTransaction + // that would result in an error. + if (Interlocked.CompareExchange(ref this._transactionDepth, 1, 0) == 0) + try + { + Execute("begin transaction"); + } + catch (Exception ex) + { + SQLiteException sqlExp = ex as SQLiteException; + if (sqlExp != null) + switch (sqlExp.Result) + { + case SQLite3.Result.IOError: + case SQLite3.Result.Full: + case SQLite3.Result.Busy: + case SQLite3.Result.NoMem: + case SQLite3.Result.Interrupt: + RollbackTo(null, true); + break; + } + else + Interlocked.Decrement(ref this._transactionDepth); + + throw; + } + else + throw new InvalidOperationException("Cannot begin a transaction while already in a transaction."); + } + + /// + /// Creates a savepoint in the database at the current point in the transaction timeline. + /// Begins a new transaction if one is not in progress. + /// Call to undo transactions since the returned savepoint. + /// Call to commit transactions after the savepoint returned here. + /// Call to end the transaction, committing all changes. + /// + /// A string naming the savepoint. + public string SaveTransactionPoint() + { + int depth = Interlocked.Increment(ref this._transactionDepth) - 1; + string retVal = "S" + this._rand.Next(short.MaxValue) + "D" + depth; + + try + { + Execute("savepoint " + retVal); + } + catch (Exception ex) + { + SQLiteException sqlExp = ex as SQLiteException; + if (sqlExp != null) + switch (sqlExp.Result) + { + case SQLite3.Result.IOError: + case SQLite3.Result.Full: + case SQLite3.Result.Busy: + case SQLite3.Result.NoMem: + case SQLite3.Result.Interrupt: + RollbackTo(null, true); + break; + } + else + Interlocked.Decrement(ref this._transactionDepth); + + throw; + } + + return retVal; + } + + /// + /// Rolls back the transaction that was begun by or + /// . + /// + public void Rollback() + { + RollbackTo(null, false); + } + + /// + /// Rolls back the savepoint created by or SaveTransactionPoint. + /// + /// + /// The name of the savepoint to roll back to, as returned by . + /// If savepoint is null or empty, this method is equivalent to a call to + /// + public void RollbackTo(string savepoint) + { + RollbackTo(savepoint, false); + } + + /// + /// Rolls back the transaction that was begun by . + /// + /// true to avoid throwing exceptions, false otherwise + private void RollbackTo(string savepoint, bool noThrow) + { + // Rolling back without a TO clause rolls backs all transactions + // and leaves the transaction stack empty. + try + { + if (string.IsNullOrEmpty(savepoint)) + { + if (Interlocked.Exchange(ref this._transactionDepth, 0) > 0) Execute("rollback"); + } + else + { + DoSavePointExecute(savepoint, "rollback to "); + } + } + catch (SQLiteException) + { + if (!noThrow) + throw; + } + + // No need to rollback if there are no transactions open. + } + + /// + /// Releases a savepoint returned from . Releasing a savepoint + /// makes changes since that savepoint permanent if the savepoint began the transaction, + /// or otherwise the changes are permanent pending a call to . + /// The RELEASE command is like a COMMIT for a SAVEPOINT. + /// + /// + /// The name of the savepoint to release. The string should be the result of a call to + /// + /// + public void Release(string savepoint) + { + DoSavePointExecute(savepoint, "release "); + } + + private void DoSavePointExecute(string savepoint, string cmd) + { + // Validate the savepoint + int firstLen = savepoint.IndexOf('D'); + if (firstLen >= 2 && savepoint.Length > firstLen + 1) + { + int depth; + if (int.TryParse(savepoint.Substring(firstLen + 1), out depth)) + if (0 <= depth && depth < this._transactionDepth) + { +#if NETFX_CORE Volatile.Write (ref _transactionDepth, depth); - #elif SILVERLIGHT +#elif SILVERLIGHT _transactionDepth = depth; - #else - Thread.VolatileWrite (ref _transactionDepth, depth); - #endif - Execute (cmd + savepoint); - return; - } - } - } - - throw new ArgumentException ("savePoint is not valid, and should be the result of a call to SaveTransactionPoint.", "savePoint"); - } - - /// - /// Commits the transaction that was begun by . - /// - public void Commit () - { - if (Interlocked.Exchange (ref _transactionDepth, 0) != 0) { - Execute ("commit"); - } - // Do nothing on a commit with no open transaction - } - - /// - /// 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 void RunInTransaction (Action action) - { - try { - var savePoint = SaveTransactionPoint (); - action (); - Release (savePoint); - } catch (Exception) { - Rollback (); - throw; - } - } - - /// - /// Inserts all specified objects. - /// - /// - /// An of the objects to insert. - /// - /// - /// The number of rows added to the table. - /// - public int InsertAll (System.Collections.IEnumerable objects) - { - var c = 0; - RunInTransaction(() => { - foreach (var r in objects) { - c += Insert (r); - } - }); - return c; - } - - /// - /// Inserts all specified objects. - /// - /// - /// An of the objects to insert. - /// - /// - /// Literal SQL code that gets placed into the command. INSERT {extra} INTO ... - /// - /// - /// The number of rows added to the table. - /// - public int InsertAll (System.Collections.IEnumerable objects, string extra) - { - var c = 0; - RunInTransaction (() => { - foreach (var r in objects) { - c += Insert (r, extra); - } - }); - return c; - } - - /// - /// Inserts all specified objects. - /// - /// - /// An of the objects to insert. - /// - /// - /// The type of object to insert. - /// - /// - /// The number of rows added to the table. - /// - public int InsertAll (System.Collections.IEnumerable objects, Type objType) - { - var c = 0; - RunInTransaction (() => { - foreach (var r in objects) { - c += Insert (r, objType); - } - }); - return c; - } - - /// - /// 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 int Insert (object obj) - { - if (obj == null) { - return 0; - } - return Insert (obj, "", obj.GetType ()); - } - - /// - /// Inserts the given object and retrieves its - /// auto incremented primary key if it has one. - /// 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 int InsertOrReplace (object obj) - { - if (obj == null) { - return 0; - } - return Insert (obj, "OR REPLACE", obj.GetType ()); - } - - /// - /// Inserts the given object and retrieves its - /// auto incremented primary key if it has one. - /// - /// - /// The object to insert. - /// - /// - /// The type of object to insert. - /// - /// - /// The number of rows added to the table. - /// - public int Insert (object obj, Type objType) - { - return Insert (obj, "", objType); - } - - /// - /// Inserts the given object and retrieves its - /// auto incremented primary key if it has one. - /// 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 int InsertOrReplace (object obj, Type objType) - { - return Insert (obj, "OR REPLACE", objType); - } - - /// - /// Inserts the given object and retrieves its - /// auto incremented primary key if it has one. - /// - /// - /// The object to insert. - /// - /// - /// Literal SQL code that gets placed into the command. INSERT {extra} INTO ... - /// - /// - /// The number of rows added to the table. - /// - public int Insert (object obj, string extra) - { - if (obj == null) { - return 0; - } - return Insert (obj, extra, obj.GetType ()); - } - - /// - /// Inserts the given object and retrieves its - /// auto incremented primary key if it has one. - /// - /// - /// The object to insert. - /// - /// - /// Literal SQL code that gets placed into the command. INSERT {extra} INTO ... - /// - /// - /// The type of object to insert. - /// - /// - /// The number of rows added to the table. - /// - public int Insert (object obj, string extra, Type objType) - { - if (obj == null || objType == null) { - return 0; - } - - - var map = GetMapping (objType); - - #if NETFX_CORE - if (map.PK != null && map.PK.IsAutoGuid) - { - // no GetProperty so search our way up the inheritance chain till we find it - PropertyInfo prop; - while (objType != null) - { - var info = objType.GetTypeInfo(); - prop = info.GetDeclaredProperty(map.PK.PropertyName); - if (prop != null) - { - if (prop.GetValue(obj, null).Equals(Guid.Empty)) - { - prop.SetValue(obj, Guid.NewGuid(), null); - } - break; - } - - objType = info.BaseType; - } - } - #else - if (map.PK != null && map.PK.IsAutoGuid) { - var prop = objType.GetProperty(map.PK.PropertyName); - if (prop != null) { - if (prop.GetValue(obj, null).Equals(Guid.Empty)) { - prop.SetValue(obj, Guid.NewGuid(), null); - } - } - } - #endif - - - var replacing = string.Compare (extra, "OR REPLACE", StringComparison.OrdinalIgnoreCase) == 0; - - var cols = replacing ? map.InsertOrReplaceColumns : map.InsertColumns; - var vals = new object[cols.Length]; - for (var i = 0; i < vals.Length; i++) { - vals [i] = cols [i].GetValue (obj); - } - - var insertCmd = map.GetInsertCommand (this, extra); - int count; - - try { - count = insertCmd.ExecuteNonQuery (vals); - } - catch (SQLiteException ex) { - - if (SQLite3.ExtendedErrCode (this.Handle) == SQLite3.ExtendedResult.ConstraintNotNull) { - throw NotNullConstraintViolationException.New (ex.Result, ex.Message, map, obj); - } - throw; - } - - if (map.HasAutoIncPK) - { - var id = SQLite3.LastInsertRowid (Handle); - map.SetAutoIncPK (obj, id); - } - - return count; - } - - /// - /// 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 int Update (object obj) - { - if (obj == null) { - return 0; - } - return Update (obj, obj.GetType ()); - } - - /// - /// 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 int Update (object obj, Type objType) { - int rowsAffected = 0; - if (obj == null || objType == null) { - return 0; - } - - var map = GetMapping (objType); - var pk = map.PK; - - if (pk == null) { - throw new NotSupportedException ("Cannot update " + map.TableName + ": it has no PK"); - } - - var cols = from p in map.Columns where p != pk select p; - var vals = from c in cols select c.GetValue (obj); - var ps = new List (vals); - ps.Add (pk.GetValue (obj)); - - var q = string.Format ("update \"{0}\" set {1} where {2} = ? ", map.TableName, string.Join (",", (from c in cols - select "\"" + c.Name + "\" = ? ").ToArray ()), pk.Name); - - try { - rowsAffected = Execute (q, ps.ToArray ()); - } - catch (SQLiteException ex) { - - if (ex.Result == SQLite3.Result.Constraint && SQLite3.ExtendedErrCode (this.Handle) == SQLite3.ExtendedResult.ConstraintNotNull) { - throw NotNullConstraintViolationException.New (ex, map, obj); - } - - throw ex; - } - - return rowsAffected; - } - - /// - /// Updates all specified objects. - /// - /// - /// An of the objects to insert. - /// - /// - /// The number of rows modified. - /// - public int UpdateAll (System.Collections.IEnumerable objects) { - var c = 0; - RunInTransaction (() => { - foreach (var r in objects) { - c += Update (r); - } - }); - return c; - } - - /// - /// 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 int Delete (object objectToDelete) - { - var map = GetMapping (objectToDelete.GetType ()); - var pk = map.PK; - if (pk == null) { - throw new NotSupportedException ("Cannot delete " + map.TableName + ": it has no PK"); - } - var q = string.Format ("delete from \"{0}\" where \"{1}\" = ?", map.TableName, pk.Name); - return Execute (q, pk.GetValue (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 int Delete (object primaryKey) - { - var map = GetMapping (typeof (T)); - var pk = map.PK; - if (pk == null) { - throw new NotSupportedException ("Cannot delete " + map.TableName + ": it has no PK"); - } - var q = string.Format ("delete from \"{0}\" where \"{1}\" = ?", map.TableName, pk.Name); - return Execute (q, primaryKey); - } - - /// - /// 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 int DeleteAll () - { - var map = GetMapping (typeof (T)); - var query = string.Format("delete from \"{0}\"", map.TableName); - return Execute (query); - } - - ~SQLiteConnection () - { - Dispose (false); - } - - public void Dispose () - { - Dispose (true); - GC.SuppressFinalize (this); - } - - protected virtual void Dispose (bool disposing) - { - Close (); - } - - public void Close () - { - if (_open && Handle != NullHandle) { - try { - if (_mappings != null) { - foreach (var sqlInsertCommand in _mappings.Values) { - sqlInsertCommand.Dispose(); - } - } - var r = SQLite3.Close (Handle); - if (r != SQLite3.Result.OK) { - string msg = SQLite3.GetErrmsg (Handle); - throw SQLiteException.New (r, msg); - } - } - finally { - Handle = NullHandle; - _open = false; - } - } - } - } - - /// - /// Represents a parsed connection string. - /// - class SQLiteConnectionString - { - public string ConnectionString { get; private set; } - public string DatabasePath { get; private set; } - public bool StoreDateTimeAsTicks { get; private set; } - - #if NETFX_CORE - static readonly string MetroStyleDataPath = Windows.Storage.ApplicationData.Current.LocalFolder.Path; - #endif - - public SQLiteConnectionString (string databasePath, bool storeDateTimeAsTicks) - { - ConnectionString = databasePath; - StoreDateTimeAsTicks = storeDateTimeAsTicks; - - #if NETFX_CORE - DatabasePath = System.IO.Path.Combine (MetroStyleDataPath, databasePath); - #else - DatabasePath = databasePath; - #endif - } - } - - - - public class TableMapping { - public Type MappedType { get; private set; } - public string TableName { get; private set; } - public Column[] Columns { get; private set; } - public Column PK { get; private set; } - public string GetByPrimaryKeySql { get; private set; } - - Column _autoPk; - Column[] _insertColumns; - Column[] _insertOrReplaceColumns; - - public TableMapping(Type type, CreateFlags createFlags = CreateFlags.None) - { - MappedType = type; - - #if NETFX_CORE - var tableAttr = (TableAttribute)System.Reflection.CustomAttributeExtensions - .GetCustomAttribute(type.GetTypeInfo(), typeof(TableAttribute), true); - #else - var tableAttr = (TableAttribute)type.GetCustomAttributes (typeof (TableAttribute), true).FirstOrDefault (); - #endif - - TableName = tableAttr != null ? tableAttr.Name : MappedType.Name; - - #if !NETFX_CORE - var props = MappedType.GetProperties (BindingFlags.Public | BindingFlags.Instance | BindingFlags.SetProperty); - #else - var props = from p in MappedType.GetRuntimeProperties() - where ((p.GetMethod != null && p.GetMethod.IsPublic) || (p.SetMethod != null && p.SetMethod.IsPublic) || (p.GetMethod != null && p.GetMethod.IsStatic) || (p.SetMethod != null && p.SetMethod.IsStatic)) - select p; - #endif - var cols = new List (); - foreach (var p in props) { - #if !NETFX_CORE - var ignore = p.GetCustomAttributes (typeof(IgnoreAttribute), true).Length > 0; - #else - var ignore = p.GetCustomAttributes (typeof(IgnoreAttribute), true).Count() > 0; - #endif - if (p.CanWrite && !ignore) { - cols.Add (new Column (p, createFlags)); - } - } - Columns = cols.ToArray (); - foreach (var c in Columns) { - if (c.IsAutoInc && c.IsPK) { - _autoPk = c; - } - if (c.IsPK) { - PK = c; - } - } - - HasAutoIncPK = _autoPk != null; - - if (PK != null) { - GetByPrimaryKeySql = string.Format ("select * from \"{0}\" where \"{1}\" = ?", TableName, PK.Name); - } - else { - // People should not be calling Get/Find without a PK - GetByPrimaryKeySql = string.Format ("select * from \"{0}\" limit 1", TableName); - } - } - - public bool HasAutoIncPK { get; private set; } - - public void SetAutoIncPK (object obj, long id) - { - if (_autoPk != null) { - _autoPk.SetValue (obj, Convert.ChangeType (id, _autoPk.ColumnType, null)); - } - } - - public Column[] InsertColumns { - get { - if (_insertColumns == null) { - _insertColumns = Columns.Where (c => !c.IsAutoInc).ToArray (); - } - return _insertColumns; - } - } - - public Column[] InsertOrReplaceColumns { - get { - if (_insertOrReplaceColumns == null) { - _insertOrReplaceColumns = Columns.ToArray (); - } - return _insertOrReplaceColumns; - } - } - - public Column FindColumnWithPropertyName (string propertyName) - { - var exact = Columns.FirstOrDefault (c => c.PropertyName == propertyName); - return exact; - } - - public Column FindColumn (string columnName) - { - var exact = Columns.FirstOrDefault (c => c.Name == columnName); - return exact; - } - - PreparedSqlLiteInsertCommand _insertCommand; - string _insertCommandExtra; - - public PreparedSqlLiteInsertCommand GetInsertCommand(SQLiteConnection conn, string extra) - { - if (_insertCommand == null) { - _insertCommand = CreateInsertCommand(conn, extra); - _insertCommandExtra = extra; - } - else if (_insertCommandExtra != extra) { - _insertCommand.Dispose(); - _insertCommand = CreateInsertCommand(conn, extra); - _insertCommandExtra = extra; - } - return _insertCommand; - } - - PreparedSqlLiteInsertCommand CreateInsertCommand(SQLiteConnection conn, string extra) - { - var cols = InsertColumns; - string insertSql; - if (!cols.Any() && Columns.Count() == 1 && Columns[0].IsAutoInc) - { - insertSql = string.Format("insert {1} into \"{0}\" default values", TableName, extra); - } - else - { - var replacing = string.Compare (extra, "OR REPLACE", StringComparison.OrdinalIgnoreCase) == 0; - - if (replacing) { - cols = InsertOrReplaceColumns; - } - - insertSql = string.Format("insert {3} into \"{0}\"({1}) values ({2})", TableName, - string.Join(",", (from c in cols - select "\"" + c.Name + "\"").ToArray()), - string.Join(",", (from c in cols - select "?").ToArray()), extra); - - } - - var insertCommand = new PreparedSqlLiteInsertCommand(conn); - insertCommand.CommandText = insertSql; - return insertCommand; - } - - protected internal void Dispose() - { - if (_insertCommand != null) { - _insertCommand.Dispose(); - _insertCommand = null; - } - } - - public class Column - { - PropertyInfo _prop; - - public string Name { get; private set; } - - public string PropertyName { get { return _prop.Name; } } - - public Type ColumnType { get; private set; } - - public string Collation { get; private set; } - - public bool IsAutoInc { get; private set; } - public bool IsAutoGuid { get; private set; } - - public bool IsPK { get; private set; } - - public IEnumerable Indices { get; set; } - - public bool IsNullable { get; private set; } - - public int? MaxStringLength { get; private set; } - - public Column(PropertyInfo prop, CreateFlags createFlags = CreateFlags.None) - { - var colAttr = (ColumnAttribute)prop.GetCustomAttributes(typeof(ColumnAttribute), true).FirstOrDefault(); - - _prop = prop; - Name = colAttr == null ? prop.Name : colAttr.Name; - //If this type is Nullable then Nullable.GetUnderlyingType returns the T, otherwise it returns null, so get the actual type instead - ColumnType = Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType; - Collation = Orm.Collation(prop); - - IsPK = Orm.IsPK(prop) || - (((createFlags & CreateFlags.ImplicitPK) == CreateFlags.ImplicitPK) && - string.Compare (prop.Name, Orm.ImplicitPkName, StringComparison.OrdinalIgnoreCase) == 0); - - var isAuto = Orm.IsAutoInc(prop) || (IsPK && ((createFlags & CreateFlags.AutoIncPK) == CreateFlags.AutoIncPK)); - IsAutoGuid = isAuto && ColumnType == typeof(Guid); - IsAutoInc = isAuto && !IsAutoGuid; - - Indices = Orm.GetIndices(prop); - if (!Indices.Any() - && !IsPK - && ((createFlags & CreateFlags.ImplicitIndex) == CreateFlags.ImplicitIndex) - && Name.EndsWith (Orm.ImplicitIndexSuffix, StringComparison.OrdinalIgnoreCase) - ) - { - Indices = new IndexedAttribute[] { new IndexedAttribute() }; - } - IsNullable = !(IsPK || Orm.IsMarkedNotNull(prop)); - MaxStringLength = Orm.MaxStringLength(prop); - } - - public void SetValue (object obj, object val) - { - _prop.SetValue (obj, val, null); - } - - public object GetValue (object obj) - { - return _prop.GetValue (obj, null); - } - } - } - - public static class Orm - { - public const int DefaultMaxStringLength = 140; - public const string ImplicitPkName = "Id"; - public const string ImplicitIndexSuffix = "Id"; - - public static string SqlDecl (TableMapping.Column p, bool storeDateTimeAsTicks) - { - string decl = "\"" + p.Name + "\" " + SqlType (p, storeDateTimeAsTicks) + " "; - - if (p.IsPK) { - decl += "primary key "; - } - if (p.IsAutoInc) { - decl += "autoincrement "; - } - if (!p.IsNullable) { - decl += "not null "; - } - if (!string.IsNullOrEmpty (p.Collation)) { - decl += "collate " + p.Collation + " "; - } - - return decl; - } - - public static string SqlType (TableMapping.Column p, bool storeDateTimeAsTicks) - { - var clrType = p.ColumnType; - if (clrType == typeof(Boolean) || clrType == typeof(Byte) || clrType == typeof(UInt16) || clrType == typeof(SByte) || clrType == typeof(Int16) || clrType == typeof(Int32)) { - return "integer"; - } else if (clrType == typeof(UInt32) || clrType == typeof(Int64)) { - return "bigint"; - } else if (clrType == typeof(Single) || clrType == typeof(Double) || clrType == typeof(Decimal)) { - return "float"; - } else if (clrType == typeof(String)) { - int? len = p.MaxStringLength; - - if (len.HasValue) - return "varchar(" + len.Value + ")"; - - return "varchar"; - } else if (clrType == typeof(TimeSpan)) { - return "bigint"; - } else if (clrType == typeof(DateTime)) { - return storeDateTimeAsTicks ? "bigint" : "datetime"; - } else if (clrType == typeof(DateTimeOffset)) { - return "bigint"; - #if !NETFX_CORE - } else if (clrType.IsEnum) { - #else - } else if (clrType.GetTypeInfo().IsEnum) { - #endif - return "integer"; - } else if (clrType == typeof(byte[])) { - return "blob"; - } else if (clrType == typeof(Guid)) { - return "varchar(36)"; - } else { - throw new NotSupportedException ("Don't know about " + clrType); - } - } - - public static bool IsPK (MemberInfo p) - { - var attrs = p.GetCustomAttributes (typeof(PrimaryKeyAttribute), true); - #if !NETFX_CORE - return attrs.Length > 0; - #else - return attrs.Count() > 0; - #endif - } - - public static string Collation (MemberInfo p) - { - var attrs = p.GetCustomAttributes (typeof(CollationAttribute), true); - #if !NETFX_CORE - if (attrs.Length > 0) { - return ((CollationAttribute)attrs [0]).Value; - } - #else - if (attrs.Count() > 0) { - return ((CollationAttribute)attrs.First()).Value; - } - #endif - else { - return string.Empty; - } - } - - public static bool IsAutoInc (MemberInfo p) - { - var attrs = p.GetCustomAttributes (typeof(AutoIncrementAttribute), true); - #if !NETFX_CORE - return attrs.Length > 0; - #else - return attrs.Count() > 0; - #endif - } - - public static IEnumerable GetIndices(MemberInfo p) - { - var attrs = p.GetCustomAttributes(typeof(IndexedAttribute), true); - return attrs.Cast(); - } - - public static int? MaxStringLength(PropertyInfo p) - { - var attrs = p.GetCustomAttributes (typeof(MaxLengthAttribute), true); - #if !NETFX_CORE - if (attrs.Length > 0) - return ((MaxLengthAttribute)attrs [0]).Value; - #else - if (attrs.Count() > 0) - return ((MaxLengthAttribute)attrs.First()).Value; - #endif - - return null; - } - - public static bool IsMarkedNotNull(MemberInfo p) - { - var attrs = p.GetCustomAttributes (typeof (NotNullAttribute), true); - #if !NETFX_CORE - return attrs.Length > 0; - #else - return attrs.Count() > 0; - #endif - } - } - - public partial class SQLiteCommand - { - SQLiteConnection _conn; - private List _bindings; - - public string CommandText { get; set; } - - internal SQLiteCommand (SQLiteConnection conn) - { - _conn = conn; - _bindings = new List (); - CommandText = ""; - } - - public int ExecuteNonQuery () - { - if (_conn.Trace) { - System.Diagnostics.Debug.WriteLine ("Executing: " + this); - } - - var r = SQLite3.Result.OK; - var stmt = Prepare (); - r = SQLite3.Step (stmt); - Finalize (stmt); - if (r == SQLite3.Result.Done) { - int rowsAffected = SQLite3.Changes (_conn.Handle); - return rowsAffected; - } else if (r == SQLite3.Result.Error) { - string msg = SQLite3.GetErrmsg (_conn.Handle); - throw SQLiteException.New (r, msg); - } - else if (r == SQLite3.Result.Constraint) { - if (SQLite3.ExtendedErrCode (_conn.Handle) == SQLite3.ExtendedResult.ConstraintNotNull) { - throw NotNullConstraintViolationException.New (r, SQLite3.GetErrmsg (_conn.Handle)); - } - } - - throw SQLiteException.New(r, r.ToString()); - } - - public IEnumerable ExecuteDeferredQuery () - { - return ExecuteDeferredQuery(_conn.GetMapping(typeof(T))); - } - - public List ExecuteQuery () - { - return ExecuteDeferredQuery(_conn.GetMapping(typeof(T))).ToList(); - } - - public List ExecuteQuery (TableMapping map) - { - return ExecuteDeferredQuery(map).ToList(); - } - - /// - /// Invoked every time an instance is loaded from the database. - /// - /// - /// The newly created object. - /// - /// - /// This can be overridden in combination with the - /// method to hook into the life-cycle of objects. - /// - /// Type safety is not possible because MonoTouch does not support virtual generic methods. - /// - protected virtual void OnInstanceCreated (object obj) - { - // Can be overridden. - } - - public IEnumerable ExecuteDeferredQuery (TableMapping map) - { - if (_conn.Trace) { - System.Diagnostics.Debug.WriteLine ("Executing Query: " + this); - } - - var stmt = Prepare (); - try - { - var cols = new TableMapping.Column[SQLite3.ColumnCount (stmt)]; - - for (int i = 0; i < cols.Length; i++) { - var name = SQLite3.ColumnName16 (stmt, i); - cols [i] = map.FindColumn (name); - } - - while (SQLite3.Step (stmt) == SQLite3.Result.Row) { - var obj = Activator.CreateInstance(map.MappedType); - for (int i = 0; i < cols.Length; i++) { - if (cols [i] == null) - continue; - var colType = SQLite3.ColumnType (stmt, i); - var val = ReadCol (stmt, i, colType, cols [i].ColumnType); - cols [i].SetValue (obj, val); - } - OnInstanceCreated (obj); - yield return (T)obj; - } - } - finally - { - SQLite3.Finalize(stmt); - } - } - - public T ExecuteScalar () - { - if (_conn.Trace) { - System.Diagnostics.Debug.WriteLine ("Executing Query: " + this); - } - - T val = default(T); - - var stmt = Prepare (); - - try - { - var r = SQLite3.Step (stmt); - if (r == SQLite3.Result.Row) { - var colType = SQLite3.ColumnType (stmt, 0); - val = (T)ReadCol (stmt, 0, colType, typeof(T)); - } - else if (r == SQLite3.Result.Done) { - } - else - { - throw SQLiteException.New (r, SQLite3.GetErrmsg (_conn.Handle)); - } - } - finally - { - Finalize (stmt); - } - - return val; - } - - public void Bind (string name, object val) - { - _bindings.Add (new Binding { - Name = name, - Value = val - }); - } - - public void Bind (object val) - { - Bind (null, val); - } - - public override string ToString () - { - var parts = new string[1 + _bindings.Count]; - parts [0] = CommandText; - var i = 1; - foreach (var b in _bindings) { - parts [i] = string.Format (" {0}: {1}", i - 1, b.Value); - i++; - } - return string.Join (Environment.NewLine, parts); - } - - Sqlite3Statement Prepare() { - var stmt = SQLite3.Prepare2 (_conn.Handle, CommandText); - BindAll (stmt); - return stmt; - } - - void Finalize (Sqlite3Statement stmt) { - SQLite3.Finalize (stmt); - } - - void BindAll (Sqlite3Statement stmt) { - int nextIdx = 1; - foreach (var b in _bindings) { - if (b.Name != null) { - b.Index = SQLite3.BindParameterIndex (stmt, b.Name); - } else { - b.Index = nextIdx++; - } - - BindParameter (stmt, b.Index, b.Value, _conn.StoreDateTimeAsTicks); - } - } - - internal static IntPtr NegativePointer = new IntPtr (-1); - - internal static int BindParameter (Sqlite3Statement stmt, int index, object value, bool storeDateTimeAsTicks) - { - if (value == null) - return SQLite3.BindNull (stmt, index); - if (value is Int32) - return SQLite3.BindInt (stmt, index, (int)value); - if (value is String) - return SQLite3.BindText (stmt, index, (string)value, -1, NegativePointer); - if (value is Byte || value is UInt16 || value is SByte || value is Int16) - return SQLite3.BindInt (stmt, index, Convert.ToInt32 (value)); - if (value is Boolean) - return SQLite3.BindInt (stmt, index, (bool)value ? 1 : 0); - if (value is UInt32 || value is Int64) - return SQLite3.BindInt64 (stmt, index, Convert.ToInt64 (value)); - if (value is Single || value is Double || value is Decimal) - return SQLite3.BindDouble (stmt, index, Convert.ToDouble (value)); - if (value is TimeSpan) - return SQLite3.BindInt64(stmt, index, ((TimeSpan)value).Ticks); - if (value is DateTime) { - if (storeDateTimeAsTicks) { - return SQLite3.BindInt64 (stmt, index, ((DateTime)value).Ticks); - } - else { - return SQLite3.BindText (stmt, index, ((DateTime)value).ToString ("yyyy-MM-dd HH:mm:ss"), -1, NegativePointer); - } - } - if (value is DateTimeOffset) - return SQLite3.BindInt64 (stmt, index, ((DateTimeOffset)value).UtcTicks); - - - { - #if !NETFX_CORE - if (value.GetType ().IsEnum) - #else - if (value.GetType().GetTypeInfo().IsEnum) - #endif - return SQLite3.BindInt (stmt, index, Convert.ToInt32 (value)); - } - - if (value is byte[]) - return SQLite3.BindBlob(stmt, index, (byte[]) value, ((byte[]) value).Length, NegativePointer); - if (value is Guid) - return SQLite3.BindText(stmt, index, ((Guid)value).ToString(), 72, NegativePointer); - - - throw new NotSupportedException("Cannot store type: " + value.GetType()); - } - - class Binding { - public string Name { get; set; } - public object Value { get; set; } - public int Index { get; set; } - } - - - object ReadCol (Sqlite3Statement stmt, int index, SQLite3.ColType type, Type clrType) - { - if (type == SQLite3.ColType.Null) - return null; - - if (clrType == typeof(String)) { - return SQLite3.ColumnString (stmt, index); - } else if (clrType == typeof(Int32)) { - return (int)SQLite3.ColumnInt (stmt, index); - } else if (clrType == typeof(Boolean)) { - return SQLite3.ColumnInt (stmt, index) == 1; - } else if (clrType == typeof(double)) { - return SQLite3.ColumnDouble (stmt, index); - } else if (clrType == typeof(float)) { - return (float)SQLite3.ColumnDouble (stmt, index); - } else if (clrType == typeof(TimeSpan)) { - return new TimeSpan(SQLite3.ColumnInt64(stmt, index)); - } else if (clrType == typeof(DateTime)) { - if (_conn.StoreDateTimeAsTicks) { - return new DateTime (SQLite3.ColumnInt64 (stmt, index)); - } - else { - var text = SQLite3.ColumnString (stmt, index); - return DateTime.Parse (text); - } - } else if (clrType == typeof(DateTimeOffset)) { - return new DateTimeOffset(SQLite3.ColumnInt64 (stmt, index),TimeSpan.Zero); - #if !NETFX_CORE - } else if (clrType.IsEnum) { - #else - } else if (clrType.GetTypeInfo().IsEnum) { - #endif - return SQLite3.ColumnInt (stmt, index); - } else if (clrType == typeof(Int64)) { - return SQLite3.ColumnInt64 (stmt, index); - } else if (clrType == typeof(UInt32)) { - return (uint)SQLite3.ColumnInt64 (stmt, index); - } else if (clrType == typeof(decimal)) { - return (decimal)SQLite3.ColumnDouble (stmt, index); - } else if (clrType == typeof(Byte)) { - return (byte)SQLite3.ColumnInt (stmt, index); - } else if (clrType == typeof(UInt16)) { - return (ushort)SQLite3.ColumnInt (stmt, index); - } else if (clrType == typeof(Int16)) { - return (short)SQLite3.ColumnInt (stmt, index); - } else if (clrType == typeof(sbyte)) { - return (sbyte)SQLite3.ColumnInt (stmt, index); - } else if (clrType == typeof(byte[])) { - return SQLite3.ColumnByteArray (stmt, index); - } else if (clrType == typeof(Guid)) { - var text = SQLite3.ColumnString(stmt, index); - return new Guid(text); - } else{ - throw new NotSupportedException ("Don't know how to read " + clrType); - } - } - } - - /// - /// Since the insert never changed, we only need to prepare once. - /// - public class PreparedSqlLiteInsertCommand : IDisposable - { - public bool Initialized { get; set; } - public string CommandText { get; set; } - - protected SQLiteConnection Connection { get; set; } - protected Sqlite3Statement Statement { get; set; } - internal static readonly Sqlite3Statement NullStatement = default(Sqlite3Statement); - - internal PreparedSqlLiteInsertCommand (SQLiteConnection conn) { - Connection = conn; - } - - public int ExecuteNonQuery (object[] source) { - if (Connection.Trace) { - System.Diagnostics.Debug.WriteLine ("Executing: " + CommandText); - } - - var r = SQLite3.Result.OK; - - if (!Initialized) { - Statement = Prepare (); - Initialized = true; - } - - //bind the values. - if (source != null) { - for (int i = 0; i < source.Length; i++) { - SQLiteCommand.BindParameter (Statement, i + 1, source [i], Connection.StoreDateTimeAsTicks); - } - } - r = SQLite3.Step (Statement); - - if (r == SQLite3.Result.Done) { - int rowsAffected = SQLite3.Changes (Connection.Handle); - SQLite3.Reset (Statement); - return rowsAffected; - } - else if (r == SQLite3.Result.Error) { - string msg = SQLite3.GetErrmsg (Connection.Handle); - SQLite3.Reset (Statement); - throw SQLiteException.New (r, msg); - } - else if (r == SQLite3.Result.Constraint && SQLite3.ExtendedErrCode (Connection.Handle) == SQLite3.ExtendedResult.ConstraintNotNull) { - SQLite3.Reset (Statement); - throw NotNullConstraintViolationException.New (r, SQLite3.GetErrmsg (Connection.Handle)); - } - else { - SQLite3.Reset (Statement); - throw SQLiteException.New (r, r.ToString ()); - } - } - - protected virtual Sqlite3Statement Prepare () - { - var stmt = SQLite3.Prepare2 (Connection.Handle, CommandText); - return stmt; - } - - public void Dispose () { - Dispose (true); - GC.SuppressFinalize (this); - } - - private void Dispose (bool disposing) { - if (Statement != NullStatement) { - try { - SQLite3.Finalize (Statement); - } - finally { - Statement = NullStatement; - Connection = null; - } - } - } - - ~PreparedSqlLiteInsertCommand () { - Dispose (false); - } - } - - public abstract class BaseTableQuery - { - protected class Ordering - { - public string ColumnName { get; set; } - public bool Ascending { get; set; } - } - } - - public class TableQuery : BaseTableQuery, IEnumerable - { - public SQLiteConnection Connection { get; private set; } - - public TableMapping Table { get; private set; } - - Expression _where; - List _orderBys; - int? _limit; - int? _offset; - bool _deferred; - - BaseTableQuery _joinInner; - Expression _joinInnerKeySelector; - BaseTableQuery _joinOuter; - Expression _joinOuterKeySelector; - Expression _joinSelector; - - Expression _selector; - - class CompileResult { - public string CommandText { get; set; } - public object Value { get; set; } - } - - TableQuery (SQLiteConnection conn, TableMapping table) { - Connection = conn; - Table = table; - } - - public TableQuery (SQLiteConnection conn) { - Connection = conn; - Table = Connection.GetMapping (typeof(T)); - } - - public TableQuery Clone () { - var q = new TableQuery (Connection, Table); - q._where = _where; - q._deferred = _deferred; - if (_orderBys != null) { - q._orderBys = new List (_orderBys); - } - q._limit = _limit; - q._offset = _offset; - q._joinInner = _joinInner; - q._joinInnerKeySelector = _joinInnerKeySelector; - q._joinOuter = _joinOuter; - q._joinOuterKeySelector = _joinOuterKeySelector; - q._joinSelector = _joinSelector; - q._selector = _selector; - return q; - } - - public TableQuery Where (Expression> predExpr) { - if (predExpr.NodeType == ExpressionType.Lambda) { - var lambda = (LambdaExpression)predExpr; - var pred = lambda.Body; - var q = Clone (); - q.AddWhere (pred); - return q; - } - else { - throw new NotSupportedException ("Must be a predicate"); - } - } - - public TableQuery Take (int n) { - var q = Clone (); - q._limit = n; - return q; - } - - public TableQuery Skip (int n) { - var q = Clone (); - q._offset = n; - return q; - } - - public T ElementAt (int index) { - return Skip (index).Take (1).First (); - } - - public TableQuery Deferred () { - var q = Clone (); - q._deferred = true; - return q; - } - - public TableQuery OrderBy (Expression> orderExpr) { - return AddOrderBy (orderExpr, true); - } - - public TableQuery OrderByDescending (Expression> orderExpr) { - return AddOrderBy (orderExpr, false); - } - - public TableQuery ThenBy(Expression> orderExpr) { - return AddOrderBy(orderExpr, true); - } - - public TableQuery ThenByDescending(Expression> orderExpr) { - return AddOrderBy(orderExpr, false); - } - - private TableQuery AddOrderBy (Expression> orderExpr, bool asc) { - if (orderExpr.NodeType == ExpressionType.Lambda) { - var lambda = (LambdaExpression)orderExpr; - - MemberExpression mem = null; - - var unary = lambda.Body as UnaryExpression; - if (unary != null && unary.NodeType == ExpressionType.Convert) { - mem = unary.Operand as MemberExpression; - } - else { - mem = lambda.Body as MemberExpression; - } - - if (mem != null && (mem.Expression.NodeType == ExpressionType.Parameter)) { - var q = Clone (); - if (q._orderBys == null) { - q._orderBys = new List (); - } - q._orderBys.Add (new Ordering { - ColumnName = Table.FindColumnWithPropertyName(mem.Member.Name).Name, - Ascending = asc - }); - return q; - } - else { - throw new NotSupportedException ("Order By does not support: " + orderExpr); - } - } - else { - throw new NotSupportedException ("Must be a predicate"); - } - } - - private void AddWhere (Expression pred) { - if (_where == null) { - _where = pred; - } - else { - _where = Expression.AndAlso (_where, pred); - } - } - - public TableQuery Join ( - TableQuery inner, - Expression> outerKeySelector, - Expression> innerKeySelector, - Expression> resultSelector) { - var q = new TableQuery (Connection, Connection.GetMapping (typeof (TResult))) { - _joinOuter = this, - _joinOuterKeySelector = outerKeySelector, - _joinInner = inner, - _joinInnerKeySelector = innerKeySelector, - _joinSelector = resultSelector, - }; - return q; - } - - public TableQuery Select (Expression> selector) { - var q = Clone (); - q._selector = selector; - return q; - } - - private SQLiteCommand GenerateCommand (string selectionList) { - if (_joinInner != null && _joinOuter != null) { - throw new NotSupportedException ("Joins are not supported."); - } - else { - var cmdText = "select " + selectionList + " from \"" + Table.TableName + "\""; - var args = new List (); - if (_where != null) { - var w = CompileExpr (_where, args); - cmdText += " where " + w.CommandText; - } - if ((_orderBys != null) && (_orderBys.Count > 0)) { - var t = string.Join (", ", _orderBys.Select (o => "\"" + o.ColumnName + "\"" + (o.Ascending ? "" : " desc")).ToArray ()); - cmdText += " order by " + t; - } - if (_limit.HasValue) { - cmdText += " limit " + _limit.Value; - } - if (_offset.HasValue) { - if (!_limit.HasValue) { - cmdText += " limit -1 "; - } - cmdText += " offset " + _offset.Value; - } - return Connection.CreateCommand (cmdText, args.ToArray ()); - } - } - - private CompileResult CompileExpr (Expression expr, List queryArgs) { - if (expr == null) - throw new NotSupportedException ("Expression is NULL"); - - if (expr is BinaryExpression) { - var bin = (BinaryExpression)expr; - - var leftr = CompileExpr (bin.Left, queryArgs); - var rightr = CompileExpr (bin.Right, queryArgs); - - //If either side is a parameter and is null, then handle the other side specially (for "is null"/"is not null") - string text; - if (leftr.CommandText == "?" && leftr.Value == null) - text = CompileNullBinaryExpression(bin, rightr); - else if (rightr.CommandText == "?" && rightr.Value == null) - text = CompileNullBinaryExpression(bin, leftr); - else - text = "(" + leftr.CommandText + " " + GetSqlName(bin) + " " + rightr.CommandText + ")"; - return new CompileResult { CommandText = text }; - } - else if (expr.NodeType == ExpressionType.Call) { - - var call = (MethodCallExpression)expr; - var args = new CompileResult[call.Arguments.Count]; - var obj = call.Object != null ? CompileExpr (call.Object, queryArgs) : null; - - for (var i = 0; i < args.Length; i++) { - args [i] = CompileExpr (call.Arguments [i], queryArgs); - } - - var sqlCall = ""; - - if (call.Method.Name == "Like" && args.Length == 2) { - sqlCall = "(" + args [0].CommandText + " like " + args [1].CommandText + ")"; - } - else if (call.Method.Name == "Contains" && args.Length == 2) { - sqlCall = "(" + args [1].CommandText + " in " + args [0].CommandText + ")"; - } - else if (call.Method.Name == "Contains" && args.Length == 1) { - if (call.Object != null && call.Object.Type == typeof(string)) { - sqlCall = "(" + obj.CommandText + " like ('%' || " + args [0].CommandText + " || '%'))"; - } - else { - sqlCall = "(" + args [0].CommandText + " in " + obj.CommandText + ")"; - } - } - else if (call.Method.Name == "StartsWith" && args.Length == 1) { - sqlCall = "(" + obj.CommandText + " like (" + args [0].CommandText + " || '%'))"; - } - else if (call.Method.Name == "EndsWith" && args.Length == 1) { - sqlCall = "(" + obj.CommandText + " like ('%' || " + args [0].CommandText + "))"; - } - else if (call.Method.Name == "Equals" && args.Length == 1) { - sqlCall = "(" + obj.CommandText + " = (" + args[0].CommandText + "))"; - } else if (call.Method.Name == "ToLower") { - sqlCall = "(lower(" + obj.CommandText + "))"; - } else if (call.Method.Name == "ToUpper") { - sqlCall = "(upper(" + obj.CommandText + "))"; - } else { - sqlCall = call.Method.Name.ToLower () + "(" + string.Join (",", args.Select (a => a.CommandText).ToArray ()) + ")"; - } - return new CompileResult { CommandText = sqlCall }; - - } - else if (expr.NodeType == ExpressionType.Constant) { - var c = (ConstantExpression)expr; - queryArgs.Add (c.Value); - return new CompileResult { - CommandText = "?", - Value = c.Value - }; - } - else if (expr.NodeType == ExpressionType.Convert) { - var u = (UnaryExpression)expr; - var ty = u.Type; - var valr = CompileExpr (u.Operand, queryArgs); - return new CompileResult { - CommandText = valr.CommandText, - Value = valr.Value != null ? ConvertTo (valr.Value, ty) : null - }; - } - else if (expr.NodeType == ExpressionType.MemberAccess) { - var mem = (MemberExpression)expr; - - if (mem.Expression!=null && mem.Expression.NodeType == ExpressionType.Parameter) { - // - // This is a column of our table, output just the column name - // Need to translate it if that column name is mapped - // - var columnName = Table.FindColumnWithPropertyName (mem.Member.Name).Name; - return new CompileResult { CommandText = "\"" + columnName + "\"" }; - } - else { - object obj = null; - if (mem.Expression != null) { - var r = CompileExpr (mem.Expression, queryArgs); - if (r.Value == null) { - throw new NotSupportedException ("Member access failed to compile expression"); - } - if (r.CommandText == "?") { - queryArgs.RemoveAt (queryArgs.Count - 1); - } - obj = r.Value; - } - - // - // Get the member value - // - object val = null; - - #if !NETFX_CORE - if (mem.Member.MemberType == MemberTypes.Property) { - #else - if (mem.Member is PropertyInfo) { - #endif - var m = (PropertyInfo)mem.Member; - val = m.GetValue (obj, null); - #if !NETFX_CORE - } - else if (mem.Member.MemberType == MemberTypes.Field) { - #else - } else if (mem.Member is FieldInfo) { - #endif - #if SILVERLIGHT - val = Expression.Lambda (expr).Compile ().DynamicInvoke (); - #else - var m = (FieldInfo)mem.Member; - val = m.GetValue (obj); - #endif - } - else { - #if !NETFX_CORE - throw new NotSupportedException ("MemberExpr: " + mem.Member.MemberType); - #else - throw new NotSupportedException ("MemberExpr: " + mem.Member.DeclaringType); - #endif - } - - // - // Work special magic for enumerables - // - if (val != null && val is System.Collections.IEnumerable && !(val is string) && !(val is System.Collections.Generic.IEnumerable)) { - var sb = new System.Text.StringBuilder(); - sb.Append("("); - var head = ""; - foreach (var a in (System.Collections.IEnumerable)val) { - queryArgs.Add(a); - sb.Append(head); - sb.Append("?"); - head = ","; - } - sb.Append(")"); - return new CompileResult { - CommandText = sb.ToString(), - Value = val - }; - } - else { - queryArgs.Add (val); - return new CompileResult { - CommandText = "?", - Value = val - }; - } - } - } - throw new NotSupportedException ("Cannot compile: " + expr.NodeType.ToString ()); - } - - static object ConvertTo (object obj, Type t) { - if (obj == null) - return null; - - Type nut = Nullable.GetUnderlyingType(t); - if (nut == null) - return Convert.ChangeType (obj, t); - return Convert.ChangeType (obj, nut); - } - - /// - /// Compiles a BinaryExpression where one of the parameters is null. - /// - /// The non-null parameter - private string CompileNullBinaryExpression(BinaryExpression expression, CompileResult parameter) { - switch (expression.NodeType) { - case ExpressionType.Equal: return "(" + parameter.CommandText + " is ?)"; - case ExpressionType.NotEqual: return "(" + parameter.CommandText + " is not ?)"; - default: - throw new NotSupportedException ("Cannot compile Null-BinaryExpression with type " + expression.NodeType.ToString ()); - } - } - - string GetSqlName (Expression expr) { - var n = expr.NodeType; - - switch (n) { - case ExpressionType.GreaterThan: return ">"; - case ExpressionType.GreaterThanOrEqual: return ">="; - case ExpressionType.LessThan: return "<"; - case ExpressionType.LessThanOrEqual: return "<="; - case ExpressionType.And: return "&"; - case ExpressionType.AndAlso: return "and"; - case ExpressionType.Or: return "|"; - case ExpressionType.OrElse: return "or"; - case ExpressionType.Equal: return "="; - case ExpressionType.NotEqual: return "!="; - default: - throw new NotSupportedException ("Cannot get SQL for: " + n); - } - } - - public int Count () { - return GenerateCommand("count(*)").ExecuteScalar (); - } - - public int Count (Expression> predExpr) { - return Where (predExpr).Count (); - } - - public IEnumerator GetEnumerator () { - if (!_deferred) - return GenerateCommand("*").ExecuteQuery().GetEnumerator(); - - return GenerateCommand("*").ExecuteDeferredQuery().GetEnumerator(); - } - - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator () { - return GetEnumerator (); - } - - public T First () { - var query = Take (1); - return query.ToList().First (); - } - - public T FirstOrDefault () { - var query = Take (1); - return query.ToList().FirstOrDefault (); - } - } -} +#else + Thread.VolatileWrite(ref this._transactionDepth, depth); +#endif + Execute(cmd + savepoint); + return; + } + } + + throw new ArgumentException( + "savePoint is not valid, and should be the result of a call to SaveTransactionPoint.", "savePoint"); + } + + /// + /// Commits the transaction that was begun by . + /// + public void Commit() + { + if (Interlocked.Exchange(ref this._transactionDepth, 0) != 0) Execute("commit"); + // Do nothing on a commit with no open transaction + } + + /// + /// 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 void RunInTransaction(Action action) + { + try + { + string savePoint = SaveTransactionPoint(); + action(); + Release(savePoint); + } + catch (Exception) + { + Rollback(); + throw; + } + } + + /// + /// Inserts all specified objects. + /// + /// + /// An of the objects to insert. + /// + /// + /// The number of rows added to the table. + /// + public int InsertAll(IEnumerable objects) + { + int c = 0; + RunInTransaction(() => + { + foreach (object r in objects) c += Insert(r); + }); + return c; + } + + /// + /// Inserts all specified objects. + /// + /// + /// An of the objects to insert. + /// + /// + /// Literal SQL code that gets placed into the command. INSERT {extra} INTO ... + /// + /// + /// The number of rows added to the table. + /// + public int InsertAll(IEnumerable objects, string extra) + { + int c = 0; + RunInTransaction(() => + { + foreach (object r in objects) c += Insert(r, extra); + }); + return c; + } + + /// + /// Inserts all specified objects. + /// + /// + /// An of the objects to insert. + /// + /// + /// The type of object to insert. + /// + /// + /// The number of rows added to the table. + /// + public int InsertAll(IEnumerable objects, Type objType) + { + int c = 0; + RunInTransaction(() => + { + foreach (object r in objects) c += Insert(r, objType); + }); + return c; + } + + /// + /// 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 int Insert(object obj) + { + if (obj == null) return 0; + return Insert(obj, "", obj.GetType()); + } + + /// + /// Inserts the given object and retrieves its + /// auto incremented primary key if it has one. + /// 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 int InsertOrReplace(object obj) + { + if (obj == null) return 0; + return Insert(obj, "OR REPLACE", obj.GetType()); + } + + /// + /// Inserts the given object and retrieves its + /// auto incremented primary key if it has one. + /// + /// + /// The object to insert. + /// + /// + /// The type of object to insert. + /// + /// + /// The number of rows added to the table. + /// + public int Insert(object obj, Type objType) + { + return Insert(obj, "", objType); + } + + /// + /// Inserts the given object and retrieves its + /// auto incremented primary key if it has one. + /// 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 int InsertOrReplace(object obj, Type objType) + { + return Insert(obj, "OR REPLACE", objType); + } + + /// + /// Inserts the given object and retrieves its + /// auto incremented primary key if it has one. + /// + /// + /// The object to insert. + /// + /// + /// Literal SQL code that gets placed into the command. INSERT {extra} INTO ... + /// + /// + /// The number of rows added to the table. + /// + public int Insert(object obj, string extra) + { + if (obj == null) return 0; + return Insert(obj, extra, obj.GetType()); + } + + /// + /// Inserts the given object and retrieves its + /// auto incremented primary key if it has one. + /// + /// + /// The object to insert. + /// + /// + /// Literal SQL code that gets placed into the command. INSERT {extra} INTO ... + /// + /// + /// The type of object to insert. + /// + /// + /// The number of rows added to the table. + /// + public int Insert(object obj, string extra, Type objType) + { + if (obj == null || objType == null) return 0; + + TableMapping map = GetMapping(objType); + + if (map.PK != null && map.PK.IsAutoGuid) + { + PropertyInfo prop = objType.GetProperty(map.PK.PropertyName); + if (prop != null) + if (prop.GetValue(obj, null).Equals(Guid.Empty)) + prop.SetValue(obj, Guid.NewGuid(), null); + } + + bool replacing = string.Compare(extra, "OR REPLACE", StringComparison.OrdinalIgnoreCase) == 0; + + TableMapping.Column[] cols = replacing ? map.InsertOrReplaceColumns : map.InsertColumns; + object[] vals = new object[cols.Length]; + for (int i = 0; i < vals.Length; i++) vals[i] = cols[i].GetValue(obj); + + PreparedSqlLiteInsertCommand insertCmd = map.GetInsertCommand(this, extra); + int count; + + try + { + count = insertCmd.ExecuteNonQuery(vals); + } + catch (SQLiteException ex) + { + if (SQLite3.ExtendedErrCode(this.Handle) == SQLite3.ExtendedResult.ConstraintNotNull) + throw NotNullConstraintViolationException.New(ex.Result, ex.Message, map, obj); + throw; + } + + if (map.HasAutoIncPK) + { + long id = SQLite3.LastInsertRowid(this.Handle); + map.SetAutoIncPK(obj, id); + } + + return count; + } + + /// + /// 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 int Update(object obj) + { + if (obj == null) return 0; + return Update(obj, obj.GetType()); + } + + /// + /// 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 int Update(object obj, Type objType) + { + int rowsAffected = 0; + if (obj == null || objType == null) return 0; + + TableMapping map = GetMapping(objType); + TableMapping.Column pk = map.PK; + + if (pk == null) throw new NotSupportedException("Cannot update " + map.TableName + ": it has no PK"); + + IEnumerable cols = from p in map.Columns where p != pk select p; + IEnumerable vals = from c in cols select c.GetValue(obj); + List ps = new List(vals); + ps.Add(pk.GetValue(obj)); + + string q = string.Format("update \"{0}\" set {1} where {2} = ? ", map.TableName, + string.Join(",", (from c in cols select "\"" + c.Name + "\" = ? ").ToArray()), pk.Name); + + try + { + rowsAffected = Execute(q, ps.ToArray()); + } + catch (SQLiteException ex) + { + if (ex.Result == SQLite3.Result.Constraint && + SQLite3.ExtendedErrCode(this.Handle) == SQLite3.ExtendedResult.ConstraintNotNull) + throw NotNullConstraintViolationException.New(ex, map, obj); + + throw ex; + } + + return rowsAffected; + } + + /// + /// Updates all specified objects. + /// + /// + /// An of the objects to insert. + /// + /// + /// The number of rows modified. + /// + public int UpdateAll(IEnumerable objects) + { + int c = 0; + RunInTransaction(() => + { + foreach (object r in objects) c += Update(r); + }); + return c; + } + + /// + /// 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 int Delete(object objectToDelete) + { + TableMapping map = GetMapping(objectToDelete.GetType()); + TableMapping.Column pk = map.PK; + if (pk == null) throw new NotSupportedException("Cannot delete " + map.TableName + ": it has no PK"); + string q = string.Format("delete from \"{0}\" where \"{1}\" = ?", map.TableName, pk.Name); + return Execute(q, pk.GetValue(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 int Delete(object primaryKey) + { + TableMapping map = GetMapping(typeof(T)); + TableMapping.Column pk = map.PK; + if (pk == null) throw new NotSupportedException("Cannot delete " + map.TableName + ": it has no PK"); + string q = string.Format("delete from \"{0}\" where \"{1}\" = ?", map.TableName, pk.Name); + return Execute(q, primaryKey); + } + + /// + /// 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 int DeleteAll() + { + TableMapping map = GetMapping(typeof(T)); + string query = string.Format("delete from \"{0}\"", map.TableName); + return Execute(query); + } + + ~SQLiteConnection() + { + Dispose(false); + } + + protected virtual void Dispose(bool disposing) + { + Close(); + } + + public void Close() + { + if (this._open && this.Handle != NullHandle) + try + { + if (this._mappings != null) + foreach (TableMapping sqlInsertCommand in this._mappings.Values) + sqlInsertCommand.Dispose(); + SQLite3.Result r = SQLite3.Close(this.Handle); + if (r != SQLite3.Result.OK) + { + string msg = SQLite3.GetErrmsg(this.Handle); + throw SQLiteException.New(r, msg); + } + } + finally + { + this.Handle = NullHandle; + this._open = false; + } + } + + private struct IndexedColumn + { + public int Order; + public string ColumnName; + } + + private struct IndexInfo + { + public string IndexName; + public string TableName; + public bool Unique; + public List Columns; + } + + public class ColumnInfo + { + // public int cid { get; set; } + + [Column("name")] public string Name { get; set; } + + // [Column ("type")] + // public string ColumnType { get; set; } + + public int notnull { get; set; } + + // public string dflt_value { get; set; } + + // public int pk { get; set; } + + public override string ToString() + { + return this.Name; + } + } + } + + /// + /// Represents a parsed connection string. + /// + internal class SQLiteConnectionString + { + public string ConnectionString { get; private set; } + public string DatabasePath { get; private set; } + public bool StoreDateTimeAsTicks { get; private set; } + + public SQLiteConnectionString(string databasePath, bool storeDateTimeAsTicks) + { + this.ConnectionString = databasePath; + this.StoreDateTimeAsTicks = storeDateTimeAsTicks; + this.DatabasePath = databasePath; + } + } + + public class TableMapping + { + private readonly Column _autoPk; + private Column[] _insertColumns; + + private PreparedSqlLiteInsertCommand _insertCommand; + private string _insertCommandExtra; + private Column[] _insertOrReplaceColumns; + + public TableMapping(Type type, CreateFlags createFlags = CreateFlags.None) + { + this.MappedType = type; + TableAttribute tableAttr = + (TableAttribute) type.GetCustomAttributes(typeof(TableAttribute), true).FirstOrDefault(); + this.TableName = tableAttr != null ? tableAttr.Name : this.MappedType.Name; + + PropertyInfo[] props = this.MappedType.GetProperties(BindingFlags.Public | BindingFlags.Instance | + BindingFlags.SetProperty); + List cols = new List(); + foreach (PropertyInfo p in props) + { + bool ignore = p.GetCustomAttributes(typeof(IgnoreAttribute), true).Length > 0; + if (p.CanWrite && !ignore) cols.Add(new Column(p, createFlags)); + } + + this.Columns = cols.ToArray(); + foreach (Column c in this.Columns) + { + if (c.IsAutoInc && c.IsPK) this._autoPk = c; + if (c.IsPK) this.PK = c; + } + + this.HasAutoIncPK = this._autoPk != null; + + if (this.PK != null) + this.GetByPrimaryKeySql = + string.Format("select * from \"{0}\" where \"{1}\" = ?", this.TableName, this.PK.Name); + else + this.GetByPrimaryKeySql = string.Format("select * from \"{0}\" limit 1", this.TableName); + } + + public Type MappedType { get; private set; } + public string TableName { get; private set; } + public Column[] Columns { get; private set; } + public Column PK { get; private set; } + public string GetByPrimaryKeySql { get; private set; } + + public bool HasAutoIncPK { get; private set; } + + public Column[] InsertColumns + { + get + { + if (this._insertColumns == null) this._insertColumns = this.Columns.Where(c => !c.IsAutoInc).ToArray(); + return this._insertColumns; + } + } + + public Column[] InsertOrReplaceColumns + { + get + { + if (this._insertOrReplaceColumns == null) this._insertOrReplaceColumns = this.Columns.ToArray(); + return this._insertOrReplaceColumns; + } + } + + public void SetAutoIncPK(object obj, long id) + { + if (this._autoPk != null) this._autoPk.SetValue(obj, Convert.ChangeType(id, this._autoPk.ColumnType, null)); + } + + public Column FindColumnWithPropertyName(string propertyName) + { + Column exact = this.Columns.FirstOrDefault(c => c.PropertyName == propertyName); + return exact; + } + + public Column FindColumn(string columnName) + { + Column exact = this.Columns.FirstOrDefault(c => c.Name == columnName); + return exact; + } + + public PreparedSqlLiteInsertCommand GetInsertCommand(SQLiteConnection conn, string extra) + { + if (this._insertCommand == null) + { + this._insertCommand = CreateInsertCommand(conn, extra); + this._insertCommandExtra = extra; + } + else if (this._insertCommandExtra != extra) + { + this._insertCommand.Dispose(); + this._insertCommand = CreateInsertCommand(conn, extra); + this._insertCommandExtra = extra; + } + + return this._insertCommand; + } + + private PreparedSqlLiteInsertCommand CreateInsertCommand(SQLiteConnection conn, string extra) + { + Column[] cols = this.InsertColumns; + string insertSql; + if (!cols.Any() && this.Columns.Count() == 1 && this.Columns[0].IsAutoInc) + { + insertSql = string.Format("insert {1} into \"{0}\" default values", this.TableName, extra); + } + else + { + bool replacing = string.Compare(extra, "OR REPLACE", StringComparison.OrdinalIgnoreCase) == 0; + + if (replacing) cols = this.InsertOrReplaceColumns; + + insertSql = string.Format("insert {3} into \"{0}\"({1}) values ({2})", this.TableName, + string.Join(",", (from c in cols select "\"" + c.Name + "\"").ToArray()), + string.Join(",", (from c in cols select "?").ToArray()), extra); + } + + PreparedSqlLiteInsertCommand insertCommand = new PreparedSqlLiteInsertCommand(conn); + insertCommand.CommandText = insertSql; + return insertCommand; + } + + protected internal void Dispose() + { + if (this._insertCommand != null) + { + this._insertCommand.Dispose(); + this._insertCommand = null; + } + } + + public class Column + { + private readonly PropertyInfo _prop; + + public Column(PropertyInfo prop, CreateFlags createFlags = CreateFlags.None) + { + ColumnAttribute colAttr = + (ColumnAttribute) prop.GetCustomAttributes(typeof(ColumnAttribute), true).FirstOrDefault(); + + this._prop = prop; + this.Name = colAttr == null ? prop.Name : colAttr.Name; + //If this type is Nullable then Nullable.GetUnderlyingType returns the T, otherwise it returns null, so get the actual type instead + this.ColumnType = Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType; + this.Collation = Orm.Collation(prop); + + this.IsPK = Orm.IsPK(prop) || + (createFlags & CreateFlags.ImplicitPK) == CreateFlags.ImplicitPK && + string.Compare(prop.Name, Orm.ImplicitPkName, StringComparison.OrdinalIgnoreCase) == 0; + + bool isAuto = Orm.IsAutoInc(prop) || + this.IsPK && (createFlags & CreateFlags.AutoIncPK) == CreateFlags.AutoIncPK; + this.IsAutoGuid = isAuto && this.ColumnType == typeof(Guid); + this.IsAutoInc = isAuto && !this.IsAutoGuid; + + this.Indices = Orm.GetIndices(prop); + if (!this.Indices.Any() && + !this.IsPK && + (createFlags & CreateFlags.ImplicitIndex) == CreateFlags.ImplicitIndex && + this.Name.EndsWith(Orm.ImplicitIndexSuffix, StringComparison.OrdinalIgnoreCase) + ) + this.Indices = new[] {new IndexedAttribute()}; + this.IsNullable = !(this.IsPK || Orm.IsMarkedNotNull(prop)); + this.MaxStringLength = Orm.MaxStringLength(prop); + } + + public string Name { get; private set; } + + public string PropertyName + { + get { return this._prop.Name; } + } + + public Type ColumnType { get; private set; } + + public string Collation { get; private set; } + + public bool IsAutoInc { get; private set; } + public bool IsAutoGuid { get; private set; } + + public bool IsPK { get; private set; } + + public IEnumerable Indices { get; set; } + + public bool IsNullable { get; private set; } + + public int? MaxStringLength { get; private set; } + + public void SetValue(object obj, object val) + { + this._prop.SetValue(obj, val, null); + } + + public object GetValue(object obj) + { + return this._prop.GetValue(obj, null); + } + } + } + + public static class Orm + { + public const int DefaultMaxStringLength = 140; + public const string ImplicitPkName = "Id"; + public const string ImplicitIndexSuffix = "Id"; + + public static string SqlDecl(TableMapping.Column p, bool storeDateTimeAsTicks) + { + string decl = "\"" + p.Name + "\" " + SqlType(p, storeDateTimeAsTicks) + " "; + + if (p.IsPK) decl += "primary key "; + if (p.IsAutoInc) decl += "autoincrement "; + if (!p.IsNullable) decl += "not null "; + if (!string.IsNullOrEmpty(p.Collation)) decl += "collate " + p.Collation + " "; + + return decl; + } + + public static string SqlType(TableMapping.Column p, bool storeDateTimeAsTicks) + { + Type clrType = p.ColumnType; + if (clrType == typeof(bool) || clrType == typeof(byte) || clrType == typeof(ushort) || + clrType == typeof(sbyte) || clrType == typeof(short) || clrType == typeof(int)) return "integer"; + + if (clrType == typeof(uint) || clrType == typeof(long)) return "bigint"; + + if (clrType == typeof(float) || clrType == typeof(double) || clrType == typeof(decimal)) return "float"; + + if (clrType == typeof(string)) + { + int? len = p.MaxStringLength; + + if (len.HasValue) + return "varchar(" + len.Value + ")"; + + return "varchar"; + } + + if (clrType == typeof(TimeSpan)) return "bigint"; + + if (clrType == typeof(DateTime)) return storeDateTimeAsTicks ? "bigint" : "datetime"; + + if (clrType == typeof(DateTimeOffset)) + { + return "bigint"; + } + + if (clrType.IsEnum) + { + return "integer"; + } + + if (clrType == typeof(byte[])) + return "blob"; + if (clrType == typeof(Guid)) + return "varchar(36)"; + throw new NotSupportedException("Don't know about " + clrType); + } + + public static bool IsPK(MemberInfo p) + { + object[] attrs = p.GetCustomAttributes(typeof(PrimaryKeyAttribute), true); + return attrs.Length > 0; + } + + public static string Collation(MemberInfo p) + { + object[] attrs = p.GetCustomAttributes(typeof(CollationAttribute), true); + if (attrs.Length > 0) + return ((CollationAttribute) attrs[0]).Value; + return string.Empty; + } + + public static bool IsAutoInc(MemberInfo p) + { + object[] attrs = p.GetCustomAttributes(typeof(AutoIncrementAttribute), true); + return attrs.Length > 0; + } + + public static IEnumerable GetIndices(MemberInfo p) + { + object[] attrs = p.GetCustomAttributes(typeof(IndexedAttribute), true); + return attrs.Cast(); + } + + public static int? MaxStringLength(PropertyInfo p) + { + object[] attrs = p.GetCustomAttributes(typeof(MaxLengthAttribute), true); + if (attrs.Length > 0) + return ((MaxLengthAttribute) attrs[0]).Value; + return null; + } + + public static bool IsMarkedNotNull(MemberInfo p) + { + object[] attrs = p.GetCustomAttributes(typeof(NotNullAttribute), true); + return attrs.Length > 0; + } + } + + public class SQLiteCommand + { + internal static IntPtr NegativePointer = new IntPtr(-1); + private readonly List _bindings; + private readonly SQLiteConnection _conn; + + internal SQLiteCommand(SQLiteConnection conn) + { + this._conn = conn; + this._bindings = new List(); + this.CommandText = ""; + } + + public string CommandText { get; set; } + + public int ExecuteNonQuery() + { + if (this._conn.Trace) Debug.WriteLine("Executing: " + this); + + SQLite3.Result r = SQLite3.Result.OK; + IntPtr stmt = Prepare(); + r = SQLite3.Step(stmt); + Finalize(stmt); + if (r == SQLite3.Result.Done) + { + int rowsAffected = SQLite3.Changes(this._conn.Handle); + return rowsAffected; + } + + if (r == SQLite3.Result.Error) + { + string msg = SQLite3.GetErrmsg(this._conn.Handle); + throw SQLiteException.New(r, msg); + } + + if (r == SQLite3.Result.Constraint) + if (SQLite3.ExtendedErrCode(this._conn.Handle) == SQLite3.ExtendedResult.ConstraintNotNull) + throw NotNullConstraintViolationException.New(r, SQLite3.GetErrmsg(this._conn.Handle)); + + throw SQLiteException.New(r, r.ToString()); + } + + public IEnumerable ExecuteDeferredQuery() + { + return ExecuteDeferredQuery(this._conn.GetMapping(typeof(T))); + } + + public List ExecuteQuery() + { + return ExecuteDeferredQuery(this._conn.GetMapping(typeof(T))).ToList(); + } + + public List ExecuteQuery(TableMapping map) + { + return ExecuteDeferredQuery(map).ToList(); + } + + /// + /// Invoked every time an instance is loaded from the database. + /// + /// + /// The newly created object. + /// + /// + /// This can be overridden in combination with the + /// method to hook into the life-cycle of objects. + /// Type safety is not possible because MonoTouch does not support virtual generic methods. + /// + protected virtual void OnInstanceCreated(object obj) + { + // Can be overridden. + } + + public IEnumerable ExecuteDeferredQuery(TableMapping map) + { + if (this._conn.Trace) Debug.WriteLine("Executing Query: " + this); + + IntPtr stmt = Prepare(); + try + { + TableMapping.Column[] cols = new TableMapping.Column[SQLite3.ColumnCount(stmt)]; + + for (int i = 0; i < cols.Length; i++) + { + string name = SQLite3.ColumnName16(stmt, i); + cols[i] = map.FindColumn(name); + } + + while (SQLite3.Step(stmt) == SQLite3.Result.Row) + { + object obj = Activator.CreateInstance(map.MappedType); + for (int i = 0; i < cols.Length; i++) + { + if (cols[i] == null) + continue; + SQLite3.ColType colType = SQLite3.ColumnType(stmt, i); + object val = ReadCol(stmt, i, colType, cols[i].ColumnType); + cols[i].SetValue(obj, val); + } + + OnInstanceCreated(obj); + yield return (T) obj; + } + } + finally + { + SQLite3.Finalize(stmt); + } + } + + public T ExecuteScalar() + { + if (this._conn.Trace) Debug.WriteLine("Executing Query: " + this); + + T val = default(T); + + IntPtr stmt = Prepare(); + + try + { + SQLite3.Result r = SQLite3.Step(stmt); + if (r == SQLite3.Result.Row) + { + SQLite3.ColType colType = SQLite3.ColumnType(stmt, 0); + val = (T) ReadCol(stmt, 0, colType, typeof(T)); + } + else if (r == SQLite3.Result.Done) { } + else + { + throw SQLiteException.New(r, SQLite3.GetErrmsg(this._conn.Handle)); + } + } + finally + { + Finalize(stmt); + } + + return val; + } + + public void Bind(string name, object val) + { + this._bindings.Add(new Binding + { + Name = name, + Value = val + }); + } + + public void Bind(object val) + { + Bind(null, val); + } + + public override string ToString() + { + string[] parts = new string[1 + this._bindings.Count]; + parts[0] = this.CommandText; + int i = 1; + foreach (Binding b in this._bindings) + { + parts[i] = string.Format(" {0}: {1}", i - 1, b.Value); + i++; + } + + return string.Join(Environment.NewLine, parts); + } + + private Sqlite3Statement Prepare() + { + IntPtr stmt = SQLite3.Prepare2(this._conn.Handle, this.CommandText); + BindAll(stmt); + return stmt; + } + + private void Finalize(Sqlite3Statement stmt) + { + SQLite3.Finalize(stmt); + } + + private void BindAll(Sqlite3Statement stmt) + { + int nextIdx = 1; + foreach (Binding b in this._bindings) + { + if (b.Name != null) + b.Index = SQLite3.BindParameterIndex(stmt, b.Name); + else + b.Index = nextIdx++; + + BindParameter(stmt, b.Index, b.Value, this._conn.StoreDateTimeAsTicks); + } + } + + internal static int BindParameter(Sqlite3Statement stmt, int index, object value, bool storeDateTimeAsTicks) + { + if (value == null) + return SQLite3.BindNull(stmt, index); + if (value is int) + return SQLite3.BindInt(stmt, index, (int) value); + if (value is string) + return SQLite3.BindText(stmt, index, (string) value, -1, NegativePointer); + if (value is byte || value is ushort || value is sbyte || value is short) + return SQLite3.BindInt(stmt, index, Convert.ToInt32(value)); + if (value is bool) + return SQLite3.BindInt(stmt, index, (bool) value ? 1 : 0); + if (value is uint || value is long) + return SQLite3.BindInt64(stmt, index, Convert.ToInt64(value)); + if (value is float || value is double || value is decimal) + return SQLite3.BindDouble(stmt, index, Convert.ToDouble(value)); + if (value is TimeSpan) + return SQLite3.BindInt64(stmt, index, ((TimeSpan) value).Ticks); + if (value is DateTime) + { + if (storeDateTimeAsTicks) + return SQLite3.BindInt64(stmt, index, ((DateTime) value).Ticks); + return SQLite3.BindText(stmt, index, ((DateTime) value).ToString("yyyy-MM-dd HH:mm:ss"), -1, + NegativePointer); + } + + if (value is DateTimeOffset) + return SQLite3.BindInt64(stmt, index, ((DateTimeOffset) value).UtcTicks); + + { + if (value.GetType().IsEnum) + return SQLite3.BindInt(stmt, index, Convert.ToInt32(value)); + } + + if (value is byte[]) + return SQLite3.BindBlob(stmt, index, (byte[]) value, ((byte[]) value).Length, NegativePointer); + if (value is Guid) + return SQLite3.BindText(stmt, index, ((Guid) value).ToString(), 72, NegativePointer); + + throw new NotSupportedException("Cannot store type: " + value.GetType()); + } + + private object ReadCol(Sqlite3Statement stmt, int index, SQLite3.ColType type, Type clrType) + { + if (type == SQLite3.ColType.Null) + return null; + + if (clrType == typeof(string)) return SQLite3.ColumnString(stmt, index); + + if (clrType == typeof(int)) return SQLite3.ColumnInt(stmt, index); + + if (clrType == typeof(bool)) return SQLite3.ColumnInt(stmt, index) == 1; + + if (clrType == typeof(double)) return SQLite3.ColumnDouble(stmt, index); + + if (clrType == typeof(float)) return (float) SQLite3.ColumnDouble(stmt, index); + + if (clrType == typeof(TimeSpan)) return new TimeSpan(SQLite3.ColumnInt64(stmt, index)); + + if (clrType == typeof(DateTime)) + { + if (this._conn.StoreDateTimeAsTicks) return new DateTime(SQLite3.ColumnInt64(stmt, index)); + + string text = SQLite3.ColumnString(stmt, index); + return DateTime.Parse(text); + } + + if (clrType == typeof(DateTimeOffset)) + { + return new DateTimeOffset(SQLite3.ColumnInt64(stmt, index), TimeSpan.Zero); + } + + if (clrType.IsEnum) + { + return SQLite3.ColumnInt(stmt, index); + } + + if (clrType == typeof(long)) return SQLite3.ColumnInt64(stmt, index); + + if (clrType == typeof(uint)) return (uint) SQLite3.ColumnInt64(stmt, index); + + if (clrType == typeof(decimal)) return (decimal) SQLite3.ColumnDouble(stmt, index); + + if (clrType == typeof(byte)) return (byte) SQLite3.ColumnInt(stmt, index); + + if (clrType == typeof(ushort)) return (ushort) SQLite3.ColumnInt(stmt, index); + + if (clrType == typeof(short)) return (short) SQLite3.ColumnInt(stmt, index); + + if (clrType == typeof(sbyte)) return (sbyte) SQLite3.ColumnInt(stmt, index); + + if (clrType == typeof(byte[])) return SQLite3.ColumnByteArray(stmt, index); + + if (clrType == typeof(Guid)) + { + string text = SQLite3.ColumnString(stmt, index); + return new Guid(text); + } + + throw new NotSupportedException("Don't know how to read " + clrType); + } + + private class Binding + { + public string Name { get; set; } + public object Value { get; set; } + public int Index { get; set; } + } + } + + /// + /// Since the insert never changed, we only need to prepare once. + /// + public class PreparedSqlLiteInsertCommand : IDisposable + { + internal static readonly Sqlite3Statement NullStatement = default(Sqlite3Statement); + + internal PreparedSqlLiteInsertCommand(SQLiteConnection conn) + { + this.Connection = conn; + } + + public bool Initialized { get; set; } + public string CommandText { get; set; } + + protected SQLiteConnection Connection { get; set; } + protected Sqlite3Statement Statement { get; set; } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + public int ExecuteNonQuery(object[] source) + { + if (this.Connection.Trace) Debug.WriteLine("Executing: " + this.CommandText); + + SQLite3.Result r = SQLite3.Result.OK; + + if (!this.Initialized) + { + this.Statement = Prepare(); + this.Initialized = true; + } + + //bind the values. + if (source != null) + for (int i = 0; i < source.Length; i++) + SQLiteCommand.BindParameter(this.Statement, i + 1, source[i], this.Connection.StoreDateTimeAsTicks); + r = SQLite3.Step(this.Statement); + + if (r == SQLite3.Result.Done) + { + int rowsAffected = SQLite3.Changes(this.Connection.Handle); + SQLite3.Reset(this.Statement); + return rowsAffected; + } + + if (r == SQLite3.Result.Error) + { + string msg = SQLite3.GetErrmsg(this.Connection.Handle); + SQLite3.Reset(this.Statement); + throw SQLiteException.New(r, msg); + } + + if (r == SQLite3.Result.Constraint && + SQLite3.ExtendedErrCode(this.Connection.Handle) == SQLite3.ExtendedResult.ConstraintNotNull) + { + SQLite3.Reset(this.Statement); + throw NotNullConstraintViolationException.New(r, SQLite3.GetErrmsg(this.Connection.Handle)); + } + + SQLite3.Reset(this.Statement); + throw SQLiteException.New(r, r.ToString()); + } + + protected virtual Sqlite3Statement Prepare() + { + IntPtr stmt = SQLite3.Prepare2(this.Connection.Handle, this.CommandText); + return stmt; + } + + private void Dispose(bool disposing) + { + if (this.Statement != NullStatement) + try + { + SQLite3.Finalize(this.Statement); + } + finally + { + this.Statement = NullStatement; + this.Connection = null; + } + } + + ~PreparedSqlLiteInsertCommand() + { + Dispose(false); + } + } + + public abstract class BaseTableQuery + { + protected class Ordering + { + public string ColumnName { get; set; } + public bool Ascending { get; set; } + } + } + + public class TableQuery : BaseTableQuery, IEnumerable + { + private bool _deferred; + + private BaseTableQuery _joinInner; + private Expression _joinInnerKeySelector; + private BaseTableQuery _joinOuter; + private Expression _joinOuterKeySelector; + private Expression _joinSelector; + private int? _limit; + private int? _offset; + private List _orderBys; + + private Expression _selector; + + private Expression _where; + + private TableQuery(SQLiteConnection conn, TableMapping table) + { + this.Connection = conn; + this.Table = table; + } + + public TableQuery(SQLiteConnection conn) + { + this.Connection = conn; + this.Table = this.Connection.GetMapping(typeof(T)); + } + + public SQLiteConnection Connection { get; private set; } + + public TableMapping Table { get; private set; } + + public IEnumerator GetEnumerator() + { + if (!this._deferred) + return GenerateCommand("*").ExecuteQuery().GetEnumerator(); + + return GenerateCommand("*").ExecuteDeferredQuery().GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public TableQuery Clone() + { + TableQuery q = new TableQuery(this.Connection, this.Table); + q._where = this._where; + q._deferred = this._deferred; + if (this._orderBys != null) q._orderBys = new List(this._orderBys); + q._limit = this._limit; + q._offset = this._offset; + q._joinInner = this._joinInner; + q._joinInnerKeySelector = this._joinInnerKeySelector; + q._joinOuter = this._joinOuter; + q._joinOuterKeySelector = this._joinOuterKeySelector; + q._joinSelector = this._joinSelector; + q._selector = this._selector; + return q; + } + + public TableQuery Where(Expression> predExpr) + { + if (predExpr.NodeType == ExpressionType.Lambda) + { + LambdaExpression lambda = predExpr; + Expression pred = lambda.Body; + TableQuery q = Clone(); + q.AddWhere(pred); + return q; + } + + throw new NotSupportedException("Must be a predicate"); + } + + public TableQuery Take(int n) + { + TableQuery q = Clone(); + q._limit = n; + return q; + } + + public TableQuery Skip(int n) + { + TableQuery q = Clone(); + q._offset = n; + return q; + } + + public T ElementAt(int index) + { + return Skip(index).Take(1).First(); + } + + public TableQuery Deferred() + { + TableQuery q = Clone(); + q._deferred = true; + return q; + } + + public TableQuery OrderBy(Expression> orderExpr) + { + return AddOrderBy(orderExpr, true); + } + + public TableQuery OrderByDescending(Expression> orderExpr) + { + return AddOrderBy(orderExpr, false); + } + + public TableQuery ThenBy(Expression> orderExpr) + { + return AddOrderBy(orderExpr, true); + } + + public TableQuery ThenByDescending(Expression> orderExpr) + { + return AddOrderBy(orderExpr, false); + } + + private TableQuery AddOrderBy(Expression> orderExpr, bool asc) + { + if (orderExpr.NodeType == ExpressionType.Lambda) + { + LambdaExpression lambda = orderExpr; + + MemberExpression mem = null; + + UnaryExpression unary = lambda.Body as UnaryExpression; + if (unary != null && unary.NodeType == ExpressionType.Convert) + mem = unary.Operand as MemberExpression; + else + mem = lambda.Body as MemberExpression; + + if (mem != null && mem.Expression.NodeType == ExpressionType.Parameter) + { + TableQuery q = Clone(); + if (q._orderBys == null) q._orderBys = new List(); + q._orderBys.Add(new Ordering + { + ColumnName = this.Table.FindColumnWithPropertyName(mem.Member.Name).Name, + Ascending = asc + }); + return q; + } + + throw new NotSupportedException("Order By does not support: " + orderExpr); + } + + throw new NotSupportedException("Must be a predicate"); + } + + private void AddWhere(Expression pred) + { + if (this._where == null) + this._where = pred; + else + this._where = Expression.AndAlso(this._where, pred); + } + + public TableQuery Join( + TableQuery inner, + Expression> outerKeySelector, + Expression> innerKeySelector, + Expression> resultSelector) + { + TableQuery q = + new TableQuery(this.Connection, this.Connection.GetMapping(typeof(TResult))) + { + _joinOuter = this, + _joinOuterKeySelector = outerKeySelector, + _joinInner = inner, + _joinInnerKeySelector = innerKeySelector, + _joinSelector = resultSelector + }; + return q; + } + + public TableQuery Select(Expression> selector) + { + TableQuery q = Clone(); + q._selector = selector; + return q; + } + + private SQLiteCommand GenerateCommand(string selectionList) + { + if (this._joinInner != null && this._joinOuter != null) + throw new NotSupportedException("Joins are not supported."); + + string cmdText = "select " + selectionList + " from \"" + this.Table.TableName + "\""; + List args = new List(); + if (this._where != null) + { + CompileResult w = CompileExpr(this._where, args); + cmdText += " where " + w.CommandText; + } + + if (this._orderBys != null && this._orderBys.Count > 0) + { + string t = string.Join(", ", + this._orderBys.Select(o => "\"" + o.ColumnName + "\"" + (o.Ascending ? "" : " desc")).ToArray()); + cmdText += " order by " + t; + } + + if (this._limit.HasValue) cmdText += " limit " + this._limit.Value; + if (this._offset.HasValue) + { + if (!this._limit.HasValue) cmdText += " limit -1 "; + cmdText += " offset " + this._offset.Value; + } + + return this.Connection.CreateCommand(cmdText, args.ToArray()); + } + + private CompileResult CompileExpr(Expression expr, List queryArgs) + { + if (expr == null) + throw new NotSupportedException("Expression is NULL"); + + if (expr is BinaryExpression) + { + BinaryExpression bin = (BinaryExpression) expr; + + CompileResult leftr = CompileExpr(bin.Left, queryArgs); + CompileResult rightr = CompileExpr(bin.Right, queryArgs); + + //If either side is a parameter and is null, then handle the other side specially (for "is null"/"is not null") + string text; + if (leftr.CommandText == "?" && leftr.Value == null) + text = CompileNullBinaryExpression(bin, rightr); + else if (rightr.CommandText == "?" && rightr.Value == null) + text = CompileNullBinaryExpression(bin, leftr); + else + text = "(" + leftr.CommandText + " " + GetSqlName(bin) + " " + rightr.CommandText + ")"; + return new CompileResult {CommandText = text}; + } + + if (expr.NodeType == ExpressionType.Call) + { + MethodCallExpression call = (MethodCallExpression) expr; + CompileResult[] args = new CompileResult[call.Arguments.Count]; + CompileResult obj = call.Object != null ? CompileExpr(call.Object, queryArgs) : null; + + for (int i = 0; i < args.Length; i++) args[i] = CompileExpr(call.Arguments[i], queryArgs); + + string sqlCall = ""; + + if (call.Method.Name == "Like" && args.Length == 2) + { + sqlCall = "(" + args[0].CommandText + " like " + args[1].CommandText + ")"; + } + else if (call.Method.Name == "Contains" && args.Length == 2) + { + sqlCall = "(" + args[1].CommandText + " in " + args[0].CommandText + ")"; + } + else if (call.Method.Name == "Contains" && args.Length == 1) + { + if (call.Object != null && call.Object.Type == typeof(string)) + sqlCall = "(" + obj.CommandText + " like ('%' || " + args[0].CommandText + " || '%'))"; + else + sqlCall = "(" + args[0].CommandText + " in " + obj.CommandText + ")"; + } + else if (call.Method.Name == "StartsWith" && args.Length == 1) + { + sqlCall = "(" + obj.CommandText + " like (" + args[0].CommandText + " || '%'))"; + } + else if (call.Method.Name == "EndsWith" && args.Length == 1) + { + sqlCall = "(" + obj.CommandText + " like ('%' || " + args[0].CommandText + "))"; + } + else if (call.Method.Name == "Equals" && args.Length == 1) + { + sqlCall = "(" + obj.CommandText + " = (" + args[0].CommandText + "))"; + } + else if (call.Method.Name == "ToLower") + { + sqlCall = "(lower(" + obj.CommandText + "))"; + } + else if (call.Method.Name == "ToUpper") + { + sqlCall = "(upper(" + obj.CommandText + "))"; + } + else + { + sqlCall = call.Method.Name.ToLower() + "(" + + string.Join(",", args.Select(a => a.CommandText).ToArray()) + ")"; + } + + return new CompileResult {CommandText = sqlCall}; + } + + if (expr.NodeType == ExpressionType.Constant) + { + ConstantExpression c = (ConstantExpression) expr; + queryArgs.Add(c.Value); + return new CompileResult + { + CommandText = "?", + Value = c.Value + }; + } + + if (expr.NodeType == ExpressionType.Convert) + { + UnaryExpression u = (UnaryExpression) expr; + Type ty = u.Type; + CompileResult valr = CompileExpr(u.Operand, queryArgs); + return new CompileResult + { + CommandText = valr.CommandText, + Value = valr.Value != null ? ConvertTo(valr.Value, ty) : null + }; + } + + if (expr.NodeType == ExpressionType.MemberAccess) + { + MemberExpression mem = (MemberExpression) expr; + + if (mem.Expression != null && mem.Expression.NodeType == ExpressionType.Parameter) + { + // + // This is a column of our table, output just the column name + // Need to translate it if that column name is mapped + // + string columnName = this.Table.FindColumnWithPropertyName(mem.Member.Name).Name; + return new CompileResult {CommandText = "\"" + columnName + "\""}; + } + + object obj = null; + if (mem.Expression != null) + { + CompileResult r = CompileExpr(mem.Expression, queryArgs); + if (r.Value == null) throw new NotSupportedException("Member access failed to compile expression"); + if (r.CommandText == "?") queryArgs.RemoveAt(queryArgs.Count - 1); + obj = r.Value; + } + + // + // Get the member value + // + object val = null; + + if (mem.Member.MemberType == MemberTypes.Property) + { + PropertyInfo m = (PropertyInfo) mem.Member; + val = m.GetValue(obj, null); + } + else if (mem.Member.MemberType == MemberTypes.Field) + { + FieldInfo m = (FieldInfo) mem.Member; + val = m.GetValue(obj); + } + else + { + throw new NotSupportedException("MemberExpr: " + mem.Member.MemberType); + } + + // + // Work special magic for enumerables + // + if (val != null && val is IEnumerable && !(val is string) && !(val is IEnumerable)) + { + StringBuilder sb = new StringBuilder(); + sb.Append("("); + string head = ""; + foreach (object a in (IEnumerable) val) + { + queryArgs.Add(a); + sb.Append(head); + sb.Append("?"); + head = ","; + } + + sb.Append(")"); + return new CompileResult + { + CommandText = sb.ToString(), + Value = val + }; + } + + queryArgs.Add(val); + return new CompileResult + { + CommandText = "?", + Value = val + }; + } + + throw new NotSupportedException("Cannot compile: " + expr.NodeType); + } + + private static object ConvertTo(object obj, Type t) + { + if (obj == null) + return null; + + Type nut = Nullable.GetUnderlyingType(t); + if (nut == null) + return Convert.ChangeType(obj, t); + return Convert.ChangeType(obj, nut); + } + + /// + /// Compiles a BinaryExpression where one of the parameters is null. + /// + /// The non-null parameter + private string CompileNullBinaryExpression(BinaryExpression expression, CompileResult parameter) + { + switch (expression.NodeType) + { + case ExpressionType.Equal: + return "(" + parameter.CommandText + " is ?)"; + case ExpressionType.NotEqual: + return "(" + parameter.CommandText + " is not ?)"; + default: + throw new NotSupportedException("Cannot compile Null-BinaryExpression with type " + + expression.NodeType); + } + } + + private string GetSqlName(Expression expr) + { + ExpressionType n = expr.NodeType; + + switch (n) + { + case ExpressionType.GreaterThan: + return ">"; + case ExpressionType.GreaterThanOrEqual: + return ">="; + case ExpressionType.LessThan: + return "<"; + case ExpressionType.LessThanOrEqual: + return "<="; + case ExpressionType.And: + return "&"; + case ExpressionType.AndAlso: + return "and"; + case ExpressionType.Or: + return "|"; + case ExpressionType.OrElse: + return "or"; + case ExpressionType.Equal: + return "="; + case ExpressionType.NotEqual: + return "!="; + default: + throw new NotSupportedException("Cannot get SQL for: " + n); + } + } + + public int Count() + { + return GenerateCommand("count(*)").ExecuteScalar(); + } + + public int Count(Expression> predExpr) + { + return Where(predExpr).Count(); + } + + public T First() + { + TableQuery query = Take(1); + return query.ToList().First(); + } + + public T FirstOrDefault() + { + TableQuery query = Take(1); + return query.ToList().FirstOrDefault(); + } + + private class CompileResult + { + public string CommandText { get; set; } + public object Value { get; set; } + } + } +} \ No newline at end of file diff --git a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/Sqlite3Define.cs b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/Sqlite3Define.cs index b891302..5aab4ca 100644 --- a/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/Sqlite3Define.cs +++ b/SqlCipher4Unity3D/Assets/SqlCipher4Unity3D/Sqlite3Define.cs @@ -1,72 +1,82 @@ -using System.Collections; -using System; +using System; using System.Collections.Generic; using System.Linq; namespace SqlCipher4Unity3D { - [Flags] - public enum SQLiteOpenFlags { - ReadOnly = 1, - ReadWrite = 2, - Create = 4, - NoMutex = 0x8000, - FullMutex = 0x10000, - SharedCache = 0x20000, - PrivateCache = 0x40000, - ProtectionComplete = 0x00100000, - ProtectionCompleteUnlessOpen = 0x00200000, - ProtectionCompleteUntilFirstUserAuthentication = 0x00300000, - ProtectionNone = 0x00400000 - } + [Flags] + public enum SQLiteOpenFlags + { + ReadOnly = 1, + ReadWrite = 2, + Create = 4, + NoMutex = 0x8000, + FullMutex = 0x10000, + SharedCache = 0x20000, + PrivateCache = 0x40000, + ProtectionComplete = 0x00100000, + ProtectionCompleteUnlessOpen = 0x00200000, + ProtectionCompleteUntilFirstUserAuthentication = 0x00300000, + ProtectionNone = 0x00400000 + } - [Flags] - public enum CreateFlags { - None = 0, - ImplicitPK = 1, // create a primary key for field called 'Id' (Orm.ImplicitPkName) - ImplicitIndex = 2, // create an index for fields ending in 'Id' (Orm.ImplicitIndexSuffix) - AllImplicit = 3, // do both above + [Flags] + public enum CreateFlags + { + None = 0, + ImplicitPK = 1, // create a primary key for field called 'Id' (Orm.ImplicitPkName) + ImplicitIndex = 2, // create an index for fields ending in 'Id' (Orm.ImplicitIndexSuffix) + AllImplicit = 3, // do both above - AutoIncPK = 4 // force PK field to be auto inc - } + AutoIncPK = 4 // force PK field to be auto inc + } - public class SQLiteException : Exception - { - public SQLite3.Result Result { get; private set; } + public class SQLiteException : Exception + { + protected SQLiteException(SQLite3.Result r, string message) : base(message) + { + this.Result = r; + } - protected SQLiteException (SQLite3.Result r,string message) : base(message) { - Result = r; - } + public SQLite3.Result Result { get; private set; } - public static SQLiteException New (SQLite3.Result r, string message) { - return new SQLiteException (r, message); - } - } + public static SQLiteException New(SQLite3.Result r, string message) + { + return new SQLiteException(r, message); + } + } - public class NotNullConstraintViolationException : SQLiteException - { - public IEnumerable Columns { get; protected set; } + public class NotNullConstraintViolationException : SQLiteException + { + protected NotNullConstraintViolationException(SQLite3.Result r, string message) : + this(r, message, null, null) { } - protected NotNullConstraintViolationException (SQLite3.Result r, string message) : this (r, message, null, null) { - } + protected NotNullConstraintViolationException(SQLite3.Result r, string message, TableMapping mapping, + object obj) : base(r, message) + { + if (mapping != null && obj != null) + this.Columns = from c in mapping.Columns + where c.IsNullable == false && c.GetValue(obj) == null + select c; + } - protected NotNullConstraintViolationException (SQLite3.Result r, string message, TableMapping mapping, object obj) : base (r, message) { - if (mapping != null && obj != null) { - this.Columns = from c in mapping.Columns where c.IsNullable == false && c.GetValue (obj) == null select c; - } - } + public IEnumerable Columns { get; protected set; } - public static new NotNullConstraintViolationException New (SQLite3.Result r, string message) { - return new NotNullConstraintViolationException (r, message); - } + public new static NotNullConstraintViolationException New(SQLite3.Result r, string message) + { + return new NotNullConstraintViolationException(r, message); + } - public static NotNullConstraintViolationException New (SQLite3.Result r, string message, TableMapping mapping, object obj) { - return new NotNullConstraintViolationException (r, message, mapping, obj); - } - - public static NotNullConstraintViolationException New (SQLiteException exception, TableMapping mapping, object obj) { - return new NotNullConstraintViolationException (exception.Result, exception.Message, mapping, obj); - } - } + public static NotNullConstraintViolationException New(SQLite3.Result r, string message, TableMapping mapping, + object obj) + { + return new NotNullConstraintViolationException(r, message, mapping, obj); + } + public static NotNullConstraintViolationException New(SQLiteException exception, TableMapping mapping, + object obj) + { + return new NotNullConstraintViolationException(exception.Result, exception.Message, mapping, obj); + } + } } \ No newline at end of file diff --git a/SqlCipher4Unity3D/Assets/StreamingAssets/existing.db b/SqlCipher4Unity3D/Assets/StreamingAssets/existing.db index b6cac5b..29fc29b 100644 Binary files a/SqlCipher4Unity3D/Assets/StreamingAssets/existing.db and b/SqlCipher4Unity3D/Assets/StreamingAssets/existing.db differ diff --git a/SqlCipher4Unity3D/Assets/StreamingAssets/tempDatabase.db b/SqlCipher4Unity3D/Assets/StreamingAssets/tempDatabase.db new file mode 100644 index 0000000..e8dfab9 Binary files /dev/null and b/SqlCipher4Unity3D/Assets/StreamingAssets/tempDatabase.db differ diff --git a/SqlCipher4Unity3D/Assets/example/Scripts.meta b/SqlCipher4Unity3D/Assets/StreamingAssets/tempDatabase.db.meta similarity index 56% rename from SqlCipher4Unity3D/Assets/example/Scripts.meta rename to SqlCipher4Unity3D/Assets/StreamingAssets/tempDatabase.db.meta index 314d30e..6ffa690 100644 --- a/SqlCipher4Unity3D/Assets/example/Scripts.meta +++ b/SqlCipher4Unity3D/Assets/StreamingAssets/tempDatabase.db.meta @@ -1,9 +1,9 @@ fileFormatVersion: 2 -guid: 817994a91ac197e42baaef83a9db4e19 -folderAsset: yes -timeCreated: 1453576960 +guid: 8ef567af992464f7094dce4343bdd95b +timeCreated: 1529077792 licenseType: Free DefaultImporter: + externalObjects: {} userData: assetBundleName: assetBundleVariant: diff --git a/SqlCipher4Unity3D/Assets/example/Scenes/CreateDBFromScript.unity b/SqlCipher4Unity3D/Assets/example/CreateDBFromScript.unity similarity index 100% rename from SqlCipher4Unity3D/Assets/example/Scenes/CreateDBFromScript.unity rename to SqlCipher4Unity3D/Assets/example/CreateDBFromScript.unity diff --git a/SqlCipher4Unity3D/Assets/example/Scenes/CreateDBFromScript.unity.meta b/SqlCipher4Unity3D/Assets/example/CreateDBFromScript.unity.meta similarity index 100% rename from SqlCipher4Unity3D/Assets/example/Scenes/CreateDBFromScript.unity.meta rename to SqlCipher4Unity3D/Assets/example/CreateDBFromScript.unity.meta diff --git a/SqlCipher4Unity3D/Assets/example/CreateDBScript.cs b/SqlCipher4Unity3D/Assets/example/CreateDBScript.cs new file mode 100644 index 0000000..92473af --- /dev/null +++ b/SqlCipher4Unity3D/Assets/example/CreateDBScript.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.UI; + +namespace example +{ + public class CreateDBScript : MonoBehaviour + { + public Text DebugText; + + // Use this for initialization + private void Start() + { + StartSync(); + } + + private void StartSync() + { + DataService ds = new DataService("tempDatabase.db"); + ds.CreateDB(); + + IEnumerable people = ds.GetPersons(); + ToConsole(people); + people = ds.GetPersonsNamedRoberto(); + ToConsole("Searching for Roberto ..."); + ToConsole(people); + } + + private void ToConsole(IEnumerable people) + { + foreach (Person person in people) ToConsole(person.ToString()); + } + + private void ToConsole(string msg) + { + this.DebugText.text += Environment.NewLine + msg; + Debug.Log(msg); + } + } +} diff --git a/SqlCipher4Unity3D/Assets/example/Scripts/CreateDBScript.cs.meta b/SqlCipher4Unity3D/Assets/example/CreateDBScript.cs.meta similarity index 100% rename from SqlCipher4Unity3D/Assets/example/Scripts/CreateDBScript.cs.meta rename to SqlCipher4Unity3D/Assets/example/CreateDBScript.cs.meta diff --git a/SqlCipher4Unity3D/Assets/example/DataService.cs b/SqlCipher4Unity3D/Assets/example/DataService.cs new file mode 100644 index 0000000..8de5172 --- /dev/null +++ b/SqlCipher4Unity3D/Assets/example/DataService.cs @@ -0,0 +1,133 @@ +using System.Collections.Generic; +using SqlCipher4Unity3D; +using UnityEngine; + +#if !UNITY_EDITOR +using System.Collections; +using System.IO; +#endif +namespace example +{ + + public class DataService + { + private readonly SQLiteConnection _connection; + + public DataService(string DatabaseName) + { +#if UNITY_EDITOR + string dbPath = string.Format(@"Assets/StreamingAssets/{0}", DatabaseName); +#else +// check if file exists in Application.persistentDataPath + var filepath = string.Format ("{0}/{1}", Application.persistentDataPath, DatabaseName); + + if (!File.Exists (filepath)) { + Debug.Log ("Database not in Persistent path"); + // if it doesn't -> + // open StreamingAssets directory and load the db -> + +#if UNITY_ANDROID + var loadDb = + new WWW ("jar:file://" + Application.dataPath + "!/assets/" + DatabaseName); // this is the path to your StreamingAssets in android + while (!loadDb.isDone) { } // CAREFUL here, for safety reasons you shouldn't let this while loop unattended, place a timer and error check + // then save to Application.persistentDataPath + File.WriteAllBytes (filepath, loadDb.bytes); +#elif UNITY_IOS + var loadDb = + Application.dataPath + "/Raw/" + DatabaseName; // this is the path to your StreamingAssets in iOS + // then save to Application.persistentDataPath + File.Copy (loadDb, filepath); +#elif UNITY_WP8 + var loadDb = + Application.dataPath + "/StreamingAssets/" + DatabaseName; // this is the path to your StreamingAssets in iOS + // then save to Application.persistentDataPath + File.Copy (loadDb, filepath); + +#elif UNITY_WINRT + var loadDb = + Application.dataPath + "/StreamingAssets/" + DatabaseName; // this is the path to your StreamingAssets in iOS + // then save to Application.persistentDataPath + File.Copy (loadDb, filepath); +#else + var loadDb = + Application.dataPath + "/StreamingAssets/" + DatabaseName; // this is the path to your StreamingAssets in iOS + // then save to Application.persistentDataPath + File.Copy (loadDb, filepath); + +#endif + + Debug.Log ("Database written"); + } + + var dbPath = filepath; +#endif + this._connection = new SQLiteConnection(dbPath, "password"); + Debug.Log("Final PATH: " + dbPath); + } + + public void CreateDB() + { + this._connection.DropTable(); + this._connection.CreateTable(); + + this._connection.InsertAll(new[] + { + new Person + { + Id = 1, + Name = "Tom", + Surname = "Perez", + Age = 56 + }, + new Person + { + Id = 2, + Name = "Fred", + Surname = "Arthurson", + Age = 16 + }, + new Person + { + Id = 3, + Name = "John", + Surname = "Doe", + Age = 25 + }, + new Person + { + Id = 4, + Name = "Roberto", + Surname = "Huertas", + Age = 37 + } + }); + } + + public IEnumerable GetPersons() + { + return this._connection.Table(); + } + + public IEnumerable GetPersonsNamedRoberto() + { + return this._connection.Table().Where(x => x.Name == "Roberto"); + } + + public Person GetJohnny() + { + return this._connection.Table().Where(x => x.Name == "Johnny").FirstOrDefault(); + } + + public Person CreatePerson() + { + Person p = new Person + { + Name = "Johnny", + Surname = "Mnemonic", + Age = 21 + }; + this._connection.Insert(p); + return p; + } + } +} \ No newline at end of file diff --git a/SqlCipher4Unity3D/Assets/example/Scripts/DataService.cs.meta b/SqlCipher4Unity3D/Assets/example/DataService.cs.meta similarity index 100% rename from SqlCipher4Unity3D/Assets/example/Scripts/DataService.cs.meta rename to SqlCipher4Unity3D/Assets/example/DataService.cs.meta diff --git a/SqlCipher4Unity3D/Assets/example/ExistingDBScript.cs b/SqlCipher4Unity3D/Assets/example/ExistingDBScript.cs new file mode 100644 index 0000000..1da4a47 --- /dev/null +++ b/SqlCipher4Unity3D/Assets/example/ExistingDBScript.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.UI; + +namespace example +{ + + public class ExistingDBScript : MonoBehaviour + { + public Text DebugText; + + // Use this for initialization + private void Start() + { + DataService ds = new DataService("existing.db"); + ds.CreateDB(); + IEnumerable people = ds.GetPersons(); + ToConsole(people); + + people = ds.GetPersonsNamedRoberto(); + ToConsole("Searching for Roberto ..."); + ToConsole(people); + + ds.CreatePerson(); + ToConsole("New person has been created"); + Person p = ds.GetJohnny(); + ToConsole(p.ToString()); + } + + private void ToConsole(IEnumerable people) + { + foreach (Person person in people) ToConsole(person.ToString()); + } + + private void ToConsole(string msg) + { + this.DebugText.text += Environment.NewLine + msg; + Debug.Log(msg); + } + } +} \ No newline at end of file diff --git a/SqlCipher4Unity3D/Assets/example/Scripts/ExistingDBScript.cs.meta b/SqlCipher4Unity3D/Assets/example/ExistingDBScript.cs.meta similarity index 100% rename from SqlCipher4Unity3D/Assets/example/Scripts/ExistingDBScript.cs.meta rename to SqlCipher4Unity3D/Assets/example/ExistingDBScript.cs.meta diff --git a/SqlCipher4Unity3D/Assets/example/Scenes/ExistingDBScript.unity b/SqlCipher4Unity3D/Assets/example/ExistingDBScript.unity similarity index 100% rename from SqlCipher4Unity3D/Assets/example/Scenes/ExistingDBScript.unity rename to SqlCipher4Unity3D/Assets/example/ExistingDBScript.unity diff --git a/SqlCipher4Unity3D/Assets/example/Scenes/ExistingDBScript.unity.meta b/SqlCipher4Unity3D/Assets/example/ExistingDBScript.unity.meta similarity index 100% rename from SqlCipher4Unity3D/Assets/example/Scenes/ExistingDBScript.unity.meta rename to SqlCipher4Unity3D/Assets/example/ExistingDBScript.unity.meta diff --git a/SqlCipher4Unity3D/Assets/example/Person.cs b/SqlCipher4Unity3D/Assets/example/Person.cs new file mode 100644 index 0000000..e6d1672 --- /dev/null +++ b/SqlCipher4Unity3D/Assets/example/Person.cs @@ -0,0 +1,22 @@ +using SQLite.Attribute; +using UnityEngine.Scripting; + +namespace example +{ + + [Preserve] + public class Person + { + [PrimaryKey] [AutoIncrement] public int Id { get; set; } + + public string Name { get; set; } + public string Surname { get; set; } + public int Age { get; set; } + + public override string ToString() + { + return string.Format("[Person: Id={0}, Name={1}, Surname={2}, Age={3}]", this.Id, this.Name, this.Surname, + this.Age); + } + } +} \ No newline at end of file diff --git a/SqlCipher4Unity3D/Assets/example/Scripts/Person.cs.meta b/SqlCipher4Unity3D/Assets/example/Person.cs.meta similarity index 100% rename from SqlCipher4Unity3D/Assets/example/Scripts/Person.cs.meta rename to SqlCipher4Unity3D/Assets/example/Person.cs.meta diff --git a/SqlCipher4Unity3D/Assets/example/Scripts/CreateDBScript.cs b/SqlCipher4Unity3D/Assets/example/Scripts/CreateDBScript.cs deleted file mode 100644 index 77701e4..0000000 --- a/SqlCipher4Unity3D/Assets/example/Scripts/CreateDBScript.cs +++ /dev/null @@ -1,36 +0,0 @@ -using UnityEngine; -using System.Collections.Generic; -using UnityEngine.UI; - -public class CreateDBScript : MonoBehaviour { - - public Text DebugText; - - // Use this for initialization - void Start () { - StartSync(); - } - - private void StartSync() - { - var ds = new DataService("tempDatabase.db"); - ds.CreateDB(); - - var people = ds.GetPersons (); - ToConsole (people); - people = ds.GetPersonsNamedRoberto (); - ToConsole("Searching for Roberto ..."); - ToConsole (people); - } - - private void ToConsole(IEnumerable people){ - foreach (var person in people) { - ToConsole(person.ToString()); - } - } - - private void ToConsole(string msg){ - DebugText.text += System.Environment.NewLine + msg; - Debug.Log (msg); - } -} diff --git a/SqlCipher4Unity3D/Assets/example/Scripts/DataService.cs b/SqlCipher4Unity3D/Assets/example/Scripts/DataService.cs deleted file mode 100644 index 01693e9..0000000 --- a/SqlCipher4Unity3D/Assets/example/Scripts/DataService.cs +++ /dev/null @@ -1,114 +0,0 @@ -using SqlCipher4Unity3D; -using UnityEngine; -#if !UNITY_EDITOR -using System.Collections; -using System.IO; -#endif -using System.Collections.Generic; - -public class DataService { - - private SQLiteConnection _connection; - - public DataService(string DatabaseName){ - -#if UNITY_EDITOR - var dbPath = string.Format(@"Assets/StreamingAssets/{0}", DatabaseName); -#else - // check if file exists in Application.persistentDataPath - var filepath = string.Format("{0}/{1}", Application.persistentDataPath, DatabaseName); - - if (!File.Exists(filepath)) - { - Debug.Log("Database not in Persistent path"); - // if it doesn't -> - // open StreamingAssets directory and load the db -> - -#if UNITY_ANDROID - var loadDb = new WWW("jar:file://" + Application.dataPath + "!/assets/" + DatabaseName); // this is the path to your StreamingAssets in android - while (!loadDb.isDone) { } // CAREFUL here, for safety reasons you shouldn't let this while loop unattended, place a timer and error check - // then save to Application.persistentDataPath - File.WriteAllBytes(filepath, loadDb.bytes); -#elif UNITY_IOS - var loadDb = Application.dataPath + "/Raw/" + DatabaseName; // this is the path to your StreamingAssets in iOS - // then save to Application.persistentDataPath - File.Copy(loadDb, filepath); -#elif UNITY_WP8 - var loadDb = Application.dataPath + "/StreamingAssets/" + DatabaseName; // this is the path to your StreamingAssets in iOS - // then save to Application.persistentDataPath - File.Copy(loadDb, filepath); - -#elif UNITY_WINRT - var loadDb = Application.dataPath + "/StreamingAssets/" + DatabaseName; // this is the path to your StreamingAssets in iOS - // then save to Application.persistentDataPath - File.Copy(loadDb, filepath); -#else - var loadDb = Application.dataPath + "/StreamingAssets/" + DatabaseName; // this is the path to your StreamingAssets in iOS - // then save to Application.persistentDataPath - File.Copy(loadDb, filepath); - -#endif - - Debug.Log("Database written"); - } - - var dbPath = filepath; -#endif - _connection = new SQLiteConnection(dbPath, "password"); - Debug.Log("Final PATH: " + dbPath); - } - - public void CreateDB(){ - _connection.DropTable (); - _connection.CreateTable (); - - _connection.InsertAll (new[]{ - new Person{ - Id = 1, - Name = "Tom", - Surname = "Perez", - Age = 56 - }, - new Person{ - Id = 2, - Name = "Fred", - Surname = "Arthurson", - Age = 16 - }, - new Person{ - Id = 3, - Name = "John", - Surname = "Doe", - Age = 25 - }, - new Person{ - Id = 4, - Name = "Roberto", - Surname = "Huertas", - Age = 37 - } - }); - } - - public IEnumerable GetPersons(){ - return _connection.Table(); - } - - public IEnumerable GetPersonsNamedRoberto(){ - return _connection.Table().Where(x => x.Name == "Roberto"); - } - - public Person GetJohnny(){ - return _connection.Table().Where(x => x.Name == "Johnny").FirstOrDefault(); - } - - public Person CreatePerson(){ - var p = new Person{ - Name = "Johnny", - Surname = "Mnemonic", - Age = 21 - }; - _connection.Insert (p); - return p; - } -} diff --git a/SqlCipher4Unity3D/Assets/example/Scripts/ExistingDBScript.cs b/SqlCipher4Unity3D/Assets/example/Scripts/ExistingDBScript.cs deleted file mode 100644 index c922954..0000000 --- a/SqlCipher4Unity3D/Assets/example/Scripts/ExistingDBScript.cs +++ /dev/null @@ -1,38 +0,0 @@ -using UnityEngine; -using System.Collections.Generic; -using UnityEngine.UI; - -public class ExistingDBScript : MonoBehaviour { - - public Text DebugText; - - // Use this for initialization - void Start () { - var ds = new DataService ("existing.db"); - ds.CreateDB (); - var people = ds.GetPersons (); - ToConsole (people); - - people = ds.GetPersonsNamedRoberto (); - ToConsole("Searching for Roberto ..."); - ToConsole (people); - - ds.CreatePerson (); - ToConsole("New person has been created"); - var p = ds.GetJohnny (); - ToConsole(p.ToString()); - - } - - private void ToConsole(IEnumerable people){ - foreach (var person in people) { - ToConsole(person.ToString()); - } - } - - private void ToConsole(string msg){ - DebugText.text += System.Environment.NewLine + msg; - Debug.Log (msg); - } - -} diff --git a/SqlCipher4Unity3D/Assets/example/Scripts/Person.cs b/SqlCipher4Unity3D/Assets/example/Scripts/Person.cs deleted file mode 100644 index d514ef1..0000000 --- a/SqlCipher4Unity3D/Assets/example/Scripts/Person.cs +++ /dev/null @@ -1,17 +0,0 @@ -using SQLite.Attribute; -using UnityEngine.Scripting; - -[Preserve] -public class Person { - - [PrimaryKey, AutoIncrement] - public int Id { get; set; } - public string Name { get; set; } - public string Surname { get; set; } - public int Age { get; set; } - - public override string ToString () - { - return string.Format ("[Person: Id={0}, Name={1}, Surname={2}, Age={3}]", Id, Name, Surname, Age); - } -} diff --git a/SqlCipher4Unity3D/Assets/unitypackage-builder/Editor/PackageTool.cs b/SqlCipher4Unity3D/Assets/unitypackage-builder/Editor/PackageTool.cs index 9a53db0..6b20ef9 100644 --- a/SqlCipher4Unity3D/Assets/unitypackage-builder/Editor/PackageTool.cs +++ b/SqlCipher4Unity3D/Assets/unitypackage-builder/Editor/PackageTool.cs @@ -1,11 +1,11 @@ -using UnityEngine; -using UnityEditor; +using UnityEditor; public class PackageTool { [MenuItem("Package/Update Package")] static void UpdatePackage() { - AssetDatabase.ExportPackage(new string[] {"Assets/SqlCipher4Unity3D"}, "../SqlCipher4Unity3D.unitypackage", ExportPackageOptions.Recurse); + const string version = "v1.0.0"; + AssetDatabase.ExportPackage(new string[] {"Assets/SqlCipher4Unity3D"}, $"../SqlCipher4Unity3D-{version}.unitypackage", ExportPackageOptions.Recurse); } }