Index: org/postgresql/jdbc2/AbstractJdbc2Connection.java =================================================================== RCS file: /usr/local/cvsroot/pgjdbc/pgjdbc/org/postgresql/jdbc2/AbstractJdbc2Connection.java,v retrieving revision 1.37 diff -c -r1.37 AbstractJdbc2Connection.java *** org/postgresql/jdbc2/AbstractJdbc2Connection.java 24 Nov 2005 06:44:21 -0000 1.37 --- org/postgresql/jdbc2/AbstractJdbc2Connection.java 12 Feb 2006 17:57:44 -0000 *************** *** 1073,1078 **** --- 1073,1083 ---- return protoConnection.getProtocolVersion(); } + public int getTransactionState() + { + return protoConnection.getTransactionState(); + } + public boolean getStringVarcharFlag() { return bindStringAsVarchar; Index: org/postgresql/test/xa/XADataSourceTest.java =================================================================== RCS file: /usr/local/cvsroot/pgjdbc/pgjdbc/org/postgresql/test/xa/XADataSourceTest.java,v retrieving revision 1.5 diff -c -r1.5 XADataSourceTest.java *** org/postgresql/test/xa/XADataSourceTest.java 24 Nov 2005 02:31:44 -0000 1.5 --- org/postgresql/test/xa/XADataSourceTest.java 12 Feb 2006 17:57:45 -0000 *************** *** 187,201 **** } } ! public void testRollback() throws XAException { Xid xid = new CustomXid(3); xaRes.start(xid, XAResource.TMNOFLAGS); xaRes.end(xid, XAResource.TMSUCCESS); xaRes.prepare(xid); xaRes.rollback(xid); } public void testRollbackWithoutPrepare() throws XAException { Xid xid = new CustomXid(4); --- 187,210 ---- } } ! public void testRollback() throws Exception { Xid xid = new CustomXid(3); xaRes.start(xid, XAResource.TMNOFLAGS); + conn.createStatement().executeQuery("SELECT * FROM testxa1"); xaRes.end(xid, XAResource.TMSUCCESS); xaRes.prepare(xid); xaRes.rollback(xid); } + public void testEmptyTransaction() throws Exception { + Xid xid = new CustomXid(6); + + xaRes.start(xid, XAResource.TMNOFLAGS); + xaRes.end(xid, XAResource.TMSUCCESS); + assertEquals(xaRes.prepare(xid), XAResource.XA_RDONLY); + } + public void testRollbackWithoutPrepare() throws XAException { Xid xid = new CustomXid(4); *************** *** 204,209 **** --- 213,236 ---- xaRes.rollback(xid); } + public void testPrepareInAbortedTransaction() throws Exception { + Xid xid = new CustomXid(5); + xaRes.start(xid, XAResource.TMNOFLAGS); + try { + conn.createStatement().executeQuery("SELECT * FROM table_that_doesnt_exist"); + } catch(SQLException ex) { } + xaRes.end(xid, XAResource.TMSUCCESS); + try { + xaRes.prepare(xid); + xaRes.rollback(xid); + fail("Prepare succeeded on aborted transaction"); + } catch(XAException ex) + { + assertTrue("Prepare returned unexpected error code"+ex.errorCode, ex.errorCode >= XAException.XA_RBBASE); + assertTrue("Prepare returned unexpected error code"+ex.errorCode, ex.errorCode <= XAException.XA_RBEND); + } + } + /* We don't support transaction interleaving. public void testInterleaving1() throws Exception { Xid xid1 = new CustomXid(1); Index: org/postgresql/xa/PGXAConnection.java =================================================================== RCS file: /usr/local/cvsroot/pgjdbc/pgjdbc/org/postgresql/xa/PGXAConnection.java,v retrieving revision 1.6 diff -c -r1.6 PGXAConnection.java *** org/postgresql/xa/PGXAConnection.java 24 Nov 2005 07:25:16 -0000 1.6 --- org/postgresql/xa/PGXAConnection.java 12 Feb 2006 17:57:45 -0000 *************** *** 4,9 **** --- 4,16 ---- import org.postgresql.ds.common.PooledConnectionImpl; import org.postgresql.core.BaseConnection; import org.postgresql.core.Logger; + import org.postgresql.core.QueryExecutor; + import org.postgresql.core.Query; + import org.postgresql.core.ResultHandler; + import org.postgresql.core.ResultCursor; + import org.postgresql.core.Field; + import org.postgresql.core.ProtocolConnection; + import org.postgresql.jdbc2.AbstractJdbc2Connection; import org.postgresql.util.GT; *************** *** 33,39 **** * Underlying physical database connection. It's used for issuing PREPARE TRANSACTION/ * COMMIT PREPARED/ROLLBACK PREPARED commands. */ ! private final BaseConnection conn; private final Logger logger; private Xid currentXid; --- 40,46 ---- * Underlying physical database connection. It's used for issuing PREPARE TRANSACTION/ * COMMIT PREPARED/ROLLBACK PREPARED commands. */ ! private final AbstractJdbc2Connection conn; private final Logger logger; private Xid currentXid; *************** *** 44,60 **** private static final int STATE_ACTIVE = 1; private static final int STATE_ENDED = 2; private void debug(String s) { logger.debug("XAResource " + Integer.toHexString(this.hashCode()) + ": " + s); } ! PGXAConnection(BaseConnection conn) throws SQLException { super(conn, false); this.conn = conn; this.conn.setAutoCommit(false); this.state = STATE_IDLE; ! this.logger = conn.getLogger(); } --- 51,70 ---- private static final int STATE_ACTIVE = 1; private static final int STATE_ENDED = 2; + private QueryExecutor queryExecutor; + private void debug(String s) { logger.debug("XAResource " + Integer.toHexString(this.hashCode()) + ": " + s); } ! PGXAConnection(AbstractJdbc2Connection conn) throws SQLException { super(conn, false); this.conn = conn; this.conn.setAutoCommit(false); this.state = STATE_IDLE; ! this.logger = conn.getLogger(); ! this.queryExecutor = conn.getQueryExecutor(); } *************** *** 180,205 **** if (!conn.haveMinimumServerVersion("8.1")) throw new PGXAException(GT.tr("Server versions prior to 8.1 do not support two-phase commit."), XAException.XAER_RMERR); ! try { ! String s = RecoveredXid.xidToString(xid); ! Statement stmt = conn.createStatement(); ! try ! { ! stmt.executeUpdate("PREPARE TRANSACTION '" + s + "'"); ! } ! finally ! { ! stmt.close(); ! } ! return XA_OK; ! } ! catch (SQLException ex) ! { ! throw new PGXAException(GT.tr("Error preparing transaction"), ex, XAException.XAER_RMERR); ! } } /** --- 190,238 ---- if (!conn.haveMinimumServerVersion("8.1")) throw new PGXAException(GT.tr("Server versions prior to 8.1 do not support two-phase commit."), XAException.XAER_RMERR); ! // Done with preconditions. Check the transaction state. If ! // there was no transaction in progress, return XA_RDONLY. ! // If the transaction had already failed, throw an exception. ! ! switch(conn.getTransactionState()) { ! case ProtocolConnection.TRANSACTION_IDLE: ! return XAResource.XA_RDONLY; ! case ProtocolConnection.TRANSACTION_FAILED: ! throw new PGXAException(GT.tr("Transaction had already failed"), XAException.XA_RBOTHER); ! case ProtocolConnection.TRANSACTION_OPEN: ! try { ! String s = RecoveredXid.xidToString(xid); ! ! Statement stmt = conn.createStatement(); ! Query prepareQuery = null; ! try ! { ! prepareQuery = queryExecutor.createSimpleQuery("PREPARE TRANSACTION '" + s + "'"); ! queryExecutor.execute(prepareQuery, null, ! new PrepareCommandHandler(), 0, 0, ! QueryExecutor.QUERY_NO_METADATA | QueryExecutor.QUERY_NO_RESULTS | QueryExecutor.QUERY_SUPPRESS_BEGIN); ! ! } ! finally ! { ! if(prepareQuery != null) ! prepareQuery.close(); ! } ! ! return XA_OK; ! } ! catch (SQLException ex) ! { ! throw new PGXAException(GT.tr("Error preparing transaction"), ex, XAException.XAER_RMERR); ! } ! // break; // Unreachable. ! ! default: ! throw new Error("Unexpected transaction state "+conn.getTransactionState()); ! } } /** *************** *** 431,434 **** --- 464,498 ---- public boolean setTransactionTimeout(int seconds) { return false; } + + // + // Handler for PREPARE TRANSACTION queries + // + private class PrepareCommandHandler implements ResultHandler { + private SQLException error; + + public void handleResultRows(Query fromQuery, Field[] fields, Vector tuples, ResultCursor cursor) { + } + public void handleCommandStatus(String status, int updateCount, long insertOID) { + if(!status.equals("PREPARE TRANSACTION")) + handleError(new SQLException("Error preparing transaction")); + } + + public void handleWarning(SQLWarning warning) { + PGXAConnection.this.conn.addWarning(warning); + } + + public void handleError(SQLException newError) { + if (error == null) + error = newError; + else + error.setNextException(newError); + } + + public void handleCompletion() throws SQLException { + if (error != null) + throw error; + } + } + } Index: org/postgresql/xa/PGXADataSource.java =================================================================== RCS file: /usr/local/cvsroot/pgjdbc/pgjdbc/org/postgresql/xa/PGXADataSource.java,v retrieving revision 1.2 diff -c -r1.2 PGXADataSource.java *** org/postgresql/xa/PGXADataSource.java 29 Oct 2005 18:59:39 -0000 1.2 --- org/postgresql/xa/PGXADataSource.java 12 Feb 2006 17:57:45 -0000 *************** *** 7,14 **** import javax.sql.XAConnection; import javax.sql.XADataSource; - import org.postgresql.core.BaseConnection; import org.postgresql.ds.common.BaseDataSource; /** * XA-enabled DataSource implementation. --- 7,14 ---- import javax.sql.XAConnection; import javax.sql.XADataSource; import org.postgresql.ds.common.BaseDataSource; + import org.postgresql.jdbc2.AbstractJdbc2Connection; /** * XA-enabled DataSource implementation. *************** *** 44,50 **** public XAConnection getXAConnection(String user, String password) throws SQLException { Connection con = super.getConnection(user, password); ! return new PGXAConnection((BaseConnection) con); } public String getDescription() { --- 44,50 ---- public XAConnection getXAConnection(String user, String password) throws SQLException { Connection con = super.getConnection(user, password); ! return new PGXAConnection((AbstractJdbc2Connection) con); } public String getDescription() {