diff --git a/src/main/java/org/mariadb/jdbc/Configuration.java b/src/main/java/org/mariadb/jdbc/Configuration.java index ff125a4ce..dfaac09fd 100644 --- a/src/main/java/org/mariadb/jdbc/Configuration.java +++ b/src/main/java/org/mariadb/jdbc/Configuration.java @@ -67,7 +67,6 @@ public class Configuration { private TransactionIsolation transactionIsolation = TransactionIsolation.REPEATABLE_READ; private int defaultFetchSize = 0; private int maxQuerySizeToLog = 1024; - private boolean pinGlobalTxToPhysicalConnection = false; private String geometryDefaultType = null; private String restrictedAuth = null; @@ -157,7 +156,6 @@ private Configuration( TransactionIsolation transactionIsolation, int defaultFetchSize, int maxQuerySizeToLog, - boolean pinGlobalTxToPhysicalConnection, String geometryDefaultType, String restrictedAuth, String socketFactory, @@ -222,7 +220,6 @@ private Configuration( this.transactionIsolation = transactionIsolation; this.defaultFetchSize = defaultFetchSize; this.maxQuerySizeToLog = maxQuerySizeToLog; - this.pinGlobalTxToPhysicalConnection = pinGlobalTxToPhysicalConnection; this.geometryDefaultType = geometryDefaultType; this.restrictedAuth = restrictedAuth; this.socketFactory = socketFactory; @@ -286,7 +283,6 @@ private Configuration( String user, String password, String enabledSslProtocolSuites, - Boolean pinGlobalTxToPhysicalConnection, String socketFactory, Integer connectTimeout, String pipe, @@ -354,8 +350,6 @@ private Configuration( this.user = user; this.password = password; this.enabledSslProtocolSuites = enabledSslProtocolSuites; - if (pinGlobalTxToPhysicalConnection != null) - this.pinGlobalTxToPhysicalConnection = pinGlobalTxToPhysicalConnection; this.socketFactory = socketFactory; if (connectTimeout != null) this.connectTimeout = connectTimeout; this.pipe = pipe; @@ -650,8 +644,8 @@ private static HaMode parseHaMode(String url, int separator) { public Configuration clone(String username, String password) { return new Configuration( - username, - password, + username != null && username.isEmpty() ? null : username, + password != null && password.isEmpty() ? null : password, this.database, this.addresses, this.haMode, @@ -661,7 +655,6 @@ public Configuration clone(String username, String password) { this.transactionIsolation, this.defaultFetchSize, this.maxQuerySizeToLog, - this.pinGlobalTxToPhysicalConnection, this.geometryDefaultType, this.restrictedAuth, this.socketFactory, @@ -765,10 +758,6 @@ public String enabledSslProtocolSuites() { return enabledSslProtocolSuites; } - public boolean pinGlobalTxToPhysicalConnection() { - return pinGlobalTxToPhysicalConnection; - } - public String socketFactory() { return socketFactory; } @@ -1155,7 +1144,6 @@ public static final class Builder implements Cloneable { private Boolean autocommit; private Integer defaultFetchSize; private Integer maxQuerySizeToLog; - private Boolean pinGlobalTxToPhysicalConnection; private String geometryDefaultType; private String restrictedAuth; private String transactionIsolation; @@ -1278,11 +1266,6 @@ public Builder enabledSslProtocolSuites(String enabledSslProtocolSuites) { return this; } - public Builder pinGlobalTxToPhysicalConnection(Boolean pinGlobalTxToPhysicalConnection) { - this.pinGlobalTxToPhysicalConnection = pinGlobalTxToPhysicalConnection; - return this; - } - public Builder database(String database) { this.database = database; return this; @@ -1695,7 +1678,6 @@ public Configuration build() throws SQLException { this.user, this.password, this.enabledSslProtocolSuites, - this.pinGlobalTxToPhysicalConnection, this.socketFactory, this.connectTimeout, this.pipe, diff --git a/src/main/java/org/mariadb/jdbc/Connection.java b/src/main/java/org/mariadb/jdbc/Connection.java index 3623601e3..72f6881a6 100644 --- a/src/main/java/org/mariadb/jdbc/Connection.java +++ b/src/main/java/org/mariadb/jdbc/Connection.java @@ -111,16 +111,20 @@ public PreparedStatement prepareInternal( throws SQLException { checkNotClosed(); if (useBinary) { - return new ServerPreparedStatement( - NativeSql.parse(sql, client.getContext()), - this, - lock, - canUseServerTimeout, - canUseServerMaxRows, - autoGeneratedKeys, - resultSetType, - resultSetConcurrency, - defaultFetchSize); + try { + return new ServerPreparedStatement( + NativeSql.parse(sql, client.getContext()), + this, + lock, + canUseServerTimeout, + canUseServerMaxRows, + autoGeneratedKeys, + resultSetType, + resultSetConcurrency, + defaultFetchSize); + } catch (SQLException e) { + // failover to client + } } return new ClientPreparedStatement( NativeSql.parse(sql, client.getContext()), diff --git a/src/main/java/org/mariadb/jdbc/MariaDbDataSource.java b/src/main/java/org/mariadb/jdbc/MariaDbDataSource.java index c9a3f879e..a526d8bdd 100644 --- a/src/main/java/org/mariadb/jdbc/MariaDbDataSource.java +++ b/src/main/java/org/mariadb/jdbc/MariaDbDataSource.java @@ -8,11 +8,9 @@ import java.sql.*; import java.sql.Connection; import java.util.logging.Logger; -import javax.sql.ConnectionPoolDataSource; -import javax.sql.DataSource; -import javax.sql.PooledConnection; +import javax.sql.*; -public class MariaDbDataSource implements DataSource, ConnectionPoolDataSource { +public class MariaDbDataSource implements DataSource, ConnectionPoolDataSource, XADataSource { private final Configuration conf; @@ -161,15 +159,24 @@ public Logger getParentLogger() { @Override public PooledConnection getPooledConnection() throws SQLException { - org.mariadb.jdbc.Connection connection = Driver.connect(conf); - return new MariaDbPoolConnection(connection); + return new MariaDbPoolConnection(Driver.connect(conf)); } @Override public PooledConnection getPooledConnection(String username, String password) throws SQLException { Configuration conf = this.conf.clone(username, password); - org.mariadb.jdbc.Connection connection = Driver.connect(conf); - return new MariaDbPoolConnection(connection); + return new MariaDbPoolConnection(Driver.connect(conf)); + } + + @Override + public XAConnection getXAConnection() throws SQLException { + return new MariaDbPoolConnection(Driver.connect(conf)); + } + + @Override + public XAConnection getXAConnection(String username, String password) throws SQLException { + Configuration conf = this.conf.clone(username, password); + return new MariaDbPoolConnection(Driver.connect(conf)); } } diff --git a/src/main/java/org/mariadb/jdbc/MariaDbPoolConnection.java b/src/main/java/org/mariadb/jdbc/MariaDbPoolConnection.java index c10f8e5c5..1b9f208d3 100644 --- a/src/main/java/org/mariadb/jdbc/MariaDbPoolConnection.java +++ b/src/main/java/org/mariadb/jdbc/MariaDbPoolConnection.java @@ -5,12 +5,18 @@ package org.mariadb.jdbc; import java.sql.PreparedStatement; +import java.sql.ResultSet; import java.sql.SQLException; +import java.util.ArrayList; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import javax.sql.*; +import javax.transaction.xa.XAException; +import javax.transaction.xa.XAResource; +import javax.transaction.xa.Xid; +import org.mariadb.jdbc.util.StringUtils; -public class MariaDbPoolConnection implements PooledConnection { +public class MariaDbPoolConnection implements PooledConnection, XAConnection { private final Connection connection; private final List connectionEventListeners; @@ -86,4 +92,176 @@ public void close() throws SQLException { connection.setPoolConnection(null); connection.close(); } + + public static String xidToString(Xid xid) { + return "0x" + + StringUtils.byteArrayToHexString(xid.getGlobalTransactionId()) + + ",0x" + + StringUtils.byteArrayToHexString(xid.getBranchQualifier()) + + ",0x" + + Integer.toHexString(xid.getFormatId()); + } + + @Override + public XAResource getXAResource() throws SQLException { + return new MariaDbXAResource(); + } + + private class MariaDbXAResource implements XAResource { + + private String flagsToString(int flags) { + switch (flags) { + case TMJOIN: + return "JOIN"; + case TMONEPHASE: + return "ONE PHASE"; + case TMRESUME: + return "RESUME"; + case TMSUSPEND: + return "SUSPEND"; + default: + return ""; + } + } + + private XAException mapXaException(SQLException sqle) { + int xaErrorCode; + + switch (sqle.getErrorCode()) { + case 1397: + xaErrorCode = XAException.XAER_NOTA; + break; + case 1398: + xaErrorCode = XAException.XAER_INVAL; + break; + case 1399: + xaErrorCode = XAException.XAER_RMFAIL; + break; + case 1400: + xaErrorCode = XAException.XAER_OUTSIDE; + break; + case 1401: + xaErrorCode = XAException.XAER_RMERR; + break; + case 1402: + xaErrorCode = XAException.XA_RBROLLBACK; + break; + default: + xaErrorCode = 0; + break; + } + XAException xaException; + if (xaErrorCode != 0) { + xaException = new XAException(xaErrorCode); + } else { + xaException = new XAException(sqle.getMessage()); + } + xaException.initCause(sqle); + return xaException; + } + + private void execute(String command) throws XAException { + try { + connection.createStatement().execute(command); + } catch (SQLException sqle) { + throw mapXaException(sqle); + } + } + + @Override + public void commit(Xid xid, boolean onePhase) throws XAException { + execute("XA COMMIT " + xidToString(xid) + ((onePhase) ? " ONE PHASE" : "")); + } + + @Override + public void end(Xid xid, int flags) throws XAException { + if (flags != TMSUCCESS && flags != TMSUSPEND && flags != TMFAIL) { + throw new XAException(XAException.XAER_INVAL); + } + + execute("XA END " + xidToString(xid) + " " + flagsToString(flags)); + } + + @Override + public void forget(Xid xid) throws XAException { + // Not implemented by the server + } + + @Override + public int getTransactionTimeout() throws XAException { + // not implemented + return 0; + } + + public Configuration getConf() { + return connection.getContext().getConf(); + } + + @Override + public boolean isSameRM(XAResource xaResource) throws XAException { + if (xaResource instanceof MariaDbXAResource) { + MariaDbXAResource other = (MariaDbXAResource) xaResource; + return other.getConf().equals(this.getConf()); + } + return false; + } + + @Override + public int prepare(Xid xid) throws XAException { + execute("XA PREPARE " + xidToString(xid)); + return XA_OK; + } + + @Override + public Xid[] recover(int flags) throws XAException { + if (((flags & TMSTARTRSCAN) == 0) && ((flags & TMENDRSCAN) == 0) && (flags != TMNOFLAGS)) { + throw new XAException(XAException.XAER_INVAL); + } + + if ((flags & TMSTARTRSCAN) == 0) { + return new MariaDbXid[0]; + } + + try { + ResultSet rs = connection.createStatement().executeQuery("XA RECOVER"); + ArrayList xidList = new ArrayList<>(); + + while (rs.next()) { + int formatId = rs.getInt(1); + int len1 = rs.getInt(2); + int len2 = rs.getInt(3); + byte[] arr = rs.getBytes(4); + + byte[] globalTransactionId = new byte[len1]; + byte[] branchQualifier = new byte[len2]; + System.arraycopy(arr, 0, globalTransactionId, 0, len1); + System.arraycopy(arr, len1, branchQualifier, 0, len2); + xidList.add(new MariaDbXid(formatId, globalTransactionId, branchQualifier)); + } + Xid[] xids = new Xid[xidList.size()]; + xidList.toArray(xids); + return xids; + } catch (SQLException sqle) { + throw mapXaException(sqle); + } + } + + @Override + public void rollback(Xid xid) throws XAException { + execute("XA ROLLBACK " + xidToString(xid)); + } + + @Override + public boolean setTransactionTimeout(int i) throws XAException { + return false; + } + + @Override + public void start(Xid xid, int flags) throws XAException { + if (flags != TMJOIN && flags != TMRESUME && flags != TMNOFLAGS) { + throw new XAException(XAException.XAER_INVAL); + } + execute("XA START " + xidToString(xid) + " " + flagsToString(flags)); + } + } } diff --git a/src/main/java/org/mariadb/jdbc/MariaDbPoolDataSource.java b/src/main/java/org/mariadb/jdbc/MariaDbPoolDataSource.java index 81d4e8af8..44d6e9fec 100644 --- a/src/main/java/org/mariadb/jdbc/MariaDbPoolDataSource.java +++ b/src/main/java/org/mariadb/jdbc/MariaDbPoolDataSource.java @@ -13,12 +13,14 @@ import java.util.logging.Logger; import javax.sql.ConnectionPoolDataSource; import javax.sql.DataSource; +import javax.sql.XAConnection; +import javax.sql.XADataSource; import org.mariadb.jdbc.pool.InternalPoolConnection; import org.mariadb.jdbc.pool.Pool; import org.mariadb.jdbc.pool.Pools; public class MariaDbPoolDataSource - implements DataSource, ConnectionPoolDataSource, Closeable, AutoCloseable { + implements DataSource, ConnectionPoolDataSource, XADataSource, Closeable, AutoCloseable { private final Pool pool; @@ -176,6 +178,16 @@ public InternalPoolConnection getPooledConnection(String username, String passwo return pool.getPoolConnection(username, password); } + @Override + public XAConnection getXAConnection() throws SQLException { + return pool.getPoolConnection(); + } + + @Override + public XAConnection getXAConnection(String username, String password) throws SQLException { + return pool.getPoolConnection(username, password); + } + /** Close datasource. */ public void close() { try { diff --git a/src/main/java/org/mariadb/jdbc/MariaDbXid.java b/src/main/java/org/mariadb/jdbc/MariaDbXid.java new file mode 100644 index 000000000..84bb686b7 --- /dev/null +++ b/src/main/java/org/mariadb/jdbc/MariaDbXid.java @@ -0,0 +1,52 @@ +package org.mariadb.jdbc; + +import java.util.Arrays; +import javax.transaction.xa.Xid; + +public class MariaDbXid implements Xid { + + private final int formatId; + private final byte[] globalTransactionId; + private final byte[] branchQualifier; + + /** + * Global transaction identifier. + * + * @param formatId the format identifier part of the XID. + * @param globalTransactionId the global transaction identifier part of XID as an array of bytes. + * @param branchQualifier the transaction branch identifier part of XID as an array of bytes. + */ + public MariaDbXid(int formatId, byte[] globalTransactionId, byte[] branchQualifier) { + this.formatId = formatId; + this.globalTransactionId = globalTransactionId; + this.branchQualifier = branchQualifier; + } + + /** + * Equal implementation. + * + * @param obj object to compare + * @return true if object is MariaDbXi and as same parameters + */ + public boolean equals(Object obj) { + if (obj instanceof Xid) { + Xid other = (Xid) obj; + return formatId == other.getFormatId() + && Arrays.equals(globalTransactionId, other.getGlobalTransactionId()) + && Arrays.equals(branchQualifier, other.getBranchQualifier()); + } + return false; + } + + public int getFormatId() { + return formatId; + } + + public byte[] getGlobalTransactionId() { + return globalTransactionId; + } + + public byte[] getBranchQualifier() { + return branchQualifier; + } +} diff --git a/src/main/java/org/mariadb/jdbc/ServerPreparedStatement.java b/src/main/java/org/mariadb/jdbc/ServerPreparedStatement.java index b98e946dc..fa03c7815 100644 --- a/src/main/java/org/mariadb/jdbc/ServerPreparedStatement.java +++ b/src/main/java/org/mariadb/jdbc/ServerPreparedStatement.java @@ -7,6 +7,7 @@ import java.sql.*; import java.util.*; import java.util.concurrent.locks.ReentrantLock; +import java.util.regex.Pattern; import org.mariadb.jdbc.client.result.CompleteResult; import org.mariadb.jdbc.client.result.Result; import org.mariadb.jdbc.message.client.BulkExecutePacket; @@ -21,6 +22,10 @@ import org.mariadb.jdbc.util.constants.Capabilities; public class ServerPreparedStatement extends BasePreparedStatement { + private static final Pattern PREPARABLE_STATEMENT_PATTERN = + Pattern.compile( + "^(\\s*\\/\\*([^\\*]|\\*[^\\/])*\\*\\/)*\\s*(SELECT|UPDATE|INSERT|DELETE|REPLACE|DO|CALL)", + Pattern.CASE_INSENSITIVE); public ServerPreparedStatement( String sql, @@ -31,7 +36,8 @@ public ServerPreparedStatement( int autoGeneratedKeys, int resultSetType, int resultSetConcurrency, - int defaultFetchSize) { + int defaultFetchSize) + throws SQLException { super( sql, con, @@ -42,7 +48,12 @@ public ServerPreparedStatement( resultSetType, resultSetConcurrency, defaultFetchSize); - + if (!PREPARABLE_STATEMENT_PATTERN.matcher(sql).find()) { + prepareResult = con.getContext().getPrepareCache().get(sql, this); + if (prepareResult == null) { + con.getClient().execute(new PreparePacket(sql), this); + } + } parameters = new ParameterList(); } diff --git a/src/main/java/org/mariadb/jdbc/codec/Parameter.java b/src/main/java/org/mariadb/jdbc/codec/Parameter.java index 9352f30a5..75536265d 100644 --- a/src/main/java/org/mariadb/jdbc/codec/Parameter.java +++ b/src/main/java/org/mariadb/jdbc/codec/Parameter.java @@ -6,7 +6,6 @@ import java.io.IOException; import java.sql.SQLException; -import java.util.Calendar; import org.mariadb.jdbc.client.context.Context; import org.mariadb.jdbc.client.socket.PacketWriter; @@ -49,8 +48,7 @@ public void encodeBinary(PacketWriter encoder) throws IOException, SQLException codec.encodeBinary(encoder, this.value, null, length); } - public void encodeLongData(PacketWriter encoder) - throws IOException, SQLException { + public void encodeLongData(PacketWriter encoder) throws IOException, SQLException { codec.encodeLongData(encoder, this.value, length); } diff --git a/src/main/java/org/mariadb/jdbc/codec/ParameterWithCal.java b/src/main/java/org/mariadb/jdbc/codec/ParameterWithCal.java index ae1bc336b..ed6776053 100644 --- a/src/main/java/org/mariadb/jdbc/codec/ParameterWithCal.java +++ b/src/main/java/org/mariadb/jdbc/codec/ParameterWithCal.java @@ -4,14 +4,13 @@ package org.mariadb.jdbc.codec; -import org.mariadb.jdbc.client.context.Context; -import org.mariadb.jdbc.client.socket.PacketWriter; - import java.io.IOException; import java.sql.SQLException; import java.util.Calendar; +import org.mariadb.jdbc.client.context.Context; +import org.mariadb.jdbc.client.socket.PacketWriter; -public class ParameterWithCal extends Parameter { +public class ParameterWithCal extends Parameter { private final Calendar cal; diff --git a/src/main/java/org/mariadb/jdbc/util/StringUtils.java b/src/main/java/org/mariadb/jdbc/util/StringUtils.java new file mode 100644 index 000000000..d4205684b --- /dev/null +++ b/src/main/java/org/mariadb/jdbc/util/StringUtils.java @@ -0,0 +1,17 @@ +package org.mariadb.jdbc.util; + +public final class StringUtils { + private static final char[] hexArray = "0123456789ABCDEF".toCharArray(); + + public static String byteArrayToHexString(final byte[] bytes) { + return (bytes != null) ? getHex(bytes) : ""; + } + + private static String getHex(final byte[] raw) { + final StringBuilder hex = new StringBuilder(2 * raw.length); + for (final byte b : raw) { + hex.append(hexArray[(b & 0xF0) >> 4]).append(hexArray[(b & 0x0F)]); + } + return hex.toString(); + } +} diff --git a/src/main/resources/driver.properties b/src/main/resources/driver.properties index c554a47d9..cfe8203c9 100644 --- a/src/main/resources/driver.properties +++ b/src/main/resources/driver.properties @@ -33,8 +33,7 @@ tlsSocketType=Indicate the TLS org.mariadb.jdbc.tls.TlsSocketPlugin plugin type maxQuerySizeToLog=Only the first characters corresponding to this options size will be displayed in logs. Default: 1024 retriesAllDown=When the connector is performing a failover and all hosts are down, this parameter defines the maximum number of connection attempts the connector will make before throwing an exception. Default: 120 seconds. galeraAllowedState=Usually, Connection.isValid just send an empty packet to server, and server send a small response to ensure connectivity. When this option is set, connector will ensure Galera server state "wsrep_local_state" correspond to allowed values (separated by comma). example "4,5", recommended is "4". see galera state to know more. -enabledSslProtocolSuites=Force TLS/SSL protocol to a specific set of TLS versions (comma separated list). Example : "TLSv1, TLSv1.1, TLSv1.2" -pinGlobalTxToPhysicalConnection= +enabledSslProtocolSuites=Force TLS/SSL protocol to a specific set of TLS versions (comma separated list). Example : "TLSv1, TLSv1.1, TLSv1.2" pool=Use pool. This option is useful only if not using a DataSource object, but only a connection object. Default: false. poolName=Pool name that permits identifying threads. default: auto-generated as MariaDb-pool- maxPoolSize=The maximum number of physical connections that the pool should contain. Default: 8. diff --git a/src/test/java/org/mariadb/jdbc/integration/XaTest.java b/src/test/java/org/mariadb/jdbc/integration/XaTest.java new file mode 100644 index 000000000..fec6b6c75 --- /dev/null +++ b/src/test/java/org/mariadb/jdbc/integration/XaTest.java @@ -0,0 +1,276 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +// Copyright (c) 2012-2014 Monty Program Ab +// Copyright (c) 2015-2021 MariaDB Corporation Ab + +package org.mariadb.jdbc.integration; + +import static org.junit.jupiter.api.Assertions.*; + +import java.sql.*; +import java.sql.Connection; +import java.util.UUID; +import javax.sql.XAConnection; +import javax.sql.XADataSource; +import javax.transaction.xa.XAException; +import javax.transaction.xa.XAResource; +import javax.transaction.xa.Xid; +import org.junit.jupiter.api.*; +import org.mariadb.jdbc.*; +import org.mariadb.jdbc.Statement; + +public class XaTest extends Common { + + private static MariaDbDataSource dataSource; + private static MariaDbPoolDataSource poolDataSource; + + @AfterAll + public static void drop() throws SQLException { + Statement stmt = sharedConn.createStatement(); + stmt.execute("DROP TABLE IF EXISTS xatable"); + if (poolDataSource != null) poolDataSource.close(); + } + + @BeforeAll + public static void beforeAll2() throws SQLException { + Assumptions.assumeTrue( + !"skysql".equals(System.getenv("srv")) && !"skysql-ha".equals(System.getenv("srv"))); + + drop(); + Statement stmt = sharedConn.createStatement(); + stmt.execute("CREATE TABLE xatable(i int)"); + stmt.execute("FLUSH TABLES"); + dataSource = new MariaDbDataSource(mDefUrl); + poolDataSource = new MariaDbPoolDataSource(mDefUrl); + } + + @Test + public void xidToString() { + MariaDbXid xid = new MariaDbXid(1575, new byte[] {0x00}, new byte[] {0x01}); + assertEquals("0x00,0x01,0x627", MariaDbPoolConnection.xidToString(xid)); + assertEquals( + "0x00,0x,0x627", + MariaDbPoolConnection.xidToString(new MariaDbXid(1575, new byte[] {0x00}, null))); + assertEquals( + "0x,0x000100,0x400", + MariaDbPoolConnection.xidToString( + new MariaDbXid(1024, new byte[] {}, new byte[] {0x00, 0x01, 0x00}))); + assertEquals( + "0x00,0x000100,0xc3c20186", + MariaDbPoolConnection.xidToString( + new MariaDbXid(-1010695802, new byte[] {0x00}, new byte[] {0x00, 0x01, 0x00}))); + assertEquals(xid, xid); + assertEquals(xid, new MariaDbXid(1575, new byte[] {0x00}, new byte[] {0x01})); + assertFalse(xid.equals("dd")); + assertFalse(xid.equals(null)); + } + + @Test + public void xaRmTest() throws Exception { + MariaDbDataSource dataSource1 = new MariaDbDataSource(mDefUrl); + MariaDbDataSource dataSource2 = new MariaDbDataSource(mDefUrl + "&test=t"); + XAConnection con1 = dataSource1.getXAConnection(); + XAConnection con2 = dataSource1.getXAConnection(); + XAConnection con3 = dataSource2.getXAConnection(); + assertTrue(con1.getXAResource().isSameRM(con1.getXAResource())); + assertTrue(con1.getXAResource().isSameRM(con2.getXAResource())); + assertFalse(con1.getXAResource().isSameRM(con3.getXAResource())); + con1.close(); + con2.close(); + con3.close(); + } + + private Xid newXid() { + return new MariaDbXid( + 1, UUID.randomUUID().toString().getBytes(), UUID.randomUUID().toString().getBytes()); + } + + private Xid newXid(Xid branchFrom) { + return new MariaDbXid( + 1, branchFrom.getGlobalTransactionId(), UUID.randomUUID().toString().getBytes()); + } + + /** + * 2 phase commit , with either commit or rollback at the end. + * + * @param doCommit must commit + * @throws Exception exception + */ + private int test2PhaseCommit(boolean doCommit, XADataSource dataSource) throws Exception { + + int connectionNumber = 2; + + Xid parentXid = newXid(); + java.sql.Connection[] connections = new java.sql.Connection[connectionNumber]; + XAConnection[] xaConnections = new XAConnection[connectionNumber]; + XAResource[] xaResources = new XAResource[connectionNumber]; + Xid[] xids = new Xid[connectionNumber]; + + try { + + for (int i = 0; i < connectionNumber; i++) { + if (i == 0) { + xaConnections[i] = dataSource.getXAConnection(user, password); + } else xaConnections[i] = dataSource.getXAConnection(); + + connections[i] = xaConnections[i].getConnection(); + xaResources[i] = xaConnections[i].getXAResource(); + xids[i] = newXid(parentXid); + } + + startAllResources(connectionNumber, xaResources, xids); + insertDatas(connectionNumber, connections); + endAllResources(connectionNumber, xaResources, xids); + prepareAllResources(connectionNumber, xaResources, xids); + + for (int i = 0; i < connectionNumber; i++) { + if (doCommit) { + xaResources[i].commit(xids[i], false); + } else { + xaResources[i].rollback(xids[i]); + } + } + + } finally { + for (int i = 0; i < connectionNumber; i++) { + try { + if (xaConnections[i] != null) { + xaConnections[i].close(); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + } + return connectionNumber; + } + + private void startAllResources(int connectionNumber, XAResource[] xaResources, Xid[] xids) + throws XAException { + for (int i = 0; i < connectionNumber; i++) { + xaResources[i].start(xids[i], XAResource.TMNOFLAGS); + } + } + + private void endAllResources(int connectionNumber, XAResource[] xaResources, Xid[] xids) + throws XAException { + for (int i = 0; i < connectionNumber; i++) { + xaResources[i].end(xids[i], XAResource.TMSUCCESS); + } + } + + private void prepareAllResources(int connectionNumber, XAResource[] xaResources, Xid[] xids) + throws XAException { + for (int i = 0; i < connectionNumber; i++) { + xaResources[i].prepare(xids[i]); + } + } + + private void insertDatas(int connectionNumber, java.sql.Connection[] connections) + throws SQLException { + for (int i = 0; i < connectionNumber; i++) { + connections[i].createStatement().executeUpdate("INSERT INTO xatable VALUES (" + i + ")"); + } + } + + @Test + public void testCommit() throws Exception { + testCommit(dataSource); + testCommit(poolDataSource); + } + + public void testCommit(XADataSource dataSource) throws Exception { + java.sql.Statement stmt = sharedConn.createStatement(); + stmt.execute("TRUNCATE xatable"); + int connectionNumber = test2PhaseCommit(true, dataSource); + + // check the completion + ResultSet rs = stmt.executeQuery("SELECT * from xatable order by i"); + for (int i = 0; i < connectionNumber; i++) { + assertTrue(rs.next()); + assertEquals(rs.getInt(1), i); + } + } + + @Test + public void testRollback() throws Exception { + testRollback(dataSource); + testRollback(poolDataSource); + } + + public void testRollback(XADataSource dataSource) throws Exception { + java.sql.Statement stmt = sharedConn.createStatement(); + stmt.execute("TRUNCATE xatable"); + test2PhaseCommit(false, dataSource); + // check the completion + ResultSet rs = stmt.executeQuery("SELECT * from xatable order by i"); + assertFalse(rs.next()); + } + + @Test + public void testRecover() throws Exception { + XAConnection xaConnection = dataSource.getXAConnection(); + try { + java.sql.Connection connection = xaConnection.getConnection(); + Xid xid = newXid(); + XAResource xaResource = xaConnection.getXAResource(); + xaResource.start(xid, XAResource.TMNOFLAGS); + connection.createStatement().executeQuery("SELECT 1"); + xaResource.end(xid, XAResource.TMSUCCESS); + xaResource.prepare(xid); + Xid[] recoveredXids = xaResource.recover(XAResource.TMSTARTRSCAN | XAResource.TMENDRSCAN); + assertTrue(recoveredXids != null); + assertTrue(recoveredXids.length > 0); + boolean found = false; + + for (Xid x : recoveredXids) { + if (x != null && x.equals(xid)) { + found = true; + break; + } + } + assertTrue(found); + } finally { + xaConnection.close(); + } + } + + @Test + public void resumeAndJoinTest() throws Exception { + Connection conn1; + MariaDbDataSource ds = new MariaDbDataSource(mDefUrl); + + XAConnection xaConn1 = null; + Xid xid = newXid(); + try { + xaConn1 = ds.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(); + + xaConn1 = ds.getXAConnection(); + xaRes1 = xaConn1.getXAResource(); + conn1 = xaConn1.getConnection(); + xaRes1.start(xid, XAResource.TMNOFLAGS); + conn1.createStatement().executeQuery("SELECT 1"); + xaRes1.end(xid, XAResource.TMSUCCESS); + try { + xaRes1.start(xid, XAResource.TMJOIN); + fail(); // without pinGlobalTxToPhysicalConnection=true + } catch (XAException xaex) { + xaConn1.close(); + } + + } finally { + if (xaConn1 != null) { + xaConn1.close(); + } + } + } +} diff --git a/src/test/java/org/mariadb/jdbc/unit/util/ConfigurationTest.java b/src/test/java/org/mariadb/jdbc/unit/util/ConfigurationTest.java index 79172d6e5..75fc0df7d 100644 --- a/src/test/java/org/mariadb/jdbc/unit/util/ConfigurationTest.java +++ b/src/test/java/org/mariadb/jdbc/unit/util/ConfigurationTest.java @@ -542,11 +542,11 @@ public void testJdbcParserParameterErrorEqual() { SQLException.class, () -> Configuration.parse(wrongIntVal), "Optional parameter socketTimeout must be Integer, was 'blabla'"); - String wrongBoolVal = "jdbc:mariadb://localhost?pinGlobalTxToPhysicalConnection=blabla"; + String wrongBoolVal = "jdbc:mariadb://localhost?autocommit=blabla"; assertThrowsContains( SQLException.class, () -> Configuration.parse(wrongBoolVal), - "Optional parameter pinGlobalTxToPhysicalConnection must be boolean (true/false or 0/1)"); + "Optional parameter autocommit must be boolean (true/false or 0/1)"); String url = "jdbc:mariadb://address=(type=)(port=3306)(host=master1),address=(port=3307)(type=primary)" + "(host=master2),address=(type=replica)(host=slave1)(port=3308)/database?user=greg&password=pass"; @@ -589,7 +589,6 @@ public void testJdbcParserReplicationParameter() throws SQLException { assertEquals("greg", conf.user()); assertEquals("pass", conf.password()); assertEquals("BLA", conf.servicePrincipalName()); - assertTrue(conf.pinGlobalTxToPhysicalConnection()); assertTrue(conf.allowPublicKeyRetrieval()); assertEquals("/tmp/path", conf.serverRsaPublicKeyFile()); assertEquals(3, conf.addresses().size()); @@ -768,7 +767,6 @@ public void builder() throws SQLException { .retriesAllDown(10) .galeraAllowedState("A,B") .enabledSslProtocolSuites("TLSv1.2") - .pinGlobalTxToPhysicalConnection(true) .transactionReplay(true) .pool(true) .poolName("myPool") @@ -793,7 +791,7 @@ public void builder() throws SQLException { .allowPublicKeyRetrieval(true) .build(); assertEquals( - "jdbc:mariadb://address=(host=host1)(port=3305)(type=primary),address=(host=host2)(port=3307)(type=replica)/db?user=me&password=pwd&timezone=UTC&autocommit=false&defaultFetchSize=10&maxQuerySizeToLog=100&pinGlobalTxToPhysicalConnection=true&geometryDefaultType=default&restrictedAuth=mysql_native_password,client_ed25519&socketFactory=someSocketFactory&connectTimeout=22&pipe=pipeName&localSocket=localSocket&tcpKeepAlive=true&tcpKeepIdle=10&tcpKeepCount=50&tcpKeepInterval=50&tcpAbortiveClose=true&localSocketAddress=localSocketAddress&socketTimeout=1000&useReadAheadInput=false&tlsSocketType=TLStype&sslMode=TRUST&serverSslCert=mycertPath&keyStore=/tmp&keyStorePassword=MyPWD&keyStoreType=JKS&enabledSslCipherSuites=myCipher,cipher2&enabledSslProtocolSuites=TLSv1.2&allowMultiQueries=true&allowLocalInfile=true&useCompression=true&useAffectedRows=true&useBulkStmts=false&cachePrepStmts=false&prepStmtCacheSize=2&useServerPrepStmts=true&credentialType=ENV&sessionVariables=blabla&connectionAttributes=bla=bla&servicePrincipalName=SPN&blankTableNameMeta=true&tinyInt1isBit=false&yearIsDateType=false&dumpQueriesOnException=true&includeInnodbStatusInDeadlockExceptions=true&includeThreadDumpInDeadlockExceptions=true&retriesAllDown=10&galeraAllowedState=A,B&transactionReplay=true&pool=true&poolName=myPool&maxPoolSize=16&minPoolSize=12&maxIdleTime=25000®isterJmxPool=false&poolValidMinDelay=260&useResetConnection=true&serverRsaPublicKeyFile=RSAPath&allowPublicKeyRetrieval=true", + "jdbc:mariadb://address=(host=host1)(port=3305)(type=primary),address=(host=host2)(port=3307)(type=replica)/db?user=me&password=pwd&timezone=UTC&autocommit=false&defaultFetchSize=10&maxQuerySizeToLog=100&geometryDefaultType=default&restrictedAuth=mysql_native_password,client_ed25519&socketFactory=someSocketFactory&connectTimeout=22&pipe=pipeName&localSocket=localSocket&tcpKeepAlive=true&tcpKeepIdle=10&tcpKeepCount=50&tcpKeepInterval=50&tcpAbortiveClose=true&localSocketAddress=localSocketAddress&socketTimeout=1000&useReadAheadInput=false&tlsSocketType=TLStype&sslMode=TRUST&serverSslCert=mycertPath&keyStore=/tmp&keyStorePassword=MyPWD&keyStoreType=JKS&enabledSslCipherSuites=myCipher,cipher2&enabledSslProtocolSuites=TLSv1.2&allowMultiQueries=true&allowLocalInfile=true&useCompression=true&useAffectedRows=true&useBulkStmts=false&cachePrepStmts=false&prepStmtCacheSize=2&useServerPrepStmts=true&credentialType=ENV&sessionVariables=blabla&connectionAttributes=bla=bla&servicePrincipalName=SPN&blankTableNameMeta=true&tinyInt1isBit=false&yearIsDateType=false&dumpQueriesOnException=true&includeInnodbStatusInDeadlockExceptions=true&includeThreadDumpInDeadlockExceptions=true&retriesAllDown=10&galeraAllowedState=A,B&transactionReplay=true&pool=true&poolName=myPool&maxPoolSize=16&minPoolSize=12&maxIdleTime=25000®isterJmxPool=false&poolValidMinDelay=260&useResetConnection=true&serverRsaPublicKeyFile=RSAPath&allowPublicKeyRetrieval=true", conf.toString()); }