From: Date: February 15 2006 11:06pm Subject: Connector/J commit: r4936 - in branches: branch_5_0/connector-j/src/com/mysql/jdbc branch_5_0/connector-j/src/com/mysql/jdbc/jdbc2/optional branch_5_0/connector-j/src/testsuite/simple branch_5_1/connector-j branch_5_1/connector-j/src/com/mysql/jdbc branch_5_1/connector-j/src/com/mysql/jdbc/jdbc2/optional branch_5_1/connector-j/src/testsuite/simple List-Archive: http://lists.mysql.com/commits/2691 X-Bug: 17401 Message-Id: <200602152206.k1FM6buG030252@bk-internal.mysql.com> Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added: branches/branch_5_0/connector-j/src/com/mysql/jdbc/jdbc2/optional/MysqlXAException.java branches/branch_5_1/connector-j/src/com/mysql/jdbc/jdbc2/optional/MysqlXAException.java Modified: branches/branch_5_0/connector-j/src/com/mysql/jdbc/Connection.java branches/branch_5_0/connector-j/src/com/mysql/jdbc/MysqlErrorNumbers.java branches/branch_5_0/connector-j/src/com/mysql/jdbc/ReplicationConnection.java branches/branch_5_0/connector-j/src/com/mysql/jdbc/SQLError.java branches/branch_5_0/connector-j/src/com/mysql/jdbc/jdbc2/optional/ConnectionWrapper.java branches/branch_5_0/connector-j/src/com/mysql/jdbc/jdbc2/optional/MysqlXAConnection.java branches/branch_5_0/connector-j/src/testsuite/simple/XATest.java branches/branch_5_1/connector-j/CHANGES branches/branch_5_1/connector-j/src/com/mysql/jdbc/Connection.java branches/branch_5_1/connector-j/src/com/mysql/jdbc/MysqlErrorNumbers.java branches/branch_5_1/connector-j/src/com/mysql/jdbc/SQLError.java branches/branch_5_1/connector-j/src/com/mysql/jdbc/jdbc2/optional/ConnectionWrapper.java branches/branch_5_1/connector-j/src/com/mysql/jdbc/jdbc2/optional/MysqlXAConnection.java branches/branch_5_1/connector-j/src/testsuite/simple/XATest.java Log: Fixed BUG#17401 - Can't use XAConnection for local transactions when no global transaction is in progress. Modified: branches/branch_5_0/connector-j/src/com/mysql/jdbc/Connection.java =================================================================== --- branches/branch_5_0/connector-j/src/com/mysql/jdbc/Connection.java 2006-02-15 01:01:48 UTC (rev 4935) +++ branches/branch_5_0/connector-j/src/com/mysql/jdbc/Connection.java 2006-02-15 22:06:32 UTC (rev 4936) @@ -1231,6 +1231,9 @@ /** Has this connection been closed? */ private boolean isClosed = true; + /** Is this connection associated with a global tx? */ + private boolean isInGlobalTx = false; + /** Is this connection running inside a JDK-1.3 VM? */ private boolean isRunningOnJDK13 = false; @@ -5445,4 +5448,12 @@ protected boolean isRunningOnJDK13() { return this.isRunningOnJDK13; } + + public boolean isInGlobalTx() { + return this.isInGlobalTx; + } + + public void setInGlobalTx(boolean flag) { + this.isInGlobalTx = flag; + } } Modified: branches/branch_5_0/connector-j/src/com/mysql/jdbc/MysqlErrorNumbers.java =================================================================== --- branches/branch_5_0/connector-j/src/com/mysql/jdbc/MysqlErrorNumbers.java 2006-02-15 01:01:48 UTC (rev 4935) +++ branches/branch_5_0/connector-j/src/com/mysql/jdbc/MysqlErrorNumbers.java 2006-02-15 22:06:32 UTC (rev 4936) @@ -624,6 +624,8 @@ public final static int ER_WRONG_VALUE_COUNT_ON_ROW = 1136; public final static int ER_WRONG_VALUE_FOR_VAR = 1231; + + public final static int ER_XA_RMERR = 1401; public final static int ER_YES = 1003; Modified: branches/branch_5_0/connector-j/src/com/mysql/jdbc/ReplicationConnection.java =================================================================== --- branches/branch_5_0/connector-j/src/com/mysql/jdbc/ReplicationConnection.java 2006-02-15 01:01:48 UTC (rev 4935) +++ branches/branch_5_0/connector-j/src/com/mysql/jdbc/ReplicationConnection.java 2006-02-15 22:06:32 UTC (rev 4936) @@ -379,9 +379,13 @@ */ public synchronized void setReadOnly(boolean readOnly) throws SQLException { if (readOnly) { - switchToSlavesConnection(); + if (currentConnection != slavesConnection) { + switchToSlavesConnection(); + } } else { - switchToMasterConnection(); + if (currentConnection != masterConnection) { + switchToMasterConnection(); + } } } @@ -425,60 +429,51 @@ } private synchronized void switchToMasterConnection() throws SQLException { - String slaveCatalog = this.slavesConnection.getCatalog(); - String masterCatalog = this.masterConnection.getCatalog(); - - if (slaveCatalog != null && !slaveCatalog.equals(masterCatalog)) { - this.masterConnection.setCatalog(slaveCatalog); - } else if (masterCatalog != null) { - this.masterConnection.setCatalog(masterCatalog); - } - - boolean slavesAutoCommit = this.slavesConnection.getAutoCommit(); - - if (this.masterConnection.getAutoCommit() != slavesAutoCommit) { - this.masterConnection.setAutoCommit(slavesAutoCommit); - } - - int slavesTransactionIsolation = this.slavesConnection - .getTransactionIsolation(); - - if (this.masterConnection.getTransactionIsolation() != slavesTransactionIsolation) { - this.masterConnection - .setTransactionIsolation(slavesTransactionIsolation); - } - - this.currentConnection = this.masterConnection; + swapConnections(this.masterConnection, this.slavesConnection); } private synchronized void switchToSlavesConnection() throws SQLException { - String slaveCatalog = this.slavesConnection.getCatalog(); - String masterCatalog = this.masterConnection.getCatalog(); + swapConnections(this.slavesConnection, this.masterConnection); + } + + /** + * Swaps current context (catalog, autocommit and txn_isolation) from + * sourceConnection to targetConnection, and makes targetConnection + * the "current" connection that will be used for queries. + * + * @param switchToConnection the connection to swap from + * @param switchFromConnection the connection to swap to + * + * @throws SQLException if an error occurs + */ + private synchronized void swapConnections(Connection switchToConnection, + Connection switchFromConnection) throws SQLException { + String switchFromCatalog = switchFromConnection.getCatalog(); + String switchToCatalog = switchToConnection.getCatalog(); - if (masterCatalog != null && !masterCatalog.equals(slaveCatalog)) { - this.slavesConnection.setCatalog(masterCatalog); - } else if (slaveCatalog != null) { - this.slavesConnection.setCatalog(slaveCatalog); + if (switchToCatalog != null && !switchToCatalog.equals(switchFromCatalog)) { + switchToConnection.setCatalog(switchFromCatalog); + } else if (switchFromCatalog != null) { + switchToConnection.setCatalog(switchFromCatalog); } - boolean masterAutoCommit = this.masterConnection.getAutoCommit(); - - if (this.slavesConnection.getAutoCommit() != masterAutoCommit) { - this.slavesConnection.setAutoCommit(masterAutoCommit); + boolean switchToAutoCommit = switchToConnection.getAutoCommit(); + boolean switchFromConnectionAutoCommit = switchFromConnection.getAutoCommit(); + + if (switchFromConnectionAutoCommit != switchToAutoCommit) { + switchToConnection.setAutoCommit(switchFromConnectionAutoCommit); } - int masterTransactionIsolation = this.masterConnection + int switchToIsolation = switchToConnection .getTransactionIsolation(); - if (this.slavesConnection.getTransactionIsolation() != masterTransactionIsolation) { - this.slavesConnection - .setTransactionIsolation(masterTransactionIsolation); + int switchFromIsolation = switchFromConnection.getTransactionIsolation(); + + if (switchFromIsolation != switchToIsolation) { + switchToConnection + .setTransactionIsolation(switchFromIsolation); } - this.currentConnection = this.slavesConnection; - - this.slavesConnection.setAutoCommit(this.masterConnection - .getAutoCommit()); - this.slavesConnection.setTransactionIsolation(this.masterConnection - .getTransactionIsolation()); + + this.currentConnection = switchToConnection; } } Modified: branches/branch_5_0/connector-j/src/com/mysql/jdbc/SQLError.java =================================================================== --- branches/branch_5_0/connector-j/src/com/mysql/jdbc/SQLError.java 2006-02-15 01:01:48 UTC (rev 4935) +++ branches/branch_5_0/connector-j/src/com/mysql/jdbc/SQLError.java 2006-02-15 22:06:32 UTC (rev 4936) @@ -127,6 +127,8 @@ public static final String SQL_STATE_UNABLE_TO_CONNECT_TO_DATASOURCE = "08001"; //$NON-NLS-1$ public static final String SQL_STATE_WRONG_NO_OF_PARAMETERS = "07001"; //$NON-NLS-1$ + + public static final String SQL_STATE_INVALID_TRANSACTION_TERMINATION = "2D000"; //$NON_NLS-1$ private static Map sqlStateMessages; Modified: branches/branch_5_0/connector-j/src/com/mysql/jdbc/jdbc2/optional/ConnectionWrapper.java =================================================================== --- branches/branch_5_0/connector-j/src/com/mysql/jdbc/jdbc2/optional/ConnectionWrapper.java 2006-02-15 01:01:48 UTC (rev 4935) +++ branches/branch_5_0/connector-j/src/com/mysql/jdbc/jdbc2/optional/ConnectionWrapper.java 2006-02-15 22:06:32 UTC (rev 4936) @@ -29,6 +29,7 @@ import java.sql.Savepoint; import java.sql.Statement; +import com.mysql.jdbc.MysqlErrorNumbers; import com.mysql.jdbc.SQLError; /** @@ -62,7 +63,7 @@ private boolean closed; private boolean isForXa; - + /** * Construct a new LogicalHandle and set instance variables * @@ -84,6 +85,7 @@ this.isForXa = forXa; if (this.isForXa) { + setInGlobalTx(false); setAutoCommit(false); } } @@ -97,9 +99,10 @@ public void setAutoCommit(boolean autoCommit) throws SQLException { checkClosed(); - if (autoCommit && this.isForXa) { + if (autoCommit && isInGlobalTx()) { throw SQLError.createSQLException("Can't set autocommit to 'true' on an XAConnection", - SQLError.SQL_STATE_ILLEGAL_ARGUMENT); + SQLError.SQL_STATE_INVALID_TRANSACTION_TERMINATION, + MysqlErrorNumbers.ER_XA_RMERR); } try { @@ -277,6 +280,12 @@ public java.sql.Savepoint setSavepoint() throws SQLException { checkClosed(); + if (isInGlobalTx()) { + throw SQLError.createSQLException("Can't set autocommit to 'true' on an XAConnection", + SQLError.SQL_STATE_INVALID_TRANSACTION_TERMINATION, + MysqlErrorNumbers.ER_XA_RMERR); + } + try { return this.mc.setSavepoint(); } catch (SQLException sqlException) { @@ -292,6 +301,12 @@ public java.sql.Savepoint setSavepoint(String arg0) throws SQLException { checkClosed(); + if (isInGlobalTx()) { + throw SQLError.createSQLException("Can't set autocommit to 'true' on an XAConnection", + SQLError.SQL_STATE_INVALID_TRANSACTION_TERMINATION, + MysqlErrorNumbers.ER_XA_RMERR); + } + try { return this.mc.setSavepoint(arg0); } catch (SQLException sqlException) { @@ -429,9 +444,11 @@ public void commit() throws SQLException { checkClosed(); - if (this.isForXa) { - throw SQLError.createSQLException("Can't call commit() on an XAConnection", - SQLError.SQL_STATE_ILLEGAL_ARGUMENT); + if (isInGlobalTx()) { + throw SQLError.createSQLException( + "Can't call commit() on an XAConnection associated with a global transaction", + SQLError.SQL_STATE_INVALID_TRANSACTION_TERMINATION, + MysqlErrorNumbers.ER_XA_RMERR); } try { @@ -733,9 +750,10 @@ checkClosed(); - if (this.isForXa) { - throw SQLError.createSQLException("Can't call rollback() on an XAConnection", - SQLError.SQL_STATE_ILLEGAL_ARGUMENT); + if (isInGlobalTx()) { + throw SQLError.createSQLException("Can't call rollback() on an XAConnection associated with a global transaction", + SQLError.SQL_STATE_INVALID_TRANSACTION_TERMINATION, + MysqlErrorNumbers.ER_XA_RMERR); } try { @@ -751,9 +769,10 @@ public void rollback(Savepoint arg0) throws SQLException { checkClosed(); - if (this.isForXa) { - throw SQLError.createSQLException("Can't call rollback() on an XAConnection", - SQLError.SQL_STATE_ILLEGAL_ARGUMENT); + if (isInGlobalTx()) { + throw SQLError.createSQLException("Can't call rollback() on an XAConnection associated with a global transaction", + SQLError.SQL_STATE_INVALID_TRANSACTION_TERMINATION, + MysqlErrorNumbers.ER_XA_RMERR); } try { @@ -769,7 +788,7 @@ return; } - if (!this.isForXa + if (!isInGlobalTx() && this.mc.getRollbackOnPooledClose() && !this.getAutoCommit()) { rollback(); @@ -794,4 +813,12 @@ throw SQLError.createSQLException(this.invalidHandleStr); } } + + protected boolean isInGlobalTx() { + return this.mc.isInGlobalTx(); + } + + protected void setInGlobalTx(boolean flag) { + this.mc.setInGlobalTx(flag); + } } Modified: branches/branch_5_0/connector-j/src/com/mysql/jdbc/jdbc2/optional/MysqlXAConnection.java =================================================================== --- branches/branch_5_0/connector-j/src/com/mysql/jdbc/jdbc2/optional/MysqlXAConnection.java 2006-02-15 01:01:48 UTC (rev 4935) +++ branches/branch_5_0/connector-j/src/com/mysql/jdbc/jdbc2/optional/MysqlXAConnection.java 2006-02-15 22:06:32 UTC (rev 4936) @@ -19,8 +19,6 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - - */ package com.mysql.jdbc.jdbc2.optional; @@ -190,6 +188,10 @@ * XAER_RMFAIL, XAER_INVAL, and XAER_PROTO. */ public Xid[] recover(int flag) throws XAException { + return recover(this.underlyingConnection, flag); + } + + protected static Xid[] recover(Connection c, int flag) throws XAException { switch (flag) { case TMSTARTRSCAN: case TMENDRSCAN: @@ -202,7 +204,7 @@ try { // TODO: Cache this for lifetime of XAConnection - stmt = this.underlyingConnection.createStatement(); + stmt = c.createStatement(); rs = stmt.executeQuery("XA RECOVER"); @@ -329,7 +331,11 @@ commandBuf.append("XA ROLLBACK "); commandBuf.append(xidToString(xid)); - dispatchCommand(commandBuf.toString()); + try { + dispatchCommand(commandBuf.toString()); + } finally { + this.underlyingConnection.setInGlobalTx(false); + } } /** @@ -422,7 +428,8 @@ } dispatchCommand(commandBuf.toString()); - + + this.underlyingConnection.setInGlobalTx(true); } /** @@ -455,7 +462,11 @@ commandBuf.append(" ONE PHASE"); } - dispatchCommand(commandBuf.toString()); + try { + dispatchCommand(commandBuf.toString()); + } finally { + this.underlyingConnection.setInGlobalTx(false); + } } private ResultSet dispatchCommand(String command) throws XAException { @@ -487,7 +498,7 @@ } } - private XAException mapXAExceptionFromSQLException(SQLException sqlEx) { + protected static XAException mapXAExceptionFromSQLException(SQLException sqlEx) { Integer xaCode = (Integer) MYSQL_ERROR_CODES_TO_XA_ERROR_CODES .get(new Integer(sqlEx.getErrorCode())); @@ -568,44 +579,4 @@ return connToWrap; } - - /** - * The stock XAException class isn't too friendly (i.e. no - * error messages), so we extend it a bit. - */ - class MysqlXAException extends XAException { - private static final long serialVersionUID = -9075817535836563004L; - - private String message; - private String xidAsString; - - public MysqlXAException(int errorCode, String message, String xidAsString) { - super(errorCode); - this.message = message; - this.xidAsString = xidAsString; - } - - public MysqlXAException(String message, String xidAsString) { - super(); - - this.message = message; - this.xidAsString = xidAsString; - } - - public String getMessage() { - String superMessage = super.getMessage(); - StringBuffer returnedMessage = new StringBuffer(); - - if (superMessage != null) { - returnedMessage.append(superMessage); - returnedMessage.append(":"); - } - - if (this.message != null) { - returnedMessage.append(this.message); - } - - return returnedMessage.toString(); - } - } } Added: branches/branch_5_0/connector-j/src/com/mysql/jdbc/jdbc2/optional/MysqlXAException.java =================================================================== --- branches/branch_5_0/connector-j/src/com/mysql/jdbc/jdbc2/optional/MysqlXAException.java 2006-02-15 01:01:48 UTC (rev 4935) +++ branches/branch_5_0/connector-j/src/com/mysql/jdbc/jdbc2/optional/MysqlXAException.java 2006-02-15 22:06:32 UTC (rev 4936) @@ -0,0 +1,66 @@ +/* + Copyright (C) 2005 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 of the GNU General Public License 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-CONNECTOR-J 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 + + */ + +package com.mysql.jdbc.jdbc2.optional; + +import javax.transaction.xa.XAException; + +/** + * The stock XAException class isn't too friendly (i.e. no + * error messages), so we extend it a bit. + */ +class MysqlXAException extends XAException { + private static final long serialVersionUID = -9075817535836563004L; + + private String message; + private String xidAsString; + + public MysqlXAException(int errorCode, String message, String xidAsString) { + super(errorCode); + this.message = message; + this.xidAsString = xidAsString; + } + + public MysqlXAException(String message, String xidAsString) { + super(); + + this.message = message; + this.xidAsString = xidAsString; + } + + public String getMessage() { + String superMessage = super.getMessage(); + StringBuffer returnedMessage = new StringBuffer(); + + if (superMessage != null) { + returnedMessage.append(superMessage); + returnedMessage.append(":"); + } + + if (this.message != null) { + returnedMessage.append(this.message); + } + + return returnedMessage.toString(); + } +} \ No newline at end of file Modified: branches/branch_5_0/connector-j/src/testsuite/simple/XATest.java =================================================================== --- branches/branch_5_0/connector-j/src/testsuite/simple/XATest.java 2006-02-15 01:01:48 UTC (rev 4935) +++ branches/branch_5_0/connector-j/src/testsuite/simple/XATest.java 2006-02-15 22:06:32 UTC (rev 4936) @@ -28,6 +28,8 @@ 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.XAResource; @@ -40,7 +42,7 @@ /** * Unit tests for our XA implementation. - * + * * @version $Id: $ */ public class XATest extends BaseTestCase { @@ -55,10 +57,10 @@ } /** - * Tests that simple distributed transaction processing - * works as expected. + * Tests that simple distributed transaction processing works as expected. * - * @throws Exception if the test fails. + * @throws Exception + * if the test fails. */ public void testCoordination() throws Exception { if (!versionMeetsMinimum(5, 0)) { @@ -91,6 +93,11 @@ xaRes1.end(xid1, XAResource.TMSUCCESS); xaRes2.end(xid2, XAResource.TMSUCCESS); + if (true) { + xaRes1.start(xid1, XAResource.TMJOIN); + xaRes2.start(xid2, XAResource.TMJOIN); + } + xaRes1.prepare(xid1); xaRes2.prepare(xid2); @@ -123,12 +130,17 @@ conn2.createStatement().executeUpdate("INSERT INTO testCoordination VALUES (2)"); - // ensure visibility + // 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); + if (true) { + xaRes1.start(xid1, XAResource.TMJOIN); + xaRes2.start(xid2, XAResource.TMJOIN); + } + xaRes1.prepare(xid1); xaRes2.prepare(xid2); @@ -164,7 +176,8 @@ /** * Tests that XA RECOVER works as expected. * - * @throws Exception if test fails + * @throws Exception + * if test fails */ public void testRecover() throws Exception { if (!versionMeetsMinimum(5, 0)) { @@ -218,6 +231,123 @@ } } + /** + * 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(false, 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) { + xaConn1.close(); + } + } + } + private Xid createXid() throws IOException { ByteArrayOutputStream gtridOut = new ByteArrayOutputStream(); DataOutputStream dataOut = new DataOutputStream(gtridOut); Modified: branches/branch_5_1/connector-j/CHANGES =================================================================== --- branches/branch_5_1/connector-j/CHANGES 2006-02-15 01:01:48 UTC (rev 4935) +++ branches/branch_5_1/connector-j/CHANGES 2006-02-15 22:06:32 UTC (rev 4936) @@ -1,6 +1,13 @@ # Changelog # $Id$ +xx-xx-06 - Version 5.1.0-alpha + +xx-xx-06 - Version 5.0.1-beta + + - Fixed BUG#17401 - Can't use XAConnection for local transactions when + no global transaction is in progress. + 12-23-05 - Version 5.0.0-beta - XADataSource implemented (ported from 3.2 branch which won't be Modified: branches/branch_5_1/connector-j/src/com/mysql/jdbc/Connection.java =================================================================== --- branches/branch_5_1/connector-j/src/com/mysql/jdbc/Connection.java 2006-02-15 01:01:48 UTC (rev 4935) +++ branches/branch_5_1/connector-j/src/com/mysql/jdbc/Connection.java 2006-02-15 22:06:32 UTC (rev 4936) @@ -358,6 +358,9 @@ /** Has this connection been closed? */ private boolean isClosed = true; + /** Is this connection associated with a global tx? */ + private boolean isInGlobalTx = false; + /** Is this connection running inside a JDK-1.3 VM? */ private boolean isRunningOnJDK13 = false; @@ -4618,4 +4621,12 @@ return this.io.versionMeetsMinimum(major, minor, subminor); } + + public boolean isInGlobalTx() { + return this.isInGlobalTx; + } + + public void setInGlobalTx(boolean flag) { + this.isInGlobalTx = flag; + } } Modified: branches/branch_5_1/connector-j/src/com/mysql/jdbc/MysqlErrorNumbers.java =================================================================== --- branches/branch_5_1/connector-j/src/com/mysql/jdbc/MysqlErrorNumbers.java 2006-02-15 01:01:48 UTC (rev 4935) +++ branches/branch_5_1/connector-j/src/com/mysql/jdbc/MysqlErrorNumbers.java 2006-02-15 22:06:32 UTC (rev 4936) @@ -625,6 +625,8 @@ public final static int ER_WRONG_VALUE_FOR_VAR = 1231; + public final static int ER_XA_RMERR = 1401; + public final static int ER_YES = 1003; public final static int ER_ZLIB_Z_BUF_ERROR = 1258; Modified: branches/branch_5_1/connector-j/src/com/mysql/jdbc/SQLError.java =================================================================== --- branches/branch_5_1/connector-j/src/com/mysql/jdbc/SQLError.java 2006-02-15 01:01:48 UTC (rev 4935) +++ branches/branch_5_1/connector-j/src/com/mysql/jdbc/SQLError.java 2006-02-15 22:06:32 UTC (rev 4936) @@ -128,6 +128,8 @@ public static final String SQL_STATE_WRONG_NO_OF_PARAMETERS = "07001"; //$NON-NLS-1$ + public static final String SQL_STATE_INVALID_TRANSACTION_TERMINATION = "2D000"; //$NON_NLS-1$ + private static Map sqlStateMessages; static { Modified: branches/branch_5_1/connector-j/src/com/mysql/jdbc/jdbc2/optional/ConnectionWrapper.java =================================================================== --- branches/branch_5_1/connector-j/src/com/mysql/jdbc/jdbc2/optional/ConnectionWrapper.java 2006-02-15 01:01:48 UTC (rev 4935) +++ branches/branch_5_1/connector-j/src/com/mysql/jdbc/jdbc2/optional/ConnectionWrapper.java 2006-02-15 22:06:32 UTC (rev 4936) @@ -36,6 +36,7 @@ import java.sql.Statement; import java.util.Properties; +import com.mysql.jdbc.MysqlErrorNumbers; import com.mysql.jdbc.SQLError; import com.mysql.jdbc.ToBeImplementedException; @@ -92,6 +93,7 @@ this.isForXa = forXa; if (this.isForXa) { + setInGlobalTx(false); setAutoCommit(false); } } @@ -105,9 +107,10 @@ public void setAutoCommit(boolean autoCommit) throws SQLException { checkClosed(); - if (autoCommit && this.isForXa) { + if (autoCommit && isInGlobalTx()) { throw SQLError.createSQLException("Can't set autocommit to 'true' on an XAConnection", - SQLError.SQL_STATE_ILLEGAL_ARGUMENT); + SQLError.SQL_STATE_INVALID_TRANSACTION_TERMINATION, + MysqlErrorNumbers.ER_XA_RMERR); } try { @@ -285,6 +288,12 @@ public java.sql.Savepoint setSavepoint() throws SQLException { checkClosed(); + if (isInGlobalTx()) { + throw SQLError.createSQLException("Can't set autocommit to 'true' on an XAConnection", + SQLError.SQL_STATE_INVALID_TRANSACTION_TERMINATION, + MysqlErrorNumbers.ER_XA_RMERR); + } + try { return this.mc.setSavepoint(); } catch (SQLException sqlException) { @@ -300,6 +309,12 @@ public java.sql.Savepoint setSavepoint(String arg0) throws SQLException { checkClosed(); + if (isInGlobalTx()) { + throw SQLError.createSQLException("Can't set autocommit to 'true' on an XAConnection", + SQLError.SQL_STATE_INVALID_TRANSACTION_TERMINATION, + MysqlErrorNumbers.ER_XA_RMERR); + } + try { return this.mc.setSavepoint(arg0); } catch (SQLException sqlException) { @@ -437,9 +452,11 @@ public void commit() throws SQLException { checkClosed(); - if (this.isForXa) { - throw SQLError.createSQLException("Can't call commit() on an XAConnection", - SQLError.SQL_STATE_ILLEGAL_ARGUMENT); + if (isInGlobalTx()) { + throw SQLError.createSQLException( + "Can't call commit() on an XAConnection associated with a global transaction", + SQLError.SQL_STATE_INVALID_TRANSACTION_TERMINATION, + MysqlErrorNumbers.ER_XA_RMERR); } try { @@ -741,9 +758,10 @@ checkClosed(); - if (this.isForXa) { - throw SQLError.createSQLException("Can't call rollback() on an XAConnection", - SQLError.SQL_STATE_ILLEGAL_ARGUMENT); + if (isInGlobalTx()) { + throw SQLError.createSQLException("Can't call rollback() on an XAConnection associated with a global transaction", + SQLError.SQL_STATE_INVALID_TRANSACTION_TERMINATION, + MysqlErrorNumbers.ER_XA_RMERR); } try { @@ -759,9 +777,10 @@ public void rollback(Savepoint arg0) throws SQLException { checkClosed(); - if (this.isForXa) { - throw SQLError.createSQLException("Can't call rollback() on an XAConnection", - SQLError.SQL_STATE_ILLEGAL_ARGUMENT); + if (isInGlobalTx()) { + throw SQLError.createSQLException("Can't call rollback() on an XAConnection associated with a global transaction", + SQLError.SQL_STATE_INVALID_TRANSACTION_TERMINATION, + MysqlErrorNumbers.ER_XA_RMERR); } try { @@ -777,7 +796,7 @@ return; } - if (!this.isForXa + if (!isInGlobalTx() && this.mc.getRollbackOnPooledClose() && !this.getAutoCommit()) { rollback(); @@ -803,6 +822,14 @@ } } + protected boolean isInGlobalTx() { + return this.mc.isInGlobalTx(); + } + + protected void setInGlobalTx(boolean flag) { + this.mc.setInGlobalTx(flag); + } + public Clob createClob() throws SQLException { throw new ToBeImplementedException(); } Modified: branches/branch_5_1/connector-j/src/com/mysql/jdbc/jdbc2/optional/MysqlXAConnection.java =================================================================== --- branches/branch_5_1/connector-j/src/com/mysql/jdbc/jdbc2/optional/MysqlXAConnection.java 2006-02-15 01:01:48 UTC (rev 4935) +++ branches/branch_5_1/connector-j/src/com/mysql/jdbc/jdbc2/optional/MysqlXAConnection.java 2006-02-15 22:06:32 UTC (rev 4936) @@ -19,8 +19,6 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - - */ package com.mysql.jdbc.jdbc2.optional; @@ -190,6 +188,10 @@ * XAER_RMFAIL, XAER_INVAL, and XAER_PROTO. */ public Xid[] recover(int flag) throws XAException { + return recover(this.underlyingConnection, flag); + } + + protected static Xid[] recover(Connection c, int flag) throws XAException { switch (flag) { case TMSTARTRSCAN: case TMENDRSCAN: @@ -202,7 +204,7 @@ try { // TODO: Cache this for lifetime of XAConnection - stmt = this.underlyingConnection.createStatement(); + stmt = c.createStatement(); rs = stmt.executeQuery("XA RECOVER"); @@ -329,7 +331,11 @@ commandBuf.append("XA ROLLBACK "); commandBuf.append(xidToString(xid)); - dispatchCommand(commandBuf.toString()); + try { + dispatchCommand(commandBuf.toString()); + } finally { + this.underlyingConnection.setInGlobalTx(false); + } } /** @@ -422,7 +428,8 @@ } dispatchCommand(commandBuf.toString()); - + + this.underlyingConnection.setInGlobalTx(true); } /** @@ -455,7 +462,11 @@ commandBuf.append(" ONE PHASE"); } - dispatchCommand(commandBuf.toString()); + try { + dispatchCommand(commandBuf.toString()); + } finally { + this.underlyingConnection.setInGlobalTx(false); + } } private ResultSet dispatchCommand(String command) throws XAException { @@ -487,7 +498,7 @@ } } - private XAException mapXAExceptionFromSQLException(SQLException sqlEx) { + protected static XAException mapXAExceptionFromSQLException(SQLException sqlEx) { Integer xaCode = (Integer) MYSQL_ERROR_CODES_TO_XA_ERROR_CODES .get(new Integer(sqlEx.getErrorCode())); @@ -568,44 +579,4 @@ return connToWrap; } - - /** - * The stock XAException class isn't too friendly (i.e. no - * error messages), so we extend it a bit. - */ - class MysqlXAException extends XAException { - private static final long serialVersionUID = -9075817535836563004L; - - private String message; - private String xidAsString; - - public MysqlXAException(int errorCode, String message, String xidAsString) { - super(errorCode); - this.message = message; - this.xidAsString = xidAsString; - } - - public MysqlXAException(String message, String xidAsString) { - super(); - - this.message = message; - this.xidAsString = xidAsString; - } - - public String getMessage() { - String superMessage = super.getMessage(); - StringBuffer returnedMessage = new StringBuffer(); - - if (superMessage != null) { - returnedMessage.append(superMessage); - returnedMessage.append(":"); - } - - if (this.message != null) { - returnedMessage.append(this.message); - } - - return returnedMessage.toString(); - } - } } Added: branches/branch_5_1/connector-j/src/com/mysql/jdbc/jdbc2/optional/MysqlXAException.java =================================================================== --- branches/branch_5_1/connector-j/src/com/mysql/jdbc/jdbc2/optional/MysqlXAException.java 2006-02-15 01:01:48 UTC (rev 4935) +++ branches/branch_5_1/connector-j/src/com/mysql/jdbc/jdbc2/optional/MysqlXAException.java 2006-02-15 22:06:32 UTC (rev 4936) @@ -0,0 +1,66 @@ +/* + Copyright (C) 2005 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 of the GNU General Public License 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-CONNECTOR-J 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 + + */ + +package com.mysql.jdbc.jdbc2.optional; + +import javax.transaction.xa.XAException; + +/** + * The stock XAException class isn't too friendly (i.e. no + * error messages), so we extend it a bit. + */ +class MysqlXAException extends XAException { + private static final long serialVersionUID = -9075817535836563004L; + + private String message; + private String xidAsString; + + public MysqlXAException(int errorCode, String message, String xidAsString) { + super(errorCode); + this.message = message; + this.xidAsString = xidAsString; + } + + public MysqlXAException(String message, String xidAsString) { + super(); + + this.message = message; + this.xidAsString = xidAsString; + } + + public String getMessage() { + String superMessage = super.getMessage(); + StringBuffer returnedMessage = new StringBuffer(); + + if (superMessage != null) { + returnedMessage.append(superMessage); + returnedMessage.append(":"); + } + + if (this.message != null) { + returnedMessage.append(this.message); + } + + return returnedMessage.toString(); + } +} \ No newline at end of file Modified: branches/branch_5_1/connector-j/src/testsuite/simple/XATest.java =================================================================== --- branches/branch_5_1/connector-j/src/testsuite/simple/XATest.java 2006-02-15 01:01:48 UTC (rev 4935) +++ branches/branch_5_1/connector-j/src/testsuite/simple/XATest.java 2006-02-15 22:06:32 UTC (rev 4936) @@ -28,6 +28,8 @@ 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.XAResource; @@ -40,7 +42,7 @@ /** * Unit tests for our XA implementation. - * + * * @version $Id: $ */ public class XATest extends BaseTestCase { @@ -55,10 +57,10 @@ } /** - * Tests that simple distributed transaction processing - * works as expected. + * Tests that simple distributed transaction processing works as expected. * - * @throws Exception if the test fails. + * @throws Exception + * if the test fails. */ public void testCoordination() throws Exception { if (!versionMeetsMinimum(5, 0)) { @@ -91,6 +93,11 @@ xaRes1.end(xid1, XAResource.TMSUCCESS); xaRes2.end(xid2, XAResource.TMSUCCESS); + if (true) { + xaRes1.start(xid1, XAResource.TMJOIN); + xaRes2.start(xid2, XAResource.TMJOIN); + } + xaRes1.prepare(xid1); xaRes2.prepare(xid2); @@ -123,12 +130,17 @@ conn2.createStatement().executeUpdate("INSERT INTO testCoordination VALUES (2)"); - // ensure visibility + // 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); + if (true) { + xaRes1.start(xid1, XAResource.TMJOIN); + xaRes2.start(xid2, XAResource.TMJOIN); + } + xaRes1.prepare(xid1); xaRes2.prepare(xid2); @@ -164,7 +176,8 @@ /** * Tests that XA RECOVER works as expected. * - * @throws Exception if test fails + * @throws Exception + * if test fails */ public void testRecover() throws Exception { if (!versionMeetsMinimum(5, 0)) { @@ -218,6 +231,123 @@ } } + /** + * 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(false, 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) { + xaConn1.close(); + } + } + } + private Xid createXid() throws IOException { ByteArrayOutputStream gtridOut = new ByteArrayOutputStream(); DataOutputStream dataOut = new DataOutputStream(gtridOut);