From 85b02de34fe8f5a0732c02484d715edabc60cd88 Mon Sep 17 00:00:00 2001 From: Thomas Levesque Date: Thu, 22 Jan 2015 21:50:02 +0100 Subject: [PATCH 001/103] Add DeleteAllAsync --- src/SQLiteAsync.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/SQLiteAsync.cs b/src/SQLiteAsync.cs index 0abbae11e..6194aa546 100644 --- a/src/SQLiteAsync.cs +++ b/src/SQLiteAsync.cs @@ -159,6 +159,18 @@ public Task DeleteAsync (object item) }); } + public Task DeleteAllAsync() + { + return Task.Factory.StartNew(() => + { + var conn = GetConnection(); + using (conn.Lock()) + { + return conn.DeleteAll(); + } + }); + } + public Task GetAsync(object pk) where T : new() { From 84d300eab4f4a1fb3ff6420390da76bab700659e Mon Sep 17 00:00:00 2001 From: Tim Uy Date: Sat, 17 Dec 2016 13:13:45 -0800 Subject: [PATCH 002/103] fix for bundle_green ByteArray Empty vs null test --- src/SQLite.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/SQLite.cs b/src/SQLite.cs index 7ef11fb6f..f5ccc43a3 100644 --- a/src/SQLite.cs +++ b/src/SQLite.cs @@ -3574,8 +3574,13 @@ public static string ColumnString(Sqlite3Statement stmt, int index) } public static byte[] ColumnByteArray(Sqlite3Statement stmt, int index) - { - return ColumnBlob(stmt, index); + { + int length = ColumnBytes(stmt, index); + if (length > 0) + { + return ColumnBlob(stmt, index); + } + return new byte[0]; } #if !USE_SQLITEPCL_RAW From c6478a7e57dcb17ef15cb3fcb53827596c00d690 Mon Sep 17 00:00:00 2001 From: Dan Ardelean Date: Wed, 22 Feb 2017 15:31:30 +0100 Subject: [PATCH 003/103] -SQLiteAsync added support or ThenBy ThenByDescending --- src/SQLiteAsync.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/SQLiteAsync.cs b/src/SQLiteAsync.cs index 25ff372fc..8bfa8e51e 100644 --- a/src/SQLiteAsync.cs +++ b/src/SQLiteAsync.cs @@ -358,6 +358,17 @@ public AsyncTableQuery OrderByDescending (Expression> orderExpr return new AsyncTableQuery (_innerQuery.OrderByDescending (orderExpr)); } + public AsyncTableQuery ThenBy(Expression> orderExpr) + { + return new AsyncTableQuery(_innerQuery.ThenBy(orderExpr)); + } + + public AsyncTableQuery ThenByDescending(Expression> orderExpr) + { + return new AsyncTableQuery(_innerQuery.ThenByDescending(orderExpr)); + } + + public Task> ToListAsync () { return Task.Factory.StartNew (() => { From ede6e20015b1d5a88da89b133d33d8a90d8650f6 Mon Sep 17 00:00:00 2001 From: CyrusNajmabadi Date: Tue, 28 Mar 2017 20:18:10 -0700 Subject: [PATCH 004/103] Fix broken doc comments. --- src/SQLite.cs | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/src/SQLite.cs b/src/SQLite.cs index 7ef11fb6f..ba2afe6bf 100644 --- a/src/SQLite.cs +++ b/src/SQLite.cs @@ -28,6 +28,7 @@ #endif using System; +using System.Collections; using System.Diagnostics; #if !USE_SQLITEPCL_RAW using System.Runtime.InteropServices; @@ -197,6 +198,9 @@ public SQLiteConnection (string databasePath, bool storeDateTimeAsTicks = true) /// /// Specifies the path to the database file. /// + /// + /// Flags controlling how the connection should be opened. + /// /// /// Specifies whether to store DateTime properties as ticks (true) or strings (false). You /// absolutely do want to store them as Ticks in all new projects. The value of false is @@ -510,7 +514,7 @@ public int CreateIndex(string tableName, string[] columnNames, bool unique = fal /// /// Creates an index for the specified object property. - /// e.g. CreateIndex(c => c.Name); + /// e.g. CreateIndex<Client>(c => c.Name); /// /// Type to reflect to a database table. /// Property to index @@ -608,7 +612,7 @@ protected virtual SQLiteCommand NewCommand () /// /// The fully escaped SQL. /// - /// + /// /// Arguments to substitute for the occurences of '?' in the command text. /// /// @@ -962,7 +966,7 @@ public void BeginTransaction () /// 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 undo transactions since the returned savepoint. /// Call to commit transactions after the savepoint returned here. /// Call to end the transaction, committing all changes. /// @@ -1014,11 +1018,12 @@ public void Rollback () public void RollbackTo (string savepoint) { RollbackTo (savepoint, false); - } - + } + /// /// Rolls back the transaction that was begun by . /// + /// 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 /// true to avoid throwing exceptions, false otherwise void RollbackTo (string savepoint, bool noThrow) { @@ -1090,12 +1095,12 @@ public void Commit () } /// - /// Executes within a (possibly nested) transaction by wrapping it in a SAVEPOINT. If an + /// 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 + /// The to perform within a transaction. can contain any number /// of operations on the connection but should never call or /// . /// @@ -1149,7 +1154,7 @@ public int InsertAll (System.Collections.IEnumerable objects, bool runInTransact /// /// Literal SQL code that gets placed into the command. INSERT {extra} INTO ... /// - /// + /// /// A boolean indicating if the inserts should be wrapped in a transaction. /// /// @@ -1182,7 +1187,7 @@ public int InsertAll (System.Collections.IEnumerable objects, string extra, bool /// /// The type of object to insert. /// - /// + /// /// A boolean indicating if the inserts should be wrapped in a transaction. /// /// @@ -1479,7 +1484,7 @@ public int Update (object obj, Type objType) /// /// An of the objects to insert. /// - /// + /// /// A boolean indicating if the inserts should be wrapped in a transaction /// /// @@ -3047,6 +3052,7 @@ static object ConvertTo (object obj, Type t) /// /// Compiles a BinaryExpression where one of the parameters is null. /// + /// The expression to compile /// The non-null parameter private string CompileNullBinaryExpression(BinaryExpression expression, CompileResult parameter) { From 95f91e34348290cc62123c376c9727761d9877b8 Mon Sep 17 00:00:00 2001 From: Erica Abbott Date: Mon, 3 Apr 2017 15:45:36 +1000 Subject: [PATCH 005/103] Boxing required in EnumCacheInfo to avoid invalid cast exception --- src/SQLite.cs | 6 +++--- tests/EnumCacheTest.cs | 27 +++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/SQLite.cs b/src/SQLite.cs index 7ef11fb6f..b2e14ac33 100644 --- a/src/SQLite.cs +++ b/src/SQLite.cs @@ -2028,9 +2028,9 @@ public EnumCacheInfo(Type type) if (IsEnum) { - // This is a big assumption, but for now support ints only as key, otherwise we still - // have to pay the price for boxing - EnumValues = Enum.GetValues(type).Cast().ToDictionary(x => x, x => x.ToString()); + // Boxing required to avoid invalid cast exception + EnumValues = Enum.GetValues(type).Cast().ToDictionary(Convert.ToInt32, x => Convert.ToInt32(x).ToString()); + #if !USE_NEW_REFLECTION_API StoreAsText = type.GetCustomAttribute(typeof(StoreAsTextAttribute), false) != null; diff --git a/tests/EnumCacheTest.cs b/tests/EnumCacheTest.cs index 74426bc4e..f9011ac15 100644 --- a/tests/EnumCacheTest.cs +++ b/tests/EnumCacheTest.cs @@ -34,9 +34,19 @@ public enum TestEnumStoreAsInt Value2, + Value3 + } + + public enum TestByteEnumStoreAsInt : byte + { + Value1, + + Value2, + Value3 } + public class TestClassThusNotEnum { @@ -70,6 +80,23 @@ public void ShouldReturnTrueForEnumStoreAsInt() var values = Enum.GetValues(typeof(TestEnumStoreAsInt)).Cast().ToList(); + for (int i = 0; i < values.Count; i++) + { + Assert.AreEqual(values[i].ToString(), info.EnumValues[i]); + } + } + + [Test] + public void ShouldReturnTrueForByteEnumStoreAsInt() + { + var info = EnumCache.GetInfo(); + + Assert.IsTrue(info.IsEnum); + Assert.IsFalse(info.StoreAsText); + Assert.IsNotNull(info.EnumValues); + + var values = Enum.GetValues(typeof(TestByteEnumStoreAsInt)).Cast().Select(x=>Convert.ToInt32(x)).ToList(); + for (int i = 0; i < values.Count; i++) { Assert.AreEqual(values[i].ToString(), info.EnumValues[i]); From 93c1b858b540b083d620b49c70ffb0394a8eb5dd Mon Sep 17 00:00:00 2001 From: Jeroen Bernsen Date: Tue, 9 May 2017 16:07:57 +0200 Subject: [PATCH 006/103] Concurrency Test to prove failure on iOS --- tests/ConcurrencyTest.cs | 189 ++++++++++++++++++ tests/SQLite.Tests.csproj | 1 + .../SQLite.Tests.iOS/SQLite.Tests.iOS.csproj | 52 ++++- 3 files changed, 236 insertions(+), 6 deletions(-) create mode 100644 tests/ConcurrencyTest.cs diff --git a/tests/ConcurrencyTest.cs b/tests/ConcurrencyTest.cs new file mode 100644 index 000000000..7bff8ab47 --- /dev/null +++ b/tests/ConcurrencyTest.cs @@ -0,0 +1,189 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using System.IO; + +#if NETFX_CORE +using Microsoft.VisualStudio.TestPlatform.UnitTestFramework; +using SetUp = Microsoft.VisualStudio.TestPlatform.UnitTestFramework.TestInitializeAttribute; +using TearDown = Microsoft.VisualStudio.TestPlatform.UnitTestFramework.TestCleanupAttribute; +using TestFixture = Microsoft.VisualStudio.TestPlatform.UnitTestFramework.TestClassAttribute; +using Test = Microsoft.VisualStudio.TestPlatform.UnitTestFramework.TestMethodAttribute; +#else +using NUnit.Framework; +#endif + +namespace SQLite.Tests +{ + [TestFixture] + public class ConcurrencyTest + { + public class TestObj + { + [AutoIncrement, PrimaryKey] + public int Id { get; set; } + + public override string ToString() + { + return string.Format("[TestObj: Id={0}]", Id); + } + } + + public class DbReader + { + private CancellationToken cancellationToken; + + public DbReader(CancellationToken cancellationToken) + { + this.cancellationToken = cancellationToken; + } + + public Task Run() + { + var t = Task.Run(() => + { + try + { + while (true) + { + // + // NOTE: Change this to readwrite and then it does work ??? + // No more IOERROR + // + + using (var dbConnection = new DbConnection(SQLiteOpenFlags.FullMutex | SQLiteOpenFlags.ReadOnly)) + { + var records = dbConnection.Table().ToList(); + System.Diagnostics.Debug.WriteLine($"{Environment.CurrentManagedThreadId} Read records: {records.Count}"); + } + + // No await so we stay on the same thread + Task.Delay(10).GetAwaiter().GetResult(); + cancellationToken.ThrowIfCancellationRequested(); + } + } + catch (OperationCanceledException) + { + } + }); + + return t; + } + + } + + public class DbWriter + { + private CancellationToken cancellationToken; + + public DbWriter(CancellationToken cancellationToken) + { + this.cancellationToken = cancellationToken; + } + + public Task Run() + { + var t = Task.Run(() => + { + try + { + while (true) + { + using (var dbConnection = new DbConnection(SQLiteOpenFlags.FullMutex | SQLiteOpenFlags.ReadWrite)) + { + System.Diagnostics.Debug.WriteLine($"{Environment.CurrentManagedThreadId} Start insert"); + + for (var i = 0; i < 50; i++) + { + var newRecord = new TestObj() + { + }; + + dbConnection.Insert(newRecord); + } + + System.Diagnostics.Debug.WriteLine($"{Environment.CurrentManagedThreadId} Inserted records"); + } + + // No await so we stay on the same thread + Task.Delay(1).GetAwaiter().GetResult(); + cancellationToken.ThrowIfCancellationRequested(); + } + } + catch (OperationCanceledException) + { + } + }); + + return t; + } + + } + + public class DbConnection : SQLiteConnection + { + private static string DbPath = GetTempFileName(); + + private static string GetTempFileName() + { +#if NETFX_CORE + var name = Guid.NewGuid() + ".sqlite"; + return Path.Combine(Windows.Storage.ApplicationData.Current.LocalFolder.Path, name); +#else + return Path.GetTempFileName(); +#endif + } + + + public DbConnection(SQLiteOpenFlags openflags) : base(DbPath, openflags) + { + this.BusyTimeout = TimeSpan.FromSeconds(5); + } + + public void CreateTables() + { + CreateTable(); + } + } + + [SetUp] + public void Setup() + { + using (var dbConenction = new DbConnection(SQLiteOpenFlags.FullMutex | SQLiteOpenFlags.ReadWrite | SQLiteOpenFlags.Create)) + { + dbConenction.CreateTables(); + } + } + + + [Test] + public void TestLoad() + { + try + { + //var result = SQLitePCL.raw.sqlite3_threadsafe(); + //Assert.AreEqual(2, result); + // Yes it's threadsafe on iOS + + var tokenSource = new CancellationTokenSource(); + var tasks = new List(); + tasks.Add(new DbReader(tokenSource.Token).Run()); + tasks.Add(new DbWriter(tokenSource.Token).Run()); + + // Wait 5sec + tokenSource.CancelAfter(5000); + + Task.WhenAll(tasks).GetAwaiter().GetResult(); + } + catch (Exception ex) + { + Assert.Fail(ex.ToString()); + } + } + + + } +} + diff --git a/tests/SQLite.Tests.csproj b/tests/SQLite.Tests.csproj index 543568fbc..ad727f369 100644 --- a/tests/SQLite.Tests.csproj +++ b/tests/SQLite.Tests.csproj @@ -43,6 +43,7 @@ + diff --git a/tests/SQLite.Tests.iOS/SQLite.Tests.iOS.csproj b/tests/SQLite.Tests.iOS/SQLite.Tests.iOS.csproj index 26748dec2..b38f6174b 100644 --- a/tests/SQLite.Tests.iOS/SQLite.Tests.iOS.csproj +++ b/tests/SQLite.Tests.iOS/SQLite.Tests.iOS.csproj @@ -1,4 +1,4 @@ - + Debug @@ -33,7 +33,8 @@ false - + + true bin\iPhone\Release __UNIFIED__;__MOBILE__;__IOS__;NO_VB @@ -43,14 +44,16 @@ true true true - + + SdkOnly ARMv7, ARM64 HttpClientHandler Default - + + true bin\iPhoneSimulator\Release __UNIFIED__;__MOBILE__;__IOS__;NO_VB @@ -80,7 +83,8 @@ true true true - + + SdkOnly ARMv7, ARM64 HttpClientHandler @@ -103,7 +107,43 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ConcurrencyTest.cs + SQLite.cs From a21e6c6a34a174cbe07c6935e8e9f94460a143fd Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Thu, 25 May 2017 16:34:55 -0700 Subject: [PATCH 007/103] Bump Raw to 1.1.5 --- nuget/SQLite-net/SQLite-net.csproj | 6 +++--- nuget/SQLite-net/packages.config | 4 ++-- sqlite-net-pcl.nuspec | 12 ++++++------ 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/nuget/SQLite-net/SQLite-net.csproj b/nuget/SQLite-net/SQLite-net.csproj index a26f07de0..421217200 100644 --- a/nuget/SQLite-net/SQLite-net.csproj +++ b/nuget/SQLite-net/SQLite-net.csproj @@ -54,13 +54,13 @@ - ..\..\packages\SQLitePCLRaw.core.1.1.2\lib\portable-net45+netcore45+wpa81+wp8+MonoAndroid10+MonoTouch10+Xamarin.iOS10\SQLitePCLRaw.core.dll + ..\..\packages\SQLitePCLRaw.core.1.1.5\lib\portable-net45+netcore45+wpa81+wp8+MonoAndroid10+MonoTouch10+Xamarin.iOS10\SQLitePCLRaw.core.dll - ..\..\packages\SQLitePCLRaw.bundle_green.1.1.2\lib\portable-net45+netcore45+wpa81+wp8+MonoAndroid10+MonoTouch10+Xamarin.iOS10\SQLitePCLRaw.batteries_green.dll + ..\..\packages\SQLitePCLRaw.bundle_green.1.1.5\lib\portable-net45+netcore45+wpa81+wp8+MonoAndroid10+MonoTouch10+Xamarin.iOS10\SQLitePCLRaw.batteries_green.dll - ..\..\packages\SQLitePCLRaw.bundle_green.1.1.2\lib\portable-net45+netcore45+wpa81+wp8+MonoAndroid10+MonoTouch10+Xamarin.iOS10\SQLitePCLRaw.batteries_v2.dll + ..\..\packages\SQLitePCLRaw.bundle_green.1.1.5\lib\portable-net45+netcore45+wpa81+wp8+MonoAndroid10+MonoTouch10+Xamarin.iOS10\SQLitePCLRaw.batteries_v2.dll diff --git a/nuget/SQLite-net/packages.config b/nuget/SQLite-net/packages.config index 5165d0036..6f84d4588 100644 --- a/nuget/SQLite-net/packages.config +++ b/nuget/SQLite-net/packages.config @@ -1,5 +1,5 @@  - - + + \ No newline at end of file diff --git a/sqlite-net-pcl.nuspec b/sqlite-net-pcl.nuspec index 6f1be12e2..347369281 100644 --- a/sqlite-net-pcl.nuspec +++ b/sqlite-net-pcl.nuspec @@ -1,29 +1,29 @@ - 1.3.1 + 1.3.2 Frank A. Krueger Frank A. Krueger https://raw.githubusercontent.com/praeclarum/sqlite-net/master/LICENSE.md https://github.com/praeclarum/sqlite-net https://raw.githubusercontent.com/praeclarum/sqlite-net/master/nuget/Logo-low.png sqlite-net-pcl - SQLite-net PCL - The official portable version of sqlite-net: the easy way to access sqlite from .NET apps. + SQLite-net Official Portable Library + The easy way to access sqlite from .NET apps. false SQLite-net is an open source and light weight library providing easy SQLite database storage for .NET, Mono, and Xamarin applications. This version uses SQLitePCLRaw to provide platform independent versions of SQLite. sqlite pcl database orm mobile - + - + From c95cca09436524ed95ef220df13febd4f11989b7 Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Thu, 25 May 2017 16:49:17 -0700 Subject: [PATCH 008/103] Add a configurable tracer --- src/SQLite.cs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/SQLite.cs b/src/SQLite.cs index 081fa75a8..145e48149 100644 --- a/src/SQLite.cs +++ b/src/SQLite.cs @@ -1,5 +1,5 @@ // -// Copyright (c) 2009-2016 Krueger Systems, Inc. +// Copyright (c) 2009-2017 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 @@ -163,6 +163,7 @@ public partial class SQLiteConnection : IDisposable public bool TimeExecution { get; set; } public bool Trace { get; set; } + public Action Tracer { get; set; } public bool StoreDateTimeAsTicks { get; private set; } @@ -241,6 +242,8 @@ public SQLiteConnection (string databasePath, SQLiteOpenFlags openFlags, bool st StoreDateTimeAsTicks = storeDateTimeAsTicks; BusyTimeout = TimeSpan.FromSeconds (0.1); + + Tracer = line => Debug.WriteLine (line); } #if __IOS__ @@ -1383,7 +1386,10 @@ public int Insert (object obj, string extra, Type objType) // We lock here to protect the prepared statement returned via GetInsertCommand. // A SQLite prepared statement can be bound for only one operation at a time. try { - count = insertCmd.ExecuteNonQuery (vals); + if (Trace) { + Tracer?.Invoke ("Execute Insert: " + insertCmd.CommandText); + } + count = insertCmd.ExecuteNonQuery (vals); } catch (SQLiteException ex) { if (SQLite3.ExtendedErrCode (this.Handle) == SQLite3.ExtendedResult.ConstraintNotNull) { throw NotNullConstraintViolationException.New (ex.Result, ex.Message, map, obj); @@ -2225,7 +2231,7 @@ internal SQLiteCommand (SQLiteConnection conn) public int ExecuteNonQuery () { if (_conn.Trace) { - Debug.WriteLine ("Executing: " + this); + _conn.Tracer?.Invoke ("Executing: " + this); } var r = SQLite3.Result.OK; @@ -2283,7 +2289,7 @@ protected virtual void OnInstanceCreated (object obj) public IEnumerable ExecuteDeferredQuery (TableMapping map) { if (_conn.Trace) { - Debug.WriteLine ("Executing Query: " + this); + _conn.Tracer?.Invoke ("Executing Query: " + this); } var stmt = Prepare (); @@ -2318,7 +2324,7 @@ public IEnumerable ExecuteDeferredQuery (TableMapping map) public T ExecuteScalar () { if (_conn.Trace) { - Debug.WriteLine ("Executing Query: " + this); + _conn.Tracer.Invoke ("Executing Query: " + this); } T val = default(T); @@ -2551,7 +2557,7 @@ internal PreparedSqlLiteInsertCommand (SQLiteConnection conn) public int ExecuteNonQuery (object[] source) { if (Connection.Trace) { - Debug.WriteLine ("Executing: " + CommandText); + Connection.Tracer?.Invoke ("Executing: " + CommandText); } var r = SQLite3.Result.OK; From ed244cd1557e5073a170535941ff7094a5343d96 Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Thu, 25 May 2017 16:55:43 -0700 Subject: [PATCH 009/103] Cleanup AutoGuid code --- src/SQLite.cs | 38 ++++++-------------------------------- 1 file changed, 6 insertions(+), 32 deletions(-) diff --git a/src/SQLite.cs b/src/SQLite.cs index 145e48149..de01c10e7 100644 --- a/src/SQLite.cs +++ b/src/SQLite.cs @@ -1334,42 +1334,14 @@ public int Insert (object obj, string extra, Type objType) if (obj == null || objType == null) { return 0; } - var map = GetMapping (objType); -#if USE_NEW_REFLECTION_API - if (map.PK != null && map.PK.IsAutoGuid) - { - // no GetProperty so search our way up the inheritance chain till we find it - PropertyInfo prop; - while (objType != null) - { - var info = objType.GetTypeInfo(); - prop = info.GetDeclaredProperty(map.PK.PropertyName); - if (prop != null) - { - if (prop.GetValue(obj, null).Equals(Guid.Empty)) - { - prop.SetValue(obj, Guid.NewGuid(), null); - } - break; - } - - objType = info.BaseType; - } - } -#else - if (map.PK != null && map.PK.IsAutoGuid) { - var prop = objType.GetProperty(map.PK.PropertyName); - if (prop != null) { - if (prop.GetValue(obj, null).Equals(Guid.Empty)) { - prop.SetValue(obj, Guid.NewGuid(), null); - } - } + if (map.PK != null && map.PK.IsAutoGuid) { + if (map.PK.GetValue (obj).Equals (Guid.Empty)) { + map.PK.SetValue (obj, Guid.NewGuid ()); + } } -#endif - var replacing = string.Compare (extra, "OR REPLACE", StringComparison.OrdinalIgnoreCase) == 0; @@ -1963,6 +1935,8 @@ public class Column public string Name { get; private set; } + public PropertyInfo PropertyInfo => _prop; + public string PropertyName { get { return _prop.Name; } } public Type ColumnType { get; private set; } From 86cefe8f24437c322f3d86147b08b917de063cd3 Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Thu, 25 May 2017 17:03:53 -0700 Subject: [PATCH 010/103] Fix insert tracing --- src/SQLite.cs | 3 --- tests/InsertTest.cs | 20 ++++++++++++++++++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/SQLite.cs b/src/SQLite.cs index de01c10e7..51072b375 100644 --- a/src/SQLite.cs +++ b/src/SQLite.cs @@ -1358,9 +1358,6 @@ public int Insert (object obj, string extra, Type objType) // We lock here to protect the prepared statement returned via GetInsertCommand. // A SQLite prepared statement can be bound for only one operation at a time. try { - if (Trace) { - Tracer?.Invoke ("Execute Insert: " + insertCmd.CommandText); - } count = insertCmd.ExecuteNonQuery (vals); } catch (SQLiteException ex) { if (SQLite3.ExtendedErrCode (this.Handle) == SQLite3.ExtendedResult.ConstraintNotNull) { diff --git a/tests/InsertTest.cs b/tests/InsertTest.cs index 144f8a44b..57dcd7a23 100644 --- a/tests/InsertTest.cs +++ b/tests/InsertTest.cs @@ -119,6 +119,26 @@ public void InsertALot() Assert.AreEqual(numCount, n, "Num counted must = num objects"); } + [Test] + public void InsertTraces () + { + var oldTracer = _db.Tracer; + var oldTrace = _db.Trace; + + var traces = new List (); + _db.Tracer = traces.Add; + _db.Trace = true; + + var obj1 = new TestObj () { Text = "GLaDOS loves tracing!" }; + var numIn1 = _db.Insert (obj1); + + Assert.AreEqual (1, numIn1); + Assert.AreEqual (1, traces.Count); + + _db.Tracer = oldTracer; + _db.Trace = oldTrace; + } + [Test] public void InsertTwoTimes() { From 74395343e13ae7ca108cf61d79451160d0a89ae3 Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Thu, 25 May 2017 17:39:33 -0700 Subject: [PATCH 011/103] Add non-generic versions of all functions --- src/SQLite.cs | 111 ++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 98 insertions(+), 13 deletions(-) diff --git a/src/SQLite.cs b/src/SQLite.cs index 51072b375..cb9315cb0 100644 --- a/src/SQLite.cs +++ b/src/SQLite.cs @@ -320,7 +320,7 @@ public IEnumerable TableMappings { /// The mapping represents the schema of the columns of the database and contains /// methods to set and get properties of objects. /// - public TableMapping GetMapping(Type type, CreateFlags createFlags = CreateFlags.None) + public TableMapping GetMapping (Type type, CreateFlags createFlags = CreateFlags.None) { if (_mappings == null) { _mappings = new Dictionary (); @@ -336,13 +336,16 @@ public TableMapping GetMapping(Type type, CreateFlags createFlags = CreateFlags. /// /// Retrieves the mapping that is automatically generated for the given type. /// + /// + /// Optional flags allowing implicit PK and indexes based on naming conventions + /// /// /// The mapping represents the schema of the columns of the database and contains /// methods to set and get properties of objects. /// - public TableMapping GetMapping () + public TableMapping GetMapping (CreateFlags createFlags = CreateFlags.None) { - return GetMapping (typeof (T)); + return GetMapping (typeof (T), createFlags); } private struct IndexedColumn @@ -364,13 +367,21 @@ private struct IndexInfo /// public int DropTable() { - var map = GetMapping (typeof (T)); - - var query = string.Format("drop table if exists \"{0}\"", map.TableName); - - return Execute (query); + return DropTable (GetMapping (typeof (T))); } + /// + /// Executes a "drop table" on the database. This is non-recoverable. + /// + /// + /// The TableMapping used to identify the table. + /// + public int DropTable (TableMapping map) + { + 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 @@ -821,8 +832,25 @@ public IEnumerable DeferredQuery(TableMapping map, string query, params /// public T Get (object pk) where T : new() { - var map = GetMapping (typeof(T)); - return Query (map.GetByPrimaryKeySql, pk).First (); + var map = GetMapping (typeof (T)); + return Query (map.GetByPrimaryKeySql, pk).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. Throws a not found exception + /// if the object is not found. + /// + public object Get (object pk, TableMapping map) + { + return Query (map, map.GetByPrimaryKeySql, pk).First (); } /// @@ -868,7 +896,7 @@ public IEnumerable DeferredQuery(TableMapping map, string query, params /// The primary key. /// /// - /// The TableMapping used to identify the object type. + /// The TableMapping used to identify the table. /// /// /// The object with the given primary key or null @@ -914,6 +942,28 @@ public object Find (object pk, TableMapping map) return Query (query, args).FirstOrDefault (); } + /// + /// Attempts to retrieve the first object that matches the query from the table + /// associated with the specified type. + /// + /// + /// The TableMapping used to identify the table. + /// + /// + /// The fully escaped SQL. + /// + /// + /// Arguments to substitute for the occurences of '?' in the query. + /// + /// + /// The object that matches the given predicate or null + /// if the object is not found. + /// + public object FindWithQuery (TableMapping map, string query, params object[] args) + { + return Query (map, query, args).FirstOrDefault (); + } + /// /// Whether has been called and the database is waiting for a . /// @@ -1520,7 +1570,23 @@ public int Delete (object objectToDelete) /// public int Delete (object primaryKey) { - var map = GetMapping (typeof (T)); + return Delete (primaryKey, GetMapping (typeof (T))); + } + + /// + /// Deletes the object with the specified primary key. + /// + /// + /// The primary key of the object to delete. + /// + /// + /// The TableMapping used to identify the table. + /// + /// + /// The number of objects deleted. + /// + public int Delete (object primaryKey, TableMapping map) + { var pk = map.PK; if (pk == null) { throw new NotSupportedException ("Cannot delete " + map.TableName + ": it has no PK"); @@ -1546,7 +1612,26 @@ public int Delete (object primaryKey) public int DeleteAll () { var map = GetMapping (typeof (T)); - var query = string.Format("delete from \"{0}\"", map.TableName); + return DeleteAll (map); + } + + /// + /// Deletes all the objects from the specified table. + /// WARNING WARNING: Let me repeat. It deletes ALL the objects from the + /// specified table. Do you really want to do that? + /// + /// + /// The TableMapping used to identify the table. + /// + /// + /// The number of objects deleted. + /// + /// + /// The type of objects to delete. + /// + public int DeleteAll (TableMapping map) + { + var query = string.Format ("delete from \"{0}\"", map.TableName); var count = Execute (query); if (count > 0) OnTableChanged (map, NotifyTableChangedAction.Delete); From 7fa6077c518a87712b55075a875b1ec0a0c9e95f Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Thu, 25 May 2017 17:42:26 -0700 Subject: [PATCH 012/103] Fix doc comments --- nuget/SQLite-net/SQLite-net.csproj | 2 ++ src/SQLite.cs | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/nuget/SQLite-net/SQLite-net.csproj b/nuget/SQLite-net/SQLite-net.csproj index 421217200..ef12dbf49 100644 --- a/nuget/SQLite-net/SQLite-net.csproj +++ b/nuget/SQLite-net/SQLite-net.csproj @@ -26,6 +26,7 @@ TRACE;DEBUG;USE_SQLITEPCL_RAW USE_NEW_REFLECTION_API NO_CONCURRENT prompt 4 + true bin\Debug\SQLite-net.xml @@ -36,6 +37,7 @@ TRACE;USE_SQLITEPCL_RAW USE_NEW_REFLECTION_API NO_CONCURRENT prompt 4 + true bin\Release\SQLite-net.xml diff --git a/src/SQLite.cs b/src/SQLite.cs index cb9315cb0..96a696d71 100644 --- a/src/SQLite.cs +++ b/src/SQLite.cs @@ -844,6 +844,9 @@ public IEnumerable DeferredQuery(TableMapping map, string query, params /// /// The primary key. /// + /// + /// The TableMapping used to identify the table. + /// /// /// The object with the given primary key. Throws a not found exception /// if the object is not found. @@ -1626,9 +1629,6 @@ public int DeleteAll () /// /// The number of objects deleted. /// - /// - /// The type of objects to delete. - /// public int DeleteAll (TableMapping map) { var query = string.Format ("delete from \"{0}\"", map.TableName); From f053c67daf4aeaeb7b60d650be5a224cb3998d63 Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Fri, 26 May 2017 14:38:32 -0700 Subject: [PATCH 013/103] Remove the old reflection API --- src/SQLite.cs | 284 ++++++++++++++++++++--------------------- tests/EnumCacheTest.cs | 6 +- tests/EnumTest.cs | 50 +++++++- 3 files changed, 188 insertions(+), 152 deletions(-) diff --git a/src/SQLite.cs b/src/SQLite.cs index 96a696d71..aef6e0dcf 100644 --- a/src/SQLite.cs +++ b/src/SQLite.cs @@ -23,10 +23,6 @@ #define USE_CSHARP_SQLITE #endif -#if NETFX_CORE || NETCORE -#define USE_NEW_REFLECTION_API -#endif - using System; using System.Collections; using System.Diagnostics; @@ -1868,29 +1864,29 @@ public TableMapping(Type type, CreateFlags createFlags = CreateFlags.None) { MappedType = type; -#if USE_NEW_REFLECTION_API - var tableAttr = (TableAttribute)System.Reflection.CustomAttributeExtensions - .GetCustomAttribute(type.GetTypeInfo(), typeof(TableAttribute), true); -#else - var tableAttr = (TableAttribute)type.GetCustomAttributes (typeof (TableAttribute), true).FirstOrDefault (); -#endif + var typeInfo = type.GetTypeInfo (); + var tableAttr = + typeInfo.CustomAttributes + .Where(x => x.AttributeType == typeof(TableAttribute)) + .Select(x => (TableAttribute)Orm.InflateAttribute(x)) + .FirstOrDefault(); - TableName = tableAttr != null ? tableAttr.Name : MappedType.Name; + TableName = (tableAttr != null && !string.IsNullOrEmpty(tableAttr.Name)) ? tableAttr.Name : MappedType.Name; + + var props = new List (); + var baseType = type; + while (baseType != typeof(object)) { + var ti = baseType.GetTypeInfo(); + props.AddRange( + from p in ti.DeclaredProperties + 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); + baseType = ti.BaseType; + } -#if !USE_NEW_REFLECTION_API - 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 !USE_NEW_REFLECTION_API - var ignore = p.GetCustomAttributes (typeof(IgnoreAttribute), true).Length > 0; -#else - var ignore = p.GetCustomAttributes (typeof(IgnoreAttribute), true).Count() > 0; -#endif + var ignore = p.CustomAttributes.Any(x => x.AttributeType == typeof(IgnoreAttribute)); if (p.CanWrite && !ignore) { cols.Add (new Column (p, createFlags)); } @@ -2040,10 +2036,12 @@ public class Column public Column(PropertyInfo prop, CreateFlags createFlags = CreateFlags.None) { - var colAttr = (ColumnAttribute)prop.GetCustomAttributes(typeof(ColumnAttribute), true).FirstOrDefault(); + var colAttr = prop.CustomAttributes.FirstOrDefault(x => x.AttributeType == typeof(ColumnAttribute)); _prop = prop; - Name = colAttr == null ? prop.Name : colAttr.Name; + Name = (colAttr != null && colAttr.ConstructorArguments.Count > 0) ? + colAttr.ConstructorArguments[0].Value?.ToString() : + prop.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); @@ -2068,7 +2066,7 @@ public Column(PropertyInfo prop, CreateFlags createFlags = CreateFlags.None) IsNullable = !(IsPK || Orm.IsMarkedNotNull(prop)); MaxStringLength = Orm.MaxStringLength(prop); - StoreAsText = prop.PropertyType.GetTypeInfo().GetCustomAttribute(typeof(StoreAsTextAttribute), false) != null; + StoreAsText = prop.PropertyType.GetTypeInfo().CustomAttributes.Any(x => x.AttributeType == typeof(StoreAsTextAttribute)); } public void SetValue (object obj, object val) @@ -2081,69 +2079,66 @@ public object GetValue (object obj) return _prop.GetValue (obj, null); } } - } - - internal class EnumCacheInfo - { - public EnumCacheInfo(Type type) - { -#if !USE_NEW_REFLECTION_API - IsEnum = type.IsEnum; -#else - IsEnum = type.GetTypeInfo().IsEnum; -#endif - - if (IsEnum) - { - // Boxing required to avoid invalid cast exception - EnumValues = Enum.GetValues(type).Cast().ToDictionary(Convert.ToInt32, x => Convert.ToInt32(x).ToString()); - - -#if !USE_NEW_REFLECTION_API - StoreAsText = type.GetCustomAttribute(typeof(StoreAsTextAttribute), false) != null; -#else - StoreAsText = type.GetTypeInfo().GetCustomAttribute(typeof(StoreAsTextAttribute), false) != null; -#endif - } - } - - public bool IsEnum { get; private set; } - - public bool StoreAsText { get; private set; } - - public Dictionary EnumValues { get; private set; } - } + } - internal static class EnumCache - { - private static readonly Dictionary Cache = new Dictionary(); + class EnumCacheInfo + { + public EnumCacheInfo (Type type) + { + var typeInfo = type.GetTypeInfo(); - public static EnumCacheInfo GetInfo() - { - return GetInfo(typeof(T)); - } + IsEnum = typeInfo.IsEnum; - public static EnumCacheInfo GetInfo(Type type) - { - lock (Cache) - { - EnumCacheInfo info = null; - if (!Cache.TryGetValue(type, out info)) - { - info = new EnumCacheInfo(type); - Cache[type] = info; - } + if (IsEnum) + { + StoreAsText = typeInfo.CustomAttributes.Any(x => x.AttributeType == typeof(StoreAsTextAttribute)); - return info; - } - } - } + if (StoreAsText) { + EnumValues = Enum.GetValues(type).Cast().ToDictionary(Convert.ToInt32, x => x.ToString()); + } + else { + EnumValues = Enum.GetValues(type).Cast().ToDictionary(Convert.ToInt32, x => Convert.ToInt32(x).ToString()); + } + } + } + + public bool IsEnum { get; private set; } + + public bool StoreAsText { get; private set; } + + public Dictionary EnumValues { get; private set; } + } + + static class EnumCache + { + static readonly Dictionary Cache = new Dictionary(); + + public static EnumCacheInfo GetInfo () + { + return GetInfo(typeof(T)); + } + + public static EnumCacheInfo GetInfo (Type type) + { + lock (Cache) + { + EnumCacheInfo info = null; + if (!Cache.TryGetValue(type, out info)) + { + info = new EnumCacheInfo(type); + Cache[type] = info; + } + + return info; + } + } + } public static class Orm { - public const int DefaultMaxStringLength = 140; - public const string ImplicitPkName = "Id"; - public const string ImplicitIndexSuffix = "Id"; + public const int DefaultMaxStringLength = 140; + public const string ImplicitPkName = "Id"; + public const string ImplicitIndexSuffix = "Id"; public static string SqlDecl (TableMapping.Column p, bool storeDateTimeAsTicks) { @@ -2186,11 +2181,7 @@ public static string SqlType (TableMapping.Column p, bool storeDateTimeAsTicks) return storeDateTimeAsTicks ? "bigint" : "datetime"; } else if (clrType == typeof(DateTimeOffset)) { return "bigint"; -#if !USE_NEW_REFLECTION_API - } else if (clrType.IsEnum) { -#else } else if (clrType.GetTypeInfo().IsEnum) { -#endif if (p.StoreAsText) return "varchar"; else @@ -2206,67 +2197,83 @@ public static string SqlType (TableMapping.Column p, bool storeDateTimeAsTicks) public static bool IsPK (MemberInfo p) { - var attrs = p.GetCustomAttributes (typeof(PrimaryKeyAttribute), true); -#if !USE_NEW_REFLECTION_API - return attrs.Length > 0; -#else - return attrs.Count() > 0; -#endif + return p.CustomAttributes.Any(x => x.AttributeType == typeof(PrimaryKeyAttribute)); } public static string Collation (MemberInfo p) { - var attrs = p.GetCustomAttributes (typeof(CollationAttribute), true); -#if !USE_NEW_REFLECTION_API - if (attrs.Length > 0) { - return ((CollationAttribute)attrs [0]).Value; -#else - if (attrs.Count() > 0) { - return ((CollationAttribute)attrs.First()).Value; -#endif - } else { - return string.Empty; - } + return + (p.CustomAttributes + .Where(x => typeof(CollationAttribute) == x.AttributeType) + .Select(x => + { + var args = x.ConstructorArguments; + return args.Count > 0 ? ((args[0].Value as string) ?? "") : ""; + }) + .FirstOrDefault()) ?? ""; } public static bool IsAutoInc (MemberInfo p) { - var attrs = p.GetCustomAttributes (typeof(AutoIncrementAttribute), true); -#if !USE_NEW_REFLECTION_API - return attrs.Length > 0; -#else - return attrs.Count() > 0; -#endif + return p.CustomAttributes.Any(x => x.AttributeType == typeof(AutoIncrementAttribute)); + } + + public static FieldInfo GetField (TypeInfo t, string name) + { + var f = t.GetDeclaredField(name); + if (f != null) + return f; + return GetField(t.BaseType.GetTypeInfo(), name); + } + + public static PropertyInfo GetProperty (TypeInfo t, string name) + { + var f = t.GetDeclaredProperty(name); + if (f != null) + return f; + return GetProperty(t.BaseType.GetTypeInfo(), name); + } + + public static object InflateAttribute (CustomAttributeData x) + { + var atype = x.AttributeType; + var typeInfo = atype.GetTypeInfo(); + var args = x.ConstructorArguments.Select(a => a.Value).ToArray(); + var r = Activator.CreateInstance(x.AttributeType, args); + foreach (var arg in x.NamedArguments) { + if (arg.IsField) { + GetField(typeInfo, arg.MemberName).SetValue(r, arg.TypedValue.Value); + } + else { + GetProperty(typeInfo, arg.MemberName).SetValue(r, arg.TypedValue.Value); + } + } + return r; } public static IEnumerable GetIndices(MemberInfo p) { - var attrs = p.GetCustomAttributes(typeof(IndexedAttribute), true); - return attrs.Cast(); + var indexedInfo = typeof(IndexedAttribute).GetTypeInfo(); + return + p.CustomAttributes + .Where(x => indexedInfo.IsAssignableFrom(x.AttributeType.GetTypeInfo())) + .Select(x => (IndexedAttribute)InflateAttribute(x)); } public static int? MaxStringLength(PropertyInfo p) { - var attrs = p.GetCustomAttributes (typeof(MaxLengthAttribute), true); -#if !USE_NEW_REFLECTION_API - if (attrs.Length > 0) - return ((MaxLengthAttribute)attrs [0]).Value; -#else - if (attrs.Count() > 0) - return ((MaxLengthAttribute)attrs.First()).Value; -#endif - + var attr = p.CustomAttributes.FirstOrDefault(x => x.AttributeType == typeof(MaxLengthAttribute)); + if (attr != null) + { + var attrv = (MaxLengthAttribute)InflateAttribute(attr); + return attrv.Value; + } return null; } public static bool IsMarkedNotNull(MemberInfo p) { - var attrs = p.GetCustomAttributes (typeof (NotNullAttribute), true); -#if !USE_NEW_REFLECTION_API - return attrs.Length > 0; -#else - return attrs.Count() > 0; -#endif + return p.CustomAttributes.Any(x => x.AttributeType == typeof(NotNullAttribute)); } } @@ -2527,6 +2534,7 @@ object ReadCol (Sqlite3Statement stmt, int index, SQLite3.ColType type, Type clr if (type == SQLite3.ColType.Null) { return null; } else { + var clrTypeInfo = clrType.GetTypeInfo(); if (clrType == typeof(String)) { return SQLite3.ColumnString (stmt, index); } else if (clrType == typeof(Int32)) { @@ -2553,11 +2561,7 @@ object ReadCol (Sqlite3Statement stmt, int index, SQLite3.ColType type, Type clr } } else if (clrType == typeof(DateTimeOffset)) { return new DateTimeOffset(SQLite3.ColumnInt64 (stmt, index),TimeSpan.Zero); -#if !USE_NEW_REFLECTION_API - } else if (clrType.IsEnum) { -#else - } else if (clrType.GetTypeInfo().IsEnum) { -#endif + } else if (clrTypeInfo.IsEnum) { if (type == SQLite3.ColType.Text) { var value = SQLite3.ColumnString(stmt, index); @@ -3042,30 +3046,14 @@ private CompileResult CompileExpr (Expression expr, List queryArgs) // object val = null; -#if !USE_NEW_REFLECTION_API - if (mem.Member.MemberType == MemberTypes.Property) { -#else if (mem.Member is PropertyInfo) { -#endif var m = (PropertyInfo)mem.Member; val = m.GetValue (obj, null); -#if !USE_NEW_REFLECTION_API - } 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 !USE_NEW_REFLECTION_API - throw new NotSupportedException ("MemberExpr: " + mem.Member.MemberType); -#else - throw new NotSupportedException ("MemberExpr: " + mem.Member.DeclaringType); -#endif + throw new NotSupportedException ("MemberExpr: " + mem.Member.GetType()); } // diff --git a/tests/EnumCacheTest.cs b/tests/EnumCacheTest.cs index f9011ac15..d7c1e0789 100644 --- a/tests/EnumCacheTest.cs +++ b/tests/EnumCacheTest.cs @@ -61,7 +61,7 @@ public void ShouldReturnTrueForEnumStoreAsText() Assert.IsTrue(info.StoreAsText); Assert.IsNotNull(info.EnumValues); - var values = Enum.GetValues(typeof(TestEnumStoreAsText)).Cast().ToList(); + var values = Enum.GetValues(typeof(TestEnumStoreAsText)).Cast().ToList(); for (int i = 0; i < values.Count; i++) { @@ -78,11 +78,11 @@ public void ShouldReturnTrueForEnumStoreAsInt() Assert.IsFalse(info.StoreAsText); Assert.IsNotNull(info.EnumValues); - var values = Enum.GetValues(typeof(TestEnumStoreAsInt)).Cast().ToList(); + var values = Enum.GetValues(typeof(TestEnumStoreAsInt)).Cast().ToList(); for (int i = 0; i < values.Count; i++) { - Assert.AreEqual(values[i].ToString(), info.EnumValues[i]); + Assert.AreEqual(((int)values[i]).ToString(), info.EnumValues[i]); } } diff --git a/tests/EnumTest.cs b/tests/EnumTest.cs index 264a82a65..591a74d74 100644 --- a/tests/EnumTest.cs +++ b/tests/EnumTest.cs @@ -27,7 +27,17 @@ public enum TestEnum Value3 } - public class TestObj + [StoreAsText] + public enum StringTestEnum + { + Value1, + + Value2, + + Value3 + } + + public class TestObj { [PrimaryKey] public int Id { get; set; } @@ -40,12 +50,26 @@ public override string ToString() } + public class StringTestObj + { + [PrimaryKey] + public int Id { get; set; } + public StringTestEnum Value { get; set; } + + public override string ToString () + { + return string.Format("[StringTestObj: Id={0}, Value={1}]", Id, Value); + } + + } + public class TestDb : SQLiteConnection { public TestDb(String path) : base(path) { CreateTable(); + CreateTable(); } } @@ -72,5 +96,29 @@ public void ShouldPersistAndReadEnum() db.Close(); } + + [Test] + public void ShouldPersistAndReadStringEnum () + { + var db = new TestDb(TestPath.GetTempFileName()); + + var obj1 = new StringTestObj() { Id = 1, Value = StringTestEnum.Value2 }; + var obj2 = new StringTestObj() { Id = 2, Value = StringTestEnum.Value3 }; + + var numIn1 = db.Insert(obj1); + var numIn2 = db.Insert(obj2); + Assert.AreEqual(1, numIn1); + Assert.AreEqual(1, numIn2); + + var result = db.Query("select * from StringTestObj").ToList(); + Assert.AreEqual(2, result.Count); + Assert.AreEqual(obj1.Value, result[0].Value); + Assert.AreEqual(obj2.Value, result[1].Value); + + Assert.AreEqual(obj1.Id, result[0].Id); + Assert.AreEqual(obj2.Id, result[1].Id); + + db.Close(); + } } } \ No newline at end of file From 9776f4f59fe222a4e9beb286e736acd6f466d504 Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Fri, 26 May 2017 14:51:44 -0700 Subject: [PATCH 014/103] Use IReflectableType inplace of GetType() when possible --- src/SQLite.cs | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/src/SQLite.cs b/src/SQLite.cs index aef6e0dcf..f039f9040 100644 --- a/src/SQLite.cs +++ b/src/SQLite.cs @@ -1278,7 +1278,7 @@ public int Insert (object obj) if (obj == null) { return 0; } - return Insert (obj, "", obj.GetType ()); + return Insert (obj, "", Orm.GetType (obj)); } /// @@ -1299,7 +1299,7 @@ public int InsertOrReplace (object obj) if (obj == null) { return 0; } - return Insert (obj, "OR REPLACE", obj.GetType ()); + return Insert (obj, "OR REPLACE", Orm.GetType (obj)); } /// @@ -1359,7 +1359,7 @@ public int Insert (object obj, string extra) if (obj == null) { return 0; } - return Insert (obj, extra, obj.GetType ()); + return Insert (obj, extra, Orm.GetType (obj)); } /// @@ -1442,7 +1442,7 @@ public int Update (object obj) if (obj == null) { return 0; } - return Update (obj, obj.GetType ()); + return Update (obj, Orm.GetType (obj)); } /// @@ -1543,7 +1543,7 @@ public int UpdateAll (System.Collections.IEnumerable objects, bool runInTransact /// public int Delete (object objectToDelete) { - var map = GetMapping (objectToDelete.GetType ()); + var map = GetMapping (Orm.GetType (objectToDelete)); var pk = map.PK; if (pk == null) { throw new NotSupportedException ("Cannot delete " + map.TableName + ": it has no PK"); @@ -2140,6 +2140,16 @@ public static class Orm public const string ImplicitPkName = "Id"; public const string ImplicitIndexSuffix = "Id"; + public static Type GetType (object obj) + { + if (obj == null) + return typeof(object); + var rt = obj as IReflectableType; + if (rt != null) + return rt.GetTypeInfo().AsType(); + return obj.GetType(); + } + public static string SqlDecl (TableMapping.Column p, bool storeDateTimeAsTicks) { string decl = "\"" + p.Name + "\" " + SqlType (p, storeDateTimeAsTicks) + " "; @@ -2499,7 +2509,11 @@ internal static void BindParameter (Sqlite3Statement stmt, int index, object val } } else if (value is DateTimeOffset) { SQLite3.BindInt64 (stmt, index, ((DateTimeOffset)value).UtcTicks); - } else { + } else if (value is byte[]) { + SQLite3.BindBlob(stmt, index, (byte[])value, ((byte[])value).Length, NegativePointer); + } else if (value is Guid) { + SQLite3.BindText(stmt, index, ((Guid)value).ToString(), 72, NegativePointer); + } else { // Now we could possibly get an enum, retrieve cached info var valueType = value.GetType(); var enumInfo = EnumCache.GetInfo(valueType); @@ -2509,12 +2523,8 @@ internal static void BindParameter (Sqlite3Statement stmt, int index, object val SQLite3.BindText(stmt, index, enumInfo.EnumValues[enumIntValue], -1, NegativePointer); else SQLite3.BindInt(stmt, index, enumIntValue); - } else if (value is byte[]){ - SQLite3.BindBlob(stmt, index, (byte[]) value, ((byte[]) value).Length, NegativePointer); - } else if (value is Guid) { - SQLite3.BindText(stmt, index, ((Guid)value).ToString(), 72, NegativePointer); } else { - throw new NotSupportedException("Cannot store type: " + value.GetType()); + throw new NotSupportedException("Cannot store type: " + Orm.GetType(value)); } } } From c91cab9d26a55f4960eccd37ef873dfac26a02c9 Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Fri, 26 May 2017 14:54:51 -0700 Subject: [PATCH 015/103] Prepare for 1.3.3 release --- sqlite-net-pcl.nuspec | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sqlite-net-pcl.nuspec b/sqlite-net-pcl.nuspec index 347369281..09bc71367 100644 --- a/sqlite-net-pcl.nuspec +++ b/sqlite-net-pcl.nuspec @@ -1,7 +1,7 @@ - 1.3.2 + 1.3.3 Frank A. Krueger Frank A. Krueger https://raw.githubusercontent.com/praeclarum/sqlite-net/master/LICENSE.md @@ -9,13 +9,13 @@ https://raw.githubusercontent.com/praeclarum/sqlite-net/master/nuget/Logo-low.png sqlite-net-pcl SQLite-net Official Portable Library - The easy way to access sqlite from .NET apps. + SQLite-net Official Portable Library is the easy way to access sqlite from .NET apps. false SQLite-net is an open source and light weight library providing easy SQLite database storage for .NET, Mono, and Xamarin applications. This version uses SQLitePCLRaw to provide platform independent versions of SQLite. sqlite pcl database orm mobile From c4b4031633892123986c9a9c5263231128ef585b Mon Sep 17 00:00:00 2001 From: Gameleon12 Date: Fri, 9 Jun 2017 13:13:02 +0200 Subject: [PATCH 016/103] Use IsDefined to check for IgnoreAttribute --- src/SQLite.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SQLite.cs b/src/SQLite.cs index f039f9040..35d0fc60d 100644 --- a/src/SQLite.cs +++ b/src/SQLite.cs @@ -1886,7 +1886,7 @@ from p in ti.DeclaredProperties var cols = new List (); foreach (var p in props) { - var ignore = p.CustomAttributes.Any(x => x.AttributeType == typeof(IgnoreAttribute)); + var ignore = p.IsDefined(typeof(IgnoreAttribute),true); if (p.CanWrite && !ignore) { cols.Add (new Column (p, createFlags)); } From bcb8a60f4f5ccbaf58e419cfa0297ba8ed64c47e Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Wed, 14 Jun 2017 00:30:07 -0700 Subject: [PATCH 017/103] Move nuget logo into root --- Logo-low.png | Bin 0 -> 65869 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 Logo-low.png diff --git a/Logo-low.png b/Logo-low.png new file mode 100644 index 0000000000000000000000000000000000000000..f3ae12480f9166d6bb015007363a233715713d20 GIT binary patch literal 65869 zcmV)cK&ZcoP)^S;@D-tM^*z`# z3+^_OEDGO(Zn0$AwF9OwOn8`?nR({-EW>|TW`<#A499UeF^i>^iuOra<@o5FFeiQO zdtSXZ!~ObIweR~pl}aI%PKkIt&f~EdXN)(lGmt#bYqUqcuF)Et3#XKF)*967%4wr* zy;gUXO4$~RMN=x3>3ZD)a3^WB_3Y?C2o(Y#41BwJ`#POY^WMH*+11%4lBuMmu!~3} zB7EPIB+&@CF_u}|uwhDRG}P#E6d9u(NJ2>F=p8}gLO9MjnK{t}goe^eYpqPRS~cZT z6Amh0DA>t~Nloh(Aeyy(-Es(o%li;84E@o~+w0KKknHcD?{{@|%S0mX#S;nXdD2r# zbEWFg+VE!3$*xhIYp#cfhttEuLmi!+of!f|2YpdNR%!lZ*tCJ1ni0t~fYo ztpf^oo&uel$=-FH=PYB)(In9X1IbDuETc6+#~Q5(0wqIJgPO@^_1K|9 zYHV!Wv>?!4kM4^f1|a}KvDx)_L2o!3jq>Hom-}<)&G9=syZmG_=`#k=2)kB_TW49V zR%gZ-zI?^Y@Zd7<%4bTVA?sslROL(r8zAca#F`S!=k~4Xb7~TJsxjx?ynnvgIqgy1SOp z-Fz_xYcU0^0~e6A*0~@EMrdl-Iz2VLeIx0$*IwV_oHJ4g>-oO%2nr5x)k;NWbGh2S zz5D8W_Uusrjt+zn1|a}0rr@`kx109wV(q%M(fRY|M>CmB)EF)3uuWKJS-q}ABpUJV zz2}~lL&HOBySlp8(w+SZ3VQ|@p|sZdN~QAc%*@P}J4beY`G-ILabvYoRRjVf2nG>? zf;Pq!3&q;xL>W28otg#lpOee}b{P$2*=ywJC*XoY_LhV?NDeT>HXm~)o@e-AI{ zuE}3{_0?PE&YiQC-fs<^+ONbVECRyDd_MoxfddD>{Q1v*{uaT_c%ElC=bAC*2mrP5 z@$uT|&e7R0XjK=&AOygL7<}Gt8t3J;*IgU!@9%Gh-WVgbQoOE|Shebk-fOPC=7zq$ zzH4cuzlL)j!(}LIZM9G+e0b>4p%34E=iPTd`sm|v#+YsZK?q?90KwRyvD&t6+v+5J z^vNIuK?s1etmEwKMXN4cxX@p{W_6sxozz+%3H|ETtNL!d>BhV0`W-aZuK?N=iXJU) zI(YEle_#9WYybY>{SOXF0)aL}gR>8U5CCED1�C&9~f~q`iGoDdjomgfZHG`Q?}I?jPvC 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 Date: Wed, 21 Jun 2017 21:38:37 +0200 Subject: [PATCH 018/103] Fix for Nullable Enum --- src/SQLite.cs | 23 ++++++++---- tests/EnumNullableTest.cs | 76 +++++++++++++++++++++++++++++++++++++++ tests/SQLite.Tests.csproj | 1 + 3 files changed, 94 insertions(+), 6 deletions(-) create mode 100644 tests/EnumNullableTest.cs diff --git a/src/SQLite.cs b/src/SQLite.cs index 7ef11fb6f..3fb9c1326 100644 --- a/src/SQLite.cs +++ b/src/SQLite.cs @@ -2004,12 +2004,23 @@ public Column(PropertyInfo prop, CreateFlags createFlags = CreateFlags.None) StoreAsText = prop.PropertyType.GetTypeInfo().GetCustomAttribute(typeof(StoreAsTextAttribute), false) != null; } - public void SetValue (object obj, object val) - { - _prop.SetValue (obj, val, null); - } - - public object GetValue (object obj) + public void SetValue(object obj, object val) + { +#if !USE_NEW_REFLECTION_API + if (ColumnType.IsEnum) +#else + if (ColumnType.GetTypeInfo().IsEnum) +#endif + { + _prop.SetValue(obj, Enum.ToObject(ColumnType, val)); + } + else + { + _prop.SetValue(obj, val, null); + } + } + + public object GetValue (object obj) { return _prop.GetValue (obj, null); } diff --git a/tests/EnumNullableTest.cs b/tests/EnumNullableTest.cs new file mode 100644 index 000000000..bc8961209 --- /dev/null +++ b/tests/EnumNullableTest.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; + +#if NETFX_CORE +using Microsoft.VisualStudio.TestPlatform.UnitTestFramework; +using SetUp = Microsoft.VisualStudio.TestPlatform.UnitTestFramework.TestInitializeAttribute; +using TestFixture = Microsoft.VisualStudio.TestPlatform.UnitTestFramework.TestClassAttribute; +using Test = Microsoft.VisualStudio.TestPlatform.UnitTestFramework.TestMethodAttribute; +#else +using NUnit.Framework; +#endif + +namespace SQLite.Tests +{ + [TestFixture] + public class EnumNullableTests + { + public enum TestEnum + { + Value1, + + Value2, + + Value3 + } + + public class TestObj + { + [PrimaryKey] + public int Id { get; set; } + public TestEnum? Value { get; set; } + + public override string ToString() + { + return string.Format("[TestObj: Id={0}, Value={1}]", Id, Value); + } + + } + + public class TestDb : SQLiteConnection + { + public TestDb(String path) + : base(path) + { + CreateTable(); + } + } + + [Test] + public void ShouldPersistAndReadEnum() + { + var db = new TestDb(TestPath.GetTempFileName()); + + var obj1 = new TestObj() { Id = 1, Value = TestEnum.Value2 }; + var obj2 = new TestObj() { Id = 2, Value = TestEnum.Value3 }; + + var numIn1 = db.Insert(obj1); + var numIn2 = db.Insert(obj2); + Assert.AreEqual(1, numIn1); + Assert.AreEqual(1, numIn2); + + var result = db.Query("select * from TestObj").ToList(); + Assert.AreEqual(2, result.Count); + Assert.AreEqual(obj1.Value, result[0].Value); + Assert.AreEqual(obj2.Value, result[1].Value); + + Assert.AreEqual(obj1.Id, result[0].Id); + Assert.AreEqual(obj2.Id, result[1].Id); + + db.Close(); + } + } +} \ No newline at end of file diff --git a/tests/SQLite.Tests.csproj b/tests/SQLite.Tests.csproj index 543568fbc..cdc81d9c0 100644 --- a/tests/SQLite.Tests.csproj +++ b/tests/SQLite.Tests.csproj @@ -44,6 +44,7 @@ + From 665007aa5d70f018d14d44e120e05dae5b339e41 Mon Sep 17 00:00:00 2001 From: Andrew Tavera Date: Tue, 11 Jul 2017 03:13:17 -0400 Subject: [PATCH 019/103] StartsWith, EndsWith, and Contains generated SQL behaves like the System.String methods used to construct the query: Contains is case sensitive, using instr() instead of like(). ToUpper() or ToLower can be used to compare without case sensitivity. StartsWith and EndsWith is case sensitive unless a case insensitive StringComparison argument is passed using substr() = ? or the original like() depending upon case sensitivity --- src/SQLite.cs | 48 +++++++++++++++++++++++++++++++++++----- tests/StringQueryTest.cs | 40 ++++++++++++++++++++++++--------- 2 files changed, 72 insertions(+), 16 deletions(-) diff --git a/src/SQLite.cs b/src/SQLite.cs index 3ef06c5f9..4735f5322 100644 --- a/src/SQLite.cs +++ b/src/SQLite.cs @@ -3001,17 +3001,53 @@ private CompileResult CompileExpr (Expression expr, List queryArgs) } 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 + " || '%'))"; + sqlCall = "( instr(" + obj.CommandText + "," + args [0].CommandText + ") >0 )"; } 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 == "StartsWith" && args.Length >= 1) + { + var startsWithCmpOp = StringComparison.CurrentCulture; + if (args.Length == 2) + { + startsWithCmpOp = (StringComparison) args[1].Value; + } + switch (startsWithCmpOp) + { + case StringComparison.Ordinal: + case StringComparison.CurrentCulture: + case StringComparison.InvariantCulture: + sqlCall = "( substr(" + obj.CommandText + ", 1, " + args[0].Value.ToString().Length + ") = " + args[0].CommandText + ")"; + break; + case StringComparison.OrdinalIgnoreCase: + case StringComparison.CurrentCultureIgnoreCase: + case StringComparison.InvariantCultureIgnoreCase: + sqlCall = "(" + obj.CommandText + " like (" + args[0].CommandText + " || '%'))"; + break; + } + + } + else if (call.Method.Name == "EndsWith" && args.Length >= 1) { + var endsWithCmpOp = StringComparison.CurrentCulture; + if (args.Length == 2) + { + endsWithCmpOp = (StringComparison)args[1].Value; + } + switch (endsWithCmpOp) + { + case StringComparison.Ordinal: + case StringComparison.CurrentCulture: + case StringComparison.InvariantCulture: + sqlCall = "( substr(" + obj.CommandText + ", length(" + obj.CommandText + ") - "+args[0].Value.ToString().Length+ "+1, " + args[0].Value.ToString().Length + ") = " + args[0].CommandText + ")"; + break; + case StringComparison.OrdinalIgnoreCase: + case StringComparison.CurrentCultureIgnoreCase: + case StringComparison.InvariantCultureIgnoreCase: + sqlCall = "(" + obj.CommandText + " like ('%' || " + args[0].CommandText + "))"; + break; + } } else if (call.Method.Name == "Equals" && args.Length == 1) { sqlCall = "(" + obj.CommandText + " = (" + args[0].CommandText + "))"; diff --git a/tests/StringQueryTest.cs b/tests/StringQueryTest.cs index 35976666f..ae474b91c 100644 --- a/tests/StringQueryTest.cs +++ b/tests/StringQueryTest.cs @@ -62,8 +62,16 @@ public void StartsWith () { var fs = db.Table ().Where (x => x.Name.StartsWith ("F")).ToList (); Assert.AreEqual (2, fs.Count); - - var bs = db.Table ().Where (x => x.Name.StartsWith ("B")).ToList (); + + var lfs = db.Table().Where(x => x.Name.StartsWith("f")).ToList(); + Assert.AreEqual(0, lfs.Count); + + + var lfs2 = db.Table().Where(x => x.Name.StartsWith("f",StringComparison.OrdinalIgnoreCase)).ToList(); + Assert.AreEqual(2, lfs2.Count); + + + var bs = db.Table ().Where (x => x.Name.StartsWith ("B")).ToList (); Assert.AreEqual (1, bs.Count); } @@ -71,20 +79,32 @@ public void StartsWith () public void EndsWith () { var fs = db.Table ().Where (x => x.Name.EndsWith ("ar")).ToList (); - Assert.AreEqual (2, fs.Count); - - var bs = db.Table ().Where (x => x.Name.EndsWith ("o")).ToList (); + Assert.AreEqual (2, fs.Count); + + var lfs = db.Table().Where(x => x.Name.EndsWith("Ar")).ToList(); + Assert.AreEqual(0, lfs.Count); + + var bs = db.Table ().Where (x => x.Name.EndsWith ("o")).ToList (); Assert.AreEqual (1, bs.Count); } [Test] public void Contains () { - var fs = db.Table ().Where (x => x.Name.Contains ("o")).ToList (); - Assert.AreEqual (2, fs.Count); - - var bs = db.Table ().Where (x => x.Name.Contains ("a")).ToList (); + var fs = db.Table().Where(x => x.Name.Contains("o")).ToList(); + Assert.AreEqual(2, fs.Count); + + var lfs = db.Table ().Where (x => x.Name.Contains ("O")).ToList (); + Assert.AreEqual (0, lfs.Count); + + var lfsu = db.Table().Where(x => x.Name.ToUpper().Contains("O")).ToList(); + Assert.AreEqual(2, lfsu.Count); + + var bs = db.Table ().Where (x => x.Name.Contains ("a")).ToList (); Assert.AreEqual (2, bs.Count); - } + + var zs = db.Table().Where(x => x.Name.Contains("z")).ToList(); + Assert.AreEqual(0, zs.Count); + } } } From feca677531a182d9cec06ed9eb229b50226ac07f Mon Sep 17 00:00:00 2001 From: Andrew Tavera Date: Wed, 12 Jul 2017 16:22:36 -0400 Subject: [PATCH 020/103] Remove StringComparison.InvariantCulture (not supported by PCL) --- src/SQLite.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/SQLite.cs b/src/SQLite.cs index 4735f5322..a90df2785 100644 --- a/src/SQLite.cs +++ b/src/SQLite.cs @@ -3018,12 +3018,10 @@ private CompileResult CompileExpr (Expression expr, List queryArgs) { case StringComparison.Ordinal: case StringComparison.CurrentCulture: - case StringComparison.InvariantCulture: sqlCall = "( substr(" + obj.CommandText + ", 1, " + args[0].Value.ToString().Length + ") = " + args[0].CommandText + ")"; break; case StringComparison.OrdinalIgnoreCase: case StringComparison.CurrentCultureIgnoreCase: - case StringComparison.InvariantCultureIgnoreCase: sqlCall = "(" + obj.CommandText + " like (" + args[0].CommandText + " || '%'))"; break; } @@ -3039,12 +3037,10 @@ private CompileResult CompileExpr (Expression expr, List queryArgs) { case StringComparison.Ordinal: case StringComparison.CurrentCulture: - case StringComparison.InvariantCulture: sqlCall = "( substr(" + obj.CommandText + ", length(" + obj.CommandText + ") - "+args[0].Value.ToString().Length+ "+1, " + args[0].Value.ToString().Length + ") = " + args[0].CommandText + ")"; break; case StringComparison.OrdinalIgnoreCase: case StringComparison.CurrentCultureIgnoreCase: - case StringComparison.InvariantCultureIgnoreCase: sqlCall = "(" + obj.CommandText + " like ('%' || " + args[0].CommandText + "))"; break; } From 6a535019ffdcc0be085f9f1ef98b093cbb7b4f0f Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Thu, 27 Jul 2017 13:37:50 -0700 Subject: [PATCH 021/103] Fix concurrency test on iOS --- tests/ConcurrencyTest.cs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/tests/ConcurrencyTest.cs b/tests/ConcurrencyTest.cs index 7bff8ab47..358a11330 100644 --- a/tests/ConcurrencyTest.cs +++ b/tests/ConcurrencyTest.cs @@ -48,12 +48,16 @@ public Task Run() { while (true) { - // - // NOTE: Change this to readwrite and then it does work ??? - // No more IOERROR - // - - using (var dbConnection = new DbConnection(SQLiteOpenFlags.FullMutex | SQLiteOpenFlags.ReadOnly)) + // + // NOTE: Change this to readwrite and then it does work ??? + // No more IOERROR + // + + var flags = SQLiteOpenFlags.FullMutex | SQLiteOpenFlags.ReadOnly; +#if __IOS__ + flags = SQLiteOpenFlags.FullMutex | SQLiteOpenFlags.ReadWrite; +#endif + using (var dbConnection = new DbConnection(flags)) { var records = dbConnection.Table().ToList(); System.Diagnostics.Debug.WriteLine($"{Environment.CurrentManagedThreadId} Read records: {records.Count}"); From f2ad311e4b00cfc0a90dc1046a8f3b26b15d02df Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Thu, 27 Jul 2017 13:39:18 -0700 Subject: [PATCH 022/103] Fix setting nullable enums --- src/SQLite.cs | 8 ++--- tests/NullableTest.cs | 68 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 6 deletions(-) diff --git a/src/SQLite.cs b/src/SQLite.cs index 3ef06c5f9..c7a83bcba 100644 --- a/src/SQLite.cs +++ b/src/SQLite.cs @@ -1661,7 +1661,7 @@ protected virtual void Dispose(bool disposing) } } - var r = SQLite3.Close(Handle); + var r = SQLite3.Close2(Handle); if (r != SQLite3.Result.OK) { string msg = SQLite3.GetErrmsg(Handle); @@ -2071,11 +2071,7 @@ public Column(PropertyInfo prop, CreateFlags createFlags = CreateFlags.None) public void SetValue(object obj, object val) { -#if !USE_NEW_REFLECTION_API - if (ColumnType.IsEnum) -#else - if (ColumnType.GetTypeInfo().IsEnum) -#endif + if (val != null && ColumnType.GetTypeInfo().IsEnum) { _prop.SetValue(obj, Enum.ToObject(ColumnType, val)); } diff --git a/tests/NullableTest.cs b/tests/NullableTest.cs index 29e1e2575..98067d6d0 100644 --- a/tests/NullableTest.cs +++ b/tests/NullableTest.cs @@ -243,5 +243,73 @@ public void StringWhereNotNull() Assert.AreEqual(withEmpty, results[0]); Assert.AreEqual(withData, results[1]); } + + public enum TestIntEnum + { + One = 1, + Two = 2, + } + + [StoreAsText] + public enum TestTextEnum + { + Alpha, + Beta, + } + + public class NullableEnumClass + { + [PrimaryKey, AutoIncrement] + public int ID { get; set; } + + public TestIntEnum? NullableIntEnum { get; set; } + public TestTextEnum? NullableTextEnum { get; set; } + + public override bool Equals (object obj) + { + var other = (NullableEnumClass)obj; + return this.ID == other.ID && this.NullableIntEnum == other.NullableIntEnum && this.NullableTextEnum == other.NullableTextEnum; + } + + public override int GetHashCode () + { + return ID.GetHashCode () + NullableIntEnum.GetHashCode () + NullableTextEnum.GetHashCode (); + } + + public override string ToString () + { + return string.Format ("[NullableEnumClass: ID={0}, NullableIntEnum={1}, NullableTextEnum={2}]", ID, NullableIntEnum, NullableTextEnum); + } + } + + [Test] + [Description ("Create a table with a nullable enum column then insert and select against it")] + public void NullableEnum () + { + SQLiteConnection db = new SQLiteConnection (TestPath.GetTempFileName ()); + db.CreateTable (); + + var withNull = new NullableEnumClass { NullableIntEnum = null, NullableTextEnum = null }; + var with1 = new NullableEnumClass { NullableIntEnum = TestIntEnum.One, NullableTextEnum = null }; + var with2 = new NullableEnumClass { NullableIntEnum = TestIntEnum.Two, NullableTextEnum = null }; + var withNullA = new NullableEnumClass { NullableIntEnum = null, NullableTextEnum = TestTextEnum.Alpha }; + var with1B = new NullableEnumClass { NullableIntEnum = TestIntEnum.One, NullableTextEnum = TestTextEnum.Beta }; + + db.Insert (withNull); + db.Insert (with1); + db.Insert (with2); + db.Insert (withNullA); + db.Insert (with1B); + + var results = db.Table ().OrderBy (x => x.ID).ToArray (); + + Assert.AreEqual (5, results.Length); + + Assert.AreEqual (withNull, results[0]); + Assert.AreEqual (with1, results[1]); + Assert.AreEqual (with2, results[2]); + Assert.AreEqual (withNullA, results[3]); + Assert.AreEqual (with1B, results[4]); + } } } From ff0931629f01769421feb4d2dae468d1d2cb5673 Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Thu, 27 Jul 2017 13:39:40 -0700 Subject: [PATCH 023/103] Switch iOS sim builds to 64-bit --- tests/SQLite.Tests.iOS/SQLite.Tests.iOS.csproj | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/tests/SQLite.Tests.iOS/SQLite.Tests.iOS.csproj b/tests/SQLite.Tests.iOS/SQLite.Tests.iOS.csproj index b38f6174b..477278489 100644 --- a/tests/SQLite.Tests.iOS/SQLite.Tests.iOS.csproj +++ b/tests/SQLite.Tests.iOS/SQLite.Tests.iOS.csproj @@ -24,13 +24,12 @@ true true true - true - true None - i386 + x86_64 HttpClientHandler Default false + true @@ -41,8 +40,6 @@ prompt 4 iPhone Developer - true - true true @@ -60,10 +57,8 @@ prompt 4 iPhone Developer - true - true None - i386 + x86_64 HttpClientHandler Default @@ -80,8 +75,6 @@ true true true - true - true true From 9be917105f443fe61f1d54e9c50ba2a6807d7185 Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Thu, 27 Jul 2017 14:06:56 -0700 Subject: [PATCH 024/103] Package PDB files --- SQLite.sln | 1 + sqlite-net-pcl.nuspec | 8 ++++---- sqlite-net.nuspec | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/SQLite.sln b/SQLite.sln index 7cc6f835a..f2dca1882 100644 --- a/SQLite.sln +++ b/SQLite.sln @@ -7,6 +7,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ProjectSection(SolutionItems) = preProject sqlite-net-pcl.nuspec = sqlite-net-pcl.nuspec sqlite-net.nuspec = sqlite-net.nuspec + Makefile = Makefile EndProjectSection EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SQLite-net", "nuget\SQLite-net\SQLite-net.csproj", "{7F946494-8EE0-432B-8A87-98961143D5C1}" diff --git a/sqlite-net-pcl.nuspec b/sqlite-net-pcl.nuspec index 09bc71367..9cb703a91 100644 --- a/sqlite-net-pcl.nuspec +++ b/sqlite-net-pcl.nuspec @@ -1,7 +1,7 @@ - 1.3.3 + 1.3.4 Frank A. Krueger Frank A. Krueger https://raw.githubusercontent.com/praeclarum/sqlite-net/master/LICENSE.md @@ -15,7 +15,7 @@ sqlite pcl database orm mobile @@ -30,10 +30,10 @@ - + - + diff --git a/sqlite-net.nuspec b/sqlite-net.nuspec index e798f4835..72d0f84ea 100644 --- a/sqlite-net.nuspec +++ b/sqlite-net.nuspec @@ -1,7 +1,7 @@ - 1.2.1 + 1.3.4 Frank Krueger Frank Krueger,Tim Heuer https://raw.githubusercontent.com/praeclarum/sqlite-net/master/LICENSE.md From 9c496bbc1b3483df04c8724e6400a5e2f99987e8 Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Thu, 27 Jul 2017 15:19:09 -0700 Subject: [PATCH 025/103] Create new .NET Standard library --- Makefile | 3 ++- SQLite.sln | 14 +++++++++++ nuget/SQLite-net-std/SQLite-net-std.csproj | 28 ++++++++++++++++++++++ nuget/SQLite-net/SQLite-net.csproj | 6 ++--- sqlite-net-pcl.nuspec | 10 ++++---- sqlite-net.nuspec | 2 +- src/AssemblyInfo.cs | 6 ++--- 7 files changed, 56 insertions(+), 13 deletions(-) create mode 100644 nuget/SQLite-net-std/SQLite-net-std.csproj diff --git a/Makefile b/Makefile index 08ac68739..1fadfa0c2 100644 --- a/Makefile +++ b/Makefile @@ -16,6 +16,7 @@ srcnuget: src/SQLite.cs src/SQLiteAsync.cs sqlite-net.nuspec pclnuget: src/SQLite.cs src/SQLiteAsync.cs nuget restore SQLite.sln - '/Applications/Xamarin Studio.app/Contents/MacOS/mdtool' build '-c:Release|iPhone' SQLite.sln -p:SQLite-net + msbuild "/p:Configuration=Release" nuget/SQLite-net-std/SQLite-net-std.csproj + msbuild "/p:Configuration=Release" nuget/SQLite-net-std/SQLite-net-std.csproj nuget pack sqlite-net-pcl.nuspec -o .\ diff --git a/SQLite.sln b/SQLite.sln index f2dca1882..4325e17a7 100644 --- a/SQLite.sln +++ b/SQLite.sln @@ -16,6 +16,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{FECC0E44 EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SQLite.Tests.iOS", "tests\SQLite.Tests.iOS\SQLite.Tests.iOS.csproj", "{81850129-71C3-40C7-A48B-AA5D2C2E365E}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SQLite-net-std", "nuget\SQLite-net-std\SQLite-net-std.csproj", "{081D08D6-10F1-431B-88FE-469FD9FE898C}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -62,6 +64,18 @@ Global {81850129-71C3-40C7-A48B-AA5D2C2E365E}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator {81850129-71C3-40C7-A48B-AA5D2C2E365E}.Debug|iPhone.ActiveCfg = Debug|iPhone {81850129-71C3-40C7-A48B-AA5D2C2E365E}.Debug|iPhone.Build.0 = Debug|iPhone + {081D08D6-10F1-431B-88FE-469FD9FE898C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {081D08D6-10F1-431B-88FE-469FD9FE898C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {081D08D6-10F1-431B-88FE-469FD9FE898C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {081D08D6-10F1-431B-88FE-469FD9FE898C}.Release|Any CPU.Build.0 = Release|Any CPU + {081D08D6-10F1-431B-88FE-469FD9FE898C}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {081D08D6-10F1-431B-88FE-469FD9FE898C}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {081D08D6-10F1-431B-88FE-469FD9FE898C}.Release|iPhone.ActiveCfg = Release|Any CPU + {081D08D6-10F1-431B-88FE-469FD9FE898C}.Release|iPhone.Build.0 = Release|Any CPU + {081D08D6-10F1-431B-88FE-469FD9FE898C}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {081D08D6-10F1-431B-88FE-469FD9FE898C}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {081D08D6-10F1-431B-88FE-469FD9FE898C}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {081D08D6-10F1-431B-88FE-469FD9FE898C}.Debug|iPhone.Build.0 = Debug|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {6947A8F1-99BE-4DD1-AD4D-D89425CE67A2} = {FECC0E44-E626-49CB-BD8B-0CFBD93FBEFF} diff --git a/nuget/SQLite-net-std/SQLite-net-std.csproj b/nuget/SQLite-net-std/SQLite-net-std.csproj new file mode 100644 index 000000000..5a2acd486 --- /dev/null +++ b/nuget/SQLite-net-std/SQLite-net-std.csproj @@ -0,0 +1,28 @@ + + + + netstandard1.1 + SQLite-net + + + + true + portable + TRACE;USE_SQLITEPCL_RAW;RELEASE;NETSTANDARD1_1 + bin\Release\netstandard1.1\SQLite-net.xml + + + TRACE;USE_SQLITEPCL_RAW;DEBUG;NETSTANDARD1_1 + + + + + + + SQLite.cs + + + SQLiteAsync.cs + + + diff --git a/nuget/SQLite-net/SQLite-net.csproj b/nuget/SQLite-net/SQLite-net.csproj index ef12dbf49..9a40e93fb 100644 --- a/nuget/SQLite-net/SQLite-net.csproj +++ b/nuget/SQLite-net/SQLite-net.csproj @@ -23,7 +23,7 @@ full false bin\Debug\ - TRACE;DEBUG;USE_SQLITEPCL_RAW USE_NEW_REFLECTION_API NO_CONCURRENT + TRACE;DEBUG;USE_SQLITEPCL_RAW;NO_CONCURRENT prompt 4 true @@ -31,10 +31,10 @@ true - + portable true bin\Release\ - TRACE;USE_SQLITEPCL_RAW USE_NEW_REFLECTION_API NO_CONCURRENT + TRACE;USE_SQLITEPCL_RAW;NO_CONCURRENT prompt 4 true diff --git a/sqlite-net-pcl.nuspec b/sqlite-net-pcl.nuspec index 9cb703a91..4e6500ca3 100644 --- a/sqlite-net-pcl.nuspec +++ b/sqlite-net-pcl.nuspec @@ -1,7 +1,7 @@ - 1.3.4 + 1.4.0 Frank A. Krueger Frank A. Krueger https://raw.githubusercontent.com/praeclarum/sqlite-net/master/LICENSE.md @@ -15,7 +15,7 @@ sqlite pcl database orm mobile @@ -32,8 +32,8 @@ - - - + + + diff --git a/sqlite-net.nuspec b/sqlite-net.nuspec index 72d0f84ea..59375955a 100644 --- a/sqlite-net.nuspec +++ b/sqlite-net.nuspec @@ -1,7 +1,7 @@ - 1.3.4 + 1.4.0 Frank Krueger Frank Krueger,Tim Heuer https://raw.githubusercontent.com/praeclarum/sqlite-net/master/LICENSE.md diff --git a/src/AssemblyInfo.cs b/src/AssemblyInfo.cs index dcc0d06aa..c06011b48 100644 --- a/src/AssemblyInfo.cs +++ b/src/AssemblyInfo.cs @@ -11,7 +11,7 @@ [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Krueger Systems, Inc.")] [assembly: AssemblyProduct("SQLite-net")] -[assembly: AssemblyCopyright("Copyright © 2009-2016 Krueger Systems, Inc.")] +[assembly: AssemblyCopyright("Copyright © 2009-2017 Krueger Systems, Inc.")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: NeutralResourcesLanguage("en")] @@ -26,5 +26,5 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.1.0.0")] -[assembly: AssemblyFileVersion("1.1.0.0")] +[assembly: AssemblyVersion("1.4.0.0")] +[assembly: AssemblyFileVersion("1.4.0.0")] From 6145656594189230c0aa53bb3e102a4231ac845b Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Thu, 27 Jul 2017 15:35:39 -0700 Subject: [PATCH 026/103] Fix building the old PCL version --- Makefile | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/Makefile b/Makefile index 1fadfa0c2..c7d36353a 100644 --- a/Makefile +++ b/Makefile @@ -1,22 +1,24 @@ - +SRC=src/SQLite.cs src/SQLiteAsync.cs all: test nuget test: tests/bin/Debug/SQLite.Tests.dll nunit-console tests/bin/Debug/SQLite.Tests.dll -tests/bin/Debug/SQLite.Tests.dll: tests/SQLite.Tests.csproj src/SQLite.cs src/SQLiteAsync.cs - xbuild tests/SQLite.Tests.csproj +tests/bin/Debug/SQLite.Tests.dll: tests/SQLite.Tests.csproj $(SRC) + msbuild tests/SQLite.Tests.csproj nuget: srcnuget pclnuget -srcnuget: src/SQLite.cs src/SQLiteAsync.cs sqlite-net.nuspec +packages: nuget/SQLite-net/packages.config + nuget restore SQLite.sln + +srcnuget: sqlite-net.nuspec $(SRC) nuget pack sqlite-net.nuspec -pclnuget: src/SQLite.cs src/SQLiteAsync.cs - nuget restore SQLite.sln - msbuild "/p:Configuration=Release" nuget/SQLite-net-std/SQLite-net-std.csproj +pclnuget: sqlite-net-pcl.nuspec packages $(SRC) + msbuild "/p:Configuration=Release" nuget/SQLite-net/SQLite-net.csproj msbuild "/p:Configuration=Release" nuget/SQLite-net-std/SQLite-net-std.csproj nuget pack sqlite-net-pcl.nuspec -o .\ From 27fa298eedea925a8597a067414aeae7e4023ba1 Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Thu, 27 Jul 2017 16:02:33 -0700 Subject: [PATCH 027/103] Use explicit dependencies in nuspec --- sqlite-net-pcl.nuspec | 47 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 38 insertions(+), 9 deletions(-) diff --git a/sqlite-net-pcl.nuspec b/sqlite-net-pcl.nuspec index 4e6500ca3..c00bc9df8 100644 --- a/sqlite-net-pcl.nuspec +++ b/sqlite-net-pcl.nuspec @@ -19,21 +19,50 @@ ]]> - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - + + From 7e4309e9df3155b7f72b6c5f07e44d6697fa3957 Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Fri, 28 Jul 2017 12:27:45 -0700 Subject: [PATCH 028/103] Add test for inherited ignores --- tests/IgnoreTest.cs | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/tests/IgnoreTest.cs b/tests/IgnoreTest.cs index ec0614af3..55829604b 100644 --- a/tests/IgnoreTest.cs +++ b/tests/IgnoreTest.cs @@ -91,5 +91,39 @@ public void GetDoesntHaveIgnores () Assert.AreEqual ("Hello", oo.Text); Assert.AreEqual (null, oo.IgnoredText); } + + public class BaseClass + { + [Ignore] + public string ToIgnore { + get; + set; + } + } + + public class TableClass : BaseClass + { + public string Name { get; set; } + } + + [Test] + public void BaseIgnores () + { + var db = new TestDb (); + db.CreateTable (); + + var o = new TableClass { + ToIgnore = "Hello", + Name = "World", + }; + + db.Insert (o); + + var oo = db.Table ().First (); + + Assert.AreEqual (null, oo.ToIgnore); + Assert.AreEqual ("World", oo.Name); + } + } } From 0772010792a5352e71fee22128c5b9c3ad098617 Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Tue, 1 Aug 2017 09:05:10 -0700 Subject: [PATCH 029/103] Added more SQLCipher details --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5c2e769f8..12514bda2 100644 --- a/README.md +++ b/README.md @@ -184,8 +184,9 @@ Debug.WriteLine(string.Format("Found '{0}' stock items.", result)); You can add support for encrypted databases using SQLCipher by including an additional package [SQLitePCLRaw.bundle_sqlcipher](https://www.nuget.org/packages/SQLitePCLRaw.bundle_sqlcipher/). -I'll let [Eric Sink explain](https://github.com/ericsink/SQLitePCL.raw/wiki/How-to-use-SQLCipher-with-SQLite-net): +I'll let [Eric Sink explain how to use SQLCipher with SQLite-net](https://github.com/ericsink/SQLitePCL.raw/wiki/How-to-use-SQLCipher-with-SQLite-net): +> The reference to bundle_sqlcipher must be placed in your app project. > What happens here is that SQLite-net references bundle_green, but at build time, bundle_sqlcipher gets substituted in its place. ## Thank you! From 05e71dc1b9a64d7c539947a967be6c501d711310 Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Tue, 1 Aug 2017 10:59:17 -0700 Subject: [PATCH 030/103] Fix ignoring redefined columns Fixes #586 --- src/SQLite.cs | 18 ++++++++++++++---- tests/IgnoreTest.cs | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 4 deletions(-) diff --git a/src/SQLite.cs b/src/SQLite.cs index 6dfb4b9e7..a7611cc22 100644 --- a/src/SQLite.cs +++ b/src/SQLite.cs @@ -1875,19 +1875,29 @@ public TableMapping(Type type, CreateFlags createFlags = CreateFlags.None) var props = new List (); var baseType = type; + var propNames = new HashSet (); while (baseType != typeof(object)) { var ti = baseType.GetTypeInfo(); - props.AddRange( + var newProps = ( from p in ti.DeclaredProperties - 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); + where + !propNames.Contains(p.Name) && + p.CanRead && p.CanWrite && + (p.GetMethod != null) && (p.SetMethod != null) && + (p.GetMethod.IsPublic && p.SetMethod.IsPublic) && + (!p.GetMethod.IsStatic) && (!p.SetMethod.IsStatic) + select p).ToList(); + foreach (var p in newProps) { + propNames.Add(p.Name); + } + props.AddRange (newProps); baseType = ti.BaseType; } var cols = new List (); foreach (var p in props) { var ignore = p.IsDefined(typeof(IgnoreAttribute),true); - if (p.CanWrite && !ignore) { + if (!ignore) { cols.Add (new Column (p, createFlags)); } } diff --git a/tests/IgnoreTest.cs b/tests/IgnoreTest.cs index 55829604b..1d26eea4a 100644 --- a/tests/IgnoreTest.cs +++ b/tests/IgnoreTest.cs @@ -125,5 +125,38 @@ public void BaseIgnores () Assert.AreEqual ("World", oo.Name); } + public class RedefinedBaseClass + { + public string Name { get; set; } + public List Values { get; set; } + } + + public class RedefinedClass : RedefinedBaseClass + { + [Ignore] + public new List Values { get; set; } + public string Value { get; set; } + } + + [Test] + public void RedefinedIgnores () + { + var db = new TestDb (); + db.CreateTable (); + + var o = new RedefinedClass { + Name = "Foo", + Value = "Bar", + Values = new List { "hello", "world" }, + }; + + db.Insert (o); + + var oo = db.Table ().First (); + + Assert.AreEqual ("Foo", oo.Name); + Assert.AreEqual ("Bar", oo.Value); + Assert.AreEqual (null, oo.Values); + } } } From c3edf02b2f2c43cebc304cd29aea3a4ff42afb3f Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Tue, 1 Aug 2017 11:16:53 -0700 Subject: [PATCH 031/103] Add a test to make sure the migrator adds columns Trying to repro #590 --- tests/MigrationTest.cs | 78 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/tests/MigrationTest.cs b/tests/MigrationTest.cs index 5d0056efa..fe5a62733 100644 --- a/tests/MigrationTest.cs +++ b/tests/MigrationTest.cs @@ -22,6 +22,7 @@ public class MigrationTest class LowerId { public int Id { get; set; } } + [Table ("Test")] class UpperId { public int ID { get; set; } @@ -39,5 +40,82 @@ public void UpperAndLowerColumnNames () Assert.AreEqual ("Id", cols[0].Name); } } + + [Table ("TestAdd")] + class TestAddBefore + { + [PrimaryKey, AutoIncrement] + public int Id { get; set; } + + public string Name { get; set; } + } + + [Table ("TestAdd")] + class TestAddAfter + { + [PrimaryKey, AutoIncrement] + public int Id { get; set; } + + public string Name { get; set; } + + public int IntValue { get; set; } + public string StringValue { get; set; } + } + + [Test] + public void AddColumns () + { + // + // Init the DB + // + var path = ""; + using (var db = new TestDb (true) { Trace = true }) { + path = db.DatabasePath; + + db.CreateTable (); + + var cols = db.GetTableInfo ("TestAdd"); + Assert.AreEqual (2, cols.Count); + + var o = new TestAddBefore { + Name = "Foo", + }; + + db.Insert (o); + + var oo = db.Table ().First (); + + Assert.AreEqual ("Foo", oo.Name); + } + + // + // Migrate and use it + // + using (var db = new SQLiteConnection (path, true) { Trace = true }) { + + db.CreateTable (); + + var cols = db.GetTableInfo ("TestAdd"); + Assert.AreEqual (4, cols.Count); + + var oo = db.Table ().First (); + + Assert.AreEqual ("Foo", oo.Name); + Assert.AreEqual (0, oo.IntValue); + Assert.AreEqual (null, oo.StringValue); + + var o = new TestAddAfter { + Name = "Bar", + IntValue = 42, + StringValue = "Hello", + }; + db.Insert (o); + + var ooo = db.Get (o.Id); + Assert.AreEqual ("Bar", ooo.Name); + Assert.AreEqual (42, ooo.IntValue); + Assert.AreEqual ("Hello", ooo.StringValue); + } + } } } From ccb2ee333dc754f175d1fc5636a491f864f26860 Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Tue, 1 Aug 2017 11:30:14 -0700 Subject: [PATCH 032/103] Add test for derived ignore attributes Repro for #558 --- tests/IgnoreTest.cs | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/tests/IgnoreTest.cs b/tests/IgnoreTest.cs index 1d26eea4a..596b15c43 100644 --- a/tests/IgnoreTest.cs +++ b/tests/IgnoreTest.cs @@ -158,5 +158,40 @@ public void RedefinedIgnores () Assert.AreEqual ("Bar", oo.Value); Assert.AreEqual (null, oo.Values); } + + [AttributeUsage (AttributeTargets.Property, AllowMultiple = false, Inherited = true)] + class DerivedIgnoreAttribute : IgnoreAttribute + { + } + + class DerivedIgnoreClass + { + [PrimaryKey, AutoIncrement] + public int Id { get; set; } + + public string NotIgnored { get; set; } + + [DerivedIgnore] + public string Ignored { get; set; } + } + + [Test] + public void DerivedIgnore () + { + var db = new TestDb (); + db.CreateTable (); + + var o = new DerivedIgnoreClass { + Ignored = "Hello", + NotIgnored = "World", + }; + + db.Insert (o); + + var oo = db.Table ().First (); + + Assert.AreEqual (null, oo.Ignored); + Assert.AreEqual ("World", oo.NotIgnored); + } } } From fd4945dc2f059c4472d49b7b7404cef9e9c4558b Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Tue, 1 Aug 2017 11:41:58 -0700 Subject: [PATCH 033/103] Add test for named column overrides Repro for #548 --- tests/MappingTest.cs | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/tests/MappingTest.cs b/tests/MappingTest.cs index df5d0272f..f4ce2220b 100644 --- a/tests/MappingTest.cs +++ b/tests/MappingTest.cs @@ -41,6 +41,47 @@ public void HasGoodNames () Assert.AreEqual ("AGoodColumnName", mapping.Columns [1].Name); } + class OverrideNamesBase + { + [PrimaryKey, AutoIncrement] + public int Id { get; set; } + + public virtual string Name { get; set; } + public virtual string Value { get; set; } + } + + class OverrideNamesClass : OverrideNamesBase + { + [Column ("n")] + public override string Name { get; set; } + [Column ("v")] + public override string Value { get; set; } + } + + [Test] + public void OverrideNames () + { + var db = new TestDb (); + db.CreateTable (); + + var cols = db.GetTableInfo ("OverrideNamesClass"); + Assert.AreEqual (3, cols.Count); + Assert.IsTrue (cols.Exists (x => x.Name == "n")); + Assert.IsTrue (cols.Exists (x => x.Name == "v")); + + var o = new OverrideNamesClass { + Name = "Foo", + Value = "Bar", + }; + + db.Insert (o); + + var oo = db.Table ().First (); + + Assert.AreEqual ("Foo", oo.Name); + Assert.AreEqual ("Bar", oo.Value); + } + #region Issue #86 [Table("foo")] From 2c5f1e1337fddd9f1ae62e3af9828693f2c21d89 Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Tue, 1 Aug 2017 12:22:10 -0700 Subject: [PATCH 034/103] Fix using Select() table queries Support was partially added when working on Joins. That work never completed and this function was causing unexpected errors because it never executed the selector. Fixes #575 --- src/SQLite.cs | 16 +++++++++------- tests/JoinTest.cs | 18 +++++++++--------- tests/LinqTest.cs | 28 ++++++++++++++++++++++++++++ 3 files changed, 46 insertions(+), 16 deletions(-) diff --git a/src/SQLite.cs b/src/SQLite.cs index a7611cc22..af28b3a4a 100644 --- a/src/SQLite.cs +++ b/src/SQLite.cs @@ -2903,13 +2903,15 @@ public TableQuery Join ( }; return q; } - - public TableQuery Select (Expression> selector) - { - var q = Clone (); - q._selector = selector; - return q; - } + + // Not needed until Joins are supported + // Keeping this commented out forces the default Linq to objects processor to run + //public TableQuery Select (Expression> selector) + //{ + // var q = Clone (); + // q._selector = selector; + // return q; + //} private SQLiteCommand GenerateCommand (string selectionList) { diff --git a/tests/JoinTest.cs b/tests/JoinTest.cs index db3f31a53..96969461c 100644 --- a/tests/JoinTest.cs +++ b/tests/JoinTest.cs @@ -64,17 +64,17 @@ class R } //[Test] - public void JoinThenWhere () - { - var q = from ol in _db.Table () - join o in _db.Table () on ol.OrderId equals o.Id - where o.Id == 1 - select new { o.Id, ol.ProductId, ol.Quantity }; + //public void JoinThenWhere () + //{ + // var q = from ol in _db.Table () + // join o in _db.Table () on ol.OrderId equals o.Id + // where o.Id == 1 + // select new { o.Id, ol.ProductId, ol.Quantity }; - var r = System.Linq.Enumerable.ToList (q); + // var r = System.Linq.Enumerable.ToList (q); - Assert.AreEqual (2, r.Count); - } + // Assert.AreEqual (2, r.Count); + //} //[Test] public void WhereThenJoin () diff --git a/tests/LinqTest.cs b/tests/LinqTest.cs index 2ca2f443c..8066b68e5 100644 --- a/tests/LinqTest.cs +++ b/tests/LinqTest.cs @@ -233,5 +233,33 @@ public void Issue303_WhereNot_B() Assert.AreEqual(4, r[1].Id); } } + + [Test] + public void QuerySelectAverage () + { + var db = CreateDb (); + + db.Insert (new Product { + Name = "A", + Price = 20, + TotalSales = 100, + }); + + db.Insert (new Product { + Name = "B", + Price = 10, + TotalSales = 100, + }); + + db.Insert (new Product { + Name = "C", + Price = 1000, + TotalSales = 1, + }); + + var r = db.Table ().Where (x => x.TotalSales > 50).Select (s => s.Price).Average (); + + Assert.AreEqual (15, r); + } } } From f8a8a8b586238e660f5ec1685de345220c27a2e8 Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Tue, 1 Aug 2017 12:46:36 -0700 Subject: [PATCH 035/103] Support updating tables with only a PK Fixes #572 --- src/SQLite.cs | 8 +++++++ tests/MappingTest.cs | 55 +++++++++++++++++++++++++++++++++----------- 2 files changed, 50 insertions(+), 13 deletions(-) diff --git a/src/SQLite.cs b/src/SQLite.cs index af28b3a4a..33c1af26c 100644 --- a/src/SQLite.cs +++ b/src/SQLite.cs @@ -1480,6 +1480,14 @@ public int Update (object obj, Type objType) var vals = from c in cols select c.GetValue (obj); var ps = new List (vals); + if (ps.Count == 0) { + // There is a PK but no accompanying data, + // so reset the PK to make the UPDATE work. + cols = map.Columns; + vals = from c in cols + select c.GetValue (obj); + 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); diff --git a/tests/MappingTest.cs b/tests/MappingTest.cs index f4ce2220b..d55f596cf 100644 --- a/tests/MappingTest.cs +++ b/tests/MappingTest.cs @@ -30,15 +30,15 @@ class AFunnyTableName public void HasGoodNames () { var db = new TestDb (); - + db.CreateTable (); var mapping = db.GetMapping (); Assert.AreEqual ("AGoodTableName", mapping.TableName); - Assert.AreEqual ("Id", mapping.Columns [0].Name); - Assert.AreEqual ("AGoodColumnName", mapping.Columns [1].Name); + Assert.AreEqual ("Id", mapping.Columns[0].Name); + Assert.AreEqual ("AGoodColumnName", mapping.Columns[1].Name); } class OverrideNamesBase @@ -84,11 +84,11 @@ public void OverrideNames () #region Issue #86 - [Table("foo")] + [Table ("foo")] public class Foo { - [Column("baz")] - public int Bar { get; set; } + [Column ("baz")] + public int Bar { get; set; } } [Test] @@ -97,16 +97,45 @@ public void Issue86 () var db = new TestDb (); db.CreateTable (); - db.Insert (new Foo { Bar = 42 } ); - db.Insert (new Foo { Bar = 69 } ); + db.Insert (new Foo { Bar = 42 }); + db.Insert (new Foo { Bar = 69 }); - var found42 = db.Table ().Where (f => f.Bar == 42).FirstOrDefault(); + var found42 = db.Table ().Where (f => f.Bar == 42).FirstOrDefault (); Assert.IsNotNull (found42); - var ordered = new List(db.Table().OrderByDescending(f => f.Bar)); - Assert.AreEqual(2, ordered.Count); - Assert.AreEqual(69, ordered[0].Bar); - Assert.AreEqual(42, ordered[1].Bar); + var ordered = new List (db.Table ().OrderByDescending (f => f.Bar)); + Assert.AreEqual (2, ordered.Count); + Assert.AreEqual (69, ordered[0].Bar); + Assert.AreEqual (42, ordered[1].Bar); + } + + #endregion + + #region Issue #572 + + public class OnlyKeyModel + { + [PrimaryKey] + public string MyModelId { get; set; } + } + + [Test] + public void OnlyKey () + { + var db = new TestDb (); + db.CreateTable (); + + db.InsertOrReplace (new OnlyKeyModel { MyModelId = "Foo" }); + var foo = db.Get ("Foo"); + Assert.AreEqual (foo.MyModelId, "Foo"); + + db.Insert (new OnlyKeyModel { MyModelId = "Bar" }); + var bar = db.Get ("Bar"); + Assert.AreEqual (bar.MyModelId, "Bar"); + + db.Update (new OnlyKeyModel { MyModelId = "Foo" }); + var foo2 = db.Get ("Foo"); + Assert.AreEqual (foo2.MyModelId, "Foo"); } #endregion From 2df530a3d2215ee4ac4b7c034a4d4fa0a46543bf Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Tue, 1 Aug 2017 13:15:24 -0700 Subject: [PATCH 036/103] Fix querying interfaces Fixes #552 --- src/SQLite.cs | 10 +++++++++- tests/LinqTest.cs | 35 ++++++++++++++++++++++++++++++++++- 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/src/SQLite.cs b/src/SQLite.cs index 33c1af26c..61ab1f6bf 100644 --- a/src/SQLite.cs +++ b/src/SQLite.cs @@ -3089,8 +3089,16 @@ private CompileResult CompileExpr (Expression expr, List queryArgs) }; } else if (expr.NodeType == ExpressionType.MemberAccess) { var mem = (MemberExpression)expr; + + var paramExpr = mem.Expression as ParameterExpression; + if (paramExpr == null) { + var convert = mem.Expression as UnaryExpression; + if (convert != null && convert.NodeType == ExpressionType.Convert) { + paramExpr = convert.Operand as ParameterExpression; + } + } - if (mem.Expression!=null && mem.Expression.NodeType == ExpressionType.Parameter) { + if (paramExpr != null) { // // This is a column of our table, output just the column name // Need to translate it if that column name is mapped diff --git a/tests/LinqTest.cs b/tests/LinqTest.cs index 8066b68e5..ad1f350bf 100644 --- a/tests/LinqTest.cs +++ b/tests/LinqTest.cs @@ -259,7 +259,40 @@ public void QuerySelectAverage () var r = db.Table ().Where (x => x.TotalSales > 50).Select (s => s.Price).Average (); - Assert.AreEqual (15, r); + Assert.AreEqual (15m, r); + } + + interface IEntity + { + int Id { get; set; } + string Value { get; set; } + } + + class Entity : IEntity + { + [AutoIncrement, PrimaryKey] + public int Id { get; set; } + public string Value { get; set; } + } + + static T GetEntity (TestDb db, int id) where T : IEntity, new () + { + return db.Table ().FirstOrDefault (x => x.Id == id); + } + + [Test] + public void CastedParameters () + { + var db = CreateDb (); + db.CreateTable (); + + db.Insert (new Entity { + Value = "Foo", + }); + + var r = GetEntity (db, 1); + + Assert.AreEqual ("Foo", r.Value); } } } From 0039925b98b5267a5090ca3bef8c47200f25fd7a Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Tue, 1 Aug 2017 13:34:46 -0700 Subject: [PATCH 037/103] Only use sqlite3_close_v2 if it is supported Fixes #510 --- src/SQLite.cs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/SQLite.cs b/src/SQLite.cs index 61ab1f6bf..d4d17d15d 100644 --- a/src/SQLite.cs +++ b/src/SQLite.cs @@ -156,6 +156,8 @@ public partial class SQLiteConnection : IDisposable public string DatabasePath { get; private set; } + public int LibVersionNumber { get; private set; } + public bool TimeExecution { get; set; } public bool Trace { get; set; } @@ -213,6 +215,8 @@ public SQLiteConnection (string databasePath, SQLiteOpenFlags openFlags, bool st DatabasePath = databasePath; + LibVersionNumber = SQLite3.LibVersionNumber (); + #if NETFX_CORE SQLite3.SetDirectory(/*temp directory type*/2, Windows.Storage.ApplicationData.Current.TemporaryFolder.Path); #endif @@ -1660,6 +1664,8 @@ public void Close() protected virtual void Dispose(bool disposing) { + var useClose2 = LibVersionNumber >= 3007014; + if (_open && Handle != NullHandle) { try { if (disposing) { @@ -1669,14 +1675,14 @@ protected virtual void Dispose(bool disposing) } } - var r = SQLite3.Close2(Handle); + var r = useClose2 ? SQLite3.Close2(Handle) : SQLite3.Close(Handle); if (r != SQLite3.Result.OK) { string msg = SQLite3.GetErrmsg(Handle); throw SQLiteException.New(r, msg); } } else { - SQLite3.Close2(Handle); + var r = useClose2 ? SQLite3.Close2(Handle) : SQLite3.Close(Handle); } } finally { @@ -3723,6 +3729,11 @@ public static Result EnableLoadExtension(Sqlite3DatabaseHandle db, int onoff) } #endif + public static int LibVersionNumber() + { + return Sqlite3.sqlite3_libversion_number(); + } + public static ExtendedResult ExtendedErrCode(Sqlite3DatabaseHandle db) { return (ExtendedResult)Sqlite3.sqlite3_extended_errcode(db); From 68767da39fdc968acec4787b02614dc1ac7f11af Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Tue, 1 Aug 2017 15:10:35 -0700 Subject: [PATCH 038/103] Add tool to diff the async api Working on #480 --- SQLite.sln | 16 ++ tests/ApiDiff/ApiDiff.csproj | 46 ++++++ tests/ApiDiff/ListDiff.cs | 196 +++++++++++++++++++++++ tests/ApiDiff/Program.cs | 91 +++++++++++ tests/ApiDiff/Properties/AssemblyInfo.cs | 26 +++ 5 files changed, 375 insertions(+) create mode 100644 tests/ApiDiff/ApiDiff.csproj create mode 100644 tests/ApiDiff/ListDiff.cs create mode 100644 tests/ApiDiff/Program.cs create mode 100644 tests/ApiDiff/Properties/AssemblyInfo.cs diff --git a/SQLite.sln b/SQLite.sln index 4325e17a7..cc3147365 100644 --- a/SQLite.sln +++ b/SQLite.sln @@ -8,6 +8,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution sqlite-net-pcl.nuspec = sqlite-net-pcl.nuspec sqlite-net.nuspec = sqlite-net.nuspec Makefile = Makefile + diffapis.fsx = diffapis.fsx EndProjectSection EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SQLite-net", "nuget\SQLite-net\SQLite-net.csproj", "{7F946494-8EE0-432B-8A87-98961143D5C1}" @@ -18,6 +19,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SQLite.Tests.iOS", "tests\S EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SQLite-net-std", "nuget\SQLite-net-std\SQLite-net-std.csproj", "{081D08D6-10F1-431B-88FE-469FD9FE898C}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ApiDiff", "tests\ApiDiff\ApiDiff.csproj", "{1DEF735C-B973-4ED9-8446-7FFA6D0B410B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -76,10 +79,23 @@ Global {081D08D6-10F1-431B-88FE-469FD9FE898C}.Release|iPhoneSimulator.Build.0 = Release|Any CPU {081D08D6-10F1-431B-88FE-469FD9FE898C}.Debug|iPhone.ActiveCfg = Debug|Any CPU {081D08D6-10F1-431B-88FE-469FD9FE898C}.Debug|iPhone.Build.0 = Debug|Any CPU + {1DEF735C-B973-4ED9-8446-7FFA6D0B410B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1DEF735C-B973-4ED9-8446-7FFA6D0B410B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1DEF735C-B973-4ED9-8446-7FFA6D0B410B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1DEF735C-B973-4ED9-8446-7FFA6D0B410B}.Release|Any CPU.Build.0 = Release|Any CPU + {1DEF735C-B973-4ED9-8446-7FFA6D0B410B}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {1DEF735C-B973-4ED9-8446-7FFA6D0B410B}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {1DEF735C-B973-4ED9-8446-7FFA6D0B410B}.Release|iPhone.ActiveCfg = Release|Any CPU + {1DEF735C-B973-4ED9-8446-7FFA6D0B410B}.Release|iPhone.Build.0 = Release|Any CPU + {1DEF735C-B973-4ED9-8446-7FFA6D0B410B}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {1DEF735C-B973-4ED9-8446-7FFA6D0B410B}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {1DEF735C-B973-4ED9-8446-7FFA6D0B410B}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {1DEF735C-B973-4ED9-8446-7FFA6D0B410B}.Debug|iPhone.Build.0 = Debug|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {6947A8F1-99BE-4DD1-AD4D-D89425CE67A2} = {FECC0E44-E626-49CB-BD8B-0CFBD93FBEFF} {81850129-71C3-40C7-A48B-AA5D2C2E365E} = {FECC0E44-E626-49CB-BD8B-0CFBD93FBEFF} + {1DEF735C-B973-4ED9-8446-7FFA6D0B410B} = {FECC0E44-E626-49CB-BD8B-0CFBD93FBEFF} EndGlobalSection GlobalSection(MonoDevelopProperties) = preSolution StartupItem = tests\SQLite.Tests.csproj diff --git a/tests/ApiDiff/ApiDiff.csproj b/tests/ApiDiff/ApiDiff.csproj new file mode 100644 index 000000000..3fa23b0b8 --- /dev/null +++ b/tests/ApiDiff/ApiDiff.csproj @@ -0,0 +1,46 @@ + + + + Debug + AnyCPU + 8.0.30703 + 2.0 + {1DEF735C-B973-4ED9-8446-7FFA6D0B410B} + Exe + ApiDiff + ApiDiff + v4.6.1 + + + true + full + false + bin\Debug + DEBUG; + prompt + 4 + true + + + true + bin\Release + prompt + 4 + true + + + + + + + + + SQLite.cs + + + SQLiteAsync.cs + + + + + \ No newline at end of file diff --git a/tests/ApiDiff/ListDiff.cs b/tests/ApiDiff/ListDiff.cs new file mode 100644 index 000000000..a8cda060a --- /dev/null +++ b/tests/ApiDiff/ListDiff.cs @@ -0,0 +1,196 @@ +using System; +using System.Collections.Generic; + +namespace Praeclarum +{ + /// + /// The type of . + /// + public enum ListDiffActionType + { + /// + /// Update the SourceItem to make it like the DestinationItem + /// + Update, + /// + /// Add the DestinationItem + /// + Add, + /// + /// Remove the SourceItem + /// + Remove, + } + + /// + /// A action that can be one of: Update, Add, or Remove. + /// + /// The type of the source list elements + /// The type of the destination list elements + public class ListDiffAction + { + public ListDiffActionType ActionType; + public S SourceItem; + public D DestinationItem; + + public ListDiffAction(ListDiffActionType type, S source, D dest) + { + ActionType = type; + SourceItem = source; + DestinationItem = dest; + } + + public override string ToString() + { + return string.Format("{0} {1} {2}", ActionType, SourceItem, DestinationItem); + } + } + + /// + /// Finds a diff between two lists (that contain possibly different types). + /// are generated such that the order of items in the + /// destination list is preserved. + /// The algorithm is from: http://en.wikipedia.org/wiki/Longest_common_subsequence_problem + /// + /// The type of the source list elements + /// The type of the destination list elements + public class ListDiff + { + /// + /// The actions needed to transform a source list to a destination list. + /// + public List> Actions { get; private set; } + + /// + /// Whether the only contain Update actions + /// (no Adds or Removes). + /// + public bool ContainsOnlyUpdates { get; private set; } + + public ListDiff(IEnumerable sources, IEnumerable destinations) + : this (sources, destinations, (a,b) => a.Equals (b)) + { + } + + public ListDiff(IEnumerable sources, + IEnumerable destinations, + Func match) + { + if (sources == null) throw new ArgumentNullException("sources"); + if (destinations == null) throw new ArgumentNullException("destinations"); + if (match == null) throw new ArgumentNullException("match"); + + var x = new List(sources); + var y = new List(destinations); + + Actions = new List>(); + + var m = x.Count; + var n = y.Count; + + // + // Construct the C matrix + // + var c = new int[m + 1, n + 1]; + for (var i = 1; i <= m; i++) + { + for (var j = 1; j <= n; j++) + { + if (match(x[i - 1], y[j - 1])) + { + c[i, j] = c[i - 1, j - 1] + 1; + } + else + { + c[i, j] = Math.Max(c[i, j - 1], c[i - 1, j]); + } + } + } + + // + // Generate the actions + // + ContainsOnlyUpdates = true; + GenDiff(c, x, y, m, n, match); + } + + void GenDiff(int[,] c, List x, List y, int i, int j, Func match) + { + if (i > 0 && j > 0 && match(x[i - 1], y[j - 1])) + { + GenDiff(c, x, y, i - 1, j - 1, match); + Actions.Add(new ListDiffAction(ListDiffActionType.Update, x[i - 1], y[j - 1])); + } + else + { + if (j > 0 && (i == 0 || c[i, j - 1] >= c[i - 1, j])) + { + GenDiff(c, x, y, i, j - 1, match); + ContainsOnlyUpdates = false; + Actions.Add(new ListDiffAction(ListDiffActionType.Add, default(S), y[j - 1])); + } + else if (i > 0 && (j == 0 || c[i, j - 1] < c[i - 1, j])) + { + GenDiff(c, x, y, i - 1, j, match); + ContainsOnlyUpdates = false; + Actions.Add(new ListDiffAction(ListDiffActionType.Remove, x[i - 1], default(D))); + } + } + } + } + + public static class ListDiffEx + { + public static ListDiff MergeInto (this IList source, IEnumerable destination, Func match) + { + var diff = new ListDiff (source, destination, match); + + var p = 0; + + foreach (var a in diff.Actions) { + if (a.ActionType == ListDiffActionType.Add) { + source.Insert (p, a.DestinationItem); + p++; + } else if (a.ActionType == ListDiffActionType.Remove) { + source.RemoveAt (p); + } else { + p++; + } + } + + return diff; + } + + public static ListDiff MergeInto (this IList source, IEnumerable destination, Func match, Func create, Action update, Action delete) + { + var diff = new ListDiff (source, destination, match); + + var p = 0; + + foreach (var a in diff.Actions) { + if (a.ActionType == ListDiffActionType.Add) { + source.Insert (p, create (a.DestinationItem)); + p++; + } else if (a.ActionType == ListDiffActionType.Remove) { + delete (a.SourceItem); + source.RemoveAt (p); + } else { + update (a.SourceItem, a.DestinationItem); + p++; + } + } + + return diff; + } + + public static ListDiff Diff (this IEnumerable sources, IEnumerable destinations) + { + return new ListDiff (sources, destinations); + } + + public static ListDiff Diff (this IEnumerable sources, IEnumerable destinations, Func match) + { + return new ListDiff (sources, destinations, match); + } + } +} diff --git a/tests/ApiDiff/Program.cs b/tests/ApiDiff/Program.cs new file mode 100644 index 000000000..0c15ced73 --- /dev/null +++ b/tests/ApiDiff/Program.cs @@ -0,0 +1,91 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Linq; +using System.Text.RegularExpressions; +using SQLite; +using Praeclarum; + +namespace ApiDiff +{ + class Api + { + public string Name; + public string Declaration; + public string Index; + + static readonly Regex taskRe = new Regex (@"System\.Threading\.Tasks\.Task`1\[([^\]]*)\]"); + + public Api (MemberInfo member, string nameSuffix) + { + Name = member.Name; + Declaration = member.ToString (); + Index = Declaration; + + if (nameSuffix.Length > 0 && Name.EndsWith (nameSuffix)) { + var indexName = Name.Substring (0, Name.IndexOf (nameSuffix)); + Index = taskRe.Replace (Index.Replace (Name, indexName), "$1").Replace ("System.Int32", "Int32"); + } + } + } + + class Apis + { + public List All; + readonly string nameSuffix; + readonly Type type; + + public Apis (Type type, string nameSuffix = "") + { + this.type = type; + this.nameSuffix = nameSuffix; + All = type.GetMembers (BindingFlags.Public|BindingFlags.Instance) + .Where (x => !x.Name.StartsWith("get_") && !x.Name.StartsWith ("set_") && !x.Name.StartsWith ("remove_")) + .Select (x => new Api(x, nameSuffix)) + .OrderBy (x => x.Name) + .ToList (); + } + + public void DumpComparison (Apis other) + { + Console.ForegroundColor = ConsoleColor.Cyan; + Console.WriteLine (type.FullName); + + var diff = new ListDiff (All, other.All, (x, y) => x.Index == y.Index); + + foreach (var a in diff.Actions) { + switch (a.ActionType) { + case ListDiffActionType.Add: + Console.ForegroundColor = ConsoleColor.Green; + Console.Write ($" + {a.DestinationItem.Name}"); + Console.ForegroundColor = ConsoleColor.Gray; + Console.WriteLine ($" {a.DestinationItem.Declaration}"); + break; + case ListDiffActionType.Remove: + Console.ForegroundColor = ConsoleColor.Red; + Console.Write ($" - {a.SourceItem.Name}"); + Console.ForegroundColor = ConsoleColor.Gray; + Console.WriteLine ($" {a.SourceItem.Declaration}"); + break; + case ListDiffActionType.Update: + Console.ForegroundColor = ConsoleColor.Yellow; + Console.Write ($" {a.DestinationItem.Name}"); + Console.ForegroundColor = ConsoleColor.Gray; + Console.WriteLine ($" {a.DestinationItem.Declaration}"); + break; + } + } + Console.ResetColor (); + } + } + + class MainClass + { + public static void Main (string[] args) + { + var synchronous = new Apis (typeof (SQLiteConnection)); + var asynchronous = new Apis (typeof (SQLiteAsyncConnection), "Async"); + asynchronous.DumpComparison (synchronous); + } + } +} diff --git a/tests/ApiDiff/Properties/AssemblyInfo.cs b/tests/ApiDiff/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..6549154de --- /dev/null +++ b/tests/ApiDiff/Properties/AssemblyInfo.cs @@ -0,0 +1,26 @@ +using System.Reflection; +using System.Runtime.CompilerServices; + +// Information about this assembly is defined by the following attributes. +// Change them to the values specific to your project. + +[assembly: AssemblyTitle ("ApiDiff")] +[assembly: AssemblyDescription ("")] +[assembly: AssemblyConfiguration ("")] +[assembly: AssemblyCompany ("")] +[assembly: AssemblyProduct ("")] +[assembly: AssemblyCopyright ("${AuthorCopyright}")] +[assembly: AssemblyTrademark ("")] +[assembly: AssemblyCulture ("")] + +// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". +// The form "{Major}.{Minor}.*" will automatically update the build and revision, +// and "{Major}.{Minor}.{Build}.*" will update just the revision. + +[assembly: AssemblyVersion ("1.0.*")] + +// The following attributes are used to specify the signing key for the assembly, +// if desired. See the Mono documentation for more information about signing. + +//[assembly: AssemblyDelaySign(false)] +//[assembly: AssemblyKeyFile("")] From cce26c1656993a70bf892e8855b5437bc0345ac1 Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Tue, 1 Aug 2017 16:30:43 -0700 Subject: [PATCH 039/103] Add Delete async functions Working on #480 --- SQLite.sln | 1 - src/SQLiteAsync.cs | 80 +++++++++++++++++++++++++++++++++++----- tests/ApiDiff/Program.cs | 45 +++++++++++++++------- 3 files changed, 103 insertions(+), 23 deletions(-) diff --git a/SQLite.sln b/SQLite.sln index cc3147365..315acc901 100644 --- a/SQLite.sln +++ b/SQLite.sln @@ -8,7 +8,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution sqlite-net-pcl.nuspec = sqlite-net-pcl.nuspec sqlite-net.nuspec = sqlite-net.nuspec Makefile = Makefile - diffapis.fsx = diffapis.fsx EndProjectSection EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SQLite-net", "nuget\SQLite-net\SQLite-net.csproj", "{7F946494-8EE0-432B-8A87-98961143D5C1}" diff --git a/src/SQLiteAsync.cs b/src/SQLiteAsync.cs index c594e7ab2..a2d96738c 100644 --- a/src/SQLiteAsync.cs +++ b/src/SQLiteAsync.cs @@ -48,6 +48,9 @@ public SQLiteAsyncConnection(string databasePath, SQLiteOpenFlags openFlags, boo _connectionString = new SQLiteConnectionString(databasePath, storeDateTimeAsTicks); } + public string DatabasePath => GetConnection ().DatabasePath; + public int LibVersionNumber => GetConnection ().LibVersionNumber; + public static void ResetPool() { SQLiteConnectionPool.Shared.Reset(); @@ -166,19 +169,47 @@ public Task DeleteAsync (object item) }); } - public Task DeleteAllAsync() + public Task DeleteAsync (object primaryKey) + { + return Task.Factory.StartNew (() => { + var conn = GetConnection (); + using (conn.Lock ()) { + return conn.Delete (primaryKey); + } + }); + } + + public Task DeleteAsync (object primaryKey, TableMapping map) + { + return Task.Factory.StartNew (() => { + var conn = GetConnection (); + using (conn.Lock ()) { + return conn.Delete (primaryKey, map); + } + }); + } + + public Task DeleteAllAsync() { - return Task.Factory.StartNew(() => - { + return Task.Factory.StartNew(() => { var conn = GetConnection(); - using (conn.Lock()) - { + using (conn.Lock()) { return conn.DeleteAll(); } }); } - public Task GetAsync(object pk) + public Task DeleteAllAsync (TableMapping map) + { + return Task.Factory.StartNew (() => { + var conn = GetConnection (); + using (conn.Lock ()) { + return conn.DeleteAll (map); + } + }); + } + + public Task GetAsync(object pk) where T : new() { return Task.Factory.StartNew(() => @@ -191,6 +222,16 @@ public Task GetAsync(object pk) }); } + public Task GetAsync(object pk, TableMapping map) + { + return Task.Factory.StartNew (() => { + var conn = GetConnection (); + using (conn.Lock ()) { + return conn.Get (pk, map); + } + }); + } + public Task FindAsync (object pk) where T : new () { @@ -236,12 +277,12 @@ public Task ExecuteAsync (string query, params object[] args) }); } - public Task InsertAllAsync (IEnumerable items) + public Task InsertAllAsync (IEnumerable items, bool runInTransaction = true) { return Task.Factory.StartNew (() => { var conn = GetConnection (); using (conn.Lock ()) { - return conn.InsertAll (items); + return conn.InsertAll (items, runInTransaction); } }); } @@ -322,13 +363,34 @@ public Task ExecuteScalarAsync (string sql, params object[] args) public Task> QueryAsync (string sql, params object[] args) where T : new () { - return Task>.Factory.StartNew (() => { + return Task.Factory.StartNew (() => { var conn = GetConnection (); using (conn.Lock ()) { return conn.Query (sql, args); } }); } + + public Task> DeferredQueryAsync (string query, params object[] args) + where T : new() + { + return Task.Factory.StartNew (() => { + var conn = GetConnection (); + using (conn.Lock ()) { + return (IEnumerable)conn.DeferredQuery (query, args).ToList (); + } + }); + } + + public Task> DeferredQueryAsync (TableMapping map, string query, params object[] args) + { + return Task.Factory.StartNew (() => { + var conn = GetConnection (); + using (conn.Lock ()) { + return (IEnumerable)conn.DeferredQuery (map, query, args).ToList (); + } + }); + } } // diff --git a/tests/ApiDiff/Program.cs b/tests/ApiDiff/Program.cs index 0c15ced73..bc56b50ff 100644 --- a/tests/ApiDiff/Program.cs +++ b/tests/ApiDiff/Program.cs @@ -25,6 +25,7 @@ public Api (MemberInfo member, string nameSuffix) if (nameSuffix.Length > 0 && Name.EndsWith (nameSuffix)) { var indexName = Name.Substring (0, Name.IndexOf (nameSuffix)); Index = taskRe.Replace (Index.Replace (Name, indexName), "$1").Replace ("System.Int32", "Int32"); + Name = indexName; } } } @@ -35,57 +36,75 @@ class Apis readonly string nameSuffix; readonly Type type; + static readonly HashSet ignores = new HashSet { + "BeginTransaction", + "Commit", + "Rollback", + "RollbackTo", + "IsInTransaction", + "EndTransaction", + + "GetConnection", + "Handle", + + "Close", + "Dispose", + }; + public Apis (Type type, string nameSuffix = "") { this.type = type; this.nameSuffix = nameSuffix; All = type.GetMembers (BindingFlags.Public|BindingFlags.Instance) - .Where (x => !x.Name.StartsWith("get_") && !x.Name.StartsWith ("set_") && !x.Name.StartsWith ("remove_")) + .Where (x => !ignores.Contains(x.Name)) + .Where (x => !x.Name.StartsWith("get_") && !x.Name.StartsWith ("set_") && !x.Name.StartsWith ("remove_") && !x.Name.StartsWith ("add_")) .Select (x => new Api(x, nameSuffix)) .OrderBy (x => x.Name) .ToList (); } - public void DumpComparison (Apis other) + public int DumpComparison (Apis other) { Console.ForegroundColor = ConsoleColor.Cyan; Console.WriteLine (type.FullName); var diff = new ListDiff (All, other.All, (x, y) => x.Index == y.Index); + var n = 0; + foreach (var a in diff.Actions) { switch (a.ActionType) { case ListDiffActionType.Add: Console.ForegroundColor = ConsoleColor.Green; - Console.Write ($" + {a.DestinationItem.Name}"); - Console.ForegroundColor = ConsoleColor.Gray; - Console.WriteLine ($" {a.DestinationItem.Declaration}"); + Console.WriteLine ($" + {a.DestinationItem.Index}"); + n++; break; case ListDiffActionType.Remove: Console.ForegroundColor = ConsoleColor.Red; - Console.Write ($" - {a.SourceItem.Name}"); - Console.ForegroundColor = ConsoleColor.Gray; - Console.WriteLine ($" {a.SourceItem.Declaration}"); + Console.WriteLine ($" - {a.SourceItem.Index}"); + n++; break; case ListDiffActionType.Update: Console.ForegroundColor = ConsoleColor.Yellow; - Console.Write ($" {a.DestinationItem.Name}"); - Console.ForegroundColor = ConsoleColor.Gray; - Console.WriteLine ($" {a.DestinationItem.Declaration}"); + Console.WriteLine ($" {a.SourceItem.Index}"); break; } } Console.ResetColor (); + Console.WriteLine ($"{n} differences"); + + return n; } } class MainClass { - public static void Main (string[] args) + public static int Main (string[] args) { var synchronous = new Apis (typeof (SQLiteConnection)); var asynchronous = new Apis (typeof (SQLiteAsyncConnection), "Async"); - asynchronous.DumpComparison (synchronous); + var n = asynchronous.DumpComparison (synchronous); + return n > 0 ? 1 : 0; } } } From e8b4e75fc57c8c423f6010e99be4bfcf436de3ff Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Tue, 1 Aug 2017 16:39:57 -0700 Subject: [PATCH 040/103] Make API diff output GFM --- tests/ApiDiff/Program.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/ApiDiff/Program.cs b/tests/ApiDiff/Program.cs index bc56b50ff..738876cf0 100644 --- a/tests/ApiDiff/Program.cs +++ b/tests/ApiDiff/Program.cs @@ -76,22 +76,23 @@ public int DumpComparison (Apis other) switch (a.ActionType) { case ListDiffActionType.Add: Console.ForegroundColor = ConsoleColor.Green; - Console.WriteLine ($" + {a.DestinationItem.Index}"); + Console.WriteLine ($"- [ ] *add* `{a.DestinationItem.Index.Replace('`', '_')}`"); n++; break; case ListDiffActionType.Remove: Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine ($" - {a.SourceItem.Index}"); + Console.WriteLine ($"- [ ] *remove* `{a.SourceItem.Index.Replace('`', '_')}`"); n++; break; case ListDiffActionType.Update: Console.ForegroundColor = ConsoleColor.Yellow; - Console.WriteLine ($" {a.SourceItem.Index}"); + Console.WriteLine ($"- [x] `{a.SourceItem.Index.Replace('`', '_')}`"); break; } } Console.ResetColor (); - Console.WriteLine ($"{n} differences"); + Console.WriteLine (); + Console.WriteLine ($"**{n}** differences"); return n; } From d0a6d3eb9309593ec0f194b757a4255650b0b605 Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Tue, 1 Aug 2017 18:44:26 -0700 Subject: [PATCH 041/103] Add `Close` to async connections Working on #480 --- src/SQLiteAsync.cs | 42 ++++++++++++++++++++++++++-------------- tests/ApiDiff/Program.cs | 1 - 2 files changed, 27 insertions(+), 16 deletions(-) diff --git a/src/SQLiteAsync.cs b/src/SQLiteAsync.cs index a2d96738c..d525f5bed 100644 --- a/src/SQLiteAsync.cs +++ b/src/SQLiteAsync.cs @@ -1,5 +1,5 @@ // -// Copyright (c) 2012-2016 Krueger Systems, Inc. +// Copyright (c) 2012-2017 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 @@ -51,6 +51,9 @@ public SQLiteAsyncConnection(string databasePath, SQLiteOpenFlags openFlags, boo public string DatabasePath => GetConnection ().DatabasePath; public int LibVersionNumber => GetConnection ().LibVersionNumber; + /// + /// Closes all connections to all async databases. + /// public static void ResetPool() { SQLiteConnectionPool.Shared.Reset(); @@ -58,7 +61,12 @@ public static void ResetPool() public SQLiteConnectionWithLock GetConnection () { - return SQLiteConnectionPool.Shared.GetConnection (_connectionString, _openFlags); + return SQLiteConnectionPool.Shared.GetConnection(_connectionString, _openFlags); + } + + public void Close() + { + SQLiteConnectionPool.Shared.CloseConnection(_connectionString, _openFlags); } public Task CreateTableAsync (CreateFlags createFlags = CreateFlags.None) @@ -493,9 +501,9 @@ public class CreateTablesResult { public Dictionary Results { get; private set; } - internal CreateTablesResult () + public CreateTablesResult () { - this.Results = new Dictionary (); + Results = new Dictionary (); } } @@ -512,7 +520,7 @@ public Entry (SQLiteConnectionString connectionString, SQLiteOpenFlags openFlags Connection = new SQLiteConnectionWithLock (connectionString, openFlags); } - public void OnApplicationSuspended () + public void Close () { Connection.Dispose (); Connection = null; @@ -550,6 +558,19 @@ public SQLiteConnectionWithLock GetConnection (SQLiteConnectionString connection } } + public void CloseConnection (SQLiteConnectionString connectionString, SQLiteOpenFlags openFlags) + { + lock (_entriesLock) { + Entry entry; + string key = connectionString.ConnectionString; + + if (_entries.TryGetValue (key, out entry)) { + _entries.Remove (key); + entry.Close (); + } + } + } + /// /// Closes all connections managed by this pool. /// @@ -557,20 +578,11 @@ public void Reset () { lock (_entriesLock) { foreach (var entry in _entries.Values) { - entry.OnApplicationSuspended (); + entry.Close (); } _entries.Clear (); } } - - /// - /// Call this method when the application is suspended. - /// - /// Behaviour here is to close any open connections. - public void ApplicationSuspended () - { - Reset (); - } } public class SQLiteConnectionWithLock : SQLiteConnection diff --git a/tests/ApiDiff/Program.cs b/tests/ApiDiff/Program.cs index 738876cf0..6afffd77d 100644 --- a/tests/ApiDiff/Program.cs +++ b/tests/ApiDiff/Program.cs @@ -47,7 +47,6 @@ class Apis "GetConnection", "Handle", - "Close", "Dispose", }; From 1bd11c915756099ceb94cd8a907c825b42904462 Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Tue, 1 Aug 2017 20:11:39 -0700 Subject: [PATCH 042/103] Add async connection properties Working on #480 --- src/SQLite.cs | 4 ++-- src/SQLiteAsync.cs | 17 +++++++++++++++++ tests/ApiDiff/Program.cs | 10 +++++++++- 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/SQLite.cs b/src/SQLite.cs index d4d17d15d..a0ec3c94b 100644 --- a/src/SQLite.cs +++ b/src/SQLite.cs @@ -679,7 +679,7 @@ public int Execute (string query, params object[] args) if (TimeExecution) { _sw.Stop (); _elapsedMilliseconds += _sw.ElapsedMilliseconds; - Debug.WriteLine (string.Format ("Finished in {0} ms ({1:0.0} s total)", _sw.ElapsedMilliseconds, _elapsedMilliseconds / 1000.0)); + Tracer?.Invoke (string.Format ("Finished in {0} ms ({1:0.0} s total)", _sw.ElapsedMilliseconds, _elapsedMilliseconds / 1000.0)); } return r; @@ -702,7 +702,7 @@ public T ExecuteScalar (string query, params object[] args) if (TimeExecution) { _sw.Stop (); _elapsedMilliseconds += _sw.ElapsedMilliseconds; - Debug.WriteLine (string.Format ("Finished in {0} ms ({1:0.0} s total)", _sw.ElapsedMilliseconds, _elapsedMilliseconds / 1000.0)); + Tracer?.Invoke (string.Format ("Finished in {0} ms ({1:0.0} s total)", _sw.ElapsedMilliseconds, _elapsedMilliseconds / 1000.0)); } return r; diff --git a/src/SQLiteAsync.cs b/src/SQLiteAsync.cs index d525f5bed..f9e167658 100644 --- a/src/SQLiteAsync.cs +++ b/src/SQLiteAsync.cs @@ -50,6 +50,23 @@ public SQLiteAsyncConnection(string databasePath, SQLiteOpenFlags openFlags, boo public string DatabasePath => GetConnection ().DatabasePath; public int LibVersionNumber => GetConnection ().LibVersionNumber; + public TimeSpan BusyTimeout { + get { return GetConnection ().BusyTimeout; } + set { GetConnection ().BusyTimeout = value; } + } + public bool StoreDateTimeAsTicks => GetConnection ().StoreDateTimeAsTicks; + public bool Trace { + get { return GetConnection ().Trace; } + set { GetConnection ().Trace = value; } + } + public Action Tracer { + get { return GetConnection ().Tracer; } + set { GetConnection ().Tracer = value; } + } + public bool TimeExecution { + get { return GetConnection ().TimeExecution; } + set { GetConnection ().TimeExecution = value; } + } /// /// Closes all connections to all async databases. diff --git a/tests/ApiDiff/Program.cs b/tests/ApiDiff/Program.cs index 6afffd77d..cb954241f 100644 --- a/tests/ApiDiff/Program.cs +++ b/tests/ApiDiff/Program.cs @@ -37,17 +37,24 @@ class Apis readonly Type type; static readonly HashSet ignores = new HashSet { + "RunInTransaction", + "RunInTransactionAsync", "BeginTransaction", + "SaveTransactionPoint", "Commit", "Rollback", "RollbackTo", "IsInTransaction", + "Release", "EndTransaction", "GetConnection", "Handle", "Dispose", + + "Table", + "CreateCommand", }; public Apis (Type type, string nameSuffix = "") @@ -56,6 +63,7 @@ public Apis (Type type, string nameSuffix = "") this.nameSuffix = nameSuffix; All = type.GetMembers (BindingFlags.Public|BindingFlags.Instance) .Where (x => !ignores.Contains(x.Name)) + .Where (x => x.MemberType != MemberTypes.NestedType) .Where (x => !x.Name.StartsWith("get_") && !x.Name.StartsWith ("set_") && !x.Name.StartsWith ("remove_") && !x.Name.StartsWith ("add_")) .Select (x => new Api(x, nameSuffix)) .OrderBy (x => x.Name) @@ -84,7 +92,7 @@ public int DumpComparison (Apis other) n++; break; case ListDiffActionType.Update: - Console.ForegroundColor = ConsoleColor.Yellow; + Console.ForegroundColor = ConsoleColor.Gray; Console.WriteLine ($"- [x] `{a.SourceItem.Index.Replace('`', '_')}`"); break; } From 864fffdc28cf9a1c9e5e30dda43efee63bdfd7f1 Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Tue, 1 Aug 2017 20:39:35 -0700 Subject: [PATCH 043/103] Add async query functions --- src/SQLite.cs | 13 +++-- src/SQLiteAsync.cs | 107 ++++++++++++++++++++++++++++++++++----- tests/ApiDiff/Program.cs | 1 + 3 files changed, 101 insertions(+), 20 deletions(-) diff --git a/src/SQLite.cs b/src/SQLite.cs index a0ec3c94b..f626cb106 100644 --- a/src/SQLite.cs +++ b/src/SQLite.cs @@ -245,7 +245,7 @@ public SQLiteConnection (string databasePath, SQLiteOpenFlags openFlags, bool st Tracer = line => Debug.WriteLine (line); } - + #if __IOS__ static SQLiteConnection () { @@ -262,16 +262,17 @@ static SQLiteConnection () static bool _preserveDuringLinkMagic; #endif -#if !USE_SQLITEPCL_RAW - public void EnableLoadExtension(int onoff) + /// + /// Enable or disable extension loading. + /// + public void EnableLoadExtension(bool enabled) { - SQLite3.Result r = SQLite3.EnableLoadExtension(Handle, onoff); + SQLite3.Result r = SQLite3.EnableLoadExtension (Handle, enabled ? 1 : 0); if (r != SQLite3.Result.OK) { string msg = SQLite3.GetErrmsg (Handle); throw SQLiteException.New (r, msg); } } -#endif #if !USE_SQLITEPCL_RAW static byte[] GetNullTerminatedUtf8 (string s) @@ -3722,12 +3723,10 @@ public static byte[] ColumnByteArray(Sqlite3Statement stmt, int index) return new byte[0]; } -#if !USE_SQLITEPCL_RAW public static Result EnableLoadExtension(Sqlite3DatabaseHandle db, int onoff) { return (Result)Sqlite3.sqlite3_enable_load_extension(db, onoff); } -#endif public static int LibVersionNumber() { diff --git a/src/SQLiteAsync.cs b/src/SQLiteAsync.cs index f9e167658..6d0d1d0f9 100644 --- a/src/SQLiteAsync.cs +++ b/src/SQLiteAsync.cs @@ -67,6 +67,7 @@ public bool TimeExecution { get { return GetConnection ().TimeExecution; } set { GetConnection ().TimeExecution = value; } } + public IEnumerable TableMappings => GetConnection ().TableMappings; /// /// Closes all connections to all async databases. @@ -152,6 +153,16 @@ public Task DropTableAsync () }); } + public Task DropTableAsync (TableMapping map) + { + return Task.Factory.StartNew (() => { + var conn = GetConnection (); + using (conn.Lock ()) { + return conn.DropTable (map); + } + }); + } + public Task InsertAsync (object item) { return Task.Factory.StartNew (() => { @@ -257,6 +268,17 @@ public Task GetAsync(object pk, TableMapping map) }); } + public Task GetAsync (Expression> predicate) + where T : new() + { + return Task.Factory.StartNew (() => { + var conn = GetConnection (); + using (conn.Lock ()) { + return conn.Get (predicate); + } + }); + } + public Task FindAsync (object pk) where T : new () { @@ -267,19 +289,16 @@ public Task FindAsync (object pk) } }); } - - public Task GetAsync (Expression> predicate) - where T : new() - { - return Task.Factory.StartNew(() => - { - var conn = GetConnection(); - using (conn.Lock()) - { - return conn.Get (predicate); - } - }); - } + + public Task FindAsync (object pk, TableMapping map) + { + return Task.Factory.StartNew (() => { + var conn = GetConnection (); + using (conn.Lock ()) { + return conn.Find (pk, map); + } + }); + } public Task FindAsync (Expression> predicate) where T : new () @@ -292,6 +311,58 @@ public Task FindAsync (Expression> predicate) }); } + public Task FindWithQueryAsync (string query, params object[] args) + where T : new() + { + return Task.Factory.StartNew (() => { + var conn = GetConnection (); + using (conn.Lock ()) { + return conn.FindWithQuery (query, args); + } + }); + } + + public Task FindWithQueryAsync (TableMapping map, string query, params object[] args) + { + return Task.Factory.StartNew (() => { + var conn = GetConnection (); + using (conn.Lock ()) { + return conn.FindWithQuery (map, query, args); + } + }); + } + + public Task GetMappingAsync (Type type, CreateFlags createFlags = CreateFlags.None) + { + return Task.Factory.StartNew (() => { + var conn = GetConnection (); + using (conn.Lock ()) { + return conn.GetMapping (type, createFlags); + } + }); + } + + public Task GetMappingAsync (CreateFlags createFlags = CreateFlags.None) + where T : new() + { + return Task.Factory.StartNew (() => { + var conn = GetConnection (); + using (conn.Lock ()) { + return conn.GetMapping (createFlags); + } + }); + } + + public Task> GetTableInfoAsync (string tableName) + { + return Task.Factory.StartNew (() => { + var conn = GetConnection (); + using (conn.Lock ()) { + return conn.GetTableInfo (tableName); + } + }); + } + public Task ExecuteAsync (string query, params object[] args) { return Task.Factory.StartNew (() => { @@ -396,6 +467,16 @@ public Task> QueryAsync (string sql, params object[] args) }); } + public Task> QueryAsync (TableMapping map, string sql, params object[] args) + { + return Task.Factory.StartNew (() => { + var conn = GetConnection (); + using (conn.Lock ()) { + return conn.Query (map, sql, args); + } + }); + } + public Task> DeferredQueryAsync (string query, params object[] args) where T : new() { diff --git a/tests/ApiDiff/Program.cs b/tests/ApiDiff/Program.cs index cb954241f..76e0dff1e 100644 --- a/tests/ApiDiff/Program.cs +++ b/tests/ApiDiff/Program.cs @@ -55,6 +55,7 @@ class Apis "Table", "CreateCommand", + "TableChanged", }; public Apis (Type type, string nameSuffix = "") From 0829d449f6f3c4dae3b9f519f6a142a87fca717f Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Tue, 1 Aug 2017 20:55:36 -0700 Subject: [PATCH 044/103] Add async update methods Working on #480 --- src/SQLiteAsync.cs | 100 +++++++++++++++++++++++++++++++++++---- tests/ApiDiff/Program.cs | 5 +- 2 files changed, 93 insertions(+), 12 deletions(-) diff --git a/src/SQLiteAsync.cs b/src/SQLiteAsync.cs index 6d0d1d0f9..e6cde7c84 100644 --- a/src/SQLiteAsync.cs +++ b/src/SQLiteAsync.cs @@ -87,6 +87,16 @@ public void Close() SQLiteConnectionPool.Shared.CloseConnection(_connectionString, _openFlags); } + public Task EnableLoadExtensionAsync (bool enabled) + { + return Task.Factory.StartNew (() => { + var conn = GetConnection (); + using (conn.Lock ()) { + conn.EnableLoadExtension (enabled); + } + }); + } + public Task CreateTableAsync (CreateFlags createFlags = CreateFlags.None) where T : new () { @@ -163,34 +173,84 @@ public Task DropTableAsync (TableMapping map) }); } - public Task InsertAsync (object item) + public Task InsertAsync (object obj) { return Task.Factory.StartNew (() => { var conn = GetConnection (); using (conn.Lock ()) { - return conn.Insert (item); + return conn.Insert (obj); } }); } - public Task InsertOrReplaceAsync(object item) + public Task InsertAsync (object obj, Type objType) + { + return Task.Factory.StartNew (() => { + var conn = GetConnection (); + using (conn.Lock ()) { + return conn.Insert (obj, objType); + } + }); + } + + public Task InsertAsync (object obj, string extra) + { + return Task.Factory.StartNew (() => { + var conn = GetConnection (); + using (conn.Lock ()) { + return conn.Insert (obj, extra); + } + }); + } + + public Task InsertAsync (object obj, string extra, Type objType) + { + return Task.Factory.StartNew (() => { + var conn = GetConnection (); + using (conn.Lock ()) { + return conn.Insert (obj, extra, objType); + } + }); + } + + public Task InsertOrReplaceAsync(object obj) { return Task.Factory.StartNew(() => { var conn = GetConnection(); using (conn.Lock()) { - return conn.InsertOrReplace(item); + return conn.InsertOrReplace(obj); } }); } - public Task UpdateAsync (object item) + public Task InsertOrReplaceAsync (object obj, Type objType) { return Task.Factory.StartNew (() => { var conn = GetConnection (); using (conn.Lock ()) { - return conn.Update (item); + return conn.InsertOrReplace (obj, objType); + } + }); + } + + public Task UpdateAsync (object obj) + { + return Task.Factory.StartNew (() => { + var conn = GetConnection (); + using (conn.Lock ()) { + return conn.Update (obj); + } + }); + } + + public Task UpdateAsync (object obj, Type objType) + { + return Task.Factory.StartNew (() => { + var conn = GetConnection (); + using (conn.Lock ()) { + return conn.Update (obj, objType); } }); } @@ -382,18 +442,38 @@ public Task InsertAllAsync (IEnumerable items, bool runInTransaction = true } }); } - - public Task UpdateAllAsync (IEnumerable items) + + public Task InsertAllAsync (IEnumerable items, string extra, bool runInTransaction = true) + { + return Task.Factory.StartNew (() => { + var conn = GetConnection (); + using (conn.Lock ()) { + return conn.InsertAll (items, extra, runInTransaction); + } + }); + } + + public Task InsertAllAsync (IEnumerable items, Type objType, bool runInTransaction = true) + { + return Task.Factory.StartNew (() => { + var conn = GetConnection (); + using (conn.Lock ()) { + return conn.InsertAll (items, objType, runInTransaction); + } + }); + } + + public Task UpdateAllAsync (IEnumerable objects, bool runInTransaction = true) { return Task.Factory.StartNew (() => { var conn = GetConnection (); using (conn.Lock ()) { - return conn.UpdateAll (items); + return conn.UpdateAll (objects, runInTransaction); } }); } - [Obsolete("Will cause a deadlock if any call in action ends up in a different thread. Use RunInTransactionAsync(Action) instead.")] + [Obsolete("Will cause a deadlock if any call in action ends up in a different thread. Use RunInTransactionAsync(Action) instead.")] public Task RunInTransactionAsync (Action action) { return Task.Factory.StartNew (() => { diff --git a/tests/ApiDiff/Program.cs b/tests/ApiDiff/Program.cs index 76e0dff1e..5abbe9249 100644 --- a/tests/ApiDiff/Program.cs +++ b/tests/ApiDiff/Program.cs @@ -14,7 +14,8 @@ class Api public string Declaration; public string Index; - static readonly Regex taskRe = new Regex (@"System\.Threading\.Tasks\.Task`1\[([^\]]*)\]"); + static readonly Regex task1Re = new Regex (@"System\.Threading\.Tasks\.Task`1\[([^\]]*)\]"); + static readonly Regex taskRe = new Regex (@"System\.Threading\.Tasks\.Task(\s*)"); public Api (MemberInfo member, string nameSuffix) { @@ -24,7 +25,7 @@ public Api (MemberInfo member, string nameSuffix) if (nameSuffix.Length > 0 && Name.EndsWith (nameSuffix)) { var indexName = Name.Substring (0, Name.IndexOf (nameSuffix)); - Index = taskRe.Replace (Index.Replace (Name, indexName), "$1").Replace ("System.Int32", "Int32"); + Index = taskRe.Replace (task1Re.Replace (Index.Replace (Name, indexName), "$1"), "Void$1").Replace ("System.Int32", "Int32"); Name = indexName; } } From 3db31a00c728382fcd0e53f60f91c897c5c31979 Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Tue, 1 Aug 2017 21:21:25 -0700 Subject: [PATCH 045/103] Add async CreateTables and CreateIndex overloads Working on #480 --- src/SQLite.cs | 119 +++++++++++++++++++++++++++++++++++---- src/SQLiteAsync.cs | 85 ++++++++++++++++++++++------ tests/ApiDiff/Program.cs | 1 + 3 files changed, 178 insertions(+), 27 deletions(-) diff --git a/src/SQLite.cs b/src/SQLite.cs index f626cb106..b59c5e8a4 100644 --- a/src/SQLite.cs +++ b/src/SQLite.cs @@ -479,14 +479,103 @@ public int CreateTable(Type ty, CreateFlags createFlags = CreateFlags.None) 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) + /// + /// Executes a "create table if not exists" on the database for each type. It also + /// creates any specified indexes on the columns of the table. It uses + /// a schema automatically generated from the specified type. You can + /// later access this schema by calling GetMapping. + /// + /// + /// The number of entries added to the database schema for each type. + /// + public CreateTablesResult CreateTables (CreateFlags createFlags = CreateFlags.None) + where T : new() + where T2 : new() + { + return CreateTables (createFlags, typeof (T), typeof (T2)); + } + + /// + /// Executes a "create table if not exists" on the database for each type. It also + /// creates any specified indexes on the columns of the table. It uses + /// a schema automatically generated from the specified type. You can + /// later access this schema by calling GetMapping. + /// + /// + /// The number of entries added to the database schema for each type. + /// + public CreateTablesResult CreateTables (CreateFlags createFlags = CreateFlags.None) + where T : new() + where T2 : new() + where T3 : new() + { + return CreateTables (createFlags, typeof (T), typeof (T2), typeof (T3)); + } + + /// + /// Executes a "create table if not exists" on the database for each type. It also + /// creates any specified indexes on the columns of the table. It uses + /// a schema automatically generated from the specified type. You can + /// later access this schema by calling GetMapping. + /// + /// + /// The number of entries added to the database schema for each type. + /// + public CreateTablesResult CreateTables (CreateFlags createFlags = CreateFlags.None) + where T : new() + where T2 : new() + where T3 : new() + where T4 : new() + { + return CreateTables (createFlags, typeof (T), typeof (T2), typeof (T3), typeof (T4)); + } + + /// + /// Executes a "create table if not exists" on the database for each type. It also + /// creates any specified indexes on the columns of the table. It uses + /// a schema automatically generated from the specified type. You can + /// later access this schema by calling GetMapping. + /// + /// + /// The number of entries added to the database schema for each type. + /// + public CreateTablesResult CreateTables (CreateFlags createFlags = CreateFlags.None) + where T : new() + where T2 : new() + where T3 : new() + where T4 : new() + where T5 : new() + { + return CreateTables (createFlags, typeof (T), typeof (T2), typeof (T3), typeof (T4), typeof (T5)); + } + + /// + /// Executes a "create table if not exists" on the database for each type. It also + /// creates any specified indexes on the columns of the table. It uses + /// a schema automatically generated from the specified type. You can + /// later access this schema by calling GetMapping. + /// + /// + /// The number of entries added to the database schema for each type. + /// + public CreateTablesResult CreateTables (CreateFlags createFlags = CreateFlags.None, params Type[] types) + { + var result = new CreateTablesResult (); + foreach (Type type in types) { + int aResult = CreateTable (type, createFlags); + result.Results[type] = aResult; + } + return result; + } + + /// + /// 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); @@ -534,7 +623,7 @@ public int CreateIndex(string tableName, string[] columnNames, bool unique = fal /// Type to reflect to a database table. /// Property to index /// Whether the index should be unique - public void CreateIndex(Expression> property, bool unique = false) + public int CreateIndex(Expression> property, bool unique = false) { MemberExpression mx; if (property.Body.NodeType == ExpressionType.Convert) @@ -556,7 +645,7 @@ public void CreateIndex(Expression> property, bool unique = f var map = GetMapping(); var colName = map.FindColumnWithPropertyName(propName).Name; - CreateIndex(map.TableName, colName, unique); + return CreateIndex(map.TableName, colName, unique); } public class ColumnInfo @@ -2724,6 +2813,16 @@ private void Dispose (bool disposing) } } + public class CreateTablesResult + { + public Dictionary Results { get; private set; } + + public CreateTablesResult () + { + Results = new Dictionary (); + } + } + public abstract class BaseTableQuery { protected class Ordering diff --git a/src/SQLiteAsync.cs b/src/SQLiteAsync.cs index e6cde7c84..3228c2aa7 100644 --- a/src/SQLiteAsync.cs +++ b/src/SQLiteAsync.cs @@ -97,10 +97,25 @@ public Task EnableLoadExtensionAsync (bool enabled) }); } - public Task CreateTableAsync (CreateFlags createFlags = CreateFlags.None) + public Task CreateTableAsync (CreateFlags createFlags = CreateFlags.None) where T : new () { - return CreateTablesAsync (createFlags, typeof(T)); + return Task.Factory.StartNew (() => { + var conn = GetConnection (); + using (conn.Lock ()) { + return conn.CreateTable (createFlags); + } + }); + } + + public Task CreateTableAsync (Type ty, CreateFlags createFlags = CreateFlags.None) + { + return Task.Factory.StartNew (() => { + var conn = GetConnection (); + using (conn.Lock ()) { + return conn.CreateTable (ty, createFlags); + } + }); } public Task CreateTablesAsync (CreateFlags createFlags = CreateFlags.None) @@ -143,12 +158,8 @@ public Task CreateTablesAsync (CreateFlags createFlags = Cre CreateTablesResult result = new CreateTablesResult (); var conn = GetConnection (); using (conn.Lock ()) { - foreach (Type type in types) { - int aResult = conn.CreateTable (type, createFlags); - result.Results[type] = aResult; - } + return conn.CreateTables (createFlags, types); } - return result; }); } @@ -173,6 +184,56 @@ public Task DropTableAsync (TableMapping map) }); } + public Task CreateIndexAsync (string tableName, string columnName, bool unique = false) + { + return Task.Factory.StartNew (() => { + var conn = GetConnection (); + using (conn.Lock ()) { + return conn.CreateIndex (tableName, columnName, unique); + } + }); + } + + public Task CreateIndexAsync (string indexName, string tableName, string columnName, bool unique = false) + { + return Task.Factory.StartNew (() => { + var conn = GetConnection (); + using (conn.Lock ()) { + return conn.CreateIndex (indexName, tableName, columnName, unique); + } + }); + } + + public Task CreateIndexAsync (string tableName, string[] columnNames, bool unique = false) + { + return Task.Factory.StartNew (() => { + var conn = GetConnection (); + using (conn.Lock ()) { + return conn.CreateIndex (tableName, columnNames, unique); + } + }); + } + + public Task CreateIndexAsync (string indexName, string tableName, string[] columnNames, bool unique = false) + { + return Task.Factory.StartNew (() => { + var conn = GetConnection (); + using (conn.Lock ()) { + return conn.CreateIndex (indexName, tableName, columnNames, unique); + } + }); + } + + public Task CreateIndexAsync (Expression> property, bool unique = false) + { + return Task.Factory.StartNew (() => { + var conn = GetConnection (); + using (conn.Lock ()) { + return conn.CreateIndex (property, unique); + } + }); + } + public Task InsertAsync (object obj) { return Task.Factory.StartNew (() => { @@ -675,16 +736,6 @@ public Task FirstOrDefaultAsync () } } - public class CreateTablesResult - { - public Dictionary Results { get; private set; } - - public CreateTablesResult () - { - Results = new Dictionary (); - } - } - class SQLiteConnectionPool { class Entry diff --git a/tests/ApiDiff/Program.cs b/tests/ApiDiff/Program.cs index 5abbe9249..4b22246e6 100644 --- a/tests/ApiDiff/Program.cs +++ b/tests/ApiDiff/Program.cs @@ -68,6 +68,7 @@ public Apis (Type type, string nameSuffix = "") .Where (x => x.MemberType != MemberTypes.NestedType) .Where (x => !x.Name.StartsWith("get_") && !x.Name.StartsWith ("set_") && !x.Name.StartsWith ("remove_") && !x.Name.StartsWith ("add_")) .Select (x => new Api(x, nameSuffix)) + .OrderBy (x => x.Index) .OrderBy (x => x.Name) .ToList (); } From afb8590364b2a871f4d20c1755c5ae273b9a2b17 Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Tue, 1 Aug 2017 21:43:35 -0700 Subject: [PATCH 046/103] Refactor async database locking code --- src/SQLiteAsync.cs | 375 +++++++++------------------------------------ 1 file changed, 74 insertions(+), 301 deletions(-) diff --git a/src/SQLiteAsync.cs b/src/SQLiteAsync.cs index 3228c2aa7..215308248 100644 --- a/src/SQLiteAsync.cs +++ b/src/SQLiteAsync.cs @@ -87,37 +87,45 @@ public void Close() SQLiteConnectionPool.Shared.CloseConnection(_connectionString, _openFlags); } - public Task EnableLoadExtensionAsync (bool enabled) + Task ReadAsync (Func read) { return Task.Factory.StartNew (() => { var conn = GetConnection (); using (conn.Lock ()) { - conn.EnableLoadExtension (enabled); + return read (conn); } }); } - public Task CreateTableAsync (CreateFlags createFlags = CreateFlags.None) - where T : new () + Task WriteAsync (Func write) { return Task.Factory.StartNew (() => { var conn = GetConnection (); using (conn.Lock ()) { - return conn.CreateTable (createFlags); + return write (conn); } }); } - public Task CreateTableAsync (Type ty, CreateFlags createFlags = CreateFlags.None) + public Task EnableLoadExtensionAsync (bool enabled) { - return Task.Factory.StartNew (() => { - var conn = GetConnection (); - using (conn.Lock ()) { - return conn.CreateTable (ty, createFlags); - } + return WriteAsync (conn => { + conn.EnableLoadExtension (enabled); + return null; }); } + public Task CreateTableAsync (CreateFlags createFlags = CreateFlags.None) + where T : new () + { + return WriteAsync (conn => conn.CreateTable (createFlags)); + } + + public Task CreateTableAsync (Type ty, CreateFlags createFlags = CreateFlags.None) + { + return WriteAsync (conn => conn.CreateTable (ty, createFlags)); + } + public Task CreateTablesAsync (CreateFlags createFlags = CreateFlags.None) where T : new () where T2 : new () @@ -154,423 +162,211 @@ public Task CreateTablesAsync (CreateFlag public Task CreateTablesAsync (CreateFlags createFlags = CreateFlags.None, params Type[] types) { - return Task.Factory.StartNew (() => { - CreateTablesResult result = new CreateTablesResult (); - var conn = GetConnection (); - using (conn.Lock ()) { - return conn.CreateTables (createFlags, types); - } - }); + return WriteAsync (conn => conn.CreateTables (createFlags, types)); } public Task DropTableAsync () where T : new () { - return Task.Factory.StartNew (() => { - var conn = GetConnection (); - using (conn.Lock ()) { - return conn.DropTable (); - } - }); + return WriteAsync (conn => conn.DropTable ()); } public Task DropTableAsync (TableMapping map) { - return Task.Factory.StartNew (() => { - var conn = GetConnection (); - using (conn.Lock ()) { - return conn.DropTable (map); - } - }); + return WriteAsync (conn => conn.DropTable (map)); } public Task CreateIndexAsync (string tableName, string columnName, bool unique = false) { - return Task.Factory.StartNew (() => { - var conn = GetConnection (); - using (conn.Lock ()) { - return conn.CreateIndex (tableName, columnName, unique); - } - }); + return WriteAsync (conn => conn.CreateIndex (tableName, columnName, unique)); } public Task CreateIndexAsync (string indexName, string tableName, string columnName, bool unique = false) { - return Task.Factory.StartNew (() => { - var conn = GetConnection (); - using (conn.Lock ()) { - return conn.CreateIndex (indexName, tableName, columnName, unique); - } - }); + return WriteAsync (conn => conn.CreateIndex (indexName, tableName, columnName, unique)); } public Task CreateIndexAsync (string tableName, string[] columnNames, bool unique = false) { - return Task.Factory.StartNew (() => { - var conn = GetConnection (); - using (conn.Lock ()) { - return conn.CreateIndex (tableName, columnNames, unique); - } - }); + return WriteAsync (conn => conn.CreateIndex (tableName, columnNames, unique)); } public Task CreateIndexAsync (string indexName, string tableName, string[] columnNames, bool unique = false) { - return Task.Factory.StartNew (() => { - var conn = GetConnection (); - using (conn.Lock ()) { - return conn.CreateIndex (indexName, tableName, columnNames, unique); - } - }); + return WriteAsync (conn => conn.CreateIndex (indexName, tableName, columnNames, unique)); } public Task CreateIndexAsync (Expression> property, bool unique = false) { - return Task.Factory.StartNew (() => { - var conn = GetConnection (); - using (conn.Lock ()) { - return conn.CreateIndex (property, unique); - } - }); + return WriteAsync (conn => conn.CreateIndex (property, unique)); } public Task InsertAsync (object obj) { - return Task.Factory.StartNew (() => { - var conn = GetConnection (); - using (conn.Lock ()) { - return conn.Insert (obj); - } - }); + return WriteAsync (conn => conn.Insert (obj)); } public Task InsertAsync (object obj, Type objType) { - return Task.Factory.StartNew (() => { - var conn = GetConnection (); - using (conn.Lock ()) { - return conn.Insert (obj, objType); - } - }); + return WriteAsync (conn => conn.Insert (obj, objType)); } public Task InsertAsync (object obj, string extra) { - return Task.Factory.StartNew (() => { - var conn = GetConnection (); - using (conn.Lock ()) { - return conn.Insert (obj, extra); - } - }); + return WriteAsync (conn => conn.Insert (obj, extra)); } public Task InsertAsync (object obj, string extra, Type objType) { - return Task.Factory.StartNew (() => { - var conn = GetConnection (); - using (conn.Lock ()) { - return conn.Insert (obj, extra, objType); - } - }); + return WriteAsync (conn => conn.Insert (obj, extra, objType)); } public Task InsertOrReplaceAsync(object obj) { - return Task.Factory.StartNew(() => - { - var conn = GetConnection(); - using (conn.Lock()) - { - return conn.InsertOrReplace(obj); - } - }); + return WriteAsync (conn => conn.InsertOrReplace(obj)); } public Task InsertOrReplaceAsync (object obj, Type objType) { - return Task.Factory.StartNew (() => { - var conn = GetConnection (); - using (conn.Lock ()) { - return conn.InsertOrReplace (obj, objType); - } - }); + return WriteAsync (conn => conn.InsertOrReplace (obj, objType)); } public Task UpdateAsync (object obj) { - return Task.Factory.StartNew (() => { - var conn = GetConnection (); - using (conn.Lock ()) { - return conn.Update (obj); - } - }); + return WriteAsync (conn => conn.Update (obj)); } public Task UpdateAsync (object obj, Type objType) { - return Task.Factory.StartNew (() => { - var conn = GetConnection (); - using (conn.Lock ()) { - return conn.Update (obj, objType); - } - }); + return WriteAsync (conn => conn.Update (obj, objType)); } public Task DeleteAsync (object item) { - return Task.Factory.StartNew (() => { - var conn = GetConnection (); - using (conn.Lock ()) { - return conn.Delete (item); - } - }); + return WriteAsync (conn => conn.Delete (item)); } public Task DeleteAsync (object primaryKey) { - return Task.Factory.StartNew (() => { - var conn = GetConnection (); - using (conn.Lock ()) { - return conn.Delete (primaryKey); - } - }); + return WriteAsync (conn => conn.Delete (primaryKey)); } public Task DeleteAsync (object primaryKey, TableMapping map) { - return Task.Factory.StartNew (() => { - var conn = GetConnection (); - using (conn.Lock ()) { - return conn.Delete (primaryKey, map); - } - }); + return WriteAsync (conn => conn.Delete (primaryKey, map)); } public Task DeleteAllAsync() { - return Task.Factory.StartNew(() => { - var conn = GetConnection(); - using (conn.Lock()) { - return conn.DeleteAll(); - } - }); + return WriteAsync (conn => conn.DeleteAll()); } public Task DeleteAllAsync (TableMapping map) { - return Task.Factory.StartNew (() => { - var conn = GetConnection (); - using (conn.Lock ()) { - return conn.DeleteAll (map); - } - }); + return WriteAsync (conn => conn.DeleteAll (map)); } public Task GetAsync(object pk) where T : new() { - return Task.Factory.StartNew(() => - { - var conn = GetConnection(); - using (conn.Lock()) - { - return conn.Get(pk); - } - }); + return ReadAsync (conn => conn.Get(pk)); } public Task GetAsync(object pk, TableMapping map) { - return Task.Factory.StartNew (() => { - var conn = GetConnection (); - using (conn.Lock ()) { - return conn.Get (pk, map); - } - }); + return ReadAsync (conn => conn.Get (pk, map)); } public Task GetAsync (Expression> predicate) where T : new() { - return Task.Factory.StartNew (() => { - var conn = GetConnection (); - using (conn.Lock ()) { - return conn.Get (predicate); - } - }); + return ReadAsync (conn => conn.Get (predicate)); } public Task FindAsync (object pk) where T : new () { - return Task.Factory.StartNew (() => { - var conn = GetConnection (); - using (conn.Lock ()) { - return conn.Find (pk); - } - }); + return ReadAsync (conn => conn.Find (pk)); } public Task FindAsync (object pk, TableMapping map) { - return Task.Factory.StartNew (() => { - var conn = GetConnection (); - using (conn.Lock ()) { - return conn.Find (pk, map); - } - }); + return ReadAsync (conn => conn.Find (pk, map)); } public Task FindAsync (Expression> predicate) where T : new () { - return Task.Factory.StartNew (() => { - var conn = GetConnection (); - using (conn.Lock ()) { - return conn.Find (predicate); - } - }); + return ReadAsync (conn => conn.Find (predicate)); } public Task FindWithQueryAsync (string query, params object[] args) where T : new() { - return Task.Factory.StartNew (() => { - var conn = GetConnection (); - using (conn.Lock ()) { - return conn.FindWithQuery (query, args); - } - }); + return ReadAsync (conn => conn.FindWithQuery (query, args)); } public Task FindWithQueryAsync (TableMapping map, string query, params object[] args) { - return Task.Factory.StartNew (() => { - var conn = GetConnection (); - using (conn.Lock ()) { - return conn.FindWithQuery (map, query, args); - } - }); + return ReadAsync (conn => conn.FindWithQuery (map, query, args)); } public Task GetMappingAsync (Type type, CreateFlags createFlags = CreateFlags.None) { - return Task.Factory.StartNew (() => { - var conn = GetConnection (); - using (conn.Lock ()) { - return conn.GetMapping (type, createFlags); - } - }); + return ReadAsync (conn => conn.GetMapping (type, createFlags)); } public Task GetMappingAsync (CreateFlags createFlags = CreateFlags.None) where T : new() { - return Task.Factory.StartNew (() => { - var conn = GetConnection (); - using (conn.Lock ()) { - return conn.GetMapping (createFlags); - } - }); + return ReadAsync (conn => conn.GetMapping (createFlags)); } public Task> GetTableInfoAsync (string tableName) { - return Task.Factory.StartNew (() => { - var conn = GetConnection (); - using (conn.Lock ()) { - return conn.GetTableInfo (tableName); - } - }); + return ReadAsync (conn => conn.GetTableInfo (tableName)); } public Task ExecuteAsync (string query, params object[] args) { - return Task.Factory.StartNew (() => { - var conn = GetConnection (); - using (conn.Lock ()) { - return conn.Execute (query, args); - } - }); + return WriteAsync (conn => conn.Execute (query, args)); } public Task InsertAllAsync (IEnumerable items, bool runInTransaction = true) { - return Task.Factory.StartNew (() => { - var conn = GetConnection (); - using (conn.Lock ()) { - return conn.InsertAll (items, runInTransaction); - } - }); + return WriteAsync (conn => conn.InsertAll (items, runInTransaction)); } public Task InsertAllAsync (IEnumerable items, string extra, bool runInTransaction = true) { - return Task.Factory.StartNew (() => { - var conn = GetConnection (); - using (conn.Lock ()) { - return conn.InsertAll (items, extra, runInTransaction); - } - }); + return WriteAsync (conn => conn.InsertAll (items, extra, runInTransaction)); } public Task InsertAllAsync (IEnumerable items, Type objType, bool runInTransaction = true) { - return Task.Factory.StartNew (() => { - var conn = GetConnection (); - using (conn.Lock ()) { - return conn.InsertAll (items, objType, runInTransaction); - } - }); + return WriteAsync (conn => conn.InsertAll (items, objType, runInTransaction)); } public Task UpdateAllAsync (IEnumerable objects, bool runInTransaction = true) { - return Task.Factory.StartNew (() => { - var conn = GetConnection (); - using (conn.Lock ()) { - return conn.UpdateAll (objects, runInTransaction); - } - }); - } - - [Obsolete("Will cause a deadlock if any call in action ends up in a different thread. Use RunInTransactionAsync(Action) instead.")] - public Task RunInTransactionAsync (Action action) - { - return Task.Factory.StartNew (() => { - var conn = this.GetConnection (); - using (conn.Lock ()) { - conn.BeginTransaction (); - try { - action (this); - conn.Commit (); - } - catch (Exception) { - conn.Rollback (); - throw; - } - } - }); + return WriteAsync (conn => conn.UpdateAll (objects, runInTransaction)); } public Task RunInTransactionAsync(Action action) { - return Task.Factory.StartNew(() => + return WriteAsync (conn => { - var conn = this.GetConnection(); - using (conn.Lock()) + conn.BeginTransaction(); + try { - conn.BeginTransaction(); - try - { - action(conn); - conn.Commit(); - } - catch (Exception) - { - conn.Rollback(); - throw; - } + action(conn); + conn.Commit(); + return null; + } + catch (Exception) + { + conn.Rollback(); + throw; } }); } @@ -588,55 +384,32 @@ public AsyncTableQuery Table () public Task ExecuteScalarAsync (string sql, params object[] args) { - return Task.Factory.StartNew (() => { - var conn = GetConnection (); - using (conn.Lock ()) { - var command = conn.CreateCommand (sql, args); - return command.ExecuteScalar (); - } + return WriteAsync (conn => { + var command = conn.CreateCommand (sql, args); + return command.ExecuteScalar (); }); } public Task> QueryAsync (string sql, params object[] args) where T : new () { - return Task.Factory.StartNew (() => { - var conn = GetConnection (); - using (conn.Lock ()) { - return conn.Query (sql, args); - } - }); + return ReadAsync (conn => conn.Query (sql, args)); } public Task> QueryAsync (TableMapping map, string sql, params object[] args) { - return Task.Factory.StartNew (() => { - var conn = GetConnection (); - using (conn.Lock ()) { - return conn.Query (map, sql, args); - } - }); + return ReadAsync (conn => conn.Query (map, sql, args)); } public Task> DeferredQueryAsync (string query, params object[] args) where T : new() { - return Task.Factory.StartNew (() => { - var conn = GetConnection (); - using (conn.Lock ()) { - return (IEnumerable)conn.DeferredQuery (query, args).ToList (); - } - }); + return ReadAsync (conn => (IEnumerable)conn.DeferredQuery (query, args).ToList ()); } public Task> DeferredQueryAsync (TableMapping map, string query, params object[] args) { - return Task.Factory.StartNew (() => { - var conn = GetConnection (); - using (conn.Lock ()) { - return (IEnumerable)conn.DeferredQuery (map, query, args).ToList (); - } - }); + return ReadAsync (conn => (IEnumerable)conn.DeferredQuery (map, query, args).ToList ()); } } From d215bf8137e2c9ffe8d3495efad8deea1744169a Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Wed, 2 Aug 2017 08:49:26 -0700 Subject: [PATCH 047/103] Add .editorconfig Fixes #592 --- .editorconfig | 169 ++++++++++++++++++++++++++++++++++++++++++++++++++ SQLite.sln | 15 +++++ 2 files changed, 184 insertions(+) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..2fbea20ce --- /dev/null +++ b/.editorconfig @@ -0,0 +1,169 @@ +# editorconfig.org + +# top-most EditorConfig file +root = true + +# Default settings: +# A newline ending every file +# Use 4 spaces as indentation +[*] +insert_final_newline = true +indent_style = space +indent_size = 4 +end_of_line = lf + +[project.json] +indent_size = 2 + +# C# files +[*.cs] +end_of_line = lf +indent_style = tab +tab_width = 4 + +# New line preferences +csharp_new_line_before_open_brace = types:methods +csharp_new_line_before_else = true +csharp_new_line_before_catch = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_within_query_expression_clauses = true + +# Indentation preferences +csharp_indent_block_contents = true +csharp_indent_braces = false +csharp_indent_case_contents = true +csharp_indent_switch_labels = true +csharp_indent_labels = flush_left + +# avoid this. unless absolutely necessary +dotnet_style_qualification_for_field = false:suggestion +dotnet_style_qualification_for_property = false:suggestion +dotnet_style_qualification_for_method = false:suggestion +dotnet_style_qualification_for_event = false:suggestion + +# only use var when it's obvious what the variable type is +csharp_style_var_for_built_in_types = false:none +csharp_style_var_when_type_is_apparent = false:none +csharp_style_var_elsewhere = false:suggestion + +# use language keywords instead of BCL types +dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion +dotnet_style_predefined_type_for_member_access = true:suggestion + +# name all constant fields using PascalCase +dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields +dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style + +dotnet_naming_symbols.constant_fields.applicable_kinds = field +dotnet_naming_symbols.constant_fields.required_modifiers = const + +dotnet_naming_style.pascal_case_style.capitalization = pascal_case + +# static fields should have s_ prefix +dotnet_naming_rule.static_fields_should_have_prefix.severity = suggestion +dotnet_naming_rule.static_fields_should_have_prefix.symbols = static_fields +dotnet_naming_rule.static_fields_should_have_prefix.style = static_prefix_style + +dotnet_naming_symbols.static_fields.applicable_kinds = field +dotnet_naming_symbols.static_fields.required_modifiers = static + +dotnet_naming_style.static_prefix_style.required_prefix = s_ +dotnet_naming_style.static_prefix_style.capitalization = camel_case + +# internal and private fields should be _camelCase +dotnet_naming_rule.camel_case_for_private_internal_fields.severity = suggestion +dotnet_naming_rule.camel_case_for_private_internal_fields.symbols = private_internal_fields +dotnet_naming_rule.camel_case_for_private_internal_fields.style = camel_case_underscore_style + +dotnet_naming_symbols.private_internal_fields.applicable_kinds = field +dotnet_naming_symbols.private_internal_fields.applicable_accessibilities = private, internal + +dotnet_naming_style.camel_case_underscore_style.required_prefix = _ +dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case + +# Code style defaults +dotnet_sort_system_directives_first = true +csharp_preserve_single_line_blocks = true +csharp_preserve_single_line_statements = false + +# Expression-level preferences +dotnet_style_object_initializer = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_explicit_tuple_names = true:suggestion +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_null_propagation = true:suggestion + +# Expression-bodied members +csharp_style_expression_bodied_methods = false:none +csharp_style_expression_bodied_constructors = false:none +csharp_style_expression_bodied_operators = false:none +csharp_style_expression_bodied_properties = true:none +csharp_style_expression_bodied_indexers = true:none +csharp_style_expression_bodied_accessors = true:none + +# Pattern matching +csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion +csharp_style_pattern_matching_over_as_with_null_check = true:suggestion +csharp_style_inlined_variable_declaration = true:suggestion + +# Null checking preferences +csharp_style_throw_expression = true:suggestion +csharp_style_conditional_delegate_call = true:suggestion + +# Space preferences +csharp_space_after_cast = false +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_after_comma = true +csharp_space_after_dot = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_after_semicolon_in_for_statement = true +csharp_space_around_binary_operators = before_and_after +csharp_space_around_declaration_statements = do_not_ignore +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_before_comma = false +csharp_space_before_dot = false +csharp_space_before_open_square_brackets = false +csharp_space_before_semicolon_in_for_statement = false +csharp_space_between_empty_square_brackets = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_name_and_open_parenthesis = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = false +csharp_space_between_square_brackets = false + +# C++ Files +[*.{cpp,h,in}] +curly_bracket_next_line = true +indent_brace_style = Allman + +# Xml project files +[*.{csproj,vcxproj,vcxproj.filters,proj,nativeproj,locproj}] +indent_size = 2 +end_of_line = crlf + +# Xml build files +[*.builds] +indent_size = 2 + +# Xml files +[*.{xml,stylecop,resx,ruleset}] +indent_size = 2 + +# Xml config files +[*.{props,targets,config,nuspec}] +indent_size = 2 + +# Shell scripts +[*.sh] +end_of_line = lf +[*.{cmd, bat}] +end_of_line = crlf + +[Makefile] +indent_style = tab diff --git a/SQLite.sln b/SQLite.sln index 315acc901..9e38efc12 100644 --- a/SQLite.sln +++ b/SQLite.sln @@ -98,5 +98,20 @@ Global EndGlobalSection GlobalSection(MonoDevelopProperties) = preSolution StartupItem = tests\SQLite.Tests.csproj + Policies = $0 + $0.TextStylePolicy = $1 + $1.FileWidth = 128 + $1.NoTabsAfterNonTabs = True + $1.EolMarker = Unix + $1.scope = text/x-csharp + $0.CSharpFormattingPolicy = $2 + $2.NewLinesForBracesInProperties = False + $2.NewLinesForBracesInAccessors = False + $2.NewLinesForBracesInAnonymousMethods = False + $2.NewLinesForBracesInControlBlocks = False + $2.NewLinesForBracesInAnonymousTypes = False + $2.NewLinesForBracesInObjectCollectionArrayInitializers = False + $2.NewLinesForBracesInLambdaExpressionBody = False + $2.scope = text/x-csharp EndGlobalSection EndGlobal From 1c4977c1be4540e577642cd8f9d12e6e23e38a2f Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Wed, 2 Aug 2017 09:04:31 -0700 Subject: [PATCH 048/103] Add space around method params/args Improve #592 --- .editorconfig | 4 ++-- SQLite.sln | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.editorconfig b/.editorconfig index 2fbea20ce..24ba7f359 100644 --- a/.editorconfig +++ b/.editorconfig @@ -129,10 +129,10 @@ csharp_space_before_open_square_brackets = false csharp_space_before_semicolon_in_for_statement = false csharp_space_between_empty_square_brackets = false csharp_space_between_method_call_empty_parameter_list_parentheses = false -csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_name_and_opening_parenthesis = true csharp_space_between_method_call_parameter_list_parentheses = false csharp_space_between_method_declaration_empty_parameter_list_parentheses = false -csharp_space_between_method_declaration_name_and_open_parenthesis = false +csharp_space_between_method_declaration_name_and_open_parenthesis = true csharp_space_between_method_declaration_parameter_list_parentheses = false csharp_space_between_parentheses = false csharp_space_between_square_brackets = false diff --git a/SQLite.sln b/SQLite.sln index 9e38efc12..97cc8b237 100644 --- a/SQLite.sln +++ b/SQLite.sln @@ -113,5 +113,7 @@ Global $2.NewLinesForBracesInObjectCollectionArrayInitializers = False $2.NewLinesForBracesInLambdaExpressionBody = False $2.scope = text/x-csharp + $2.SpacingAfterMethodDeclarationName = True + $2.SpaceAfterMethodCallName = True EndGlobalSection EndGlobal From a3b30b050f722fab37f17baf0c150e6934900385 Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Wed, 2 Aug 2017 09:06:05 -0700 Subject: [PATCH 049/103] Change white space to match .editorconfig Completes #592 --- src/SQLite.cs | 1561 +++++++++++++++++++++++--------------------- src/SQLiteAsync.cs | 159 +++-- 2 files changed, 886 insertions(+), 834 deletions(-) diff --git a/src/SQLite.cs b/src/SQLite.cs index b59c5e8a4..b67863ee3 100644 --- a/src/SQLite.cs +++ b/src/SQLite.cs @@ -66,7 +66,7 @@ public class SQLiteException : Exception { public SQLite3.Result Result { get; private set; } - protected SQLiteException (SQLite3.Result r,string message) : base(message) + protected SQLiteException (SQLite3.Result r, string message) : base (message) { Result = r; } @@ -114,7 +114,8 @@ public static NotNullConstraintViolationException New (SQLiteException exception } [Flags] - public enum SQLiteOpenFlags { + public enum SQLiteOpenFlags + { ReadOnly = 1, ReadWrite = 2, Create = 4, NoMutex = 0x8000, FullMutex = 0x10000, SharedCache = 0x20000, PrivateCache = 0x40000, @@ -124,17 +125,17 @@ public enum SQLiteOpenFlags { ProtectionNone = 0x00400000 } - [Flags] - public enum CreateFlags - { - None = 0x000, - ImplicitPK = 0x001, // create a primary key for field called 'Id' (Orm.ImplicitPkName) - ImplicitIndex = 0x002, // create an index for fields ending in 'Id' (Orm.ImplicitIndexSuffix) - AllImplicit = 0x003, // do both above - AutoIncPK = 0x004, // force PK field to be auto inc - FullTextSearch3 = 0x100, // create virtual table using FTS3 - FullTextSearch4 = 0x200 // create virtual table using FTS4 - } + [Flags] + public enum CreateFlags + { + None = 0x000, + ImplicitPK = 0x001, // create a primary key for field called 'Id' (Orm.ImplicitPkName) + ImplicitIndex = 0x002, // create an index for fields ending in 'Id' (Orm.ImplicitIndexSuffix) + AllImplicit = 0x003, // do both above + AutoIncPK = 0x004, // force PK field to be auto inc + FullTextSearch3 = 0x100, // create virtual table using FTS3 + FullTextSearch4 = 0x200 // create virtual table using FTS4 + } /// /// Represents an open connection to a SQLite database. @@ -152,7 +153,7 @@ public partial class SQLiteConnection : IDisposable private Random _rand = new Random (); public Sqlite3DatabaseHandle Handle { get; private set; } - internal static readonly Sqlite3DatabaseHandle NullHandle = default(Sqlite3DatabaseHandle); + internal static readonly Sqlite3DatabaseHandle NullHandle = default (Sqlite3DatabaseHandle); public string DatabasePath { get; private set; } @@ -166,9 +167,9 @@ public partial class SQLiteConnection : IDisposable public bool StoreDateTimeAsTicks { get; private set; } #if USE_SQLITEPCL_RAW - static SQLiteConnection() + static SQLiteConnection () { - SQLitePCL.Batteries_V2.Init(); + SQLitePCL.Batteries_V2.Init (); } #endif @@ -224,7 +225,7 @@ public SQLiteConnection (string databasePath, SQLiteOpenFlags openFlags, bool st Sqlite3DatabaseHandle handle; #if SILVERLIGHT || USE_CSHARP_SQLITE || USE_SQLITEPCL_RAW - var r = SQLite3.Open (databasePath, out handle, (int)openFlags, IntPtr.Zero); + var r = SQLite3.Open (databasePath, out handle, (int)openFlags, IntPtr.Zero); #else // open using the byte[] // in the case where the path may include Unicode @@ -240,7 +241,7 @@ public SQLiteConnection (string databasePath, SQLiteOpenFlags openFlags, bool st _open = true; StoreDateTimeAsTicks = storeDateTimeAsTicks; - + BusyTimeout = TimeSpan.FromSeconds (0.1); Tracer = line => Debug.WriteLine (line); @@ -265,14 +266,14 @@ static SQLiteConnection () /// /// Enable or disable extension loading. /// - public void EnableLoadExtension(bool enabled) - { + public void EnableLoadExtension (bool enabled) + { SQLite3.Result r = SQLite3.EnableLoadExtension (Handle, enabled ? 1 : 0); if (r != SQLite3.Result.OK) { string msg = SQLite3.GetErrmsg (Handle); throw SQLiteException.New (r, msg); } - } + } #if !USE_SQLITEPCL_RAW static byte[] GetNullTerminatedUtf8 (string s) @@ -284,7 +285,7 @@ static byte[] GetNullTerminatedUtf8 (string s) } #endif - /// + /// /// 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. /// @@ -314,14 +315,14 @@ public IEnumerable TableMappings { /// /// The type whose mapping to the database is returned. /// - /// + /// /// Optional flags allowing implicit PK and indexes based on naming conventions /// /// /// The mapping represents the schema of the columns of the database and contains /// methods to set and get properties of objects. /// - public TableMapping GetMapping (Type type, CreateFlags createFlags = CreateFlags.None) + public TableMapping GetMapping (Type type, CreateFlags createFlags = CreateFlags.None) { if (_mappings == null) { _mappings = new Dictionary (); @@ -329,11 +330,11 @@ public TableMapping GetMapping (Type type, CreateFlags createFlags = CreateFlags TableMapping map; if (!_mappings.TryGetValue (type.FullName, out map)) { map = new TableMapping (type, createFlags); - _mappings [type.FullName] = map; + _mappings[type.FullName] = map; } return map; } - + /// /// Retrieves the mapping that is automatically generated for the given type. /// @@ -366,11 +367,11 @@ private struct IndexInfo /// /// Executes a "drop table" on the database. This is non-recoverable. /// - public int DropTable() + public int DropTable () { return DropTable (GetMapping (typeof (T))); } - + /// /// Executes a "drop table" on the database. This is non-recoverable. /// @@ -392,9 +393,9 @@ public int DropTable (TableMapping map) /// /// The number of entries added to the database schema. /// - public int CreateTable(CreateFlags createFlags = CreateFlags.None) + public int CreateTable (CreateFlags createFlags = CreateFlags.None) { - return CreateTable(typeof (T), createFlags); + return CreateTable (typeof (T), createFlags); } /// @@ -404,11 +405,11 @@ public int CreateTable(CreateFlags createFlags = CreateFlags.None) /// later access this schema by calling GetMapping. /// /// Type to reflect to a database table. - /// Optional flags allowing implicit PK and indexes based on naming conventions. + /// Optional flags allowing implicit PK and indexes based on naming conventions. /// /// The number of entries added to the database schema. /// - public int CreateTable(Type ty, CreateFlags createFlags = CreateFlags.None) + public int CreateTable (Type ty, CreateFlags createFlags = CreateFlags.None) { if (_tables == null) { _tables = new Dictionary (); @@ -424,24 +425,24 @@ public int CreateTable(Type ty, CreateFlags createFlags = CreateFlags.None) throw new Exception (string.Format ("Cannot create a table with zero columns (does '{0}' have public properties?)", ty.FullName)); } - // Facilitate virtual tables a.k.a. full-text search. - bool fts3 = (createFlags & CreateFlags.FullTextSearch3) != 0; - bool fts4 = (createFlags & CreateFlags.FullTextSearch4) != 0; - bool fts = fts3 || fts4; - var @virtual = fts ? "virtual " : string.Empty; - var @using = fts3 ? "using fts3 " : fts4 ? "using fts4 " : string.Empty; + // Facilitate virtual tables a.k.a. full-text search. + bool fts3 = (createFlags & CreateFlags.FullTextSearch3) != 0; + bool fts4 = (createFlags & CreateFlags.FullTextSearch4) != 0; + bool fts = fts3 || fts4; + var @virtual = fts ? "virtual " : string.Empty; + var @using = fts3 ? "using fts3 " : fts4 ? "using fts4 " : string.Empty; - // Build query. + // Build query. var query = "create " + @virtual + "table if not exists \"" + map.TableName + "\" " + @using + "(\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 + // Table already exists, migrate it MigrateTable (map); } @@ -472,10 +473,10 @@ public int CreateTable(Type ty, CreateFlags createFlags = CreateFlags.None) foreach (var indexName in indexes.Keys) { var index = indexes[indexName]; - var columns = index.Columns.OrderBy(i => i.Order).Select(i => i.ColumnName).ToArray(); - count += CreateIndex(indexName, index.TableName, columns, index.Unique); + var columns = index.Columns.OrderBy (i => i.Order).Select (i => i.ColumnName).ToArray (); + count += CreateIndex (indexName, index.TableName, columns, index.Unique); } - + return count; } @@ -575,94 +576,91 @@ public CreateTablesResult CreateTables (CreateFlags createFlags = CreateFlags.No /// 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<Client>(c => c.Name); - /// - /// Type to reflect to a database table. - /// Property to index - /// Whether the index should be unique - public int 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; - - return CreateIndex(map.TableName, colName, 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<Client>(c => c.Name); + /// + /// Type to reflect to a database table. + /// Property to index + /// Whether the index should be unique + public int 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; + + return CreateIndex (map.TableName, colName, unique); + } public class ColumnInfo { -// public int cid { get; set; } + // public int cid { get; set; } [Column ("name")] public string Name { get; set; } -// [Column ("type")] -// public string ColumnType { get; set; } + // [Column ("type")] + // public string ColumnType { get; set; } public int notnull { get; set; } -// public string dflt_value { get; set; } + // public string dflt_value { get; set; } -// public int pk { get; set; } + // public int pk { get; set; } public override string ToString () { @@ -672,16 +670,16 @@ public override string ToString () public List GetTableInfo (string tableName) { - var query = "pragma table_info(\"" + 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) { @@ -693,7 +691,7 @@ void MigrateTable (TableMapping map) toBeAdded.Add (p); } } - + foreach (var p in toBeAdded) { var addCol = "alter table \"" + map.TableName + "\" add column " + Orm.SqlDecl (p, StoreDateTimeAsTicks); Execute (addCol); @@ -755,7 +753,7 @@ public SQLiteCommand CreateCommand (string cmdText, params object[] ps) public int Execute (string query, params object[] args) { var cmd = CreateCommand (query, args); - + if (TimeExecution) { if (_sw == null) { _sw = new Stopwatch (); @@ -765,20 +763,20 @@ public int Execute (string query, params object[] args) } var r = cmd.ExecuteNonQuery (); - + if (TimeExecution) { _sw.Stop (); _elapsedMilliseconds += _sw.ElapsedMilliseconds; Tracer?.Invoke (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 (); @@ -786,15 +784,15 @@ public T ExecuteScalar (string query, params object[] args) _sw.Reset (); _sw.Start (); } - + var r = cmd.ExecuteScalar (); - + if (TimeExecution) { _sw.Stop (); _elapsedMilliseconds += _sw.ElapsedMilliseconds; Tracer?.Invoke (string.Format ("Finished in {0} ms ({1:0.0} s total)", _sw.ElapsedMilliseconds, _elapsedMilliseconds / 1000.0)); } - + return r; } @@ -836,10 +834,10 @@ public T ExecuteScalar (string query, params object[] args) /// 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() + public IEnumerable DeferredQuery (string query, params object[] args) where T : new() { - var cmd = CreateCommand(query, args); - return cmd.ExecuteDeferredQuery(); + var cmd = CreateCommand (query, args); + return cmd.ExecuteDeferredQuery (); } /// @@ -890,10 +888,10 @@ public List Query (TableMapping map, string query, params object[] args) /// 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) + public IEnumerable DeferredQuery (TableMapping map, string query, params object[] args) { - var cmd = CreateCommand(query, args); - return cmd.ExecuteDeferredQuery(map); + var cmd = CreateCommand (query, args); + return cmd.ExecuteDeferredQuery (map); } /// @@ -923,7 +921,7 @@ public IEnumerable DeferredQuery(TableMapping map, string query, params public T Get (object pk) where T : new() { var map = GetMapping (typeof (T)); - return Query (map.GetByPrimaryKeySql, pk).First (); + return Query (map.GetByPrimaryKeySql, pk).First (); } /// @@ -946,21 +944,21 @@ public object Get (object pk, TableMapping map) return Query (map, map.GetByPrimaryKeySql, pk).First (); } - /// - /// Attempts to retrieve the first object that matches the predicate from the table - /// associated with the specified type. - /// - /// - /// A predicate for which object to find. - /// - /// - /// The object that matches the given predicate. Throws a not found exception - /// if the object is not found. - /// - public T Get (Expression> predicate) where T : new() - { - return Table ().Where (predicate).First (); - } + /// + /// Attempts to retrieve the first object that matches the predicate from the table + /// associated with the specified type. + /// + /// + /// A predicate for which object to find. + /// + /// + /// The object that matches the given predicate. Throws a not found exception + /// if the object is not found. + /// + public T Get (Expression> predicate) where T : new() + { + return Table ().Where (predicate).First (); + } /// /// Attempts to retrieve an object with the given primary key from the table @@ -974,7 +972,7 @@ public object Get (object pk, TableMapping map) /// The object with the given primary key or null /// if the object is not found. /// - public T Find (object pk) where T : new () + public T Find (object pk) where T : new() { var map = GetMapping (typeof (T)); return Query (map.GetByPrimaryKeySql, pk).FirstOrDefault (); @@ -999,22 +997,22 @@ 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 (); - } + /// Attempts to retrieve the first object that matches the predicate from the table + /// associated with the specified type. + /// + /// + /// A predicate for which object to find. + /// + /// + /// The object that matches the given predicate or null + /// if the object is not found. + /// + public T Find (Expression> predicate) where T : new() + { + return Table ().Where (predicate).FirstOrDefault (); + } /// /// Attempts to retrieve the first object that matches the query from the table @@ -1079,22 +1077,24 @@ public void BeginTransaction () if (Interlocked.CompareExchange (ref _transactionDepth, 1, 0) == 0) { try { Execute ("begin transaction"); - } catch (Exception ex) { + } + 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; + 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 { + } + else { // Call decrement and not VolatileWrite in case we've already // created a transaction point in SaveTransactionPoint since the catch. Interlocked.Decrement (ref _transactionDepth); @@ -1102,7 +1102,8 @@ public void BeginTransaction () throw; } - } else { + } + else { // Calling BeginTransaction on an already open transaction is invalid throw new InvalidOperationException ("Cannot begin a transaction while already in a transaction."); } @@ -1124,22 +1125,24 @@ public string SaveTransactionPoint () try { Execute ("savepoint " + retVal); - } catch (Exception ex) { + } + 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; + 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 { + } + else { Interlocked.Decrement (ref _transactionDepth); } @@ -1164,8 +1167,8 @@ public void Rollback () public void RollbackTo (string savepoint) { RollbackTo (savepoint, false); - } - + } + /// /// Rolls back the transaction that was begun by . /// @@ -1180,13 +1183,15 @@ void RollbackTo (string savepoint, bool noThrow) if (Interlocked.Exchange (ref _transactionDepth, 0) > 0) { Execute ("rollback"); } - } else { + } + else { DoSavePointExecute (savepoint, "rollback to "); - } - } catch (SQLiteException) { + } + } + catch (SQLiteException) { if (!noThrow) throw; - + } // No need to rollback if there are no transactions open. } @@ -1214,13 +1219,13 @@ void DoSavePointExecute (string savepoint, string cmd) // TODO: Mild race here, but inescapable without locking almost everywhere. if (0 <= depth && depth < _transactionDepth) { #if NETFX_CORE || USE_SQLITEPCL_RAW || NETCORE - Volatile.Write (ref _transactionDepth, depth); + Volatile.Write (ref _transactionDepth, depth); #elif SILVERLIGHT _transactionDepth = depth; #else Thread.VolatileWrite (ref _transactionDepth, depth); #endif - Execute (cmd + savepoint); + Execute (cmd + savepoint); return; } } @@ -1256,7 +1261,8 @@ public void RunInTransaction (Action action) var savePoint = SaveTransactionPoint (); action (); Release (savePoint); - } catch (Exception) { + } + catch (Exception) { Rollback (); throw; } @@ -1273,11 +1279,11 @@ public void RunInTransaction (Action action) /// /// The number of rows added to the table. /// - public int InsertAll (System.Collections.IEnumerable objects, bool runInTransaction=true) + public int InsertAll (System.Collections.IEnumerable objects, bool runInTransaction = true) { var c = 0; if (runInTransaction) { - RunInTransaction(() => { + RunInTransaction (() => { foreach (var r in objects) { c += Insert (r); } @@ -1306,7 +1312,7 @@ public int InsertAll (System.Collections.IEnumerable objects, bool runInTransact /// /// The number of rows added to the table. /// - public int InsertAll (System.Collections.IEnumerable objects, string extra, bool runInTransaction=true) + public int InsertAll (System.Collections.IEnumerable objects, string extra, bool runInTransaction = true) { var c = 0; if (runInTransaction) { @@ -1318,7 +1324,7 @@ public int InsertAll (System.Collections.IEnumerable objects, string extra, bool } else { foreach (var r in objects) { - c+= Insert (r); + c += Insert (r); } } return c; @@ -1339,7 +1345,7 @@ public int InsertAll (System.Collections.IEnumerable objects, string extra, bool /// /// The number of rows added to the table. /// - public int InsertAll (System.Collections.IEnumerable objects, Type objType, bool runInTransaction=true) + public int InsertAll (System.Collections.IEnumerable objects, Type objType, bool runInTransaction = true) { var c = 0; if (runInTransaction) { @@ -1356,7 +1362,7 @@ public int InsertAll (System.Collections.IEnumerable objects, Type objType, bool } return c; } - + /// /// Inserts the given object and retrieves its /// auto incremented primary key if it has one. @@ -1434,7 +1440,7 @@ 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. @@ -1456,44 +1462,44 @@ public int Insert (object obj, string extra) return Insert (obj, extra, Orm.GetType (obj)); } - /// - /// Inserts the given object and retrieves its - /// auto incremented primary key if it has one. - /// - /// - /// The object to insert. - /// - /// - /// Literal SQL code that gets placed into the command. INSERT {extra} INTO ... - /// - /// - /// The type of object to insert. - /// - /// - /// The number of rows added to the table. - /// - public int Insert (object obj, string extra, Type objType) + /// + /// Inserts the given object and retrieves its + /// auto incremented primary key if it has one. + /// + /// + /// The object to insert. + /// + /// + /// Literal SQL code that gets placed into the command. INSERT {extra} INTO ... + /// + /// + /// The type of object to insert. + /// + /// + /// The number of rows added to the table. + /// + public int Insert (object obj, string extra, Type objType) { if (obj == null || objType == null) { return 0; } - + var map = GetMapping (objType); if (map.PK != null && map.PK.IsAutoGuid) { if (map.PK.GetValue (obj).Equals (Guid.Empty)) { map.PK.SetValue (obj, Guid.NewGuid ()); } - } + } 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); + vals[i] = cols[i].GetValue (obj); } - + var insertCmd = map.GetInsertCommand (this, extra); int count; @@ -1501,8 +1507,9 @@ public int Insert (object obj, string extra, Type objType) // We lock here to protect the prepared statement returned via GetInsertCommand. // A SQLite prepared statement can be bound for only one operation at a time. try { - count = insertCmd.ExecuteNonQuery (vals); - } catch (SQLiteException ex) { + count = insertCmd.ExecuteNonQuery (vals); + } + catch (SQLiteException ex) { if (SQLite3.ExtendedErrCode (this.Handle) == SQLite3.ExtendedResult.ConstraintNotNull) { throw NotNullConstraintViolationException.New (ex.Result, ex.Message, map, obj); } @@ -1559,20 +1566,20 @@ 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; + where p != pk + select p; var vals = from c in cols - select c.GetValue (obj); + select c.GetValue (obj); var ps = new List (vals); if (ps.Count == 0) { // There is a PK but no accompanying data, @@ -1584,7 +1591,7 @@ public int Update (object obj, Type objType) } 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); + select "\"" + c.Name + "\" = ? ").ToArray ()), pk.Name); try { rowsAffected = Execute (q, ps.ToArray ()); @@ -1616,7 +1623,7 @@ public int Update (object obj, Type objType) /// /// The number of rows modified. /// - public int UpdateAll (System.Collections.IEnumerable objects, bool runInTransaction=true) + public int UpdateAll (System.Collections.IEnumerable objects, bool runInTransaction = true) { var c = 0; if (runInTransaction) { @@ -1743,16 +1750,16 @@ public int DeleteAll (TableMapping map) public void Dispose () { - Dispose(true); - GC.SuppressFinalize(this); + Dispose (true); + GC.SuppressFinalize (this); } - public void Close() + public void Close () { - Dispose(true); + Dispose (true); } - protected virtual void Dispose(bool disposing) + protected virtual void Dispose (bool disposing) { var useClose2 = LibVersionNumber >= 3007014; @@ -1760,19 +1767,19 @@ protected virtual void Dispose(bool disposing) try { if (disposing) { if (_mappings != null) { - foreach (var sqlInsertCommand in _mappings.Values){ - sqlInsertCommand.Dispose(); + foreach (var sqlInsertCommand in _mappings.Values) { + sqlInsertCommand.Dispose (); } } - var r = useClose2 ? SQLite3.Close2(Handle) : SQLite3.Close(Handle); - if (r != SQLite3.Result.OK) - { - string msg = SQLite3.GetErrmsg(Handle); - throw SQLiteException.New(r, msg); + var r = useClose2 ? SQLite3.Close2 (Handle) : SQLite3.Close (Handle); + if (r != SQLite3.Result.OK) { + string msg = SQLite3.GetErrmsg (Handle); + throw SQLiteException.New (r, msg); } - } else { - var r = useClose2 ? SQLite3.Close2(Handle) : SQLite3.Close(Handle); + } + else { + var r = useClose2 ? SQLite3.Close2 (Handle) : SQLite3.Close (Handle); } } finally { @@ -1800,7 +1807,7 @@ public class NotifyTableChangedEventArgs : EventArgs public NotifyTableChangedEventArgs (TableMapping table, NotifyTableChangedAction action) { Table = table; - Action = action; + Action = action; } } @@ -1818,8 +1825,8 @@ public class SQLiteConnectionString { public string ConnectionString { get; private set; } public string DatabasePath { get; private set; } - public bool StoreDateTimeAsTicks { get; private set; } - + public bool StoreDateTimeAsTicks { get; private set; } + #if NETFX_CORE static readonly string MetroStyleDataPath = Windows.Storage.ApplicationData.Current.LocalFolder.Path; @@ -1835,24 +1842,24 @@ public static bool IsInMemoryPath(string databasePath) } #endif - - public SQLiteConnectionString (string databasePath, bool storeDateTimeAsTicks) + + public SQLiteConnectionString (string databasePath, bool storeDateTimeAsTicks) { ConnectionString = databasePath; - StoreDateTimeAsTicks = storeDateTimeAsTicks; - + StoreDateTimeAsTicks = storeDateTimeAsTicks; + #if NETFX_CORE DatabasePath = IsInMemoryPath(databasePath) ? databasePath : System.IO.Path.Combine(MetroStyleDataPath, databasePath); #else - DatabasePath = databasePath; + DatabasePath = databasePath; #endif } } - [AttributeUsage (AttributeTargets.Class)] + [AttributeUsage (AttributeTargets.Class)] public class TableAttribute : Attribute { public string Name { get; set; } @@ -1890,12 +1897,12 @@ public class IndexedAttribute : Attribute public string Name { get; set; } public int Order { get; set; } public virtual bool Unique { get; set; } - - public IndexedAttribute() + + public IndexedAttribute () { } - - public IndexedAttribute(string name, int order) + + public IndexedAttribute (string name, int order) { Name = name; Order = order; @@ -1928,7 +1935,7 @@ public MaxLengthAttribute (int length) } [AttributeUsage (AttributeTargets.Property)] - public class CollationAttribute: Attribute + public class CollationAttribute : Attribute { public string Value { get; private set; } @@ -1941,14 +1948,14 @@ public CollationAttribute (string collation) [AttributeUsage (AttributeTargets.Property)] public class NotNullAttribute : Attribute { - } - - [AttributeUsage(AttributeTargets.Enum)] - public class StoreAsTextAttribute : Attribute - { - } - - public class TableMapping + } + + [AttributeUsage (AttributeTargets.Enum)] + public class StoreAsTextAttribute : Attribute + { + } + + public class TableMapping { public Type MappedType { get; private set; } @@ -1964,35 +1971,35 @@ public class TableMapping Column[] _insertColumns; Column[] _insertOrReplaceColumns; - public TableMapping(Type type, CreateFlags createFlags = CreateFlags.None) + public TableMapping (Type type, CreateFlags createFlags = CreateFlags.None) { MappedType = type; var typeInfo = type.GetTypeInfo (); var tableAttr = typeInfo.CustomAttributes - .Where(x => x.AttributeType == typeof(TableAttribute)) - .Select(x => (TableAttribute)Orm.InflateAttribute(x)) - .FirstOrDefault(); + .Where (x => x.AttributeType == typeof (TableAttribute)) + .Select (x => (TableAttribute)Orm.InflateAttribute (x)) + .FirstOrDefault (); - TableName = (tableAttr != null && !string.IsNullOrEmpty(tableAttr.Name)) ? tableAttr.Name : MappedType.Name; + TableName = (tableAttr != null && !string.IsNullOrEmpty (tableAttr.Name)) ? tableAttr.Name : MappedType.Name; var props = new List (); var baseType = type; var propNames = new HashSet (); - while (baseType != typeof(object)) { - var ti = baseType.GetTypeInfo(); + while (baseType != typeof (object)) { + var ti = baseType.GetTypeInfo (); var newProps = ( from p in ti.DeclaredProperties where - !propNames.Contains(p.Name) && + !propNames.Contains (p.Name) && p.CanRead && p.CanWrite && (p.GetMethod != null) && (p.SetMethod != null) && (p.GetMethod.IsPublic && p.SetMethod.IsPublic) && (!p.GetMethod.IsStatic) && (!p.SetMethod.IsStatic) - select p).ToList(); + select p).ToList (); foreach (var p in newProps) { - propNames.Add(p.Name); + propNames.Add (p.Name); } props.AddRange (newProps); baseType = ti.BaseType; @@ -2000,7 +2007,7 @@ from p in ti.DeclaredProperties var cols = new List (); foreach (var p in props) { - var ignore = p.IsDefined(typeof(IgnoreAttribute),true); + var ignore = p.IsDefined (typeof (IgnoreAttribute), true); if (!ignore) { cols.Add (new Column (p, createFlags)); } @@ -2014,7 +2021,7 @@ from p in ti.DeclaredProperties PK = c; } } - + HasAutoIncPK = _autoPk != null; if (PK != null) { @@ -2062,16 +2069,16 @@ public Column FindColumnWithPropertyName (string propertyName) public Column FindColumn (string columnName) { - var exact = Columns.FirstOrDefault (c => c.Name.ToLower() == columnName.ToLower()); + var exact = Columns.FirstOrDefault (c => c.Name.ToLower () == columnName.ToLower ()); return exact; } - ConcurrentStringDictionary _insertCommandMap; + ConcurrentStringDictionary _insertCommandMap; - public PreparedSqlLiteInsertCommand GetInsertCommand(SQLiteConnection conn, string extra) + public PreparedSqlLiteInsertCommand GetInsertCommand (SQLiteConnection conn, string extra) { object prepCmdO; - + if (!_insertCommandMap.TryGetValue (extra, out prepCmdO)) { var prepCmd = CreateInsertCommand (conn, extra); prepCmdO = prepCmd; @@ -2083,40 +2090,38 @@ public PreparedSqlLiteInsertCommand GetInsertCommand(SQLiteConnection conn, stri } return (PreparedSqlLiteInsertCommand)prepCmdO; } - - PreparedSqlLiteInsertCommand CreateInsertCommand(SQLiteConnection conn, string extra) + + PreparedSqlLiteInsertCommand CreateInsertCommand (SQLiteConnection conn, string extra) { var cols = InsertColumns; - string insertSql; - if (!cols.Any() && Columns.Count() == 1 && Columns[0].IsAutoInc) - { - insertSql = string.Format("insert {1} into \"{0}\" default values", TableName, extra); - } - else - { + string insertSql; + if (!cols.Any () && Columns.Count () == 1 && Columns[0].IsAutoInc) { + insertSql = string.Format ("insert {1} into \"{0}\" default values", TableName, extra); + } + else { var replacing = string.Compare (extra, "OR REPLACE", StringComparison.OrdinalIgnoreCase) == 0; if (replacing) { cols = InsertOrReplaceColumns; } - insertSql = string.Format("insert {3} into \"{0}\"({1}) values ({2})", TableName, - string.Join(",", (from c in cols - select "\"" + c.Name + "\"").ToArray()), - string.Join(",", (from c in cols - select "?").ToArray()), extra); - - } - - var insertCommand = new PreparedSqlLiteInsertCommand(conn); + 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() + + protected internal void Dispose () { foreach (var pair in _insertCommandMap) { - ((PreparedSqlLiteInsertCommand)pair.Value).Dispose (); + ((PreparedSqlLiteInsertCommand)pair.Value).Dispose (); } _insertCommandMap = null; } @@ -2135,8 +2140,8 @@ public class Column public string Collation { get; private set; } - public bool IsAutoInc { get; private set; } - public bool IsAutoGuid { get; private set; } + public bool IsAutoInc { get; private set; } + public bool IsAutoGuid { get; private set; } public bool IsPK { get; private set; } @@ -2146,56 +2151,53 @@ public class Column public int? MaxStringLength { get; private set; } - public bool StoreAsText { get; private set; } + public bool StoreAsText { get; private set; } - public Column(PropertyInfo prop, CreateFlags createFlags = CreateFlags.None) - { - var colAttr = prop.CustomAttributes.FirstOrDefault(x => x.AttributeType == typeof(ColumnAttribute)); + public Column (PropertyInfo prop, CreateFlags createFlags = CreateFlags.None) + { + var colAttr = prop.CustomAttributes.FirstOrDefault (x => x.AttributeType == typeof (ColumnAttribute)); - _prop = prop; + _prop = prop; Name = (colAttr != null && colAttr.ConstructorArguments.Count > 0) ? - colAttr.ConstructorArguments[0].Value?.ToString() : - prop.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); + colAttr.ConstructorArguments[0].Value?.ToString () : + prop.Name; + //If this type is Nullable then Nullable.GetUnderlyingType returns the T, otherwise it returns null, so get the actual type instead + ColumnType = Nullable.GetUnderlyingType (prop.PropertyType) ?? prop.PropertyType; + Collation = Orm.Collation (prop); - IsPK = Orm.IsPK(prop) || + IsPK = Orm.IsPK (prop) || (((createFlags & CreateFlags.ImplicitPK) == CreateFlags.ImplicitPK) && string.Compare (prop.Name, Orm.ImplicitPkName, StringComparison.OrdinalIgnoreCase) == 0); - var isAuto = Orm.IsAutoInc(prop) || (IsPK && ((createFlags & CreateFlags.AutoIncPK) == CreateFlags.AutoIncPK)); - IsAutoGuid = isAuto && ColumnType == typeof(Guid); - IsAutoInc = isAuto && !IsAutoGuid; - - Indices = Orm.GetIndices(prop); - if (!Indices.Any() - && !IsPK - && ((createFlags & CreateFlags.ImplicitIndex) == CreateFlags.ImplicitIndex) - && Name.EndsWith (Orm.ImplicitIndexSuffix, StringComparison.OrdinalIgnoreCase) - ) - { - Indices = new IndexedAttribute[] { new IndexedAttribute() }; - } - IsNullable = !(IsPK || Orm.IsMarkedNotNull(prop)); - MaxStringLength = Orm.MaxStringLength(prop); - - StoreAsText = prop.PropertyType.GetTypeInfo().CustomAttributes.Any(x => x.AttributeType == typeof(StoreAsTextAttribute)); - } - - public void SetValue(object obj, object val) - { - if (val != null && ColumnType.GetTypeInfo().IsEnum) - { - _prop.SetValue(obj, Enum.ToObject(ColumnType, val)); - } - else - { - _prop.SetValue(obj, val, null); - } - } - - public object GetValue (object obj) + 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); + + StoreAsText = prop.PropertyType.GetTypeInfo ().CustomAttributes.Any (x => x.AttributeType == typeof (StoreAsTextAttribute)); + } + + public void SetValue (object obj, object val) + { + if (val != null && ColumnType.GetTypeInfo ().IsEnum) { + _prop.SetValue (obj, Enum.ToObject (ColumnType, val)); + } + else { + _prop.SetValue (obj, val, null); + } + } + + public object GetValue (object obj) { return _prop.GetValue (obj, null); } @@ -2206,19 +2208,18 @@ class EnumCacheInfo { public EnumCacheInfo (Type type) { - var typeInfo = type.GetTypeInfo(); + var typeInfo = type.GetTypeInfo (); IsEnum = typeInfo.IsEnum; - if (IsEnum) - { - StoreAsText = typeInfo.CustomAttributes.Any(x => x.AttributeType == typeof(StoreAsTextAttribute)); + if (IsEnum) { + StoreAsText = typeInfo.CustomAttributes.Any (x => x.AttributeType == typeof (StoreAsTextAttribute)); if (StoreAsText) { - EnumValues = Enum.GetValues(type).Cast().ToDictionary(Convert.ToInt32, x => x.ToString()); + EnumValues = Enum.GetValues (type).Cast ().ToDictionary (Convert.ToInt32, x => x.ToString ()); } else { - EnumValues = Enum.GetValues(type).Cast().ToDictionary(Convert.ToInt32, x => Convert.ToInt32(x).ToString()); + EnumValues = Enum.GetValues (type).Cast ().ToDictionary (Convert.ToInt32, x => Convert.ToInt32 (x).ToString ()); } } } @@ -2232,21 +2233,19 @@ public EnumCacheInfo (Type type) static class EnumCache { - static readonly Dictionary Cache = new Dictionary(); + static readonly Dictionary Cache = new Dictionary (); public static EnumCacheInfo GetInfo () { - return GetInfo(typeof(T)); + return GetInfo (typeof (T)); } public static EnumCacheInfo GetInfo (Type type) { - lock (Cache) - { + lock (Cache) { EnumCacheInfo info = null; - if (!Cache.TryGetValue(type, out info)) - { - info = new EnumCacheInfo(type); + if (!Cache.TryGetValue (type, out info)) { + info = new EnumCacheInfo (type); Cache[type] = info; } @@ -2264,17 +2263,17 @@ public static class Orm public static Type GetType (object obj) { if (obj == null) - return typeof(object); + return typeof (object); var rt = obj as IReflectableType; if (rt != null) - return rt.GetTypeInfo().AsType(); - return obj.GetType(); + return rt.GetTypeInfo ().AsType (); + return obj.GetType (); } public static string SqlDecl (TableMapping.Column p, bool storeDateTimeAsTicks) { string decl = "\"" + p.Name + "\" " + SqlType (p, storeDateTimeAsTicks) + " "; - + if (p.IsPK) { decl += "primary key "; } @@ -2287,124 +2286,130 @@ public static string SqlDecl (TableMapping.Column p, bool storeDateTimeAsTicks) 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) || clrType == typeof(UInt32) || clrType == typeof(Int64)) - { + if (clrType == typeof (Boolean) || clrType == typeof (Byte) || clrType == typeof (UInt16) || clrType == typeof (SByte) || clrType == typeof (Int16) || clrType == typeof (Int32) || clrType == typeof (UInt32) || clrType == typeof (Int64)) { return "integer"; - } else if (clrType == typeof(Single) || clrType == typeof(Double) || clrType == typeof(Decimal)) { + } + else if (clrType == typeof (Single) || clrType == typeof (Double) || clrType == typeof (Decimal)) { return "float"; - } else if (clrType == typeof(String)) { + } + 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)) { + } + else if (clrType == typeof (TimeSpan)) { + return "bigint"; + } + else if (clrType == typeof (DateTime)) { return storeDateTimeAsTicks ? "bigint" : "datetime"; - } else if (clrType == typeof(DateTimeOffset)) { + } + else if (clrType == typeof (DateTimeOffset)) { return "bigint"; - } else if (clrType.GetTypeInfo().IsEnum) { - if (p.StoreAsText) - return "varchar"; - else - return "integer"; - } else if (clrType == typeof(byte[])) { + } + else if (clrType.GetTypeInfo ().IsEnum) { + if (p.StoreAsText) + return "varchar"; + else + return "integer"; + } + else if (clrType == typeof (byte[])) { return "blob"; - } else if (clrType == typeof(Guid)) { - return "varchar(36)"; - } else { + } + else if (clrType == typeof (Guid)) { + return "varchar(36)"; + } + else { throw new NotSupportedException ("Don't know about " + clrType); } } public static bool IsPK (MemberInfo p) { - return p.CustomAttributes.Any(x => x.AttributeType == typeof(PrimaryKeyAttribute)); + return p.CustomAttributes.Any (x => x.AttributeType == typeof (PrimaryKeyAttribute)); } public static string Collation (MemberInfo p) { return (p.CustomAttributes - .Where(x => typeof(CollationAttribute) == x.AttributeType) - .Select(x => - { - var args = x.ConstructorArguments; - return args.Count > 0 ? ((args[0].Value as string) ?? "") : ""; + .Where (x => typeof (CollationAttribute) == x.AttributeType) + .Select (x => { + var args = x.ConstructorArguments; + return args.Count > 0 ? ((args[0].Value as string) ?? "") : ""; }) - .FirstOrDefault()) ?? ""; + .FirstOrDefault ()) ?? ""; } public static bool IsAutoInc (MemberInfo p) { - return p.CustomAttributes.Any(x => x.AttributeType == typeof(AutoIncrementAttribute)); + return p.CustomAttributes.Any (x => x.AttributeType == typeof (AutoIncrementAttribute)); } public static FieldInfo GetField (TypeInfo t, string name) { - var f = t.GetDeclaredField(name); + var f = t.GetDeclaredField (name); if (f != null) return f; - return GetField(t.BaseType.GetTypeInfo(), name); + return GetField (t.BaseType.GetTypeInfo (), name); } public static PropertyInfo GetProperty (TypeInfo t, string name) { - var f = t.GetDeclaredProperty(name); + var f = t.GetDeclaredProperty (name); if (f != null) return f; - return GetProperty(t.BaseType.GetTypeInfo(), name); + return GetProperty (t.BaseType.GetTypeInfo (), name); } public static object InflateAttribute (CustomAttributeData x) { var atype = x.AttributeType; - var typeInfo = atype.GetTypeInfo(); - var args = x.ConstructorArguments.Select(a => a.Value).ToArray(); - var r = Activator.CreateInstance(x.AttributeType, args); + var typeInfo = atype.GetTypeInfo (); + var args = x.ConstructorArguments.Select (a => a.Value).ToArray (); + var r = Activator.CreateInstance (x.AttributeType, args); foreach (var arg in x.NamedArguments) { if (arg.IsField) { - GetField(typeInfo, arg.MemberName).SetValue(r, arg.TypedValue.Value); + GetField (typeInfo, arg.MemberName).SetValue (r, arg.TypedValue.Value); } else { - GetProperty(typeInfo, arg.MemberName).SetValue(r, arg.TypedValue.Value); + GetProperty (typeInfo, arg.MemberName).SetValue (r, arg.TypedValue.Value); } } return r; } - public static IEnumerable GetIndices(MemberInfo p) + public static IEnumerable GetIndices (MemberInfo p) { - var indexedInfo = typeof(IndexedAttribute).GetTypeInfo(); + var indexedInfo = typeof (IndexedAttribute).GetTypeInfo (); return p.CustomAttributes - .Where(x => indexedInfo.IsAssignableFrom(x.AttributeType.GetTypeInfo())) - .Select(x => (IndexedAttribute)InflateAttribute(x)); + .Where (x => indexedInfo.IsAssignableFrom (x.AttributeType.GetTypeInfo ())) + .Select (x => (IndexedAttribute)InflateAttribute (x)); } - - public static int? MaxStringLength(PropertyInfo p) + + public static int? MaxStringLength (PropertyInfo p) { - var attr = p.CustomAttributes.FirstOrDefault(x => x.AttributeType == typeof(MaxLengthAttribute)); - if (attr != null) - { - var attrv = (MaxLengthAttribute)InflateAttribute(attr); + var attr = p.CustomAttributes.FirstOrDefault (x => x.AttributeType == typeof (MaxLengthAttribute)); + if (attr != null) { + var attrv = (MaxLengthAttribute)InflateAttribute (attr); return attrv.Value; } return null; } - public static bool IsMarkedNotNull(MemberInfo p) + public static bool IsMarkedNotNull (MemberInfo p) { - return p.CustomAttributes.Any(x => x.AttributeType == typeof(NotNullAttribute)); + return p.CustomAttributes.Any (x => x.AttributeType == typeof (NotNullAttribute)); } } @@ -2427,7 +2432,7 @@ public int ExecuteNonQuery () if (_conn.Trace) { _conn.Tracer?.Invoke ("Executing: " + this); } - + var r = SQLite3.Result.OK; var stmt = Prepare (); r = SQLite3.Step (stmt); @@ -2435,7 +2440,8 @@ public int ExecuteNonQuery () if (r == SQLite3.Result.Done) { int rowsAffected = SQLite3.Changes (_conn.Handle); return rowsAffected; - } else if (r == SQLite3.Result.Error) { + } + else if (r == SQLite3.Result.Error) { string msg = SQLite3.GetErrmsg (_conn.Handle); throw SQLiteException.New (r, msg); } @@ -2445,22 +2451,22 @@ public int ExecuteNonQuery () } } - throw SQLiteException.New(r, r.ToString()); + throw SQLiteException.New (r, r.ToString ()); } public IEnumerable ExecuteDeferredQuery () { - return ExecuteDeferredQuery(_conn.GetMapping(typeof(T))); + return ExecuteDeferredQuery (_conn.GetMapping (typeof (T))); } public List ExecuteQuery () { - return ExecuteDeferredQuery(_conn.GetMapping(typeof(T))).ToList(); + return ExecuteDeferredQuery (_conn.GetMapping (typeof (T))).ToList (); } public List ExecuteQuery (TableMapping map) { - return ExecuteDeferredQuery(map).ToList(); + return ExecuteDeferredQuery (map).ToList (); } /// @@ -2487,31 +2493,29 @@ public IEnumerable ExecuteDeferredQuery (TableMapping map) } var stmt = Prepare (); - try - { + 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); + cols[i] = map.FindColumn (name); } - + while (SQLite3.Step (stmt) == SQLite3.Result.Row) { - var obj = Activator.CreateInstance(map.MappedType); + var obj = Activator.CreateInstance (map.MappedType); for (int i = 0; i < cols.Length; i++) { - if (cols [i] == null) + 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); - } + var val = ReadCol (stmt, i, colType, cols[i].ColumnType); + cols[i].SetValue (obj, val); + } OnInstanceCreated (obj); yield return (T)obj; } } - finally - { - SQLite3.Finalize(stmt); + finally { + SQLite3.Finalize (stmt); } } @@ -2520,30 +2524,27 @@ public T ExecuteScalar () if (_conn.Trace) { _conn.Tracer.Invoke ("Executing Query: " + this); } - - T val = default(T); - + + 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); - } - + 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; } @@ -2563,16 +2564,16 @@ public void Bind (object val) public override string ToString () { var parts = new string[1 + _bindings.Count]; - parts [0] = CommandText; + parts[0] = CommandText; var i = 1; foreach (var b in _bindings) { - parts [i] = string.Format (" {0}: {1}", i - 1, b.Value); + parts[i] = string.Format (" {0}: {1}", i - 1, b.Value); i++; } return string.Join (Environment.NewLine, parts); } - Sqlite3Statement Prepare() + Sqlite3Statement Prepare () { var stmt = SQLite3.Prepare2 (_conn.Handle, CommandText); BindAll (stmt); @@ -2590,10 +2591,11 @@ void BindAll (Sqlite3Statement stmt) foreach (var b in _bindings) { if (b.Name != null) { b.Index = SQLite3.BindParameterIndex (stmt, b.Name); - } else { + } + else { b.Index = nextIdx++; } - + BindParameter (stmt, b.Index, b.Value, _conn.StoreDateTimeAsTicks); } } @@ -2606,47 +2608,60 @@ internal static void BindParameter (Sqlite3Statement stmt, int index, object val { if (value == null) { SQLite3.BindNull (stmt, index); - } else { + } + else { if (value is Int32) { SQLite3.BindInt (stmt, index, (int)value); - } else if (value is String) { + } + 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) { + } + 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) { + } + else if (value is Boolean) { SQLite3.BindInt (stmt, index, (bool)value ? 1 : 0); - } else if (value is UInt32 || value is Int64) { + } + 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) { + } + else if (value is Single || value is Double || value is Decimal) { SQLite3.BindDouble (stmt, index, Convert.ToDouble (value)); - } else if (value is TimeSpan) { - SQLite3.BindInt64(stmt, index, ((TimeSpan)value).Ticks); - } else if (value is DateTime) { + } + else if (value is TimeSpan) { + SQLite3.BindInt64 (stmt, index, ((TimeSpan)value).Ticks); + } + else if (value is DateTime) { if (storeDateTimeAsTicks) { SQLite3.BindInt64 (stmt, index, ((DateTime)value).Ticks); } else { SQLite3.BindText (stmt, index, ((DateTime)value).ToString (DateTimeExactStoreFormat, System.Globalization.CultureInfo.InvariantCulture), -1, NegativePointer); } - } else if (value is DateTimeOffset) { + } + else if (value is DateTimeOffset) { SQLite3.BindInt64 (stmt, index, ((DateTimeOffset)value).UtcTicks); - } else if (value is byte[]) { - SQLite3.BindBlob(stmt, index, (byte[])value, ((byte[])value).Length, NegativePointer); - } else if (value is Guid) { - SQLite3.BindText(stmt, index, ((Guid)value).ToString(), 72, NegativePointer); - } else { - // Now we could possibly get an enum, retrieve cached info - var valueType = value.GetType(); - var enumInfo = EnumCache.GetInfo(valueType); - if (enumInfo.IsEnum) { - var enumIntValue = Convert.ToInt32(value); - if (enumInfo.StoreAsText) - SQLite3.BindText(stmt, index, enumInfo.EnumValues[enumIntValue], -1, NegativePointer); - else - SQLite3.BindInt(stmt, index, enumIntValue); - } else { - throw new NotSupportedException("Cannot store type: " + Orm.GetType(value)); - } + } + else if (value is byte[]) { + SQLite3.BindBlob (stmt, index, (byte[])value, ((byte[])value).Length, NegativePointer); + } + else if (value is Guid) { + SQLite3.BindText (stmt, index, ((Guid)value).ToString (), 72, NegativePointer); + } + else { + // Now we could possibly get an enum, retrieve cached info + var valueType = value.GetType (); + var enumInfo = EnumCache.GetInfo (valueType); + if (enumInfo.IsEnum) { + var enumIntValue = Convert.ToInt32 (value); + if (enumInfo.StoreAsText) + SQLite3.BindText (stmt, index, enumInfo.EnumValues[enumIntValue], -1, NegativePointer); + else + SQLite3.BindInt (stmt, index, enumIntValue); + } + else { + throw new NotSupportedException ("Cannot store type: " + Orm.GetType (value)); + } } } } @@ -2664,21 +2679,28 @@ object ReadCol (Sqlite3Statement stmt, int index, SQLite3.ColType type, Type clr { if (type == SQLite3.ColType.Null) { return null; - } else { - var clrTypeInfo = clrType.GetTypeInfo(); - if (clrType == typeof(String)) { + } + else { + var clrTypeInfo = clrType.GetTypeInfo (); + if (clrType == typeof (String)) { return SQLite3.ColumnString (stmt, index); - } else if (clrType == typeof(Int32)) { + } + else if (clrType == typeof (Int32)) { return (int)SQLite3.ColumnInt (stmt, index); - } else if (clrType == typeof(Boolean)) { + } + else if (clrType == typeof (Boolean)) { return SQLite3.ColumnInt (stmt, index) == 1; - } else if (clrType == typeof(double)) { + } + else if (clrType == typeof (double)) { return SQLite3.ColumnDouble (stmt, index); - } else if (clrType == typeof(float)) { + } + 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)) { + } + 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)); } @@ -2690,36 +2712,47 @@ object ReadCol (Sqlite3Statement stmt, int index, SQLite3.ColType type, Type clr } return resultDate; } - } else if (clrType == typeof(DateTimeOffset)) { - return new DateTimeOffset(SQLite3.ColumnInt64 (stmt, index),TimeSpan.Zero); - } else if (clrTypeInfo.IsEnum) { - if (type == SQLite3.ColType.Text) - { - var value = SQLite3.ColumnString(stmt, index); - return Enum.Parse(clrType, value.ToString(), true); - } - else - return SQLite3.ColumnInt (stmt, index); - } else if (clrType == typeof(Int64)) { + } + else if (clrType == typeof (DateTimeOffset)) { + return new DateTimeOffset (SQLite3.ColumnInt64 (stmt, index), TimeSpan.Zero); + } + else if (clrTypeInfo.IsEnum) { + if (type == SQLite3.ColType.Text) { + var value = SQLite3.ColumnString (stmt, index); + return Enum.Parse (clrType, value.ToString (), true); + } + else + return SQLite3.ColumnInt (stmt, index); + } + else if (clrType == typeof (Int64)) { return SQLite3.ColumnInt64 (stmt, index); - } else if (clrType == typeof(UInt32)) { + } + else if (clrType == typeof (UInt32)) { return (uint)SQLite3.ColumnInt64 (stmt, index); - } else if (clrType == typeof(decimal)) { + } + else if (clrType == typeof (decimal)) { return (decimal)SQLite3.ColumnDouble (stmt, index); - } else if (clrType == typeof(Byte)) { + } + else if (clrType == typeof (Byte)) { return (byte)SQLite3.ColumnInt (stmt, index); - } else if (clrType == typeof(UInt16)) { + } + else if (clrType == typeof (UInt16)) { return (ushort)SQLite3.ColumnInt (stmt, index); - } else if (clrType == typeof(Int16)) { + } + else if (clrType == typeof (Int16)) { return (short)SQLite3.ColumnInt (stmt, index); - } else if (clrType == typeof(sbyte)) { + } + else if (clrType == typeof (sbyte)) { return (sbyte)SQLite3.ColumnInt (stmt, index); - } else if (clrType == typeof(byte[])) { + } + 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{ + } + 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); } } @@ -2738,7 +2771,7 @@ public class PreparedSqlLiteInsertCommand : IDisposable public string CommandText { get; set; } protected Sqlite3Statement Statement { get; set; } - internal static readonly Sqlite3Statement NullStatement = default(Sqlite3Statement); + internal static readonly Sqlite3Statement NullStatement = default (Sqlite3Statement); internal PreparedSqlLiteInsertCommand (SQLiteConnection conn) { @@ -2761,7 +2794,7 @@ public int ExecuteNonQuery (object[] source) //bind the values. if (source != null) { for (int i = 0; i < source.Length; i++) { - SQLiteCommand.BindParameter (Statement, i + 1, source [i], Connection.StoreDateTimeAsTicks); + SQLiteCommand.BindParameter (Statement, i + 1, source[i], Connection.StoreDateTimeAsTicks); } } r = SQLite3.Step (Statement); @@ -2770,14 +2803,17 @@ public int ExecuteNonQuery (object[] source) int rowsAffected = SQLite3.Changes (Connection.Handle); SQLite3.Reset (Statement); return rowsAffected; - } else if (r == SQLite3.Result.Error) { + } + 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) { + } + 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 { + } + else { SQLite3.Reset (Statement); throw SQLiteException.New (r, r.ToString ()); } @@ -2800,7 +2836,8 @@ private void Dispose (bool disposing) if (Statement != NullStatement) { try { SQLite3.Finalize (Statement); - } finally { + } + finally { Statement = NullStatement; Connection = null; } @@ -2848,7 +2885,7 @@ public class TableQuery : BaseTableQuery, IEnumerable BaseTableQuery _joinOuter; Expression _joinOuterKeySelector; Expression _joinSelector; - + Expression _selector; TableQuery (SQLiteConnection conn, TableMapping table) @@ -2860,7 +2897,7 @@ public class TableQuery : BaseTableQuery, IEnumerable public TableQuery (SQLiteConnection conn) { Connection = conn; - Table = Connection.GetMapping (typeof(T)); + Table = Connection.GetMapping (typeof (T)); } public TableQuery Clone () @@ -2890,12 +2927,13 @@ public TableQuery Where (Expression> predExpr) var q = Clone (); q.AddWhere (pred); return q; - } else { + } + else { throw new NotSupportedException ("Must be a predicate"); } } - public int Delete(Expression> predExpr) + public int Delete (Expression> predExpr) { if (predExpr.NodeType == ExpressionType.Lambda) { var lambda = (LambdaExpression)predExpr; @@ -2906,9 +2944,10 @@ public int Delete(Expression> predExpr) cmdText += " where " + w.CommandText; var command = Connection.CreateCommand (cmdText, args.ToArray ()); - int result = command.ExecuteNonQuery(); + int result = command.ExecuteNonQuery (); return result; - } else { + } + else { throw new NotSupportedException ("Must be a predicate"); } } @@ -2950,23 +2989,23 @@ public TableQuery OrderByDescending (Expression> orderExpr) return AddOrderBy (orderExpr, false); } - public TableQuery ThenBy(Expression> orderExpr) + public TableQuery ThenBy (Expression> orderExpr) { - return AddOrderBy(orderExpr, true); + return AddOrderBy (orderExpr, true); } - public TableQuery ThenByDescending(Expression> orderExpr) + public TableQuery ThenByDescending (Expression> orderExpr) { - return AddOrderBy(orderExpr, false); + 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; @@ -2974,21 +3013,23 @@ private TableQuery AddOrderBy (Expression> orderExpr, bool asc) 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, + ColumnName = Table.FindColumnWithPropertyName (mem.Member.Name).Name, Ascending = asc }); return q; - } else { + } + else { throw new NotSupportedException ("Order By does not support: " + orderExpr); } - } else { + } + else { throw new NotSupportedException ("Must be a predicate"); } } @@ -2997,11 +3038,12 @@ private void AddWhere (Expression pred) { if (_where == null) { _where = pred; - } else { + } + else { _where = Expression.AndAlso (_where, pred); } } - + public TableQuery Join ( TableQuery inner, Expression> outerKeySelector, @@ -3067,16 +3109,17 @@ private CompileResult CompileExpr (Expression expr, List queryArgs) { if (expr == null) { throw new NotSupportedException ("Expression is NULL"); - } else if (expr is BinaryExpression) { + } + else if (expr is BinaryExpression) { var bin = (BinaryExpression)expr; // VB turns 'x=="foo"' into 'CompareString(x,"foo",true/false)==0', so we need to unwrap it // http://blogs.msdn.com/b/vbteam/archive/2007/09/18/vb-expression-trees-string-comparisons.aspx - if (bin.Left.NodeType == ExpressionType.Call) { + if (bin.Left.NodeType == ExpressionType.Call) { var call = (MethodCallExpression)bin.Left; if (call.Method.DeclaringType.FullName == "Microsoft.VisualBasic.CompilerServices.Operators" && call.Method.Name == "CompareString") - bin = Expression.MakeBinary(bin.NodeType, call.Arguments[0], call.Arguments[1]); + bin = Expression.MakeBinary (bin.NodeType, call.Arguments[0], call.Arguments[1]); } @@ -3086,106 +3129,107 @@ private CompileResult CompileExpr (Expression expr, List 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); + text = CompileNullBinaryExpression (bin, rightr); else if (rightr.CommandText == "?" && rightr.Value == null) - text = CompileNullBinaryExpression(bin, leftr); + text = CompileNullBinaryExpression (bin, leftr); else - text = "(" + leftr.CommandText + " " + GetSqlName(bin) + " " + rightr.CommandText + ")"; + text = "(" + leftr.CommandText + " " + GetSqlName (bin) + " " + rightr.CommandText + ")"; return new CompileResult { CommandText = text }; - } else if (expr.NodeType == ExpressionType.Not) { - var operandExpr = ((UnaryExpression)expr).Operand; - var opr = CompileExpr(operandExpr, queryArgs); - object val = opr.Value; - if (val is bool) - val = !((bool) val); - return new CompileResult - { - CommandText = "NOT(" + opr.CommandText + ")", - Value = val - }; - } else if (expr.NodeType == ExpressionType.Call) { - + } + else if (expr.NodeType == ExpressionType.Not) { + var operandExpr = ((UnaryExpression)expr).Operand; + var opr = CompileExpr (operandExpr, queryArgs); + object val = opr.Value; + if (val is bool) + val = !((bool)val); + return new CompileResult { + CommandText = "NOT(" + opr.CommandText + ")", + Value = val + }; + } + 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); + 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 + ")"; + 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 + ")"; + 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 = "( instr(" + obj.CommandText + "," + args [0].CommandText + ") >0 )"; + if (call.Object != null && call.Object.Type == typeof (string)) { + sqlCall = "( instr(" + obj.CommandText + "," + args[0].CommandText + ") >0 )"; } else { - sqlCall = "(" + args [0].CommandText + " in " + obj.CommandText + ")"; + sqlCall = "(" + args[0].CommandText + " in " + obj.CommandText + ")"; } } - else if (call.Method.Name == "StartsWith" && args.Length >= 1) - { - var startsWithCmpOp = StringComparison.CurrentCulture; - if (args.Length == 2) - { - startsWithCmpOp = (StringComparison) args[1].Value; - } - switch (startsWithCmpOp) - { - case StringComparison.Ordinal: - case StringComparison.CurrentCulture: - sqlCall = "( substr(" + obj.CommandText + ", 1, " + args[0].Value.ToString().Length + ") = " + args[0].CommandText + ")"; - break; - case StringComparison.OrdinalIgnoreCase: - case StringComparison.CurrentCultureIgnoreCase: - sqlCall = "(" + obj.CommandText + " like (" + args[0].CommandText + " || '%'))"; - break; - } - - } - else if (call.Method.Name == "EndsWith" && args.Length >= 1) { - var endsWithCmpOp = StringComparison.CurrentCulture; - if (args.Length == 2) - { - endsWithCmpOp = (StringComparison)args[1].Value; - } - switch (endsWithCmpOp) - { - case StringComparison.Ordinal: - case StringComparison.CurrentCulture: - sqlCall = "( substr(" + obj.CommandText + ", length(" + obj.CommandText + ") - "+args[0].Value.ToString().Length+ "+1, " + args[0].Value.ToString().Length + ") = " + args[0].CommandText + ")"; - break; - case StringComparison.OrdinalIgnoreCase: - case StringComparison.CurrentCultureIgnoreCase: - sqlCall = "(" + obj.CommandText + " like ('%' || " + args[0].CommandText + "))"; - break; - } + else if (call.Method.Name == "StartsWith" && args.Length >= 1) { + var startsWithCmpOp = StringComparison.CurrentCulture; + if (args.Length == 2) { + startsWithCmpOp = (StringComparison)args[1].Value; + } + switch (startsWithCmpOp) { + case StringComparison.Ordinal: + case StringComparison.CurrentCulture: + sqlCall = "( substr(" + obj.CommandText + ", 1, " + args[0].Value.ToString ().Length + ") = " + args[0].CommandText + ")"; + break; + case StringComparison.OrdinalIgnoreCase: + case StringComparison.CurrentCultureIgnoreCase: + sqlCall = "(" + obj.CommandText + " like (" + args[0].CommandText + " || '%'))"; + break; + } + + } + else if (call.Method.Name == "EndsWith" && args.Length >= 1) { + var endsWithCmpOp = StringComparison.CurrentCulture; + if (args.Length == 2) { + endsWithCmpOp = (StringComparison)args[1].Value; + } + switch (endsWithCmpOp) { + case StringComparison.Ordinal: + case StringComparison.CurrentCulture: + sqlCall = "( substr(" + obj.CommandText + ", length(" + obj.CommandText + ") - " + args[0].Value.ToString ().Length + "+1, " + args[0].Value.ToString ().Length + ") = " + args[0].CommandText + ")"; + break; + case StringComparison.OrdinalIgnoreCase: + case StringComparison.CurrentCultureIgnoreCase: + sqlCall = "(" + obj.CommandText + " like ('%' || " + args[0].CommandText + "))"; + break; + } } 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 { + } + 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) { + + } + 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) { + } + else if (expr.NodeType == ExpressionType.Convert) { var u = (UnaryExpression)expr; var ty = u.Type; var valr = CompileExpr (u.Operand, queryArgs); @@ -3193,7 +3237,8 @@ private CompileResult CompileExpr (Expression expr, List queryArgs) CommandText = valr.CommandText, Value = valr.Value != null ? ConvertTo (valr.Value, ty) : null }; - } else if (expr.NodeType == ExpressionType.MemberAccess) { + } + else if (expr.NodeType == ExpressionType.MemberAccess) { var mem = (MemberExpression)expr; var paramExpr = mem.Expression as ParameterExpression; @@ -3203,7 +3248,7 @@ private CompileResult CompileExpr (Expression expr, List queryArgs) paramExpr = convert.Operand as ParameterExpression; } } - + if (paramExpr != null) { // // This is a column of our table, output just the column name @@ -3211,7 +3256,8 @@ private CompileResult CompileExpr (Expression expr, List queryArgs) // var columnName = Table.FindColumnWithPropertyName (mem.Member.Name).Name; return new CompileResult { CommandText = "\"" + columnName + "\"" }; - } else { + } + else { object obj = null; if (mem.Expression != null) { var r = CompileExpr (mem.Expression, queryArgs); @@ -3223,38 +3269,40 @@ private CompileResult CompileExpr (Expression expr, List queryArgs) } obj = r.Value; } - + // // Get the member value // object val = null; - + if (mem.Member is PropertyInfo) { var m = (PropertyInfo)mem.Member; val = m.GetValue (obj, null); - } else if (mem.Member is FieldInfo) { + } + else if (mem.Member is FieldInfo) { var m = (FieldInfo)mem.Member; val = m.GetValue (obj); - } else { - throw new NotSupportedException ("MemberExpr: " + mem.Member.GetType()); } - + else { + throw new NotSupportedException ("MemberExpr: " + mem.Member.GetType ()); + } + // // 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 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("?"); + queryArgs.Add (a); + sb.Append (head); + sb.Append ("?"); head = ","; } - sb.Append(")"); + sb.Append (")"); return new CompileResult { - CommandText = sb.ToString(), + CommandText = sb.ToString (), Value = val }; } @@ -3272,12 +3320,13 @@ private CompileResult CompileExpr (Expression expr, List queryArgs) static object ConvertTo (object obj, Type t) { - Type nut = Nullable.GetUnderlyingType(t); - + Type nut = Nullable.GetUnderlyingType (t); + if (nut != null) { - if (obj == null) return null; + if (obj == null) return null; return Convert.ChangeType (obj, nut); - } else { + } + else { return Convert.ChangeType (obj, t); } } @@ -3287,46 +3336,56 @@ static object ConvertTo (object obj, Type t) /// /// The expression to compile /// The non-null parameter - private string CompileNullBinaryExpression(BinaryExpression expression, CompileResult 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()); + throw new NotSupportedException ("Cannot compile Null-BinaryExpression with type " + expression.NodeType.ToString ()); } string GetSqlName (Expression expr) { var n = expr.NodeType; if (n == ExpressionType.GreaterThan) - return ">"; else if (n == ExpressionType.GreaterThanOrEqual) { + return ">"; + else if (n == ExpressionType.GreaterThanOrEqual) { return ">="; - } else if (n == ExpressionType.LessThan) { + } + else if (n == ExpressionType.LessThan) { return "<"; - } else if (n == ExpressionType.LessThanOrEqual) { + } + else if (n == ExpressionType.LessThanOrEqual) { return "<="; - } else if (n == ExpressionType.And) { + } + else if (n == ExpressionType.And) { return "&"; - } else if (n == ExpressionType.AndAlso) { + } + else if (n == ExpressionType.AndAlso) { return "and"; - } else if (n == ExpressionType.Or) { + } + else if (n == ExpressionType.Or) { return "|"; - } else if (n == ExpressionType.OrElse) { + } + else if (n == ExpressionType.OrElse) { return "or"; - } else if (n == ExpressionType.Equal) { + } + else if (n == ExpressionType.Equal) { return "="; - } else if (n == ExpressionType.NotEqual) { + } + else if (n == ExpressionType.NotEqual) { return "!="; - } else { + } + else { throw new NotSupportedException ("Cannot get SQL for: " + n); } } - + public int Count () { - return GenerateCommand("count(*)").ExecuteScalar (); + return GenerateCommand ("count(*)").ExecuteScalar (); } public int Count (Expression> predExpr) @@ -3337,9 +3396,9 @@ public int Count (Expression> predExpr) public IEnumerator GetEnumerator () { if (!_deferred) - return GenerateCommand("*").ExecuteQuery().GetEnumerator(); + return GenerateCommand ("*").ExecuteQuery ().GetEnumerator (); - return GenerateCommand("*").ExecuteDeferredQuery().GetEnumerator(); + return GenerateCommand ("*").ExecuteDeferredQuery ().GetEnumerator (); } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator () @@ -3350,13 +3409,13 @@ System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator () public T First () { var query = Take (1); - return query.ToList().First (); + return query.ToList ().First (); } public T FirstOrDefault () { var query = Take (1); - return query.ToList().FirstOrDefault (); + return query.ToList ().FirstOrDefault (); } public T First (Expression> predExpr) @@ -3368,7 +3427,7 @@ public T FirstOrDefault (Expression> predExpr) { return Where (predExpr).FirstOrDefault (); } - } + } public static class SQLite3 { @@ -3455,7 +3514,7 @@ public enum ExtendedResult : int NoticeRecoverWAL = (Result.Notice | (1 << 8)), NoticeRecoverRollback = (Result.Notice | (2 << 8)) } - + public enum ConfigOption : int { @@ -3630,211 +3689,209 @@ public static byte[] ColumnByteArray (IntPtr stmt, int index) [DllImport (LibraryPath, EntryPoint = "sqlite3_libversion_number", CallingConvention = CallingConvention.Cdecl)] public static extern int LibVersionNumber (); #else - public static Result Open(string filename, out Sqlite3DatabaseHandle db) + public static Result Open (string filename, out Sqlite3DatabaseHandle db) { - return (Result) Sqlite3.sqlite3_open(filename, out db); + return (Result)Sqlite3.sqlite3_open (filename, out db); } - public static Result Open(string filename, out Sqlite3DatabaseHandle db, int flags, IntPtr zVfs) + public static Result Open (string filename, out Sqlite3DatabaseHandle db, int flags, IntPtr zVfs) { #if USE_WP8_NATIVE_SQLITE return (Result)Sqlite3.sqlite3_open_v2(filename, out db, flags, ""); #else - return (Result)Sqlite3.sqlite3_open_v2(filename, out db, flags, null); + return (Result)Sqlite3.sqlite3_open_v2 (filename, out db, flags, null); #endif } - public static Result Close(Sqlite3DatabaseHandle db) + public static Result Close (Sqlite3DatabaseHandle db) { - return (Result)Sqlite3.sqlite3_close(db); + return (Result)Sqlite3.sqlite3_close (db); } - public static Result Close2(Sqlite3DatabaseHandle db) + public static Result Close2 (Sqlite3DatabaseHandle db) { - return (Result)Sqlite3.sqlite3_close_v2(db); + return (Result)Sqlite3.sqlite3_close_v2 (db); } - public static Result BusyTimeout(Sqlite3DatabaseHandle db, int milliseconds) + public static Result BusyTimeout (Sqlite3DatabaseHandle db, int milliseconds) { - return (Result)Sqlite3.sqlite3_busy_timeout(db, milliseconds); + return (Result)Sqlite3.sqlite3_busy_timeout (db, milliseconds); } - public static int Changes(Sqlite3DatabaseHandle db) + public static int Changes (Sqlite3DatabaseHandle db) { - return Sqlite3.sqlite3_changes(db); + return Sqlite3.sqlite3_changes (db); } - public static Sqlite3Statement Prepare2(Sqlite3DatabaseHandle db, string query) + public static Sqlite3Statement Prepare2 (Sqlite3DatabaseHandle db, string query) { - Sqlite3Statement stmt = default(Sqlite3Statement); + Sqlite3Statement stmt = default (Sqlite3Statement); #if USE_WP8_NATIVE_SQLITE || USE_SQLITEPCL_RAW - var r = Sqlite3.sqlite3_prepare_v2(db, query, out stmt); + var r = Sqlite3.sqlite3_prepare_v2 (db, query, out stmt); #else stmt = new Sqlite3Statement(); var r = Sqlite3.sqlite3_prepare_v2(db, query, -1, ref stmt, 0); #endif - if (r != 0) - { - throw SQLiteException.New((Result)r, GetErrmsg(db)); + if (r != 0) { + throw SQLiteException.New ((Result)r, GetErrmsg (db)); } return stmt; } - public static Result Step(Sqlite3Statement stmt) + public static Result Step (Sqlite3Statement stmt) { - return (Result)Sqlite3.sqlite3_step(stmt); + return (Result)Sqlite3.sqlite3_step (stmt); } - public static Result Reset(Sqlite3Statement stmt) + public static Result Reset (Sqlite3Statement stmt) { - return (Result)Sqlite3.sqlite3_reset(stmt); + return (Result)Sqlite3.sqlite3_reset (stmt); } - public static Result Finalize(Sqlite3Statement stmt) + public static Result Finalize (Sqlite3Statement stmt) { - return (Result)Sqlite3.sqlite3_finalize(stmt); + return (Result)Sqlite3.sqlite3_finalize (stmt); } - public static long LastInsertRowid(Sqlite3DatabaseHandle db) + public static long LastInsertRowid (Sqlite3DatabaseHandle db) { - return Sqlite3.sqlite3_last_insert_rowid(db); + return Sqlite3.sqlite3_last_insert_rowid (db); } - public static string GetErrmsg(Sqlite3DatabaseHandle db) + public static string GetErrmsg (Sqlite3DatabaseHandle db) { - return Sqlite3.sqlite3_errmsg(db); + return Sqlite3.sqlite3_errmsg (db); } - public static int BindParameterIndex(Sqlite3Statement stmt, string name) + public static int BindParameterIndex (Sqlite3Statement stmt, string name) { - return Sqlite3.sqlite3_bind_parameter_index(stmt, name); + return Sqlite3.sqlite3_bind_parameter_index (stmt, name); } - public static int BindNull(Sqlite3Statement stmt, int index) + public static int BindNull (Sqlite3Statement stmt, int index) { - return Sqlite3.sqlite3_bind_null(stmt, index); + return Sqlite3.sqlite3_bind_null (stmt, index); } - public static int BindInt(Sqlite3Statement stmt, int index, int val) + public static int BindInt (Sqlite3Statement stmt, int index, int val) { - return Sqlite3.sqlite3_bind_int(stmt, index, val); + return Sqlite3.sqlite3_bind_int (stmt, index, val); } - public static int BindInt64(Sqlite3Statement stmt, int index, long val) + public static int BindInt64 (Sqlite3Statement stmt, int index, long val) { - return Sqlite3.sqlite3_bind_int64(stmt, index, val); + return Sqlite3.sqlite3_bind_int64 (stmt, index, val); } - public static int BindDouble(Sqlite3Statement stmt, int index, double val) + public static int BindDouble (Sqlite3Statement stmt, int index, double val) { - return Sqlite3.sqlite3_bind_double(stmt, index, val); + return Sqlite3.sqlite3_bind_double (stmt, index, val); } - public static int BindText(Sqlite3Statement stmt, int index, string val, int n, IntPtr free) + public static int BindText (Sqlite3Statement stmt, int index, string val, int n, IntPtr free) { #if USE_WP8_NATIVE_SQLITE return Sqlite3.sqlite3_bind_text(stmt, index, val, n); #elif USE_SQLITEPCL_RAW - return Sqlite3.sqlite3_bind_text(stmt, index, val); + return Sqlite3.sqlite3_bind_text (stmt, index, val); #else return Sqlite3.sqlite3_bind_text(stmt, index, val, n, null); #endif } - public static int BindBlob(Sqlite3Statement stmt, int index, byte[] val, int n, IntPtr free) + public static int BindBlob (Sqlite3Statement stmt, int index, byte[] val, int n, IntPtr free) { #if USE_WP8_NATIVE_SQLITE return Sqlite3.sqlite3_bind_blob(stmt, index, val, n); #elif USE_SQLITEPCL_RAW - return Sqlite3.sqlite3_bind_blob(stmt, index, val); + return Sqlite3.sqlite3_bind_blob (stmt, index, val); #else return Sqlite3.sqlite3_bind_blob(stmt, index, val, n, null); #endif } - public static int ColumnCount(Sqlite3Statement stmt) + public static int ColumnCount (Sqlite3Statement stmt) { - return Sqlite3.sqlite3_column_count(stmt); + return Sqlite3.sqlite3_column_count (stmt); } - public static string ColumnName(Sqlite3Statement stmt, int index) + public static string ColumnName (Sqlite3Statement stmt, int index) { - return Sqlite3.sqlite3_column_name(stmt, index); + return Sqlite3.sqlite3_column_name (stmt, index); } - public static string ColumnName16(Sqlite3Statement stmt, int index) + public static string ColumnName16 (Sqlite3Statement stmt, int index) { - return Sqlite3.sqlite3_column_name(stmt, index); + return Sqlite3.sqlite3_column_name (stmt, index); } - public static ColType ColumnType(Sqlite3Statement stmt, int index) + public static ColType ColumnType (Sqlite3Statement stmt, int index) { - return (ColType)Sqlite3.sqlite3_column_type(stmt, index); + return (ColType)Sqlite3.sqlite3_column_type (stmt, index); } - public static int ColumnInt(Sqlite3Statement stmt, int index) + public static int ColumnInt (Sqlite3Statement stmt, int index) { - return Sqlite3.sqlite3_column_int(stmt, index); + return Sqlite3.sqlite3_column_int (stmt, index); } - public static long ColumnInt64(Sqlite3Statement stmt, int index) + public static long ColumnInt64 (Sqlite3Statement stmt, int index) { - return Sqlite3.sqlite3_column_int64(stmt, index); + return Sqlite3.sqlite3_column_int64 (stmt, index); } - public static double ColumnDouble(Sqlite3Statement stmt, int index) + public static double ColumnDouble (Sqlite3Statement stmt, int index) { - return Sqlite3.sqlite3_column_double(stmt, index); + return Sqlite3.sqlite3_column_double (stmt, index); } - public static string ColumnText(Sqlite3Statement stmt, int index) + public static string ColumnText (Sqlite3Statement stmt, int index) { - return Sqlite3.sqlite3_column_text(stmt, index); + return Sqlite3.sqlite3_column_text (stmt, index); } - public static string ColumnText16(Sqlite3Statement stmt, int index) + public static string ColumnText16 (Sqlite3Statement stmt, int index) { - return Sqlite3.sqlite3_column_text(stmt, index); + return Sqlite3.sqlite3_column_text (stmt, index); } - public static byte[] ColumnBlob(Sqlite3Statement stmt, int index) + public static byte[] ColumnBlob (Sqlite3Statement stmt, int index) { - return Sqlite3.sqlite3_column_blob(stmt, index); + return Sqlite3.sqlite3_column_blob (stmt, index); } - public static int ColumnBytes(Sqlite3Statement stmt, int index) + public static int ColumnBytes (Sqlite3Statement stmt, int index) { - return Sqlite3.sqlite3_column_bytes(stmt, index); + return Sqlite3.sqlite3_column_bytes (stmt, index); } - public static string ColumnString(Sqlite3Statement stmt, int index) + public static string ColumnString (Sqlite3Statement stmt, int index) { - return Sqlite3.sqlite3_column_text(stmt, index); + return Sqlite3.sqlite3_column_text (stmt, index); } - public static byte[] ColumnByteArray(Sqlite3Statement stmt, int index) + public static byte[] ColumnByteArray (Sqlite3Statement stmt, int index) { - int length = ColumnBytes(stmt, index); - if (length > 0) - { - return ColumnBlob(stmt, index); + int length = ColumnBytes (stmt, index); + if (length > 0) { + return ColumnBlob (stmt, index); } return new byte[0]; } - public static Result EnableLoadExtension(Sqlite3DatabaseHandle db, int onoff) + public static Result EnableLoadExtension (Sqlite3DatabaseHandle db, int onoff) { - return (Result)Sqlite3.sqlite3_enable_load_extension(db, onoff); + return (Result)Sqlite3.sqlite3_enable_load_extension (db, onoff); } - public static int LibVersionNumber() + public static int LibVersionNumber () { - return Sqlite3.sqlite3_libversion_number(); + return Sqlite3.sqlite3_libversion_number (); } - public static ExtendedResult ExtendedErrCode(Sqlite3DatabaseHandle db) + public static ExtendedResult ExtendedErrCode (Sqlite3DatabaseHandle db) { - return (ExtendedResult)Sqlite3.sqlite3_extended_errcode(db); + return (ExtendedResult)Sqlite3.sqlite3_extended_errcode (db); } #endif @@ -3852,19 +3909,19 @@ public enum ColType : int #if NO_CONCURRENT namespace SQLite.Extensions { - public static class ListEx - { - public static bool TryAdd (this IDictionary dict, TKey key, TValue value) - { - try { - dict.Add (key, value); - return true; - } - catch (ArgumentException) { - return false; - } - } - } + public static class ListEx + { + public static bool TryAdd (this IDictionary dict, TKey key, TValue value) + { + try { + dict.Add (key, value); + return true; + } + catch (ArgumentException) { + return false; + } + } + } } #endif diff --git a/src/SQLiteAsync.cs b/src/SQLiteAsync.cs index 215308248..53eddd022 100644 --- a/src/SQLiteAsync.cs +++ b/src/SQLiteAsync.cs @@ -35,18 +35,18 @@ namespace SQLite public partial class SQLiteAsyncConnection { SQLiteConnectionString _connectionString; - SQLiteOpenFlags _openFlags; - - public SQLiteAsyncConnection(string databasePath, bool storeDateTimeAsTicks = true) - : this(databasePath, SQLiteOpenFlags.ReadWrite | SQLiteOpenFlags.Create, storeDateTimeAsTicks) - { - } - - public SQLiteAsyncConnection(string databasePath, SQLiteOpenFlags openFlags, bool storeDateTimeAsTicks = true) - { - _openFlags = openFlags; - _connectionString = new SQLiteConnectionString(databasePath, storeDateTimeAsTicks); - } + SQLiteOpenFlags _openFlags; + + public SQLiteAsyncConnection (string databasePath, bool storeDateTimeAsTicks = true) + : this (databasePath, SQLiteOpenFlags.ReadWrite | SQLiteOpenFlags.Create, storeDateTimeAsTicks) + { + } + + public SQLiteAsyncConnection (string databasePath, SQLiteOpenFlags openFlags, bool storeDateTimeAsTicks = true) + { + _openFlags = openFlags; + _connectionString = new SQLiteConnectionString (databasePath, storeDateTimeAsTicks); + } public string DatabasePath => GetConnection ().DatabasePath; public int LibVersionNumber => GetConnection ().LibVersionNumber; @@ -72,19 +72,19 @@ public bool TimeExecution { /// /// Closes all connections to all async databases. /// - public static void ResetPool() + public static void ResetPool () { - SQLiteConnectionPool.Shared.Reset(); + SQLiteConnectionPool.Shared.Reset (); } public SQLiteConnectionWithLock GetConnection () { - return SQLiteConnectionPool.Shared.GetConnection(_connectionString, _openFlags); + return SQLiteConnectionPool.Shared.GetConnection (_connectionString, _openFlags); } - public void Close() + public void Close () { - SQLiteConnectionPool.Shared.CloseConnection(_connectionString, _openFlags); + SQLiteConnectionPool.Shared.CloseConnection (_connectionString, _openFlags); } Task ReadAsync (Func read) @@ -116,7 +116,7 @@ public Task EnableLoadExtensionAsync (bool enabled) } public Task CreateTableAsync (CreateFlags createFlags = CreateFlags.None) - where T : new () + where T : new() { return WriteAsync (conn => conn.CreateTable (createFlags)); } @@ -127,35 +127,35 @@ public Task CreateTableAsync (Type ty, CreateFlags createFlags = CreateFlag } public Task CreateTablesAsync (CreateFlags createFlags = CreateFlags.None) - where T : new () - where T2 : new () + where T : new() + where T2 : new() { return CreateTablesAsync (createFlags, typeof (T), typeof (T2)); } public Task CreateTablesAsync (CreateFlags createFlags = CreateFlags.None) - where T : new () - where T2 : new () - where T3 : new () + where T : new() + where T2 : new() + where T3 : new() { return CreateTablesAsync (createFlags, typeof (T), typeof (T2), typeof (T3)); } public Task CreateTablesAsync (CreateFlags createFlags = CreateFlags.None) - where T : new () - where T2 : new () - where T3 : new () - where T4 : new () + where T : new() + where T2 : new() + where T3 : new() + where T4 : new() { return CreateTablesAsync (createFlags, typeof (T), typeof (T2), typeof (T3), typeof (T4)); } public Task CreateTablesAsync (CreateFlags createFlags = CreateFlags.None) - where T : new () - where T2 : new () - where T3 : new () - where T4 : new () - where T5 : new () + where T : new() + where T2 : new() + where T3 : new() + where T4 : new() + where T5 : new() { return CreateTablesAsync (createFlags, typeof (T), typeof (T2), typeof (T3), typeof (T4), typeof (T5)); } @@ -166,7 +166,7 @@ public Task CreateTablesAsync (CreateFlags createFlags = Cre } public Task DropTableAsync () - where T : new () + where T : new() { return WriteAsync (conn => conn.DropTable ()); } @@ -221,10 +221,10 @@ public Task InsertAsync (object obj, string extra, Type objType) return WriteAsync (conn => conn.Insert (obj, extra, objType)); } - public Task InsertOrReplaceAsync(object obj) - { - return WriteAsync (conn => conn.InsertOrReplace(obj)); - } + public Task InsertOrReplaceAsync (object obj) + { + return WriteAsync (conn => conn.InsertOrReplace (obj)); + } public Task InsertOrReplaceAsync (object obj, Type objType) { @@ -256,23 +256,23 @@ public Task DeleteAsync (object primaryKey, TableMapping map) return WriteAsync (conn => conn.Delete (primaryKey, map)); } - public Task DeleteAllAsync() - { - return WriteAsync (conn => conn.DeleteAll()); - } + public Task DeleteAllAsync () + { + return WriteAsync (conn => conn.DeleteAll ()); + } public Task DeleteAllAsync (TableMapping map) { return WriteAsync (conn => conn.DeleteAll (map)); } - public Task GetAsync(object pk) - where T : new() - { - return ReadAsync (conn => conn.Get(pk)); - } + public Task GetAsync (object pk) + where T : new() + { + return ReadAsync (conn => conn.Get (pk)); + } - public Task GetAsync(object pk, TableMapping map) + public Task GetAsync (object pk, TableMapping map) { return ReadAsync (conn => conn.Get (pk, map)); } @@ -284,7 +284,7 @@ public Task GetAsync (Expression> predicate) } public Task FindAsync (object pk) - where T : new () + where T : new() { return ReadAsync (conn => conn.Find (pk)); } @@ -295,7 +295,7 @@ public Task FindAsync (object pk, TableMapping map) } public Task FindAsync (Expression> predicate) - where T : new () + where T : new() { return ReadAsync (conn => conn.Find (predicate)); } @@ -352,27 +352,24 @@ public Task UpdateAllAsync (IEnumerable objects, bool runInTransaction = tr return WriteAsync (conn => conn.UpdateAll (objects, runInTransaction)); } - public Task RunInTransactionAsync(Action action) - { - return WriteAsync (conn => - { - conn.BeginTransaction(); - try - { - action(conn); - conn.Commit(); + public Task RunInTransactionAsync (Action action) + { + return WriteAsync (conn => { + conn.BeginTransaction (); + try { + action (conn); + conn.Commit (); return null; - } - catch (Exception) - { - conn.Rollback(); - throw; - } - }); - } + } + catch (Exception) { + conn.Rollback (); + throw; + } + }); + } public AsyncTableQuery Table () - where T : new () + where T : new() { // // This isn't async as the underlying connection doesn't go out to the database @@ -391,7 +388,7 @@ public Task ExecuteScalarAsync (string sql, params object[] args) } public Task> QueryAsync (string sql, params object[] args) - where T : new () + where T : new() { return ReadAsync (conn => conn.Query (sql, args)); } @@ -418,7 +415,7 @@ public Task> DeferredQueryAsync (TableMapping map, string qu // execution can still work after a Pool.Reset. // public class AsyncTableQuery - where T : new () + where T : new() { TableQuery _innerQuery; @@ -452,14 +449,14 @@ public AsyncTableQuery OrderByDescending (Expression> orderExpr return new AsyncTableQuery (_innerQuery.OrderByDescending (orderExpr)); } - public AsyncTableQuery ThenBy(Expression> orderExpr) + public AsyncTableQuery ThenBy (Expression> orderExpr) { - return new AsyncTableQuery(_innerQuery.ThenBy(orderExpr)); + return new AsyncTableQuery (_innerQuery.ThenBy (orderExpr)); } - public AsyncTableQuery ThenByDescending(Expression> orderExpr) + public AsyncTableQuery ThenByDescending (Expression> orderExpr) { - return new AsyncTableQuery(_innerQuery.ThenByDescending(orderExpr)); + return new AsyncTableQuery (_innerQuery.ThenByDescending (orderExpr)); } @@ -492,7 +489,7 @@ public Task ElementAtAsync (int index) public Task FirstAsync () { - return Task.Factory.StartNew(() => { + return Task.Factory.StartNew (() => { using (((SQLiteConnectionWithLock)_innerQuery.Connection).Lock ()) { return _innerQuery.First (); } @@ -501,13 +498,13 @@ public Task FirstAsync () public Task FirstOrDefaultAsync () { - return Task.Factory.StartNew(() => { + return Task.Factory.StartNew (() => { using (((SQLiteConnectionWithLock)_innerQuery.Connection).Lock ()) { return _innerQuery.FirstOrDefault (); } }); } - } + } class SQLiteConnectionPool { @@ -516,7 +513,7 @@ class Entry public SQLiteConnectionString ConnectionString { get; private set; } public SQLiteConnectionWithLock Connection { get; private set; } - public Entry (SQLiteConnectionString connectionString, SQLiteOpenFlags openFlags) + public Entry (SQLiteConnectionString connectionString, SQLiteOpenFlags openFlags) { ConnectionString = connectionString; Connection = new SQLiteConnectionWithLock (connectionString, openFlags); @@ -537,10 +534,8 @@ public void Close () /// /// Gets the singleton instance of the connection tool. /// - public static SQLiteConnectionPool Shared - { - get - { + public static SQLiteConnectionPool Shared { + get { return _shared; } } @@ -591,7 +586,7 @@ public class SQLiteConnectionWithLock : SQLiteConnection { readonly object _lockPoint = new object (); - public SQLiteConnectionWithLock (SQLiteConnectionString connectionString, SQLiteOpenFlags openFlags) + public SQLiteConnectionWithLock (SQLiteConnectionString connectionString, SQLiteOpenFlags openFlags) : base (connectionString.DatabasePath, openFlags, connectionString.StoreDateTimeAsTicks) { } From 5bc7d66c4489ca966716baf2815ff92622cf0764 Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Wed, 2 Aug 2017 09:50:07 -0700 Subject: [PATCH 050/103] Bump SQLitePCLRaw to 1.1.7 to fix macOS Working on #483 --- nuget/SQLite-net-std/SQLite-net-std.csproj | 2 +- nuget/SQLite-net/SQLite-net.csproj | 6 ++--- nuget/SQLite-net/packages.config | 4 ++-- sqlite-net-pcl.nuspec | 26 +++++++++++----------- 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/nuget/SQLite-net-std/SQLite-net-std.csproj b/nuget/SQLite-net-std/SQLite-net-std.csproj index 5a2acd486..347506fe5 100644 --- a/nuget/SQLite-net-std/SQLite-net-std.csproj +++ b/nuget/SQLite-net-std/SQLite-net-std.csproj @@ -15,7 +15,7 @@ TRACE;USE_SQLITEPCL_RAW;DEBUG;NETSTANDARD1_1 - + diff --git a/nuget/SQLite-net/SQLite-net.csproj b/nuget/SQLite-net/SQLite-net.csproj index 9a40e93fb..4a70441cd 100644 --- a/nuget/SQLite-net/SQLite-net.csproj +++ b/nuget/SQLite-net/SQLite-net.csproj @@ -56,13 +56,13 @@ - ..\..\packages\SQLitePCLRaw.core.1.1.5\lib\portable-net45+netcore45+wpa81+wp8+MonoAndroid10+MonoTouch10+Xamarin.iOS10\SQLitePCLRaw.core.dll + ..\..\packages\SQLitePCLRaw.core.1.1.7\lib\portable-net45+netcore45+wpa81+wp8+MonoAndroid10+MonoTouch10+Xamarin.iOS10\SQLitePCLRaw.core.dll - ..\..\packages\SQLitePCLRaw.bundle_green.1.1.5\lib\portable-net45+netcore45+wpa81+wp8+MonoAndroid10+MonoTouch10+Xamarin.iOS10\SQLitePCLRaw.batteries_green.dll + ..\..\packages\SQLitePCLRaw.bundle_green.1.1.7\lib\portable-net45+netcore45+wpa81+wp8+MonoAndroid10+MonoTouch10+Xamarin.iOS10\SQLitePCLRaw.batteries_green.dll - ..\..\packages\SQLitePCLRaw.bundle_green.1.1.5\lib\portable-net45+netcore45+wpa81+wp8+MonoAndroid10+MonoTouch10+Xamarin.iOS10\SQLitePCLRaw.batteries_v2.dll + ..\..\packages\SQLitePCLRaw.bundle_green.1.1.7\lib\portable-net45+netcore45+wpa81+wp8+MonoAndroid10+MonoTouch10+Xamarin.iOS10\SQLitePCLRaw.batteries_v2.dll diff --git a/nuget/SQLite-net/packages.config b/nuget/SQLite-net/packages.config index 6f84d4588..04db2ed75 100644 --- a/nuget/SQLite-net/packages.config +++ b/nuget/SQLite-net/packages.config @@ -1,5 +1,5 @@  - - + + \ No newline at end of file diff --git a/sqlite-net-pcl.nuspec b/sqlite-net-pcl.nuspec index c00bc9df8..e7d9137e1 100644 --- a/sqlite-net-pcl.nuspec +++ b/sqlite-net-pcl.nuspec @@ -20,44 +20,44 @@ - + - + - + - + - + - + - + - + - + - + - + - + - + From 786ee042732f06426cb2de3997851e0bc122554c Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Wed, 2 Aug 2017 09:52:54 -0700 Subject: [PATCH 051/103] Update Profile 259 nuget lib path Working on #483 --- sqlite-net-pcl.nuspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sqlite-net-pcl.nuspec b/sqlite-net-pcl.nuspec index e7d9137e1..dc908b5f3 100644 --- a/sqlite-net-pcl.nuspec +++ b/sqlite-net-pcl.nuspec @@ -62,7 +62,7 @@ - + From 0b0855059fd2e708f381a4954fd184e1c01612f0 Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Wed, 2 Aug 2017 09:58:56 -0700 Subject: [PATCH 052/103] Bump the version to 1.5 --- sqlite-net-pcl.nuspec | 2 +- sqlite-net.nuspec | 2 +- src/AssemblyInfo.cs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/sqlite-net-pcl.nuspec b/sqlite-net-pcl.nuspec index dc908b5f3..ac95ec65e 100644 --- a/sqlite-net-pcl.nuspec +++ b/sqlite-net-pcl.nuspec @@ -1,7 +1,7 @@ - 1.4.0 + 1.5.0 Frank A. Krueger Frank A. Krueger https://raw.githubusercontent.com/praeclarum/sqlite-net/master/LICENSE.md diff --git a/sqlite-net.nuspec b/sqlite-net.nuspec index 59375955a..9f9e24076 100644 --- a/sqlite-net.nuspec +++ b/sqlite-net.nuspec @@ -1,7 +1,7 @@ - 1.4.0 + 1.5.0 Frank Krueger Frank Krueger,Tim Heuer https://raw.githubusercontent.com/praeclarum/sqlite-net/master/LICENSE.md diff --git a/src/AssemblyInfo.cs b/src/AssemblyInfo.cs index c06011b48..addbdb5ac 100644 --- a/src/AssemblyInfo.cs +++ b/src/AssemblyInfo.cs @@ -26,5 +26,5 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.4.0.0")] -[assembly: AssemblyFileVersion("1.4.0.0")] +[assembly: AssemblyVersion("1.5.0.0")] +[assembly: AssemblyFileVersion("1.5.0.0")] From feead3c3787fefa28660a35784c85b98465f8f04 Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Wed, 2 Aug 2017 10:02:54 -0700 Subject: [PATCH 053/103] Add API diff to tests --- Makefile | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index c7d36353a..48eedeb19 100644 --- a/Makefile +++ b/Makefile @@ -3,12 +3,16 @@ SRC=src/SQLite.cs src/SQLiteAsync.cs all: test nuget -test: tests/bin/Debug/SQLite.Tests.dll +test: tests/bin/Debug/SQLite.Tests.dll tests/ApiDiff/bin/Debug/ApiDiff.exe nunit-console tests/bin/Debug/SQLite.Tests.dll + mono tests/ApiDiff/bin/Debug/ApiDiff.exe tests/bin/Debug/SQLite.Tests.dll: tests/SQLite.Tests.csproj $(SRC) msbuild tests/SQLite.Tests.csproj +tests/ApiDiff/bin/Debug/ApiDiff.exe: tests/ApiDiff/ApiDiff.csproj $(SRC) + msbuild tests/ApiDiff/ApiDiff.csproj + nuget: srcnuget pclnuget packages: nuget/SQLite-net/packages.config From a98925b15d644ac67cd9c74c8c6b06e9a58fe046 Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Wed, 2 Aug 2017 13:32:05 -0700 Subject: [PATCH 054/103] Add AsyncConnection documentation comments Fixes #415 --- src/SQLite.cs | 91 +++++- src/SQLiteAsync.cs | 783 +++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 842 insertions(+), 32 deletions(-) diff --git a/src/SQLite.cs b/src/SQLite.cs index b67863ee3..e7d0c76b8 100644 --- a/src/SQLite.cs +++ b/src/SQLite.cs @@ -138,7 +138,7 @@ public enum CreateFlags } /// - /// Represents an open connection to a SQLite database. + /// An open connection to a SQLite database. /// public partial class SQLiteConnection : IDisposable { @@ -155,15 +155,36 @@ public partial class SQLiteConnection : IDisposable public Sqlite3DatabaseHandle Handle { get; private set; } internal static readonly Sqlite3DatabaseHandle NullHandle = default (Sqlite3DatabaseHandle); + /// + /// Gets the database path used by this connection. + /// public string DatabasePath { get; private set; } + /// + /// Gets the SQLite library version number. 3007014 would be v3.7.14 + /// public int LibVersionNumber { get; private set; } + /// + /// Whether Trace lines should be written that show the execution time of queries. + /// public bool TimeExecution { get; set; } + /// + /// Whether to writer queries to during execution. + /// + /// The tracer. public bool Trace { get; set; } + + /// + /// The delegate responsible for writing trace lines. + /// + /// The tracer. public Action Tracer { get; set; } + /// + /// Whether to store DateTime properties as ticks (true) or strings (false). + /// public bool StoreDateTimeAsTicks { get; private set; } #if USE_SQLITEPCL_RAW @@ -668,6 +689,11 @@ public override string ToString () } } + /// + /// Query the built-in sqlite table_info table for a specific tables columns. + /// + /// The columns contains in the table. + /// Table name. public List GetTableInfo (string tableName) { var query = "pragma table_info(\"" + tableName + "\")"; @@ -773,6 +799,22 @@ public int Execute (string query, params object[] args) 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. + /// Use this method when return primitive values. + /// You can set the Trace or TimeExecution properties of the connection + /// to profile execution. + /// + /// + /// The fully escaped SQL. + /// + /// + /// Arguments to substitute for the occurences of '?' in the query. + /// + /// + /// The number of rows modified in the database as a result of this execution. + /// public T ExecuteScalar (string query, params object[] args) { var cmd = CreateCommand (query, args); @@ -2522,7 +2564,7 @@ public IEnumerable ExecuteDeferredQuery (TableMapping map) public T ExecuteScalar () { if (_conn.Trace) { - _conn.Tracer.Invoke ("Executing Query: " + this); + _conn.Tracer?.Invoke ("Executing Query: " + this); } T val = default (T); @@ -2919,6 +2961,9 @@ public TableQuery Clone () return q; } + /// + /// Filters the query based on a predicate. + /// public TableQuery Where (Expression> predExpr) { if (predExpr.NodeType == ExpressionType.Lambda) { @@ -2952,6 +2997,9 @@ public int Delete (Expression> predExpr) } } + /// + /// Yields a given number of elements from the query and then skips the remainder. + /// public TableQuery Take (int n) { var q = Clone (); @@ -2959,6 +3007,9 @@ public TableQuery Take (int n) return q; } + /// + /// Skips a given number of elements from the query and then yields the remainder. + /// public TableQuery Skip (int n) { var q = Clone (); @@ -2966,6 +3017,9 @@ public TableQuery Skip (int n) return q; } + /// + /// Returns the element at a given index + /// public T ElementAt (int index) { return Skip (index).Take (1).First (); @@ -2979,27 +3033,39 @@ public TableQuery Deferred () return q; } + /// + /// Order the query results according to a key. + /// public TableQuery OrderBy (Expression> orderExpr) { return AddOrderBy (orderExpr, true); } + /// + /// Order the query results according to a key. + /// public TableQuery OrderByDescending (Expression> orderExpr) { return AddOrderBy (orderExpr, false); } + /// + /// Order the query results according to a key. + /// public TableQuery ThenBy (Expression> orderExpr) { return AddOrderBy (orderExpr, true); } + /// + /// Order the query results according to a key. + /// public TableQuery ThenByDescending (Expression> orderExpr) { return AddOrderBy (orderExpr, false); } - private TableQuery AddOrderBy (Expression> orderExpr, bool asc) + TableQuery AddOrderBy (Expression> orderExpr, bool asc) { if (orderExpr.NodeType == ExpressionType.Lambda) { var lambda = (LambdaExpression)orderExpr; @@ -3044,6 +3110,9 @@ private void AddWhere (Expression pred) } } + /// + /// Performs an inner join of two queries based on matching keys extracted from the elements. + /// public TableQuery Join ( TableQuery inner, Expression> outerKeySelector, @@ -3383,6 +3452,9 @@ string GetSqlName (Expression expr) } } + /// + /// Execute SELECT COUNT(*) on the query + /// public int Count () { return GenerateCommand ("count(*)").ExecuteScalar (); @@ -3406,23 +3478,36 @@ System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator () return GetEnumerator (); } + /// + /// Returns the first element of this query. + /// public T First () { var query = Take (1); return query.ToList ().First (); } + /// + /// Returns the first element of this query, or null if no element is found. + /// public T FirstOrDefault () { var query = Take (1); return query.ToList ().FirstOrDefault (); } + /// + /// Returns the first element of this query that matches the predicate. + /// public T First (Expression> predExpr) { return Where (predExpr).First (); } + /// + /// Returns the first element of this query that matches the predicate, or null + /// if no element is found. + /// public T FirstOrDefault (Expression> predExpr) { return Where (predExpr).FirstOrDefault (); diff --git a/src/SQLiteAsync.cs b/src/SQLiteAsync.cs index 53eddd022..dba77cbdc 100644 --- a/src/SQLiteAsync.cs +++ b/src/SQLiteAsync.cs @@ -28,45 +28,122 @@ using System.Threading; using System.Threading.Tasks; -#pragma warning disable 1591 // XML Doc Comments - namespace SQLite { + /// + /// A pooled asynchronous connection to a SQLite database. + /// public partial class SQLiteAsyncConnection { SQLiteConnectionString _connectionString; SQLiteOpenFlags _openFlags; + /// + /// Constructs a new SQLiteAsyncConnection and opens a pooled SQLite database specified by databasePath. + /// + /// + /// Specifies the path to the database file. + /// + /// + /// Specifies whether to store DateTime properties as ticks (true) or strings (false). You + /// absolutely do want to store them as Ticks in all new projects. The value of false is + /// only here for backwards compatibility. There is a *significant* speed advantage, with no + /// down sides, when setting storeDateTimeAsTicks = true. + /// If you use DateTimeOffset properties, it will be always stored as ticks regardingless + /// the storeDateTimeAsTicks parameter. + /// public SQLiteAsyncConnection (string databasePath, bool storeDateTimeAsTicks = true) : this (databasePath, SQLiteOpenFlags.ReadWrite | SQLiteOpenFlags.Create, storeDateTimeAsTicks) { } + /// + /// Constructs a new SQLiteAsyncConnection and opens a pooled SQLite database specified by databasePath. + /// + /// + /// Specifies the path to the database file. + /// + /// + /// Flags controlling how the connection should be opened. + /// + /// + /// Specifies whether to store DateTime properties as ticks (true) or strings (false). You + /// absolutely do want to store them as Ticks in all new projects. The value of false is + /// only here for backwards compatibility. There is a *significant* speed advantage, with no + /// down sides, when setting storeDateTimeAsTicks = true. + /// If you use DateTimeOffset properties, it will be always stored as ticks regardingless + /// the storeDateTimeAsTicks parameter. + /// public SQLiteAsyncConnection (string databasePath, SQLiteOpenFlags openFlags, bool storeDateTimeAsTicks = true) { _openFlags = openFlags; _connectionString = new SQLiteConnectionString (databasePath, storeDateTimeAsTicks); } + /// + /// Gets the database path used by this connection. + /// public string DatabasePath => GetConnection ().DatabasePath; + + /// + /// Gets the SQLite library version number. 3007014 would be v3.7.14 + /// public int LibVersionNumber => GetConnection ().LibVersionNumber; - public TimeSpan BusyTimeout { - get { return GetConnection ().BusyTimeout; } - set { GetConnection ().BusyTimeout = value; } + + /// + /// The amount of time to wait for a table to become unlocked. + /// + public TimeSpan GetBusyTimeout () + { + return GetConnection ().BusyTimeout; + } + + /// + /// Sets the amount of time to wait for a table to become unlocked. + /// + public Task SetBusyTimeoutAsync (TimeSpan value) + { + return ReadAsync (conn => { + conn.BusyTimeout = value; + return null; + }); } + + /// + /// Whether to store DateTime properties as ticks (true) or strings (false). + /// public bool StoreDateTimeAsTicks => GetConnection ().StoreDateTimeAsTicks; + + /// + /// Whether to writer queries to during execution. + /// + /// The tracer. public bool Trace { get { return GetConnection ().Trace; } set { GetConnection ().Trace = value; } } + + /// + /// The delegate responsible for writing trace lines. + /// + /// The tracer. public Action Tracer { get { return GetConnection ().Tracer; } set { GetConnection ().Tracer = value; } } + + /// + /// Whether Trace lines should be written that show the execution time of queries. + /// public bool TimeExecution { get { return GetConnection ().TimeExecution; } set { GetConnection ().TimeExecution = value; } } + + /// + /// Returns the mappings from types to tables that the connection + /// currently understands. + /// public IEnumerable TableMappings => GetConnection ().TableMappings; /// @@ -77,11 +154,20 @@ public static void ResetPool () SQLiteConnectionPool.Shared.Reset (); } + /// + /// Gets the pooled lockable connection used by this async connection. + /// You should never need to use this. This is provided only to add additional + /// functionality to SQLite-net. If you use this connection, you must use + /// the Lock method on it while using it. + /// public SQLiteConnectionWithLock GetConnection () { return SQLiteConnectionPool.Shared.GetConnection (_connectionString, _openFlags); } + /// + /// Closes any pooled connections used by the database. + /// public void Close () { SQLiteConnectionPool.Shared.CloseConnection (_connectionString, _openFlags); @@ -107,6 +193,9 @@ Task WriteAsync (Func write) }); } + /// + /// Enable or disable extension loading. + /// public Task EnableLoadExtensionAsync (bool enabled) { return WriteAsync (conn => { @@ -115,17 +204,46 @@ public Task EnableLoadExtensionAsync (bool enabled) }); } + /// + /// 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 Task CreateTableAsync (CreateFlags createFlags = CreateFlags.None) where T : new() { return WriteAsync (conn => conn.CreateTable (createFlags)); } + /// + /// Executes a "create table if not exists" on the database. It also + /// creates any specified indexes on the columns of the table. It uses + /// a schema automatically generated from the specified type. You can + /// later access this schema by calling GetMapping. + /// + /// Type to reflect to a database table. + /// Optional flags allowing implicit PK and indexes based on naming conventions. + /// + /// The number of entries added to the database schema. + /// public Task CreateTableAsync (Type ty, CreateFlags createFlags = CreateFlags.None) { return WriteAsync (conn => conn.CreateTable (ty, createFlags)); } + /// + /// Executes a "create table if not exists" on the database for each type. It also + /// creates any specified indexes on the columns of the table. It uses + /// a schema automatically generated from the specified type. You can + /// later access this schema by calling GetMapping. + /// + /// + /// The number of entries added to the database schema for each type. + /// public Task CreateTablesAsync (CreateFlags createFlags = CreateFlags.None) where T : new() where T2 : new() @@ -133,6 +251,15 @@ public Task CreateTablesAsync (CreateFlags createFlag return CreateTablesAsync (createFlags, typeof (T), typeof (T2)); } + /// + /// Executes a "create table if not exists" on the database for each type. It also + /// creates any specified indexes on the columns of the table. It uses + /// a schema automatically generated from the specified type. You can + /// later access this schema by calling GetMapping. + /// + /// + /// The number of entries added to the database schema for each type. + /// public Task CreateTablesAsync (CreateFlags createFlags = CreateFlags.None) where T : new() where T2 : new() @@ -141,6 +268,15 @@ public Task CreateTablesAsync (CreateFlags create return CreateTablesAsync (createFlags, typeof (T), typeof (T2), typeof (T3)); } + /// + /// Executes a "create table if not exists" on the database for each type. It also + /// creates any specified indexes on the columns of the table. It uses + /// a schema automatically generated from the specified type. You can + /// later access this schema by calling GetMapping. + /// + /// + /// The number of entries added to the database schema for each type. + /// public Task CreateTablesAsync (CreateFlags createFlags = CreateFlags.None) where T : new() where T2 : new() @@ -150,6 +286,15 @@ public Task CreateTablesAsync (CreateFlags cr return CreateTablesAsync (createFlags, typeof (T), typeof (T2), typeof (T3), typeof (T4)); } + /// + /// Executes a "create table if not exists" on the database for each type. It also + /// creates any specified indexes on the columns of the table. It uses + /// a schema automatically generated from the specified type. You can + /// later access this schema by calling GetMapping. + /// + /// + /// The number of entries added to the database schema for each type. + /// public Task CreateTablesAsync (CreateFlags createFlags = CreateFlags.None) where T : new() where T2 : new() @@ -160,198 +305,625 @@ public Task CreateTablesAsync (CreateFlag return CreateTablesAsync (createFlags, typeof (T), typeof (T2), typeof (T3), typeof (T4), typeof (T5)); } + /// + /// Executes a "create table if not exists" on the database for each type. It also + /// creates any specified indexes on the columns of the table. It uses + /// a schema automatically generated from the specified type. You can + /// later access this schema by calling GetMapping. + /// + /// + /// The number of entries added to the database schema for each type. + /// public Task CreateTablesAsync (CreateFlags createFlags = CreateFlags.None, params Type[] types) { return WriteAsync (conn => conn.CreateTables (createFlags, types)); } + /// + /// Executes a "drop table" on the database. This is non-recoverable. + /// public Task DropTableAsync () where T : new() { return WriteAsync (conn => conn.DropTable ()); } + /// + /// Executes a "drop table" on the database. This is non-recoverable. + /// + /// + /// The TableMapping used to identify the table. + /// public Task DropTableAsync (TableMapping map) { return WriteAsync (conn => conn.DropTable (map)); } + /// + /// Creates an index for the specified table and column. + /// + /// Name of the database table + /// Name of the column to index + /// Whether the index should be unique public Task CreateIndexAsync (string tableName, string columnName, bool unique = false) { return WriteAsync (conn => conn.CreateIndex (tableName, columnName, unique)); } + /// + /// Creates an index for the specified table and column. + /// + /// Name of the index to create + /// Name of the database table + /// Name of the column to index + /// Whether the index should be unique public Task CreateIndexAsync (string indexName, string tableName, string columnName, bool unique = false) { return WriteAsync (conn => conn.CreateIndex (indexName, tableName, columnName, unique)); } + /// + /// Creates an index for the specified table and columns. + /// + /// Name of the database table + /// An array of column names to index + /// Whether the index should be unique public Task CreateIndexAsync (string tableName, string[] columnNames, bool unique = false) { return WriteAsync (conn => conn.CreateIndex (tableName, columnNames, unique)); } + /// + /// Creates an index for the specified table and columns. + /// + /// Name of the index to create + /// Name of the database table + /// An array of column names to index + /// Whether the index should be unique public Task CreateIndexAsync (string indexName, string tableName, string[] columnNames, bool unique = false) { return WriteAsync (conn => conn.CreateIndex (indexName, tableName, columnNames, unique)); } + /// + /// Creates an index for the specified object property. + /// e.g. CreateIndex<Client>(c => c.Name); + /// + /// Type to reflect to a database table. + /// Property to index + /// Whether the index should be unique public Task CreateIndexAsync (Expression> property, bool unique = false) { return WriteAsync (conn => conn.CreateIndex (property, unique)); } + /// + /// Inserts the given object and retrieves its + /// auto incremented primary key if it has one. + /// + /// + /// The object to insert. + /// + /// + /// The number of rows added to the table. + /// public Task InsertAsync (object obj) { return WriteAsync (conn => conn.Insert (obj)); } + /// + /// 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 Task InsertAsync (object obj, Type objType) { return WriteAsync (conn => conn.Insert (obj, 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 Task InsertAsync (object obj, string extra) { return WriteAsync (conn => conn.Insert (obj, extra)); } + /// + /// 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 Task InsertAsync (object obj, string extra, Type objType) { return WriteAsync (conn => conn.Insert (obj, extra, 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 number of rows modified. + /// public Task InsertOrReplaceAsync (object obj) { return WriteAsync (conn => conn.InsertOrReplace (obj)); } + /// + /// 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 Task InsertOrReplaceAsync (object obj, Type objType) { return WriteAsync (conn => conn.InsertOrReplace (obj, objType)); } + /// + /// Updates all of the columns of a table using the specified object + /// except for its primary key. + /// The object is required to have a primary key. + /// + /// + /// The object to update. It must have a primary key designated using the PrimaryKeyAttribute. + /// + /// + /// The number of rows updated. + /// public Task UpdateAsync (object obj) { return WriteAsync (conn => conn.Update (obj)); } + /// + /// Updates all of the columns of a table using the specified object + /// except for its primary key. + /// The object is required to have a primary key. + /// + /// + /// The object to update. It must have a primary key designated using the PrimaryKeyAttribute. + /// + /// + /// The type of object to insert. + /// + /// + /// The number of rows updated. + /// public Task UpdateAsync (object obj, Type objType) { return WriteAsync (conn => conn.Update (obj, objType)); } - public Task DeleteAsync (object item) + /// + /// Updates all specified objects. + /// + /// + /// An of the objects to insert. + /// + /// + /// A boolean indicating if the inserts should be wrapped in a transaction + /// + /// + /// The number of rows modified. + /// + public Task UpdateAllAsync (IEnumerable objects, bool runInTransaction = true) { - return WriteAsync (conn => conn.Delete (item)); + return WriteAsync (conn => conn.UpdateAll (objects, runInTransaction)); } + /// + /// Deletes the given object from the database using its primary key. + /// + /// + /// The object to delete. It must have a primary key designated using the PrimaryKeyAttribute. + /// + /// + /// The number of rows deleted. + /// + public Task DeleteAsync (object objectToDelete) + { + return WriteAsync (conn => conn.Delete (objectToDelete)); + } + + /// + /// Deletes the object with the specified primary key. + /// + /// + /// The primary key of the object to delete. + /// + /// + /// The number of objects deleted. + /// + /// + /// The type of object. + /// public Task DeleteAsync (object primaryKey) { return WriteAsync (conn => conn.Delete (primaryKey)); } + /// + /// Deletes the object with the specified primary key. + /// + /// + /// The primary key of the object to delete. + /// + /// + /// The TableMapping used to identify the table. + /// + /// + /// The number of objects deleted. + /// public Task DeleteAsync (object primaryKey, TableMapping map) { return WriteAsync (conn => conn.Delete (primaryKey, map)); } + /// + /// Deletes all the objects from the specified table. + /// WARNING WARNING: Let me repeat. It deletes ALL the objects from the + /// specified table. Do you really want to do that? + /// + /// + /// The number of objects deleted. + /// + /// + /// The type of objects to delete. + /// public Task DeleteAllAsync () { return WriteAsync (conn => conn.DeleteAll ()); } + /// + /// Deletes all the objects from the specified table. + /// WARNING WARNING: Let me repeat. It deletes ALL the objects from the + /// specified table. Do you really want to do that? + /// + /// + /// The TableMapping used to identify the table. + /// + /// + /// The number of objects deleted. + /// public Task DeleteAllAsync (TableMapping map) { return WriteAsync (conn => conn.DeleteAll (map)); } + /// + /// 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 Task GetAsync (object pk) where T : new() { return ReadAsync (conn => conn.Get (pk)); } + /// + /// Attempts to retrieve an object with the given primary key from the table + /// associated with the specified type. Use of this method requires that + /// the given type have a designated PrimaryKey (using the PrimaryKeyAttribute). + /// + /// + /// The primary key. + /// + /// + /// The TableMapping used to identify the table. + /// + /// + /// The object with the given primary key. Throws a not found exception + /// if the object is not found. + /// public Task GetAsync (object pk, TableMapping map) { return ReadAsync (conn => conn.Get (pk, map)); } + /// + /// Attempts to retrieve the first object that matches the predicate from the table + /// associated with the specified type. + /// + /// + /// A predicate for which object to find. + /// + /// + /// The object that matches the given predicate. Throws a not found exception + /// if the object is not found. + /// public Task GetAsync (Expression> predicate) where T : new() { return ReadAsync (conn => conn.Get (predicate)); } + /// + /// Attempts to retrieve an object with the given primary key from the table + /// associated with the specified type. Use of this method requires that + /// the given type have a designated PrimaryKey (using the PrimaryKeyAttribute). + /// + /// + /// The primary key. + /// + /// + /// The object with the given primary key or null + /// if the object is not found. + /// public Task FindAsync (object pk) where T : new() { return ReadAsync (conn => conn.Find (pk)); } + /// + /// Attempts to retrieve an object with the given primary key from the table + /// associated with the specified type. Use of this method requires that + /// the given type have a designated PrimaryKey (using the PrimaryKeyAttribute). + /// + /// + /// The primary key. + /// + /// + /// The TableMapping used to identify the table. + /// + /// + /// The object with the given primary key or null + /// if the object is not found. + /// public Task FindAsync (object pk, TableMapping map) { return ReadAsync (conn => conn.Find (pk, map)); } + /// + /// Attempts to retrieve the first object that matches the predicate from the table + /// associated with the specified type. + /// + /// + /// A predicate for which object to find. + /// + /// + /// The object that matches the given predicate or null + /// if the object is not found. + /// public Task FindAsync (Expression> predicate) where T : new() { return ReadAsync (conn => conn.Find (predicate)); } + /// + /// Attempts to retrieve the first object that matches the query from the table + /// associated with the specified type. + /// + /// + /// The fully escaped SQL. + /// + /// + /// Arguments to substitute for the occurences of '?' in the query. + /// + /// + /// The object that matches the given predicate or null + /// if the object is not found. + /// public Task FindWithQueryAsync (string query, params object[] args) where T : new() { return ReadAsync (conn => conn.FindWithQuery (query, args)); } + /// + /// Attempts to retrieve the first object that matches the query from the table + /// associated with the specified type. + /// + /// + /// The TableMapping used to identify the table. + /// + /// + /// The fully escaped SQL. + /// + /// + /// Arguments to substitute for the occurences of '?' in the query. + /// + /// + /// The object that matches the given predicate or null + /// if the object is not found. + /// public Task FindWithQueryAsync (TableMapping map, string query, params object[] args) { return ReadAsync (conn => conn.FindWithQuery (map, query, args)); } + /// + /// Retrieves the mapping that is automatically generated for the given type. + /// + /// + /// The type whose mapping to the database is returned. + /// + /// + /// Optional flags allowing implicit PK and indexes based on naming conventions + /// + /// + /// The mapping represents the schema of the columns of the database and contains + /// methods to set and get properties of objects. + /// public Task GetMappingAsync (Type type, CreateFlags createFlags = CreateFlags.None) { return ReadAsync (conn => conn.GetMapping (type, createFlags)); } + /// + /// Retrieves the mapping that is automatically generated for the given type. + /// + /// + /// Optional flags allowing implicit PK and indexes based on naming conventions + /// + /// + /// The mapping represents the schema of the columns of the database and contains + /// methods to set and get properties of objects. + /// public Task GetMappingAsync (CreateFlags createFlags = CreateFlags.None) where T : new() { return ReadAsync (conn => conn.GetMapping (createFlags)); } + /// + /// Query the built-in sqlite table_info table for a specific tables columns. + /// + /// The columns contains in the table. + /// Table name. public Task> GetTableInfoAsync (string tableName) { return ReadAsync (conn => conn.GetTableInfo (tableName)); } + /// + /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?' + /// in the command text for each of the arguments and then executes that command. + /// Use this method instead of Query when you don't expect rows back. Such cases include + /// INSERTs, UPDATEs, and DELETEs. + /// You can set the Trace or TimeExecution properties of the connection + /// to profile execution. + /// + /// + /// The fully escaped SQL. + /// + /// + /// Arguments to substitute for the occurences of '?' in the query. + /// + /// + /// The number of rows modified in the database as a result of this execution. + /// public Task ExecuteAsync (string query, params object[] args) { return WriteAsync (conn => conn.Execute (query, args)); } - public Task InsertAllAsync (IEnumerable items, bool runInTransaction = true) - { - return WriteAsync (conn => conn.InsertAll (items, runInTransaction)); - } - - public Task InsertAllAsync (IEnumerable items, string extra, bool runInTransaction = true) + /// + /// Inserts all specified objects. + /// + /// + /// An of the objects to insert. + /// + /// A boolean indicating if the inserts should be wrapped in a transaction. + /// + /// + /// The number of rows added to the table. + /// + public Task InsertAllAsync (IEnumerable objects, bool runInTransaction = true) { - return WriteAsync (conn => conn.InsertAll (items, extra, runInTransaction)); + return WriteAsync (conn => conn.InsertAll (objects, runInTransaction)); } - public Task InsertAllAsync (IEnumerable items, Type objType, bool runInTransaction = true) - { - return WriteAsync (conn => conn.InsertAll (items, objType, runInTransaction)); + /// + /// Inserts all specified objects. + /// + /// + /// An of the objects to insert. + /// + /// + /// Literal SQL code that gets placed into the command. INSERT {extra} INTO ... + /// + /// + /// A boolean indicating if the inserts should be wrapped in a transaction. + /// + /// + /// The number of rows added to the table. + /// + public Task InsertAllAsync (IEnumerable objects, string extra, bool runInTransaction = true) + { + return WriteAsync (conn => conn.InsertAll (objects, extra, runInTransaction)); } - public Task UpdateAllAsync (IEnumerable objects, bool runInTransaction = true) - { - return WriteAsync (conn => conn.UpdateAll (objects, runInTransaction)); + /// + /// Inserts all specified objects. + /// + /// + /// An of the objects to insert. + /// + /// + /// The type of object to insert. + /// + /// + /// A boolean indicating if the inserts should be wrapped in a transaction. + /// + /// + /// The number of rows added to the table. + /// + public Task InsertAllAsync (IEnumerable objects, Type objType, bool runInTransaction = true) + { + return WriteAsync (conn => conn.InsertAll (objects, objType, runInTransaction)); } + /// + /// Executes within a (possibly nested) transaction by wrapping it in a SAVEPOINT. If an + /// exception occurs the whole transaction is rolled back, not just the current savepoint. The exception + /// is rethrown. + /// + /// + /// The to perform within a transaction. can contain any number + /// of operations on the connection but should never call or + /// . + /// public Task RunInTransactionAsync (Action action) { return WriteAsync (conn => { @@ -368,6 +940,13 @@ public Task RunInTransactionAsync (Action action) }); } + /// + /// Returns a queryable interface to the table represented by the given type. + /// + /// + /// A queryable object that is able to translate Where, OrderBy, and Take + /// queries into native SQL. + /// public AsyncTableQuery Table () where T : new() { @@ -379,31 +958,121 @@ public AsyncTableQuery Table () return new AsyncTableQuery (conn.Table ()); } - public Task ExecuteScalarAsync (string sql, params object[] args) + /// + /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?' + /// in the command text for each of the arguments and then executes that command. + /// Use this method when return primitive values. + /// You can set the Trace or TimeExecution properties of the connection + /// to profile execution. + /// + /// + /// The fully escaped SQL. + /// + /// + /// Arguments to substitute for the occurences of '?' in the query. + /// + /// + /// The number of rows modified in the database as a result of this execution. + /// + public Task ExecuteScalarAsync (string query, params object[] args) { return WriteAsync (conn => { - var command = conn.CreateCommand (sql, args); + var command = conn.CreateCommand (query, args); return command.ExecuteScalar (); }); } - public Task> QueryAsync (string sql, params object[] args) + /// + /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?' + /// in the command text for each of the arguments and then executes that command. + /// It returns each row of the result using the mapping automatically generated for + /// the given type. + /// + /// + /// The fully escaped SQL. + /// + /// + /// Arguments to substitute for the occurences of '?' in the query. + /// + /// + /// An enumerable with one result for each row returned by the query. + /// + public Task> QueryAsync (string query, params object[] args) where T : new() { - return ReadAsync (conn => conn.Query (sql, args)); + return ReadAsync (conn => conn.Query (query, args)); } - public Task> QueryAsync (TableMapping map, string sql, params object[] args) - { - return ReadAsync (conn => conn.Query (map, sql, args)); + /// + /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?' + /// in the command text for each of the arguments and then executes that command. + /// It returns each row of the result using the specified mapping. This function is + /// only used by libraries in order to query the database via introspection. It is + /// normally not used. + /// + /// + /// A to use to convert the resulting rows + /// into objects. + /// + /// + /// The fully escaped SQL. + /// + /// + /// Arguments to substitute for the occurences of '?' in the query. + /// + /// + /// An enumerable with one result for each row returned by the query. + /// + public Task> QueryAsync (TableMapping map, string query, params object[] args) + { + return ReadAsync (conn => conn.Query (map, query, args)); } + /// + /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?' + /// in the command text for each of the arguments and then executes that command. + /// It returns each row of the result using the mapping automatically generated for + /// the given type. + /// + /// + /// The fully escaped SQL. + /// + /// + /// Arguments to substitute for the occurences of '?' in the query. + /// + /// + /// An enumerable with one result for each row returned by the query. + /// The enumerator will call sqlite3_step on each call to MoveNext, so the database + /// connection must remain open for the lifetime of the enumerator. + /// public Task> DeferredQueryAsync (string query, params object[] args) where T : new() { return ReadAsync (conn => (IEnumerable)conn.DeferredQuery (query, args).ToList ()); } + /// + /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?' + /// in the command text for each of the arguments and then executes that command. + /// It returns each row of the result using the specified mapping. This function is + /// only used by libraries in order to query the database via introspection. It is + /// normally not used. + /// + /// + /// A to use to convert the resulting rows + /// into objects. + /// + /// + /// The fully escaped SQL. + /// + /// + /// Arguments to substitute for the occurences of '?' in the query. + /// + /// + /// An enumerable with one result for each row returned by the query. + /// The enumerator will call sqlite3_step on each call to MoveNext, so the database + /// connection must remain open for the lifetime of the enumerator. + /// public Task> DeferredQueryAsync (TableMapping map, string query, params object[] args) { return ReadAsync (conn => (IEnumerable)conn.DeferredQuery (map, query, args).ToList ()); @@ -414,52 +1083,82 @@ public Task> DeferredQueryAsync (TableMapping map, string qu // TODO: Bind to AsyncConnection.GetConnection instead so that delayed // execution can still work after a Pool.Reset. // + + /// + /// Query to an asynchronous database connection. + /// public class AsyncTableQuery where T : new() { TableQuery _innerQuery; + /// + /// Creates a new async query that uses given the synchronous query. + /// public AsyncTableQuery (TableQuery innerQuery) { _innerQuery = innerQuery; } + /// + /// Filters the query based on a predicate. + /// public AsyncTableQuery Where (Expression> predExpr) { return new AsyncTableQuery (_innerQuery.Where (predExpr)); } + /// + /// Skips a given number of elements from the query and then yields the remainder. + /// public AsyncTableQuery Skip (int n) { return new AsyncTableQuery (_innerQuery.Skip (n)); } + /// + /// Yields a given number of elements from the query and then skips the remainder. + /// public AsyncTableQuery Take (int n) { return new AsyncTableQuery (_innerQuery.Take (n)); } + /// + /// Order the query results according to a key. + /// public AsyncTableQuery OrderBy (Expression> orderExpr) { return new AsyncTableQuery (_innerQuery.OrderBy (orderExpr)); } + /// + /// Order the query results according to a key. + /// public AsyncTableQuery OrderByDescending (Expression> orderExpr) { return new AsyncTableQuery (_innerQuery.OrderByDescending (orderExpr)); } + /// + /// Order the query results according to a key. + /// public AsyncTableQuery ThenBy (Expression> orderExpr) { return new AsyncTableQuery (_innerQuery.ThenBy (orderExpr)); } + /// + /// Order the query results according to a key. + /// public AsyncTableQuery ThenByDescending (Expression> orderExpr) { return new AsyncTableQuery (_innerQuery.ThenByDescending (orderExpr)); } - + /// + /// Execute the query and return the results as a list + /// public Task> ToListAsync () { return Task.Factory.StartNew (() => { @@ -469,6 +1168,9 @@ public Task> ToListAsync () }); } + /// + /// Execute SELECT COUNT(*) on the query + /// public Task CountAsync () { return Task.Factory.StartNew (() => { @@ -478,6 +1180,9 @@ public Task CountAsync () }); } + /// + /// Returns the element at a given index + /// public Task ElementAtAsync (int index) { return Task.Factory.StartNew (() => { @@ -487,6 +1192,9 @@ public Task ElementAtAsync (int index) }); } + /// + /// Returns the first element of this query. + /// public Task FirstAsync () { return Task.Factory.StartNew (() => { @@ -496,6 +1204,9 @@ public Task FirstAsync () }); } + /// + /// Returns the first element of this query, or null if no element is found. + /// public Task FirstOrDefaultAsync () { return Task.Factory.StartNew (() => { @@ -582,21 +1293,35 @@ public void Reset () } } + /// + /// This is a normal connection except it contains a Lock method that + /// can be used to serialize access to the database. + /// public class SQLiteConnectionWithLock : SQLiteConnection { readonly object _lockPoint = new object (); + /// + /// Initializes a new instance of the class. + /// + /// Connection string containing the DatabasePath. + /// Open flags. public SQLiteConnectionWithLock (SQLiteConnectionString connectionString, SQLiteOpenFlags openFlags) : base (connectionString.DatabasePath, openFlags, connectionString.StoreDateTimeAsTicks) { } + /// + /// Lock the database to serialize access to it. To unlock it, call Dispose + /// on the returned object. + /// + /// The lock. public IDisposable Lock () { return new LockWrapper (_lockPoint); } - private class LockWrapper : IDisposable + class LockWrapper : IDisposable { object _lockPoint; From 4e04161ffb63029ce9229f9521c74fda9f7b2478 Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Wed, 2 Aug 2017 14:03:54 -0700 Subject: [PATCH 055/103] Fix API diff test --- tests/ApiDiff/Program.cs | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/tests/ApiDiff/Program.cs b/tests/ApiDiff/Program.cs index 4b22246e6..55ec9c8e4 100644 --- a/tests/ApiDiff/Program.cs +++ b/tests/ApiDiff/Program.cs @@ -21,11 +21,13 @@ public Api (MemberInfo member, string nameSuffix) { Name = member.Name; Declaration = member.ToString (); - Index = Declaration; + Index = Declaration.Replace ("AsyncTableQuery`1", "TableQuery`1"); if (nameSuffix.Length > 0 && Name.EndsWith (nameSuffix)) { var indexName = Name.Substring (0, Name.IndexOf (nameSuffix)); - Index = taskRe.Replace (task1Re.Replace (Index.Replace (Name, indexName), "$1"), "Void$1").Replace ("System.Int32", "Int32"); + Index = taskRe + .Replace (task1Re.Replace (Index.Replace (Name, indexName), "$1"), "Void$1") + .Replace ("System.Int32", "Int32"); Name = indexName; } } @@ -49,6 +51,10 @@ class Apis "Release", "EndTransaction", + "BusyTimeout", + "GetBusyTimeout", + "SetBusyTimeoutAsync", + "GetConnection", "Handle", @@ -76,7 +82,8 @@ public Apis (Type type, string nameSuffix = "") public int DumpComparison (Apis other) { Console.ForegroundColor = ConsoleColor.Cyan; - Console.WriteLine (type.FullName); + Console.WriteLine ("## " + type.FullName); + Console.WriteLine (); var diff = new ListDiff (All, other.All, (x, y) => x.Index == y.Index); @@ -103,6 +110,7 @@ public int DumpComparison (Apis other) Console.ResetColor (); Console.WriteLine (); Console.WriteLine ($"**{n}** differences"); + Console.WriteLine (); return n; } @@ -112,9 +120,14 @@ class MainClass { public static int Main (string[] args) { - var synchronous = new Apis (typeof (SQLiteConnection)); - var asynchronous = new Apis (typeof (SQLiteAsyncConnection), "Async"); - var n = asynchronous.DumpComparison (synchronous); + var synchronousConnection = new Apis (typeof (SQLiteConnection)); + var asynchronousConnection = new Apis (typeof (SQLiteAsyncConnection), "Async"); + var n = asynchronousConnection.DumpComparison (synchronousConnection); + + //var synchronousQuery = new Apis (typeof (TableQuery<>)); + //var asynchronousQuery = new Apis (typeof (AsyncTableQuery<>), "Async"); + //n += asynchronousQuery.DumpComparison (synchronousQuery); + return n > 0 ? 1 : 0; } } From 32651389e7e8111cc61759ce6c65c4cbb190e8ff Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Wed, 2 Aug 2017 14:25:42 -0700 Subject: [PATCH 056/103] Add query ToList and ToArray implementations --- src/SQLite.cs | 52 +++++++++++++++++++++++++++++++++------------------ 1 file changed, 34 insertions(+), 18 deletions(-) diff --git a/src/SQLite.cs b/src/SQLite.cs index e7d0c76b8..b8fa7ea5d 100644 --- a/src/SQLite.cs +++ b/src/SQLite.cs @@ -3110,24 +3110,24 @@ private void AddWhere (Expression pred) } } - /// - /// Performs an inner join of two queries based on matching keys extracted from the elements. - /// - 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; - } + ///// + ///// Performs an inner join of two queries based on matching keys extracted from the elements. + ///// + //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; + //} // Not needed until Joins are supported // Keeping this commented out forces the default Linq to objects processor to run @@ -3478,6 +3478,22 @@ System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator () return GetEnumerator (); } + /// + /// Queries the database and returns the results as a List. + /// + public List ToList () + { + return GenerateCommand ("*").ExecuteQuery (); + } + + /// + /// Queries the database and returns the results as an array. + /// + public T[] ToArray () + { + return GenerateCommand ("*").ExecuteQuery ().ToArray (); + } + /// /// Returns the first element of this query. /// From 1bc15f5bc2a356dd11b3df1144928bba88211a01 Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Wed, 2 Aug 2017 14:26:19 -0700 Subject: [PATCH 057/103] Refactor db access code in async queries --- src/SQLiteAsync.cs | 50 ++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 26 deletions(-) diff --git a/src/SQLiteAsync.cs b/src/SQLiteAsync.cs index dba77cbdc..89c114a66 100644 --- a/src/SQLiteAsync.cs +++ b/src/SQLiteAsync.cs @@ -1100,6 +1100,16 @@ public AsyncTableQuery (TableQuery innerQuery) _innerQuery = innerQuery; } + Task ReadAsync (Func read) + { + return Task.Factory.StartNew (() => { + var conn = (SQLiteConnectionWithLock)_innerQuery.Connection; + using (conn.Lock ()) { + return read (conn); + } + }); + } + /// /// Filters the query based on a predicate. /// @@ -1157,15 +1167,19 @@ public AsyncTableQuery ThenByDescending (Expression> orderExpr) } /// - /// Execute the query and return the results as a list + /// Queries the database and returns the results as a List. /// public Task> ToListAsync () { - return Task.Factory.StartNew (() => { - using (((SQLiteConnectionWithLock)_innerQuery.Connection).Lock ()) { - return _innerQuery.ToList (); - } - }); + return ReadAsync (conn => _innerQuery.ToList ()); + } + + /// + /// Queries the database and returns the results as an array. + /// + public Task ToArrayAsync () + { + return ReadAsync (conn => _innerQuery.ToArray ()); } /// @@ -1173,11 +1187,7 @@ public Task> ToListAsync () /// public Task CountAsync () { - return Task.Factory.StartNew (() => { - using (((SQLiteConnectionWithLock)_innerQuery.Connection).Lock ()) { - return _innerQuery.Count (); - } - }); + return ReadAsync (conn => _innerQuery.Count ()); } /// @@ -1185,11 +1195,7 @@ public Task CountAsync () /// public Task ElementAtAsync (int index) { - return Task.Factory.StartNew (() => { - using (((SQLiteConnectionWithLock)_innerQuery.Connection).Lock ()) { - return _innerQuery.ElementAt (index); - } - }); + return ReadAsync (conn => _innerQuery.ElementAt (index)); } /// @@ -1197,11 +1203,7 @@ public Task ElementAtAsync (int index) /// public Task FirstAsync () { - return Task.Factory.StartNew (() => { - using (((SQLiteConnectionWithLock)_innerQuery.Connection).Lock ()) { - return _innerQuery.First (); - } - }); + return ReadAsync (conn => _innerQuery.First ()); } /// @@ -1209,11 +1211,7 @@ public Task FirstAsync () /// public Task FirstOrDefaultAsync () { - return Task.Factory.StartNew (() => { - using (((SQLiteConnectionWithLock)_innerQuery.Connection).Lock ()) { - return _innerQuery.FirstOrDefault (); - } - }); + return ReadAsync (conn => _innerQuery.FirstOrDefault ()); } } From 3c1a4acbbe6f36dec876d54fae1d8005c486b7fb Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Wed, 2 Aug 2017 14:28:05 -0700 Subject: [PATCH 058/103] Fully disable Joins test --- tests/JoinTest.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/JoinTest.cs b/tests/JoinTest.cs index 96969461c..65c94119b 100644 --- a/tests/JoinTest.cs +++ b/tests/JoinTest.cs @@ -77,16 +77,16 @@ class R //} //[Test] - public void WhereThenJoin () - { - var q = from ol in _db.Table () - where ol.OrderId == 1 - join o in _db.Table () on ol.OrderId equals o.Id - select new { o.Id, ol.ProductId, ol.Quantity }; + //public void WhereThenJoin () + //{ + // var q = from ol in _db.Table () + // where ol.OrderId == 1 + // join o in _db.Table () on ol.OrderId equals o.Id + // select new { o.Id, ol.ProductId, ol.Quantity }; - var r = System.Linq.Enumerable.ToList (q); + // var r = System.Linq.Enumerable.ToList (q); - Assert.AreEqual (2, r.Count); - } + // Assert.AreEqual (2, r.Count); + //} } } From 49a053772c731ef910b4eb54f8de54e63b182c31 Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Wed, 2 Aug 2017 14:41:54 -0700 Subject: [PATCH 059/103] Add missing async query methods More work on #480 --- src/SQLite.cs | 3 +++ src/SQLiteAsync.cs | 24 ++++++++++++++++++++++++ tests/ApiDiff/Program.cs | 24 +++++++++++++++++------- 3 files changed, 44 insertions(+), 7 deletions(-) diff --git a/src/SQLite.cs b/src/SQLite.cs index b8fa7ea5d..f6f23ae70 100644 --- a/src/SQLite.cs +++ b/src/SQLite.cs @@ -3460,6 +3460,9 @@ public int Count () return GenerateCommand ("count(*)").ExecuteScalar (); } + /// + /// Execute SELECT COUNT(*) on the query with an additional WHERE clause. + /// public int Count (Expression> predExpr) { return Where (predExpr).Count (); diff --git a/src/SQLiteAsync.cs b/src/SQLiteAsync.cs index 89c114a66..0b426d778 100644 --- a/src/SQLiteAsync.cs +++ b/src/SQLiteAsync.cs @@ -1190,6 +1190,14 @@ public Task CountAsync () return ReadAsync (conn => _innerQuery.Count ()); } + /// + /// Execute SELECT COUNT(*) on the query with an additional WHERE clause. + /// + public Task CountAsync (Expression> predExpr) + { + return ReadAsync (conn => _innerQuery.Count (predExpr)); + } + /// /// Returns the element at a given index /// @@ -1213,6 +1221,22 @@ public Task FirstOrDefaultAsync () { return ReadAsync (conn => _innerQuery.FirstOrDefault ()); } + + /// + /// Returns the first element of this query that matches the predicate. + /// + public Task FirstAsync (Expression> predExpr) + { + return ReadAsync (conn => _innerQuery.First (predExpr)); + } + + /// + /// Returns the first element of this query that matches the predicate. + /// + public Task FirstOrDefaultAsync (Expression> predExpr) + { + return ReadAsync (conn => _innerQuery.FirstOrDefault (predExpr)); + } } class SQLiteConnectionPool diff --git a/tests/ApiDiff/Program.cs b/tests/ApiDiff/Program.cs index 55ec9c8e4..45eedc93a 100644 --- a/tests/ApiDiff/Program.cs +++ b/tests/ApiDiff/Program.cs @@ -39,7 +39,7 @@ class Apis readonly string nameSuffix; readonly Type type; - static readonly HashSet ignores = new HashSet { + public static readonly HashSet connectionIgnores = new HashSet { "RunInTransaction", "RunInTransactionAsync", "BeginTransaction", @@ -65,7 +65,17 @@ class Apis "TableChanged", }; - public Apis (Type type, string nameSuffix = "") + public static readonly HashSet queryIgnores = new HashSet { + ".ctor", + "Clone", + "Connection", + "Deferred", + "Table", + "GetEnumerator", + "Delete", + }; + + public Apis (Type type, HashSet ignores, string nameSuffix = "") { this.type = type; this.nameSuffix = nameSuffix; @@ -120,13 +130,13 @@ class MainClass { public static int Main (string[] args) { - var synchronousConnection = new Apis (typeof (SQLiteConnection)); - var asynchronousConnection = new Apis (typeof (SQLiteAsyncConnection), "Async"); + var synchronousConnection = new Apis (typeof (SQLiteConnection), Apis.connectionIgnores); + var asynchronousConnection = new Apis (typeof (SQLiteAsyncConnection), Apis.connectionIgnores, "Async"); var n = asynchronousConnection.DumpComparison (synchronousConnection); - //var synchronousQuery = new Apis (typeof (TableQuery<>)); - //var asynchronousQuery = new Apis (typeof (AsyncTableQuery<>), "Async"); - //n += asynchronousQuery.DumpComparison (synchronousQuery); + var synchronousQuery = new Apis (typeof (TableQuery<>), Apis.queryIgnores); + var asynchronousQuery = new Apis (typeof (AsyncTableQuery<>), Apis.queryIgnores, "Async"); + n += asynchronousQuery.DumpComparison (synchronousQuery); return n > 0 ? 1 : 0; } From 0735ca45b5336b184a3182cfc8ad0e27362d3668 Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Wed, 2 Aug 2017 15:15:11 -0700 Subject: [PATCH 060/103] Fix query Delete to use the query Where clause --- src/SQLite.cs | 44 ++++++++++++++++++++--------- src/SQLiteAsync.cs | 26 +++++++++++++++++ tests/ApiDiff/Program.cs | 1 - tests/DeleteTest.cs | 60 ++++++++++++++++++++++++++++++++++++---- 4 files changed, 112 insertions(+), 19 deletions(-) diff --git a/src/SQLite.cs b/src/SQLite.cs index f6f23ae70..816b65f0d 100644 --- a/src/SQLite.cs +++ b/src/SQLite.cs @@ -2978,23 +2978,41 @@ public TableQuery Where (Expression> predExpr) } } + /// + /// Delete all the rows that match this query. + /// + public int Delete () + { + return Delete (null); + } + + /// + /// Delete all the rows that match this query and the given predicate. + /// public int Delete (Expression> predExpr) { - if (predExpr.NodeType == ExpressionType.Lambda) { - var lambda = (LambdaExpression)predExpr; - var pred = lambda.Body; - var args = new List (); - var w = CompileExpr (pred, args); - var cmdText = "delete from \"" + Table.TableName + "\""; - cmdText += " where " + w.CommandText; - var command = Connection.CreateCommand (cmdText, args.ToArray ()); + if (_limit.HasValue || _offset.HasValue) + throw new InvalidOperationException ("Cannot delete with limits or offsets"); - int result = command.ExecuteNonQuery (); - return result; - } - else { - throw new NotSupportedException ("Must be a predicate"); + if (_where == null && predExpr == null) + throw new InvalidOperationException ("No condition specified"); + + var pred = _where; + + if (predExpr != null && predExpr.NodeType == ExpressionType.Lambda) { + var lambda = (LambdaExpression)predExpr; + pred = pred != null ? Expression.AndAlso (pred, lambda.Body) : lambda.Body; } + + var args = new List (); + var cmdText = "delete from \"" + Table.TableName + "\""; + var w = CompileExpr (pred, args); + cmdText += " where " + w.CommandText; + + var command = Connection.CreateCommand (cmdText, args.ToArray ()); + + int result = command.ExecuteNonQuery (); + return result; } /// diff --git a/src/SQLiteAsync.cs b/src/SQLiteAsync.cs index 0b426d778..84d8cffb7 100644 --- a/src/SQLiteAsync.cs +++ b/src/SQLiteAsync.cs @@ -1110,6 +1110,16 @@ Task ReadAsync (Func read) }); } + Task WriteAsync (Func write) + { + return Task.Factory.StartNew (() => { + var conn = (SQLiteConnectionWithLock)_innerQuery.Connection; + using (conn.Lock ()) { + return write (conn); + } + }); + } + /// /// Filters the query based on a predicate. /// @@ -1237,6 +1247,22 @@ public Task FirstOrDefaultAsync (Expression> predExpr) { return ReadAsync (conn => _innerQuery.FirstOrDefault (predExpr)); } + + /// + /// Delete all the rows that match this query and the given predicate. + /// + public Task DeleteAsync (Expression> predExpr) + { + return WriteAsync (conn => _innerQuery.Delete (predExpr)); + } + + /// + /// Delete all the rows that match this query. + /// + public Task DeleteAsync () + { + return WriteAsync (conn => _innerQuery.Delete ()); + } } class SQLiteConnectionPool diff --git a/tests/ApiDiff/Program.cs b/tests/ApiDiff/Program.cs index 45eedc93a..5c16dd664 100644 --- a/tests/ApiDiff/Program.cs +++ b/tests/ApiDiff/Program.cs @@ -72,7 +72,6 @@ class Apis "Deferred", "Table", "GetEnumerator", - "Delete", }; public Apis (Type type, HashSet ignores, string nameSuffix = "") diff --git a/tests/DeleteTest.cs b/tests/DeleteTest.cs index 81a42d88e..ac77cfa93 100644 --- a/tests/DeleteTest.cs +++ b/tests/DeleteTest.cs @@ -81,27 +81,77 @@ public void DeleteAll () } [Test] - public void DeleteAllWithPredicate() + public void DeleteWithPredicate() { var db = CreateDb(); - var r = db.Table().Delete(p => p.Test == "Hello World"); + var r = db.Table().Delete (p => p.Test == "Hello World"); Assert.AreEqual (Count, r); Assert.AreEqual (0, db.Table ().Count ()); } [Test] - public void DeleteAllWithPredicateHalf() + public void DeleteWithPredicateHalf() { var db = CreateDb(); - db.Insert(new TestTable() { Datum = 1, Test = "Hello World 2" }); + db.Insert(new TestTable() { Datum = 1, Test = "Hello World 2" }); - var r = db.Table().Delete(p => p.Test == "Hello World"); + var r = db.Table().Delete (p => p.Test == "Hello World"); Assert.AreEqual (Count, r); Assert.AreEqual (1, db.Table ().Count ()); } + + [Test] + public void DeleteWithWherePredicate () + { + var db = CreateDb (); + + var r = db.Table ().Where (p => p.Test == "Hello World").Delete (); + + Assert.AreEqual (Count, r); + Assert.AreEqual (0, db.Table ().Count ()); + } + + [Test] + public void DeleteWithoutPredicate () + { + var db = CreateDb (); + + try { + var r = db.Table ().Delete (); + Assert.Fail (); + } + catch (InvalidOperationException) { + } + } + + [Test] + public void DeleteWithTake () + { + var db = CreateDb (); + + try { + var r = db.Table ().Where (p => p.Test == "Hello World").Take (2).Delete (); + Assert.Fail (); + } + catch (InvalidOperationException) { + } + } + + [Test] + public void DeleteWithSkip () + { + var db = CreateDb (); + + try { + var r = db.Table ().Where (p => p.Test == "Hello World").Skip (2).Delete (); + Assert.Fail (); + } + catch (InvalidOperationException) { + } + } } } From d96e30fa916ac5ccd96df7260e5b1eea36d4f9ba Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Wed, 2 Aug 2017 17:03:00 -0700 Subject: [PATCH 061/103] Fix the return value of CreateTable It now indicates whether the table was created or migrated. Turns out, sqlite3_changes doesn't work with create table. Fixes #541 Fixes #461 --- src/SQLite.cs | 77 ++++++++++++++++++++++++++------------------- src/SQLiteAsync.cs | 18 +++++------ tests/AsyncTests.cs | 27 +++++++++++++++- 3 files changed, 79 insertions(+), 43 deletions(-) diff --git a/src/SQLite.cs b/src/SQLite.cs index 816b65f0d..8a21bcc3d 100644 --- a/src/SQLite.cs +++ b/src/SQLite.cs @@ -412,9 +412,9 @@ public int DropTable (TableMapping map) /// later access this schema by calling GetMapping. /// /// - /// The number of entries added to the database schema. + /// Whether the table was created or migrated. /// - public int CreateTable (CreateFlags createFlags = CreateFlags.None) + public CreateTableResult CreateTable (CreateFlags createFlags = CreateFlags.None) { return CreateTable (typeof (T), createFlags); } @@ -428,9 +428,9 @@ public int CreateTable (CreateFlags createFlags = CreateFlags.None) /// 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. + /// Whether the table was created or migrated. /// - public int CreateTable (Type ty, CreateFlags createFlags = CreateFlags.None) + public CreateTableResult CreateTable (Type ty, CreateFlags createFlags = CreateFlags.None) { if (_tables == null) { _tables = new Dictionary (); @@ -446,25 +446,32 @@ public int CreateTable (Type ty, CreateFlags createFlags = CreateFlags.None) throw new Exception (string.Format ("Cannot create a table with zero columns (does '{0}' have public properties?)", ty.FullName)); } - // Facilitate virtual tables a.k.a. full-text search. - bool fts3 = (createFlags & CreateFlags.FullTextSearch3) != 0; - bool fts4 = (createFlags & CreateFlags.FullTextSearch4) != 0; - bool fts = fts3 || fts4; - var @virtual = fts ? "virtual " : string.Empty; - var @using = fts3 ? "using fts3 " : fts4 ? "using fts4 " : string.Empty; + // Check if the table exists + var result = CreateTableResult.Created; + var existingCols = GetTableInfo (map.TableName); - // Build query. - var query = "create " + @virtual + "table if not exists \"" + map.TableName + "\" " + @using + "(\n"; - var decls = map.Columns.Select (p => Orm.SqlDecl (p, StoreDateTimeAsTicks)); - var decl = string.Join (",\n", decls.ToArray ()); - query += decl; - query += ")"; + // Create or migrate it + if (existingCols.Count == 0) { - var count = Execute (query); + // Facilitate virtual tables a.k.a. full-text search. + bool fts3 = (createFlags & CreateFlags.FullTextSearch3) != 0; + bool fts4 = (createFlags & CreateFlags.FullTextSearch4) != 0; + bool fts = fts3 || fts4; + var @virtual = fts ? "virtual " : string.Empty; + var @using = fts3 ? "using fts3 " : fts4 ? "using fts4 " : string.Empty; + + // Build query. + var query = "create " + @virtual + "table if not exists \"" + map.TableName + "\" " + @using + "(\n"; + var decls = map.Columns.Select (p => Orm.SqlDecl (p, StoreDateTimeAsTicks)); + var decl = string.Join (",\n", decls.ToArray ()); + query += decl; + query += ")"; - if (count == 0) { //Possible bug: This always seems to return 0? - // Table already exists, migrate it - MigrateTable (map); + Execute (query); + } + else { + result = CreateTableResult.Migrated; + MigrateTable (map, existingCols); } var indexes = new Dictionary (); @@ -495,10 +502,10 @@ public int CreateTable (Type ty, CreateFlags createFlags = CreateFlags.None) foreach (var indexName in indexes.Keys) { var index = indexes[indexName]; var columns = index.Columns.OrderBy (i => i.Order).Select (i => i.ColumnName).ToArray (); - count += CreateIndex (indexName, index.TableName, columns, index.Unique); + CreateIndex (indexName, index.TableName, columns, index.Unique); } - return count; + return result; } /// @@ -508,7 +515,7 @@ public int CreateTable (Type ty, CreateFlags createFlags = CreateFlags.None) /// later access this schema by calling GetMapping. /// /// - /// The number of entries added to the database schema for each type. + /// Whether the table was created or migrated for each type. /// public CreateTablesResult CreateTables (CreateFlags createFlags = CreateFlags.None) where T : new() @@ -524,7 +531,7 @@ public CreateTablesResult CreateTables (CreateFlags createFlags = CreateF /// later access this schema by calling GetMapping. /// /// - /// The number of entries added to the database schema for each type. + /// Whether the table was created or migrated for each type. /// public CreateTablesResult CreateTables (CreateFlags createFlags = CreateFlags.None) where T : new() @@ -541,7 +548,7 @@ public CreateTablesResult CreateTables (CreateFlags createFlags = Cre /// later access this schema by calling GetMapping. /// /// - /// The number of entries added to the database schema for each type. + /// Whether the table was created or migrated for each type. /// public CreateTablesResult CreateTables (CreateFlags createFlags = CreateFlags.None) where T : new() @@ -559,7 +566,7 @@ public CreateTablesResult CreateTables (CreateFlags createFlags = /// later access this schema by calling GetMapping. /// /// - /// The number of entries added to the database schema for each type. + /// Whether the table was created or migrated for each type. /// public CreateTablesResult CreateTables (CreateFlags createFlags = CreateFlags.None) where T : new() @@ -578,13 +585,13 @@ public CreateTablesResult CreateTables (CreateFlags createFla /// later access this schema by calling GetMapping. /// /// - /// The number of entries added to the database schema for each type. + /// Whether the table was created or migrated for each type. /// public CreateTablesResult CreateTables (CreateFlags createFlags = CreateFlags.None, params Type[] types) { var result = new CreateTablesResult (); foreach (Type type in types) { - int aResult = CreateTable (type, createFlags); + var aResult = CreateTable (type, createFlags); result.Results[type] = aResult; } return result; @@ -700,10 +707,8 @@ public List GetTableInfo (string tableName) return Query (query); } - void MigrateTable (TableMapping map) + void MigrateTable (TableMapping map, List existingCols) { - var existingCols = GetTableInfo (map.TableName); - var toBeAdded = new List (); foreach (var p in map.Columns) { @@ -2892,13 +2897,19 @@ private void Dispose (bool disposing) } } + public enum CreateTableResult + { + Created, + Migrated, + } + public class CreateTablesResult { - public Dictionary Results { get; private set; } + public Dictionary Results { get; private set; } public CreateTablesResult () { - Results = new Dictionary (); + Results = new Dictionary (); } } diff --git a/src/SQLiteAsync.cs b/src/SQLiteAsync.cs index 84d8cffb7..cf9d0765a 100644 --- a/src/SQLiteAsync.cs +++ b/src/SQLiteAsync.cs @@ -211,9 +211,9 @@ public Task EnableLoadExtensionAsync (bool enabled) /// later access this schema by calling GetMapping. /// /// - /// The number of entries added to the database schema. + /// Whether the table was created or migrated. /// - public Task CreateTableAsync (CreateFlags createFlags = CreateFlags.None) + public Task CreateTableAsync (CreateFlags createFlags = CreateFlags.None) where T : new() { return WriteAsync (conn => conn.CreateTable (createFlags)); @@ -228,9 +228,9 @@ public Task CreateTableAsync (CreateFlags createFlags = CreateFlags.None /// 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. + /// Whether the table was created or migrated. /// - public Task CreateTableAsync (Type ty, CreateFlags createFlags = CreateFlags.None) + public Task CreateTableAsync (Type ty, CreateFlags createFlags = CreateFlags.None) { return WriteAsync (conn => conn.CreateTable (ty, createFlags)); } @@ -242,7 +242,7 @@ public Task CreateTableAsync (Type ty, CreateFlags createFlags = CreateFlag /// later access this schema by calling GetMapping. /// /// - /// The number of entries added to the database schema for each type. + /// Whether the table was created or migrated for each type. /// public Task CreateTablesAsync (CreateFlags createFlags = CreateFlags.None) where T : new() @@ -258,7 +258,7 @@ public Task CreateTablesAsync (CreateFlags createFlag /// later access this schema by calling GetMapping. /// /// - /// The number of entries added to the database schema for each type. + /// Whether the table was created or migrated for each type. /// public Task CreateTablesAsync (CreateFlags createFlags = CreateFlags.None) where T : new() @@ -275,7 +275,7 @@ public Task CreateTablesAsync (CreateFlags create /// later access this schema by calling GetMapping. /// /// - /// The number of entries added to the database schema for each type. + /// Whether the table was created or migrated for each type. /// public Task CreateTablesAsync (CreateFlags createFlags = CreateFlags.None) where T : new() @@ -293,7 +293,7 @@ public Task CreateTablesAsync (CreateFlags cr /// later access this schema by calling GetMapping. /// /// - /// The number of entries added to the database schema for each type. + /// Whether the table was created or migrated for each type. /// public Task CreateTablesAsync (CreateFlags createFlags = CreateFlags.None) where T : new() @@ -312,7 +312,7 @@ public Task CreateTablesAsync (CreateFlag /// later access this schema by calling GetMapping. /// /// - /// The number of entries added to the database schema for each type. + /// Whether the table was created or migrated for each type. /// public Task CreateTablesAsync (CreateFlags createFlags = CreateFlags.None, params Type[] types) { diff --git a/tests/AsyncTests.cs b/tests/AsyncTests.cs index 15fd2b751..ed621103f 100644 --- a/tests/AsyncTests.cs +++ b/tests/AsyncTests.cs @@ -31,7 +31,7 @@ public class Customer [MaxLength (64)] public string LastName { get; set; } - [MaxLength (64)] + [MaxLength (64), Indexed] public string Email { get; set; } } @@ -817,5 +817,30 @@ public void TestAsyncGetWithExpression() // check... Assert.AreEqual("7", loaded.FirstName); } + + [Test] + public void CreateTable () + { + var conn = GetConnection (); + + var trace = new List (); + conn.Tracer = trace.Add; + conn.Trace = true; + + var r0 = conn.CreateTableAsync ().Result; + + Assert.AreEqual (CreateTableResult.Created, r0); + + var r1 = conn.CreateTableAsync ().Result; + + Assert.AreEqual (CreateTableResult.Migrated, r1); + + var r2 = conn.CreateTableAsync ().Result; + + Assert.AreEqual (CreateTableResult.Migrated, r1); + + Assert.AreEqual (7, trace.Count); + } + } } From 3d1ffc160058168920ba0f8026d72eb92fa214fb Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Wed, 2 Aug 2017 17:20:48 -0700 Subject: [PATCH 062/103] Add CreateFlags doc comments --- src/SQLite.cs | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/src/SQLite.cs b/src/SQLite.cs index 8a21bcc3d..5ea4df6b0 100644 --- a/src/SQLite.cs +++ b/src/SQLite.cs @@ -129,12 +129,31 @@ public enum SQLiteOpenFlags public enum CreateFlags { None = 0x000, - ImplicitPK = 0x001, // create a primary key for field called 'Id' (Orm.ImplicitPkName) - ImplicitIndex = 0x002, // create an index for fields ending in 'Id' (Orm.ImplicitIndexSuffix) - AllImplicit = 0x003, // do both above - AutoIncPK = 0x004, // force PK field to be auto inc - FullTextSearch3 = 0x100, // create virtual table using FTS3 - FullTextSearch4 = 0x200 // create virtual table using FTS4 + /// + /// Create a primary key for properties called 'Id' + /// + ImplicitPK = 0x001, + /// + /// Create an index for properties ending in 'Id' + /// + ImplicitIndex = 0x002, + /// + /// Create a primary key for properties called 'Id' and + /// create an index for properties ending in 'Id'. + /// + AllImplicit = 0x003, + /// + /// Force the primary key to be auto incrementing + /// + AutoIncPK = 0x004, + /// + /// Create virtual table using FTS3 + /// + FullTextSearch3 = 0x100, + /// + /// Create virtual table using FTS4 + /// + FullTextSearch4 = 0x200 } /// From 8988f2552e8e3822daa6866dde3c53d9d2cd95a8 Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Wed, 2 Aug 2017 17:28:43 -0700 Subject: [PATCH 063/103] Add test for scalar queries with 1 row Repro for #496 --- tests/ScalarTest.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/ScalarTest.cs b/tests/ScalarTest.cs index 339d073cd..757024f02 100644 --- a/tests/ScalarTest.cs +++ b/tests/ScalarTest.cs @@ -45,6 +45,16 @@ public void Int32 () Assert.AreEqual (Count * 2, r); } + + [Test] + public void SelectSingleRowValue () + { + var db = CreateDb (); + + var r = db.ExecuteScalar ("SELECT Two FROM TestTable WHERE Id = 1 LIMIT 1"); + + Assert.AreEqual (2, r); + } } } From 9209ea9e0585e05f223e9e62d8bfcbbfab6754d4 Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Wed, 2 Aug 2017 18:03:36 -0700 Subject: [PATCH 064/103] Improve the doc comments for CreateFlags Trying to avoid confusion as in #421 --- src/SQLite.cs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/SQLite.cs b/src/SQLite.cs index 5ea4df6b0..d5faff908 100644 --- a/src/SQLite.cs +++ b/src/SQLite.cs @@ -128,22 +128,28 @@ public enum SQLiteOpenFlags [Flags] public enum CreateFlags { + /// + /// Use the default creation options + /// None = 0x000, /// - /// Create a primary key for properties called 'Id' + /// Create a primary key index for a property called 'Id' (case-insensitive). + /// This avoids the need for the [PrimaryKey] attribute. /// ImplicitPK = 0x001, /// - /// Create an index for properties ending in 'Id' + /// Create indices for properties ending in 'Id' (case-insensitive). /// ImplicitIndex = 0x002, /// - /// Create a primary key for properties called 'Id' and - /// create an index for properties ending in 'Id'. + /// Create a primary key for a property called 'Id' and + /// create an indices for properties ending in 'Id' (case-insensitive). /// AllImplicit = 0x003, /// - /// Force the primary key to be auto incrementing + /// Force the primary key property to be auto incrementing. + /// This avoids the need for the [AutoIncrement] attribute. + /// The primary key property on the class should have type int or long. /// AutoIncPK = 0x004, /// From 4d5167c1dc337a1f0938c908b0fbe6184b16dd05 Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Wed, 2 Aug 2017 18:24:19 -0700 Subject: [PATCH 065/103] Add doc comments to CollationAttribute Help with #417 --- src/SQLite.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/SQLite.cs b/src/SQLite.cs index d5faff908..ac87fdbd4 100644 --- a/src/SQLite.cs +++ b/src/SQLite.cs @@ -2006,6 +2006,11 @@ public MaxLengthAttribute (int length) } } + /// + /// Select the collating sequence to use on a column. + /// "BINARY", "NOCASE", and "RTRIM" are supported. + /// "BINARY" is the default. + /// [AttributeUsage (AttributeTargets.Property)] public class CollationAttribute : Attribute { From 4cb1296073b9e8b721a6ddb2c26ae03edd2a6130 Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Wed, 2 Aug 2017 18:41:58 -0700 Subject: [PATCH 066/103] Improve doc comments for Insert Make it clear what the return value is. Fixes #312 --- src/SQLite.cs | 30 ++++++++++++++++++------------ src/SQLiteAsync.cs | 25 +++++++++++++++---------- 2 files changed, 33 insertions(+), 22 deletions(-) diff --git a/src/SQLite.cs b/src/SQLite.cs index ac87fdbd4..fccf65159 100644 --- a/src/SQLite.cs +++ b/src/SQLite.cs @@ -1436,8 +1436,9 @@ public int InsertAll (System.Collections.IEnumerable objects, Type objType, bool } /// - /// Inserts the given object and retrieves its - /// auto incremented primary key if it has one. + /// Inserts the given object (and updates its + /// auto incremented primary key if it has one). + /// The return value is the number of rows added to the table. /// /// /// The object to insert. @@ -1454,8 +1455,9 @@ public int Insert (object obj) } /// - /// Inserts the given object and retrieves its - /// auto incremented primary key if it has one. + /// Inserts the given object (and updates its + /// auto incremented primary key if it has one). + /// The return value is the number of rows added to the table. /// If a UNIQUE constraint violation occurs with /// some pre-existing object, this function deletes /// the old object. @@ -1475,8 +1477,9 @@ public int InsertOrReplace (object obj) } /// - /// Inserts the given object and retrieves its - /// auto incremented primary key if it has one. + /// Inserts the given object (and updates its + /// auto incremented primary key if it has one). + /// The return value is the number of rows added to the table. /// /// /// The object to insert. @@ -1493,8 +1496,9 @@ public int Insert (object obj, Type objType) } /// - /// Inserts the given object and retrieves its - /// auto incremented primary key if it has one. + /// Inserts the given object (and updates its + /// auto incremented primary key if it has one). + /// The return value is the number of rows added to the table. /// If a UNIQUE constraint violation occurs with /// some pre-existing object, this function deletes /// the old object. @@ -1514,8 +1518,9 @@ public int InsertOrReplace (object obj, Type objType) } /// - /// Inserts the given object and retrieves its - /// auto incremented primary key if it has one. + /// Inserts the given object (and updates its + /// auto incremented primary key if it has one). + /// The return value is the number of rows added to the table. /// /// /// The object to insert. @@ -1535,8 +1540,9 @@ public int Insert (object obj, string extra) } /// - /// Inserts the given object and retrieves its - /// auto incremented primary key if it has one. + /// Inserts the given object (and updates its + /// auto incremented primary key if it has one). + /// The return value is the number of rows added to the table. /// /// /// The object to insert. diff --git a/src/SQLiteAsync.cs b/src/SQLiteAsync.cs index cf9d0765a..e7bb3e2da 100644 --- a/src/SQLiteAsync.cs +++ b/src/SQLiteAsync.cs @@ -413,8 +413,9 @@ public Task InsertAsync (object obj) } /// - /// Inserts the given object and retrieves its - /// auto incremented primary key if it has one. + /// Inserts the given object (and updates its + /// auto incremented primary key if it has one). + /// The return value is the number of rows added to the table. /// /// /// The object to insert. @@ -431,8 +432,9 @@ public Task InsertAsync (object obj, Type objType) } /// - /// Inserts the given object and retrieves its - /// auto incremented primary key if it has one. + /// Inserts the given object (and updates its + /// auto incremented primary key if it has one). + /// The return value is the number of rows added to the table. /// /// /// The object to insert. @@ -449,8 +451,9 @@ public Task InsertAsync (object obj, string extra) } /// - /// Inserts the given object and retrieves its - /// auto incremented primary key if it has one. + /// Inserts the given object (and updates its + /// auto incremented primary key if it has one). + /// The return value is the number of rows added to the table. /// /// /// The object to insert. @@ -470,8 +473,9 @@ public Task InsertAsync (object obj, string extra, Type objType) } /// - /// Inserts the given object and retrieves its - /// auto incremented primary key if it has one. + /// Inserts the given object (and updates its + /// auto incremented primary key if it has one). + /// The return value is the number of rows added to the table. /// If a UNIQUE constraint violation occurs with /// some pre-existing object, this function deletes /// the old object. @@ -488,8 +492,9 @@ public Task InsertOrReplaceAsync (object obj) } /// - /// Inserts the given object and retrieves its - /// auto incremented primary key if it has one. + /// Inserts the given object (and updates its + /// auto incremented primary key if it has one). + /// The return value is the number of rows added to the table. /// If a UNIQUE constraint violation occurs with /// some pre-existing object, this function deletes /// the old object. From 20ad2780178a831c2c7a8971e1cbe1582c60bb42 Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Thu, 3 Aug 2017 17:25:25 -0700 Subject: [PATCH 067/103] Switch to using ListDiff nuget --- tests/ApiDiff/ApiDiff.csproj | 7 +- tests/ApiDiff/ListDiff.cs | 196 ---------------------------------- tests/ApiDiff/Program.cs | 2 +- tests/ApiDiff/packages.config | 4 + 4 files changed, 11 insertions(+), 198 deletions(-) delete mode 100644 tests/ApiDiff/ListDiff.cs create mode 100644 tests/ApiDiff/packages.config diff --git a/tests/ApiDiff/ApiDiff.csproj b/tests/ApiDiff/ApiDiff.csproj index 3fa23b0b8..577d53938 100644 --- a/tests/ApiDiff/ApiDiff.csproj +++ b/tests/ApiDiff/ApiDiff.csproj @@ -30,6 +30,9 @@ + + ..\..\packages\ListDiff.1.0.7\lib\netstandard1.0\ListDiff.dll + @@ -40,7 +43,9 @@ SQLiteAsync.cs - + + + \ No newline at end of file diff --git a/tests/ApiDiff/ListDiff.cs b/tests/ApiDiff/ListDiff.cs deleted file mode 100644 index a8cda060a..000000000 --- a/tests/ApiDiff/ListDiff.cs +++ /dev/null @@ -1,196 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Praeclarum -{ - /// - /// The type of . - /// - public enum ListDiffActionType - { - /// - /// Update the SourceItem to make it like the DestinationItem - /// - Update, - /// - /// Add the DestinationItem - /// - Add, - /// - /// Remove the SourceItem - /// - Remove, - } - - /// - /// A action that can be one of: Update, Add, or Remove. - /// - /// The type of the source list elements - /// The type of the destination list elements - public class ListDiffAction - { - public ListDiffActionType ActionType; - public S SourceItem; - public D DestinationItem; - - public ListDiffAction(ListDiffActionType type, S source, D dest) - { - ActionType = type; - SourceItem = source; - DestinationItem = dest; - } - - public override string ToString() - { - return string.Format("{0} {1} {2}", ActionType, SourceItem, DestinationItem); - } - } - - /// - /// Finds a diff between two lists (that contain possibly different types). - /// are generated such that the order of items in the - /// destination list is preserved. - /// The algorithm is from: http://en.wikipedia.org/wiki/Longest_common_subsequence_problem - /// - /// The type of the source list elements - /// The type of the destination list elements - public class ListDiff - { - /// - /// The actions needed to transform a source list to a destination list. - /// - public List> Actions { get; private set; } - - /// - /// Whether the only contain Update actions - /// (no Adds or Removes). - /// - public bool ContainsOnlyUpdates { get; private set; } - - public ListDiff(IEnumerable sources, IEnumerable destinations) - : this (sources, destinations, (a,b) => a.Equals (b)) - { - } - - public ListDiff(IEnumerable sources, - IEnumerable destinations, - Func match) - { - if (sources == null) throw new ArgumentNullException("sources"); - if (destinations == null) throw new ArgumentNullException("destinations"); - if (match == null) throw new ArgumentNullException("match"); - - var x = new List(sources); - var y = new List(destinations); - - Actions = new List>(); - - var m = x.Count; - var n = y.Count; - - // - // Construct the C matrix - // - var c = new int[m + 1, n + 1]; - for (var i = 1; i <= m; i++) - { - for (var j = 1; j <= n; j++) - { - if (match(x[i - 1], y[j - 1])) - { - c[i, j] = c[i - 1, j - 1] + 1; - } - else - { - c[i, j] = Math.Max(c[i, j - 1], c[i - 1, j]); - } - } - } - - // - // Generate the actions - // - ContainsOnlyUpdates = true; - GenDiff(c, x, y, m, n, match); - } - - void GenDiff(int[,] c, List x, List y, int i, int j, Func match) - { - if (i > 0 && j > 0 && match(x[i - 1], y[j - 1])) - { - GenDiff(c, x, y, i - 1, j - 1, match); - Actions.Add(new ListDiffAction(ListDiffActionType.Update, x[i - 1], y[j - 1])); - } - else - { - if (j > 0 && (i == 0 || c[i, j - 1] >= c[i - 1, j])) - { - GenDiff(c, x, y, i, j - 1, match); - ContainsOnlyUpdates = false; - Actions.Add(new ListDiffAction(ListDiffActionType.Add, default(S), y[j - 1])); - } - else if (i > 0 && (j == 0 || c[i, j - 1] < c[i - 1, j])) - { - GenDiff(c, x, y, i - 1, j, match); - ContainsOnlyUpdates = false; - Actions.Add(new ListDiffAction(ListDiffActionType.Remove, x[i - 1], default(D))); - } - } - } - } - - public static class ListDiffEx - { - public static ListDiff MergeInto (this IList source, IEnumerable destination, Func match) - { - var diff = new ListDiff (source, destination, match); - - var p = 0; - - foreach (var a in diff.Actions) { - if (a.ActionType == ListDiffActionType.Add) { - source.Insert (p, a.DestinationItem); - p++; - } else if (a.ActionType == ListDiffActionType.Remove) { - source.RemoveAt (p); - } else { - p++; - } - } - - return diff; - } - - public static ListDiff MergeInto (this IList source, IEnumerable destination, Func match, Func create, Action update, Action delete) - { - var diff = new ListDiff (source, destination, match); - - var p = 0; - - foreach (var a in diff.Actions) { - if (a.ActionType == ListDiffActionType.Add) { - source.Insert (p, create (a.DestinationItem)); - p++; - } else if (a.ActionType == ListDiffActionType.Remove) { - delete (a.SourceItem); - source.RemoveAt (p); - } else { - update (a.SourceItem, a.DestinationItem); - p++; - } - } - - return diff; - } - - public static ListDiff Diff (this IEnumerable sources, IEnumerable destinations) - { - return new ListDiff (sources, destinations); - } - - public static ListDiff Diff (this IEnumerable sources, IEnumerable destinations, Func match) - { - return new ListDiff (sources, destinations, match); - } - } -} diff --git a/tests/ApiDiff/Program.cs b/tests/ApiDiff/Program.cs index 5c16dd664..b0679103f 100644 --- a/tests/ApiDiff/Program.cs +++ b/tests/ApiDiff/Program.cs @@ -4,7 +4,7 @@ using System.Linq; using System.Text.RegularExpressions; using SQLite; -using Praeclarum; +using ListDiff; namespace ApiDiff { diff --git a/tests/ApiDiff/packages.config b/tests/ApiDiff/packages.config new file mode 100644 index 000000000..48073015f --- /dev/null +++ b/tests/ApiDiff/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file From 1158308375a938e6ce35e6fb2a090f5ad8c3fd9c Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Fri, 4 Aug 2017 16:14:18 -0700 Subject: [PATCH 068/103] Add SQLite-net-base project --- SQLite.sln | 14 +++++++++++ nuget/SQLite-net-base/SQLite-net-base.csproj | 25 +++++++++++++++++++ nuget/SQLite-net-std/SQLite-net-std.csproj | 2 +- nuget/SQLite-net/SQLite-net.csproj | 6 ++--- nuget/SQLite-net/packages.config | 4 +-- sqlite-net-pcl.nuspec | 26 ++++++++++---------- src/SQLite.cs | 2 +- 7 files changed, 59 insertions(+), 20 deletions(-) create mode 100644 nuget/SQLite-net-base/SQLite-net-base.csproj diff --git a/SQLite.sln b/SQLite.sln index 97cc8b237..03454fd0b 100644 --- a/SQLite.sln +++ b/SQLite.sln @@ -20,6 +20,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SQLite-net-std", "nuget\SQL EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ApiDiff", "tests\ApiDiff\ApiDiff.csproj", "{1DEF735C-B973-4ED9-8446-7FFA6D0B410B}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SQLite-net-base", "nuget\SQLite-net-base\SQLite-net-base.csproj", "{53D1953C-3641-47D0-BE08-14DB853CC576}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -90,6 +92,18 @@ Global {1DEF735C-B973-4ED9-8446-7FFA6D0B410B}.Release|iPhoneSimulator.Build.0 = Release|Any CPU {1DEF735C-B973-4ED9-8446-7FFA6D0B410B}.Debug|iPhone.ActiveCfg = Debug|Any CPU {1DEF735C-B973-4ED9-8446-7FFA6D0B410B}.Debug|iPhone.Build.0 = Debug|Any CPU + {53D1953C-3641-47D0-BE08-14DB853CC576}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {53D1953C-3641-47D0-BE08-14DB853CC576}.Debug|Any CPU.Build.0 = Debug|Any CPU + {53D1953C-3641-47D0-BE08-14DB853CC576}.Release|Any CPU.ActiveCfg = Release|Any CPU + {53D1953C-3641-47D0-BE08-14DB853CC576}.Release|Any CPU.Build.0 = Release|Any CPU + {53D1953C-3641-47D0-BE08-14DB853CC576}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {53D1953C-3641-47D0-BE08-14DB853CC576}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {53D1953C-3641-47D0-BE08-14DB853CC576}.Release|iPhone.ActiveCfg = Release|Any CPU + {53D1953C-3641-47D0-BE08-14DB853CC576}.Release|iPhone.Build.0 = Release|Any CPU + {53D1953C-3641-47D0-BE08-14DB853CC576}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {53D1953C-3641-47D0-BE08-14DB853CC576}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {53D1953C-3641-47D0-BE08-14DB853CC576}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {53D1953C-3641-47D0-BE08-14DB853CC576}.Debug|iPhone.Build.0 = Debug|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {6947A8F1-99BE-4DD1-AD4D-D89425CE67A2} = {FECC0E44-E626-49CB-BD8B-0CFBD93FBEFF} diff --git a/nuget/SQLite-net-base/SQLite-net-base.csproj b/nuget/SQLite-net-base/SQLite-net-base.csproj new file mode 100644 index 000000000..1fcecb6da --- /dev/null +++ b/nuget/SQLite-net-base/SQLite-net-base.csproj @@ -0,0 +1,25 @@ + + + + netstandard1.1 + + + + TRACE;USE_SQLITEPCL_RAW;DEBUG;NETSTANDARD1_1;NO_SQLITEPCL_RAW_BATTERIES + + + + TRACE;USE_SQLITEPCL_RAW;RELEASE;NETSTANDARD1_1;NO_SQLITEPCL_RAW_BATTERIES + + + + SQLite.cs + + + SQLiteAsync.cs + + + + + + diff --git a/nuget/SQLite-net-std/SQLite-net-std.csproj b/nuget/SQLite-net-std/SQLite-net-std.csproj index 347506fe5..13113ea96 100644 --- a/nuget/SQLite-net-std/SQLite-net-std.csproj +++ b/nuget/SQLite-net-std/SQLite-net-std.csproj @@ -15,7 +15,7 @@ TRACE;USE_SQLITEPCL_RAW;DEBUG;NETSTANDARD1_1 - + diff --git a/nuget/SQLite-net/SQLite-net.csproj b/nuget/SQLite-net/SQLite-net.csproj index 4a70441cd..9f744df96 100644 --- a/nuget/SQLite-net/SQLite-net.csproj +++ b/nuget/SQLite-net/SQLite-net.csproj @@ -56,13 +56,13 @@ - ..\..\packages\SQLitePCLRaw.core.1.1.7\lib\portable-net45+netcore45+wpa81+wp8+MonoAndroid10+MonoTouch10+Xamarin.iOS10\SQLitePCLRaw.core.dll + ..\..\packages\SQLitePCLRaw.core.1.1.8\lib\portable-net45+netcore45+wpa81+wp8+MonoAndroid10+MonoTouch10+Xamarin.iOS10\SQLitePCLRaw.core.dll - ..\..\packages\SQLitePCLRaw.bundle_green.1.1.7\lib\portable-net45+netcore45+wpa81+wp8+MonoAndroid10+MonoTouch10+Xamarin.iOS10\SQLitePCLRaw.batteries_green.dll + ..\..\packages\SQLitePCLRaw.bundle_green.1.1.8\lib\portable-net45+netcore45+wpa81+wp8+MonoAndroid10+MonoTouch10+Xamarin.iOS10\SQLitePCLRaw.batteries_green.dll - ..\..\packages\SQLitePCLRaw.bundle_green.1.1.7\lib\portable-net45+netcore45+wpa81+wp8+MonoAndroid10+MonoTouch10+Xamarin.iOS10\SQLitePCLRaw.batteries_v2.dll + ..\..\packages\SQLitePCLRaw.bundle_green.1.1.8\lib\portable-net45+netcore45+wpa81+wp8+MonoAndroid10+MonoTouch10+Xamarin.iOS10\SQLitePCLRaw.batteries_v2.dll diff --git a/nuget/SQLite-net/packages.config b/nuget/SQLite-net/packages.config index 04db2ed75..10c88ccf8 100644 --- a/nuget/SQLite-net/packages.config +++ b/nuget/SQLite-net/packages.config @@ -1,5 +1,5 @@  - - + + \ No newline at end of file diff --git a/sqlite-net-pcl.nuspec b/sqlite-net-pcl.nuspec index ac95ec65e..f9c2286eb 100644 --- a/sqlite-net-pcl.nuspec +++ b/sqlite-net-pcl.nuspec @@ -20,44 +20,44 @@ - + - + - + - + - + - + - + - + - + - + - + - + - + diff --git a/src/SQLite.cs b/src/SQLite.cs index fccf65159..32a7329e2 100644 --- a/src/SQLite.cs +++ b/src/SQLite.cs @@ -212,7 +212,7 @@ public partial class SQLiteConnection : IDisposable /// public bool StoreDateTimeAsTicks { get; private set; } -#if USE_SQLITEPCL_RAW +#if USE_SQLITEPCL_RAW && !NO_SQLITEPCL_RAW_BATTERIES static SQLiteConnection () { SQLitePCL.Batteries_V2.Init (); From 46310ce3ba334849dbb6bb332e8790bbe7a0430c Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Fri, 4 Aug 2017 16:32:02 -0700 Subject: [PATCH 069/103] Add Base nuspec --- Makefile | 6 +- SQLite.sln | 1 + nuget/SQLite-net-base/SQLite-net-base.csproj | 3 +- sqlite-net-base.nuspec | 67 ++++++++++++++++++++ sqlite-net-pcl.nuspec | 6 +- sqlite-net.nuspec | 6 +- 6 files changed, 77 insertions(+), 12 deletions(-) create mode 100644 sqlite-net-base.nuspec diff --git a/Makefile b/Makefile index 48eedeb19..af011dfcf 100644 --- a/Makefile +++ b/Makefile @@ -13,7 +13,7 @@ tests/bin/Debug/SQLite.Tests.dll: tests/SQLite.Tests.csproj $(SRC) tests/ApiDiff/bin/Debug/ApiDiff.exe: tests/ApiDiff/ApiDiff.csproj $(SRC) msbuild tests/ApiDiff/ApiDiff.csproj -nuget: srcnuget pclnuget +nuget: srcnuget pclnuget basenuget packages: nuget/SQLite-net/packages.config nuget restore SQLite.sln @@ -26,3 +26,7 @@ pclnuget: sqlite-net-pcl.nuspec packages $(SRC) msbuild "/p:Configuration=Release" nuget/SQLite-net-std/SQLite-net-std.csproj nuget pack sqlite-net-pcl.nuspec -o .\ +basenuget: sqlite-net-pcl.nuspec packages $(SRC) + msbuild "/p:Configuration=Release" nuget/SQLite-net-base/SQLite-net-base.csproj + nuget pack sqlite-net-base.nuspec -o .\ + diff --git a/SQLite.sln b/SQLite.sln index 03454fd0b..f48412940 100644 --- a/SQLite.sln +++ b/SQLite.sln @@ -8,6 +8,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution sqlite-net-pcl.nuspec = sqlite-net-pcl.nuspec sqlite-net.nuspec = sqlite-net.nuspec Makefile = Makefile + sqlite-net-base.nuspec = sqlite-net-base.nuspec EndProjectSection EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SQLite-net", "nuget\SQLite-net\SQLite-net.csproj", "{7F946494-8EE0-432B-8A87-98961143D5C1}" diff --git a/nuget/SQLite-net-base/SQLite-net-base.csproj b/nuget/SQLite-net-base/SQLite-net-base.csproj index 1fcecb6da..52c809732 100644 --- a/nuget/SQLite-net-base/SQLite-net-base.csproj +++ b/nuget/SQLite-net-base/SQLite-net-base.csproj @@ -2,10 +2,11 @@ netstandard1.1 + SQLite-net - TRACE;USE_SQLITEPCL_RAW;DEBUG;NETSTANDARD1_1;NO_SQLITEPCL_RAW_BATTERIES + TRACE;USE_SQLITEPCL_RAW;NO_SQLITEPCL_RAW_BATTERIES;DEBUG;NETSTANDARD1_1 diff --git a/sqlite-net-base.nuspec b/sqlite-net-base.nuspec new file mode 100644 index 000000000..3c0b8818e --- /dev/null +++ b/sqlite-net-base.nuspec @@ -0,0 +1,67 @@ + + + + 1.5.0 + Frank A. Krueger + Frank A. Krueger + https://raw.githubusercontent.com/praeclarum/sqlite-net/master/LICENSE.md + https://github.com/praeclarum/sqlite-net + https://raw.githubusercontent.com/praeclarum/sqlite-net/master/nuget/Logo-low.png + sqlite-net-base + SQLite-net Official Portable Library (Base) + SQLite-net Official Portable Library (Base) is the easy and configurable way to access sqlite from .NET apps. + false + + This is a special version of SQLite-net-pcl that does not include a SQLitePCLRaw bundle. + It is meant to give you all the power of SQLite-net but with the freedom to choose your own provider. + Please use the package sqlite-net-pcl if you have no idea what any of this means. + + sqlite pcl database orm mobile + See project page + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sqlite-net-pcl.nuspec b/sqlite-net-pcl.nuspec index f9c2286eb..8095ebaec 100644 --- a/sqlite-net-pcl.nuspec +++ b/sqlite-net-pcl.nuspec @@ -13,11 +13,7 @@ false SQLite-net is an open source and light weight library providing easy SQLite database storage for .NET, Mono, and Xamarin applications. This version uses SQLitePCLRaw to provide platform independent versions of SQLite. sqlite pcl database orm mobile - - - + See project page diff --git a/sqlite-net.nuspec b/sqlite-net.nuspec index 9f9e24076..37f7f4392 100644 --- a/sqlite-net.nuspec +++ b/sqlite-net.nuspec @@ -13,11 +13,7 @@ sqlite-net is an open source, minimal library to allow .NET and Mono applications to store data in SQLite databases. It is written in C# 3.0 and is meant to be simply compiled in with your projects. It was first designed to work with MonoTouch on the iPhone, but should work in any other CLI environment. sqlite sql monotouch database metro winrt A .NET client library to access SQLite embedded database files in a LINQ manner. - - - + See project page From 18ba21c74aad5bd34886d53c0761b401dd71b183 Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Fri, 4 Aug 2017 16:44:49 -0700 Subject: [PATCH 070/103] Generate debug symbols and XML for base nugets --- nuget/SQLite-net-base/SQLite-net-base.csproj | 9 +++++++-- nuget/SQLite-net-std/SQLite-net-std.csproj | 3 +++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/nuget/SQLite-net-base/SQLite-net-base.csproj b/nuget/SQLite-net-base/SQLite-net-base.csproj index 52c809732..9ae15527c 100644 --- a/nuget/SQLite-net-base/SQLite-net-base.csproj +++ b/nuget/SQLite-net-base/SQLite-net-base.csproj @@ -7,10 +7,15 @@ TRACE;USE_SQLITEPCL_RAW;NO_SQLITEPCL_RAW_BATTERIES;DEBUG;NETSTANDARD1_1 + true + bin\Debug\netstandard1.1\SQLite-net.xml - - TRACE;USE_SQLITEPCL_RAW;RELEASE;NETSTANDARD1_1;NO_SQLITEPCL_RAW_BATTERIES + true + portable + TRACE;USE_SQLITEPCL_RAW;NO_SQLITEPCL_RAW_BATTERIES;RELEASE;NETSTANDARD1_1 + true + bin\Release\netstandard1.1\SQLite-net.xml diff --git a/nuget/SQLite-net-std/SQLite-net-std.csproj b/nuget/SQLite-net-std/SQLite-net-std.csproj index 13113ea96..281238dd6 100644 --- a/nuget/SQLite-net-std/SQLite-net-std.csproj +++ b/nuget/SQLite-net-std/SQLite-net-std.csproj @@ -9,10 +9,13 @@ true portable TRACE;USE_SQLITEPCL_RAW;RELEASE;NETSTANDARD1_1 + true bin\Release\netstandard1.1\SQLite-net.xml TRACE;USE_SQLITEPCL_RAW;DEBUG;NETSTANDARD1_1 + true + bin\Debug\netstandard1.1\SQLite-net.xml From b4bc62f8a3a7867792f1961159dd1e11cedb7816 Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Fri, 4 Aug 2017 17:32:06 -0700 Subject: [PATCH 071/103] Retitle the Base package --- sqlite-net-base.nuspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sqlite-net-base.nuspec b/sqlite-net-base.nuspec index 3c0b8818e..00b087643 100644 --- a/sqlite-net-base.nuspec +++ b/sqlite-net-base.nuspec @@ -8,7 +8,7 @@ https://github.com/praeclarum/sqlite-net https://raw.githubusercontent.com/praeclarum/sqlite-net/master/nuget/Logo-low.png sqlite-net-base - SQLite-net Official Portable Library (Base) + SQLite-net Base Library SQLite-net Official Portable Library (Base) is the easy and configurable way to access sqlite from .NET apps. false From 6c78cbfc79e9dc37c46fbc27e7b341520cf52131 Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Fri, 4 Aug 2017 17:40:07 -0700 Subject: [PATCH 072/103] Fix nuget deps for base package --- sqlite-net-base.nuspec | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/sqlite-net-base.nuspec b/sqlite-net-base.nuspec index 00b087643..6e58447b4 100644 --- a/sqlite-net-base.nuspec +++ b/sqlite-net-base.nuspec @@ -20,44 +20,44 @@ See project page - + - + - + - + - + - + - + - + - + - + - + - + - + From c969adec135bf4a2d7d15f35ae71fb8492f6a880 Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Fri, 4 Aug 2017 19:43:31 -0700 Subject: [PATCH 073/103] Change Close() to be CloseAsync() Fixes #101 --- src/SQLiteAsync.cs | 33 +++++++++++++++++++++++---------- tests/AsyncTests.cs | 13 +++++++++++++ 2 files changed, 36 insertions(+), 10 deletions(-) diff --git a/src/SQLiteAsync.cs b/src/SQLiteAsync.cs index e7bb3e2da..b7e11baa1 100644 --- a/src/SQLiteAsync.cs +++ b/src/SQLiteAsync.cs @@ -148,6 +148,9 @@ public bool TimeExecution { /// /// Closes all connections to all async databases. + /// You should *never* need to do this. + /// This is a blocking operation that will return when all connections + /// have been closed. /// public static void ResetPool () { @@ -168,9 +171,11 @@ public SQLiteConnectionWithLock GetConnection () /// /// Closes any pooled connections used by the database. /// - public void Close () + public Task CloseAsync () { - SQLiteConnectionPool.Shared.CloseConnection (_connectionString, _openFlags); + return Task.Factory.StartNew (() => { + SQLiteConnectionPool.Shared.CloseConnection (_connectionString, _openFlags); + }); } Task ReadAsync (Func read) @@ -1285,7 +1290,11 @@ public Entry (SQLiteConnectionString connectionString, SQLiteOpenFlags openFlags public void Close () { - Connection.Dispose (); + if (Connection == null) + return; + using (var l = Connection.Lock ()) { + Connection.Dispose (); + } Connection = null; } } @@ -1321,15 +1330,16 @@ public SQLiteConnectionWithLock GetConnection (SQLiteConnectionString connection public void CloseConnection (SQLiteConnectionString connectionString, SQLiteOpenFlags openFlags) { - lock (_entriesLock) { - Entry entry; - string key = connectionString.ConnectionString; + var key = connectionString.ConnectionString; + Entry entry; + lock (_entriesLock) { if (_entries.TryGetValue (key, out entry)) { _entries.Remove (key); - entry.Close (); } } + + entry.Close (); } /// @@ -1337,12 +1347,15 @@ public void CloseConnection (SQLiteConnectionString connectionString, SQLiteOpen /// public void Reset () { + List entries; lock (_entriesLock) { - foreach (var entry in _entries.Values) { - entry.Close (); - } + entries = new List (_entries.Values); _entries.Clear (); } + + foreach (var e in entries) { + e.Close (); + } } } diff --git a/tests/AsyncTests.cs b/tests/AsyncTests.cs index ed621103f..b9393137b 100644 --- a/tests/AsyncTests.cs +++ b/tests/AsyncTests.cs @@ -842,5 +842,18 @@ public void CreateTable () Assert.AreEqual (7, trace.Count); } + [Test] + public void CloseAsync () + { + var conn = GetConnection (); + + var r0 = conn.CreateTableAsync ().Result; + + Assert.AreEqual (CreateTableResult.Created, r0); + + conn.CloseAsync ().Wait (); + } + + } } From 5356727a0a91b3545c78743710ead9802a2816c0 Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Fri, 11 Aug 2017 11:59:52 -0700 Subject: [PATCH 074/103] Use absolute paths in README Examples Demonstrates the correct use of the ctors. Fixes #602 --- README.md | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 12514bda2..ec0cb2332 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,6 @@ public class Stock { [PrimaryKey, AutoIncrement] public int Id { get; set; } - [MaxLength(8)] public string Symbol { get; set; } } @@ -69,7 +68,10 @@ Both APIs are explained in the two sections below. Once you have defined your entity, you can automatically generate tables in your database by calling `CreateTable`: ```csharp -var db = new SQLiteConnection("foofoo"); +// Get an absolute path to the database file +var databasePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "MyData.db"); + +var db = new SQLiteConnection(databasePath); db.CreateTable(); db.CreateTable(); ``` @@ -90,7 +92,7 @@ Similar methods exist for `Update` and `Delete`. The most straightforward way to query for data is using the `Table` method. This can take predicates for constraining via WHERE clauses and/or adding ORDER BY clauses: ```csharp -var conn = new SQLiteConnection("foofoo"); +var conn = new SQLiteConnection(databasePath); var query = conn.Table().Where(v => v.Symbol.StartsWith("A")); foreach (var stock in query) @@ -129,7 +131,10 @@ will work for you. Once you have defined your entity, you can automatically generate tables by calling `CreateTableAsync`: ```csharp -var conn = new SQLiteAsyncConnection("foofoo"); +// Get an absolute path to the database file +var databasePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "MyData.db"); + +var conn = new SQLiteAsyncConnection(databasePath); await conn.CreateTableAsync(); @@ -144,7 +149,7 @@ Stock stock = new Stock() Symbol = "AAPL" }; -var conn = new SQLiteAsyncConnection("foofoo"); +var conn = new SQLiteAsyncConnection(databasePath); await conn.InsertAsync(stock); @@ -158,7 +163,7 @@ you can add predictates for constraining via WHERE clauses and/or adding ORDER B retrieval methods - `ToListAsync`, `FirstAsync`, or `FirstOrDefaultAsync` - is called. ```csharp -var conn = new SQLiteAsyncConnection("foofoo"); +var conn = new SQLiteAsyncConnection(databasePath); var query = await conn.Table().Where(v => v.Symbol.StartsWith("A")); var result = await query.ToListAsync(); @@ -173,7 +178,7 @@ operations provided by `InsertAsync` etc you can issue `ExecuteAsync` methods to Another helpful method is `ExecuteScalarAsync`. This allows you to return a scalar value from the database easily: ```csharp -var conn = new SQLiteAsyncConnection("foofoo"); +var conn = new SQLiteAsyncConnection(databasePath); var result = await conn.ExecuteScalarAsync("select count(*) from Stock"); From 6451a525f33baa63098cd2fb586a9df46d4f279f Mon Sep 17 00:00:00 2001 From: James Clancey Date: Fri, 11 Aug 2017 11:33:04 -0800 Subject: [PATCH 075/103] When in full mutex, Writing reading lo longer locks, Default to full mutex --- src/SQLiteAsync.cs | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/SQLiteAsync.cs b/src/SQLiteAsync.cs index b7e11baa1..f6a16d4f6 100644 --- a/src/SQLiteAsync.cs +++ b/src/SQLiteAsync.cs @@ -36,6 +36,8 @@ namespace SQLite public partial class SQLiteAsyncConnection { SQLiteConnectionString _connectionString; + SQLiteConnectionWithLock _connection; + bool isFullMutex; SQLiteOpenFlags _openFlags; /// @@ -53,7 +55,7 @@ public partial class SQLiteAsyncConnection /// the storeDateTimeAsTicks parameter. /// public SQLiteAsyncConnection (string databasePath, bool storeDateTimeAsTicks = true) - : this (databasePath, SQLiteOpenFlags.ReadWrite | SQLiteOpenFlags.Create, storeDateTimeAsTicks) + : this (databasePath, SQLiteOpenFlags.FullMutex | SQLiteOpenFlags.ReadWrite | SQLiteOpenFlags.Create, storeDateTimeAsTicks) { } @@ -77,7 +79,9 @@ public SQLiteAsyncConnection (string databasePath, bool storeDateTimeAsTicks = t public SQLiteAsyncConnection (string databasePath, SQLiteOpenFlags openFlags, bool storeDateTimeAsTicks = true) { _openFlags = openFlags; + isFullMutex = _openFlags.HasFlag (SQLiteOpenFlags.FullMutex); _connectionString = new SQLiteConnectionString (databasePath, storeDateTimeAsTicks); + _connection = new SQLiteConnectionWithLock (_connectionString, openFlags) { SkipLock = isFullMutex }; } /// @@ -191,7 +195,7 @@ Task ReadAsync (Func read) Task WriteAsync (Func write) { return Task.Factory.StartNew (() => { - var conn = GetConnection (); + var conn = isFullMutex ? _connection : GetConnection (); using (conn.Lock ()) { return write (conn); } @@ -1377,6 +1381,12 @@ public SQLiteConnectionWithLock (SQLiteConnectionString connectionString, SQLite { } + /// + /// Gets or sets a value indicating whether this skip lock. + /// + /// true if skip lock; otherwise, false. + public bool SkipLock { get; set; } + /// /// Lock the database to serialize access to it. To unlock it, call Dispose /// on the returned object. @@ -1384,7 +1394,7 @@ public SQLiteConnectionWithLock (SQLiteConnectionString connectionString, SQLite /// The lock. public IDisposable Lock () { - return new LockWrapper (_lockPoint); + return SkipLock ? (IDisposable)new FakeLockWrapper() : new LockWrapper (_lockPoint); } class LockWrapper : IDisposable @@ -1402,6 +1412,12 @@ public void Dispose () Monitor.Exit (_lockPoint); } } + class FakeLockWrapper : IDisposable + { + public void Dispose () + { + } + } } } From fb9e72a8eda93adb2b3a6e057d9850f91949599c Mon Sep 17 00:00:00 2001 From: James Clancey Date: Fri, 11 Aug 2017 11:35:45 -0800 Subject: [PATCH 076/103] Added locking Table Creation --- src/SQLiteAsync.cs | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/SQLiteAsync.cs b/src/SQLiteAsync.cs index f6a16d4f6..2453da943 100644 --- a/src/SQLiteAsync.cs +++ b/src/SQLiteAsync.cs @@ -328,6 +328,38 @@ public Task CreateTablesAsync (CreateFlags createFlags = Cre return WriteAsync (conn => conn.CreateTables (createFlags, types)); } + /// + /// Executes a "create table if not exists" on the database for each type. It also + /// creates any specified indexes on the columns of the table. It uses + /// a schema automatically generated from the specified type. You can + /// later access this schema by calling GetMapping. + /// + /// + /// Whether the table was created or migrated for each type. + /// + public CreateTablesResult CreateTables (CreateFlags createFlags = CreateFlags.None, params Type[] types) + { + var result = new CreateTablesResult (); + var conn = GetConnection (); + using (conn.Lock()) + { + foreach (Type type in types) + { + try + { + var aResult = conn.CreateTable (type); + result.Results[type] = aResult; + } + catch (Exception) + { + System.Diagnostics.Debug.WriteLine("Error creating table for {0}", type); + throw; + } + } + } + return result; + } + /// /// Executes a "drop table" on the database. This is non-recoverable. /// From 841427aab14619d43024d1d733ad26526412e157 Mon Sep 17 00:00:00 2001 From: James Clancey Date: Fri, 11 Aug 2017 11:37:52 -0800 Subject: [PATCH 077/103] Added WAL Support --- src/SQLite.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/SQLite.cs b/src/SQLite.cs index 32a7329e2..8009d2f51 100644 --- a/src/SQLite.cs +++ b/src/SQLite.cs @@ -289,7 +289,9 @@ public SQLiteConnection (string databasePath, SQLiteOpenFlags openFlags, bool st StoreDateTimeAsTicks = storeDateTimeAsTicks; BusyTimeout = TimeSpan.FromSeconds (0.1); - + if (openFlags.HasFlag (SQLiteOpenFlags.ReadWrite)) { + ExecuteScalar ("PRAGMA journal_mode=WAL"); + } Tracer = line => Debug.WriteLine (line); } From 5476d98013fe44f1cd3b4a0afc81f242940b1509 Mon Sep 17 00:00:00 2001 From: James Clancey Date: Fri, 11 Aug 2017 12:18:54 -0800 Subject: [PATCH 078/103] Made the ReadAsync, non locking instead of Write --- src/SQLiteAsync.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/SQLiteAsync.cs b/src/SQLiteAsync.cs index 2453da943..9b17169ee 100644 --- a/src/SQLiteAsync.cs +++ b/src/SQLiteAsync.cs @@ -185,7 +185,7 @@ public Task CloseAsync () Task ReadAsync (Func read) { return Task.Factory.StartNew (() => { - var conn = GetConnection (); + var conn = isFullMutex ? _connection : GetConnection (); using (conn.Lock ()) { return read (conn); } @@ -195,7 +195,7 @@ Task ReadAsync (Func read) Task WriteAsync (Func write) { return Task.Factory.StartNew (() => { - var conn = isFullMutex ? _connection : GetConnection (); + var conn = GetConnection (); using (conn.Lock ()) { return write (conn); } From ccd849c89ec5f7a4495e49d4e3757acd1de6edd1 Mon Sep 17 00:00:00 2001 From: James Clancey Date: Fri, 11 Aug 2017 12:26:46 -0800 Subject: [PATCH 079/103] Removed blocking CreateTables from async --- src/SQLiteAsync.cs | 32 -------------------------------- 1 file changed, 32 deletions(-) diff --git a/src/SQLiteAsync.cs b/src/SQLiteAsync.cs index 9b17169ee..b36fe4a48 100644 --- a/src/SQLiteAsync.cs +++ b/src/SQLiteAsync.cs @@ -328,38 +328,6 @@ public Task CreateTablesAsync (CreateFlags createFlags = Cre return WriteAsync (conn => conn.CreateTables (createFlags, types)); } - /// - /// Executes a "create table if not exists" on the database for each type. It also - /// creates any specified indexes on the columns of the table. It uses - /// a schema automatically generated from the specified type. You can - /// later access this schema by calling GetMapping. - /// - /// - /// Whether the table was created or migrated for each type. - /// - public CreateTablesResult CreateTables (CreateFlags createFlags = CreateFlags.None, params Type[] types) - { - var result = new CreateTablesResult (); - var conn = GetConnection (); - using (conn.Lock()) - { - foreach (Type type in types) - { - try - { - var aResult = conn.CreateTable (type); - result.Results[type] = aResult; - } - catch (Exception) - { - System.Diagnostics.Debug.WriteLine("Error creating table for {0}", type); - throw; - } - } - } - return result; - } - /// /// Executes a "drop table" on the database. This is non-recoverable. /// From d2b144cfcea626d863296c6698f3265415e71cff Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Fri, 11 Aug 2017 23:15:18 -0700 Subject: [PATCH 080/103] Move prepared inserts out of mapping This was the wrong place for them because they are tied to connections and could accidentally get shared between connections. Also remove NO_CONCURRENT as it's dangerous --- nuget/SQLite-net/SQLite-net.csproj | 4 +- src/SQLite.cs | 185 ++++++++++++----------------- tests/NotNullAttributeTest.cs | 8 +- 3 files changed, 85 insertions(+), 112 deletions(-) diff --git a/nuget/SQLite-net/SQLite-net.csproj b/nuget/SQLite-net/SQLite-net.csproj index 9f744df96..ed9f9e6ec 100644 --- a/nuget/SQLite-net/SQLite-net.csproj +++ b/nuget/SQLite-net/SQLite-net.csproj @@ -23,7 +23,7 @@ full false bin\Debug\ - TRACE;DEBUG;USE_SQLITEPCL_RAW;NO_CONCURRENT + DEBUG;USE_SQLITEPCL_RAW prompt 4 true @@ -34,7 +34,7 @@ portable true bin\Release\ - TRACE;USE_SQLITEPCL_RAW;NO_CONCURRENT + USE_SQLITEPCL_RAW prompt 4 true diff --git a/src/SQLite.cs b/src/SQLite.cs index 32a7329e2..b0e784272 100644 --- a/src/SQLite.cs +++ b/src/SQLite.cs @@ -30,12 +30,6 @@ using System.Runtime.InteropServices; #endif using System.Collections.Generic; -#if NO_CONCURRENT -using ConcurrentStringDictionary = System.Collections.Generic.Dictionary; -using SQLite.Extensions; -#else -using ConcurrentStringDictionary = System.Collections.Concurrent.ConcurrentDictionary; -#endif using System.Reflection; using System.Linq; using System.Linq.Expressions; @@ -1578,7 +1572,7 @@ public int Insert (object obj, string extra, Type objType) vals[i] = cols[i].GetValue (obj); } - var insertCmd = map.GetInsertCommand (this, extra); + var insertCmd = GetInsertCommand (map, extra); int count; lock (insertCmd) { @@ -1605,6 +1599,61 @@ public int Insert (object obj, string extra, Type objType) return count; } + readonly Dictionary, PreparedSqlLiteInsertCommand> _insertCommandMap = new Dictionary, PreparedSqlLiteInsertCommand> (); + + PreparedSqlLiteInsertCommand GetInsertCommand (TableMapping map, string extra) + { + PreparedSqlLiteInsertCommand prepCmd; + + var key = Tuple.Create (map.MappedType.FullName, extra); + + lock (_insertCommandMap) { + _insertCommandMap.TryGetValue (key, out prepCmd); + } + + if (prepCmd == null) { + prepCmd = CreateInsertCommand (map, extra); + var added = false; + lock (_insertCommandMap) { + if (!_insertCommandMap.ContainsKey (key)) { + _insertCommandMap.Add (key, prepCmd); + added = true; + } + } + if (!added) { + prepCmd.Dispose (); + } + } + + return prepCmd; + } + + PreparedSqlLiteInsertCommand CreateInsertCommand (TableMapping map, string extra) + { + var cols = map.InsertColumns; + string insertSql; + if (cols.Length == 0 && map.Columns.Length == 1 && map.Columns[0].IsAutoInc) { + insertSql = string.Format ("insert {1} into \"{0}\" default values", map.TableName, extra); + } + else { + var replacing = string.Compare (extra, "OR REPLACE", StringComparison.OrdinalIgnoreCase) == 0; + + if (replacing) { + cols = map.InsertOrReplaceColumns; + } + + insertSql = string.Format ("insert {3} into \"{0}\"({1}) values ({2})", map.TableName, + string.Join (",", (from c in cols + select "\"" + c.Name + "\"").ToArray ()), + string.Join (",", (from c in cols + select "?").ToArray ()), extra); + + } + + var insertCommand = new PreparedSqlLiteInsertCommand (this, insertSql); + return insertCommand; + } + /// /// Updates all of the columns of a table using the specified object /// except for its primary key. @@ -1844,10 +1893,11 @@ protected virtual void Dispose (bool disposing) if (_open && Handle != NullHandle) { try { if (disposing) { - if (_mappings != null) { - foreach (var sqlInsertCommand in _mappings.Values) { + lock (_insertCommandMap) { + foreach (var sqlInsertCommand in _insertCommandMap.Values) { sqlInsertCommand.Dispose (); } + _insertCommandMap.Clear (); } var r = useClose2 ? SQLite3.Close2 (Handle) : SQLite3.Close (Handle); @@ -2114,7 +2164,6 @@ from p in ti.DeclaredProperties // People should not be calling Get/Find without a PK GetByPrimaryKeySql = string.Format ("select * from \"{0}\" limit 1", TableName); } - _insertCommandMap = new ConcurrentStringDictionary (); } public bool HasAutoIncPK { get; private set; } @@ -2156,59 +2205,6 @@ public Column FindColumn (string columnName) return exact; } - ConcurrentStringDictionary _insertCommandMap; - - public PreparedSqlLiteInsertCommand GetInsertCommand (SQLiteConnection conn, string extra) - { - object prepCmdO; - - if (!_insertCommandMap.TryGetValue (extra, out prepCmdO)) { - var prepCmd = CreateInsertCommand (conn, extra); - prepCmdO = prepCmd; - if (!_insertCommandMap.TryAdd (extra, prepCmd)) { - // Concurrent add attempt beat us. - prepCmd.Dispose (); - _insertCommandMap.TryGetValue (extra, out prepCmdO); - } - } - return (PreparedSqlLiteInsertCommand)prepCmdO; - } - - 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 () - { - foreach (var pair in _insertCommandMap) { - ((PreparedSqlLiteInsertCommand)pair.Value).Dispose (); - } - _insertCommandMap = null; - } - public class Column { PropertyInfo _prop; @@ -2845,24 +2841,29 @@ object ReadCol (Sqlite3Statement stmt, int index, SQLite3.ColType type, Type clr /// /// Since the insert never changed, we only need to prepare once. /// - public class PreparedSqlLiteInsertCommand : IDisposable + class PreparedSqlLiteInsertCommand : IDisposable { - public bool Initialized { get; set; } + bool Initialized; - protected SQLiteConnection Connection { get; set; } + SQLiteConnection Connection; - public string CommandText { get; set; } + string CommandText; - protected Sqlite3Statement Statement { get; set; } - internal static readonly Sqlite3Statement NullStatement = default (Sqlite3Statement); + Sqlite3Statement Statement; + static readonly Sqlite3Statement NullStatement = default (Sqlite3Statement); - internal PreparedSqlLiteInsertCommand (SQLiteConnection conn) + public PreparedSqlLiteInsertCommand (SQLiteConnection conn, string commandText) { Connection = conn; + CommandText = commandText; } public int ExecuteNonQuery (object[] source) { + if (Initialized && Statement == NullStatement) { + throw new ObjectDisposedException (nameof (PreparedSqlLiteInsertCommand)); + } + if (Connection.Trace) { Connection.Tracer?.Invoke ("Executing: " + CommandText); } @@ -2870,7 +2871,7 @@ public int ExecuteNonQuery (object[] source) var r = SQLite3.Result.OK; if (!Initialized) { - Statement = Prepare (); + Statement = SQLite3.Prepare2 (Connection.Handle, CommandText); Initialized = true; } @@ -2902,28 +2903,19 @@ public int ExecuteNonQuery (object[] source) } } - 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) + void Dispose (bool disposing) { - if (Statement != NullStatement) { - try { - SQLite3.Finalize (Statement); - } - finally { - Statement = NullStatement; - Connection = null; - } + var s = Statement; + Statement = NullStatement; + Connection = null; + if (s != NullStatement) { + SQLite3.Finalize (s); } } @@ -4074,24 +4066,3 @@ public enum ColType : int } } } - -#if NO_CONCURRENT -namespace SQLite.Extensions -{ - public static class ListEx - { - public static bool TryAdd (this IDictionary dict, TKey key, TValue value) - { - try { - dict.Add (key, value); - return true; - } - catch (ArgumentException) { - return false; - } - } - } -} -#endif - - diff --git a/tests/NotNullAttributeTest.cs b/tests/NotNullAttributeTest.cs index 1028951af..e3cd13bb7 100644 --- a/tests/NotNullAttributeTest.cs +++ b/tests/NotNullAttributeTest.cs @@ -285,7 +285,6 @@ public void UpdateQueryWithNullThrowsException () public void ExecuteNonQueryWithNullThrowsException () { using (TestDb db = new TestDb ()) { - TableMapping map; db.CreateTable (); @@ -297,8 +296,11 @@ public void ExecuteNonQueryWithNullThrowsException () }; db.Insert (obj); - map = db.GetMapping (); - map.GetInsertCommand (db, "OR REPLACE").ExecuteNonQuery (new object[] { 1, null, 123, null, null, null }); + NotNullNoPK obj2 = new NotNullNoPK () { + objectId = 1, + OptionalIntProp = 123, + }; + db.InsertOrReplace (obj2); } catch (NotNullConstraintViolationException) { return; From 280d4ba3d50765fd30a28990bc2b46dc5eea8116 Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Sat, 12 Aug 2017 00:33:37 -0700 Subject: [PATCH 081/103] Share table mappings and make thread safe This fixes #423 by guarding access to the mappings. It also fixes #379 by making them static. --- src/SQLite.cs | 56 +++++++++++++++++++++++++-------------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/src/SQLite.cs b/src/SQLite.cs index b0e784272..c96ece995 100644 --- a/src/SQLite.cs +++ b/src/SQLite.cs @@ -163,8 +163,7 @@ public partial class SQLiteConnection : IDisposable { private bool _open; private TimeSpan _busyTimeout; - private Dictionary _mappings = null; - private Dictionary _tables = null; + readonly static Dictionary _mappings = new Dictionary (); private System.Diagnostics.Stopwatch _sw; private long _elapsedMilliseconds = 0; @@ -172,7 +171,7 @@ public partial class SQLiteConnection : IDisposable private Random _rand = new Random (); public Sqlite3DatabaseHandle Handle { get; private set; } - internal static readonly Sqlite3DatabaseHandle NullHandle = default (Sqlite3DatabaseHandle); + static readonly Sqlite3DatabaseHandle NullHandle = default (Sqlite3DatabaseHandle); /// /// Gets the database path used by this connection. @@ -345,7 +344,9 @@ public TimeSpan BusyTimeout { /// public IEnumerable TableMappings { get { - return _tables != null ? _tables.Values : Enumerable.Empty (); + lock (_mappings) { + return new List (_mappings.Values); + } } } @@ -364,13 +365,19 @@ public IEnumerable TableMappings { /// 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; + var key = type.FullName; + lock (_mappings) { + if (_mappings.TryGetValue (key, out map)) { + if (createFlags != CreateFlags.None && createFlags != map.CreateFlags) { + map = new TableMapping (type, createFlags); + _mappings[key] = map; + } + } + else { + map = new TableMapping (type, createFlags); + _mappings.Add (key, map); + } } return map; } @@ -451,18 +458,11 @@ public CreateTableResult CreateTable (CreateFlags createFlags = CreateFlags.N /// public CreateTableResult 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 map = GetMapping (ty, createFlags); // Present a nice error if no columns specified if (map.Columns.Length == 0) { - throw new Exception (string.Format ("Cannot create a table with zero columns (does '{0}' have public properties?)", ty.FullName)); + throw new Exception (string.Format ("Cannot create a table without columns (does '{0}' have public properties?)", ty.FullName)); } // Check if the table exists @@ -2100,13 +2100,16 @@ public class TableMapping public string GetByPrimaryKeySql { get; private set; } - Column _autoPk; - Column[] _insertColumns; - Column[] _insertOrReplaceColumns; + public CreateFlags CreateFlags { get; private set; } + + readonly Column _autoPk; + readonly Column[] _insertColumns; + readonly Column[] _insertOrReplaceColumns; public TableMapping (Type type, CreateFlags createFlags = CreateFlags.None) { MappedType = type; + CreateFlags = createFlags; var typeInfo = type.GetTypeInfo (); var tableAttr = @@ -2164,6 +2167,9 @@ from p in ti.DeclaredProperties // People should not be calling Get/Find without a PK GetByPrimaryKeySql = string.Format ("select * from \"{0}\" limit 1", TableName); } + + _insertColumns = Columns.Where (c => !c.IsAutoInc).ToArray (); + _insertOrReplaceColumns = Columns.ToArray (); } public bool HasAutoIncPK { get; private set; } @@ -2177,18 +2183,12 @@ public void SetAutoIncPK (object obj, long id) 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; } } From d1fa883ea427134e1c80082aa20b0e34ea1a9c66 Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Sat, 12 Aug 2017 01:02:01 -0700 Subject: [PATCH 082/103] Switch to using custom ToList in First impls --- src/SQLite.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/SQLite.cs b/src/SQLite.cs index c96ece995..94b9e5eec 100644 --- a/src/SQLite.cs +++ b/src/SQLite.cs @@ -3560,7 +3560,7 @@ public T[] ToArray () public T First () { var query = Take (1); - return query.ToList ().First (); + return query.ToList ().First (); } /// @@ -3569,7 +3569,7 @@ public T First () public T FirstOrDefault () { var query = Take (1); - return query.ToList ().FirstOrDefault (); + return query.ToList ().FirstOrDefault (); } /// From 4fabb223e4561eb0bc73b05da2bc35ca1d3394fc Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Sat, 12 Aug 2017 10:30:40 -0700 Subject: [PATCH 083/103] Fix identifier escaping used in the query example Fixes #363 --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index ec0cb2332..500bf1973 100644 --- a/README.md +++ b/README.md @@ -102,8 +102,7 @@ foreach (var stock in query) You can also query the database at a low-level using the `Query` method: ```csharp -public static IEnumerable QueryValuations (SQLiteConnection db, Stock stock) -{ +public static IEnumerable QueryValuations (SQLiteConnection db, Stock stock) { return db.Query ("select * from Valuation where StockId = ?", stock.Id); } ``` @@ -111,13 +110,14 @@ public static IEnumerable QueryValuations (SQLiteConnection db, Stock The generic parameter to the `Query` method specifies the type of object to create for each row. It can be one of your table classes, or any other class whose public properties match the column returned by the query. For instance, we could rewrite the above query as: ```csharp -public class Val { +public class Val +{ public decimal Money { get; set; } public DateTime Date { get; set; } } -public static IEnumerable QueryVals (SQLiteConnection db, Stock stock) -{ - return db.Query ("select 'Price' as 'Money', 'Time' as 'Date' from Valuation where StockId = ?", stock.Id); + +public static IEnumerable QueryVals (SQLiteConnection db, Stock stock) { + return db.Query ("select \"Price\" as \"Money\", \"Time\" as \"Date\" from Valuation where StockId = ?", stock.Id); } ``` From a206cf8472434fc6d3e1867e16f266cebc649d60 Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Sat, 12 Aug 2017 10:42:23 -0700 Subject: [PATCH 084/103] Fix preserving ColumnInfo on iOS and Android with linker Fixes #435 --- src/SQLite.cs | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/src/SQLite.cs b/src/SQLite.cs index 94b9e5eec..ae962f1dc 100644 --- a/src/SQLite.cs +++ b/src/SQLite.cs @@ -159,6 +159,7 @@ public enum CreateFlags /// /// An open connection to a SQLite database. /// + [Preserve (AllMembers = true)] public partial class SQLiteConnection : IDisposable { private bool _open; @@ -286,22 +287,6 @@ public SQLiteConnection (string databasePath, SQLiteOpenFlags openFlags, bool st Tracer = line => Debug.WriteLine (line); } -#if __IOS__ - static SQLiteConnection () - { - if (_preserveDuringLinkMagic) { - var ti = new ColumnInfo (); - ti.Name = "magic"; - } - } - - /// - /// Used to list some code that we want the MonoTouch linker - /// to see, but that we never want to actually execute. - /// - static bool _preserveDuringLinkMagic; -#endif - /// /// Enable or disable extension loading. /// @@ -693,6 +678,7 @@ public int CreateIndex (Expression> property, bool unique = f return CreateIndex (map.TableName, colName, unique); } + [Preserve (AllMembers = true)] public class ColumnInfo { // public int cid { get; set; } @@ -2062,6 +2048,12 @@ public MaxLengthAttribute (int length) } } + public sealed class PreserveAttribute : System.Attribute + { + public bool AllMembers; + public bool Conditional; + } + /// /// Select the collating sequence to use on a column. /// "BINARY", "NOCASE", and "RTRIM" are supported. From 8c72783cade5095f1bd187ede7b7a59df4c6f350 Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Sat, 12 Aug 2017 11:17:09 -0700 Subject: [PATCH 085/103] Switch to versioning in the CI This is work for #601 --- nuget/SQLite-net-base/SQLite-net-base.csproj | 8 ++++++-- nuget/SQLite-net-std/SQLite-net-std.csproj | 8 ++++++-- nuget/SQLite-net/SQLite-net.csproj | 2 +- sqlite-net-base.nuspec | 2 +- sqlite-net-pcl.nuspec | 2 +- sqlite-net.nuspec | 2 +- src/AssemblyInfo.cs | 9 ++++----- 7 files changed, 20 insertions(+), 13 deletions(-) diff --git a/nuget/SQLite-net-base/SQLite-net-base.csproj b/nuget/SQLite-net-base/SQLite-net-base.csproj index 9ae15527c..16cb5c05c 100644 --- a/nuget/SQLite-net-base/SQLite-net-base.csproj +++ b/nuget/SQLite-net-base/SQLite-net-base.csproj @@ -3,17 +3,21 @@ netstandard1.1 SQLite-net + 1.0.0 + SQLite-net Official .NET Standard Base Library + Light weight library providing easy SQLite database storage + Krueger Systems, Inc. - TRACE;USE_SQLITEPCL_RAW;NO_SQLITEPCL_RAW_BATTERIES;DEBUG;NETSTANDARD1_1 + USE_SQLITEPCL_RAW;NO_SQLITEPCL_RAW_BATTERIES;DEBUG;NETSTANDARD1_1 true bin\Debug\netstandard1.1\SQLite-net.xml true portable - TRACE;USE_SQLITEPCL_RAW;NO_SQLITEPCL_RAW_BATTERIES;RELEASE;NETSTANDARD1_1 + USE_SQLITEPCL_RAW;NO_SQLITEPCL_RAW_BATTERIES;RELEASE;NETSTANDARD1_1 true bin\Release\netstandard1.1\SQLite-net.xml diff --git a/nuget/SQLite-net-std/SQLite-net-std.csproj b/nuget/SQLite-net-std/SQLite-net-std.csproj index 281238dd6..949101f9b 100644 --- a/nuget/SQLite-net-std/SQLite-net-std.csproj +++ b/nuget/SQLite-net-std/SQLite-net-std.csproj @@ -3,17 +3,21 @@ netstandard1.1 SQLite-net + 1.0.0 + SQLite-net Official .NET Standard Library + Light weight library providing easy SQLite database storage + Krueger Systems, Inc. true portable - TRACE;USE_SQLITEPCL_RAW;RELEASE;NETSTANDARD1_1 + USE_SQLITEPCL_RAW;RELEASE;NETSTANDARD1_1 true bin\Release\netstandard1.1\SQLite-net.xml - TRACE;USE_SQLITEPCL_RAW;DEBUG;NETSTANDARD1_1 + USE_SQLITEPCL_RAW;DEBUG;NETSTANDARD1_1 true bin\Debug\netstandard1.1\SQLite-net.xml diff --git a/nuget/SQLite-net/SQLite-net.csproj b/nuget/SQLite-net/SQLite-net.csproj index ed9f9e6ec..be06c1f86 100644 --- a/nuget/SQLite-net/SQLite-net.csproj +++ b/nuget/SQLite-net/SQLite-net.csproj @@ -34,7 +34,7 @@ portable true bin\Release\ - USE_SQLITEPCL_RAW + RELEASE;USE_SQLITEPCL_RAW prompt 4 true diff --git a/sqlite-net-base.nuspec b/sqlite-net-base.nuspec index 6e58447b4..13d298282 100644 --- a/sqlite-net-base.nuspec +++ b/sqlite-net-base.nuspec @@ -1,7 +1,7 @@ - 1.5.0 + 1.0.0 Frank A. Krueger Frank A. Krueger https://raw.githubusercontent.com/praeclarum/sqlite-net/master/LICENSE.md diff --git a/sqlite-net-pcl.nuspec b/sqlite-net-pcl.nuspec index 8095ebaec..0c769e468 100644 --- a/sqlite-net-pcl.nuspec +++ b/sqlite-net-pcl.nuspec @@ -1,7 +1,7 @@ - 1.5.0 + 1.0.0 Frank A. Krueger Frank A. Krueger https://raw.githubusercontent.com/praeclarum/sqlite-net/master/LICENSE.md diff --git a/sqlite-net.nuspec b/sqlite-net.nuspec index 37f7f4392..6560fd6dd 100644 --- a/sqlite-net.nuspec +++ b/sqlite-net.nuspec @@ -1,7 +1,7 @@ - 1.5.0 + 1.0.0 Frank Krueger Frank Krueger,Tim Heuer https://raw.githubusercontent.com/praeclarum/sqlite-net/master/LICENSE.md diff --git a/src/AssemblyInfo.cs b/src/AssemblyInfo.cs index addbdb5ac..9daa3e6a1 100644 --- a/src/AssemblyInfo.cs +++ b/src/AssemblyInfo.cs @@ -6,12 +6,11 @@ // General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. -[assembly: AssemblyTitle("SQLite-net PCL")] -[assembly: AssemblyDescription("")] +[assembly: AssemblyTitle("SQLite-net Official Portable Library")] +[assembly: AssemblyDescription("Light weight library providing easy SQLite database storage")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Krueger Systems, Inc.")] [assembly: AssemblyProduct("SQLite-net")] -[assembly: AssemblyCopyright("Copyright © 2009-2017 Krueger Systems, Inc.")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: NeutralResourcesLanguage("en")] @@ -26,5 +25,5 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.5.0.0")] -[assembly: AssemblyFileVersion("1.5.0.0")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] From 44f2c5feb8dc4d446e42dbfe7c9bd6d5ee4a747b Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Sat, 12 Aug 2017 12:43:45 -0700 Subject: [PATCH 086/103] Rename iOS Test so that Instruments works --- SQLite.sln | 2 +- .../{SQLite.Tests.iOS.csproj => SQLiteTestsiOS.csproj} | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) rename tests/SQLite.Tests.iOS/{SQLite.Tests.iOS.csproj => SQLiteTestsiOS.csproj} (94%) diff --git a/SQLite.sln b/SQLite.sln index f48412940..d388f3f8c 100644 --- a/SQLite.sln +++ b/SQLite.sln @@ -15,7 +15,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SQLite-net", "nuget\SQLite- EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{FECC0E44-E626-49CB-BD8B-0CFBD93FBEFF}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SQLite.Tests.iOS", "tests\SQLite.Tests.iOS\SQLite.Tests.iOS.csproj", "{81850129-71C3-40C7-A48B-AA5D2C2E365E}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SQLiteTestsiOS", "tests\SQLite.Tests.iOS\SQLiteTestsiOS.csproj", "{81850129-71C3-40C7-A48B-AA5D2C2E365E}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SQLite-net-std", "nuget\SQLite-net-std\SQLite-net-std.csproj", "{081D08D6-10F1-431B-88FE-469FD9FE898C}" EndProject diff --git a/tests/SQLite.Tests.iOS/SQLite.Tests.iOS.csproj b/tests/SQLite.Tests.iOS/SQLiteTestsiOS.csproj similarity index 94% rename from tests/SQLite.Tests.iOS/SQLite.Tests.iOS.csproj rename to tests/SQLite.Tests.iOS/SQLiteTestsiOS.csproj index 477278489..4acd4ebd6 100644 --- a/tests/SQLite.Tests.iOS/SQLite.Tests.iOS.csproj +++ b/tests/SQLite.Tests.iOS/SQLiteTestsiOS.csproj @@ -9,7 +9,7 @@ {FEACFBD2-3405-455C-9665-78FE426C6842};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} Exe SQLite.Tests.iOS - SQLite.Tests.iOS + SQLiteTestsiOS Resources @@ -71,17 +71,18 @@ prompt 4 iPhone Developer - true + false true - true + false true true SdkOnly - ARMv7, ARM64 + ARM64 HttpClientHandler Default + true From 689ee7740808726c362ab1df3ce0b2756cc828c8 Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Sat, 12 Aug 2017 13:20:35 -0700 Subject: [PATCH 087/103] Add String.Replace handling in linq queries Fixes #460 --- src/SQLite.cs | 3 +++ tests/LinqTest.cs | 22 ++++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/src/SQLite.cs b/src/SQLite.cs index ae962f1dc..292222e21 100644 --- a/src/SQLite.cs +++ b/src/SQLite.cs @@ -3333,6 +3333,9 @@ private CompileResult CompileExpr (Expression expr, List queryArgs) else if (call.Method.Name == "ToUpper") { sqlCall = "(upper(" + obj.CommandText + "))"; } + else if (call.Method.Name == "Replace" && args.Length == 2) { + sqlCall = "(replace(" + obj.CommandText + "," + args[0].CommandText + "," + args[1].CommandText + "))"; + } else { sqlCall = call.Method.Name.ToLower () + "(" + string.Join (",", args.Select (a => a.CommandText).ToArray ()) + ")"; } diff --git a/tests/LinqTest.cs b/tests/LinqTest.cs index ad1f350bf..0090795ba 100644 --- a/tests/LinqTest.cs +++ b/tests/LinqTest.cs @@ -294,5 +294,27 @@ public void CastedParameters () Assert.AreEqual ("Foo", r.Value); } + + [Test] + public void Issue460_ReplaceWith2Args () + { + var db = CreateDb (); + db.Trace = true; + db.Tracer = Console.WriteLine; + + db.Insert (new Product { + Name = "I am not B X B", + }); + db.Insert (new Product { + Name = "I am B O B", + }); + + var cl = (from c in db.Table () + where c.Name.Replace (" ", "").Contains ("BOB") + select c).FirstOrDefault (); + + Assert.AreEqual (2, cl.Id); + Assert.AreEqual ("I am B O B", cl.Name); + } } } From 320f16b8a7f006bc12539beafc2718cde998cf7e Mon Sep 17 00:00:00 2001 From: Martin Kuckert Date: Sat, 12 Aug 2017 22:43:37 +0200 Subject: [PATCH 088/103] Fixes #492: Create temporary on-disk database by specificing empty string as database path --- src/SQLite.cs | 4 ++-- tests/OpenTests.cs | 13 +++++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/SQLite.cs b/src/SQLite.cs index 292222e21..ca90da264 100644 --- a/src/SQLite.cs +++ b/src/SQLite.cs @@ -251,8 +251,8 @@ public SQLiteConnection (string databasePath, bool storeDateTimeAsTicks = true) /// public SQLiteConnection (string databasePath, SQLiteOpenFlags openFlags, bool storeDateTimeAsTicks = true) { - if (string.IsNullOrEmpty (databasePath)) - throw new ArgumentException ("Must be specified", "databasePath"); + if (databasePath==null) + throw new ArgumentException ("Must be specified", nameof(databasePath)); DatabasePath = databasePath; diff --git a/tests/OpenTests.cs b/tests/OpenTests.cs index fc6e06641..435b5bf26 100644 --- a/tests/OpenTests.cs +++ b/tests/OpenTests.cs @@ -42,5 +42,18 @@ public void UnicodePathsAsync() Assert.IsTrue (new FileInfo (path).Length > 0, path); } + + [Test] + public void OpenTemporaryOnDisk() + { + var path = string.Empty; + + Assert.DoesNotThrow (() => { + using (var db = new SQLiteConnection (path, true)) + { + db.CreateTable (); + } + }); + } } } From c471c42d87d9d21f09a4745e877aa2529e4ce971 Mon Sep 17 00:00:00 2001 From: Martin Kuckert Date: Sat, 12 Aug 2017 23:30:46 +0200 Subject: [PATCH 089/103] #595: Implements specifying WithoutRowId on the TableAttribute to create the table accordingly --- src/SQLite.cs | 13 +++++++++++++ tests/CreateTableTest.cs | 42 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/src/SQLite.cs b/src/SQLite.cs index 292222e21..ba86d6ebd 100644 --- a/src/SQLite.cs +++ b/src/SQLite.cs @@ -470,6 +470,9 @@ public CreateTableResult CreateTable (Type ty, CreateFlags createFlags = CreateF var decl = string.Join (",\n", decls.ToArray ()); query += decl; query += ")"; + if(map.WithoutRowId) { + query += " without rowid"; + } Execute (query); } @@ -1978,6 +1981,13 @@ public class TableAttribute : Attribute { public string Name { get; set; } + /// + /// Flag whether to create the table without rowid (see https://sqlite.org/withoutrowid.html) + /// + /// The default is false so that sqlite adds an implicit rowid to every table created. + /// + public bool WithoutRowId { get; set; } + public TableAttribute (string name) { Name = name; @@ -2086,6 +2096,8 @@ public class TableMapping public string TableName { get; private set; } + public bool WithoutRowId { get; private set; } + public Column[] Columns { get; private set; } public Column PK { get; private set; } @@ -2111,6 +2123,7 @@ public TableMapping (Type type, CreateFlags createFlags = CreateFlags.None) .FirstOrDefault (); TableName = (tableAttr != null && !string.IsNullOrEmpty (tableAttr.Name)) ? tableAttr.Name : MappedType.Name; + WithoutRowId = tableAttr != null ? tableAttr.WithoutRowId : false; var props = new List (); var baseType = type; diff --git a/tests/CreateTableTest.cs b/tests/CreateTableTest.cs index 1b16f0eaa..74d12eb2a 100644 --- a/tests/CreateTableTest.cs +++ b/tests/CreateTableTest.cs @@ -107,5 +107,47 @@ public void Issue115_MissingPrimaryKey () } } } + + [Table("WantsNoRowId", WithoutRowId = true)] + class WantsNoRowId + { + [PrimaryKey] + public int Id { get; set; } + public string Name { get; set; } + } + + [Table("sqlite_master")] + class SqliteMaster + { + [Column ("type")] + public string Type { get; set; } + + [Column ("name")] + public string Name { get; set; } + + [Column ("tbl_name")] + public string TableName { get; set; } + + [Column ("rootpage")] + public int RootPage { get; set; } + + [Column ("sql")] + public string Sql { get; set; } + } + + [Test] + public void WithoutRowId () + { + using(var conn = new TestDb ()) + { + conn.CreateTable (); + var info = conn.Table().Where(m => m.TableName=="OrderLine").First (); + Assert.That (!info.Sql.Contains ("without rowid")); + + conn.CreateTable (); + info = conn.Table().Where(m => m.TableName=="WantsNoRowId").First (); + Assert.That (info.Sql.Contains ("without rowid")); + } + } } } From e8849bf2011500c6f08afdd7a6ed3e8f20301792 Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Sat, 12 Aug 2017 14:33:58 -0700 Subject: [PATCH 090/103] Support enums with repeated values Fixes #598 --- src/SQLite.cs | 8 +++--- tests/EnumCacheTest.cs | 63 ++++++++++++++++++++++++++++++------------ 2 files changed, 49 insertions(+), 22 deletions(-) diff --git a/src/SQLite.cs b/src/SQLite.cs index 292222e21..11dc7f1f9 100644 --- a/src/SQLite.cs +++ b/src/SQLite.cs @@ -2287,10 +2287,10 @@ public EnumCacheInfo (Type type) StoreAsText = typeInfo.CustomAttributes.Any (x => x.AttributeType == typeof (StoreAsTextAttribute)); if (StoreAsText) { - EnumValues = Enum.GetValues (type).Cast ().ToDictionary (Convert.ToInt32, x => x.ToString ()); - } - else { - EnumValues = Enum.GetValues (type).Cast ().ToDictionary (Convert.ToInt32, x => Convert.ToInt32 (x).ToString ()); + EnumValues = new Dictionary (); + foreach (object e in Enum.GetValues (type)) { + EnumValues[Convert.ToInt32 (e)] = e.ToString (); + } } } } diff --git a/tests/EnumCacheTest.cs b/tests/EnumCacheTest.cs index d7c1e0789..c6163f5dc 100644 --- a/tests/EnumCacheTest.cs +++ b/tests/EnumCacheTest.cs @@ -46,8 +46,30 @@ public enum TestByteEnumStoreAsInt : byte Value3 } + public enum TestEnumWithRepeats + { + Value1 = 1, - public class TestClassThusNotEnum + Value2 = 2, + + Value2Again = 2, + + Value3 = 3, + } + + [StoreAsText] + public enum TestEnumWithRepeatsAsText + { + Value1 = 1, + + Value2 = 2, + + Value2Again = 2, + + Value3 = 3, + } + + public class TestClassThusNotEnum { } @@ -76,14 +98,7 @@ public void ShouldReturnTrueForEnumStoreAsInt() Assert.IsTrue(info.IsEnum); Assert.IsFalse(info.StoreAsText); - Assert.IsNotNull(info.EnumValues); - - var values = Enum.GetValues(typeof(TestEnumStoreAsInt)).Cast().ToList(); - - for (int i = 0; i < values.Count; i++) - { - Assert.AreEqual(((int)values[i]).ToString(), info.EnumValues[i]); - } + Assert.IsNull(info.EnumValues); } [Test] @@ -93,14 +108,6 @@ public void ShouldReturnTrueForByteEnumStoreAsInt() Assert.IsTrue(info.IsEnum); Assert.IsFalse(info.StoreAsText); - Assert.IsNotNull(info.EnumValues); - - var values = Enum.GetValues(typeof(TestByteEnumStoreAsInt)).Cast().Select(x=>Convert.ToInt32(x)).ToList(); - - for (int i = 0; i < values.Count; i++) - { - Assert.AreEqual(values[i].ToString(), info.EnumValues[i]); - } } [Test] @@ -112,5 +119,25 @@ public void ShouldReturnFalseForClass() Assert.IsFalse(info.StoreAsText); Assert.IsNull(info.EnumValues); } - } + + [Test] + public void Issue598_EnumsWithRepeatedValues () + { + var info = EnumCache.GetInfo (); + + Assert.IsTrue (info.IsEnum); + Assert.IsFalse (info.StoreAsText); + Assert.IsNull (info.EnumValues); + } + + [Test] + public void Issue598_EnumsWithRepeatedValuesAsText () + { + var info = EnumCache.GetInfo (); + + Assert.IsTrue (info.IsEnum); + Assert.IsTrue (info.StoreAsText); + Assert.IsNotNull (info.EnumValues); + } + } } From 5c9a41fe09e1779ab7b850dac03963427c463887 Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Sat, 12 Aug 2017 16:52:15 -0700 Subject: [PATCH 091/103] Make DeferredQuery docs clearer Fixes #340 --- src/SQLite.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/SQLite.cs b/src/SQLite.cs index d37006dfb..dad196231 100644 --- a/src/SQLite.cs +++ b/src/SQLite.cs @@ -886,7 +886,8 @@ public T ExecuteScalar (string query, params object[] args) /// /// /// 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 + /// The enumerator (retrieved by calling GetEnumerator() on the result of this method) + /// 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() @@ -940,7 +941,8 @@ public List Query (TableMapping map, string query, params object[] args) /// /// /// 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 + /// The enumerator (retrieved by calling GetEnumerator() on the result of this method) + /// 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) From 24c0ce1b1dc1e0aeadc07e82a1dba4b972e36eb4 Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Sat, 12 Aug 2017 18:02:11 -0700 Subject: [PATCH 092/103] Add sqlite-net-sqlcipher nuget package and project This commit is going in so I can work on the CI side. Still need to add support for the `key` pragma. Working on #597 --- Makefile | 5 +- SQLite.sln | 14 +++++ .../SQLite-net-sqlcipher.csproj | 35 +++++++++++ sqlite-net-base.nuspec | 2 +- sqlite-net-pcl.nuspec | 2 +- sqlite-net-sqlcipher.nuspec | 63 +++++++++++++++++++ sqlite-net.nuspec | 4 +- 7 files changed, 120 insertions(+), 5 deletions(-) create mode 100644 nuget/SQLite-net-sqlcipher/SQLite-net-sqlcipher.csproj create mode 100644 sqlite-net-sqlcipher.nuspec diff --git a/Makefile b/Makefile index af011dfcf..53ad35395 100644 --- a/Makefile +++ b/Makefile @@ -13,7 +13,7 @@ tests/bin/Debug/SQLite.Tests.dll: tests/SQLite.Tests.csproj $(SRC) tests/ApiDiff/bin/Debug/ApiDiff.exe: tests/ApiDiff/ApiDiff.csproj $(SRC) msbuild tests/ApiDiff/ApiDiff.csproj -nuget: srcnuget pclnuget basenuget +nuget: srcnuget pclnuget basenuget sqlciphernuget packages: nuget/SQLite-net/packages.config nuget restore SQLite.sln @@ -30,3 +30,6 @@ basenuget: sqlite-net-pcl.nuspec packages $(SRC) msbuild "/p:Configuration=Release" nuget/SQLite-net-base/SQLite-net-base.csproj nuget pack sqlite-net-base.nuspec -o .\ +sqlciphernuget: sqlite-net-sqlcipher.nuspec packages $(SRC) + msbuild "/p:Configuration=Release" nuget/SQLite-net-sqlcipher/SQLite-net-sqlcipher.csproj + nuget pack sqlite-net-sqlcipher.nuspec -o .\ diff --git a/SQLite.sln b/SQLite.sln index d388f3f8c..efe1f8f7e 100644 --- a/SQLite.sln +++ b/SQLite.sln @@ -23,6 +23,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ApiDiff", "tests\ApiDiff\Ap EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SQLite-net-base", "nuget\SQLite-net-base\SQLite-net-base.csproj", "{53D1953C-3641-47D0-BE08-14DB853CC576}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SQLite-net-sqlcipher", "nuget\SQLite-net-sqlcipher\SQLite-net-sqlcipher.csproj", "{59DB03EF-E28D-431E-9058-74AF316800EE}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -105,6 +107,18 @@ Global {53D1953C-3641-47D0-BE08-14DB853CC576}.Release|iPhoneSimulator.Build.0 = Release|Any CPU {53D1953C-3641-47D0-BE08-14DB853CC576}.Debug|iPhone.ActiveCfg = Debug|Any CPU {53D1953C-3641-47D0-BE08-14DB853CC576}.Debug|iPhone.Build.0 = Debug|Any CPU + {59DB03EF-E28D-431E-9058-74AF316800EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {59DB03EF-E28D-431E-9058-74AF316800EE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {59DB03EF-E28D-431E-9058-74AF316800EE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {59DB03EF-E28D-431E-9058-74AF316800EE}.Release|Any CPU.Build.0 = Release|Any CPU + {59DB03EF-E28D-431E-9058-74AF316800EE}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {59DB03EF-E28D-431E-9058-74AF316800EE}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {59DB03EF-E28D-431E-9058-74AF316800EE}.Release|iPhone.ActiveCfg = Release|Any CPU + {59DB03EF-E28D-431E-9058-74AF316800EE}.Release|iPhone.Build.0 = Release|Any CPU + {59DB03EF-E28D-431E-9058-74AF316800EE}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {59DB03EF-E28D-431E-9058-74AF316800EE}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {59DB03EF-E28D-431E-9058-74AF316800EE}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {59DB03EF-E28D-431E-9058-74AF316800EE}.Debug|iPhone.Build.0 = Debug|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {6947A8F1-99BE-4DD1-AD4D-D89425CE67A2} = {FECC0E44-E626-49CB-BD8B-0CFBD93FBEFF} diff --git a/nuget/SQLite-net-sqlcipher/SQLite-net-sqlcipher.csproj b/nuget/SQLite-net-sqlcipher/SQLite-net-sqlcipher.csproj new file mode 100644 index 000000000..d7b7dc4c1 --- /dev/null +++ b/nuget/SQLite-net-sqlcipher/SQLite-net-sqlcipher.csproj @@ -0,0 +1,35 @@ + + + + netstandard1.1 + SQLite-net + 1.0.0 + SQLite-net Official SQLCipher .NET Standard Library + Light weight library providing easy SQLite database storage + Krueger Systems, Inc. + + + + true + portable + USE_SQLITEPCL_RAW;RELEASE;NETSTANDARD1_1 + true + bin\Release\netstandard1.1\SQLite-net.xml + + + USE_SQLITEPCL_RAW;DEBUG;NETSTANDARD1_1 + true + bin\Debug\netstandard1.1\SQLite-net.xml + + + + + + + SQLite.cs + + + SQLiteAsync.cs + + + diff --git a/sqlite-net-base.nuspec b/sqlite-net-base.nuspec index 13d298282..74c0295c1 100644 --- a/sqlite-net-base.nuspec +++ b/sqlite-net-base.nuspec @@ -16,7 +16,7 @@ It is meant to give you all the power of SQLite-net but with the freedom to choose your own provider. Please use the package sqlite-net-pcl if you have no idea what any of this means. - sqlite pcl database orm mobile + sqlite-net sqlite pcl database orm mobile raw See project page diff --git a/sqlite-net-pcl.nuspec b/sqlite-net-pcl.nuspec index 0c769e468..236ac6ec4 100644 --- a/sqlite-net-pcl.nuspec +++ b/sqlite-net-pcl.nuspec @@ -12,7 +12,7 @@ SQLite-net Official Portable Library is the easy way to access sqlite from .NET apps. false SQLite-net is an open source and light weight library providing easy SQLite database storage for .NET, Mono, and Xamarin applications. This version uses SQLitePCLRaw to provide platform independent versions of SQLite. - sqlite pcl database orm mobile + sqlite-net sqlite pcl database orm mobile raw See project page diff --git a/sqlite-net-sqlcipher.nuspec b/sqlite-net-sqlcipher.nuspec new file mode 100644 index 000000000..93a191580 --- /dev/null +++ b/sqlite-net-sqlcipher.nuspec @@ -0,0 +1,63 @@ + + + + 1.0.0 + Frank A. Krueger + Frank A. Krueger + https://raw.githubusercontent.com/praeclarum/sqlite-net/master/LICENSE.md + https://github.com/praeclarum/sqlite-net + https://raw.githubusercontent.com/praeclarum/sqlite-net/master/nuget/Logo-low.png + sqlite-net-sqlcipher + SQLite-net Official SQLCipher .NET Standard Library + SQLite-net Official Portable Library is the easy way to access sqlite from .NET apps. + false + SQLite-net is an open source and light weight library providing easy SQLite database storage for .NET, Mono, and Xamarin applications. This version uses SQLitePCLRaw to provide platform independent versions of SQLite with the SQLCipher extension. This enables secure access to the datbase with password (key) access. + sqlite-net sqlite pcl database orm mobile encryption sqlcipher + See project page + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sqlite-net.nuspec b/sqlite-net.nuspec index 6560fd6dd..44352f2a3 100644 --- a/sqlite-net.nuspec +++ b/sqlite-net.nuspec @@ -11,8 +11,8 @@ sqlite-net false sqlite-net is an open source, minimal library to allow .NET and Mono applications to store data in SQLite databases. It is written in C# 3.0 and is meant to be simply compiled in with your projects. It was first designed to work with MonoTouch on the iPhone, but should work in any other CLI environment. - sqlite sql monotouch database metro winrt - A .NET client library to access SQLite embedded database files in a LINQ manner. + sqlite-net sqlite sql monotouch database metro winrt + A .NET client library to access SQLite embedded database files in a LINQ manner. See project page From bb5eee35fbc666a3afdc67611aaffb5ebdd4eb3e Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Sat, 12 Aug 2017 18:34:37 -0700 Subject: [PATCH 093/103] Test enums defined as bytes Fixes #33 --- tests/EnumTest.cs | 50 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 48 insertions(+), 2 deletions(-) diff --git a/tests/EnumTest.cs b/tests/EnumTest.cs index 591a74d74..b5d1402f0 100644 --- a/tests/EnumTest.cs +++ b/tests/EnumTest.cs @@ -70,6 +70,7 @@ public TestDb(String path) { CreateTable(); CreateTable(); + CreateTable (); } } @@ -120,5 +121,50 @@ public void ShouldPersistAndReadStringEnum () db.Close(); } - } -} \ No newline at end of file + + public enum ByteTestEnum : byte + { + Value1 = 1, + + Value2 = 2, + + Value3 = 3 + } + + public class ByteTestObj + { + [PrimaryKey] + public int Id { get; set; } + public ByteTestEnum Value { get; set; } + + public override string ToString () + { + return string.Format ("[ByteTestObj: Id={0}, Value={1}]", Id, Value); + } + } + + [Test] + public void Issue33_ShouldPersistAndReadByteEnum () + { + var db = new TestDb (TestPath.GetTempFileName ()); + + var obj1 = new ByteTestObj () { Id = 1, Value = ByteTestEnum.Value2 }; + var obj2 = new ByteTestObj () { Id = 2, Value = ByteTestEnum.Value3 }; + + var numIn1 = db.Insert (obj1); + var numIn2 = db.Insert (obj2); + Assert.AreEqual (1, numIn1); + Assert.AreEqual (1, numIn2); + + var result = db.Query ("select * from ByteTestObj order by Id").ToList (); + Assert.AreEqual (2, result.Count); + Assert.AreEqual (obj1.Value, result[0].Value); + Assert.AreEqual (obj2.Value, result[1].Value); + + Assert.AreEqual (obj1.Id, result[0].Id); + Assert.AreEqual (obj2.Id, result[1].Id); + + db.Close (); + } + } +} From dbe79d66e7e050c3cc6a0455f0fa0558874a88bd Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Sat, 12 Aug 2017 20:19:22 -0700 Subject: [PATCH 094/103] Add SetKey to connection to support SQLCipher The key quoting is not perfect - need to improve it in the future. Working on #597 --- src/SQLite.cs | 42 +++++++++++++++++ src/SQLiteAsync.cs | 33 ++++++++++++++ tests/SQLCipherTest.cs | 96 +++++++++++++++++++++++++++++++++++++++ tests/SQLite.Tests.csproj | 1 + tests/TestDb.cs | 5 ++ 5 files changed, 177 insertions(+) create mode 100644 tests/SQLCipherTest.cs diff --git a/src/SQLite.cs b/src/SQLite.cs index dad196231..dbd5e91e7 100644 --- a/src/SQLite.cs +++ b/src/SQLite.cs @@ -287,6 +287,48 @@ public SQLiteConnection (string databasePath, SQLiteOpenFlags openFlags, bool st Tracer = line => Debug.WriteLine (line); } + /// + /// Convert an input string to a quoted SQL string that can be safely used in queries. + /// + /// The quoted string. + /// The unsafe string to quote. + static string Quote (string unsafeString) + { + // TODO: Doesn't call sqlite3_mprintf("%Q", u) because we're waiting on https://github.com/ericsink/SQLitePCL.raw/issues/153 + if (unsafeString == null) return "NULL"; + var safe = unsafeString.Replace ("'", "''"); + return "'" + safe + "'"; + } + + /// + /// Sets the key used to encrypt/decrypt the database. + /// This must be the first thing you call before doing anything else with this connection + /// if your database is encrypted. + /// This only has an effect if you are using the SQLCipher nuget package. + /// + /// Ecryption key plain text that is converted to the real encryption key using PBKDF2 key derivation + public void SetKey (string key) + { + if (key == null) throw new ArgumentNullException (nameof (key)); + var q = Quote (key); + Execute ("pragma key = " + q); + } + + /// + /// Sets the key used to encrypt/decrypt the database. + /// This must be the first thing you call before doing anything else with this connection + /// if your database is encrypted. + /// This only has an effect if you are using the SQLCipher nuget package. + /// + /// 256-bit (32 byte) ecryption key data + public void SetKey (byte[] key) + { + if (key == null) throw new ArgumentNullException (nameof (key)); + if (key.Length != 32) throw new ArgumentException ("Key must be 32 bytes (256-bit)", nameof(key)); + var s = String.Join ("", key.Select (x => x.ToString ("X2"))); + Execute ("pragma key = \"x'" + s + "'\""); + } + /// /// Enable or disable extension loading. /// diff --git a/src/SQLiteAsync.cs b/src/SQLiteAsync.cs index b7e11baa1..4834ef827 100644 --- a/src/SQLiteAsync.cs +++ b/src/SQLiteAsync.cs @@ -198,6 +198,39 @@ Task WriteAsync (Func write) }); } + /// + /// Sets the key used to encrypt/decrypt the database. + /// This must be the first thing you call before doing anything else with this connection + /// if your database is encrypted. + /// This only has an effect if you are using the SQLCipher nuget package. + /// + /// Ecryption key plain text that is converted to the real encryption key using PBKDF2 key derivation + public Task SetKeyAsync (string key) + { + if (key == null) throw new ArgumentNullException (nameof (key)); + return WriteAsync (conn => { + conn.SetKey (key); + return null; + }); + } + + /// + /// Sets the key used to encrypt/decrypt the database. + /// This must be the first thing you call before doing anything else with this connection + /// if your database is encrypted. + /// This only has an effect if you are using the SQLCipher nuget package. + /// + /// 256-bit (32 byte) ecryption key data + public Task SetKeyAsync (byte[] key) + { + if (key == null) throw new ArgumentNullException (nameof (key)); + if (key.Length != 32) throw new ArgumentException ("Key must be 32 bytes (256-bit)", nameof (key)); + return WriteAsync (conn => { + conn.SetKey (key); + return null; + }); + } + /// /// Enable or disable extension loading. /// diff --git a/tests/SQLCipherTest.cs b/tests/SQLCipherTest.cs new file mode 100644 index 000000000..9dccb9f86 --- /dev/null +++ b/tests/SQLCipherTest.cs @@ -0,0 +1,96 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +#if NETFX_CORE +using Microsoft.VisualStudio.TestPlatform.UnitTestFramework; +using SetUp = Microsoft.VisualStudio.TestPlatform.UnitTestFramework.TestInitializeAttribute; +using TestFixture = Microsoft.VisualStudio.TestPlatform.UnitTestFramework.TestClassAttribute; +using Test = Microsoft.VisualStudio.TestPlatform.UnitTestFramework.TestMethodAttribute; +#else +using NUnit.Framework; +#endif + +namespace SQLite.Tests +{ + [TestFixture] + public class SQLCipherTest + { + class TestTable + { + [PrimaryKey, AutoIncrement] + public int Id { get; set; } + + public string Value { get; set; } + } + + [Test] + public void SetStringKey () + { + string path; + + var key = "SecretPassword"; + + using (var db = new TestDb ()) { + path = db.DatabasePath; + + db.SetKey (key); + + db.CreateTable (); + db.Insert (new TestTable { Value = "Hello" }); + } + + using (var db = new TestDb (path)) { + path = db.DatabasePath; + + db.SetKey (key); + + var r = db.Table ().First (); + + Assert.AreEqual ("Hello", r.Value); + } + } + + [Test] + public void SetBytesKey () + { + string path; + + var rand = new Random (); + var key = new byte[32]; + rand.NextBytes (key); + + using (var db = new TestDb ()) { + path = db.DatabasePath; + + db.SetKey (key); + + db.CreateTable (); + db.Insert (new TestTable { Value = "Hello" }); + } + + using (var db = new TestDb (path)) { + path = db.DatabasePath; + + db.SetKey (key); + + var r = db.Table ().First (); + + Assert.AreEqual ("Hello", r.Value); + } + } + + [Test] + public void SetBadBytesKey () + { + try { + using (var db = new TestDb ()) { + db.SetKey (new byte[] { 1, 2, 3, 4 }); + } + Assert.Fail ("Should have thrown"); + } + catch (ArgumentException) { + } + } + } +} diff --git a/tests/SQLite.Tests.csproj b/tests/SQLite.Tests.csproj index eb5ee741f..674f1cbe2 100644 --- a/tests/SQLite.Tests.csproj +++ b/tests/SQLite.Tests.csproj @@ -80,6 +80,7 @@ + diff --git a/tests/TestDb.cs b/tests/TestDb.cs index 50d1c32da..3891cf62c 100644 --- a/tests/TestDb.cs +++ b/tests/TestDb.cs @@ -57,6 +57,11 @@ public TestDb (bool storeDateTimeAsTicks = true) : base (TestPath.GetTempFileNam { Trace = true; } + + public TestDb (string path, bool storeDateTimeAsTicks = true) : base (path, storeDateTimeAsTicks) + { + Trace = true; + } } public class TestPath From 75926dbf0d441a32a5cd885e8373c9958737d3f6 Mon Sep 17 00:00:00 2001 From: James Clancey Date: Sat, 12 Aug 2017 19:59:19 -0800 Subject: [PATCH 095/103] Changed naming around the mutex connection. --- src/SQLiteAsync.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/SQLiteAsync.cs b/src/SQLiteAsync.cs index b36fe4a48..126beafb8 100644 --- a/src/SQLiteAsync.cs +++ b/src/SQLiteAsync.cs @@ -36,7 +36,7 @@ namespace SQLite public partial class SQLiteAsyncConnection { SQLiteConnectionString _connectionString; - SQLiteConnectionWithLock _connection; + SQLiteConnectionWithLock _fullMutexReadConnection; bool isFullMutex; SQLiteOpenFlags _openFlags; @@ -81,7 +81,8 @@ public SQLiteAsyncConnection (string databasePath, SQLiteOpenFlags openFlags, bo _openFlags = openFlags; isFullMutex = _openFlags.HasFlag (SQLiteOpenFlags.FullMutex); _connectionString = new SQLiteConnectionString (databasePath, storeDateTimeAsTicks); - _connection = new SQLiteConnectionWithLock (_connectionString, openFlags) { SkipLock = isFullMutex }; + if(isFullMutex) + _fullMutexReadConnection = new SQLiteConnectionWithLock (_connectionString, openFlags) { SkipLock = true }; } /// @@ -185,7 +186,7 @@ public Task CloseAsync () Task ReadAsync (Func read) { return Task.Factory.StartNew (() => { - var conn = isFullMutex ? _connection : GetConnection (); + var conn = isFullMutex ? _fullMutexReadConnection : GetConnection (); using (conn.Lock ()) { return read (conn); } From 5a9fbeb30656897e4497a5f5cabfbfa891a963e8 Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Sat, 12 Aug 2017 21:10:17 -0700 Subject: [PATCH 096/103] Log errors when stress test fails --- tests/AsyncTests.cs | 4 ++++ tests/LinqTest.cs | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/AsyncTests.cs b/tests/AsyncTests.cs index b9393137b..c76b231fd 100644 --- a/tests/AsyncTests.cs +++ b/tests/AsyncTests.cs @@ -100,6 +100,10 @@ public void StressAsync () doneEvent.WaitOne (); var count = globalConn.Table ().CountAsync ().Result; + + foreach (var e in errors) { + Console.WriteLine ("ERROR " + e); + } Assert.AreEqual (0, errors.Count); Assert.AreEqual (n, count); diff --git a/tests/LinqTest.cs b/tests/LinqTest.cs index 0090795ba..73e2d773d 100644 --- a/tests/LinqTest.cs +++ b/tests/LinqTest.cs @@ -300,7 +300,7 @@ public void Issue460_ReplaceWith2Args () { var db = CreateDb (); db.Trace = true; - db.Tracer = Console.WriteLine; + //db.Tracer = Console.WriteLine; db.Insert (new Product { Name = "I am not B X B", From 49a42b192d445222d71567f9b9ac9e9e9e10d786 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BD=D0=B4=D1=80=D1=96=D0=B9=20=D0=A7=D0=B5=D0=B1?= =?UTF-8?q?=D1=83=D0=BA=D1=96=D0=BD?= Date: Mon, 14 Aug 2017 10:14:21 +0300 Subject: [PATCH 097/103] Added Uri, UriBuilder and StringBuilder support --- src/SQLite.cs | 84 ++++++++++++++++++++++++++++++++------------------- 1 file changed, 53 insertions(+), 31 deletions(-) diff --git a/src/SQLite.cs b/src/SQLite.cs index 2ec5129d1..c2340cbec 100644 --- a/src/SQLite.cs +++ b/src/SQLite.cs @@ -1,16 +1,16 @@ // // Copyright (c) 2009-2017 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 @@ -33,6 +33,7 @@ using System.Reflection; using System.Linq; using System.Linq.Expressions; +using System.Text; using System.Threading; #if USE_CSHARP_SQLITE @@ -384,12 +385,12 @@ public IEnumerable TableMappings { /// /// /// The type whose mapping to the database is returned. - /// + /// /// /// Optional flags allowing implicit PK and indexes based on naming conventions - /// + /// /// - /// The mapping represents the schema of the columns of the database and contains + /// 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) @@ -416,9 +417,9 @@ public TableMapping GetMapping (Type type, CreateFlags createFlags = CreateFlags /// /// /// Optional flags allowing implicit PK and indexes based on naming conventions - /// + /// /// - /// The mapping represents the schema of the columns of the database and contains + /// The mapping represents the schema of the columns of the database and contains /// methods to set and get properties of objects. /// public TableMapping GetMapping (CreateFlags createFlags = CreateFlags.None) @@ -481,7 +482,7 @@ public CreateTableResult CreateTable (CreateFlags createFlags = CreateFlags.N /// later access this schema by calling GetMapping. /// /// Type to reflect to a database table. - /// Optional flags allowing implicit PK and indexes based on naming conventions. + /// Optional flags allowing implicit PK and indexes based on naming conventions. /// /// Whether the table was created or migrated. /// @@ -1047,7 +1048,7 @@ public object Get (object pk, TableMapping map) /// /// Attempts to retrieve the first object that matches the predicate from the table - /// associated with the specified type. + /// associated with the specified type. /// /// /// A predicate for which object to find. @@ -1101,7 +1102,7 @@ public object Find (object pk, TableMapping map) /// /// Attempts to retrieve the first object that matches the predicate from the table - /// associated with the specified type. + /// associated with the specified type. /// /// /// A predicate for which object to find. @@ -1117,7 +1118,7 @@ public object Find (object pk, TableMapping map) /// /// Attempts to retrieve the first object that matches the query from the table - /// associated with the specified type. + /// associated with the specified type. /// /// /// The fully escaped SQL. @@ -1136,7 +1137,7 @@ public object Find (object pk, TableMapping map) /// /// Attempts to retrieve the first object that matches the query from the table - /// associated with the specified type. + /// associated with the specified type. /// /// /// The TableMapping used to identify the table. @@ -1169,9 +1170,9 @@ public bool IsInTransaction { /// 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, + // 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. @@ -1182,7 +1183,7 @@ public void BeginTransaction () catch (Exception ex) { var sqlExp = ex as SQLiteException; if (sqlExp != null) { - // It is recommended that applications respond to the errors listed below + // 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) { @@ -1196,7 +1197,7 @@ public void BeginTransaction () } } else { - // Call decrement and not VolatileWrite in case we've already + // Call decrement and not VolatileWrite in case we've already // created a transaction point in SaveTransactionPoint since the catch. Interlocked.Decrement (ref _transactionDepth); } @@ -1213,7 +1214,7 @@ public void BeginTransaction () /// /// 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. @@ -1230,7 +1231,7 @@ public string SaveTransactionPoint () catch (Exception ex) { var sqlExp = ex as SQLiteException; if (sqlExp != null) { - // It is recommended that applications respond to the errors listed below + // 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) { @@ -1277,8 +1278,8 @@ public void RollbackTo (string savepoint) /// 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. + // 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) { @@ -1298,10 +1299,10 @@ void RollbackTo (string savepoint, bool noThrow) } /// - /// Releases a savepoint returned from . Releasing a savepoint + /// 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 @@ -2029,7 +2030,7 @@ public class TableAttribute : Attribute /// /// Flag whether to create the table without rowid (see https://sqlite.org/withoutrowid.html) - /// + /// /// The default is false so that sqlite adds an implicit rowid to every table created. /// public bool WithoutRowId { get; set; } @@ -2429,7 +2430,7 @@ public static string SqlType (TableMapping.Column p, bool storeDateTimeAsTicks) else if (clrType == typeof (Single) || clrType == typeof (Double) || clrType == typeof (Decimal)) { return "float"; } - else if (clrType == typeof (String)) { + else if (clrType == typeof (String) || clrType == typeof (StringBuilder) || clrType == typeof (Uri) || clrType == typeof (UriBuilder)) { int? len = p.MaxStringLength; if (len.HasValue) @@ -2778,6 +2779,15 @@ internal static void BindParameter (Sqlite3Statement stmt, int index, object val else if (value is Guid) { SQLite3.BindText (stmt, index, ((Guid)value).ToString (), 72, NegativePointer); } + else if (value is Uri) { + SQLite3.BindText (stmt, index, ((Uri)value).ToString (), -1, NegativePointer); + } + else if (value is StringBuilder) { + SQLite3.BindText (stmt, index, ((StringBuilder)value).ToString (), -1, NegativePointer); + } + else if (value is UriBuilder) { + SQLite3.BindText (stmt, index, ((UriBuilder)value).ToString (), -1, NegativePointer); + } else { // Now we could possibly get an enum, retrieve cached info var valueType = value.GetType (); @@ -2882,6 +2892,18 @@ object ReadCol (Sqlite3Statement stmt, int index, SQLite3.ColType type, Type clr var text = SQLite3.ColumnString (stmt, index); return new Guid (text); } + else if (clrType == typeof(Uri)) { + var text = SQLite3.ColumnString(stmt, index); + return new Uri(text); + } + else if (clrType == typeof (StringBuilder)) { + var text = SQLite3.ColumnString (stmt, index); + return new StringBuilder (text); + } + else if (clrType == typeof(UriBuilder)) { + var text = SQLite3.ColumnString(stmt, index); + return new UriBuilder(text); + } else { throw new NotSupportedException ("Don't know how to read " + clrType); } @@ -3749,7 +3771,7 @@ public enum ConfigOption : int [DllImport(LibraryPath, 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(LibraryPath, EntryPoint = "sqlite3_open_v2", CallingConvention = CallingConvention.Cdecl)] public static extern Result Open(byte[] filename, out IntPtr db, int flags, IntPtr zvfs); @@ -3761,16 +3783,16 @@ public enum ConfigOption : int [DllImport(LibraryPath, EntryPoint = "sqlite3_close", CallingConvention=CallingConvention.Cdecl)] public static extern Result Close (IntPtr db); - + [DllImport(LibraryPath, EntryPoint = "sqlite3_close_v2", CallingConvention = CallingConvention.Cdecl)] public static extern Result Close2(IntPtr db); - + [DllImport(LibraryPath, EntryPoint = "sqlite3_initialize", CallingConvention=CallingConvention.Cdecl)] public static extern Result Initialize(); - + [DllImport(LibraryPath, EntryPoint = "sqlite3_shutdown", CallingConvention=CallingConvention.Cdecl)] public static extern Result Shutdown(); - + [DllImport(LibraryPath, EntryPoint = "sqlite3_config", CallingConvention=CallingConvention.Cdecl)] public static extern Result Config (ConfigOption option); From 19ff6135283df9befe50e8ef607520ae286c8bee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BD=D0=B4=D1=80=D1=96=D0=B9=20=D0=A7=D0=B5=D0=B1?= =?UTF-8?q?=D1=83=D0=BA=D1=96=D0=BD?= Date: Mon, 14 Aug 2017 09:47:12 +0300 Subject: [PATCH 098/103] Updated .gitignore to modern set --- .gitignore | 34 ++++++++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index 406546f6e..ddf273be9 100644 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,7 @@ bld/ .vs/ # Uncomment if you have tasks that create the project's static files in wwwroot #wwwroot/ +dist/ # MSTest test Results [Tt]est[Rr]esult*/ @@ -46,6 +47,7 @@ dlldata.c # DNX project.lock.json +project.fragment.lock.json artifacts/ *_i.c @@ -84,6 +86,8 @@ ipch/ *.opensdf *.sdf *.cachefile +*.VC.db +*.VC.VC.opendb # Visual Studio profiler *.psess @@ -142,11 +146,16 @@ publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml -# TODO: Comment the next line if you want to checkin your web deploy settings +# TODO: Comment the next line if you want to checkin your web deploy settings # but database connection strings (with potential passwords) will be unencrypted -*.pubxml +#*.pubxml *.publishproj +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + # NuGet Packages *.nupkg # The packages folder can be ignored because of Package Restore @@ -167,12 +176,11 @@ csx/ ecf/ rcf/ -# Microsoft Azure ApplicationInsights config file -ApplicationInsights.config - -# Windows Store app package directory +# Windows Store app package directories and files AppPackages/ BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt # Visual Studio cache files # files ending in .cache can be ignored @@ -186,11 +194,16 @@ ClientBin/ *~ *.dbmdl *.dbproj.schemaview +*.jfm *.pfx *.publishsettings node_modules/ orleans.codegen.cs +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) + + # RIA/Silverlight projects Generated_Code/ @@ -236,6 +249,15 @@ _Pvt_Extensions # Paket dependency manager .paket/paket.exe +paket-files/ # FAKE - F# Make .fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + From d9fae5b9d8c340b8681695a7bbe550aa03f55f9b Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Tue, 15 Aug 2017 21:31:13 -0700 Subject: [PATCH 099/103] Remove debug info from nuget release builds VS2015 can't handle the portable PDB format used by mono. Fixes #614 --- nuget/SQLite-net-base/SQLite-net-base.csproj | 4 ++-- nuget/SQLite-net-sqlcipher/SQLite-net-sqlcipher.csproj | 4 ++-- nuget/SQLite-net-std/SQLite-net-std.csproj | 4 ++-- nuget/SQLite-net/SQLite-net.csproj | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/nuget/SQLite-net-base/SQLite-net-base.csproj b/nuget/SQLite-net-base/SQLite-net-base.csproj index 16cb5c05c..26074ed9f 100644 --- a/nuget/SQLite-net-base/SQLite-net-base.csproj +++ b/nuget/SQLite-net-base/SQLite-net-base.csproj @@ -15,8 +15,8 @@ bin\Debug\netstandard1.1\SQLite-net.xml - true - portable + false + USE_SQLITEPCL_RAW;NO_SQLITEPCL_RAW_BATTERIES;RELEASE;NETSTANDARD1_1 true bin\Release\netstandard1.1\SQLite-net.xml diff --git a/nuget/SQLite-net-sqlcipher/SQLite-net-sqlcipher.csproj b/nuget/SQLite-net-sqlcipher/SQLite-net-sqlcipher.csproj index d7b7dc4c1..c49a05589 100644 --- a/nuget/SQLite-net-sqlcipher/SQLite-net-sqlcipher.csproj +++ b/nuget/SQLite-net-sqlcipher/SQLite-net-sqlcipher.csproj @@ -10,8 +10,8 @@ - true - portable + false + USE_SQLITEPCL_RAW;RELEASE;NETSTANDARD1_1 true bin\Release\netstandard1.1\SQLite-net.xml diff --git a/nuget/SQLite-net-std/SQLite-net-std.csproj b/nuget/SQLite-net-std/SQLite-net-std.csproj index 949101f9b..1e2f21a62 100644 --- a/nuget/SQLite-net-std/SQLite-net-std.csproj +++ b/nuget/SQLite-net-std/SQLite-net-std.csproj @@ -10,8 +10,8 @@ - true - portable + false + USE_SQLITEPCL_RAW;RELEASE;NETSTANDARD1_1 true bin\Release\netstandard1.1\SQLite-net.xml diff --git a/nuget/SQLite-net/SQLite-net.csproj b/nuget/SQLite-net/SQLite-net.csproj index be06c1f86..3575ebfd7 100644 --- a/nuget/SQLite-net/SQLite-net.csproj +++ b/nuget/SQLite-net/SQLite-net.csproj @@ -30,8 +30,8 @@ bin\Debug\SQLite-net.xml - true - portable + false + true bin\Release\ RELEASE;USE_SQLITEPCL_RAW From 62c620fd31464b13ec5c18f37580c7f3fef973b0 Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Wed, 16 Aug 2017 00:19:35 -0700 Subject: [PATCH 100/103] Rollback transactions if the commit/release fails These methods can fail with BUSY without terminating the transaction. This caused problems with maintaining the transaction depth and also put a burdern on the user to try committing again, or rolling back. To simplify things, an automatic rollback as been added. Now when transactions fail with BUSY, they also rollback and terminate. Fixes #604 --- src/SQLite.cs | 35 ++++++++++- tests/TransactionTest.cs | 123 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 156 insertions(+), 2 deletions(-) diff --git a/src/SQLite.cs b/src/SQLite.cs index c2340cbec..092c18a6d 100644 --- a/src/SQLite.cs +++ b/src/SQLite.cs @@ -1308,7 +1308,24 @@ void RollbackTo (string savepoint, bool noThrow) /// 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 "); + try { + DoSavePointExecute (savepoint, "release "); + } + catch (SQLiteException ex) { + if (ex.Result == SQLite3.Result.Busy) { + // Force a rollback since most people don't know this function can fail + // Don't call Rollback() since the _transactionDepth is 0 and it won't try + // Calling rollback makes our _transactionDepth variable correct. + // Writes to the database only happen at depth=0, so this failure will only happen then. + try { + Execute ("rollback"); + } + catch { + // rollback can fail in all sorts of wonderful version-dependent ways. Let's just hope for the best + } + } + throw; + } } void DoSavePointExecute (string savepoint, string cmd) @@ -1342,7 +1359,21 @@ void DoSavePointExecute (string savepoint, string cmd) public void Commit () { if (Interlocked.Exchange (ref _transactionDepth, 0) != 0) { - Execute ("commit"); + try { + Execute ("commit"); + } + catch { + // Force a rollback since most people don't know this function can fail + // Don't call Rollback() since the _transactionDepth is 0 and it won't try + // Calling rollback makes our _transactionDepth variable correct. + try { + Execute ("rollback"); + } + catch { + // rollback can fail in all sorts of wonderful version-dependent ways. Let's just hope for the best + } + throw; + } } // Do nothing on a commit with no open transaction } diff --git a/tests/TransactionTest.cs b/tests/TransactionTest.cs index 77bd48cb0..7eefa378e 100644 --- a/tests/TransactionTest.cs +++ b/tests/TransactionTest.cs @@ -121,6 +121,129 @@ public void FailNestedSavepointTransaction() Assert.AreEqual(testObjects.Count, db.Table().Count()); } + + + [Test] + public void Issue604_RunInTransactionAsync () + { + var adb = new SQLiteAsyncConnection (TestPath.GetTempFileName ()); + adb.CreateTableAsync ().Wait (); + var initialCount = adb.Table ().CountAsync ().Result; + + // + // Fail a commit + // + adb.Trace = true; + adb.Tracer = m => { + //Console.WriteLine (m); + if (m.Trim().EndsWith ("commit")) + throw SQLiteException.New (SQLite3.Result.Busy, "Make commit fail"); + }; + + try { + adb.RunInTransactionAsync (db => { + db.Insert (new TestObj ()); + }).Wait (); + Assert.Fail ("Should have thrown"); + } + catch (AggregateException aex) + when (aex.InnerException is SQLiteException ex + && ex.Result == SQLite3.Result.Busy) { + // Expected + } + + // + // Are we stuck? + // + adb.Tracer = null; + adb.RunInTransactionAsync (db => { + db.Insert (new TestObj ()); + }).Wait (); + + Assert.AreEqual (initialCount + 1, adb.Table ().CountAsync ().Result); + } + + [Test] + public void Issue604_RecoversFromFailedCommit () + { + db.Trace = true; + var initialCount = db.Table ().Count (); + + // + // Well this is an issue because there is an internal variable called _transactionDepth + // that tries to track if we are in an active transaction. + // The problem is, _transactionDepth is set to 0 and then commit is executed on the database. + // Well, the commit fails and "When COMMIT fails in this way, the transaction remains active and + // the COMMIT can be retried later after the reader has had a chance to clear" + // + var rollbacks = 0; + db.Tracer = m => { + if (m == "Executing: commit") + throw SQLiteException.New (SQLite3.Result.Busy, "Make commit fail"); + if (m == "Executing: rollback") + rollbacks++; + }; + db.BeginTransaction (); + db.Insert (new TestObj ()); + try { + db.Commit (); + Assert.Fail ("Should have thrown"); + } + catch (SQLiteException ex) when (ex.Result == SQLite3.Result.Busy) { + db.Tracer = null; + } + Assert.False (db.IsInTransaction); + Assert.AreEqual (1, rollbacks); + + // + // The catch statements in the RunInTransaction family of functions catch this and call rollback, + // but since _transactionDepth is 0, the transaction isn't actually rolled back. + // + // So the next time begin transaction is called on the same connection, + // sqlite-net attempts to begin a new transaction (because _transactionDepth is 0), + // which promptly fails because there is still an active transaction on the connection. + // + // Well now we are in big trouble because _transactionDepth got set to 1, + // and when begin transaction fails in this manner, the transaction isn't rolled back + // (which would have set _transactionDepth to 0) + // + db.BeginTransaction (); + db.Insert (new TestObj ()); + db.Commit (); + Assert.AreEqual (initialCount + 1, db.Table ().Count ()); + } + + [Test] + public void Issue604_RecoversFromFailedRelease () + { + db.Trace = true; + var initialCount = db.Table ().Count (); + + var rollbacks = 0; + db.Tracer = m => { + //Console.WriteLine (m); + if (m.StartsWith ("Executing: release")) + throw SQLiteException.New (SQLite3.Result.Busy, "Make release fail"); + if (m == "Executing: rollback") + rollbacks++; + }; + var sp0 = db.SaveTransactionPoint (); + db.Insert (new TestObj ()); + try { + db.Release (sp0); + Assert.Fail ("Should have thrown"); + } + catch (SQLiteException ex) when (ex.Result == SQLite3.Result.Busy) { + db.Tracer = null; + } + Assert.False (db.IsInTransaction); + Assert.AreEqual (1, rollbacks); + + db.BeginTransaction (); + db.Insert (new TestObj ()); + db.Commit (); + Assert.AreEqual (initialCount + 1, db.Table ().Count ()); + } } } From 786f9409e15c1bfaafeaad8225237a502cd30600 Mon Sep 17 00:00:00 2001 From: "Frank A. Krueger" Date: Wed, 16 Aug 2017 00:27:06 -0700 Subject: [PATCH 101/103] Add test to make sure async transaction failures rollback Fixes #329 --- tests/TransactionTest.cs | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/tests/TransactionTest.cs b/tests/TransactionTest.cs index 7eefa378e..469598b9b 100644 --- a/tests/TransactionTest.cs +++ b/tests/TransactionTest.cs @@ -122,6 +122,38 @@ public void FailNestedSavepointTransaction() Assert.AreEqual(testObjects.Count, db.Table().Count()); } + [Test] + public void Issue329_AsyncTransactionFailuresShouldRollback () + { + var adb = new SQLiteAsyncConnection (TestPath.GetTempFileName ()); + adb.CreateTableAsync ().Wait (); + var initialCount = adb.Table ().CountAsync ().Result; + var rollbacks = 0; + + // + // Fail a commit + // + adb.Trace = true; + adb.Tracer = m => { + Console.WriteLine (m); + if (m == "Executing: rollback") + rollbacks++; + }; + + try { + adb.RunInTransactionAsync (db => { + db.Insert (new TestObj ()); + throw new Exception ("User exception"); + }).Wait (); + Assert.Fail ("Should have thrown"); + } + catch (AggregateException aex) + when (aex.InnerException.Message == "User exception") { + // Expected + } + + Assert.AreEqual (1, rollbacks); + } [Test] public void Issue604_RunInTransactionAsync () From 32ef5e69ccb19d85d6242e7d45cac0384ed0e246 Mon Sep 17 00:00:00 2001 From: JohnShu Date: Thu, 17 Aug 2017 15:49:56 +0800 Subject: [PATCH 102/103] according to this article : https://blogs.msdn.microsoft.com/pfxteam/2011/10/24/task-run-vs-task-factory-startnew/ add default Task.Factory.StartNew parameter to do the same with Task.Run --- src/SQLiteAsync.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/SQLiteAsync.cs b/src/SQLiteAsync.cs index 5f5e36e18..2823748a9 100644 --- a/src/SQLiteAsync.cs +++ b/src/SQLiteAsync.cs @@ -180,7 +180,7 @@ public Task CloseAsync () { return Task.Factory.StartNew (() => { SQLiteConnectionPool.Shared.CloseConnection (_connectionString, _openFlags); - }); + }, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default); } Task ReadAsync (Func read) @@ -190,7 +190,7 @@ Task ReadAsync (Func read) using (conn.Lock ()) { return read (conn); } - }); + }, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default); } Task WriteAsync (Func write) @@ -200,7 +200,7 @@ Task WriteAsync (Func write) using (conn.Lock ()) { return write (conn); } - }); + }, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default); } /// @@ -1155,7 +1155,7 @@ Task ReadAsync (Func read) using (conn.Lock ()) { return read (conn); } - }); + }, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default); } Task WriteAsync (Func write) @@ -1165,7 +1165,7 @@ Task WriteAsync (Func write) using (conn.Lock ()) { return write (conn); } - }); + }, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default); } /// From 405faf17e261d19a9afb7f54375b95235a8d4db7 Mon Sep 17 00:00:00 2001 From: kicsiede <4233752+kicsiede@users.noreply.github.com> Date: Sun, 20 Aug 2017 19:23:58 +0200 Subject: [PATCH 103/103] allow comparison with nullable parameter allow queries like var nullableInt = default(int?); connection .Table() .Where(x => x.Int < nullableInt); which is equivalent to connection .Query("SELECT * FROM [Test] WHERE [Int] < ?", nullableInt); --- src/SQLite.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/SQLite.cs b/src/SQLite.cs index 092c18a6d..c276066fe 100644 --- a/src/SQLite.cs +++ b/src/SQLite.cs @@ -3574,8 +3574,13 @@ private string CompileNullBinaryExpression (BinaryExpression expression, Compile if (expression.NodeType == ExpressionType.Equal) return "(" + parameter.CommandText + " is ?)"; else if (expression.NodeType == ExpressionType.NotEqual) - return "(" + parameter.CommandText + " is not ?)"; - else + return "(" + parameter.CommandText + " is not ?)"; + else if (expression.NodeType == ExpressionType.GreaterThan + || expression.NodeType == ExpressionType.GreaterThanOrEqual + || expression.NodeType == ExpressionType.LessThan + || expression.NodeType == ExpressionType.LessThanOrEqual) + return "(" + parameter.CommandText + " < ?)"; // always false + else throw new NotSupportedException ("Cannot compile Null-BinaryExpression with type " + expression.NodeType.ToString ()); }