From 442ffee3f246027893d24600e9aa2ed0d0ee9a8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20T=C3=A4schner?= Date: Wed, 10 Jun 2020 18:53:17 +0200 Subject: [PATCH 1/2] Add deferred transactions. --- .../SqliteConnection.cs | 39 ++++++++++++++++++- .../SqliteTransaction.cs | 6 +-- .../SqliteTransactionTest.cs | 37 ++++++++++++++++++ 3 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/Microsoft.Data.Sqlite.Core/SqliteConnection.cs b/src/Microsoft.Data.Sqlite.Core/SqliteConnection.cs index 84875144d86..773ec382ed7 100644 --- a/src/Microsoft.Data.Sqlite.Core/SqliteConnection.cs +++ b/src/Microsoft.Data.Sqlite.Core/SqliteConnection.cs @@ -473,6 +473,24 @@ public virtual void CreateCollation(string name, T state, Func BeginTransaction(IsolationLevel.Unspecified); + /// + /// Begins a transaction on the connection. + /// + /// to defer the creation of the transaction. + /// This also causes transactions to upgrade from read transactions to write transactions as needed by their commands. + /// The transaction. + /// + /// Warning, commands inside a deferred transaction can fail if they cause the + /// transaction to be upgraded from a read transaction to a write transaction + /// but the database is locked. The application will need to retry the entire + /// transaction when this happens. + /// + /// A SQLite error occurs during execution. + /// Transactions + /// Database Errors + public SqliteTransaction BeginTransaction(bool deferred) + => BeginTransaction(IsolationLevel.Unspecified, deferred); + /// /// Begins a transaction on the connection. /// @@ -490,6 +508,25 @@ protected override DbTransaction BeginDbTransaction(IsolationLevel isolationLeve /// Transactions /// Database Errors public new virtual SqliteTransaction BeginTransaction(IsolationLevel isolationLevel) + => BeginTransaction(isolationLevel, deferred: isolationLevel == IsolationLevel.ReadUncommitted); + + /// + /// Begins a transaction on the connection. + /// + /// The isolation level of the transaction. + /// to defer the creation of the transaction. + /// This also causes transactions to upgrade from read transactions to write transactions as needed by their commands. + /// The transaction. + /// + /// Warning, commands inside a deferred transaction can fail if they cause the + /// transaction to be upgraded from a read transaction to a write transaction + /// but the database is locked. The application will need to retry the entire + /// transaction when this happens. + /// + /// A SQLite error occurs during execution. + /// Transactions + /// Database Errors + public SqliteTransaction BeginTransaction(IsolationLevel isolationLevel, bool deferred) { if (State != ConnectionState.Open) { @@ -501,7 +538,7 @@ protected override DbTransaction BeginDbTransaction(IsolationLevel isolationLeve throw new InvalidOperationException(Resources.ParallelTransactionsNotSupported); } - return Transaction = new SqliteTransaction(this, isolationLevel); + return Transaction = new SqliteTransaction(this, isolationLevel, deferred); } /// diff --git a/src/Microsoft.Data.Sqlite.Core/SqliteTransaction.cs b/src/Microsoft.Data.Sqlite.Core/SqliteTransaction.cs index 4ff1439fd65..3c563add19a 100644 --- a/src/Microsoft.Data.Sqlite.Core/SqliteTransaction.cs +++ b/src/Microsoft.Data.Sqlite.Core/SqliteTransaction.cs @@ -19,10 +19,10 @@ public class SqliteTransaction : DbTransaction private readonly IsolationLevel _isolationLevel; private bool _completed; - internal SqliteTransaction(SqliteConnection connection, IsolationLevel isolationLevel) + internal SqliteTransaction(SqliteConnection connection, IsolationLevel isolationLevel, bool deferred) { if ((isolationLevel == IsolationLevel.ReadUncommitted - && connection.ConnectionOptions.Cache != SqliteCacheMode.Shared) + && ((connection.ConnectionOptions.Cache != SqliteCacheMode.Shared) || !deferred)) || isolationLevel == IsolationLevel.ReadCommitted || isolationLevel == IsolationLevel.RepeatableRead) { @@ -46,7 +46,7 @@ internal SqliteTransaction(SqliteConnection connection, IsolationLevel isolation } connection.ExecuteNonQuery( - IsolationLevel == IsolationLevel.Serializable + IsolationLevel == IsolationLevel.Serializable && !deferred ? "BEGIN IMMEDIATE;" : "BEGIN;"); sqlite3_rollback_hook(connection.Handle, RollbackExternal, null); diff --git a/test/Microsoft.Data.Sqlite.Tests/SqliteTransactionTest.cs b/test/Microsoft.Data.Sqlite.Tests/SqliteTransactionTest.cs index 9cb0ede916c..73f9ea6d076 100644 --- a/test/Microsoft.Data.Sqlite.Tests/SqliteTransactionTest.cs +++ b/test/Microsoft.Data.Sqlite.Tests/SqliteTransactionTest.cs @@ -3,6 +3,7 @@ using System; using System.Data; +using System.IO; using Microsoft.Data.Sqlite.Properties; using Xunit; using static SQLitePCL.raw; @@ -113,6 +114,42 @@ public void Serialized_disallows_dirty_reads() } } + [Fact] + public void Deferred_allows_parallel_reads() + { + const string connectionString = "Data Source=deferred.db"; + + try + { + using (var connection = new SqliteConnection(connectionString)) + { + connection.Open(); + connection.ExecuteNonQuery("CREATE TABLE Data (Value); INSERT INTO Data VALUES (42);"); + } + + using (var connection1 = new SqliteConnection(connectionString)) + using (var connection2 = new SqliteConnection(connectionString)) + { + connection1.Open(); + connection2.Open(); + + using (connection1.BeginTransaction(deferred: true)) + using (connection2.BeginTransaction(deferred: true)) + { + var value1 = connection1.ExecuteScalar("SELECT * FROM Data;"); + var value2 = connection2.ExecuteScalar("SELECT * FROM Data;"); + + Assert.Equal(42, value1); + Assert.Equal(42, value2); + } + } + } + finally + { + File.Delete("deferred.db"); + } + } + [Fact] public void IsolationLevel_throws_when_completed() { From bd086d8a1dc43bbfc2b4ff8178cdfdb6f1bfbe80 Mon Sep 17 00:00:00 2001 From: Brice Lambson Date: Mon, 10 Aug 2020 18:22:18 -0700 Subject: [PATCH 2/2] Make new SqliteConnection.BeginTransaction overloads virtual --- src/Microsoft.Data.Sqlite.Core/SqliteConnection.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.Data.Sqlite.Core/SqliteConnection.cs b/src/Microsoft.Data.Sqlite.Core/SqliteConnection.cs index 773ec382ed7..3497151c7e9 100644 --- a/src/Microsoft.Data.Sqlite.Core/SqliteConnection.cs +++ b/src/Microsoft.Data.Sqlite.Core/SqliteConnection.cs @@ -488,7 +488,7 @@ public virtual void CreateCollation(string name, T state, FuncA SQLite error occurs during execution. /// Transactions /// Database Errors - public SqliteTransaction BeginTransaction(bool deferred) + public virtual SqliteTransaction BeginTransaction(bool deferred) => BeginTransaction(IsolationLevel.Unspecified, deferred); /// @@ -526,7 +526,7 @@ protected override DbTransaction BeginDbTransaction(IsolationLevel isolationLeve /// A SQLite error occurs during execution. /// Transactions /// Database Errors - public SqliteTransaction BeginTransaction(IsolationLevel isolationLevel, bool deferred) + public virtual SqliteTransaction BeginTransaction(IsolationLevel isolationLevel, bool deferred) { if (State != ConnectionState.Open) {