Skip to content

Commit

Permalink
Add MySqlTransaction and basic tests.
Browse files Browse the repository at this point in the history
  • Loading branch information
bgrainger committed Mar 24, 2016
1 parent 704c9ed commit 783645f
Show file tree
Hide file tree
Showing 6 changed files with 216 additions and 9 deletions.
7 changes: 3 additions & 4 deletions src/MySql.Data/MySqlClient/MySqlCommand.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using System;
using System.Data;
using System.Data.Common;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using MySql.Data.Serialization;
Expand Down Expand Up @@ -61,7 +60,7 @@ public override object ExecuteScalar()
=> ExecuteScalarAsync(CancellationToken.None).GetAwaiter().GetResult();

public override void Prepare()
{
{
// NOTE: Prepared statements in MySQL are not currently supported.
// 1) Only a subset of statements are actually preparable by the server: http://dev.mysql.com/worklog/task/?id=2871
// 2) Although CLIENT_MULTI_STATEMENTS is supposed to mean that the Server "Can handle multiple statements per COM_QUERY and COM_STMT_PREPARE" (https://dev.mysql.com/doc/internals/en/capability-flags.html#flag-CLIENT_MULTI_STATEMENTS),
Expand Down Expand Up @@ -173,8 +172,8 @@ private void VerifyValid()
throw new InvalidOperationException("Connection property must be non-null.");
if (DbConnection.State != ConnectionState.Open && DbConnection.State != ConnectionState.Connecting)
throw new InvalidOperationException(Invariant($"Connection must be Open; current state is {DbConnection.State}"));
// TODO: if (DbTransaction != ((MySqlConnection) DbConnection).CurrentTransaction)
// throw new InvalidOperationException("The transaction associated with this command is not the connection's active transaction.");
if (DbTransaction != ((MySqlConnection) DbConnection).CurrentTransaction)
throw new InvalidOperationException("The transaction associated with this command is not the connection's active transaction.");
if (string.IsNullOrWhiteSpace(CommandText))
throw new InvalidOperationException("CommandText must be specified");
}
Expand Down
46 changes: 45 additions & 1 deletion src/MySql.Data/MySqlClient/MySqlConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,53 @@ public new MySqlTransaction BeginTransaction()

protected override DbTransaction BeginDbTransaction(IsolationLevel isolationLevel)
{
throw new NotImplementedException();
if (State != ConnectionState.Open)
throw new InvalidOperationException("Connection is not open.");
if (CurrentTransaction != null)
throw new InvalidOperationException("Transactions may not be nested.");

string isolationLevelValue;
switch (isolationLevel)
{
case IsolationLevel.ReadUncommitted:
isolationLevelValue = "read uncommitted";
break;

case IsolationLevel.ReadCommitted:
isolationLevelValue = "read committed";
break;

case IsolationLevel.Unspecified:
// "In terms of the SQL:1992 transaction isolation levels, the default InnoDB level is REPEATABLE READ." - http://dev.mysql.com/doc/refman/5.7/en/innodb-transaction-model.html
case IsolationLevel.RepeatableRead:
isolationLevelValue = "repeatable read";
break;

case IsolationLevel.Serializable:
isolationLevelValue = "serializable";
break;

case IsolationLevel.Chaos:
case IsolationLevel.Snapshot:
default:
throw new NotSupportedException(Invariant($"IsolationLevel.{isolationLevel} is not supported."));
}

using (var cmd = new MySqlCommand("set session transaction isolation level " + isolationLevelValue + "; start transaction;", this))
cmd.ExecuteNonQuery();

var transaction = new MySqlTransaction(this, isolationLevel);
CurrentTransaction = transaction;
return transaction;
}

public override void Close()
{
if (CurrentTransaction != null)
{
CurrentTransaction.Dispose();
CurrentTransaction = null;
}
Utility.Dispose(ref m_session);
SetState(ConnectionState.Closed);
m_isDisposed = true;
Expand Down Expand Up @@ -114,6 +156,8 @@ internal MySqlSession Session
}
}

internal MySqlTransaction CurrentTransaction { get; set; }

