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..bb4c97a41c5 100644 --- a/src/Microsoft.Data.Sqlite.Core/SqliteTransaction.cs +++ b/src/Microsoft.Data.Sqlite.Core/SqliteTransaction.cs @@ -113,6 +113,74 @@ public override void Rollback() RollbackInternal(); } +#if NET + /// + public override bool SupportsSavepoints => true; + + /// + 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); + } + + /// + 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); + } + + /// + 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 + /// /// 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..1ad8095fd77 100644 --- a/test/Microsoft.Data.Sqlite.Tests/SqliteTransactionTest.cs +++ b/test/Microsoft.Data.Sqlite.Tests/SqliteTransactionTest.cs @@ -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("SELECT COUNT(*) FROM TestTable;")); + + transaction.Rollback("MySavepointName"); + Assert.Equal(0L, connection.ExecuteScalar("SELECT COUNT(*) FROM TestTable;")); + + transaction.Release("MySavepointName"); + Assert.Throws(() => transaction.Rollback("MySavepointName")); + } +#endif + private static void CreateTestTable(SqliteConnection connection) { connection.ExecuteNonQuery(