// Copyright (C) 2004-2007 MySQL AB // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License version 2 as published by // the Free Software Foundation // // There are special exceptions to the terms and conditions of the GPL // as it is applied to this software. View the full text of the // exception in file EXCEPTIONS in the directory of this software // distribution. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA using System; using System.Data; using System.IO; using NUnit.Framework; using System.Transactions; using System.Data.Common; using System.Threading; namespace MySql.Data.MySqlClient.Tests { [TestFixture] public class Transactions : BaseTest { void TransactionScopeInternal(bool commit) { createTable("CREATE TABLE Test (key2 VARCHAR(1), name VARCHAR(100), name2 VARCHAR(100))", "INNODB"); using (MySqlConnection c = new MySqlConnection(GetConnectionString(true))) { MySqlCommand cmd = new MySqlCommand("INSERT INTO Test VALUES ('a', 'name', 'name2')", c); using (TransactionScope ts = new TransactionScope()) { c.Open(); cmd.ExecuteNonQuery(); if (commit) ts.Complete(); } cmd.CommandText = "SELECT COUNT(*) FROM Test"; object count = cmd.ExecuteScalar(); Assert.AreEqual(commit ? 1 : 0, count); } } [Test] public void TransactionScopeRollback() { TransactionScopeInternal(false); } [Test] public void TransactionScopeCommit() { TransactionScopeInternal(true); } // The following block is not currently supported /* void TransactionScopeMultipleInternal(bool commit) { MySqlConnection c1 = new MySqlConnection(GetConnectionString(true)); MySqlConnection c2 = new MySqlConnection(GetConnectionString(true)); MySqlCommand cmd1 = new MySqlCommand("INSERT INTO Test VALUES ('a', 'name', 'name2')", c1); MySqlCommand cmd2 = new MySqlCommand("INSERT INTO Test VALUES ('b', 'name', 'name2')", c1); try { using (TransactionScope ts = new TransactionScope()) { c1.Open(); cmd1.ExecuteNonQuery(); c2.Open(); cmd2.ExecuteNonQuery(); if (commit) ts.Complete(); } cmd1.CommandText = "SELECT COUNT(*) FROM Test"; object count = cmd1.ExecuteScalar(); Assert.AreEqual(commit ? 2 : 0, count); } catch (Exception ex) { Assert.Fail(ex.Message); } finally { if (c1 != null) c1.Close(); if (c2 != null) c2.Close(); } } [Test] public void TransactionScopeMultipleRollback() { TransactionScopeMultipleInternal(false); } [Test] public void TransactionScopeMultipleCommit() { TransactionScopeMultipleInternal(true); } */ /// /// Bug #34448 Connector .Net 5.2.0 with Transactionscope doesn´t use specified IsolationLevel /// [Test] public void TransactionScopeWithIsolationLevel() { TransactionOptions opts = new TransactionOptions(); opts.IsolationLevel = System.Transactions.IsolationLevel.ReadCommitted; using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required, opts)) { string connStr = GetConnectionString(true); using (MySqlConnection myconn = new MySqlConnection(connStr)) { myconn.Open(); MySqlCommand cmd = new MySqlCommand("SHOW VARIABLES LIKE 'tx_isolation'", myconn); using (MySqlDataReader reader = cmd.ExecuteReader()) { reader.Read(); string level = reader.GetString(1); Assert.AreEqual("READ-COMMITTED", level); } } } } /// /// Bug #27289 Transaction is not rolledback when connection close /// [Test] public void RollingBackOnClose() { execSQL("CREATE TABLE Test (id INT) ENGINE=InnoDB"); string connStr = GetPoolingConnectionString(); using (MySqlConnection c = new MySqlConnection(connStr)) { c.Open(); MySqlCommand cmd = new MySqlCommand("INSERT INTO Test VALUES (1)", c); c.BeginTransaction(); cmd.ExecuteNonQuery(); } using (MySqlConnection c2 = new MySqlConnection(connStr)) { c2.Open(); MySqlCommand cmd2 = new MySqlCommand("SELECT COUNT(*) from Test", c2); c2.BeginTransaction(); object count = cmd2.ExecuteScalar(); Assert.AreEqual(0, count); } MySqlConnection connection = new MySqlConnection(connStr); connection.Open(); KillConnection(connection); } /// /// Bug #22042 mysql-connector-net-5.0.0-alpha BeginTransaction /// [Test] public void Bug22042() { DbProviderFactory factory = new MySql.Data.MySqlClient.MySqlClientFactory(); using (DbConnection conexion = factory.CreateConnection()) { conexion.ConnectionString = GetConnectionString(true); conexion.Open(); DbTransaction trans = conexion.BeginTransaction(); trans.Rollback(); } } /// /// Bug #26754 EnlistTransaction throws false MySqlExeption "Already enlisted" /// [Test] public void EnlistTransactionNullTest() { try { MySqlCommand cmd = new MySqlCommand(); cmd.Connection = conn; cmd.Connection.EnlistTransaction(null); } catch { } using (TransactionScope ts = new TransactionScope()) { MySqlCommand cmd = new MySqlCommand(); cmd.Connection = conn; cmd.Connection.EnlistTransaction(Transaction.Current); } } /// /// Bug #26754 EnlistTransaction throws false MySqlExeption "Already enlisted" /// [Test] public void EnlistTransactionWNestedTrxTest() { MySqlTransaction t = conn.BeginTransaction(); using (TransactionScope ts = new TransactionScope()) { MySqlCommand cmd = new MySqlCommand(); cmd.Connection = conn; try { cmd.Connection.EnlistTransaction(Transaction.Current); } catch (InvalidOperationException) { /* caught NoNestedTransactions */ } } t.Rollback(); using (TransactionScope ts = new TransactionScope()) { MySqlCommand cmd = new MySqlCommand(); cmd.Connection = conn; cmd.Connection.EnlistTransaction(Transaction.Current); } } [Test] public void ManualEnlistment() { createTable("CREATE TABLE Test (key2 VARCHAR(1), name VARCHAR(100), name2 VARCHAR(100))", "INNODB"); string connStr = GetConnectionString(true) + ";auto enlist=false"; MySqlConnection c = null; using (TransactionScope ts = new TransactionScope()) { c = new MySqlConnection(connStr); c.Open(); MySqlCommand cmd = new MySqlCommand("INSERT INTO Test VALUES ('a', 'name', 'name2')", c); cmd.ExecuteNonQuery(); } MySqlCommand cmd2 = new MySqlCommand("SELECT COUNT(*) FROM Test", conn); Assert.AreEqual(1, cmd2.ExecuteScalar()); c.Dispose(); KillPooledConnection(connStr); } private void ManuallyEnlistingInitialConnection(bool complete) { createTable("CREATE TABLE Test (key2 VARCHAR(1), name VARCHAR(100), name2 VARCHAR(100))", "INNODB"); string connStr = GetConnectionString(true) + ";auto enlist=false"; using (TransactionScope ts = new TransactionScope()) { using (MySqlConnection c1 = new MySqlConnection(connStr)) { c1.Open(); c1.EnlistTransaction(Transaction.Current); MySqlCommand cmd1 = new MySqlCommand("INSERT INTO Test (key2) VALUES ('a')", c1); cmd1.ExecuteNonQuery(); } using (MySqlConnection c2 = new MySqlConnection(connStr)) { c2.Open(); c2.EnlistTransaction(Transaction.Current); MySqlCommand cmd2 = new MySqlCommand("INSERT INTO Test (key2) VALUES ('b')", c2); cmd2.ExecuteNonQuery(); } if (complete) ts.Complete(); } KillPooledConnection(connStr); } [Test] public void ManuallyEnlistingInitialConnection() { ManuallyEnlistingInitialConnection(true); } [Test] public void ManuallyEnlistingInitialConnectionNoComplete() { ManuallyEnlistingInitialConnection(false); } [Test] public void ManualEnlistmentWithActiveConnection() { using (TransactionScope ts = new TransactionScope()) { string connStr = GetConnectionString(true); using (MySqlConnection c1 = new MySqlConnection(connStr)) { c1.Open(); connStr += "; auto enlist=false"; using (MySqlConnection c2 = new MySqlConnection(connStr)) { c2.Open(); try { c2.EnlistTransaction(Transaction.Current); } catch (NotSupportedException) { } } } } } [Test] public void AttemptToEnlistTwoConnections() { using (TransactionScope ts = new TransactionScope()) { string connStr = GetConnectionString(true); using (MySqlConnection c1 = new MySqlConnection(connStr)) { c1.Open(); using (MySqlConnection c2 = new MySqlConnection(connStr)) { try { c2.Open(); } catch (NotSupportedException) { } } } } } private void ReusingSameConnection(bool pooling, bool complete) { int c1Thread; execSQL("TRUNCATE TABLE Test"); using (TransactionScope ts = new TransactionScope(TransactionScopeOption.RequiresNew, TimeSpan.MaxValue)) { string connStr = GetConnectionString(true); if (!pooling) connStr += ";pooling=false"; using (MySqlConnection c1 = new MySqlConnection(connStr)) { c1.Open(); MySqlCommand cmd1 = new MySqlCommand("INSERT INTO Test (key2) VALUES ('a')", c1); cmd1.ExecuteNonQuery(); c1Thread = c1.ServerThread; } using (MySqlConnection c2 = new MySqlConnection(connStr)) { c2.Open(); MySqlCommand cmd2 = new MySqlCommand("INSERT INTO Test (key2) VALUES ('b')", c2); cmd2.ExecuteNonQuery(); Assert.AreEqual(c1Thread, c2.ServerThread); } if (complete) ts.Complete(); } MySqlDataAdapter da = new MySqlDataAdapter("SELECT * FROM Test", conn); DataTable dt = new DataTable(); da.Fill(dt); if (complete) { Assert.AreEqual(2, dt.Rows.Count); Assert.AreEqual("a", dt.Rows[0][0]); Assert.AreEqual("b", dt.Rows[1][0]); } else { Assert.AreEqual(0, dt.Rows.Count); } } [Test] public void ReusingSameConnection() { createTable("CREATE TABLE Test (key2 VARCHAR(1), name VARCHAR(100), name2 VARCHAR(100))", "INNODB"); ReusingSameConnection(true, true); // Assert.AreEqual(processes + 1, CountProcesses()); ReusingSameConnection(true, false); // Assert.AreEqual(processes + 1, CountProcesses()); ReusingSameConnection(false, true); // Assert.AreEqual(processes + 1, CountProcesses()); ReusingSameConnection(false, false); // Assert.AreEqual(processes + 1, CountProcesses()); } /// /// bug#35330 - even if transaction scope has expired, rows can be inserted into /// the table, due to race condition with the thread doing rollback /// [Test] public void ScopeTimeoutWithMySqlHelper() { execSQL("DROP TABLE IF EXISTS Test"); createTable("CREATE TABLE Test (id int)", "INNODB"); string connStr = GetConnectionString(true); using (new TransactionScope(TransactionScopeOption.RequiresNew,TimeSpan.FromSeconds(1))) { try { for (int i = 0; ; i++) { MySqlHelper.ExecuteNonQuery(connStr, String.Format("INSERT INTO Test VALUES({0})", i));; } } catch (Exception) { } } long count = (long)MySqlHelper.ExecuteScalar(connStr,"select count(*) from test"); Assert.AreEqual(0, count); } /// /// Variation of previous test, with a single connection and maual enlistment. /// Checks that transaction rollback leaves the connection intact (does not close it) /// and checks that no command is possible after scope has expired and /// rollback by timer thread is finished. /// [Test] public void AttemptToUseConnectionAfterScopeTimeout() { execSQL("DROP TABLE IF EXISTS Test"); createTable("CREATE TABLE Test (id int)", "INNODB"); string connStr = GetConnectionString(true); using (MySqlConnection c = new MySqlConnection(connStr)) { c.Open(); MySqlCommand cmd = new MySqlCommand("select 1", c); using (new TransactionScope(TransactionScopeOption.RequiresNew, TimeSpan.FromSeconds(1))) { c.EnlistTransaction(Transaction.Current); cmd = new MySqlCommand("select 1", c); try { for (int i = 0; ; i++) { cmd.CommandText = String.Format("INSERT INTO Test VALUES({0})", i); cmd.ExecuteNonQuery(); } } catch (Exception) { // Eat exception } // Here, scope is timed out and rollback is in progress. // Wait until timeout thread finishes rollback then try to // use an aborted connection. Thread.Sleep(500); try { cmd.ExecuteNonQuery(); Assert.Fail("Using aborted transaction"); } catch (TransactionAbortedException) { } } Assert.IsTrue(c.State == ConnectionState.Open); cmd.CommandText = "select count(*) from Test"; long count = (long)cmd.ExecuteScalar(); Assert.AreEqual(0, count); } } } }