/*
Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
The MySQL Connector/J is licensed under the terms of the GPLv2
<http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most MySQL Connectors.
There are special exceptions to the terms and conditions of the GPLv2 as it is applied to
this software, see the FLOSS License Exception
<http://www.mysql.com/about/legal/licensing/foss-exception.html>.
This program is free software; you can redistribute it and/or modify it under the terms
of the GNU General Public License as published by the Free Software Foundation; version 2
of the License.
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., 51 Franklin St, Fifth
Floor, Boston, MA 02110-1301 USA
*/
package testsuite.simple;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.rmi.server.UID;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Savepoint;
import javax.sql.XAConnection;
import javax.transaction.xa.XAException;
import javax.transaction.xa.XAResource;
import javax.transaction.xa.Xid;
import testsuite.BaseTestCase;
import com.mysql.jdbc.jdbc2.optional.MysqlXADataSource;
import com.mysql.jdbc.jdbc2.optional.MysqlXid;
/**
* Unit tests for our XA implementation.
*
* @version $Id: $
*/
public class XATest extends BaseTestCase {
MysqlXADataSource xaDs;
public XATest(String name) {
super(name);
this.xaDs = new MysqlXADataSource();
this.xaDs.setUrl(BaseTestCase.dbUrl);
this.xaDs.setRollbackOnPooledClose(true);
}
/**
* Tests that simple distributed transaction processing works as expected.
*
* @throws Exception
* if the test fails.
*/
public void testCoordination() throws Exception {
if (!versionMeetsMinimum(5, 0)) {
return;
}
createTable("testCoordination", "(field1 int) ENGINE=InnoDB");
Connection conn1 = null;
Connection conn2 = null;
XAConnection xaConn1 = null;
XAConnection xaConn2 = null;
try {
xaConn1 = getXAConnection();
XAResource xaRes1 = xaConn1.getXAResource();
conn1 = xaConn1.getConnection();
xaConn2 = getXAConnection();
XAResource xaRes2 = xaConn2.getXAResource();
conn2 = xaConn2.getConnection();
Xid xid1 = createXid();
Xid xid2 = createXid(xid1);
xaRes1.start(xid1, XAResource.TMNOFLAGS);
xaRes2.start(xid2, XAResource.TMNOFLAGS);
conn1.createStatement().executeUpdate("INSERT INTO testCoordination VALUES (1)");
conn2.createStatement().executeUpdate("INSERT INTO testCoordination VALUES (2)");
xaRes1.end(xid1, XAResource.TMSUCCESS);
xaRes2.end(xid2, XAResource.TMSUCCESS);
xaRes1.prepare(xid1);
xaRes2.prepare(xid2);
xaRes1.commit(xid1, false);
xaRes2.commit(xid2, false);
this.rs = this.stmt.executeQuery("SELECT field1 FROM testCoordination ORDER BY field1");
assertTrue(this.rs.next());
assertEquals(1, this.rs.getInt(1));
assertTrue(this.rs.next());
assertEquals(2, this.rs.getInt(1));
this.stmt.executeUpdate("TRUNCATE TABLE testCoordination");
//
// Now test rollback
//
xid1 = createXid();
xid2 = createXid(xid1);
xaRes1.start(xid1, XAResource.TMNOFLAGS);
xaRes2.start(xid2, XAResource.TMNOFLAGS);
conn1.createStatement().executeUpdate("INSERT INTO testCoordination VALUES (1)");
// ensure visibility
assertEquals("1", getSingleIndexedValueWithQuery(conn1, 1, "SELECT field1 FROM testCoordination WHERE field1=1").toString());
conn2.createStatement().executeUpdate("INSERT INTO testCoordination VALUES (2)");
// ensure visibility
assertEquals("2", getSingleIndexedValueWithQuery(conn2, 1, "SELECT field1 FROM testCoordination WHERE field1=2").toString());
xaRes1.end(xid1, XAResource.TMSUCCESS);
xaRes2.end(xid2, XAResource.TMSUCCESS);
xaRes1.prepare(xid1);
xaRes2.prepare(xid2);
xaRes1.rollback(xid1);
xaRes2.rollback(xid2);
this.rs = this.stmt.executeQuery("SELECT field1 FROM testCoordination ORDER BY field1");
assertTrue(!this.rs.next());
} finally {
if (conn1 != null) {
conn1.close();
}
if (conn2 != null) {
conn2.close();
}
if (xaConn1 != null) {
xaConn1.close();
}
if (xaConn2 != null) {
xaConn2.close();
}
}
}
protected XAConnection getXAConnection() throws Exception {
return this.xaDs.getXAConnection();
}
/**
* Tests that XA RECOVER works as expected.
*
* @throws Exception
* if test fails
*/
public void testRecover() throws Exception {
if (!versionMeetsMinimum(5, 0)) {
return;
}
XAConnection xaConn = null, recoverConn = null;
try {
xaConn = getXAConnection();
Connection c = xaConn.getConnection();
Xid xid = createXid();
XAResource xaRes = xaConn.getXAResource();
xaRes.start(xid, XAResource.TMNOFLAGS);
c.createStatement().executeQuery("SELECT 1");
xaRes.end(xid, XAResource.TMSUCCESS);
xaRes.prepare(xid);
// Now try and recover
recoverConn = getXAConnection();
XAResource recoverRes = recoverConn.getXAResource();
Xid[] recoveredXids = recoverRes.recover(XAResource.TMSTARTRSCAN | XAResource.TMENDRSCAN);
assertTrue(recoveredXids != null);
assertTrue(recoveredXids.length > 0);
boolean xidFound = false;
for (int i = 0; i < recoveredXids.length; i++) {
if (recoveredXids[i] != null &&
recoveredXids[i].equals(xid)) {
xidFound = true;
break;
}
}
assertTrue(xidFound);
recoverRes = recoverConn.getXAResource();
recoveredXids = recoverRes.recover(XAResource.TMSTARTRSCAN);
assertTrue(recoveredXids != null);
assertTrue(recoveredXids.length > 0);
xidFound = false;
for (int i = 0; i < recoveredXids.length; i++) {
if (recoveredXids[i] != null &&
recoveredXids[i].equals(xid)) {
xidFound = true;
break;
}
}
assertTrue(xidFound);
// Test flags
recoverRes.recover(XAResource.TMSTARTRSCAN);
recoverRes.recover(XAResource.TMENDRSCAN);
recoverRes.recover(XAResource.TMSTARTRSCAN | XAResource.TMENDRSCAN);
// This should fail
try {
recoverRes.recover(XAResource.TMSUCCESS);
fail("XAException should have been thrown");
} catch (XAException xaEx) {
assertEquals(XAException.XAER_INVAL, xaEx.errorCode);
}
} finally {
if (xaConn != null) {
xaConn.close();
}
if (recoverConn != null) {
recoverConn.close();
}
}
}
/**
* Tests operation of local transactions on XAConnections when global
* transactions are in or not in progress (follows from BUG#17401).
*
* @throws Exception
* if the testcase fails
*/
public void testLocalTransaction() throws Exception {
if (!versionMeetsMinimum(5, 0) || isRunningOnJdk131()) {
return;
}
createTable("testLocalTransaction", "(field1 int) ENGINE=InnoDB");
Connection conn1 = null;
XAConnection xaConn1 = null;
try {
xaConn1 = getXAConnection();
XAResource xaRes1 = xaConn1.getXAResource();
conn1 = xaConn1.getConnection();
assertEquals(true, conn1.getAutoCommit());
conn1.setAutoCommit(true);
conn1.createStatement().executeUpdate(
"INSERT INTO testLocalTransaction VALUES (1)");
assertEquals("1", getSingleIndexedValueWithQuery(conn1, 1,
"SELECT field1 FROM testLocalTransaction").toString());
conn1.createStatement().executeUpdate(
"TRUNCATE TABLE testLocalTransaction");
conn1.setAutoCommit(false);
conn1.createStatement().executeUpdate(
"INSERT INTO testLocalTransaction VALUES (2)");
assertEquals("2", getSingleIndexedValueWithQuery(conn1, 1,
"SELECT field1 FROM testLocalTransaction").toString());
conn1.rollback();
assertEquals(0, getRowCount("testLocalTransaction"));
conn1.createStatement().executeUpdate(
"INSERT INTO testLocalTransaction VALUES (3)");
assertEquals("3", getSingleIndexedValueWithQuery(conn1, 1,
"SELECT field1 FROM testLocalTransaction").toString());
conn1.commit();
assertEquals("3", getSingleIndexedValueWithQuery(conn1, 1,
"SELECT field1 FROM testLocalTransaction").toString());
conn1.commit();
Savepoint sp = conn1.setSavepoint();
conn1.rollback(sp);
sp = conn1.setSavepoint("abcd");
conn1.rollback(sp);
Savepoint spSaved = sp;
Xid xid = createXid();
xaRes1.start(xid, XAResource.TMNOFLAGS);
try {
try {
conn1.setAutoCommit(true);
} catch (SQLException sqlEx) {
// we expect an exception here
assertEquals("2D000", sqlEx.getSQLState());
}
try {
conn1.commit();
} catch (SQLException sqlEx) {
// we expect an exception here
assertEquals("2D000", sqlEx.getSQLState());
}
try {
conn1.rollback();
} catch (SQLException sqlEx) {
// we expect an exception here
assertEquals("2D000", sqlEx.getSQLState());
}
try {
sp = conn1.setSavepoint();
} catch (SQLException sqlEx) {
// we expect an exception here
assertEquals("2D000", sqlEx.getSQLState());
}
try {
conn1.rollback(spSaved);
} catch (SQLException sqlEx) {
// we expect an exception here
assertEquals("2D000", sqlEx.getSQLState());
}
try {
sp = conn1.setSavepoint("abcd");
} catch (SQLException sqlEx) {
// we expect an exception here
assertEquals("2D000", sqlEx.getSQLState());
}
try {
conn1.rollback(spSaved);
} catch (SQLException sqlEx) {
// we expect an exception here
assertEquals("2D000", sqlEx.getSQLState());
}
} finally {
xaRes1.forget(xid);
}
} finally {
if (xaConn1 != null) {
try {
xaConn1.close();
} catch (SQLException sqlEx) {
// this is just busted in the server right now
}
}
}
}
public void testSuspendableTx() throws Exception {
if (!versionMeetsMinimum(5, 0) || isRunningOnJdk131()) {
return;
}
Connection conn1 = null;
MysqlXADataSource suspXaDs = new MysqlXADataSource();
suspXaDs.setUrl(BaseTestCase.dbUrl);
suspXaDs.setPinGlobalTxToPhysicalConnection(true);
suspXaDs.setRollbackOnPooledClose(true);
XAConnection xaConn1 = null;
Xid xid = createXid();
try {
/*
-- works using RESUME
xa start 0x123,0x456;
select * from foo;
xa end 0x123,0x456;
xa start 0x123,0x456 resume;
select * from foo;
xa end 0x123,0x456;
xa commit 0x123,0x456 one phase;
*/
xaConn1 = suspXaDs.getXAConnection();
XAResource xaRes1 = xaConn1.getXAResource();
conn1 = xaConn1.getConnection();
xaRes1.start(xid, XAResource.TMNOFLAGS);
conn1.createStatement().executeQuery("SELECT 1");
xaRes1.end(xid, XAResource.TMSUCCESS);
xaRes1.start(xid, XAResource.TMRESUME);
conn1.createStatement().executeQuery("SELECT 1");
xaRes1.end(xid, XAResource.TMSUCCESS);
xaRes1.commit(xid, true);
xaConn1.close();
/*
-- fails using JOIN
xa start 0x123,0x456;
select * from foo;
xa end 0x123,0x456;
xa start 0x123,0x456 join;
select * from foo;
xa end 0x123,0x456;
xa commit 0x123,0x456 one phase;
*/
xaConn1 = suspXaDs.getXAConnection();
xaRes1 = xaConn1.getXAResource();
conn1 = xaConn1.getConnection();
xaRes1.start(xid, XAResource.TMNOFLAGS);
conn1.createStatement().executeQuery("SELECT 1");
xaRes1.end(xid, XAResource.TMSUCCESS);
xaRes1.start(xid, XAResource.TMJOIN);
conn1.createStatement().executeQuery("SELECT 1");
xaRes1.end(xid, XAResource.TMSUCCESS);
xaRes1.commit(xid, true);
} finally {
if (xaConn1 != null) {
xaConn1.close();
}
}
}
private Xid createXid() throws IOException {
ByteArrayOutputStream gtridOut = new ByteArrayOutputStream();
DataOutputStream dataOut = new DataOutputStream(gtridOut);
new UID().write(dataOut);
final byte[] gtrid = gtridOut.toByteArray();
ByteArrayOutputStream bqualOut = new ByteArrayOutputStream();
dataOut = new DataOutputStream(bqualOut);
new UID().write(dataOut);
final byte[] bqual = bqualOut.toByteArray();
Xid xid = new MysqlXid(gtrid, bqual, 3306);
return xid;
}
private Xid createXid(Xid xidToBranch) throws IOException {
ByteArrayOutputStream bqualOut = new ByteArrayOutputStream();
DataOutputStream dataOut = new DataOutputStream(bqualOut);
new UID().write(dataOut);
final byte[] bqual = bqualOut.toByteArray();
Xid xid = new MysqlXid(xidToBranch.getGlobalTransactionId(), bqual, 3306);
return xid;
}
}