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 2, 2020
1 parent e4fc446 commit 611b33b
Show file tree
Hide file tree
Showing 3 changed files with 91 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
68 changes: 68 additions & 0 deletions src/Microsoft.Data.Sqlite.Core/SqliteTransaction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,74 @@ public override void Rollback()
RollbackInternal();
}

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

/// <inheritdoc />
public override void Save(string savepointName)
{
if (savepointName == null)
{
throw new ArgumentNullException(nameof(savepointName));
}

if (string.IsNullOrWhiteSpace(savepointName))
{
throw new ArgumentException($"{nameof(savepointName)} can't be empty", nameof(savepointName));
}

if (_completed || _connection.State != ConnectionState.Open)
{
throw new InvalidOperationException(Resources.TransactionCompleted);
}

_connection.ExecuteNonQuery("SAVEPOINT " + savepointName);
}

/// <inheritdoc />
public override void Rollback(string savepointName)
{
if (savepointName == null)
{
throw new ArgumentNullException(nameof(savepointName));
}

if (string.IsNullOrWhiteSpace(savepointName))
{
throw new ArgumentException($"{nameof(savepointName)} can't be empty", nameof(savepointName));
}

if (_completed || _connection.State != ConnectionState.Open)
{
throw new InvalidOperationException(Resources.TransactionCompleted);
}

_connection.ExecuteNonQuery("ROLLBACK TO " + savepointName);
}

/// <inheritdoc />
public override void Release(string savepointName)
{
if (savepointName == null)
{
throw new ArgumentNullException(nameof(savepointName));
}

if (string.IsNullOrWhiteSpace(savepointName))
{
throw new ArgumentException($"{nameof(savepointName)} can't be empty", nameof(savepointName));
}

if (_completed || _connection.State != ConnectionState.Open)
{
throw new InvalidOperationException(Resources.TransactionCompleted);
}

_connection.ExecuteNonQuery("RELEASE SAVEPOINT " + savepointName);
}
#endif

/// <summary>
/// Releases any resources used by the transaction and rolls it back.
/// </summary>
Expand Down
22 changes: 22 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,28 @@ public void Dispose_can_be_called_more_than_once()
}
}

#if NET
[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"));
}
#endif

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

0 comments on commit 611b33b

Please sign in to comment.