diff --git a/MatterControlLib/DataStorage/SQLiteUnix.cs b/MatterControl.Winforms/DataStorage/SQLiteUnix.cs similarity index 100% rename from MatterControlLib/DataStorage/SQLiteUnix.cs rename to MatterControl.Winforms/DataStorage/SQLiteUnix.cs diff --git a/MatterControlLib/DataStorage/SQLiteWin32.cs b/MatterControl.Winforms/DataStorage/SQLiteWin32.cs similarity index 99% rename from MatterControlLib/DataStorage/SQLiteWin32.cs rename to MatterControl.Winforms/DataStorage/SQLiteWin32.cs index 934dbac09..e43768f54 100644 --- a/MatterControlLib/DataStorage/SQLiteWin32.cs +++ b/MatterControl.Winforms/DataStorage/SQLiteWin32.cs @@ -44,9 +44,35 @@ using Sqlite3Statement = System.IntPtr; using MatterHackers.MatterControl.DataStorage; using System.Runtime.InteropServices; +using MatterHackers.Agg.Platform; namespace SQLiteWin32 { + public static class DesktopSqlite + { + public static ISQLite CreateInstance() + { + ISQLite dbSQLite; + + string datastoreLocation = ApplicationDataStorage.Instance.DatastorePath; + switch (AggContext.OperatingSystem) + { + + case OSType.Mac: + return new SQLiteUnix.SQLiteConnection(datastoreLocation); + break; + + case OSType.X11: + return new SQLiteUnix.SQLiteConnection(datastoreLocation); + break; + + default: + return new SQLiteWin32.SQLiteConnection(datastoreLocation); + break; + } + } + } + public class SQLiteException : System.Exception { public SQLite3.Result Result { get; private set; } diff --git a/MatterControl.Winforms/MatterControl.Winforms.csproj b/MatterControl.Winforms/MatterControl.Winforms.csproj index 3bcab6e54..0c3337fde 100644 --- a/MatterControl.Winforms/MatterControl.Winforms.csproj +++ b/MatterControl.Winforms/MatterControl.Winforms.csproj @@ -18,7 +18,7 @@ full false ..\bin\Debug\ - DEBUG;TRACE + TRACE;DEBUG;USE_OPENGL;IS_WINDOWS;IS_WINDOWS_FORMS prompt 4 AnyCPU @@ -45,6 +45,8 @@ + + Form @@ -56,6 +58,10 @@ + + {f1653f20-d47d-4f29-8c55-3c835542af5f} + Community.CsharpSqlite + {D557B079-612F-467F-AE0D-3F77BCD627F7} MatterControlLib diff --git a/MatterControl.csproj b/MatterControl.csproj index d0ea09bb4..fc6074c4a 100644 --- a/MatterControl.csproj +++ b/MatterControl.csproj @@ -76,6 +76,10 @@ {97d5ade3-c1b4-4b46-8a3e-718a4f7f079f} MatterControl.Printing + + {D6DC2669-7B1F-40FE-89BF-45D4C94473E3} + MatterControl.Winforms + {93bebfdf-b81a-4344-ab82-0dbf58b234cd} MatterControlLib diff --git a/MatterControlLib/DataStorage/ApplicationDataStorage.cs b/MatterControlLib/DataStorage/ApplicationDataStorage.cs index d268a73d2..72d7bfe3a 100644 --- a/MatterControlLib/DataStorage/ApplicationDataStorage.cs +++ b/MatterControlLib/DataStorage/ApplicationDataStorage.cs @@ -103,12 +103,10 @@ namespace MatterHackers.MatterControl.DataStorage /// public string DatastorePath => Path.Combine(EnsurePath(_applicationUserDataPath), datastoreName); -#if __ANDROID__ /// /// Returns the public storage folder (ex. download folder on Android) /// - public string PublicDataStoragePath { get; } = Android.OS.Environment.GetExternalStoragePublicDirectory(Android.OS.Environment.DirectoryDownloads).AbsolutePath; -#endif + public string PublicDataStoragePath { get; set; } /// /// Invokes CreateDirectory on all paths, creating if missing, before returning @@ -124,7 +122,7 @@ namespace MatterHackers.MatterControl.DataStorage /// Overrides the AppData location. Used by tests to set a non-standard AppData location /// /// The new AppData path. - internal void OverrideAppDataLocation(string path) + internal void OverrideAppDataLocation(string path, ISQLite sqlite) { Console.WriteLine(" Overriding ApplicationUserDataPath: " + path); @@ -135,7 +133,7 @@ namespace MatterHackers.MatterControl.DataStorage // Initialize a fresh datastore instance after overriding the AppData path Datastore.Instance = new Datastore(); - Datastore.Instance.Initialize(); + Datastore.Instance.Initialize(sqlite); } public string GetTempFileName(string fileExtension = null) diff --git a/MatterControlLib/DataStorage/Datastore.cs b/MatterControlLib/DataStorage/Datastore.cs index f3d9a68e0..b5c89202a 100644 --- a/MatterControlLib/DataStorage/Datastore.cs +++ b/MatterControlLib/DataStorage/Datastore.cs @@ -66,29 +66,6 @@ namespace MatterHackers.MatterControl.DataStorage { ApplicationDataStorage.Instance.FirstRun = true; } - - OSType osType = AggContext.OperatingSystem; - switch (osType) - { - case OSType.Windows: - dbSQLite = new SQLiteWin32.SQLiteConnection(datastoreLocation); - break; - - case OSType.Mac: - dbSQLite = new SQLiteUnix.SQLiteConnection(datastoreLocation); - break; - - case OSType.X11: - dbSQLite = new SQLiteUnix.SQLiteConnection(datastoreLocation); - break; - - case OSType.Android: - dbSQLite = new SQLiteAndroid.SQLiteConnection(datastoreLocation); - break; - - default: - throw new NotImplementedException(); - } } public static Datastore Instance @@ -102,7 +79,7 @@ namespace MatterHackers.MatterControl.DataStorage return globalInstance; } - // Special case to allow tests to set custom application paths + // Special case to allow tests to set custom application paths internal set { globalInstance = value; @@ -146,8 +123,9 @@ namespace MatterHackers.MatterControl.DataStorage } //Run initial checks and operations on sqlite datastore - public void Initialize() + public void Initialize(ISQLite dbSQLite) { + this.dbSQLite = dbSQLite; ValidateSchema(); // Construct the root library collection if missing diff --git a/MatterControlLib/DataStorage/SQLiteAndroid.cs b/MatterControlLib/DataStorage/SQLiteAndroid.cs deleted file mode 100644 index eab975ee1..000000000 --- a/MatterControlLib/DataStorage/SQLiteAndroid.cs +++ /dev/null @@ -1,3257 +0,0 @@ -// -// 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. -// - -//#define USE_CSHARP_SQLITE - -#if NETFX_CORE -#define USE_NEW_REFLECTION_API -#endif - -using System; -using System.Diagnostics; - -#if !USE_SQLITEPCL_RAW - -using System.Runtime.InteropServices; - -#endif - -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; -#elif USE_SQLITEPCL_RAW -using Sqlite3DatabaseHandle = SQLitePCL.sqlite3; -using Sqlite3Statement = SQLitePCL.sqlite3_stmt; -using Sqlite3 = SQLitePCL.raw; -#else - -using Sqlite3DatabaseHandle = System.IntPtr; - -using Sqlite3Statement = System.IntPtr; -#endif - -using MatterHackers.MatterControl.DataStorage; - -namespace SQLiteAndroid -{ - public class SQLiteException : System.Exception - { - public SQLite3.Result Result { get; private set; } - - protected SQLiteException(SQLite3.Result r, string message) - : base(message) - { - Result = r; - } - - public static SQLiteException New(SQLite3.Result r, string message) - { - return new SQLiteException(r, message); - } - } - - [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 - } - - /// - /// Represents an open connection to a SQLite database. - /// - public partial class SQLiteConnection : IDisposable, ISQLite - { - 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 _trasactionDepth = 0; - private Random _rand = new Random(); - - public Sqlite3DatabaseHandle Handle { get; private set; } - -#if USE_CSHARP_SQLITE - internal static readonly Sqlite3DatabaseHandle NullHandle = null; -#else - internal static readonly Sqlite3DatabaseHandle NullHandle = IntPtr.Zero; -#endif - - 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, bool storeDateTimeAsTicks = false) - : this(databasePath, 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, SQLiteOpenFlags openFlags, bool storeDateTimeAsTicks = false) - { - 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)); - } - _open = true; - - StoreDateTimeAsTicks = storeDateTimeAsTicks; - - BusyTimeout = TimeSpan.FromSeconds(0.1); - } - - static SQLiteConnection() - { - if (_preserveDuringLinkMagic) - { - var ti = new ColumnInfo(); - ti.Name = "magic"; - } - } - - private 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. - /// - private static bool _preserveDuringLinkMagic = false; - - /// - /// 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 - { - if (_tables == null) - { - return Enumerable.Empty(); - } - else - { - return _tables.Values; - } - } - } - - /// - /// Retrieves the mapping that is automatically generated for the given type. - /// - /// - /// The type whose mapping to the database is returned. - /// - /// - /// 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) - { - if (_mappings == null) - { - _mappings = new Dictionary(); - } - TableMapping map; - if (!_mappings.TryGetValue(type.FullName, out map)) - { - map = new TableMapping(type); - _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() - { - return DropTable(typeof(T)); - } - - /// - /// Executes a "drop table" on the database. This is non-recoverable. - /// - /// Type to reflect to a database table. - public int DropTable(Type ty) - { - lock (locker) - { - TableMapping map = GetMapping(ty); - 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() - { - return CreateTable(typeof(T)); - } - - /// - /// 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. - /// - /// The number of entries added to the database schema. - /// - public int CreateTable(Type ty) - { - lock (locker) - { - if (_tables == null) - { - _tables = new Dictionary(); - } - TableMapping map; - if (!_tables.TryGetValue(ty.FullName, out map)) - { - map = GetMapping(ty); - _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]; - const string sqlFormat = "create {3} index if not exists \"{0}\" on \"{1}\"(\"{2}\")"; - var columns = String.Join("\",\"", index.Columns.OrderBy(i => i.Order).Select(i => i.ColumnName).ToArray()); - var sql = String.Format(sqlFormat, indexName, index.TableName, columns, index.Unique ? "unique" : ""); - count += Execute(sql); - } - - return count; - } - } - - 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 IEnumerable GetTableInfo(string tableName) - { - var query = "pragma table_info(\"" + tableName + "\")"; - return Query(query); - } - - private 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.InvariantCultureIgnoreCase) == 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 occurrences 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"); - } - else - { - 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 occurrences 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 System.Diagnostics.Stopwatch(); - } - _sw.Reset(); - _sw.Start(); - } - - var r = cmd.ExecuteNonQuery(); - - if (TimeExecution) - { - _sw.Stop(); - _elapsedMilliseconds += _sw.ElapsedMilliseconds; - Debug.WriteLine(string.Format("Finished in {0} ms ({1:0.0} s total)", _sw.ElapsedMilliseconds, _elapsedMilliseconds / 1000.0)); - } - - return r; - } - - public T ExecuteScalar(string query, params object[] args) - { - lock (locker) - { - var cmd = CreateCommand(query, args); - - if (TimeExecution) - { - if (_sw == null) - { - _sw = new System.Diagnostics.Stopwatch(); - } - _sw.Reset(); - _sw.Start(); - } - - var r = cmd.ExecuteScalar(); - - if (TimeExecution) - { - _sw.Stop(); - _elapsedMilliseconds += _sw.ElapsedMilliseconds; - Debug.WriteLine(string.Format("Finished in {0} ms ({1:0.0} s total)", _sw.ElapsedMilliseconds, _elapsedMilliseconds / 1000.0)); - } - - 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 occurrences 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); - lock (locker) - { - 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 occurrences 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 occurrences 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 occurrences 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 ITableQuery Table() where T : new() - { - lock (locker) - { - return new SQLiteAndroid.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 _trasactionDepth > 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 _trasactionDepth, 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 _trasactionDepth); - } - - throw; - } - } - else - { - // Calling BeginTransaction on an already open transaction is invalid - throw new System.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 _trasactionDepth) - 1; - string retVal = "S" + (short)_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 _trasactionDepth); - } - - 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 _trasactionDepth, 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 (Int32.TryParse(savepoint.Substring(firstLen + 1), out depth)) - { - // TODO: Mild race here, but inescapable without locking almost everywhere. - if (0 <= depth && depth < _trasactionDepth) - { -#if NETFX_CORE - Volatile.Write (ref _trasactionDepth, depth); -#elif SILVERLIGHT - _trasactionDepth = depth; -#else - Thread.VolatileWrite(ref _trasactionDepth, depth); -#endif - Execute(cmd + savepoint); - return; - } - } - } - - throw new ArgumentException("savePoint", "savePoint is not valid, and should be the result of a call to SaveTransactionPoint."); - } - - /// - /// Commits the transaction that was begun by . - /// - public void Commit() - { - if (Interlocked.Exchange(ref _trasactionDepth, 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) - { - lock (locker) - { - 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) - { - lock (locker) - { - - 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; - } - lock (locker) - { - return Insert(obj, "", obj.GetType()); - } - } - - object locker = new object(); - - /// - /// 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); - - var replacing = string.Compare(extra, "OR REPLACE", StringComparison.InvariantCultureIgnoreCase) == 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); - var count = insertCmd.ExecuteNonQuery(vals); - - 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; - } - lock (locker) - { - 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) - { - 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); - return Execute(q, ps.ToArray()); - } - - /// - /// 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) - { - lock (locker) - { - 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. - /// - internal 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 - } - } - - [AttributeUsage(AttributeTargets.Class)] - public class TableAttribute : Attribute - { - public string Name { get; set; } - - public TableAttribute(string name) - { - Name = name; - } - } - - [AttributeUsage(AttributeTargets.Property)] - public class ColumnAttribute : Attribute - { - public string Name { get; set; } - - public ColumnAttribute(string name) - { - Name = name; - } - } - - [AttributeUsage(AttributeTargets.Property)] - public class IgnoreAttribute : Attribute - { - } - - [AttributeUsage(AttributeTargets.Property)] - public class UniqueAttribute : IndexedAttribute - { - public override bool Unique - { - get { return true; } - set { /* throw? */ } - } - } - - [AttributeUsage(AttributeTargets.Property)] - public class MaxLengthAttribute : Attribute - { - public int Value { get; private set; } - - public MaxLengthAttribute(int length) - { - Value = length; - } - } - - [AttributeUsage(AttributeTargets.Property)] - public class CollationAttribute : Attribute - { - public string Value { get; private set; } - - public CollationAttribute(string collation) - { - Value = collation; - } - } - - 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; } - - private Column _autoPk = null; - private Column[] _insertColumns = null; - private Column[] _insertOrReplaceColumns = null; - - public TableMapping(Type type) - { - 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)); - } - } - 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.Where(c => c.PropertyName == propertyName).FirstOrDefault(); - return exact; - } - - public Column FindColumn(string columnName) - { - var exact = Columns.Where(c => c.Name == columnName).FirstOrDefault(); - return exact; - } - - private PreparedSqlLiteInsertCommand _insertCommand; - private string _insertCommandExtra = null; - - 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; - } - - private 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.InvariantCultureIgnoreCase) == 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 - { - private 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 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) - { - 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); - IsAutoInc = Orm.IsAutoInc(prop); - IsPK = Orm.IsPK(prop); - Indices = Orm.GetIndices(prop); - IsNullable = !IsPK; - 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 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; - return "varchar(" + len + ")"; - } - else if (clrType == typeof(DateTime)) - { - return storeDateTimeAsTicks ? "bigint" : "datetime"; -#if !NETFX_CORE - } - else if (clrType.IsEnum) - { -#else - } else if (clrType.GetTypeInfo().IsEnum) { -#endif - return "integer"; - } - else if (clrType == typeof(byte[])) - { - return "blob"; -#if SQLITE_SUPPORT_GUID - } else if (clrType == typeof(Guid)) { - return "varchar(36)"; -#endif - } - 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 - } - else - { - return DefaultMaxStringLength; - } - } - } - - public partial class SQLiteCommand - { - private 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) - { - 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 - { - 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) - { - 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) - { - Debug.WriteLine("Executing Query: " + this); - } - - T val = default(T); - - var stmt = Prepare(); - if (SQLite3.Step(stmt) == SQLite3.Result.Row) - { - var colType = SQLite3.ColumnType(stmt, 0); - //Check for null columns - if (colType != SQLite3.ColType.Null) - { - val = (T)ReadCol(stmt, 0, colType, typeof(T)); - } - } - 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); - } - - private Sqlite3Statement Prepare() - { - var stmt = SQLite3.Prepare2(_conn.Handle, CommandText); - BindAll(stmt); - return stmt; - } - - private void Finalize(Sqlite3Statement stmt) - { - SQLite3.Finalize(stmt); - } - - private 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 void BindParameter(Sqlite3Statement stmt, int index, object value, bool storeDateTimeAsTicks) - { - if (value == null) - { - SQLite3.BindNull(stmt, index); - } - else - { - if (value is Int32) - { - SQLite3.BindInt(stmt, index, (int)value); - } - else if (value is String) - { - SQLite3.BindText(stmt, index, (string)value, -1, NegativePointer); - } - else if (value is Byte || value is UInt16 || value is SByte || value is Int16) - { - SQLite3.BindInt(stmt, index, Convert.ToInt32(value)); - } - else if (value is Boolean) - { - SQLite3.BindInt(stmt, index, (bool)value ? 1 : 0); - } - else if (value is UInt32 || value is Int64) - { - SQLite3.BindInt64(stmt, index, Convert.ToInt64(value)); - } - else if (value is Single || value is Double || value is Decimal) - { - SQLite3.BindDouble(stmt, index, Convert.ToDouble(value)); - } - else if (value is DateTime) - { - if (storeDateTimeAsTicks) - { - SQLite3.BindInt64(stmt, index, ((DateTime)value).Ticks); - } - else - { - SQLite3.BindText(stmt, index, ((DateTime)value).ToString("yyyy-MM-dd HH:mm:ss"), -1, NegativePointer); - } -#if !NETFX_CORE - } - else if (value.GetType().IsEnum) - { -#else - } else if (value.GetType().GetTypeInfo().IsEnum) { -#endif - SQLite3.BindInt(stmt, index, Convert.ToInt32(value)); - } - else if (value is byte[]) - { - SQLite3.BindBlob(stmt, index, (byte[])value, ((byte[])value).Length, NegativePointer); -#if SQLITE_SUPPORT_GUID - } else if (value is Guid) { - SQLite3.BindText(stmt, index, ((Guid)value).ToString(), 72, NegativePointer); -#endif - } - else - { - throw new NotSupportedException("Cannot store type: " + value.GetType()); - } - } - } - - private class Binding - { - public string Name { get; set; } - - public object Value { get; set; } - - public int Index { get; set; } - } - - private object ReadCol(Sqlite3Statement stmt, int index, SQLite3.ColType type, Type clrType) - { - if (type == SQLite3.ColType.Null) - { - return null; - } - else - { - 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(DateTime)) - { - if (_conn.StoreDateTimeAsTicks) - { - return new DateTime(SQLite3.ColumnInt64(stmt, index)); - } - else - { - var text = SQLite3.ColumnString(stmt, index); - return DateTime.Parse(text); - } -#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); -#if SQLITE_SUPPORT_GUID - } else if (clrType == typeof(Guid)) { - var text = SQLite3.ColumnString(stmt, index); - return new Guid(text); -#endif - } - 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; } - - protected SQLiteConnection Connection { get; set; } - - public string CommandText { get; set; } - - protected Sqlite3Statement Statement { get; set; } - -#if USE_CSHARP_SQLITE - internal static readonly Sqlite3Statement NullStatement = null; -#else - internal static readonly Sqlite3Statement NullStatement = IntPtr.Zero; -#endif - - internal PreparedSqlLiteInsertCommand(SQLiteConnection conn) - { - Connection = conn; - } - - public int ExecuteNonQuery(object[] source) - { - if (Connection.Trace) - { - 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 - { - 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, ITableQuery - { - public SQLiteConnection Connection { get; private set; } - - public TableMapping Table { get; private set; } - - private Expression _where; - private List _orderBys; - private int? _limit; - private int? _offset; - - private BaseTableQuery _joinInner; - private Expression _joinInnerKeySelector; - private BaseTableQuery _joinOuter; - private Expression _joinOuterKeySelector; - private Expression _joinSelector; - - private Expression _selector; - - private TableQuery(SQLiteConnection conn, TableMapping table) - { - Connection = conn; - Table = table; - } - - public TableQuery(SQLiteConnection conn) - { - Connection = conn; - Table = Connection.GetMapping(typeof(T)); - } - - public ITableQuery 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 ITableQuery Where(Expression> predExpr) - { - if (predExpr.NodeType == ExpressionType.Lambda) - { - var lambda = (LambdaExpression)predExpr; - var pred = lambda.Body; - var q = Clone(); - ((SQLiteAndroid.TableQuery)q).AddWhere(pred); - return q; - } - else - { - throw new NotSupportedException("Must be a predicate"); - } - } - - public ITableQuery Take(int n) - { - var q = Clone(); - ((SQLiteAndroid.TableQuery)q)._limit = n; - return q; - } - - public ITableQuery Skip(int n) - { - var q = Clone(); - ((SQLiteAndroid.TableQuery)q)._offset = n; - return q; - } - - public T ElementAt(int index) - { - return Skip(index).Take(1).First(); - } - - private bool _deferred = false; - - public ITableQuery Deferred() - { - var q = Clone(); - ((SQLiteAndroid.TableQuery)q)._deferred = true; - return q; - } - - public ITableQuery OrderBy(Expression> orderExpr) - { - return AddOrderBy(orderExpr, true); - } - - public ITableQuery OrderByDescending(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 (((SQLiteAndroid.TableQuery)q)._orderBys == null) - { - ((SQLiteAndroid.TableQuery)q)._orderBys = new List(); - } - ((SQLiteAndroid.TableQuery)q)._orderBys.Add(new Ordering - { - ColumnName = Table.FindColumnWithPropertyName(mem.Member.Name).Name, - Ascending = asc - }); - return ((SQLiteAndroid.TableQuery)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 ITableQuery Join( - ITableQuery inner, - Expression> outerKeySelector, - Expression> innerKeySelector, - Expression> resultSelector) - { - var q = new TableQuery(Connection, Connection.GetMapping(typeof(TResult))) - { - _joinOuter = this, - _joinOuterKeySelector = outerKeySelector, - _joinInner = (SQLiteAndroid.TableQuery)inner, - _joinInnerKeySelector = innerKeySelector, - _joinSelector = resultSelector, - }; - return q; - } - - public ITableQuery Select(Expression> selector) - { - var q = Clone(); - ((SQLiteAndroid.TableQuery)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 class CompileResult - { - public string CommandText { get; set; } - - public object Value { get; set; } - } - - private CompileResult CompileExpr(Expression expr, List queryArgs) - { - if (expr == null) - { - throw new NotSupportedException("Expression is NULL"); - } - else 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 - { - 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.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.ToString()); -#else - throw new NotSupportedException ("MemberExpr: " + mem.Member.DeclaringType.ToString ()); -#endif - } - - // - // Work special magic for enumerables - // - if (val != null && val is System.Collections.IEnumerable && !(val is string)) - { - 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()); - } - - private static object ConvertTo(object obj, Type t) - { - Type nut = Nullable.GetUnderlyingType(t); - - if (nut != null) - { - if (obj == null) return null; - return Convert.ChangeType(obj, nut); - } - else - { - return Convert.ChangeType(obj, t); - } - } - - /// - /// Compiles a BinaryExpression where one of the parameters is null. - /// - /// The non-null parameter - private string CompileNullBinaryExpression(BinaryExpression expression, CompileResult parameter) - { - if (expression.NodeType == ExpressionType.Equal) - return "(" + parameter.CommandText + " is ?)"; - else if (expression.NodeType == ExpressionType.NotEqual) - return "(" + parameter.CommandText + " is not ?)"; - else - throw new NotSupportedException("Cannot compile Null-BinaryExpression with type " + expression.NodeType.ToString()); - } - - private string GetSqlName(Expression expr) - { - var n = expr.NodeType; - if (n == ExpressionType.GreaterThan) - return ">"; - else if (n == ExpressionType.GreaterThanOrEqual) - { - return ">="; - } - else if (n == ExpressionType.LessThan) - { - return "<"; - } - else if (n == ExpressionType.LessThanOrEqual) - { - return "<="; - } - else if (n == ExpressionType.And) - { - return "&"; - } - else if (n == ExpressionType.AndAlso) - { - return "and"; - } - else if (n == ExpressionType.Or) - { - return "|"; - } - else if (n == ExpressionType.OrElse) - { - return "or"; - } - else if (n == ExpressionType.Equal) - { - return "="; - } - else if (n == ExpressionType.NotEqual) - { - return "!="; - } - else - { - throw new System.NotSupportedException("Cannot get SQL for: " + n.ToString()); - } - } - - public int Count() - { - return GenerateCommand("count(*)").ExecuteScalar(); - } - - 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 ((SQLiteAndroid.TableQuery)query).ToList().First(); - } - - public T FirstOrDefault() - { - var query = this.Take(1); - return ((SQLiteAndroid.TableQuery)query).ToList().FirstOrDefault(); - } - } - - public static 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, - Row = 100, - Done = 101 - } - - public enum ConfigOption : int - { - SingleThread = 1, - MultiThread = 2, - Serialized = 3 - } - -#if !USE_CSHARP_SQLITE - - [DllImport("sqlite3", EntryPoint = "sqlite3_open", CallingConvention = CallingConvention.Cdecl)] - public static extern Result Open([MarshalAs(UnmanagedType.LPStr)] string filename, out IntPtr db); - - [DllImport("sqlite3", 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("sqlite3", EntryPoint = "sqlite3_open_v2", CallingConvention = CallingConvention.Cdecl)] - public static extern Result Open(byte[] filename, out IntPtr db, int flags, IntPtr zvfs); - - [DllImport("sqlite3", EntryPoint = "sqlite3_open16", CallingConvention = CallingConvention.Cdecl)] - public static extern Result Open16([MarshalAs(UnmanagedType.LPWStr)] string filename, out IntPtr db); - - [DllImport("sqlite3", EntryPoint = "sqlite3_close", CallingConvention = CallingConvention.Cdecl)] - public static extern Result Close(IntPtr db); - - [DllImport("sqlite3", EntryPoint = "sqlite3_config", CallingConvention = CallingConvention.Cdecl)] - public static extern Result Config(ConfigOption option); - - [DllImport("sqlite3", EntryPoint = "sqlite3_win32_set_directory", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)] - public static extern int SetDirectory(uint directoryType, string directoryPath); - - [DllImport("sqlite3", EntryPoint = "sqlite3_busy_timeout", CallingConvention = CallingConvention.Cdecl)] - public static extern Result BusyTimeout(IntPtr db, int milliseconds); - - [DllImport("sqlite3", EntryPoint = "sqlite3_changes", CallingConvention = CallingConvention.Cdecl)] - public static extern int Changes(IntPtr db); - - [DllImport("sqlite3", 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); - - public static IntPtr Prepare2(IntPtr db, string query) - { - IntPtr stmt; - var r = Prepare2(db, query, query.Length, out stmt, IntPtr.Zero); - if (r != Result.OK) - { - throw SQLiteException.New(r, GetErrmsg(db)); - } - return stmt; - } - - [DllImport("sqlite3", EntryPoint = "sqlite3_step", CallingConvention = CallingConvention.Cdecl)] - public static extern Result Step(IntPtr stmt); - - [DllImport("sqlite3", EntryPoint = "sqlite3_reset", CallingConvention = CallingConvention.Cdecl)] - public static extern Result Reset(IntPtr stmt); - - [DllImport("sqlite3", EntryPoint = "sqlite3_finalize", CallingConvention = CallingConvention.Cdecl)] - public static extern Result Finalize(IntPtr stmt); - - [DllImport("sqlite3", EntryPoint = "sqlite3_last_insert_rowid", CallingConvention = CallingConvention.Cdecl)] - public static extern long LastInsertRowid(IntPtr db); - - [DllImport("sqlite3", 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("sqlite3", EntryPoint = "sqlite3_bind_parameter_index", CallingConvention = CallingConvention.Cdecl)] - public static extern int BindParameterIndex(IntPtr stmt, [MarshalAs(UnmanagedType.LPStr)] string name); - - [DllImport("sqlite3", EntryPoint = "sqlite3_bind_null", CallingConvention = CallingConvention.Cdecl)] - public static extern int BindNull(IntPtr stmt, int index); - - [DllImport("sqlite3", EntryPoint = "sqlite3_bind_int", CallingConvention = CallingConvention.Cdecl)] - public static extern int BindInt(IntPtr stmt, int index, int val); - - [DllImport("sqlite3", EntryPoint = "sqlite3_bind_int64", CallingConvention = CallingConvention.Cdecl)] - public static extern int BindInt64(IntPtr stmt, int index, long val); - - [DllImport("sqlite3", EntryPoint = "sqlite3_bind_double", CallingConvention = CallingConvention.Cdecl)] - public static extern int BindDouble(IntPtr stmt, int index, double val); - - [DllImport("sqlite3", 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); - - [DllImport("sqlite3", EntryPoint = "sqlite3_bind_blob", CallingConvention = CallingConvention.Cdecl)] - public static extern int BindBlob(IntPtr stmt, int index, byte[] val, int n, IntPtr free); - - [DllImport("sqlite3", EntryPoint = "sqlite3_column_count", CallingConvention = CallingConvention.Cdecl)] - public static extern int ColumnCount(IntPtr stmt); - - [DllImport("sqlite3", EntryPoint = "sqlite3_column_name", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr ColumnName(IntPtr stmt, int index); - - [DllImport("sqlite3", EntryPoint = "sqlite3_column_name16", CallingConvention = CallingConvention.Cdecl)] - private static extern IntPtr ColumnName16Internal(IntPtr stmt, int index); - - public static string ColumnName16(IntPtr stmt, int index) - { - return Marshal.PtrToStringUni(ColumnName16Internal(stmt, index)); - } - - [DllImport("sqlite3", EntryPoint = "sqlite3_column_type", CallingConvention = CallingConvention.Cdecl)] - public static extern ColType ColumnType(IntPtr stmt, int index); - - [DllImport("sqlite3", EntryPoint = "sqlite3_column_int", CallingConvention = CallingConvention.Cdecl)] - public static extern int ColumnInt(IntPtr stmt, int index); - - [DllImport("sqlite3", EntryPoint = "sqlite3_column_int64", CallingConvention = CallingConvention.Cdecl)] - public static extern long ColumnInt64(IntPtr stmt, int index); - - [DllImport("sqlite3", EntryPoint = "sqlite3_column_double", CallingConvention = CallingConvention.Cdecl)] - public static extern double ColumnDouble(IntPtr stmt, int index); - - [DllImport("sqlite3", EntryPoint = "sqlite3_column_text", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr ColumnText(IntPtr stmt, int index); - - [DllImport("sqlite3", EntryPoint = "sqlite3_column_text16", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr ColumnText16(IntPtr stmt, int index); - - [DllImport("sqlite3", EntryPoint = "sqlite3_column_blob", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr ColumnBlob(IntPtr stmt, int index); - - [DllImport("sqlite3", 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)); - } - - public static byte[] ColumnByteArray(IntPtr 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; - } - -#else - - public static Result Open(string filename, out Sqlite3.sqlite3 db) - { - return (Result) Sqlite3.sqlite3_open(filename, out db); - } - - public static Result Open(string filename, out Sqlite3.sqlite3 db, int flags, IntPtr zVfs) - { - return (Result)Sqlite3.sqlite3_open_v2(filename, out db, flags, null); - } - - public static Result Close(Sqlite3.sqlite3 db) - { - return (Result)Sqlite3.sqlite3_close(db); - } - - public static Result BusyTimeout(Sqlite3.sqlite3 db, int milliseconds) - { - return (Result)Sqlite3.sqlite3_busy_timeout(db, milliseconds); - } - - public static int Changes(Sqlite3.sqlite3 db) - { - return Sqlite3.sqlite3_changes(db); - } - - public static Sqlite3.Vdbe Prepare2(Sqlite3.sqlite3 db, string query) - { - Sqlite3.Vdbe stmt = new Sqlite3.Vdbe(); - var r = Sqlite3.sqlite3_prepare_v2(db, query, System.Text.UTF8Encoding.UTF8.GetByteCount(query), ref stmt, 0); - if (r != 0) - { - throw SQLiteException.New((Result)r, GetErrmsg(db)); - } - return stmt; - } - - public static Result Step(Sqlite3.Vdbe stmt) - { - return (Result)Sqlite3.sqlite3_step(stmt); - } - - public static Result Reset(Sqlite3.Vdbe stmt) - { - return (Result)Sqlite3.sqlite3_reset(stmt); - } - - public static Result Finalize(Sqlite3.Vdbe stmt) - { - return (Result)Sqlite3.sqlite3_finalize(stmt); - } - - public static long LastInsertRowid(Sqlite3.sqlite3 db) - { - return Sqlite3.sqlite3_last_insert_rowid(db); - } - - public static string GetErrmsg(Sqlite3.sqlite3 db) - { - return Sqlite3.sqlite3_errmsg(db); - } - - public static int BindParameterIndex(Sqlite3.Vdbe stmt, string name) - { - return Sqlite3.sqlite3_bind_parameter_index(stmt, name); - } - - public static int BindNull(Sqlite3.Vdbe stmt, int index) - { - return Sqlite3.sqlite3_bind_null(stmt, index); - } - - public static int BindInt(Sqlite3.Vdbe stmt, int index, int val) - { - return Sqlite3.sqlite3_bind_int(stmt, index, val); - } - - public static int BindInt64(Sqlite3.Vdbe stmt, int index, long val) - { - return Sqlite3.sqlite3_bind_int64(stmt, index, val); - } - - public static int BindDouble(Sqlite3.Vdbe stmt, int index, double val) - { - return Sqlite3.sqlite3_bind_double(stmt, index, val); - } - - public static int BindText(Sqlite3.Vdbe stmt, int index, string val, int n, IntPtr free) - { - return Sqlite3.sqlite3_bind_text(stmt, index, val, n, null); - } - - public static int BindBlob(Sqlite3.Vdbe stmt, int index, byte[] val, int n, IntPtr free) - { - return Sqlite3.sqlite3_bind_blob(stmt, index, val, n, null); - } - - public static int ColumnCount(Sqlite3.Vdbe stmt) - { - return Sqlite3.sqlite3_column_count(stmt); - } - - public static string ColumnName(Sqlite3.Vdbe stmt, int index) - { - return Sqlite3.sqlite3_column_name(stmt, index); - } - - public static string ColumnName16(Sqlite3.Vdbe stmt, int index) - { - return Sqlite3.sqlite3_column_name(stmt, index); - } - - public static ColType ColumnType(Sqlite3.Vdbe stmt, int index) - { - return (ColType)Sqlite3.sqlite3_column_type(stmt, index); - } - - public static int ColumnInt(Sqlite3.Vdbe stmt, int index) - { - return Sqlite3.sqlite3_column_int(stmt, index); - } - - public static long ColumnInt64(Sqlite3.Vdbe stmt, int index) - { - return Sqlite3.sqlite3_column_int64(stmt, index); - } - - public static double ColumnDouble(Sqlite3.Vdbe stmt, int index) - { - return Sqlite3.sqlite3_column_double(stmt, index); - } - - public static string ColumnText(Sqlite3.Vdbe stmt, int index) - { - return Sqlite3.sqlite3_column_text(stmt, index); - } - - public static string ColumnText16(Sqlite3.Vdbe stmt, int index) - { - return Sqlite3.sqlite3_column_text(stmt, index); - } - - public static byte[] ColumnBlob(Sqlite3.Vdbe stmt, int index) - { - return Sqlite3.sqlite3_column_blob(stmt, index); - } - - public static int ColumnBytes(Sqlite3.Vdbe stmt, int index) - { - return Sqlite3.sqlite3_column_bytes(stmt, index); - } - - public static string ColumnString(Sqlite3.Vdbe stmt, int index) - { - return Sqlite3.sqlite3_column_text(stmt, index); - } - - public static byte[] ColumnByteArray(Sqlite3.Vdbe stmt, int index) - { - return ColumnBlob(stmt, index); - } -#endif - - public enum ColType : int - { - Integer = 1, - Float = 2, - Text = 3, - Blob = 4, - Null = 5 - } - } -} \ No newline at end of file diff --git a/MatterControlLib/MatterControlLib.csproj b/MatterControlLib/MatterControlLib.csproj index c7ee6566c..6427f5cbb 100644 --- a/MatterControlLib/MatterControlLib.csproj +++ b/MatterControlLib/MatterControlLib.csproj @@ -71,7 +71,6 @@ - diff --git a/Program.cs b/Program.cs index 369b69d83..fad6f3caa 100644 --- a/Program.cs +++ b/Program.cs @@ -40,6 +40,7 @@ using MatterHackers.MatterControl.PrintQueue; using MatterHackers.MatterControl.SlicerConfiguration; using MatterHackers.SerialPortCommunication.FrostedSerial; using Microsoft.Extensions.Configuration; +using SQLiteWin32; namespace MatterHackers.MatterControl { @@ -122,7 +123,7 @@ namespace MatterHackers.MatterControl // Make sure we have the right working directory as we assume everything relative to the executable. Directory.SetCurrentDirectory(Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().Location)); - Datastore.Instance.Initialize(); + Datastore.Instance.Initialize(DesktopSqlite.CreateInstance()); // Init platformFeaturesProvider before ShowAsSystemWindow string platformFeaturesProvider = "MatterHackers.MatterControl.WindowsPlatformsFeatures, MatterControl.Winforms"; diff --git a/Tests/MatterControl.Tests/MatterControl/MatterControlUtilities.cs b/Tests/MatterControl.Tests/MatterControl/MatterControlUtilities.cs index dccab0110..8ed181e93 100644 --- a/Tests/MatterControl.Tests/MatterControl/MatterControlUtilities.cs +++ b/Tests/MatterControl.Tests/MatterControl/MatterControlUtilities.cs @@ -52,6 +52,7 @@ using MatterHackers.MatterControl.SlicerConfiguration; using MatterHackers.PrinterEmulator; using Newtonsoft.Json; using NUnit.Framework; +using SQLiteWin32; namespace MatterHackers.MatterControl.Tests.Automation { @@ -407,7 +408,7 @@ namespace MatterHackers.MatterControl.Tests.Automation public static void OverrideAppDataLocation(string matterControlDirectory) { string tempFolderPath = Path.Combine(matterControlDirectory, "Tests", "temp", runName, $"Test{testID++}"); - ApplicationDataStorage.Instance.OverrideAppDataLocation(tempFolderPath); + ApplicationDataStorage.Instance.OverrideAppDataLocation(tempFolderPath, DesktopSqlite.CreateInstance()); } public static void AddItemsToQueue(string queueItemFolderToLoad)