Skip to content

Commit

Permalink
Implement savepoint API for Microsoft.Data.Sqlite
Browse files Browse the repository at this point in the history
Closes #20228
  • Loading branch information
roji committed Oct 11, 2020
1 parent 2352499 commit 3903dff
Show file tree
Hide file tree
Showing 3 changed files with 113 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Microsoft.Data.Sqlite.SqliteException
Microsoft.Data.Sqlite.SqliteFactory
Microsoft.Data.Sqlite.SqliteParameter
Microsoft.Data.Sqlite.SqliteTransaction</Description>
<TargetFramework>netstandard2.0</TargetFramework>
<TargetFrameworks>netstandard2.0;net5.0</TargetFrameworks>
<MinClientVersion>3.6</MinClientVersion>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<CodeAnalysisRuleSet>Microsoft.Data.Sqlite.Core.ruleset</CodeAnalysisRuleSet>
Expand Down
92 changes: 92 additions & 0 deletions src/Microsoft.Data.Sqlite.Core/SqliteTransaction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -113,6 +114,97 @@ public override void Rollback()
RollbackInternal();
}

#if NET
/// <inheritdoc />
public override bool SupportsSavepoints => true;
#endif

/// <summary>
/// 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.
/// </summary>
/// <param name="savepointName">The name of the savepoint to be created.</param>
#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());
}

/// <summary>
/// Rolls back all commands that were executed after the specified savepoint was established.
/// </summary>
/// <param name="savepointName">The name of the savepoint to roll back to.</param>
#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());
}

/// <summary>
/// Destroys a savepoint previously defined in the current transaction. This allows the system to
/// reclaim some resources before the transaction ends.
/// </summary>
/// <param name="savepointName">The name of the savepoint to release.</param>
#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());
}

/// <summary>
/// Releases any resources used by the transaction and rolls it back.
/// </summary>
Expand Down
20 changes: 20 additions & 0 deletions test/Microsoft.Data.Sqlite.Tests/SqliteTransactionTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<long>("SELECT COUNT(*) FROM TestTable;"));

transaction.Rollback("MySavepointName");
Assert.Equal(0L, connection.ExecuteScalar<long>("SELECT COUNT(*) FROM TestTable;"));

transaction.Release("MySavepointName");
Assert.Throws<SqliteException>(() => transaction.Rollback("MySavepointName"));
}

private static void CreateTestTable(SqliteConnection connection)
{
connection.ExecuteNonQuery(
Expand Down

0 comments on commit 3903dff

Please sign in to comment.