From 3903dff3db1cae9e7347cff01191b5ad53416b47 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Thu, 27 Aug 2020 17:54:08 +0300 Subject: [PATCH] Implement savepoint API for Microsoft.Data.Sqlite Closes #20228 --- .../Microsoft.Data.Sqlite.Core.csproj | 2 +- .../SqliteTransaction.cs | 92 +++++++++++++++++++ .../SqliteTransactionTest.cs | 20 ++++ 3 files changed, 113 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.Data.Sqlite.Core/Microsoft.Data.Sqlite.Core.csproj b/src/Microsoft.Data.Sqlite.Core/Microsoft.Data.Sqlite.Core.csproj index 027520179af..a44c5d0d09c 100644 --- a/src/Microsoft.Data.Sqlite.Core/Microsoft.Data.Sqlite.Core.csproj +++ b/src/Microsoft.Data.Sqlite.Core/Microsoft.Data.Sqlite.Core.csproj @@ -15,7 +15,7 @@ Microsoft.Data.Sqlite.SqliteException Microsoft.Data.Sqlite.SqliteFactory Microsoft.Data.Sqlite.SqliteParameter Microsoft.Data.Sqlite.SqliteTransaction - netstandard2.0 + netstandard2.0;net5.0 3.6 true Microsoft.Data.Sqlite.Core.ruleset diff --git a/src/Microsoft.Data.Sqlite.Core/SqliteTransaction.cs b/src/Microsoft.Data.Sqlite.Core/SqliteTransaction.cs index cf6cb9a3201..945b9c2edef 100644 --- a/src/Microsoft.Data.Sqlite.Core/SqliteTransaction.cs +++ b/src/Microsoft.Data.Sqlite.Core/SqliteTransaction.cs @@ -4,6 +4,7 @@ using System; using System.Data; using System.Data.Common; +using System.Text; using Microsoft.Data.Sqlite.Properties; using static SQLitePCL.raw; @@ -113,6 +114,97 @@ public override void Rollback() RollbackInternal(); } +#if NET + /// + public override bool SupportsSavepoints => true; +#endif + + /// + /// Creates a savepoint in the transaction. This allows all commands that are executed after the savepoint was + /// established to be rolled back, restoring the transaction state to what it was at the time of the savepoint. + /// + /// The name of the savepoint to be created. +#if NET + public override void Save(string savepointName) +#else + public void Save(string savepointName) +#endif + { + if (savepointName is null) + { + throw new ArgumentNullException(nameof(savepointName)); + } + + if (_completed || _connection.State != ConnectionState.Open) + { + throw new InvalidOperationException(Resources.TransactionCompleted); + } + + _connection.ExecuteNonQuery( + new StringBuilder() + .Append("SAVEPOINT \"") + .Append(savepointName.Replace("\"", "\"\"")) + .Append("\";") + .ToString()); + } + + /// + /// Rolls back all commands that were executed after the specified savepoint was established. + /// + /// The name of the savepoint to roll back to. +#if NET + public override void Rollback(string savepointName) +#else + public void Rollback(string savepointName) +#endif + { + if (savepointName is null) + { + throw new ArgumentNullException(nameof(savepointName)); + } + + if (_completed || _connection.State != ConnectionState.Open) + { + throw new InvalidOperationException(Resources.TransactionCompleted); + } + + _connection.ExecuteNonQuery( + new StringBuilder() + .Append("ROLLBACK TO SAVEPOINT \"") + .Append(savepointName.Replace("\"", "\"\"")) + .Append("\";") + .ToString()); + } + + /// + /// Destroys a savepoint previously defined in the current transaction. This allows the system to + /// reclaim some resources before the transaction ends. + /// + /// The name of the savepoint to release. +#if NET + public override void Release(string savepointName) +#else + public void Release(string savepointName) +#endif + { + if (savepointName is null) + { + throw new ArgumentNullException(nameof(savepointName)); + } + + if (_completed || _connection.State != ConnectionState.Open) + { + throw new InvalidOperationException(Resources.TransactionCompleted); + } + + _connection.ExecuteNonQuery( + new StringBuilder() + .Append("RELEASE SAVEPOINT \"") + .Append(savepointName.Replace("\"", "\"\"")) + .Append("\";") + .ToString()); + } + /// /// Releases any resources used by the transaction and rolls it back. /// diff --git a/test/Microsoft.Data.Sqlite.Tests/SqliteTransactionTest.cs b/test/Microsoft.Data.Sqlite.Tests/SqliteTransactionTest.cs index 522234b0e9e..9e33003fbe5 100644 --- a/test/Microsoft.Data.Sqlite.Tests/SqliteTransactionTest.cs +++ b/test/Microsoft.Data.Sqlite.Tests/SqliteTransactionTest.cs @@ -356,6 +356,26 @@ public void Dispose_can_be_called_more_than_once() } } + [Fact] + public void Savepoint() + { + using var connection = new SqliteConnection("Data Source=:memory:"); + connection.Open(); + CreateTestTable(connection); + + var transaction = connection.BeginTransaction(); + transaction.Save("MySavepointName"); + + connection.ExecuteNonQuery("INSERT INTO TestTable (TestColumn) VALUES (8)"); + Assert.Equal(1L, connection.ExecuteScalar("SELECT COUNT(*) FROM TestTable;")); + + transaction.Rollback("MySavepointName"); + Assert.Equal(0L, connection.ExecuteScalar("SELECT COUNT(*) FROM TestTable;")); + + transaction.Release("MySavepointName"); + Assert.Throws(() => transaction.Rollback("MySavepointName")); + } + private static void CreateTestTable(SqliteConnection connection) { connection.ExecuteNonQuery(