private void SetState(ConnectionState newState)
{
if (m_connectionState != newState)
Expand Down
81 changes: 77 additions & 4 deletions src/MySql.Data/MySqlClient/MySqlTransaction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,88 @@ public class MySqlTransaction : DbTransaction
{
public override void Commit()
{
throw new NotImplementedException();
VerifyNotDisposed();
if (m_isFinished)
throw new InvalidOperationException("Already committed or rolled back.");

if (m_connection.CurrentTransaction == this)
{
using (var cmd = new MySqlCommand("commit", m_connection, this))
cmd.ExecuteNonQuery();
m_connection.CurrentTransaction = null;
m_isFinished = true;
}
else if (m_connection.CurrentTransaction != null)
{
throw new InvalidOperationException("This is not the active transaction.");
}
else if (m_connection.CurrentTransaction == null)
{
throw new InvalidOperationException("There is no active transaction.");
}
}

public override void Rollback()
{
throw new NotImplementedException();
VerifyNotDisposed();
if (m_isFinished)
throw new InvalidOperationException("Already committed or rolled back.");

if (m_connection.CurrentTransaction == this)
{
using (var cmd = new MySqlCommand("rollback", m_connection, this))
cmd.ExecuteNonQuery();
m_connection.CurrentTransaction = null;
m_isFinished = true;
}
else if (m_connection.CurrentTransaction != null)
{
throw new InvalidOperationException("This is not the active transaction.");
}
else if (m_connection.CurrentTransaction == null)
{
throw new InvalidOperationException("There is no active transaction.");
}
}

protected override DbConnection DbConnection => m_connection;
public override IsolationLevel IsolationLevel { get; }

protected override void Dispose(bool disposing)
{
try
{
if (disposing)
{
if (!m_isFinished && m_connection != null && m_connection.CurrentTransaction == this)
{
using (var cmd = new MySqlCommand("rollback", m_connection, this))
cmd.ExecuteNonQuery();
m_connection.CurrentTransaction = null;
}
m_connection = null;
}
}
finally
{
base.Dispose(disposing);
}
}


internal MySqlTransaction(MySqlConnection connection, IsolationLevel isolationLevel)
{
m_connection = connection;
IsolationLevel = isolationLevel;
}

private void VerifyNotDisposed()
{
if (m_connection == null)
throw new ObjectDisposedException(nameof(MySqlTransaction));
}

protected override DbConnection DbConnection { get { throw new NotImplementedException(); } }
public override IsolationLevel IsolationLevel { get { throw new NotImplementedException(); } }
MySqlConnection m_connection;
bool m_isFinished;
}
}
6 changes: 6 additions & 0 deletions tests/SideBySide.Baseline/SideBySide.Baseline.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,12 @@
<Compile Include="..\SideBySide.New\DataTypesFixture.cs">
<Link>DataTypesFixture.cs</Link>
</Compile>
<Compile Include="..\SideBySide.New\Transaction.cs">
<Link>Transaction.cs</Link>
</Compile>
<Compile Include="..\SideBySide.New\TransactionFixture.cs">
<Link>TransactionFixture.cs</Link>
</Compile>
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
Expand Down
66 changes: 66 additions & 0 deletions tests/SideBySide.New/Transaction.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
using System;
using Dapper;
using MySql.Data.MySqlClient;
using Xunit;

namespace SideBySide.New
{
public class Transaction : IClassFixture<TransactionFixture>
{
public Transaction(TransactionFixture database)
{
m_database = database;
m_connection = m_database.Connection;
}

[Fact]
public void NestedTransactions()
{
using (m_connection.BeginTransaction())
{
Assert.Throws<InvalidOperationException>(() => m_connection.BeginTransaction());
}
}

[Fact]
public void Commit()
{
m_connection.Execute("delete from transactions.test");
using (var trans = m_connection.BeginTransaction())
{
m_connection.Execute("insert into transactions.test values(1), (2)", transaction: trans);
trans.Commit();
}
var results = m_connection.Query<int>(@"select value from transactions.test order by value;");
Assert.Equal(new[] { 1, 2 }, results);
}

[Fact]
public void Rollback()
{
m_connection.Execute("delete from transactions.test");
using (var trans = m_connection.BeginTransaction())
{
m_connection.Execute("insert into transactions.test values(1), (2)", transaction: trans);
trans.Rollback();
}
var results = m_connection.Query<int>(@"select value from transactions.test order by value;");
Assert.Equal(new int[0], results);
}

[Fact]
public void NoCommit()
{
m_connection.Execute("delete from transactions.test");
using (var trans = m_connection.BeginTransaction())
{
m_connection.Execute("insert into transactions.test values(1), (2)", transaction: trans);
}
var results = m_connection.Query<int>(@"select value from transactions.test order by value;");
Assert.Equal(new int[0], results);
}

readonly TransactionFixture m_database;
readonly MySqlConnection m_connection;
}
}
19 changes: 19 additions & 0 deletions tests/SideBySide.New/TransactionFixture.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using Dapper;

namespace SideBySide.New
{
public class TransactionFixture : DatabaseFixture
{
public TransactionFixture()
{
Connection.Open();
Connection.Execute(@"
drop schema if exists transactions;
create schema transactions;
create table transactions.test(value integer null);
");
}
}
}

0 comments on commit 783645f

Please sign in to comment.