001: /*
002: Copyright (C) 2005 MySQL AB
003:
004: This program is free software; you can redistribute it and/or modify
005: it under the terms of version 2 of the GNU General Public License as
006: published by the Free Software Foundation.
007:
008: There are special exceptions to the terms and conditions of the GPL
009: as it is applied to this software. View the full text of the
010: exception in file EXCEPTIONS-CONNECTOR-J in the directory of this
011: software distribution.
012:
013: This program is distributed in the hope that it will be useful,
014: but WITHOUT ANY WARRANTY; without even the implied warranty of
015: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
016: GNU General Public License for more details.
017:
018: You should have received a copy of the GNU General Public License
019: along with this program; if not, write to the Free Software
020: Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
021:
022: */
023:
024: package testsuite.simple;
025:
026: import java.io.ByteArrayOutputStream;
027: import java.io.DataOutputStream;
028: import java.io.IOException;
029: import java.rmi.server.UID;
030: import java.sql.Connection;
031: import java.sql.SQLException;
032: import java.sql.Savepoint;
033:
034: import javax.sql.XAConnection;
035: import javax.transaction.xa.XAException;
036: import javax.transaction.xa.XAResource;
037: import javax.transaction.xa.Xid;
038:
039: import com.mysql.jdbc.jdbc2.optional.MysqlXADataSource;
040: import com.mysql.jdbc.jdbc2.optional.MysqlXid;
041:
042: import testsuite.BaseTestCase;
043:
044: /**
045: * Unit tests for our XA implementation.
046: *
047: * @version $Id: $
048: */
049: public class XATest extends BaseTestCase {
050: MysqlXADataSource xaDs;
051:
052: public XATest(String name) {
053: super (name);
054:
055: this .xaDs = new MysqlXADataSource();
056: this .xaDs.setUrl(BaseTestCase.dbUrl);
057: this .xaDs.setRollbackOnPooledClose(true);
058: }
059:
060: /**
061: * Tests that simple distributed transaction processing works as expected.
062: *
063: * @throws Exception
064: * if the test fails.
065: */
066: public void testCoordination() throws Exception {
067: if (!versionMeetsMinimum(5, 0)) {
068: return;
069: }
070:
071: createTable("testCoordination", "(field1 int) ENGINE=InnoDB");
072:
073: Connection conn1 = null;
074: Connection conn2 = null;
075: XAConnection xaConn1 = null;
076: XAConnection xaConn2 = null;
077:
078: try {
079: xaConn1 = getXAConnection();
080: XAResource xaRes1 = xaConn1.getXAResource();
081: conn1 = xaConn1.getConnection();
082:
083: xaConn2 = getXAConnection();
084: XAResource xaRes2 = xaConn2.getXAResource();
085: conn2 = xaConn2.getConnection();
086:
087: Xid xid1 = createXid();
088: Xid xid2 = createXid(xid1);
089:
090: xaRes1.start(xid1, XAResource.TMNOFLAGS);
091: xaRes2.start(xid2, XAResource.TMNOFLAGS);
092: conn1.createStatement().executeUpdate(
093: "INSERT INTO testCoordination VALUES (1)");
094: conn2.createStatement().executeUpdate(
095: "INSERT INTO testCoordination VALUES (2)");
096: xaRes1.end(xid1, XAResource.TMSUCCESS);
097: xaRes2.end(xid2, XAResource.TMSUCCESS);
098:
099: xaRes1.prepare(xid1);
100: xaRes2.prepare(xid2);
101:
102: xaRes1.commit(xid1, false);
103: xaRes2.commit(xid2, false);
104:
105: this .rs = this .stmt
106: .executeQuery("SELECT field1 FROM testCoordination ORDER BY field1");
107:
108: assertTrue(this .rs.next());
109: assertEquals(1, this .rs.getInt(1));
110:
111: assertTrue(this .rs.next());
112: assertEquals(2, this .rs.getInt(1));
113:
114: this .stmt.executeUpdate("TRUNCATE TABLE testCoordination");
115:
116: //
117: // Now test rollback
118: //
119:
120: xid1 = createXid();
121: xid2 = createXid(xid1);
122:
123: xaRes1.start(xid1, XAResource.TMNOFLAGS);
124: xaRes2.start(xid2, XAResource.TMNOFLAGS);
125: conn1.createStatement().executeUpdate(
126: "INSERT INTO testCoordination VALUES (1)");
127:
128: // ensure visibility
129: assertEquals(
130: "1",
131: getSingleIndexedValueWithQuery(conn1, 1,
132: "SELECT field1 FROM testCoordination WHERE field1=1")
133: .toString());
134:
135: conn2.createStatement().executeUpdate(
136: "INSERT INTO testCoordination VALUES (2)");
137:
138: // ensure visibility
139: assertEquals(
140: "2",
141: getSingleIndexedValueWithQuery(conn2, 1,
142: "SELECT field1 FROM testCoordination WHERE field1=2")
143: .toString());
144:
145: xaRes1.end(xid1, XAResource.TMSUCCESS);
146: xaRes2.end(xid2, XAResource.TMSUCCESS);
147:
148: xaRes1.prepare(xid1);
149: xaRes2.prepare(xid2);
150:
151: xaRes1.rollback(xid1);
152: xaRes2.rollback(xid2);
153:
154: this .rs = this .stmt
155: .executeQuery("SELECT field1 FROM testCoordination ORDER BY field1");
156:
157: assertTrue(!this .rs.next());
158: } finally {
159: if (conn1 != null) {
160: conn1.close();
161: }
162:
163: if (conn2 != null) {
164: conn2.close();
165: }
166:
167: if (xaConn1 != null) {
168: xaConn1.close();
169: }
170:
171: if (xaConn2 != null) {
172: xaConn2.close();
173: }
174: }
175: }
176:
177: protected XAConnection getXAConnection() throws Exception {
178: return this .xaDs.getXAConnection();
179: }
180:
181: /**
182: * Tests that XA RECOVER works as expected.
183: *
184: * @throws Exception
185: * if test fails
186: */
187: public void testRecover() throws Exception {
188: if (!versionMeetsMinimum(5, 0)) {
189: return;
190: }
191:
192: XAConnection xaConn = null, recoverConn = null;
193:
194: try {
195: xaConn = getXAConnection();
196:
197: Connection c = xaConn.getConnection();
198: Xid xid = createXid();
199:
200: XAResource xaRes = xaConn.getXAResource();
201: xaRes.start(xid, XAResource.TMNOFLAGS);
202: c.createStatement().executeQuery("SELECT 1");
203: xaRes.end(xid, XAResource.TMSUCCESS);
204: xaRes.prepare(xid);
205:
206: // Now try and recover
207: recoverConn = getXAConnection();
208:
209: XAResource recoverRes = recoverConn.getXAResource();
210:
211: Xid[] recoveredXids = recoverRes
212: .recover(XAResource.TMSTARTRSCAN
213: | XAResource.TMENDRSCAN);
214:
215: assertTrue(recoveredXids != null);
216: assertTrue(recoveredXids.length > 0);
217:
218: boolean xidFound = false;
219:
220: for (int i = 0; i < recoveredXids.length; i++) {
221: if (recoveredXids[i] != null
222: && recoveredXids[i].equals(xid)) {
223: xidFound = true;
224:
225: break;
226: }
227: }
228:
229: assertTrue(xidFound);
230:
231: recoverRes = recoverConn.getXAResource();
232:
233: recoveredXids = recoverRes.recover(XAResource.TMSTARTRSCAN);
234:
235: assertTrue(recoveredXids != null);
236: assertTrue(recoveredXids.length > 0);
237:
238: xidFound = false;
239:
240: for (int i = 0; i < recoveredXids.length; i++) {
241: if (recoveredXids[i] != null
242: && recoveredXids[i].equals(xid)) {
243: xidFound = true;
244:
245: break;
246: }
247: }
248:
249: assertTrue(xidFound);
250:
251: // Test flags
252: recoverRes.recover(XAResource.TMSTARTRSCAN);
253: recoverRes.recover(XAResource.TMENDRSCAN);
254: recoverRes.recover(XAResource.TMSTARTRSCAN
255: | XAResource.TMENDRSCAN);
256:
257: // This should fail
258: try {
259: recoverRes.recover(XAResource.TMSUCCESS);
260: fail("XAException should have been thrown");
261: } catch (XAException xaEx) {
262: assertEquals(XAException.XAER_INVAL, xaEx.errorCode);
263: }
264: } finally {
265: if (xaConn != null) {
266: xaConn.close();
267: }
268:
269: if (recoverConn != null) {
270: recoverConn.close();
271: }
272: }
273: }
274:
275: /**
276: * Tests operation of local transactions on XAConnections when global
277: * transactions are in or not in progress (follows from BUG#17401).
278: *
279: * @throws Exception
280: * if the testcase fails
281: */
282: public void testLocalTransaction() throws Exception {
283:
284: if (!versionMeetsMinimum(5, 0) || isRunningOnJdk131()) {
285: return;
286: }
287:
288: createTable("testLocalTransaction",
289: "(field1 int) ENGINE=InnoDB");
290:
291: Connection conn1 = null;
292:
293: XAConnection xaConn1 = null;
294:
295: try {
296: xaConn1 = getXAConnection();
297: XAResource xaRes1 = xaConn1.getXAResource();
298: conn1 = xaConn1.getConnection();
299: assertEquals(false, conn1.getAutoCommit());
300: conn1.setAutoCommit(true);
301: conn1.createStatement().executeUpdate(
302: "INSERT INTO testLocalTransaction VALUES (1)");
303: assertEquals("1", getSingleIndexedValueWithQuery(conn1, 1,
304: "SELECT field1 FROM testLocalTransaction")
305: .toString());
306:
307: conn1.createStatement().executeUpdate(
308: "TRUNCATE TABLE testLocalTransaction");
309: conn1.setAutoCommit(false);
310: conn1.createStatement().executeUpdate(
311: "INSERT INTO testLocalTransaction VALUES (2)");
312: assertEquals("2", getSingleIndexedValueWithQuery(conn1, 1,
313: "SELECT field1 FROM testLocalTransaction")
314: .toString());
315: conn1.rollback();
316: assertEquals(0, getRowCount("testLocalTransaction"));
317:
318: conn1.createStatement().executeUpdate(
319: "INSERT INTO testLocalTransaction VALUES (3)");
320: assertEquals("3", getSingleIndexedValueWithQuery(conn1, 1,
321: "SELECT field1 FROM testLocalTransaction")
322: .toString());
323: conn1.commit();
324: assertEquals("3", getSingleIndexedValueWithQuery(conn1, 1,
325: "SELECT field1 FROM testLocalTransaction")
326: .toString());
327: conn1.commit();
328:
329: Savepoint sp = conn1.setSavepoint();
330: conn1.rollback(sp);
331: sp = conn1.setSavepoint("abcd");
332: conn1.rollback(sp);
333: Savepoint spSaved = sp;
334:
335: Xid xid = createXid();
336: xaRes1.start(xid, XAResource.TMNOFLAGS);
337:
338: try {
339: try {
340: conn1.setAutoCommit(true);
341: } catch (SQLException sqlEx) {
342: // we expect an exception here
343: assertEquals("2D000", sqlEx.getSQLState());
344: }
345:
346: try {
347: conn1.commit();
348: } catch (SQLException sqlEx) {
349: // we expect an exception here
350: assertEquals("2D000", sqlEx.getSQLState());
351: }
352:
353: try {
354: conn1.rollback();
355: } catch (SQLException sqlEx) {
356: // we expect an exception here
357: assertEquals("2D000", sqlEx.getSQLState());
358: }
359:
360: try {
361: sp = conn1.setSavepoint();
362: } catch (SQLException sqlEx) {
363: // we expect an exception here
364: assertEquals("2D000", sqlEx.getSQLState());
365: }
366:
367: try {
368: conn1.rollback(spSaved);
369: } catch (SQLException sqlEx) {
370: // we expect an exception here
371: assertEquals("2D000", sqlEx.getSQLState());
372: }
373:
374: try {
375: sp = conn1.setSavepoint("abcd");
376: } catch (SQLException sqlEx) {
377: // we expect an exception here
378: assertEquals("2D000", sqlEx.getSQLState());
379: }
380:
381: try {
382: conn1.rollback(spSaved);
383: } catch (SQLException sqlEx) {
384: // we expect an exception here
385: assertEquals("2D000", sqlEx.getSQLState());
386: }
387: } finally {
388: xaRes1.forget(xid);
389: }
390: } finally {
391: if (xaConn1 != null) {
392: xaConn1.close();
393: }
394: }
395: }
396:
397: public void testSuspendableTx() throws Exception {
398: if (!versionMeetsMinimum(5, 0) || isRunningOnJdk131()) {
399: return;
400: }
401:
402: Connection conn1 = null;
403:
404: MysqlXADataSource suspXaDs = new MysqlXADataSource();
405: suspXaDs.setUrl(BaseTestCase.dbUrl);
406: suspXaDs.setPinGlobalTxToPhysicalConnection(true);
407: suspXaDs.setRollbackOnPooledClose(true);
408:
409: XAConnection xaConn1 = null;
410:
411: Xid xid = createXid();
412:
413: try {
414: /*
415: -- works using RESUME
416: xa start 0x123,0x456;
417: select * from foo;
418: xa end 0x123,0x456;
419: xa start 0x123,0x456 resume;
420: select * from foo;
421: xa end 0x123,0x456;
422: xa commit 0x123,0x456 one phase;
423: */
424:
425: xaConn1 = suspXaDs.getXAConnection();
426: XAResource xaRes1 = xaConn1.getXAResource();
427: conn1 = xaConn1.getConnection();
428: xaRes1.start(xid, XAResource.TMNOFLAGS);
429: conn1.createStatement().executeQuery("SELECT 1");
430: xaRes1.end(xid, XAResource.TMSUCCESS);
431: xaRes1.start(xid, XAResource.TMRESUME);
432: conn1.createStatement().executeQuery("SELECT 1");
433: xaRes1.end(xid, XAResource.TMSUCCESS);
434: xaRes1.commit(xid, true);
435:
436: xaConn1.close();
437:
438: /*
439:
440: -- fails using JOIN
441: xa start 0x123,0x456;
442: select * from foo;
443: xa end 0x123,0x456;
444: xa start 0x123,0x456 join;
445: select * from foo;
446: xa end 0x123,0x456;
447: xa commit 0x123,0x456 one phase;
448: */
449:
450: xaConn1 = suspXaDs.getXAConnection();
451: xaRes1 = xaConn1.getXAResource();
452: conn1 = xaConn1.getConnection();
453: xaRes1.start(xid, XAResource.TMNOFLAGS);
454: conn1.createStatement().executeQuery("SELECT 1");
455: xaRes1.end(xid, XAResource.TMSUCCESS);
456: xaRes1.start(xid, XAResource.TMJOIN);
457: conn1.createStatement().executeQuery("SELECT 1");
458: xaRes1.end(xid, XAResource.TMSUCCESS);
459: xaRes1.commit(xid, true);
460: } finally {
461: if (xaConn1 != null) {
462: xaConn1.close();
463: }
464: }
465: }
466:
467: private Xid createXid() throws IOException {
468: ByteArrayOutputStream gtridOut = new ByteArrayOutputStream();
469: DataOutputStream dataOut = new DataOutputStream(gtridOut);
470: new UID().write(dataOut);
471:
472: final byte[] gtrid = gtridOut.toByteArray();
473:
474: ByteArrayOutputStream bqualOut = new ByteArrayOutputStream();
475: dataOut = new DataOutputStream(bqualOut);
476:
477: new UID().write(dataOut);
478:
479: final byte[] bqual = bqualOut.toByteArray();
480:
481: Xid xid = new MysqlXid(gtrid, bqual, 3306);
482: return xid;
483: }
484:
485: private Xid createXid(Xid xidToBranch) throws IOException {
486: ByteArrayOutputStream bqualOut = new ByteArrayOutputStream();
487: DataOutputStream dataOut = new DataOutputStream(bqualOut);
488:
489: new UID().write(dataOut);
490:
491: final byte[] bqual = bqualOut.toByteArray();
492:
493: Xid xid = new MysqlXid(xidToBranch.getGlobalTransactionId(),
494: bqual, 3306);
495:
496: return xid;
497: }
498: }
|