getHostAddresses() {
+ return this.addresses;
}
- public String getDatabase() {
- return database;
+ protected void setUsername(String username) {
+ options.user = username;
+ }
+
+ protected void setPassword(String password) {
+ options.password = password;
+ }
+
+ public Options getOptions() {
+ return options;
}
+ protected void setDatabase(String database) {
+ this.database = database;
+ }
- public HostAddress[] getHostAddresses() {
- return this.addresses;
+ protected void setProperties(String urlParameters) {
+ DefaultOptions.parse(this.haMode, urlParameters, this.options);
}
public String toString() {
String s = "jdbc:mysql://";
+ if (!haMode.equals(UrlHAMode.NONE)) s = "jdbc:mysql:"+haMode.toString().toLowerCase()+"://";
if (addresses != null)
s += HostAddress.toString(addresses);
if (database != null)
s += "/" + database;
- return s;
+ return s;
}
+
+ public UrlHAMode getHaMode() {
+ return haMode;
+ }
+
}
diff --git a/src/main/java/org/mariadb/jdbc/MySQLCallableStatement.java b/src/main/java/org/mariadb/jdbc/MySQLCallableStatement.java
index 9026bb496..dc26c82eb 100644
--- a/src/main/java/org/mariadb/jdbc/MySQLCallableStatement.java
+++ b/src/main/java/org/mariadb/jdbc/MySQLCallableStatement.java
@@ -1239,23 +1239,26 @@ public void setObject(int parameterIndex, Object x) throws SQLException {
}
public boolean execute() throws SQLException {
- synchronized (con.getProtocol()) {
- if (rsOutputParameters != null) {
- rsOutputParameters.close();
- rsOutputParameters = null;
- }
- if(parametersCount > 0) {
- preparedStatement.execute();
- }
- boolean ret = callStatement.execute(callQuery);
-
- // Read off output parameters, if there are any
- // (but not if query is streaming)
- if (hasOutputParameters() && callStatement.getFetchSize() != Integer.MIN_VALUE) {
- readOutputParameters();
- }
- return ret;
- }
+ con.lock.writeLock().lock();
+ try {
+ if (rsOutputParameters != null) {
+ rsOutputParameters.close();
+ rsOutputParameters = null;
+ }
+ if (parametersCount > 0) {
+ preparedStatement.execute();
+ }
+ boolean ret = callStatement.execute(callQuery);
+
+ // Read off output parameters, if there are any
+ // (but not if query is streaming)
+ if (hasOutputParameters() && callStatement.getFetchSize() != Integer.MIN_VALUE) {
+ readOutputParameters();
+ }
+ return ret;
+ } finally {
+ con.lock.writeLock().unlock();
+ }
}
public void addBatch() throws SQLException {
diff --git a/src/main/java/org/mariadb/jdbc/MySQLConnection.java b/src/main/java/org/mariadb/jdbc/MySQLConnection.java
index 1e2182141..d989ec664 100644
--- a/src/main/java/org/mariadb/jdbc/MySQLConnection.java
+++ b/src/main/java/org/mariadb/jdbc/MySQLConnection.java
@@ -50,21 +50,26 @@ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWIS
package org.mariadb.jdbc;
import org.mariadb.jdbc.internal.SQLExceptionMapper;
+import org.mariadb.jdbc.internal.common.DefaultOptions;
+import org.mariadb.jdbc.internal.common.Options;
import org.mariadb.jdbc.internal.common.QueryException;
import org.mariadb.jdbc.internal.common.Utils;
-import org.mariadb.jdbc.internal.mysql.MySQLProtocol;
+import org.mariadb.jdbc.internal.mysql.Protocol;
import java.net.SocketException;
import java.sql.*;
import java.util.*;
import java.util.concurrent.Executor;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
public final class MySQLConnection implements Connection {
/**
* the protocol to communicate with.
*/
- private final MySQLProtocol protocol;
+ private final Protocol protocol;
+ public final ReentrantReadWriteLock lock;
+
/**
* save point count - to generate good names for the savepoints.
*/
@@ -72,7 +77,7 @@ public final class MySQLConnection implements Connection {
/**
* the properties for the client.
*/
- private final Properties clientInfoProperties;
+ private Options options;
public MySQLPooledConnection pooledConnection;
@@ -88,13 +93,14 @@ public final class MySQLConnection implements Connection {
*
* @param protocol the protocol to use.
*/
- private MySQLConnection(MySQLProtocol protocol) {
+ private MySQLConnection(Protocol protocol, ReentrantReadWriteLock lock) throws SQLException {
this.protocol = protocol;
- clientInfoProperties = protocol.getInfo();
+ options = protocol.getOptions();
+ this.lock = lock;
}
-
- MySQLProtocol getProtocol() {
- return protocol;
+
+ Protocol getProtocol() {
+ return protocol;
}
static TimeZone getTimeZone(String id) throws SQLException {
@@ -106,23 +112,21 @@ static TimeZone getTimeZone(String id) throws SQLException {
}
return tz;
}
-
- public static MySQLConnection newConnection(MySQLProtocol protocol) throws SQLException {
- MySQLConnection connection = new MySQLConnection(protocol);
- Properties info = protocol.getInfo();
- boolean fastConnect = info.get("fastConnect") != null ;
- String sessionVariables = info.getProperty("sessionVariables");
- String timeZoneId = info.getProperty("serverTimezone");
- if (timeZoneId != null) {
- TimeZone tz = getTimeZone(timeZoneId);
+ public static MySQLConnection newConnection(Protocol protocol, ReentrantReadWriteLock lock) throws SQLException {
+ MySQLConnection connection = new MySQLConnection(protocol, lock);
+
+ Options options = protocol.getOptions();
+ if (options.serverTimezone != null) {
+ TimeZone tz = getTimeZone(options.serverTimezone);
connection.cal = Calendar.getInstance(tz);
}
- connection.noBackslashEscapes = protocol.noBackslashEscapes();
- String nullCatalogMeansCurrentString = info.getProperty("nullCatalogMeansCurrent");
- if (nullCatalogMeansCurrentString != null && nullCatalogMeansCurrentString.equals("false")) {
- connection.nullCatalogMeansCurrent = false;
+ try {
+ connection.noBackslashEscapes = protocol.noBackslashEscapes();
+ } catch (QueryException e) {
+ SQLExceptionMapper.throwException(e, connection, null);
}
+ connection.nullCatalogMeansCurrent = options.nullCatalogMeansCurrent;
return connection;
}
@@ -146,12 +150,25 @@ int getAutoIncrementIncrement() {
* @throws SQLException if we cannot create the statement.
*/
public Statement createStatement() throws SQLException {
- if (getProtocol().isClosed()) {
- throw new SQLException("Cannot create a statement: closed connection");
- }
+ checkConnection();
return new MySQLStatement(this);
}
+ private void checkConnection() throws SQLException {
+ if (protocol.isExplicitClosed()) {
+ throw new SQLException("createStatement() is called on closed connection");
+ }
+ if (protocol.isClosed()) {
+ if (protocol.getProxy() != null) {
+ lock.writeLock().lock();
+ try {
+ protocol.getProxy().reconnect();
+ } finally {
+ lock.writeLock().unlock();
+ }
+ }
+ }
+ }
/**
* creates a new prepared statement. Only client side prepared statement emulation right now.
*
@@ -160,12 +177,14 @@ public Statement createStatement() throws SQLException {
* @throws SQLException if there is a problem preparing the statement.
*/
public PreparedStatement prepareStatement(final String sql) throws SQLException {
+ checkConnection();
return new MySQLPreparedStatement(this, sql);
}
public CallableStatement prepareCall(final String sql) throws SQLException {
- return new MySQLCallableStatement(this, sql);
+ checkConnection();
+ return new MySQLCallableStatement(this, sql);
}
@@ -182,7 +201,7 @@ public String nativeSQL(final String sql) throws SQLException {
public void setAutoCommit(boolean autoCommit) throws SQLException {
if (autoCommit == getAutoCommit())
return;
-
+
Statement stmt = createStatement();
try {
stmt.executeUpdate("set autocommit="+((autoCommit)?"1":"0"));
@@ -193,7 +212,7 @@ public void setAutoCommit(boolean autoCommit) throws SQLException {
/**
* returns true if statements on this connection are auto commited.
- *
+ *
* @return true if auto commit is on.
* @throws SQLException if there is an error
*/
@@ -243,7 +262,7 @@ public void close() throws SQLException {
pooledConnection.fireConnectionClosed();
return;
}
- protocol.close();
+ protocol.closeExplicit();
}
/**
@@ -264,7 +283,7 @@ public boolean isClosed() throws SQLException {
*/
public DatabaseMetaData getMetaData() throws SQLException {
return new MySQLDatabaseMetaData(this,protocol.getUsername(),
- "jdbc:mysql://" + protocol.getHost() + ":" + protocol.getPort() + "/" + protocol.getDatabase());
+ "jdbc:mysql://" + protocol.getHost() + ":" + protocol.getPort() + "/" + protocol.getDatabase());
}
/**
@@ -274,7 +293,11 @@ public DatabaseMetaData getMetaData() throws SQLException {
* @throws SQLException if there is a problem
*/
public void setReadOnly(final boolean readOnly) throws SQLException {
- protocol.setReadonly(readOnly);
+ try {
+ protocol.setReadonly(readOnly);
+ } catch (QueryException e) {
+ SQLExceptionMapper.throwException(e, this, null);
+ }
}
/**
@@ -285,25 +308,25 @@ public void setReadOnly(final boolean readOnly) throws SQLException {
* connection
*/
public boolean isReadOnly() throws SQLException {
- return false;
+ return protocol.getReadonly();
}
public static String quoteIdentifier(String s) {
- return "`" + s.replaceAll("`","``") + "`";
+ return "`" + s.replaceAll("`","``") + "`";
}
-
+
public static String unquoteIdentifier(String s) {
- if (s != null && s.startsWith("`") && s.endsWith("`") && s.length()>= 2) {
- return s.substring(1, s.length()-1).replace("``", "`");
- }
- return s;
+ if (s != null && s.startsWith("`") && s.endsWith("`") && s.length()>= 2) {
+ return s.substring(1, s.length()-1).replace("``", "`");
+ }
+ return s;
}
/**
* Sets the given catalog name in order to select a subspace of this Connection
object's database in
* which to work.
- *
+ *
* If the driver does not support catalogs, it will silently ignore this request.
- *
+ *
* MySQL treats catalogs and databases as equivalent
*
* @param catalog the name of a catalog (subspace in this Connection
object's database) in which to
@@ -312,24 +335,21 @@ public static String unquoteIdentifier(String s) {
* @see #getCatalog
*/
public void setCatalog(final String catalog) throws SQLException {
- if (catalog == null){
- throw new SQLException("The catalog name may not be null", "XAE05");
- }
- Statement st = createStatement();
+ if (catalog == null){
+ throw new SQLException("The catalog name may not be null", "XAE05");
+ }
try {
- /* Quote modifiers correctly, with backtick char */
- st.execute("USE "+ quoteIdentifier(catalog) );
- st.close();
- } finally {
- st.close();
+ protocol.setCatalog(catalog);
+ } catch (QueryException e) {
+ SQLExceptionMapper.throwException(e, this, null);
}
}
/**
* Retrieves this Connection
object's current catalog name.
- *
+ *
* catalogs are not supported in drizzle
- *
+ *
* TODO: Explain the wrapper interface to be able to change database
*
* @return the current catalog name or null
if there is none
@@ -337,7 +357,7 @@ public void setCatalog(final String catalog) throws SQLException {
* @see #setCatalog
*/
public String getCatalog() throws SQLException {
- String catalog = null;
+ String catalog = null;
Statement st = null;
try {
st = createStatement();
@@ -354,7 +374,7 @@ public String getCatalog() throws SQLException {
/**
* Attempts to change the transaction isolation level for this Connection
object to the one given. The
* constants defined in the interface Connection
are the possible transaction isolation levels.
- *
+ *
* Note: If this method is called during a transaction, the result is implementation-defined.
*
* @param level one of the following Connection
constants: Connection.TRANSACTION_READ_UNCOMMITTED
,
@@ -367,31 +387,11 @@ public String getCatalog() throws SQLException {
* @see #getTransactionIsolation
*/
public void setTransactionIsolation(final int level) throws SQLException {
- String query = "SET SESSION TRANSACTION ISOLATION LEVEL";
- switch (level) {
- case Connection.TRANSACTION_READ_UNCOMMITTED:
- query += " READ UNCOMMITTED";
- break;
- case Connection.TRANSACTION_READ_COMMITTED:
- query += " READ COMMITTED";
- break;
- case Connection.TRANSACTION_REPEATABLE_READ:
- query += " REPEATABLE READ";
- break;
- case Connection.TRANSACTION_SERIALIZABLE:
- query += " SERIALIZABLE";
- break;
- default:
- throw SQLExceptionMapper.getSQLException("Unsupported transaction isolation level");
- }
-
- Statement st = createStatement();
try {
- st.execute(query);
- } finally {
- st.close();
+ protocol.setTransactionIsolation(level);
+ } catch (QueryException e) {
+ SQLExceptionMapper.throwException(e, this, null);
}
-
}
/**
@@ -430,15 +430,15 @@ public int getTransactionIsolation() throws SQLException {
/**
* Not yet implemented: Protocol needs to store any warnings related to connections
- *
- *
+ *
+ *
* Retrieves the first warning reported by calls on this Connection
object. If there is more than one
* warning, subsequent warnings will be chained to the first one and can be retrieved by calling the method
* SQLWarning.getNextWarning
on the warning that was retrieved previously.
- *
+ *
* This method may not be called on a closed connection; doing so will cause an SQLException
to be
* thrown.
- *
+ *
* Note: Subsequent warnings will be chained to this SQLWarning.
*
* @return the first SQLWarning
object or null
if there are none
@@ -446,7 +446,7 @@ public int getTransactionIsolation() throws SQLException {
* @see java.sql.SQLWarning
*/
public SQLWarning getWarnings() throws SQLException {
- if (warningsCleared || isClosed() || !protocol.hasWarnings) {
+ if (warningsCleared || isClosed() || !protocol.hasWarnings()) {
return null;
}
Statement st = null;
@@ -454,26 +454,26 @@ public SQLWarning getWarnings() throws SQLException {
SQLWarning last = null;
SQLWarning first = null;
try {
- st = this.createStatement();
- rs = st.executeQuery("show warnings");
- // returned result set has 'level', 'code' and 'message' columns, in this order.
- while(rs.next()) {
- int code = rs.getInt(2);
- String message = rs.getString(3);
- SQLWarning w = new SQLWarning(message, SQLExceptionMapper.mapMySQLCodeToSQLState(code), code);
- if (first == null) {
- first = w;
- last = w;
- }
- else {
- last.setNextWarning(w);
- last = w;
- }
- }
+ st = this.createStatement();
+ rs = st.executeQuery("show warnings");
+ // returned result set has 'level', 'code' and 'message' columns, in this order.
+ while(rs.next()) {
+ int code = rs.getInt(2);
+ String message = rs.getString(3);
+ SQLWarning w = new SQLWarning(message, SQLExceptionMapper.mapMySQLCodeToSQLState(code), code);
+ if (first == null) {
+ first = w;
+ last = w;
+ }
+ else {
+ last.setNextWarning(w);
+ last = w;
+ }
+ }
}
finally {
- if (rs != null)
- rs.close();
+ if (rs != null)
+ rs.close();
if(st != null)
st.close();
}
@@ -641,7 +641,7 @@ public int getHoldability() throws SQLException {
/**
* Creates an unnamed savepoint in the current transaction and returns the new Savepoint
object that
* represents it.
- *
+ *
* if setSavepoint is invoked outside of an active transaction, a transaction will be started at this newly
* created savepoint.
*
@@ -661,7 +661,7 @@ public Savepoint setSavepoint() throws SQLException {
/**
* Creates a savepoint with the given name in the current transaction and returns the new Savepoint
* object that represents it.
- *
+ *
* if setSavepoint is invoked outside of an active transaction, a transaction will be started at this newly
* created savepoint.
*
@@ -685,7 +685,7 @@ public Savepoint setSavepoint(final String name) throws SQLException {
/**
* Undoes all changes made after the given Savepoint
object was set.
- *
+ *
* This method should be used only when auto-commit has been disabled.
*
* @param savepoint the Savepoint
object to roll back to
@@ -700,9 +700,9 @@ public Savepoint setSavepoint(final String name) throws SQLException {
* @since 1.4
*/
public void rollback(final Savepoint savepoint) throws SQLException {
- Statement st = createStatement();
- st.execute("ROLLBACK TO SAVEPOINT " + savepoint.toString());
- st.close();
+ Statement st = createStatement();
+ st.execute("ROLLBACK TO SAVEPOINT " + savepoint.toString());
+ st.close();
}
/**
@@ -719,9 +719,9 @@ public void rollback(final Savepoint savepoint) throws SQLException {
* @since 1.4
*/
public void releaseSavepoint(final Savepoint savepoint) throws SQLException {
- Statement st = createStatement();
- st.execute("RELEASE SAVEPOINT " + savepoint.toString());
- st.close();
+ Statement st = createStatement();
+ st.execute("RELEASE SAVEPOINT " + savepoint.toString());
+ st.close();
}
/**
@@ -758,7 +758,7 @@ public Statement createStatement(final int resultSetType, final int resultSetCon
/**
* Creates a PreparedStatement
object that will generate ResultSet
objects with the given
* type, concurrency, and holdability.
- *
+ *
* This method is the same as the prepareStatement
method above, but it allows the default result set
* type, concurrency, and holdability to be overridden.
*
@@ -828,13 +828,13 @@ public CallableStatement prepareCall(final String sql,
* The given constant tells the driver whether it should make auto-generated keys available for retrieval. This
* parameter is ignored if the SQL statement is not an INSERT
statement, or an SQL statement able to
* return auto-generated keys (the list of such statements is vendor-specific).
- *
+ *
* Note: This method is optimized for handling parametric SQL statements that benefit from precompilation. If
* the driver supports precompilation, the method prepareStatement
will send the statement to the
* database for precompilation. Some drivers may not support precompilation. In this case, the statement may not be
* sent to the database until the PreparedStatement
object is executed. This has no direct effect on
* users; however, it does affect which methods throw certain SQLExceptions.
- *
+ *
* Result sets created using the returned PreparedStatement
object will by default be type
* TYPE_FORWARD_ONLY
and have a concurrency level of CONCUR_READ_ONLY
. The holdability of
* the created result sets can be determined by calling {@link #getHoldability}.
@@ -862,16 +862,16 @@ public PreparedStatement prepareStatement(final String sql, final int autoGenera
* auto-generated keys that should be made available. The driver will ignore the array if the SQL statement is not
* an INSERT
statement, or an SQL statement able to return auto-generated keys (the list of such
* statements is vendor-specific).
- *
+ *
* An SQL statement with or without IN parameters can be pre-compiled and stored in a PreparedStatement
* object. This object can then be used to efficiently execute this statement multiple times.
- *
+ *
* Note: This method is optimized for handling parametric SQL statements that benefit from precompilation. If
* the driver supports precompilation, the method prepareStatement
will send the statement to the
* database for precompilation. Some drivers may not support precompilation. In this case, the statement may not be
* sent to the database until the PreparedStatement
object is executed. This has no direct effect on
* users; however, it does affect which methods throw certain SQLExceptions.
- *
+ *
* Result sets created using the returned PreparedStatement
object will by default be type
* TYPE_FORWARD_ONLY
and have a concurrency level of CONCUR_READ_ONLY
. The holdability of
* the created result sets can be determined by calling {@link #getHoldability}.
@@ -896,16 +896,16 @@ public PreparedStatement prepareStatement(final String sql, final int[] columnIn
* auto-generated keys that should be returned. The driver will ignore the array if the SQL statement is not an
* INSERT
statement, or an SQL statement able to return auto-generated keys (the list of such
* statements is vendor-specific).
- *
+ *
* An SQL statement with or without IN parameters can be pre-compiled and stored in a PreparedStatement
* object. This object can then be used to efficiently execute this statement multiple times.
- *
+ *
* Note: This method is optimized for handling parametric SQL statements that benefit from precompilation. If
* the driver supports precompilation, the method prepareStatement
will send the statement to the
* database for precompilation. Some drivers may not support precompilation. In this case, the statement may not be
* sent to the database until the PreparedStatement
object is executed. This has no direct effect on
* users; however, it does affect which methods throw certain SQLExceptions.
- *
+ *
* Result sets created using the returned PreparedStatement
object will by default be type
* TYPE_FORWARD_ONLY
and have a concurrency level of CONCUR_READ_ONLY
. The holdability of
* the created result sets can be determined by calling {@link #getHoldability}.
@@ -996,75 +996,75 @@ public java.sql.SQLXML createSQLXML() throws SQLException {
* Returns true if the connection has not been closed and is still valid. The driver shall submit a query on the
* connection or use some other mechanism that positively verifies the connection is still valid when this method is
* called.
- *
+ *
* The query submitted by the driver to validate the connection shall be executed in the context of the current
* transaction.
*
* @param timeout - The time in seconds to wait for the database operation used to validate the
* connection to complete. If the timeout period expires before the operation completes, this method
* returns false. A value of 0 indicates a timeout is not applied to the database operation.
- *
+ *
* @return true if the connection is valid, false otherwise
* @throws java.sql.SQLException if the value supplied for timeout
is less then 0
* @see java.sql.DatabaseMetaData#getClientInfoProperties
* @since 1.6
- *
+ *
*/
public boolean isValid(final int timeout) throws SQLException {
- if (timeout < 0) {
- throw new SQLException("the value supplied for timeout is negative");
- }
- if (isClosed()) {
- return false;
- }
+ if (timeout < 0) {
+ throw new SQLException("the value supplied for timeout is negative");
+ }
+ if (isClosed()) {
+ return false;
+ }
try {
return protocol.ping();
} catch (QueryException e) {
- return false;
+ return false;
}
}
/**
* Sets the value of the client info property specified by name to the value specified by value.
- *
+ *
* Applications may use the DatabaseMetaData.getClientInfoProperties
method to determine the client
* info properties supported by the driver and the maximum length that may be specified for each property.
- *
+ *
* The driver stores the value specified in a suitable location in the database. For example in a special register,
* session parameter, or system table column. For efficiency the driver may defer setting the value in the database
* until the next time a statement is executed or prepared. Other than storing the client information in the
* appropriate place in the database, these methods shall not alter the behavior of the connection in anyway. The
* values supplied to these methods are used for accounting, diagnostics and debugging purposes only.
- *
+ *
* The driver shall generate a warning if the client info name specified is not recognized by the driver.
- *
+ *
* If the value specified to this method is greater than the maximum length for the property the driver may either
* truncate the value and generate a warning or generate a SQLClientInfoException
. If the driver
* generates a SQLClientInfoException
, the value specified was not set on the connection.
- *
+ *
* The following are standard client info properties. Drivers are not required to support these properties however
* if the driver supports a client info property that can be described by one of the standard properties, the
* standard property name should be used.
- *
+ *
*
ApplicationName - The name of the application currently utilizing the connection
* ClientUser - The name of the user that the application using the connection is performing
* work for. This may not be the same as the user name that was used in establishing the connection.
* ClientHostname - The hostname of the computer the application using the connection is running
* on.
- *
+ *
*
* @param name The name of the client info property to set
* @param value The value to set the client info property to. If the value is null, the current value of the
* specified property is cleared.
- *
+ *
* @throws java.sql.SQLClientInfoException
* if the database server returns an error while setting the client info value on the database server or
* this method is called on a closed connection
- *
+ *
* @since 1.6
*/
public void setClientInfo(final String name, final String value) throws java.sql.SQLClientInfoException {
- this.clientInfoProperties.setProperty(name, value);
+ DefaultOptions.addProperty(protocol.getJdbcUrl().getHaMode(), name, value, this.options);
}
/**
@@ -1074,79 +1074,76 @@ public void setClientInfo(final String name, final String value) throws java.sql
* currently set on the connection is not present in the properties list, that property is cleared. Specifying an
* empty properties list will clear all of the properties on the connection. See setClientInfo (String,
* String)
for more information.
- *
+ *
* If an error occurs in setting any of the client info properties, a SQLClientInfoException
is thrown.
* The SQLClientInfoException
contains information indicating which client info properties were not
* set. The state of the client information is unknown because some databases do not allow multiple client info
* properties to be set atomically. For those databases, one or more properties may have been set before the error
* occurred.
- *
+ *
*
* @param properties the list of client info properties to set
- *
+ *
* @throws java.sql.SQLClientInfoException
* if the database server returns an error while setting the clientInfo values on the database server or
* this method is called on a closed connection
- *
+ *
* @see java.sql.Connection#setClientInfo(String, String) setClientInfo(String, String)
* @since 1.6
- *
+ *
*/
public void setClientInfo(final Properties properties) throws java.sql.SQLClientInfoException {
- // TODO: actually use these!
- for (final String key : properties.stringPropertyNames()) {
- this.clientInfoProperties.setProperty(key, properties.getProperty(key));
- }
+ DefaultOptions.addProperty(protocol.getJdbcUrl().getHaMode(), properties, this.options);
}
/**
* Returns the value of the client info property specified by name. This method may return null if the specified
* client info property has not been set and does not have a default value. This method will also return null if
* the specified client info property name is not supported by the driver.
- *
+ *
* Applications may use the DatabaseMetaData.getClientInfoProperties
method to determine the client
* info properties supported by the driver.
- *
+ *
*
* @param name The name of the client info property to retrieve
- *
+ *
* @return The value of the client info property specified
- *
+ *
* @throws java.sql.SQLException if the database server returns an error when fetching the client info value from
* the database or this method is called on a closed connection
- *
+ *
* @see java.sql.DatabaseMetaData#getClientInfoProperties
* @since 1.6
- *
+ *
*/
public String getClientInfo(final String name) throws SQLException {
- return clientInfoProperties.getProperty(name);
+ return DefaultOptions.getProperties(name, options);
}
/**
* Returns a list containing the name and current value of each client info property supported by the driver. The
* value of a client info property may be null if the property has not been set and does not have a default value.
- *
+ *
*
* @return A Properties
object that contains the name and current value of each of the client info
* properties supported by the driver.
- *
+ *
* @throws java.sql.SQLException if the database server returns an error when fetching the client info values from
* the database or this method is called on a closed connection
- *
+ *
* @since 1.6
*/
public Properties getClientInfo() throws SQLException {
- return clientInfoProperties;
+ return DefaultOptions.getProperties(options);
}
/**
* Factory method for creating Array objects.
- *
+ *
* Note: When createArrayOf
is used to create an array object that maps to a primitive data
* type, then it is implementation-defined whether the Array
object is an array of that primitive data
* type or an array of Object
.
- *
+ *
* Note: The JDBC driver is responsible for mapping the elements Object
array to the default
* JDBC SQL type defined in java.sql.Types for the given class of Object
. The default mapping is
* specified in Appendix B of the JDBC specification. If the resulting JDBC type is not the appropriate type for
@@ -1190,7 +1187,7 @@ public Struct createStruct(final String typeName, final Object[] attributes) thr
/**
* Returns an object that implements the given interface to allow access to non-standard methods, or standard
* methods not exposed by the proxy.
- *
+ *
* If the receiver implements the interface then the result is the receiver or a proxy for the receiver. If the
* receiver is a wrapper and the wrapped object implements the interface then the result is the wrapped object or a
* proxy for the wrapped object. Otherwise return the the result of calling unwrap
recursively on the
@@ -1269,14 +1266,13 @@ public int getPort() {
public String getDatabase() {
return protocol.getDatabase();
}
-
- public String getPinGlobalTxToPhysicalConnection() {
- return protocol.getPinGlobalTxToPhysicalConnection();
+
+ protected boolean getPinGlobalTxToPhysicalConnection() {
+ return protocol.getPinGlobalTxToPhysicalConnection();
}
-
public void setHostFailed() {
- protocol.setHostFailed();
+ if (protocol.getProxy() == null) protocol.setHostFailedWithoutProxy();
}
volatile int lowercaseTableNames = -1;
@@ -1289,66 +1285,66 @@ public int getLowercaseTableNames() throws SQLException {
}
return lowercaseTableNames;
}
-
- /* (non-Javadoc)
- * @see java.sql.Connection#abort(java.util.concurrent.Executor)
- */
- public void abort(Executor executor) throws SQLException {
- if (this.isClosed()) {
- return;
- }
- SQLPermission sqlPermission = new SQLPermission("callAbort");
+
+ /* (non-Javadoc)
+ * @see java.sql.Connection#abort(java.util.concurrent.Executor)
+ */
+ public void abort(Executor executor) throws SQLException {
+ if (this.isClosed()) {
+ return;
+ }
+ SQLPermission sqlPermission = new SQLPermission("callAbort");
SecurityManager securityManager = System.getSecurityManager();
- if (securityManager != null && sqlPermission != null) {
- securityManager.checkPermission(sqlPermission);
- }
- if (executor == null) {
- throw SQLExceptionMapper.getSQLException("Cannot abort the connection: null executor passed");
- }
- executor.execute(new Runnable() {
- @Override
- public void run() {
- try {
- close();
- pooledConnection = null;
- } catch (SQLException sqle) {
- throw new RuntimeException(sqle);
- }
- }
- });
- }
-
- public int getNetworkTimeout() throws SQLException {
- try {
- return this.protocol.getTimeout();
- } catch (SocketException se) {
- throw SQLExceptionMapper.getSQLException("Cannot retrieve the network timeout", se);
- }
- }
-
- public String getSchema() throws SQLException {
- // We support only catalog
- return null;
- }
-
- /* (non-Javadoc)
- * @see java.sql.Connection#setNetworkTimeout(java.util.concurrent.Executor, int)
- */
- public void setNetworkTimeout(Executor executor, final int milliseconds) throws SQLException {
- if (this.isClosed()) {
- throw SQLExceptionMapper.getSQLException("Connection.setNetworkTimeout cannot be called on a closed connection");
- }
- if (milliseconds < 0) {
- throw SQLExceptionMapper.getSQLException("Connection.setNetworkTimeout cannot be called with a negative timeout");
- }
- SQLPermission sqlPermission = new SQLPermission("setNetworkTimeout");
+ if (securityManager != null && sqlPermission != null) {
+ securityManager.checkPermission(sqlPermission);
+ }
+ if (executor == null) {
+ throw SQLExceptionMapper.getSQLException("Cannot abort the connection: null executor passed");
+ }
+ executor.execute(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ close();
+ pooledConnection = null;
+ } catch (SQLException sqle) {
+ throw new RuntimeException(sqle);
+ }
+ }
+ });
+ }
+
+ public int getNetworkTimeout() throws SQLException {
+ try {
+ return this.protocol.getTimeout();
+ } catch (SocketException se) {
+ throw SQLExceptionMapper.getSQLException("Cannot retrieve the network timeout", se);
+ }
+ }
+
+ public String getSchema() throws SQLException {
+ // We support only catalog
+ return null;
+ }
+
+ /* (non-Javadoc)
+ * @see java.sql.Connection#setNetworkTimeout(java.util.concurrent.Executor, int)
+ */
+ public void setNetworkTimeout(Executor executor, final int milliseconds) throws SQLException {
+ if (this.isClosed()) {
+ throw SQLExceptionMapper.getSQLException("Connection.setNetworkTimeout cannot be called on a closed connection");
+ }
+ if (milliseconds < 0) {
+ throw SQLExceptionMapper.getSQLException("Connection.setNetworkTimeout cannot be called with a negative timeout");
+ }
+ SQLPermission sqlPermission = new SQLPermission("setNetworkTimeout");
SecurityManager securityManager = System.getSecurityManager();
- if (securityManager != null && sqlPermission != null) {
- securityManager.checkPermission(sqlPermission);
- }
- if (executor == null) {
- throw SQLExceptionMapper.getSQLException("Cannot set the connection timeout: null executor passed");
- }
+ if (securityManager != null && sqlPermission != null) {
+ securityManager.checkPermission(sqlPermission);
+ }
+ if (executor == null) {
+ throw SQLExceptionMapper.getSQLException("Cannot set the connection timeout: null executor passed");
+ }
// executor.execute(new Runnable() {
// @Override
// public void run() {
@@ -1359,15 +1355,16 @@ public void setNetworkTimeout(Executor executor, final int milliseconds) throws
// }
// }
// });
- try {
- protocol.setTimeout(milliseconds);
- } catch (SocketException se) {
- throw SQLExceptionMapper.getSQLException("Cannot set the network timeout", se);
- }
- }
-
- public void setSchema(String arg0) throws SQLException {
- // We support only catalog
- throw SQLExceptionMapper.getFeatureNotSupportedException("Only catalogs are supported");
- }
+ try {
+ protocol.setTimeout(milliseconds);
+ } catch (SocketException se) {
+ throw SQLExceptionMapper.getSQLException("Cannot set the network timeout", se);
+ }
+ }
+
+ public void setSchema(String arg0) throws SQLException {
+ // We support only catalog
+ throw SQLExceptionMapper.getFeatureNotSupportedException("Only catalogs are supported");
+ }
+
}
diff --git a/src/main/java/org/mariadb/jdbc/MySQLDataSource.java b/src/main/java/org/mariadb/jdbc/MySQLDataSource.java
index 6dca50afb..190c91553 100644
--- a/src/main/java/org/mariadb/jdbc/MySQLDataSource.java
+++ b/src/main/java/org/mariadb/jdbc/MySQLDataSource.java
@@ -51,243 +51,197 @@ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWIS
package org.mariadb.jdbc;
import org.mariadb.jdbc.internal.SQLExceptionMapper;
+import org.mariadb.jdbc.internal.common.DefaultOptions;
import org.mariadb.jdbc.internal.common.QueryException;
+import org.mariadb.jdbc.internal.common.UrlHAMode;
import org.mariadb.jdbc.internal.common.Utils;
-import org.mariadb.jdbc.internal.mysql.MySQLProtocol;
+import org.mariadb.jdbc.internal.mysql.*;
import javax.sql.*;
import java.io.PrintWriter;
+import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
+import java.util.ArrayList;
import java.util.Properties;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.logging.Logger;
public class MySQLDataSource implements DataSource, ConnectionPoolDataSource, XADataSource {
+ private final static Logger log = Logger.getLogger(MySQLDataSource.class.getName());
-
- private String hostname = "localhost";
- private int port = 0;
- private String database = "";
- private String username = null;
- private String password = null;
- private Properties info = null;
- private JDBCUrl url;
+ private final JDBCUrl jdbcUrl;
public MySQLDataSource(String hostname, int port, String database) {
- this.hostname = hostname;
- this.port = port;
- this.database = database;
- this.info = new Properties();
+ ArrayList hostAddresses = new ArrayList();
+ hostAddresses.add(new HostAddress(hostname, port));
+ jdbcUrl = new JDBCUrl(database, hostAddresses, DefaultOptions.defaultValues(UrlHAMode.NONE), UrlHAMode.NONE);
+ }
+
+ public MySQLDataSource(String url) {
+ this.jdbcUrl = JDBCUrl.parse(url);
}
public MySQLDataSource() {
- this.info = new Properties();
+ ArrayList hostAddresses = new ArrayList();
+ hostAddresses.add(new HostAddress("localhost", 3306));
+ jdbcUrl = new JDBCUrl("", hostAddresses, DefaultOptions.defaultValues(UrlHAMode.NONE), UrlHAMode.NONE);
}
/**
* Sets the database name.
- *
+ *
* @param dbName
* the name of the database
*/
public void setDatabaseName(String dbName) {
- this.database = dbName;
- resetUrl();
+ jdbcUrl.setDatabase(dbName);
}
/**
* Gets the name of the database
- *
+ *
* @return the name of the database for this data source
*/
public String getDatabaseName() {
- return (this.database != null) ? this.database : "";
+ return (jdbcUrl.getDatabase() != null) ? jdbcUrl.getDatabase() : "";
}
/**
* Sets the username
- *
+ *
* @param userName
* the username
*/
public void setUser(String userName) {
- setUserName(userName);
+ setUserName(userName);
}
/**
* Gets the username
- *
+ *
* @return the username to use when connecting to the database
*/
public String getUser() {
- return this.username;
+ return jdbcUrl.getUsername();
}
/**
* Sets the username
- *
+ *
* @param userName
* the username
*/
public void setUserName(String userName) {
- this.username = userName;
+ jdbcUrl.setUsername(userName);
}
/**
* Gets the username
- *
+ *
* @return the username to use when connecting to the database
*/
public String getUserName() {
- return this.username;
+ return jdbcUrl.getUsername();
}
/**
* Sets the password
- *
+ *
* @param pass
* the password
*/
public void setPassword(String pass) {
- this.password = pass;
+ jdbcUrl.setPassword(pass);
}
/**
* Sets the database port.
- *
+ *
* @param p
* the port
*/
public void setPort(int p) {
- this.port = p;
- resetUrl();
+ jdbcUrl.getHostAddresses().get(0).port = p;
}
/**
* Returns the port number
- *
+ *
* @return the port number
*/
public int getPort() {
- return this.port;
+ return jdbcUrl.getHostAddresses().get(0).port;
}
/**
* Sets the port number
- *
+ *
* @param p
* the port
- *
+ *
* @see #setPort
*/
public void setPortNumber(int p) {
- setPort(p);
+ setPort(p);
}
/**
* Returns the port number
- *
+ *
* @return the port number
*/
public int getPortNumber() {
- return getPort();
+ return getPort();
}
/**
* Sets the server name.
- *
+ *
* @param serverName
* the server name
*/
public void setServerName(String serverName) {
- this.hostname = serverName;
- resetUrl();
+ jdbcUrl.getHostAddresses().get(0).host = serverName;
}
public void setProperties(String properties) {
- Utils.setUrlParameters(properties, this.info);
+ jdbcUrl.setProperties(properties);
}
/**
* Sets the connection string URL.
- *
+ *
* @param url
* the connection string
*/
public void setURL(String url) {
- setUrl(url);
+ setUrl(url);
}
/**
* Sets the connection string URL.
- *
+ *
* @param s
* the connection string
*/
public void setUrl(String s) {
-
- String baseUrl = s;
- int idx = s.lastIndexOf("?");
- if (idx > 0) {
- baseUrl = s.substring(0,idx);
- String urlParams = s.substring(idx+1);
- setProperties(urlParams);
- }
- this.url = JDBCUrl.parse(baseUrl);
-
- String tmpStr;
- if ((tmpStr = url.getDatabase()) != null) {
- this.database = tmpStr;
- }
- if ((tmpStr = url.getHostname()) != null) {
- this.hostname = tmpStr;
- }
- tmpStr = url.getUsername();
- if (tmpStr.equals("")) {
- tmpStr = this.info.getProperty("user", "");
- }
- if (!tmpStr.equals("")) {
- this.username = tmpStr;
- }
- tmpStr = url.getPassword();
- if (tmpStr.equals("")) {
- tmpStr = this.info.getProperty("password", "");
- }
- if (!tmpStr.equals("")) {
- this.password = tmpStr;
- }
- this.port = url.getPort();
+ this.jdbcUrl.parseUrl(s);
}
/**
* Returns the name of the database server
- *
+ *
* @return the name of the database server
*/
public String getServerName() {
- return (this.hostname != null) ? this.hostname : "";
+ return (this.jdbcUrl.getHostAddresses().get(0).host != null) ? this.jdbcUrl.getHostAddresses().get(0).host : "";
}
-
- void createUrl() {
- if (url != null)
- return;
-
- String urlString = "jdbc:mysql://" + hostname;
- if (port != 0)
- urlString = urlString + ":" + port;
- if (database != null)
- urlString = urlString + "/" + database;
- url = JDBCUrl.parse(urlString);
- }
-
- private void resetUrl() {
- this.url = null;
- }
/**
* Attempts to establish a connection with the data source that this DataSource
object represents.
*
@@ -295,9 +249,10 @@ private void resetUrl() {
* @throws java.sql.SQLException if a database access error occurs
*/
public Connection getConnection() throws SQLException {
- createUrl();
try {
- return MySQLConnection.newConnection(new MySQLProtocol(url, username, password, info));
+ ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
+ Protocol proxyfiedProtocol = Utils.retrieveProxy(jdbcUrl, lock);
+ return MySQLConnection.newConnection(proxyfiedProtocol, lock);
} catch (QueryException e) {
SQLExceptionMapper.throwException(e, null, null);
return null;
@@ -314,19 +269,15 @@ public Connection getConnection() throws SQLException {
* @since 1.4
*/
public Connection getConnection(final String username, final String password) throws SQLException {
- createUrl();
- try {
- Properties props = info == null ? new Properties() : info;
- return MySQLConnection.newConnection(new MySQLProtocol(url, username, password, props));
- } catch (QueryException e) {
- SQLExceptionMapper.throwException(e, null, null);
- return null;
- }
+ jdbcUrl.setUsername(username);
+ jdbcUrl.setPassword(password);
+ log.finest("connection : " +jdbcUrl.toString());
+ return getConnection();
}
/**
* Retrieves the log writer for this DataSource
object.
- *
+ *
* The log writer is a character output stream to which all logging and tracing messages for this data source
* will be printed. This includes messages printed by the methods of this object, messages printed by methods of
* other objects manufactured by this object, and so on. Messages printed to a data source specific log writer are
@@ -346,7 +297,7 @@ public PrintWriter getLogWriter() throws SQLException {
/**
* Sets the log writer for this DataSource
object to the given java.io.PrintWriter
* object.
- *
+ *
* The log writer is a character output stream to which all logging and tracing messages for this data source
* will be printed. This includes messages printed by the methods of this object, messages printed by methods of
* other objects manufactured by this object, and so on. Messages printed to a data source- specific log writer are
@@ -394,7 +345,7 @@ public int getLoginTimeout() throws SQLException {
/**
* Returns an object that implements the given interface to allow access to non-standard methods, or standard
* methods not exposed by the proxy.
- *
+ *
* If the receiver implements the interface then the result is the receiver or a proxy for the receiver. If the
* receiver is a wrapper and the wrapped object implements the interface then the result is the wrapped object or a
* proxy for the wrapped object. Otherwise return the the result of calling unwrap
recursively on the
@@ -458,7 +409,7 @@ public PooledConnection getPooledConnection() throws SQLException {
* @since 1.4
*/
public PooledConnection getPooledConnection(String user, String password) throws SQLException {
- return new MySQLPooledConnection((MySQLConnection)getConnection(user,password));
+ return new MySQLPooledConnection((MySQLConnection)getConnection(user,password));
}
public XAConnection getXAConnection() throws SQLException {
@@ -468,8 +419,8 @@ public XAConnection getXAConnection(String user, String password) throws SQLExce
return new MySQLXAConnection((MySQLConnection)getConnection(user,password));
}
- public Logger getParentLogger() throws SQLFeatureNotSupportedException {
- // TODO Auto-generated method stub
- return null;
- }
+ public Logger getParentLogger() throws SQLFeatureNotSupportedException {
+ // TODO Auto-generated method stub
+ return null;
+ }
}
diff --git a/src/main/java/org/mariadb/jdbc/MySQLDatabaseMetaData.java b/src/main/java/org/mariadb/jdbc/MySQLDatabaseMetaData.java
index d057d685e..945013628 100644
--- a/src/main/java/org/mariadb/jdbc/MySQLDatabaseMetaData.java
+++ b/src/main/java/org/mariadb/jdbc/MySQLDatabaseMetaData.java
@@ -96,8 +96,8 @@ private String dataTypeClause (String fullTypeColumnName){
" WHEN 'binary' THEN " + Types.BINARY +
" WHEN 'time' THEN " + Types.TIME +
" WHEN 'timestamp' THEN " + Types.TIMESTAMP +
- " WHEN 'tinyint' THEN " + (((connection.getProtocol().datatypeMappingFlags & MySQLValueObject.TINYINT1_IS_BIT)== 0)? Types.TINYINT : "IF(" + fullTypeColumnName + "='tinyint(1)'," + Types.BIT + "," + Types.TINYINT + ") ") +
- " WHEN 'year' THEN " + (((connection.getProtocol().datatypeMappingFlags & MySQLValueObject.YEAR_IS_DATE_TYPE)== 0)? Types.SMALLINT :Types.DATE) +
+ " WHEN 'tinyint' THEN " + (((connection.getProtocol().getDatatypeMappingFlags() & MySQLValueObject.TINYINT1_IS_BIT)== 0)? Types.TINYINT : "IF(" + fullTypeColumnName + "='tinyint(1)'," + Types.BIT + "," + Types.TINYINT + ") ") +
+ " WHEN 'year' THEN " + (((connection.getProtocol().getDatatypeMappingFlags() & MySQLValueObject.YEAR_IS_DATE_TYPE)== 0)? Types.SMALLINT :Types.DATE) +
" ELSE " + Types.OTHER +
" END ";
}
@@ -492,7 +492,7 @@ public String getDriverName() throws SQLException {
}
public String getDriverVersion() throws SQLException {
- return Version.pomversion;
+ return String.format("%d.%d",getDriverMajorVersion(),getDriverMinorVersion());
}
diff --git a/src/main/java/org/mariadb/jdbc/MySQLPreparedStatement.java b/src/main/java/org/mariadb/jdbc/MySQLPreparedStatement.java
index 63a15d02a..87428fd35 100644
--- a/src/main/java/org/mariadb/jdbc/MySQLPreparedStatement.java
+++ b/src/main/java/org/mariadb/jdbc/MySQLPreparedStatement.java
@@ -81,7 +81,7 @@ public MySQLPreparedStatement(MySQLConnection connection,
super(connection);
this.sql = sql;
useFractionalSeconds =
- connection.getProtocol().getInfo().getProperty("useFractionalSeconds") != null;
+ connection.getProtocol().getOptions().useFractionalSeconds;
if(log.isLoggable(Level.FINEST)) {
log.finest("Creating prepared statement for " + sql);
}
@@ -1513,16 +1513,21 @@ public void setBigDecimal(final int parameterIndex, final BigDecimal x) throws S
// Close prepared statement, maybe fire closed-statement events
@Override
- public synchronized void close() throws SQLException {
- super.close();
+ public void close() throws SQLException {
+ connection.lock.writeLock().lock();
+ try {
+ super.close();
- if (connection == null || connection.pooledConnection == null ||
- connection.pooledConnection.statementEventListeners.isEmpty()) {
- return;
- }
+ if (connection == null || connection.pooledConnection == null ||
+ connection.pooledConnection.statementEventListeners.isEmpty()) {
+ return;
+ }
- isClosed = false;
- connection.pooledConnection.fireStatementClosed(this);
+ isClosed = false;
+ connection.pooledConnection.fireStatementClosed(this);
+ } finally {
+ connection.lock.writeLock().unlock();
+ }
}
public String toString() {
diff --git a/src/main/java/org/mariadb/jdbc/MySQLResultSet.java b/src/main/java/org/mariadb/jdbc/MySQLResultSet.java
index 5c487658a..b678669ab 100644
--- a/src/main/java/org/mariadb/jdbc/MySQLResultSet.java
+++ b/src/main/java/org/mariadb/jdbc/MySQLResultSet.java
@@ -53,10 +53,7 @@ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWIS
import org.mariadb.jdbc.internal.common.QueryException;
import org.mariadb.jdbc.internal.common.ValueObject;
import org.mariadb.jdbc.internal.common.queryresults.*;
-import org.mariadb.jdbc.internal.mysql.MySQLColumnInformation;
-import org.mariadb.jdbc.internal.mysql.MySQLProtocol;
-import org.mariadb.jdbc.internal.mysql.MySQLType;
-import org.mariadb.jdbc.internal.mysql.MySQLValueObject;
+import org.mariadb.jdbc.internal.mysql.*;
import java.io.IOException;
import java.io.InputStream;
@@ -76,7 +73,7 @@ public class MySQLResultSet implements ResultSet {
public static final MySQLResultSet EMPTY = createEmptyResultSet();
private QueryResult queryResult;
private Statement statement;
- private MySQLProtocol protocol;
+ private Protocol protocol;
private boolean lastGetWasNull;
private boolean warningsCleared;
ColumnNameMap columnNameMap;
@@ -84,7 +81,7 @@ public class MySQLResultSet implements ResultSet {
protected MySQLResultSet() {
}
- public MySQLResultSet(QueryResult dqr, Statement statement, MySQLProtocol protocol, Calendar cal) {
+ public MySQLResultSet(QueryResult dqr, Statement statement, Protocol protocol, Calendar cal) {
this.queryResult = dqr;
this.statement = statement;
this.protocol = protocol;
@@ -406,13 +403,7 @@ public String getCursorName() throws SQLException {
* @throws java.sql.SQLException if a database access error occurs or this method is called on a closed result set
*/
public ResultSetMetaData getMetaData() throws SQLException {
- boolean returnTableAlias = false;
-
- if (protocol.getInfo().getProperty("useOldAliasMetadataBehavior") != null
- && "true".equalsIgnoreCase(protocol.getInfo().getProperty("useOldAliasMetadataBehavior")))
- returnTableAlias = true;
-
- return new MySQLResultSetMetaData(queryResult.getColumnInformation(), protocol.datatypeMappingFlags, returnTableAlias);
+ return new MySQLResultSetMetaData(queryResult.getColumnInformation(), protocol.getDatatypeMappingFlags(), protocol.getOptions().useOldAliasMetadataBehavior);
}
/**
@@ -447,7 +438,7 @@ public ResultSetMetaData getMetaData() throws SQLException {
*/
public Object getObject(int columnIndex) throws SQLException {
try {
- return getValueObject(columnIndex).getObject(protocol.datatypeMappingFlags, cal);
+ return getValueObject(columnIndex).getObject(protocol.getDatatypeMappingFlags(), cal);
} catch (ParseException e) {
throw SQLExceptionMapper.getSQLException("Could not get object: " + e.getMessage(), "S1009", e);
}
@@ -3753,7 +3744,7 @@ public T getObject(String arg0, Class arg1) throws SQLException {
* @param findColumnReturnsOne - special parameter, used only in generated key result sets
*/
static ResultSet createResultSet(String[] columnNames, MySQLType[] columnTypes, String[][] data,
- MySQLProtocol protocol, boolean findColumnReturnsOne) {
+ Protocol protocol, boolean findColumnReturnsOne) {
int N = columnNames.length;
MySQLColumnInformation[] columns = new MySQLColumnInformation[N];
@@ -3810,7 +3801,7 @@ public int findColumn(String name) {
* @param protocol
*/
static ResultSet createResultSet(String[] columnNames, MySQLType[] columnTypes, String[][] data,
- MySQLProtocol protocol) {
+ Protocol protocol) {
return createResultSet(columnNames, columnTypes, data, protocol,false);
}
@@ -3825,7 +3816,7 @@ static ResultSet createResultSet(String[] columnNames, MySQLType[] columnTypes,
* @param findColumnReturnsOne - special parameter, used only in generated key result sets
*/
static ResultSet createResultSet(MySQLColumnInformation[] columns, String[][] data,
- MySQLProtocol protocol, boolean findColumnReturnsOne) {
+ Protocol protocol, boolean findColumnReturnsOne) {
int N = columns.length;
byte[] BOOL_TRUE = {1};
@@ -3875,7 +3866,7 @@ public int findColumn(String name) {
* that are represented as "1" or "0" strings
* @param protocol
*/
- static ResultSet createResultSet(MySQLColumnInformation[] columns, String[][] data, MySQLProtocol protocol) {
+ static ResultSet createResultSet(MySQLColumnInformation[] columns, String[][] data, Protocol protocol) {
return createResultSet(columns, data, protocol, false);
}
diff --git a/src/main/java/org/mariadb/jdbc/MySQLServerSidePreparedStatement.java b/src/main/java/org/mariadb/jdbc/MySQLServerSidePreparedStatement.java
index 1f43d4799..9c2fd19d1 100644
--- a/src/main/java/org/mariadb/jdbc/MySQLServerSidePreparedStatement.java
+++ b/src/main/java/org/mariadb/jdbc/MySQLServerSidePreparedStatement.java
@@ -4,6 +4,7 @@
import org.mariadb.jdbc.internal.common.QueryException;
import org.mariadb.jdbc.internal.mysql.MySQLColumnInformation;
import org.mariadb.jdbc.internal.mysql.MySQLProtocol;
+import org.mariadb.jdbc.internal.mysql.Protocol;
import java.io.InputStream;
import java.io.Reader;
@@ -27,24 +28,23 @@ public class MySQLServerSidePreparedStatement implements PreparedStatement {
private void prepare(String sql) throws SQLException {
try {
- MySQLProtocol protocol = connection.getProtocol();
+ Protocol protocol = connection.getProtocol();
MySQLProtocol.PrepareResult result;
- synchronized (protocol) {
+ connection.lock.writeLock().lock();
+ try {
if (protocol.hasUnreadData()) {
throw new SQLException(
"There is an open result set on the current connection, "
+ "which must be closed prior to executing a query");
}
result = protocol.prepare(sql);
+ } finally {
+ connection.lock.writeLock().unlock();
}
-
- if (protocol.getInfo().getProperty("useOldAliasMetadataBehavior") != null
- && "true".equalsIgnoreCase(protocol.getInfo().getProperty(
- "useOldAliasMetadataBehavior")))
- returnTableAlias = true;
+ returnTableAlias = protocol.getOptions().useOldAliasMetadataBehavior;
metadata = new MySQLResultSetMetaData(result.columns,
- protocol.datatypeMappingFlags, returnTableAlias);
+ protocol.getDatatypeMappingFlags(), returnTableAlias);
parameterInfo = result.parameters;
statementId = result.statementId;
} catch (QueryException e) {
@@ -148,6 +148,7 @@ public void setAsciiStream(int parameterIndex, InputStream x, int length)
throw new UnsupportedOperationException("Not supported yet.");
}
+ @Deprecated
@Override
public void setUnicodeStream(int parameterIndex, InputStream x, int length)
throws SQLException {
diff --git a/src/main/java/org/mariadb/jdbc/MySQLStatement.java b/src/main/java/org/mariadb/jdbc/MySQLStatement.java
index d7c4dd056..f76d2eb72 100644
--- a/src/main/java/org/mariadb/jdbc/MySQLStatement.java
+++ b/src/main/java/org/mariadb/jdbc/MySQLStatement.java
@@ -57,7 +57,7 @@ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWIS
import org.mariadb.jdbc.internal.common.queryresults.ModifyQueryResult;
import org.mariadb.jdbc.internal.common.queryresults.QueryResult;
import org.mariadb.jdbc.internal.common.queryresults.ResultSetType;
-import org.mariadb.jdbc.internal.mysql.MySQLProtocol;
+import org.mariadb.jdbc.internal.mysql.Protocol;
import java.io.IOException;
import java.io.InputStream;
@@ -69,13 +69,12 @@ public class MySQLStatement implements Statement {
/**
* the protocol used to talk to the server.
*/
- private final MySQLProtocol protocol;
+ private final Protocol protocol;
/**
* the Connection object.
*/
protected MySQLConnection connection;
-
/**
* The actual query result.
*/
@@ -89,6 +88,7 @@ public class MySQLStatement implements Statement {
private int queryTimeout;
private boolean escapeProcessing;
private int fetchSize;
+ private boolean wasStreaming=false;
private int maxRows;
boolean isClosed;
private static volatile Timer timer;
@@ -116,7 +116,7 @@ public MySQLStatement(MySQLConnection connection) {
this.protocol = connection.getProtocol();
this.connection = connection;
this.escapeProcessing = true;
- cachedResultSets = new LinkedList();
+ cachedResultSets = new LinkedList<>();
}
@@ -125,7 +125,7 @@ public MySQLStatement(MySQLConnection connection) {
*
* @return the protocol used.
*/
- public MySQLProtocol getProtocol() {
+ public Protocol getProtocol() {
return protocol;
}
@@ -141,90 +141,71 @@ private static Timer getTimer() {
}
return result;
}
-
+
// Part of query prolog - setup timeout timer
private void setTimerTask() {
- assert(timerTask == null);
+ assert(timerTask == null);
timerTask = new TimerTask() {
- @Override
- public void run() {
- try {
- isTimedout = true;
- protocol.cancelCurrentQuery();
- } catch (Throwable e) {
- }
- }
- };
- getTimer().schedule(timerTask, queryTimeout*1000);
- }
-
- // Part of query prolog - check if connection is broken and reconnect
- private void checkReconnect() throws SQLException {
- if (protocol.shouldReconnect()) {
- try {
- protocol.connect();
- } catch (QueryException qe) {
- SQLExceptionMapper.throwException(qe, connection, this);
- }
- } else if (protocol.shouldTryFailback()) {
- try {
- protocol.reconnectToMaster();
- } catch (Exception e) {
- // Do nothing
- }
- }
- }
-
+ @Override
+ public void run() {
+ try {
+ isTimedout = true;
+ protocol.cancelCurrentQuery();
+ } catch (Throwable e) {
+ }
+ }
+ };
+ getTimer().schedule(timerTask, queryTimeout*1000);
+ }
+
void executeQueryProlog() throws SQLException{
if (isClosed()) {
throw new SQLException("execute() is called on closed statement");
}
- checkReconnect();
- if (protocol.isClosed()){
- throw new SQLException("execute() is called on closed connection");
- }
- if (protocol.hasUnreadData()) {
- throw new SQLException("There is an open result set on the current connection, "+
- "which must be closed prior to executing a query");
+ if (protocol.isExplicitClosed()) {
+ throw new SQLException("execute() is called on closed connection");
}
- if (protocol.hasMoreResults()) {
- // Skip remaining result sets. CallableStatement might return many of them -
- // not only the "select" result sets, but also the "update" results
+ //old failover handling
+ if (protocol.getProxy() == null) checkReconnectWithoutProxy();
+
+ protocol.closeIfActiveResult();
+ if (wasStreaming && protocol.hasMoreResults()) {
+ // Skip remaining result sets. CallableStatement might return many of them -
+ // not only the "select" result sets, but also the "update" results
while(getMoreResults(true)) {
}
}
cachedResultSets.clear();
- MySQLConnection conn = (MySQLConnection)getConnection();
- conn.reenableWarnings();
-
+ ((MySQLConnection)getConnection()).reenableWarnings();
+
try {
protocol.setMaxRows(maxRows);
} catch(QueryException qe) {
SQLExceptionMapper.throwException(qe, connection, this);
}
-
+
if (queryTimeout != 0) {
- setTimerTask();
+ setTimerTask();
}
}
private void cacheMoreResults() {
+ wasStreaming = isStreaming();
+ if (isStreaming()) return;
- if (isStreaming())
- return;
QueryResult saveResult = queryResult;
for(;;) {
try {
- if (protocol.hasMoreResults()) {
- getMoreResults(false);
- cachedResultSets.add(queryResult);
+ if (protocol.hasMoreResults()) {
+ getMoreResults(false);
+ cachedResultSets.add(queryResult);
} else {
break;
}
} catch(SQLException e) {
- cachedResultSets.add(e);
- break;
+ cachedResultSets.add(e);
+ break;
}
}
queryResult = saveResult;
@@ -238,17 +219,17 @@ private void cacheMoreResults() {
private void executeQueryEpilog(QueryException e, Query query) throws SQLException{
if (timerTask != null) {
- timerTask.cancel();
- timerTask = null;
+ timerTask.cancel();
+ timerTask = null;
}
if (isTimedout) {
isTimedout = false;
e = new QueryException("Query timed out", 1317, "JZ0002", e);
}
-
+
if (e == null)
- return;
+ return;
/* Include query into exception message, if dumpQueriesOnException is true,
* or on SQL syntax error (MySQL error code 1064).
@@ -256,16 +237,19 @@ private void executeQueryEpilog(QueryException e, Query query) throws SQLExcepti
* If SQL query is too long, truncate it to reasonable (for exception messages)
* length.
*/
- if (protocol.getInfo().getProperty("dumpQueriesOnException", "false").equalsIgnoreCase("true")
- || e.getErrorCode() == 1064 ) {
- String queryString = query.toString();
+ if (protocol.getOptions().dumpQueriesOnException
+ || e.getErrorCode() == 1064 ) {
+ String queryString = query.toString();
if (queryString.length() > 4096) {
- queryString = queryString.substring(0, 4096);
+ queryString = queryString.substring(0, 4096);
}
e.setMessage(e.getMessage()+ "\nQuery is:\n" + queryString);
}
-
- SQLExceptionMapper.throwException(e, connection, this);
+
+ //if has a failover, closing the statement
+ if (e.getSqlState() != null && e.getSqlState().startsWith("08")) close();
+
+ SQLExceptionMapper.throwException(e, connection, this);
}
/**
@@ -273,29 +257,29 @@ private void executeQueryEpilog(QueryException e, Query query) throws SQLExcepti
*
* @param query the query
* @return true if there was a result set, false otherwise.
- * @throws SQLException
+ * @throws SQLException the error description
*/
- protected boolean execute(Query query) throws SQLException {
- //System.out.println(query);
- synchronized (protocol) {
- if (protocol.activeResult != null) {
- protocol.activeResult.close();
- }
- executing = true;
- QueryException exception = null;
+ protected boolean execute(Query query) throws SQLException {
+
+ executing = true;
+ QueryException exception = null;
+ connection.lock.writeLock().lock();
+ try {
executeQueryProlog();
try {
- batchResultSet = null;
+ batchResultSet = null;
queryResult = protocol.executeQuery(query, isStreaming());
cacheMoreResults();
return (queryResult.getResultSetType() == ResultSetType.SELECT);
} catch (QueryException e) {
- exception = e;
- return false;
+ exception = e;
+ return false;
} finally {
executeQueryEpilog(exception, query);
executing = false;
}
+ } finally {
+ connection.lock.writeLock().unlock();
}
}
@@ -312,16 +296,14 @@ protected boolean execute(Query query) throws SQLException {
* @param isRewritable are the queries of the same type to be agreggated
* @param rewriteOffset offset of the parameter if query are similar
* @return true if there was a result set, false otherwise.
- * @throws SQLException
+ * @throws SQLException the error description
*/
protected boolean execute(List queries, boolean isRewritable, int rewriteOffset) throws SQLException {
- //System.out.println(query);
- synchronized (protocol) {
- if (protocol.activeResult != null) {
- protocol.activeResult.close();
- }
- executing = true;
- QueryException exception = null;
+
+ executing = true;
+ QueryException exception = null;
+ connection.lock.writeLock().lock();
+ try {
executeQueryProlog();
try {
batchResultSet = null;
@@ -335,6 +317,8 @@ protected boolean execute(List queries, boolean isRewritable, int rewrite
executeQueryEpilog(exception, queries.get(0));
executing = false;
}
+ } finally {
+ connection.lock.writeLock().unlock();
}
}
@@ -397,7 +381,7 @@ public int executeUpdate(String queryString) throws SQLException {
}
- /**
+ /**
* executes a select query.
*
* @param queryString the query to send to the server
@@ -413,9 +397,9 @@ public ResultSet executeQuery(String queryString) throws SQLException {
* Releases this Statement
object's database and JDBC resources immediately instead of waiting for this
* to happen when it is automatically closed. It is generally good practice to release resources as soon as you are
* finished with them to avoid tying up database resources.
- *
+ *
* Calling the method close
on a Statement
object that is already closed has no effect.
- *
+ *
* Note: When a Statement
object is closed, its current ResultSet
object, if one
* exists, is also closed.
*
@@ -432,10 +416,12 @@ public void close() throws SQLException {
// immediately garbage collected
cachedResultSets.clear();
if (isStreaming()) {
- synchronized (protocol) {
- // Skip all outstanding result sets
- while(getMoreResults(true)) {
+ connection.lock.writeLock().lock();
+ try {
+ while (getMoreResults(true)) {
}
+ } finally {
+ connection.lock.writeLock().unlock();
}
}
isClosed = true;
@@ -461,7 +447,7 @@ public int getMaxFieldSize() throws SQLException {
/**
* Sets the limit for the maximum number of bytes that can be returned for character and binary column values in a
* ResultSet
object produced by this Statement
object.
- *
+ *
* This limit applies only to BINARY
, VARBINARY
, LONGVARBINARY
,
* CHAR
, VARCHAR
, NCHAR
, NVARCHAR
, LONGNVARCHAR
and
* LONGVARCHAR
fields. If the limit is exceeded, the excess data is silently discarded. For maximum
@@ -510,7 +496,7 @@ public void setMaxRows(final int max) throws SQLException {
/**
* Sets escape processing on or off. If escape scanning is on (the default), the driver will do escape substitution
* before sending the SQL statement to the database.
- *
+ *
* Note: Since prepared statements have usually been parsed prior to making this call, disabling escape processing
* for PreparedStatements
objects will have no effect.
*
@@ -555,13 +541,13 @@ public void setQueryTimeout(final int seconds) throws SQLException {
* Sets the inputStream that will be used for the next execute that uses
* "LOAD DATA LOCAL INFILE". The name specified as local file/URL will be
* ignored.
- *
+ *
* @param inputStream inputStream instance, that will be used to send data to server
*/
public void setLocalInfileInputStream(InputStream inputStream) {
- protocol.setLocalInfileInputStream(inputStream);
+ protocol.setLocalInfileInputStream(inputStream);
}
-
+
/**
* Cancels this Statement
object if both the DBMS and driver support aborting an SQL statement. This
* method can be used by one thread to cancel a statement that is being executed by another thread.
@@ -589,10 +575,10 @@ public void cancel() throws SQLException {
/**
* Retrieves the first warning reported by calls on this Statement
object. Subsequent
* Statement
object warnings will be chained to this SQLWarning
object.
- *
+ *
* The warning chain is automatically cleared each time a statement is (re)executed. This method may not be
* called on a closed Statement
object; doing so will cause an SQLException
to be thrown.
- *
+ *
* Note: If you are processing a ResultSet
object, any warnings associated with reads on that
* ResultSet
object will be chained on it rather than on the Statement
object that
* produced it.
@@ -628,7 +614,7 @@ public void clearWarnings() throws SQLException {
* cursor has the proper isolation level to support updates, the cursor's SELECT
statement should have
* the form SELECT FOR UPDATE
. If FOR UPDATE
is not present, positioned updates may
* fail.
- *
+ *
*
Note: By definition, the execution of positioned updates and deletes must be done by a different
* Statement
object than the one that generated the ResultSet
object being used for
* positioning. Also, cursor names must be unique within a connection.
@@ -657,10 +643,10 @@ public Connection getConnection() throws SQLException {
* Moves to this Statement
object's next result, deals with any current ResultSet
* object(s) according to the instructions specified by the given flag, and returns true
if the next
* result is a ResultSet
object.
- *
+ *
* There are no more results when the following is true:
// stmt is a Statement object
* ((stmt.getMoreResults(current) == false) && (stmt.getUpdateCount() == -1))
- *
+ *
*
* @param current one of the following Statement
constants indicating what should happen to current
* ResultSet
objects obtained using the method getResultSet
:
@@ -686,7 +672,7 @@ public boolean getMoreResults(final int current) throws SQLException {
/**
* Retrieves any auto-generated keys created as a result of executing this Statement
object. If this
* Statement
object did not generate any keys, an empty ResultSet
object is returned.
- *
+ *
* Note: If the columns which represent the auto-generated keys were not specified, the JDBC driver
* implementation will determine the columns which best represent the auto-generated keys.
*
@@ -699,16 +685,16 @@ public boolean getMoreResults(final int current) throws SQLException {
* @since 1.4
*/
public ResultSet getGeneratedKeys() throws SQLException {
- if (batchResultSet != null) {
- return batchResultSet;
- }
+ if (batchResultSet != null) {
+ return batchResultSet;
+ }
if (queryResult != null && queryResult.getResultSetType() == ResultSetType.MODIFY) {
long insertId = ((ModifyQueryResult)queryResult).getInsertId();
if (insertId == 0) {
- return MySQLResultSet.createEmptyGeneratedKeysResultSet(connection);
+ return MySQLResultSet.createEmptyGeneratedKeysResultSet(connection);
}
int updateCount = getUpdateCount();
-
+
return MySQLResultSet.createGeneratedKeysResultSet(insertId, updateCount, connection);
}
return MySQLResultSet.EMPTY;
@@ -763,7 +749,7 @@ public int executeUpdate(final String sql, final int autoGeneratedKeys) throws S
* @since 1.4
*/
public int executeUpdate(final String sql, final int[] columnIndexes) throws SQLException {
- return executeUpdate(sql);
+ return executeUpdate(sql);
}
/**
@@ -796,11 +782,11 @@ public int executeUpdate(final String sql, final String[] columnNames) throws SQ
* auto-generated keys should be made available for retrieval. The driver will ignore this signal if the SQL
* statement is not an INSERT
statement, or an SQL statement able to return auto-generated keys (the
* list of such statements is vendor-specific).
- *
+ *
* In some (uncommon) situations, a single SQL statement may return multiple result sets and/or update counts.
* Normally you can ignore this unless you are (1) executing a stored procedure that you know may return multiple
* results or (2) you are dynamically executing an unknown SQL string.
- *
+ *
* The execute
method executes an SQL statement and indicates the form of the first result. You must
* then use the methods getResultSet
or getUpdateCount
to retrieve the result, and
* getMoreResults
to move to any subsequent result(s).
@@ -833,11 +819,11 @@ public boolean execute(final String sql, final int autoGeneratedKeys) throws SQL
* indexes of the columns in the target table that contain the auto-generated keys that should be made available.
* The driver will ignore the array if the SQL statement is not an INSERT
statement, or an SQL
* statement able to return auto-generated keys (the list of such statements is vendor-specific).
- *
+ *
* Under some (uncommon) situations, a single SQL statement may return multiple result sets and/or update counts.
* Normally you can ignore this unless you are (1) executing a stored procedure that you know may return multiple
* results or (2) you are dynamically executing an unknown SQL string.
- *
+ *
* The execute
method executes an SQL statement and indicates the form of the first result. You must
* then use the methods getResultSet
or getUpdateCount
to retrieve the result, and
* getMoreResults
to move to any subsequent result(s).
@@ -867,11 +853,11 @@ public boolean execute(final String sql, final int[] columnIndexes) throws SQLEx
* names of the columns in the target table that contain the auto-generated keys that should be made available. The
* driver will ignore the array if the SQL statement is not an INSERT
statement, or an SQL statement
* able to return auto-generated keys (the list of such statements is vendor-specific).
- *
+ *
* In some (uncommon) situations, a single SQL statement may return multiple result sets and/or update counts.
* Normally you can ignore this unless you are (1) executing a stored procedure that you know may return multiple
* results or (2) you are dynamically executing an unknown SQL string.
- *
+ *
* The execute
method executes an SQL statement and indicates the form of the first result. You must
* then use the methods getResultSet
or getUpdateCount
to retrieve the result, and
* getMoreResults
to move to any subsequent result(s).
@@ -925,18 +911,18 @@ public boolean isClosed() throws SQLException {
* Requests that a Statement
be pooled or not pooled. The value specified is a hint to the statement
* pool implementation indicating whether the applicaiton wants the statement to be pooled. It is up to the
* statement pool manager as to whether the hint is used.
- *
+ *
* The poolable value of a statement is applicable to both internal statement caches implemented by the driver and
* external statement caches implemented by application servers and other applications.
- *
+ *
* By default, a Statement
is not poolable when created, and a PreparedStatement
and
* CallableStatement
are poolable when created.
- *
+ *
*
* @param poolable requests that the statement be pooled if true and that the statement not be pooled if false
- *
+ *
* @throws java.sql.SQLException if this method is called on a closed Statement
- *
+ *
* @since 1.6
*/
public void setPoolable(final boolean poolable) throws SQLException {
@@ -945,15 +931,15 @@ public void setPoolable(final boolean poolable) throws SQLException {
/**
* Returns a value indicating whether the Statement
is poolable or not.
- *
+ *
*
* @return true
if the Statement
is poolable; false
otherwise
- *
+ *
* @throws java.sql.SQLException if this method is called on a closed Statement
- *
+ *
* @see java.sql.Statement#setPoolable(boolean) setPoolable(boolean)
* @since 1.6
- *
+ *
*/
public boolean isPoolable() throws SQLException {
return false;
@@ -976,21 +962,22 @@ public int getUpdateCount() throws SQLException {
private boolean getMoreResults(boolean streaming) throws SQLException {
+ connection.lock.writeLock().lock();
try {
- synchronized(protocol) {
- if (queryResult != null) {
- queryResult.close();
- }
-
- queryResult = protocol.getMoreResults(streaming);
- if(queryResult == null) return false;
- warningsCleared = false;
- connection.reenableWarnings();
- return true;
+ if (queryResult != null) {
+ queryResult.close();
}
+
+ queryResult = protocol.getMoreResults(streaming);
+ if(queryResult == null) return false;
+ warningsCleared = false;
+ connection.reenableWarnings();
+ return true;
} catch (QueryException e) {
SQLExceptionMapper.throwException(e, connection, this);
return false;
+ } finally {
+ connection.lock.writeLock().unlock();
}
}
@@ -998,10 +985,10 @@ private boolean getMoreResults(boolean streaming) throws SQLException {
* Moves to this Statement
object's next result, returns true
if it is a
* ResultSet
object, and implicitly closes any current ResultSet
object(s) obtained with
* the method getResultSet
.
- *
+ *
* There are no more results when the following is true: // stmt is a Statement object
* ((stmt.getMoreResults() == false) && (stmt.getUpdateCount() == -1))
- *
+ *
* @return true
if the next result is a ResultSet
object; false
if it is an
* update count or there are no more results
* @throws java.sql.SQLException if a database access error occurs or this method is called on a closed
@@ -1009,13 +996,13 @@ private boolean getMoreResults(boolean streaming) throws SQLException {
* @see #execute
*/
public boolean getMoreResults() throws SQLException {
- if (!isStreaming()) {
+ if (!isStreaming()) {
/* return pre-cached result set, if available */
if(cachedResultSets.isEmpty()) {
queryResult = null;
return false;
}
-
+
Object o = cachedResultSets.remove();
if (o instanceof SQLException)
throw (SQLException)o;
@@ -1029,7 +1016,7 @@ public boolean getMoreResults() throws SQLException {
/**
* Gives the driver a hint as to the direction in which rows will be processed in ResultSet
objects
* created using this Statement
object. The default value is ResultSet.FETCH_FORWARD
.
- *
+ *
* Note that this method sets the default fetch direction for result sets generated by this Statement
* object. Each result set has its own methods for getting and setting its own fetch direction.
*
@@ -1074,7 +1061,7 @@ public int getFetchDirection() throws SQLException {
public void setFetchSize(final int rows) throws SQLException {
if (rows < 0 && rows != Integer.MIN_VALUE)
throw new SQLException("invalid fetch size");
- this.fetchSize = rows;
+ this.fetchSize = rows;
}
/**
@@ -1123,7 +1110,7 @@ public int getResultSetType() throws SQLException {
/**
* Adds the given SQL command to the current list of commmands for this Statement
object. The commands
* in this list can be executed as a batch by calling the method executeBatch
.
- *
+ *
*
* @param sql typically this is a SQL INSERT
or UPDATE
statement
* @throws java.sql.SQLException if a database access error occurs, this method is called on a closed
@@ -1139,29 +1126,29 @@ public void addBatch(final String sql) throws SQLException {
isInsertRewriteable(sql);
batchQueries.add(new MySQLQuery(sql));
}
-
+
/**
* Parses the sql string to understand whether it is compatible with rewritten batches.
* @param sql the sql string
*/
protected void isInsertRewriteable(String sql) {
- if (!isRewriteable) {
- return;
- }
- int index = getInsertIncipit(sql);
- if (index == -1) {
- isRewriteable = false;
- return;
- }
- if (firstRewrite == null) {
- firstRewrite = sql.substring(0, index);
- }
- boolean isRewrite = sql.startsWith(firstRewrite);
+ if (!isRewriteable) {
+ return;
+ }
+ int index = getInsertIncipit(sql);
+ if (index == -1) {
+ isRewriteable = false;
+ return;
+ }
+ if (firstRewrite == null) {
+ firstRewrite = sql.substring(0, index);
+ }
+ boolean isRewrite = sql.startsWith(firstRewrite);
if (isRewrite) {
- isRewriteable = isRewriteable && true;
+ isRewriteable = isRewriteable && true;
}
}
-
+
/**
* Parses the input string to understand if it is an INSERT statement.
* Returns the position of the round bracket after the VALUE(S) SQL keyword,
@@ -1172,36 +1159,36 @@ protected void isInsertRewriteable(String sql) {
* or -1 if it cannot be parsed as an INSERT statement
*/
protected int getInsertIncipit(String sql) {
- String sqlUpper = sql.toUpperCase();
-
- if (! sqlUpper.startsWith("INSERT"))
- return -1;
-
- int idx = sqlUpper.indexOf(" VALUE");
- int startBracket = sqlUpper.indexOf("(", idx);
- int endBracket = sqlUpper.indexOf(")", startBracket);
-
- // Check for semicolons. Allow them inside the VALUES() brackets, otherwise return -1
- // there can be multiple, so let's loop through them
-
- int semicolonPos = sqlUpper.indexOf(';');
-
- while (semicolonPos > -1)
- {
- if (semicolonPos < startBracket || semicolonPos > endBracket)
- return -1;
-
- semicolonPos = sqlUpper.indexOf(';', semicolonPos + 1);
- }
-
- return startBracket;
+ String sqlUpper = sql.toUpperCase();
+
+ if (! sqlUpper.startsWith("INSERT"))
+ return -1;
+
+ int idx = sqlUpper.indexOf(" VALUE");
+ int startBracket = sqlUpper.indexOf("(", idx);
+ int endBracket = sqlUpper.indexOf(")", startBracket);
+
+ // Check for semicolons. Allow them inside the VALUES() brackets, otherwise return -1
+ // there can be multiple, so let's loop through them
+
+ int semicolonPos = sqlUpper.indexOf(';');
+
+ while (semicolonPos > -1)
+ {
+ if (semicolonPos < startBracket || semicolonPos > endBracket)
+ return -1;
+
+ semicolonPos = sqlUpper.indexOf(';', semicolonPos + 1);
+ }
+
+ return startBracket;
}
/**
* Empties this Statement
object's current list of SQL commands.
- *
+ *
*
* @throws java.sql.SQLException if a database access error occurs, this method is called on a closed
* Statement
or the driver does not support batch updates
@@ -1226,17 +1213,17 @@ public void clearBatch() throws SQLException {
* count giving the number of rows in the database that were affected by the command's execution A value of
* SUCCESS_NO_INFO
-- indicates that the command was processed successfully but that the number of rows
* affected is unknown
- *
+ *
* If one of the commands in a batch update fails to execute properly, this method throws a
* BatchUpdateException
, and a JDBC driver may or may not continue to process the remaining commands in
* the batch. However, the driver's behavior must be consistent with a particular DBMS, either always continuing to
* process commands or never continuing to process commands. If the driver continues processing after a failure,
* the array returned by the method BatchUpdateException.getUpdateCounts
will contain as many elements
* as there are commands in the batch, and at least one of the elements will be the following:
- *
+ *
* A value of EXECUTE_FAILED
-- indicates that the command failed to execute successfully and
* occurs only if a driver continues to process commands after a command fails
- *
+ *
* The possible implementations and return values have been modified in the Java 2 SDK, Standard Edition, version
* 1.3 to accommodate the option of continuing to proccess commands in a batch update after a
* BatchUpdateException
obejct has been thrown.
@@ -1258,39 +1245,36 @@ public int[] executeBatch() throws SQLException {
int[] ret = new int[batchQueries.size()];
int i = 0;
MySQLResultSet rs = null;
-
- boolean allowMultiQueries = "true".equals(getProtocol().getInfo().getProperty("allowMultiQueries"));
- boolean rewriteBatchedStatements = "true".equals(getProtocol().getInfo().getProperty("rewriteBatchedStatements"));
- if (rewriteBatchedStatements) allowMultiQueries=true;
+ connection.lock.writeLock().lock();
try {
- synchronized (this.protocol) {
- if (allowMultiQueries) {
- int size = batchQueries.size();
- boolean rewrittenBatch = isRewriteable && rewriteBatchedStatements;
- MySQLStatement ps = (MySQLStatement) connection.createStatement();
- ps.execute(batchQueries, rewrittenBatch, rewrittenBatch?firstRewrite.length():0);
- return rewrittenBatch?getUpdateCountsForReWrittenBatch(ps, size):getUpdateCounts(ps, size);
- } else {
- for(; i < batchQueries.size(); i++) {
- execute(batchQueries.get(i));
- int updateCount = getUpdateCount();
- if (updateCount == -1) {
- ret[i] = SUCCESS_NO_INFO;
- } else {
- ret[i] = updateCount;
- }
- if (i == 0) {
- rs = (MySQLResultSet)getGeneratedKeys();
- } else {
- rs = rs.joinResultSets((MySQLResultSet)getGeneratedKeys());
- }
- }
- }
- }
+ if (getProtocol().getOptions().allowMultiQueries || getProtocol().getOptions().rewriteBatchedStatements) {
+ int size = batchQueries.size();
+ boolean rewrittenBatch = isRewriteable && getProtocol().getOptions().rewriteBatchedStatements;
+ MySQLStatement ps = (MySQLStatement) connection.createStatement();
+ ps.execute(batchQueries, rewrittenBatch, rewrittenBatch ? firstRewrite.length() : 0);
+ return rewrittenBatch?getUpdateCountsForReWrittenBatch(ps, size):getUpdateCounts(ps, size);
+ } else {
+ for(; i < batchQueries.size(); i++) {
+ execute(batchQueries.get(i));
+ int updateCount = getUpdateCount();
+ if (updateCount == -1) {
+ ret[i] = SUCCESS_NO_INFO;
+ } else {
+ ret[i] = updateCount;
+ }
+ if (i == 0) {
+ rs = (MySQLResultSet)getGeneratedKeys();
+ } else {
+ rs = rs.joinResultSets((MySQLResultSet)getGeneratedKeys());
+ }
+ }
+ }
+
} catch (SQLException sqle) {
- throw new BatchUpdateException(sqle.getMessage(), sqle.getSQLState(), sqle.getErrorCode(), Arrays.copyOf(ret, i), sqle);
+ throw new BatchUpdateException(sqle.getMessage(), sqle.getSQLState(), sqle.getErrorCode(), Arrays.copyOf(ret, i), sqle);
} finally {
- clearBatch();
+ connection.lock.writeLock().unlock();
+ clearBatch();
}
batchResultSet = rs;
return ret;
@@ -1302,8 +1286,8 @@ public int[] executeBatch() throws SQLException {
* @param statement the rewritten statement
* @return an array of update counts containing one element for each command in the batch.
* The elements of the array are ordered according to the order in which commands were added to the batch.
- * @param size
- * @throws SQLException
+ * @param size the number of batch statement
+ * @throws SQLException if the connection has interruption
*/
protected int[] getUpdateCounts(Statement statement, int size) throws SQLException {
int[] result = new int[size];
@@ -1332,7 +1316,7 @@ protected int[] getUpdateCountsForReWrittenBatch(Statement statement, int size)
/**
* Returns an object that implements the given interface to allow access to non-standard methods, or standard
* methods not exposed by the proxy.
- *
+ *
* If the receiver implements the interface then the result is the receiver or a proxy for the receiver. If the
* receiver is a wrapper and the wrapped object implements the interface then the result is the wrapped object or a
* proxy for the wrapped object. Otherwise return the the result of calling unwrap
recursively on the
@@ -1345,17 +1329,17 @@ protected int[] getUpdateCountsForReWrittenBatch(Statement statement, int size)
* @since 1.6
*/
@SuppressWarnings("unchecked")
- public T unwrap(final Class iface) throws SQLException {
- try {
- if (isWrapperFor(iface)) {
- return (T)this;
- } else {
- throw new SQLException("The receiver is not a wrapper and does not implement the interface");
- }
- } catch (Exception e) {
- throw new SQLException("The receiver is not a wrapper and does not implement the interface");
- }
- }
+ public T unwrap(final Class iface) throws SQLException {
+ try {
+ if (isWrapperFor(iface)) {
+ return (T)this;
+ } else {
+ throw new SQLException("The receiver is not a wrapper and does not implement the interface");
+ }
+ } catch (Exception e) {
+ throw new SQLException("The receiver is not a wrapper and does not implement the interface");
+ }
+ }
/**
* Returns true if this either implements the interface argument or is directly or indirectly a wrapper for an
@@ -1376,37 +1360,29 @@ public boolean isWrapperFor(final Class> interfaceOrWrapper) throws SQLExcepti
return interfaceOrWrapper.isInstance(this);
}
+ public void closeOnCompletion() throws SQLException {
+ // TODO Auto-generated method stub
- /**
- * returns the query result.
- *
- * @return the queryresult
- */
- protected QueryResult getQueryResult() {
- return queryResult;
}
- /**
- * sets the current query result
- *
- * @param result
- */
- protected void setQueryResult(final QueryResult result) {
- this.queryResult = result;
+ public boolean isCloseOnCompletion() throws SQLException {
+ // TODO Auto-generated method stub
+ return false;
}
- public void closeOnCompletion() throws SQLException {
- // TODO Auto-generated method stub
-
- }
-
- public boolean isCloseOnCompletion() throws SQLException {
- // TODO Auto-generated method stub
- return false;
- }
-
public static void unloadDriver() {
if (timer != null)
timer.cancel();
}
+
+ // Part of query prolog - check if connection is broken and reconnect
+ private void checkReconnectWithoutProxy() throws SQLException {
+ if (protocol.shouldReconnectWithoutProxy()) {
+ try {
+ protocol.connectWithoutProxy();
+ } catch (QueryException qe) {
+ SQLExceptionMapper.throwException(qe, connection, this);
+ }
+ }
+ }
}
diff --git a/src/main/java/org/mariadb/jdbc/MySQLXAResource.java b/src/main/java/org/mariadb/jdbc/MySQLXAResource.java
index 899d56eec..77641eec6 100644
--- a/src/main/java/org/mariadb/jdbc/MySQLXAResource.java
+++ b/src/main/java/org/mariadb/jdbc/MySQLXAResource.java
@@ -145,7 +145,7 @@ public boolean setTransactionTimeout(int timeout) throws XAException {
public void start(Xid xid, int flags) throws XAException {
if (flags != TMJOIN && flags != TMRESUME && flags != TMNOFLAGS)
throw new XAException(XAException.XAER_INVAL);
- if (flags == TMJOIN && "true".equalsIgnoreCase(connection.getPinGlobalTxToPhysicalConnection())) {
+ if (flags == TMJOIN && connection.getPinGlobalTxToPhysicalConnection()) {
flags = TMRESUME;
}
execute("XA START " + xidToString(xid) + " "+flagsToString(flags));
diff --git a/src/main/java/org/mariadb/jdbc/Version.java b/src/main/java/org/mariadb/jdbc/Version.java
index ee6596c97..1b4106f1e 100644
--- a/src/main/java/org/mariadb/jdbc/Version.java
+++ b/src/main/java/org/mariadb/jdbc/Version.java
@@ -1,7 +1,10 @@
package org.mariadb.jdbc;
public final class Version {
- public static final String build_time="20150706-1223";
- public static final String pomversion="1.1.10-SNAPSHOT";
+ public static final String version = "1.2.0-SNAPSHOT";
+ public static final int majorVersion = 1;
+ public static final int minorVersion = 2;
+ public static final int patchVersion = 0;
+ public static final String qualifier = "SNAPSHOT";
}
\ No newline at end of file
diff --git a/src/main/java/org/mariadb/jdbc/internal/common/DefaultOptions.java b/src/main/java/org/mariadb/jdbc/internal/common/DefaultOptions.java
new file mode 100644
index 000000000..739318692
--- /dev/null
+++ b/src/main/java/org/mariadb/jdbc/internal/common/DefaultOptions.java
@@ -0,0 +1,448 @@
+/*
+MariaDB Client for Java
+
+Copyright (c) 2012 Monty Program Ab.
+
+This library is free software; you can redistribute it and/or modify it under
+the terms of the GNU Lesser General Public License as published by the Free
+Software Foundation; either version 2.1 of the License, or (at your option)
+any later version.
+
+This library 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 Lesser General Public License
+for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this library; if not, write to Monty Program Ab info@montyprogram.com.
+
+This particular MariaDB Client for Java file is work
+derived from a Drizzle-JDBC. Drizzle-JDBC file which is covered by subject to
+the following copyright and notice provisions:
+
+Copyright (c) 2009-2011, Marcus Eriksson
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+Redistributions of source code must retain the above copyright notice, this list
+of conditions and the following disclaimer.
+
+Redistributions in binary form must reproduce the above copyright notice, this
+list of conditions and the following disclaimer in the documentation and/or
+other materials provided with the distribution.
+
+Neither the name of the driver nor the names of its contributors may not be
+used to endorse or promote products derived from this software without specific
+prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+OF SUCH DAMAGE.
+*/
+
+package org.mariadb.jdbc.internal.common;
+
+import java.lang.invoke.WrongMethodTypeException;
+import java.util.Properties;
+
+public enum DefaultOptions {
+ /**
+ * Database user name
+ */
+ USER("user", "1.0.0"),
+ /**
+ * Password of database user
+ */
+ PASSWORD("password", "1.0.0"),
+
+ CONNECT_TIMOUT("connectTimeout", (Integer) null, new Integer(0), Integer.MAX_VALUE, "1.1.8"),
+
+ /**
+ * On Windows, specify named pipe name to connect to mysqld.exe
+ */
+ PIPE("pipe", "1.1.3"),
+
+ /**
+ * Allows to connect to database via Unix domain socket, if server allows it. The value is the path of Unix domain socket, i.e "socket" database parameter
+ */
+ LOCAL_SOCKET("localSocket", "1.1.4"),
+
+ /**
+ * Allowed to connect database via shared memory, if server allows it. The value is base name of the shared memory
+ */
+ SHARED_MEMORY("sharedMemory", "1.1.4"),
+
+ /**
+ * Sets corresponding option on the connection socket
+ */
+ TCP_NO_DELAY("tcpNoDelay", Boolean.FALSE, "1.0.0"),
+
+ /**
+ * Sets corresponding option on the connection socket
+ */
+ TCP_ABORTIVE_CLOSE("tcpAbortiveClose", Boolean.FALSE, "1.1.1"),
+
+ /**
+ * Hostname or IP address to bind the connection socket to a local (UNIX domain) socket.
+ */
+ LOCAL_SOCKET_ADDRESS("localSocketAddress", "1.1.8"),
+
+ /**
+ * Defined the network socket timeout (SO_TIMEOUT) in milliseconds.
+ * 0 (default) disable this timeout
+ */
+ SOCKET_TIMEOUT("socketTimeout", new Integer[]{10000, null, null, null}, new Integer(0), Integer.MAX_VALUE, "1.1.8"),
+
+ /**
+ * Session timeout is defined by the wait_timeout server variable.
+ * Setting interactiveClient to true will tell server to use the interactive_timeout server variable
+ */
+ INTERACTIVE_CLIENT("interactiveClient", Boolean.FALSE, "1.1.8"),
+
+ /**
+ * If set to 'true', exception thrown during query execution contain query string
+ */
+ DUMP_QUERY_ON_EXCEPTION("dumpQueriesOnException", Boolean.FALSE, "1.1.0"),
+
+ /**
+ * Metadata ResultSetMetaData.getTableName() return the physical table name.
+ * "useOldAliasMetadataBehavior" permit to activate the legacy code that send the table alias if set.
+ */
+ USE_OLD_ALIAS_METADATA_BEHAVIOR("useOldAliasMetadataBehavior", Boolean.FALSE, "1.1.9"),
+
+ /**
+ * var=value pairs separated by comma, mysql session variables, set upon establishing successful connection
+ */
+ SESSION_VARIABLES("sessionVariables", "1.1.0"),
+
+ /**
+ * The database precised in url will be created if doesn't exist
+ */
+ CREATE_DATABASE_IF_NOT_EXISTS("createDatabaseIfNotExist", Boolean.FALSE, "1.1.8"),
+
+ /**
+ * Defined the server time zone.
+ * to use only if jre server as a different time implementation of the server.
+ * (best to have the same server time zone when possible)
+ */
+ SERVER_TIMEZONE("serverTimezone", "1.1.8"),
+ /**
+ * DatabaseMetaData use current catalog if null
+ */
+ NULL_CATALOG_MEANS_CURRENT("nullCatalogMeansCurrent", Boolean.TRUE, "1.1.8"),
+
+ /**
+ * Datatype mapping flag, handle MySQL Tiny as BIT(boolean)
+ */
+ TINY_INT_IS_BIT("tinyInt1isBit", Boolean.TRUE, "1.0.0"),
+
+ /**
+ * Year is date type, rather than numerical
+ */
+ YEAR_IS_DATE_TYPE("yearIsDateType", Boolean.TRUE, "1.0.0"),
+
+ /**
+ * Force SSL on connection
+ */
+ USE_SSL("useSSL", Boolean.FALSE, "1.1.0"),
+
+ /**
+ * allow compression in MySQL Protocol
+ */
+ USER_COMPRESSION("useCompression", Boolean.FALSE, "1.0.0"),
+
+ /**
+ * Allows multiple statements in single executeQuery
+ */
+ ALLOW_MULTI_QUERIES("allowMultiQueries", Boolean.FALSE, "1.0.0"),
+
+ /**
+ * rewrite batchedStatement to have only one server call
+ */
+ REWRITE_BATCHED_STATEMENTS("rewriteBatchedStatements", Boolean.FALSE, "1.1.8"),
+
+ /**
+ * Sets corresponding option on the connection socket
+ */
+ TCP_KEEP_ALIVE("tcpKeepAlive", Boolean.FALSE, "1.0.0"),
+
+ /**
+ * set buffer size for TCP buffer (SO_RCVBUF)
+ */
+ TCP_RCV_BUF("tcpRcvBuf", (Integer) null, new Integer(0), Integer.MAX_VALUE, "1.0.0"),
+
+ /**
+ * set buffer size for TCP buffer (SO_SNDBUF)
+ */
+ TCP_SND_BUF("tcpSndBuf", (Integer) null, new Integer(0), Integer.MAX_VALUE, "1.0.0"),
+
+ /**
+ * to use custom socket factory, set it to full name of the class that implements javax.net.SocketFactory
+ */
+ SOCKET_FACTORY("socketFactory", "1.0.0"),
+ PIN_GLOBAL_TX_TO_PHYSICAL_CONNECTION("pinGlobalTxToPhysicalConnection", Boolean.FALSE, "1.1.8"),
+
+ /**
+ * When using SSL, do not check server's certificate
+ */
+ TRUST_SERVER_CERTIFICATE("trustServerCertificate", Boolean.FALSE, "1.1.1"),
+
+ /**
+ * Server's certificatem in DER form, or server's CA certificate. Can be used in one of 3 forms, sslServerCert=/path/to/cert.pem (full path to certificate), sslServerCert=classpath:relative/cert.pem (relative to current classpath), or as verbatim DER-encoded certificate string "------BEGING CERTIFICATE-----"
+ */
+ SERVER_SSL_CERT("serverSslCert", "1.1.3"),
+
+ /**
+ * Correctly handle subsecond precision in timestamps (feature available with MariaDB 5.3 and later).May confuse 3rd party components (Hibernated)
+ */
+ USE_FRACTIONAL_SECONDS("useFractionalSeconds", Boolean.TRUE, "1.0.0"),
+
+ /**
+ * Driver must recreateConnection after a failover
+ */
+ AUTO_RECONNECT("autoReconnect", Boolean.FALSE, "1.2.0"),
+
+ /**
+ * After a master failover and no other master found, back on a read-only host ( throw exception if not)
+ */
+ FAIL_ON_READ_ONLY("failOnReadOnly", Boolean.FALSE, "1.2.0"),
+
+ /**
+ * If autoReconnect is enabled, the initial time to wait between re-connect attempts (in seconds, defaults to 2)
+ */
+ INITIAL_TIMEOUT("initialTimeout", new Integer(2), new Integer(0), Integer.MAX_VALUE, "1.2.0"),
+
+ /**
+ * Number of seconds to issue before falling back to master when failed over (when using multi-host failover).
+ * Whichever condition is met first, 'queriesBeforeRetryMaster' or 'secondsBeforeRetryMaster' will cause an
+ * attempt to be made to reconnect to the master. Defaults to 50
+ */
+ SECONDS_BEFORE_RETRY_MASTER("secondsBeforeRetryMaster", new Integer(50), new Integer(0), Integer.MAX_VALUE, "1.2.0"),
+
+ /**
+ * Number of queries to issue before falling back to master when failed over (when using multi-host failover).
+ * Whichever condition is met first, 'queriesBeforeRetryMaster' or 'secondsBeforeRetryMaster' will cause an
+ * attempt to be made to reconnect to the master. Defaults to 30
+ */
+ QUERY_BEFORE_RETRY_MASTER("queriesBeforeRetryMaster", new Integer(30), new Integer(0), Integer.MAX_VALUE, "1.2.0"),
+
+ /**
+ * When using loadbalancing, the number of times the driver should cycle through available hosts, attempting to connect.
+ * Between cycles, the driver will pause for 250ms if no servers are available.
+ */
+ RETRY_ALL_DOWN("retriesAllDown", new Integer(120), new Integer(0), Integer.MAX_VALUE, "1.2.0"),
+
+ /**
+ * When using failover, the number of times the driver should cycle silently through available hosts, attempting to connect.
+ * Between cycles, the driver will pause for 250ms if no servers are available.
+ * if set to 0, there will be no silent reconnection
+ */
+ FAILOVER_LOOP_RETRIES("failoverLoopRetries", new Integer(120), new Integer(0), Integer.MAX_VALUE, "1.2.0"),
+
+
+ /**
+ * When in multiple hosts, after this time in second without used, verification that the connections havn't been lost.
+ * When 0, no verification will be done. Defaults to 120
+ */
+ VALID_CONNECTION_TIMEOUT("validConnectionTimeout", new Integer(120), new Integer(0), Integer.MAX_VALUE, "1.2.0"),
+
+ /**
+ * time in second a server is blacklisted after a connection failure. default to 50s
+ */
+ LOAD_BALANCE_BLACKLIST_TIMEOUT("loadBalanceBlacklistTimeout", new Integer(50), new Integer(0), Integer.MAX_VALUE, "1.2.0");
+
+ protected final String name;
+ protected final Object objType;
+ protected final Object defaultValue;
+ protected final Object minValue;
+ protected final Object maxValue;
+ protected final String implementationVersion;
+ protected Object value = null;
+
+ DefaultOptions(String name, String implementationVersion) {
+ this.name = name;
+ this.implementationVersion = implementationVersion;
+ objType = String.class;
+ defaultValue = null;
+ minValue = null;
+ maxValue = null;
+ }
+
+ DefaultOptions(String name, Boolean defaultValue, String implementationVersion) {
+ this.name = name;
+ this.objType = Boolean.class;
+ this.defaultValue = defaultValue;
+ this.implementationVersion = implementationVersion;
+ minValue = null;
+ maxValue = null;
+ }
+
+ DefaultOptions(String name, Integer defaultValue, Integer minValue, Integer maxValue, String implementationVersion) {
+ this.name = name;
+ this.objType = Integer.class;
+ this.defaultValue = defaultValue;
+ this.minValue = minValue;
+ this.maxValue = maxValue;
+ this.implementationVersion = implementationVersion;
+ }
+
+
+ DefaultOptions(String name, Integer[] defaultValue, Integer minValue, Integer maxValue, String implementationVersion) {
+ this.name = name;
+ this.objType = Integer.class;
+ this.defaultValue = defaultValue;
+ this.minValue = minValue;
+ this.maxValue = maxValue;
+ this.implementationVersion = implementationVersion;
+ }
+
+ private void setIntValue(Integer value) {
+ this.value = value;
+ }
+
+ private void setBooleanValue(Boolean value) {
+ this.value = value;
+ }
+
+ public int intValue() {
+ if (objType.equals(Integer.class)) {
+ if (value != null) return ((Integer) value).intValue();
+ else return ((Integer) defaultValue).intValue();
+ } else
+ throw new WrongMethodTypeException("Method " + name + " is of type " + objType + " intValue() does not apply");
+ }
+
+ public boolean boolValue() {
+ if (objType.equals(Boolean.class)) {
+ if (value != null) return ((Boolean) value).booleanValue();
+ else return ((Boolean) defaultValue).booleanValue();
+ } else
+ throw new WrongMethodTypeException("Method " + name + " is of type " + objType + " intValue() does not apply");
+ }
+ public String stringValue() {
+ if (objType.equals(String.class)) {
+ return ((String) value);
+ } else
+ throw new WrongMethodTypeException("Method " + name + " is of type " + objType + " intValue() does not apply");
+ }
+
+ public static Options defaultValues(UrlHAMode haMode) {
+ return parse(haMode, "", new Properties());
+ }
+
+ public static Options parse(UrlHAMode haMode, String urlParameters, Options options) {
+ Properties prop = new Properties();
+ return parse(haMode, urlParameters, prop, options);
+ }
+
+ public static Options parse(UrlHAMode haMode, String urlParameters, Properties properties) {
+ return parse(haMode, urlParameters, properties, null);
+ }
+
+ public static Options addProperty(UrlHAMode haMode, String name, String value, Options options) {
+ Properties additionnalProperties = new Properties();
+ additionnalProperties.put(name, value);
+ return parse(haMode, additionnalProperties, options);
+ }
+
+ public static Options addProperty(UrlHAMode haMode, Properties additionnalProperties, Options options) {
+ return parse(haMode, additionnalProperties, options);
+ }
+
+ private static Options parse(UrlHAMode haMode, String urlParameters, Properties properties, Options options) {
+ if (!"".equals(urlParameters)) {
+ String[] parameters = urlParameters.split("&");
+ for (String parameter : parameters) {
+ int pos = parameter.indexOf('=');
+ if (pos == -1) {
+ throw new IllegalArgumentException("Invalid connection URL, expected key=value pairs, found " + parameter);
+ }
+ properties.setProperty(parameter.substring(0, pos), parameter.substring(pos + 1));
+ }
+ }
+ return parse(haMode, properties, options);
+ }
+
+ private static Options parse(UrlHAMode haMode, Properties properties, Options options) {
+ boolean initial = false;
+ if (options==null) {
+ options = new Options();
+ initial = true;
+ }
+
+ try {
+ for (DefaultOptions o : DefaultOptions.values()) {
+
+ String propertieValue = properties.getProperty(o.name);
+ if (propertieValue == null && o.name.equals("createDatabaseIfNotExist"))
+ propertieValue = properties.getProperty("createDB");
+
+ if (propertieValue != null) {
+ if (o.objType.equals(String.class)) {
+ Options.class.getField(o.name).set(options, propertieValue);
+ } else if (o.objType.equals(Boolean.class)) {
+ String value = propertieValue.toLowerCase();
+ if ("1".equals(value)) value = "true";
+ else if ("0".equals(value)) value = "false";
+ if (!"true".equals(value) && !"false".equals(value))
+ throw new IllegalArgumentException("Optional parameter " + o.name + " must be boolean (true/false or 0/1) was \"" + propertieValue + "\"");
+ Options.class.getField(o.name).set(options, Boolean.valueOf(value));
+ } else if (o.objType.equals(Integer.class)) {
+ try {
+ Integer value = Integer.parseInt(propertieValue);
+ if (value.compareTo((Integer) o.minValue) < 0 || value.compareTo((Integer) o.maxValue) > 0)
+ throw new IllegalArgumentException("Optional parameter " + o.name + " must be greater or equal to " + o.minValue + ((((Integer) o.maxValue).intValue() != Integer.MAX_VALUE) ? " and smaller than " + o.maxValue : " ") + ", was \"" + propertieValue + "\"");
+ Options.class.getField(o.name).set(options, value);
+ } catch (NumberFormatException n) {
+ throw new IllegalArgumentException("Optional parameter " + o.name + " must be Integer, was \"" + propertieValue + "\"");
+ }
+ }
+ } else {
+ if (initial) {
+ if (o.defaultValue instanceof Integer[]) {
+ Options.class.getField(o.name).set(options, ((Integer[])o.defaultValue)[haMode.ordinal()]);
+ } else Options.class.getField(o.name).set(options, o.defaultValue);
+ }
+ }
+ }
+ } catch (NoSuchFieldException | IllegalAccessException n) {
+ n.printStackTrace();
+ } catch (SecurityException s) {
+ //only for jws, so never thrown
+ throw new IllegalArgumentException("Security too restrictive : " + s.getMessage());
+ }
+ return options;
+ }
+
+ public static Properties getProperties(Options options) {
+ Properties prop = new Properties();
+ try {
+ for (DefaultOptions o : DefaultOptions.values()) {
+ Object obj = Options.class.getField(o.name).get(options);
+ if (obj != null) {
+ prop.put(o.name, String.valueOf(Options.class.getField(o.name).get(options)).toString());
+ }
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return prop;
+ }
+
+
+ public static String getProperties(String optionName, Options options) {
+ try {
+ return String.valueOf(Options.class.getField(optionName).get(options));
+ } catch (Exception e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/mariadb/jdbc/internal/common/Options.java b/src/main/java/org/mariadb/jdbc/internal/common/Options.java
new file mode 100644
index 000000000..f49a7ce52
--- /dev/null
+++ b/src/main/java/org/mariadb/jdbc/internal/common/Options.java
@@ -0,0 +1,144 @@
+/*
+MariaDB Client for Java
+
+Copyright (c) 2012 Monty Program Ab.
+
+This library is free software; you can redistribute it and/or modify it under
+the terms of the GNU Lesser General Public License as published by the Free
+Software Foundation; either version 2.1 of the License, or (at your option)
+any later version.
+
+This library 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 Lesser General Public License
+for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this library; if not, write to Monty Program Ab info@montyprogram.com.
+
+This particular MariaDB Client for Java file is work
+derived from a Drizzle-JDBC. Drizzle-JDBC file which is covered by subject to
+the following copyright and notice provisions:
+
+Copyright (c) 2009-2011, Marcus Eriksson
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+Redistributions of source code must retain the above copyright notice, this list
+of conditions and the following disclaimer.
+
+Redistributions in binary form must reproduce the above copyright notice, this
+list of conditions and the following disclaimer in the documentation and/or
+other materials provided with the distribution.
+
+Neither the name of the driver nor the names of its contributors may not be
+used to endorse or promote products derived from this software without specific
+prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+OF SUCH DAMAGE.
+*/
+
+package org.mariadb.jdbc.internal.common;
+
+public class Options {
+ //standard options
+ public String user;
+ public String password;
+
+ //divers
+ public boolean trustServerCertificate;
+ public String serverSslCert;
+ public boolean useFractionalSeconds;
+ public boolean pinGlobalTxToPhysicalConnection;
+ public String socketFactory;
+ public Integer connectTimeout;
+ public String pipe;
+ public String localSocket;
+ public String sharedMemory;
+ public boolean tcpNoDelay;
+ public boolean tcpKeepAlive;
+ public Integer tcpRcvBuf;
+ public Integer tcpSndBuf;
+ public boolean tcpAbortiveClose;
+ public String localSocketAddress;
+ public Integer socketTimeout;
+ public boolean allowMultiQueries;
+ public boolean rewriteBatchedStatements;
+ public boolean useCompression;
+ public boolean interactiveClient;
+ public boolean useSSL;
+ public String sessionVariables;
+ public boolean tinyInt1isBit;
+ public boolean yearIsDateType;
+ public boolean createDatabaseIfNotExist;
+ public String serverTimezone;
+ public boolean nullCatalogMeansCurrent;
+ public boolean dumpQueriesOnException;
+ public boolean useOldAliasMetadataBehavior;
+
+ //HA options
+ public boolean autoReconnect;
+ public boolean failOnReadOnly;
+ public int initialTimeout;
+ public int secondsBeforeRetryMaster;
+ public int queriesBeforeRetryMaster;
+ public int retriesAllDown;
+ public int validConnectionTimeout;
+ public int loadBalanceBlacklistTimeout;
+ public int failoverLoopRetries;
+
+ @Override
+ public String toString() {
+ return "Options{" +
+ "user='" + user + '\'' +
+ ", password='" + password + '\'' +
+ ", trustServerCertificate=" + trustServerCertificate +
+ ", serverSslCert='" + serverSslCert + '\'' +
+ ", useFractionalSeconds=" + useFractionalSeconds +
+ ", pinGlobalTxToPhysicalConnection=" + pinGlobalTxToPhysicalConnection +
+ ", socketFactory='" + socketFactory + '\'' +
+ ", connectTimeout=" + connectTimeout +
+ ", pipe='" + pipe + '\'' +
+ ", localSocket='" + localSocket + '\'' +
+ ", sharedMemory='" + sharedMemory + '\'' +
+ ", tcpNoDelay=" + tcpNoDelay +
+ ", tcpKeepAlive=" + tcpKeepAlive +
+ ", tcpRcvBuf=" + tcpRcvBuf +
+ ", tcpSndBuf=" + tcpSndBuf +
+ ", tcpAbortiveClose=" + tcpAbortiveClose +
+ ", localSocketAddress='" + localSocketAddress + '\'' +
+ ", socketTimeout=" + socketTimeout +
+ ", allowMultiQueries=" + allowMultiQueries +
+ ", rewriteBatchedStatements=" + rewriteBatchedStatements +
+ ", useCompression=" + useCompression +
+ ", interactiveClient=" + interactiveClient +
+ ", useSSL=" + useSSL +
+ ", sessionVariables='" + sessionVariables + '\'' +
+ ", tinyInt1isBit=" + tinyInt1isBit +
+ ", yearIsDateType=" + yearIsDateType +
+ ", createDatabaseIfNotExist=" + createDatabaseIfNotExist +
+ ", serverTimezone='" + serverTimezone + '\'' +
+ ", nullCatalogMeansCurrent=" + nullCatalogMeansCurrent +
+ ", dumpQueriesOnException=" + dumpQueriesOnException +
+ ", useOldAliasMetadataBehavior=" + useOldAliasMetadataBehavior +
+ ", autoReconnect=" + autoReconnect +
+ ", failOnReadOnly=" + failOnReadOnly +
+ ", initialTimeout=" + initialTimeout +
+ ", secondsBeforeRetryMaster=" + secondsBeforeRetryMaster +
+ ", queriesBeforeRetryMaster=" + queriesBeforeRetryMaster +
+ ", retriesAllDown=" + retriesAllDown +
+ ", validConnectionTimeout=" + validConnectionTimeout +
+ ", loadBalanceBlacklistTimeout=" + loadBalanceBlacklistTimeout +
+ ", failoverLoopRetries=" + failoverLoopRetries +
+ '}';
+ }
+}
diff --git a/src/main/java/org/mariadb/jdbc/internal/common/ParameterConstant.java b/src/main/java/org/mariadb/jdbc/internal/common/ParameterConstant.java
new file mode 100644
index 000000000..94fb433f8
--- /dev/null
+++ b/src/main/java/org/mariadb/jdbc/internal/common/ParameterConstant.java
@@ -0,0 +1,53 @@
+package org.mariadb.jdbc.internal.common;
+/*
+MariaDB Client for Java
+
+Copyright (c) 2012 Monty Program Ab.
+
+This library is free software; you can redistribute it and/or modify it under
+the terms of the GNU Lesser General Public License as published by the Free
+Software Foundation; either version 2.1 of the License, or (at your option)
+any later version.
+
+This library 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 Lesser General Public License
+for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this library; if not, write to Monty Program Ab info@montyprogram.com.
+
+This particular MariaDB Client for Java file is work
+derived from a Drizzle-JDBC. Drizzle-JDBC file which is covered by subject to
+the following copyright and notice provisions:
+
+Copyright (c) 2009-2011, Marcus Eriksson
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+Redistributions of source code must retain the above copyright notice, this list
+of conditions and the following disclaimer.
+
+Redistributions in binary form must reproduce the above copyright notice, this
+list of conditions and the following disclaimer in the documentation and/or
+other materials provided with the distribution.
+
+Neither the name of the driver nor the names of its contributors may not be
+used to endorse or promote products derived from this software without specific
+prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+OF SUCH DAMAGE.
+*/
+public class ParameterConstant {
+ public final static String TYPE_MASTER = "master";
+ public final static String TYPE_SLAVE = "slave";
+}
diff --git a/src/main/java/org/mariadb/jdbc/internal/common/UrlHAMode.java b/src/main/java/org/mariadb/jdbc/internal/common/UrlHAMode.java
new file mode 100644
index 000000000..70d63aed9
--- /dev/null
+++ b/src/main/java/org/mariadb/jdbc/internal/common/UrlHAMode.java
@@ -0,0 +1,53 @@
+/*
+MariaDB Client for Java
+
+Copyright (c) 2012 Monty Program Ab.
+
+This library is free software; you can redistribute it and/or modify it under
+the terms of the GNU Lesser General Public License as published by the Free
+Software Foundation; either version 2.1 of the License, or (at your option)
+any later version.
+
+This library 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 Lesser General Public License
+for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this library; if not, write to Monty Program Ab info@montyprogram.com.
+
+This particular MariaDB Client for Java file is work
+derived from a Drizzle-JDBC. Drizzle-JDBC file which is covered by subject to
+the following copyright and notice provisions:
+
+Copyright (c) 2009-2011, Marcus Eriksson
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+Redistributions of source code must retain the above copyright notice, this list
+of conditions and the following disclaimer.
+
+Redistributions in binary form must reproduce the above copyright notice, this
+list of conditions and the following disclaimer in the documentation and/or
+other materials provided with the distribution.
+
+Neither the name of the driver nor the names of its contributors may not be
+used to endorse or promote products derived from this software without specific
+prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+OF SUCH DAMAGE.
+*/
+package org.mariadb.jdbc.internal.common;
+
+public enum UrlHAMode {
+ AURORA, REPLICATION, FAILOVER, NONE
+}
diff --git a/src/main/java/org/mariadb/jdbc/internal/common/Utils.java b/src/main/java/org/mariadb/jdbc/internal/common/Utils.java
index d3ccf8bdf..747a4dafb 100644
--- a/src/main/java/org/mariadb/jdbc/internal/common/Utils.java
+++ b/src/main/java/org/mariadb/jdbc/internal/common/Utils.java
@@ -48,12 +48,19 @@ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWIS
*/
package org.mariadb.jdbc.internal.common;
+import org.mariadb.jdbc.JDBCUrl;
+import org.mariadb.jdbc.internal.mysql.*;
+import org.mariadb.jdbc.internal.mysql.listener.impl.AuroraListener;
+import org.mariadb.jdbc.internal.mysql.listener.impl.MastersFailoverListener;
+import org.mariadb.jdbc.internal.mysql.listener.impl.MastersSlavesListener;
+
+import java.lang.reflect.Proxy;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
-import java.util.Properties;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
public class Utils {
@@ -511,30 +518,28 @@ else if (lastChar == '*') {
return sqlBuffer.toString();
}
- /**
- * Adds the parsed parameter to the properties object.
- *
- * @param parameter a key=value pair
- * @param info the properties object
- */
- public static void setUrlParameter(String parameter, Properties info) {
- int pos = parameter.indexOf('=');
- if (pos == -1) {
- throw new IllegalArgumentException("Invalid connection URL, expected key=value pairs, found " + parameter);
+
+
+ public static Protocol retrieveProxy(final JDBCUrl jdbcUrl, final ReentrantReadWriteLock lock) throws QueryException, SQLException {
+ if (jdbcUrl.getHaMode().equals(UrlHAMode.AURORA)) {
+ return (Protocol) Proxy.newProxyInstance(
+ AuroraProtocol.class.getClassLoader(),
+ new Class[] {Protocol.class},
+ new FailoverProxy(new AuroraListener(jdbcUrl), lock));
+ } else if (jdbcUrl.getHaMode().equals(UrlHAMode.REPLICATION)){
+ return (Protocol) Proxy.newProxyInstance(
+ MastersSlavesProtocol.class.getClassLoader(),
+ new Class[] {Protocol.class},
+ new FailoverProxy(new MastersSlavesListener(jdbcUrl), lock));
+ } else if (jdbcUrl.getHaMode().equals(UrlHAMode.FAILOVER)){
+ return (Protocol) Proxy.newProxyInstance(
+ MySQLProtocol.class.getClassLoader(),
+ new Class[]{Protocol.class},
+ new FailoverProxy(new MastersFailoverListener(jdbcUrl), lock));
+ } else {
+ MySQLProtocol protocol = new MySQLProtocol(jdbcUrl, lock);
+ protocol.connectWithoutProxy();
+ return protocol;
}
- info.setProperty(parameter.substring(0, pos), parameter.substring(pos + 1));
- }
-
- /**
- * Parses the parameters string and sets the corresponding properties in the properties object.
- *
- * @param urlParameters the parameters string
- * @param info the properties object
- */
- public static void setUrlParameters(String urlParameters, Properties info) {
- String [] parameters = urlParameters.split("&");
- for(String parameter : parameters) {
- setUrlParameter(parameter, info);
- }
}
}
diff --git a/src/main/java/org/mariadb/jdbc/internal/common/packet/MaxAllowedPacketException.java b/src/main/java/org/mariadb/jdbc/internal/common/packet/MaxAllowedPacketException.java
index 2d0f59929..852fcd1d7 100644
--- a/src/main/java/org/mariadb/jdbc/internal/common/packet/MaxAllowedPacketException.java
+++ b/src/main/java/org/mariadb/jdbc/internal/common/packet/MaxAllowedPacketException.java
@@ -1,10 +1,55 @@
+/*
+MariaDB Client for Java
+
+Copyright (c) 2012 Monty Program Ab.
+
+This library is free software; you can redistribute it and/or modify it under
+the terms of the GNU Lesser General Public License as published by the Free
+Software Foundation; either version 2.1 of the License, or (at your option)
+any later version.
+
+This library 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 Lesser General Public License
+for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this library; if not, write to Monty Program Ab info@montyprogram.com.
+
+This particular MariaDB Client for Java file is work
+derived from a Drizzle-JDBC. Drizzle-JDBC file which is covered by subject to
+the following copyright and notice provisions:
+
+Copyright (c) 2009-2011, Marcus Eriksson
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+Redistributions of source code must retain the above copyright notice, this list
+of conditions and the following disclaimer.
+
+Redistributions in binary form must reproduce the above copyright notice, this
+list of conditions and the following disclaimer in the documentation and/or
+other materials provided with the distribution.
+
+Neither the name of the driver nor the names of its contributors may not be
+used to endorse or promote products derived from this software without specific
+prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+OF SUCH DAMAGE.
+*/
package org.mariadb.jdbc.internal.common.packet;
import java.io.IOException;
-/**
- * Created by diego_000 on 03/06/2015.
- */
public class MaxAllowedPacketException extends IOException {
boolean mustReconnect;
public MaxAllowedPacketException(String message, boolean mustReconnect) {
diff --git a/src/main/java/org/mariadb/jdbc/internal/common/query/parameters/DateParameter.java b/src/main/java/org/mariadb/jdbc/internal/common/query/parameters/DateParameter.java
index cd564db55..12cc9bb27 100644
--- a/src/main/java/org/mariadb/jdbc/internal/common/query/parameters/DateParameter.java
+++ b/src/main/java/org/mariadb/jdbc/internal/common/query/parameters/DateParameter.java
@@ -62,7 +62,7 @@ public class DateParameter extends ParameterHolder {
/**
* Represents a timestamp, constructed with time in millis since epoch
*
- * @param date
+ * @param date the date
*/
public DateParameter(Date date) {
this(date, null);
diff --git a/src/main/java/org/mariadb/jdbc/internal/common/queryresults/CachedSelectResult.java b/src/main/java/org/mariadb/jdbc/internal/common/queryresults/CachedSelectResult.java
index d5f98354f..7d60c7b6d 100644
--- a/src/main/java/org/mariadb/jdbc/internal/common/queryresults/CachedSelectResult.java
+++ b/src/main/java/org/mariadb/jdbc/internal/common/queryresults/CachedSelectResult.java
@@ -104,7 +104,7 @@ public MySQLColumnInformation[] getColumnInformation() {
* gets the value at position i in the result set. i starts at zero!
*
* @param i index, starts at 0
- * @return
+ * @return the value
*/
public ValueObject getValueObject(int i) throws NoSuchColumnException {
if (rowPointer < 0) {
diff --git a/src/main/java/org/mariadb/jdbc/internal/common/queryresults/SelectQueryResult.java b/src/main/java/org/mariadb/jdbc/internal/common/queryresults/SelectQueryResult.java
index d5d9a27f9..9ebbd4a28 100644
--- a/src/main/java/org/mariadb/jdbc/internal/common/queryresults/SelectQueryResult.java
+++ b/src/main/java/org/mariadb/jdbc/internal/common/queryresults/SelectQueryResult.java
@@ -79,10 +79,10 @@ public ResultSetType getResultSetType() {
return ResultSetType.SELECT;
}
- /**
+ /**
* moves the row pointer to position i
- *
- * @param i the position
+ * @param i pointer to move
+ * @throws SQLException sql feature not supported
*/
public void moveRowPointerTo(int i) throws SQLException{
throw new SQLFeatureNotSupportedException("scrolling result set not supported");
@@ -92,6 +92,7 @@ public void moveRowPointerTo(int i) throws SQLException{
* gets the current row number
*
* @return the current row number
+ * @throws SQLException sql feature not supported
*/
public int getRowPointer() throws SQLException{
throw new SQLFeatureNotSupportedException("scrolling result set not supported");
diff --git a/src/main/java/org/mariadb/jdbc/internal/common/queryresults/StreamingSelectResult.java b/src/main/java/org/mariadb/jdbc/internal/common/queryresults/StreamingSelectResult.java
index acb2df350..27529f6db 100644
--- a/src/main/java/org/mariadb/jdbc/internal/common/queryresults/StreamingSelectResult.java
+++ b/src/main/java/org/mariadb/jdbc/internal/common/queryresults/StreamingSelectResult.java
@@ -26,12 +26,15 @@ private StreamingSelectResult(MySQLColumnInformation[] info, MySQLProtocol proto
protocol.activeResult = this;
}
- /**
- * create StreamingResultSet - precondition is that a result set packet has been read
+
+ /**
*
* @param packet the result set packet from the server
+ * @param packetFetcher packetfetcher
+ * @param protocol the current connection protocol class
* @return a StreamingQueryResult
- * @throws java.io.IOException when something goes wrong while reading/writing from the server
+ * @throws IOException when something goes wrong while reading/writing from the server
+ * @throws QueryException if there is an actual active result on the current connection
*/
public static StreamingSelectResult createStreamingSelectResult(
ResultSetPacket packet, PacketFetcher packetFetcher, MySQLProtocol protocol)
@@ -142,7 +145,7 @@ public void close() {
* gets the value at position i in the result set. i starts at zero!
*
* @param i index, starts at 0
- * @return
+ * @return the value
*/
@Override
public ValueObject getValueObject(int i) throws NoSuchColumnException {
diff --git a/src/main/java/org/mariadb/jdbc/internal/mysql/AuroraProtocol.java b/src/main/java/org/mariadb/jdbc/internal/mysql/AuroraProtocol.java
new file mode 100644
index 000000000..d8e6c2ebf
--- /dev/null
+++ b/src/main/java/org/mariadb/jdbc/internal/mysql/AuroraProtocol.java
@@ -0,0 +1,257 @@
+/*
+MariaDB Client for Java
+
+Copyright (c) 2012 Monty Program Ab.
+
+This library is free software; you can redistribute it and/or modify it under
+the terms of the GNU Lesser General Public License as published by the Free
+Software Foundation; either version 2.1 of the License, or (at your option)
+any later version.
+
+This library 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 Lesser General Public License
+for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this library; if not, write to Monty Program Ab info@montyprogram.com.
+
+This particular MariaDB Client for Java file is work
+derived from a Drizzle-JDBC. Drizzle-JDBC file which is covered by subject to
+the following copyright and notice provisions:
+
+Copyright (c) 2009-2011, Marcus Eriksson
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+Redistributions of source code must retain the above copyright notice, this list
+of conditions and the following disclaimer.
+
+Redistributions in binary form must reproduce the above copyright notice, this
+list of conditions and the following disclaimer in the documentation and/or
+other materials provided with the distribution.
+
+Neither the name of the driver nor the names of its contributors may not be
+used to endorse or promote products derived from this software without specific
+prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+OF SUCH DAMAGE.
+*/
+
+package org.mariadb.jdbc.internal.mysql;
+
+import org.mariadb.jdbc.HostAddress;
+import org.mariadb.jdbc.JDBCUrl;
+import org.mariadb.jdbc.internal.SQLExceptionMapper;
+import org.mariadb.jdbc.internal.common.QueryException;
+import org.mariadb.jdbc.internal.common.query.MySQLQuery;
+import org.mariadb.jdbc.internal.common.queryresults.SelectQueryResult;
+import org.mariadb.jdbc.internal.mysql.listener.impl.AuroraListener;
+import org.mariadb.jdbc.internal.mysql.listener.tools.SearchFilter;
+
+import java.io.IOException;
+import java.util.*;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+public class AuroraProtocol extends MastersSlavesProtocol {
+ private final static Logger log = Logger.getLogger(AuroraProtocol.class.getName());
+
+ public AuroraProtocol(final JDBCUrl url, final ReentrantReadWriteLock lock) {
+ super(url, lock);
+ }
+
+
+ @Override
+ public boolean isMasterConnection() {
+ return this.masterConnection;
+ }
+
+ /**
+ * Aurora best way to check if a node is a master : is not in read-only mode
+ *
+ * @return indicate if master has been found
+ */
+ @Override
+ public boolean checkIfMaster() throws QueryException {
+ proxy.lock.writeLock().lock();
+ try {
+ SelectQueryResult queryResult = (SelectQueryResult) executeQuery(new MySQLQuery("show global variables like 'innodb_read_only'"));
+ if (queryResult != null) {
+ queryResult.next();
+ this.masterConnection = "OFF".equals(queryResult.getValueObject(1).getString());
+ } else {
+ this.masterConnection = false;
+ }
+ this.readOnly = !this.masterConnection;
+ return this.masterConnection;
+
+ } catch (IOException ioe) {
+ log.log(Level.FINEST, "exception during checking if master", ioe);
+ throw new QueryException("could not check the 'innodb_read_only' variable status on " + this.getHostAddress() +
+ " : " + ioe.getMessage(), -1, SQLExceptionMapper.SQLStates.CONNECTION_EXCEPTION.getSqlState(), ioe);
+ } finally {
+ proxy.lock.writeLock().unlock();
+ }
+ }
+
+
+ public static void searchProbableMaster(AuroraListener listener, HostAddress probableMaster, Map blacklist, SearchFilter searchFilter) throws QueryException {
+ if (log.isLoggable(Level.FINE)) {
+ log.fine("searching for master:" + searchFilter.isSearchForMaster() + " replica:" + searchFilter.isSearchForSlave() + " address:" + probableMaster + " blacklist:" + blacklist.keySet());
+ }
+ AuroraProtocol protocol = getNewProtocol(listener.getProxy(), listener.getJdbcUrl());
+ try {
+
+ protocol.setHostAddress(probableMaster);
+ if (log.isLoggable(Level.FINE)) log.fine("trying to connect to " + protocol.getHostAddress());
+ protocol.connect();
+ if (log.isLoggable(Level.FINE)) log.fine("connected to " + protocol.getHostAddress());
+
+ if (searchFilter.isSearchForMaster() && protocol.isMasterConnection()) {
+ searchFilter.setSearchForMaster(false);
+ protocol.setMustBeMasterConnection(true);
+ listener.foundActiveMaster(protocol);
+ } else if (searchFilter.isSearchForSlave() && !protocol.isMasterConnection()) {
+ searchFilter.setSearchForSlave(false);
+ protocol.setMustBeMasterConnection(false);
+ listener.foundActiveSecondary(protocol);
+ } else {
+ if (log.isLoggable(Level.FINE))
+ log.fine("close connection because unused : " + protocol.getHostAddress());
+ protocol.close();
+ protocol = getNewProtocol(listener.getProxy(), listener.getJdbcUrl());
+ }
+
+ } catch (QueryException e) {
+ blacklist.put(protocol.getHostAddress(), System.currentTimeMillis());
+ if (log.isLoggable(Level.FINE))
+ log.fine("Could not connect to " + protocol.currentHost + " searching for master : " + searchFilter.isSearchForMaster() + " for replica :" + searchFilter.isSearchForSlave() + " error:" + e.getMessage());
+ }
+ }
+
+ /**
+ * loop until found the failed connection.
+ *
+ * @param listener current listener
+ * @param addresses list of HostAddress to loop
+ * @param blacklist current blacklist
+ * @param searchFilter search parameter
+ * @throws QueryException if not found
+ */
+ public static void loop(AuroraListener listener, final List addresses, Map blacklist, SearchFilter searchFilter) throws QueryException {
+ if (log.isLoggable(Level.FINE)) {
+ log.fine("searching for master:" + searchFilter.isSearchForMaster() + " replica:" + searchFilter.isSearchForSlave() + " addresses:" + addresses );
+ }
+
+ AuroraProtocol protocol;
+ List loopAddresses = new LinkedList<>(addresses);
+ int maxConnectionTry = listener.getRetriesAllDown();
+ QueryException lastQueryException = null;
+
+ while (!loopAddresses.isEmpty() || (!searchFilter.isUniqueLoop() && maxConnectionTry > 0)) {
+ protocol = getNewProtocol(listener.getProxy(), listener.getJdbcUrl());
+
+ if (listener.isExplicitClosed() || (!listener.isSecondaryHostFail() && !listener.isMasterHostFail())) return;
+ maxConnectionTry--;
+
+ try {
+ protocol.setHostAddress(loopAddresses.get(0));
+ loopAddresses.remove(0);
+
+ if (log.isLoggable(Level.FINE)) log.fine("trying to connect to " + protocol.getHostAddress());
+ protocol.connect();
+ blacklist.remove(protocol.getHostAddress());
+ if (log.isLoggable(Level.FINE)) log.fine("connected to " + (protocol.isMasterConnection()?"primary ":"replica ") + protocol.getHostAddress());
+
+ if (searchFilter.isSearchForMaster() && protocol.isMasterConnection()) {
+ log.finest("locks -0 : "+protocol.getProxy().lock.getReadHoldCount() + " "+protocol.getProxy().lock.getWriteHoldCount());
+ if (foundMaster(listener, protocol, searchFilter)) return;
+ log.finest("locks -1: "+protocol.getProxy().lock.getReadHoldCount() + " "+protocol.getProxy().lock.getWriteHoldCount());
+ } else if (searchFilter.isSearchForSlave() && !protocol.isMasterConnection()) {
+ log.finest("locks -2: "+protocol.getProxy().lock.getReadHoldCount() + " "+protocol.getProxy().lock.getWriteHoldCount());
+ if (foundSecondary(listener, protocol, searchFilter)) return;
+ log.finest("locks -3: "+protocol.getProxy().lock.getReadHoldCount() + " "+protocol.getProxy().lock.getWriteHoldCount());
+
+ HostAddress probableMasterHost = listener.searchByStartName(protocol, listener.getJdbcUrl().getHostAddresses());
+ if (probableMasterHost != null) {
+ loopAddresses.remove(probableMasterHost);
+ AuroraProtocol.searchProbableMaster(listener, probableMasterHost, blacklist, searchFilter);
+ log.finest("locks -4: "+protocol.getProxy().lock.getReadHoldCount() + " "+protocol.getProxy().lock.getWriteHoldCount());
+ if (!searchFilter.isSearchForMaster()) return;
+ }
+ } else {
+ protocol.close();
+ }
+ } catch (QueryException e) {
+ lastQueryException = e;
+ blacklist.put(protocol.getHostAddress(), System.currentTimeMillis());
+ if (log.isLoggable(Level.FINE)) log.fine("Could not connect to " + protocol.getHostAddress() + " searching: " + searchFilter + " error: " + e.getMessage());
+ }
+
+ if (!searchFilter.isSearchForMaster() && !searchFilter.isSearchForSlave()) return;
+
+ //loop is set so
+ if (loopAddresses.isEmpty() && !searchFilter.isUniqueLoop() && maxConnectionTry > 0) {
+ loopAddresses = new LinkedList<>(addresses);
+ listener.checkIfTypeHaveChanged(searchFilter);
+ }
+
+ }
+
+ if (searchFilter.isSearchForMaster() || searchFilter.isSearchForSlave()) {
+ String error = "No active connection found for replica";
+ if (searchFilter.isSearchForMaster()) error = "No active connection found for master";
+ if (lastQueryException != null) {
+ throw new QueryException(error, lastQueryException.getErrorCode(), lastQueryException.getSqlState(), lastQueryException);
+ }
+ throw new QueryException(error);
+ }
+ }
+
+ private static boolean foundMaster(AuroraListener listener, AuroraProtocol protocol,SearchFilter searchFilter) {
+ protocol.setMustBeMasterConnection(true);
+ searchFilter.setSearchForMaster(false);
+ listener.foundActiveMaster(protocol);
+ if (!searchFilter.isSearchForSlave()) return true;
+ else {
+ if (listener.isExplicitClosed()
+ || searchFilter.isFineIfFoundOnlyMaster()
+ || !listener.isSecondaryHostFail()) return true;
+ }
+ return false;
+ }
+
+ private static boolean foundSecondary(AuroraListener listener, AuroraProtocol protocol,SearchFilter searchFilter) {
+ searchFilter.setSearchForSlave(false);
+ protocol.setMustBeMasterConnection(false);
+ listener.foundActiveSecondary(protocol);
+ if (!searchFilter.isSearchForMaster()) return true;
+ else {
+ if (listener.isExplicitClosed()
+ || searchFilter.isFineIfFoundOnlySlave()
+ || !listener.isMasterHostFail()) return true;
+
+
+ }
+ return false;
+ }
+
+ public static AuroraProtocol getNewProtocol(FailoverProxy proxy, JDBCUrl jdbcUrl) {
+ AuroraProtocol newProtocol = new AuroraProtocol(jdbcUrl, proxy.lock);
+ newProtocol.setProxy(proxy);
+ return newProtocol;
+ }
+
+
+}
diff --git a/src/main/java/org/mariadb/jdbc/internal/mysql/FailoverProxy.java b/src/main/java/org/mariadb/jdbc/internal/mysql/FailoverProxy.java
new file mode 100644
index 000000000..08a379b98
--- /dev/null
+++ b/src/main/java/org/mariadb/jdbc/internal/mysql/FailoverProxy.java
@@ -0,0 +1,188 @@
+/*
+MariaDB Client for Java
+
+Copyright (c) 2012 Monty Program Ab.
+
+This library is free software; you can redistribute it and/or modify it under
+the terms of the GNU Lesser General Public License as published by the Free
+Software Foundation; either version 2.1 of the License, or (at your option)
+any later version.
+
+This library 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 Lesser General Public License
+for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this library; if not, write to Monty Program Ab info@montyprogram.com.
+
+This particular MariaDB Client for Java file is work
+derived from a Drizzle-JDBC. Drizzle-JDBC file which is covered by subject to
+the following copyright and notice provisions:
+
+Copyright (c) 2009-2011, Marcus Eriksson
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+Redistributions of source code must retain the above copyright notice, this list
+of conditions and the following disclaimer.
+
+Redistributions in binary form must reproduce the above copyright notice, this
+list of conditions and the following disclaimer in the documentation and/or
+other materials provided with the distribution.
+
+Neither the name of the driver nor the names of its contributors may not be
+used to endorse or promote products derived from this software without specific
+prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+OF SUCH DAMAGE.
+*/
+
+package org.mariadb.jdbc.internal.mysql;
+
+import org.mariadb.jdbc.internal.SQLExceptionMapper;
+import org.mariadb.jdbc.internal.common.QueryException;
+import org.mariadb.jdbc.internal.mysql.listener.Listener;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.sql.SQLException;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+import java.util.logging.Logger;
+
+public class FailoverProxy implements InvocationHandler {
+ private final static Logger log = Logger.getLogger(FailoverProxy.class.getName());
+
+ public final static String METHOD_IS_EXPLICIT_CLOSED = "isExplicitClosed";
+ public final static String METHOD_GET_OPTIONS = "getOptions";
+ public final static String METHOD_GET_PROXY = "getProxy";
+ public final static String METHOD_EXECUTE_QUERY = "executeQuery";
+ public final static String METHOD_SET_READ_ONLY = "setReadonly";
+ public final static String METHOD_IS_READ_ONLY = "isReadOnly";
+ public final static String METHOD_CLOSED_EXPLICIT = "closeExplicit";
+ public final static String METHOD_IS_CLOSED = "isClosed";
+
+
+ public final ReentrantReadWriteLock lock;
+
+ private Listener listener;
+
+ public FailoverProxy(Listener listener, ReentrantReadWriteLock lock) throws QueryException, SQLException{
+ this.lock = lock;
+ this.listener = listener;
+ this.listener.setProxy(this);
+ this.listener.initializeConnection();
+ }
+
+ /**
+ * proxy that catch Protocol call, to permit to catch errors and handle failover when multiple hosts
+ * @param proxy the current protocol
+ * @param method the called method on the protocol
+ * @param args methods parameters
+ * @return protocol method result
+ * @throws Throwable the method throwed error if not catch by failover
+ */
+ @Override
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ String methodName = method.getName();
+ switch (methodName) {
+ case METHOD_IS_EXPLICIT_CLOSED:
+ return listener.isExplicitClosed();
+ case METHOD_GET_OPTIONS:
+ return listener.getJdbcUrl().getOptions();
+ case METHOD_GET_PROXY:
+ return this;
+ case METHOD_IS_CLOSED:
+ return listener.isClosed();
+ case METHOD_EXECUTE_QUERY:
+ try {
+ this.listener.preExecute();
+ } catch (QueryException e) {
+ return handleFailOver(e, method, args);
+ }
+ break;
+ case METHOD_SET_READ_ONLY:
+ this.listener.switchReadOnlyConnection((Boolean) args[0]);
+ return null;
+ case METHOD_IS_READ_ONLY:
+ return this.listener.isReadOnly();
+ case METHOD_CLOSED_EXPLICIT:
+ this.listener.preClose();
+ return null;
+ }
+ try {
+ return listener.invoke(method, args);
+ } catch (InvocationTargetException e) {
+ if (e.getTargetException() != null) {
+ if (e.getTargetException() instanceof QueryException) {
+ if (hasToHandleFailover((QueryException) e.getTargetException())) {
+ return handleFailOver((QueryException) e.getTargetException(), method, args);
+ }
+ }
+ throw e.getTargetException();
+ }
+ throw e;
+ }
+ }
+
+
+ /**
+ * After a connection exception, launch failover
+ * @param qe the exception thrown
+ * @param method the method to call if failover works well
+ * @param args the arguments of the method
+ * @return the object return from the method
+ * @throws Throwable
+ */
+ private Object handleFailOver(QueryException qe, Method method, Object[] args) throws Throwable{
+ HandleErrorResult handleErrorResult = listener.handleFailover(method, args);
+ if (handleErrorResult.mustThrowError) listener.throwFailoverMessage(qe, handleErrorResult.isReconnected);
+ return handleErrorResult.resultObject;
+ }
+
+ /**
+ * Check if this Sqlerror is a connection exception. if that's the case, must be handle by failover
+ *
+ * error codes :
+ * 08000 : connection exception
+ * 08001 : SQL client unable to establish SQL connection
+ * 08002 : connection name in use
+ * 08003 : connection does not exist
+ * 08004 : SQL server rejected SQL connection
+ * 08006 : connection failure
+ * 08007 : transaction resolution unknown
+ * 70100 : connection was killed
+ * @param e the Exception
+ * @return true if there has been a connection error that must be handled by failover
+ */
+ public boolean hasToHandleFailover(QueryException e){
+ if (e.getSqlState() != null && e.getSqlState().startsWith("08")
+ //|| "70100".equals(e.getSqlState())
+ ) {
+ return true;
+ }
+ return false;
+ }
+
+ public void reconnect() throws SQLException {
+ try {
+ listener.reconnect();
+ } catch (QueryException e) {
+ SQLExceptionMapper.throwException(e, null, null);
+ }
+ }
+
+ public Listener getListener() {
+ return listener;
+ }
+}
diff --git a/src/main/java/org/mariadb/jdbc/internal/mysql/HandleErrorResult.java b/src/main/java/org/mariadb/jdbc/internal/mysql/HandleErrorResult.java
new file mode 100644
index 000000000..5dfacdc04
--- /dev/null
+++ b/src/main/java/org/mariadb/jdbc/internal/mysql/HandleErrorResult.java
@@ -0,0 +1,64 @@
+/*
+MariaDB Client for Java
+
+Copyright (c) 2012 Monty Program Ab.
+
+This library is free software; you can redistribute it and/or modify it under
+the terms of the GNU Lesser General Public License as published by the Free
+Software Foundation; either version 2.1 of the License, or (at your option)
+any later version.
+
+This library 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 Lesser General Public License
+for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this library; if not, write to Monty Program Ab info@montyprogram.com.
+
+This particular MariaDB Client for Java file is work
+derived from a Drizzle-JDBC. Drizzle-JDBC file which is covered by subject to
+the following copyright and notice provisions:
+
+Copyright (c) 2009-2011, Marcus Eriksson
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+Redistributions of source code must retain the above copyright notice, this list
+of conditions and the following disclaimer.
+
+Redistributions in binary form must reproduce the above copyright notice, this
+list of conditions and the following disclaimer in the documentation and/or
+other materials provided with the distribution.
+
+Neither the name of the driver nor the names of its contributors may not be
+used to endorse or promote products derived from this software without specific
+prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+OF SUCH DAMAGE.
+*/
+
+package org.mariadb.jdbc.internal.mysql;
+
+public class HandleErrorResult {
+ public boolean mustThrowError = true;
+ public boolean isReconnected = false;
+
+ public Object resultObject = null;
+
+ public HandleErrorResult() {
+ }
+
+ public HandleErrorResult(boolean isReconnected) {
+ this.isReconnected = isReconnected;
+ }
+}
diff --git a/src/main/java/org/mariadb/jdbc/internal/mysql/MastersSlavesProtocol.java b/src/main/java/org/mariadb/jdbc/internal/mysql/MastersSlavesProtocol.java
new file mode 100644
index 000000000..6806f902e
--- /dev/null
+++ b/src/main/java/org/mariadb/jdbc/internal/mysql/MastersSlavesProtocol.java
@@ -0,0 +1,187 @@
+/*
+MariaDB Client for Java
+
+Copyright (c) 2012 Monty Program Ab.
+
+This library is free software; you can redistribute it and/or modify it under
+the terms of the GNU Lesser General Public License as published by the Free
+Software Foundation; either version 2.1 of the License, or (at your option)
+any later version.
+
+This library 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 Lesser General Public License
+for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this library; if not, write to Monty Program Ab info@montyprogram.com.
+
+This particular MariaDB Client for Java file is work
+derived from a Drizzle-JDBC. Drizzle-JDBC file which is covered by subject to
+the following copyright and notice provisions:
+
+Copyright (c) 2009-2011, Marcus Eriksson
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+Redistributions of source code must retain the above copyright notice, this list
+of conditions and the following disclaimer.
+
+Redistributions in binary form must reproduce the above copyright notice, this
+list of conditions and the following disclaimer in the documentation and/or
+other materials provided with the distribution.
+
+Neither the name of the driver nor the names of its contributors may not be
+used to endorse or promote products derived from this software without specific
+prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+OF SUCH DAMAGE.
+*/
+
+package org.mariadb.jdbc.internal.mysql;
+
+import org.mariadb.jdbc.HostAddress;
+import org.mariadb.jdbc.JDBCUrl;
+import org.mariadb.jdbc.internal.common.QueryException;
+import org.mariadb.jdbc.internal.mysql.listener.impl.MastersSlavesListener;
+import org.mariadb.jdbc.internal.mysql.listener.tools.SearchFilter;
+
+import java.util.*;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+public class MastersSlavesProtocol extends MySQLProtocol {
+
+ private static Logger log = Logger.getLogger(MastersSlavesProtocol.class.getName());
+
+ boolean masterConnection = false;
+ boolean mustBeMasterConnection = false;
+
+ public MastersSlavesProtocol(final JDBCUrl url, final ReentrantReadWriteLock lock) {
+ super(url, lock);
+ }
+
+
+ /**
+ * loop until found the failed connection.
+ *
+ * @param listener current listener
+ * @param addresses list of HostAddress to loop
+ * @param blacklist current blacklist
+ * @param searchFilter search parameter
+ * @throws QueryException if not found
+ */
+ public static void loop(MastersSlavesListener listener, final List addresses, Map blacklist, SearchFilter searchFilter) throws QueryException {
+ if (log.isLoggable(Level.FINE)) {
+ log.fine("searching for master:" + searchFilter.isSearchForMaster() + " replica:" + searchFilter.isSearchForSlave() + " addresses:" + addresses );
+ }
+
+ MastersSlavesProtocol protocol;
+ List loopAddresses = new LinkedList<>(addresses);
+ int maxConnectionTry = listener.getRetriesAllDown();
+ QueryException lastQueryException = null;
+
+ while (!loopAddresses.isEmpty() || (!searchFilter.isUniqueLoop() && maxConnectionTry > 0)) {
+ protocol = getNewProtocol(listener.getProxy(), listener.getJdbcUrl());
+
+ if (listener.isExplicitClosed() || (!listener.isSecondaryHostFail() && !listener.isMasterHostFail())) return;
+ maxConnectionTry--;
+
+ try {
+ protocol.setHostAddress(loopAddresses.get(0));
+ loopAddresses.remove(0);
+
+ if (log.isLoggable(Level.FINE)) log.fine("trying to connect to " + protocol.getHostAddress());
+ log.finest("log **1 " + protocol.getProxy().lock.getReadLockCount()+ " " + protocol.getProxy().lock.getWriteHoldCount());
+
+ protocol.connect();
+ log.finest("log **2 " + protocol.getProxy().lock.getReadLockCount()+ " " + protocol.getProxy().lock.getWriteHoldCount());
+ blacklist.remove(protocol.getHostAddress());
+ if (log.isLoggable(Level.FINE)) log.fine("connected to " + (protocol.isMasterConnection()?"primary ":"replica ") + protocol.getHostAddress());
+
+ log.finest("log **3 " + protocol.getProxy().lock.getReadLockCount()+ " " + protocol.getProxy().lock.getWriteHoldCount());
+ if (searchFilter.isSearchForMaster() && protocol.isMasterConnection()) {
+ if (foundMaster(listener, protocol, searchFilter)) return;
+ } else if (searchFilter.isSearchForSlave() && !protocol.isMasterConnection()) {
+ if (foundSecondary(listener, protocol, searchFilter)) return;
+ } else {
+ protocol.close();
+ }
+ log.finest("log **4 " + protocol.getProxy().lock.getReadLockCount()+ " " + protocol.getProxy().lock.getWriteHoldCount());
+
+ } catch (QueryException e) {
+ lastQueryException = e;
+ blacklist.put(protocol.getHostAddress(), System.currentTimeMillis());
+ if (log.isLoggable(Level.FINE)) log.fine("Could not connect to " + protocol.getHostAddress() + " searching: " + searchFilter + " error: " + e.getMessage());
+ }
+
+ if (!searchFilter.isSearchForMaster() && !searchFilter.isSearchForSlave()) return;
+
+ //loop is set so
+ if (loopAddresses.isEmpty() && !searchFilter.isUniqueLoop() && maxConnectionTry > 0) {
+ loopAddresses = new LinkedList<>(addresses);
+ listener.checkIfTypeHaveChanged(searchFilter);
+ }
+
+ }
+
+ if (searchFilter.isSearchForMaster() || searchFilter.isSearchForSlave()) {
+ String error = "No active connection found for replica";
+ if (searchFilter.isSearchForMaster()) error = "No active connection found for master";
+ if (lastQueryException != null) {
+ throw new QueryException(error, lastQueryException.getErrorCode(), lastQueryException.getSqlState(), lastQueryException);
+ }
+ throw new QueryException(error);
+ }
+
+ }
+
+ private static boolean foundMaster(MastersSlavesListener listener, MastersSlavesProtocol protocol,SearchFilter searchFilter) {
+ protocol.setMustBeMasterConnection(true);
+ searchFilter.setSearchForMaster(false);
+ listener.foundActiveMaster(protocol);
+ if (!searchFilter.isSearchForSlave()) return true;
+ else {
+ if (listener.isExplicitClosed()
+ || searchFilter.isFineIfFoundOnlyMaster()
+ || !listener.isSecondaryHostFail()) return true;
+ }
+ return false;
+ }
+
+ private static boolean foundSecondary(MastersSlavesListener listener, MastersSlavesProtocol protocol,SearchFilter searchFilter) {
+ searchFilter.setSearchForSlave(false);
+ protocol.setMustBeMasterConnection(false);
+ listener.foundActiveSecondary(protocol);
+ if (!searchFilter.isSearchForMaster()) return true;
+ else {
+ if (listener.isExplicitClosed()
+ || searchFilter.isFineIfFoundOnlySlave()
+ || !listener.isMasterHostFail()) return true;
+ }
+ return false;
+ }
+
+ public static MastersSlavesProtocol getNewProtocol(FailoverProxy proxy, JDBCUrl jdbcUrl) {
+ MastersSlavesProtocol newProtocol = new MastersSlavesProtocol(jdbcUrl, proxy.lock);
+ newProtocol.setProxy(proxy);
+ return newProtocol;
+ }
+
+ public boolean mustBeMasterConnection() {
+ return mustBeMasterConnection;
+ }
+ public void setMustBeMasterConnection(boolean mustBeMasterConnection) {
+ this.mustBeMasterConnection = mustBeMasterConnection;
+ }
+}
diff --git a/src/main/java/org/mariadb/jdbc/internal/mysql/MySQLProtocol.java b/src/main/java/org/mariadb/jdbc/internal/mysql/MySQLProtocol.java
index 8f8675f89..4119bd754 100644
--- a/src/main/java/org/mariadb/jdbc/internal/mysql/MySQLProtocol.java
+++ b/src/main/java/org/mariadb/jdbc/internal/mysql/MySQLProtocol.java
@@ -64,6 +64,8 @@ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWIS
import org.mariadb.jdbc.internal.common.query.MySQLQuery;
import org.mariadb.jdbc.internal.common.query.Query;
import org.mariadb.jdbc.internal.common.queryresults.*;
+import org.mariadb.jdbc.internal.mysql.listener.Listener;
+import org.mariadb.jdbc.internal.mysql.listener.tools.SearchFilter;
import org.mariadb.jdbc.internal.mysql.packet.MySQLGreetingReadPacket;
import org.mariadb.jdbc.internal.mysql.packet.commands.*;
@@ -81,10 +83,9 @@ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWIS
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.sql.Statement;
+import java.sql.Connection;
import java.util.*;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -95,12 +96,12 @@ class MyX509TrustManager implements X509TrustManager {
String serverCertFile;
X509TrustManager trustManager;
- public MyX509TrustManager(Properties props) throws Exception{
- boolean trustServerCertificate = props.getProperty("trustServerCertificate") != null;
+ public MyX509TrustManager(Options options) throws Exception{
+ boolean trustServerCertificate = options.trustServerCertificate;
if (trustServerCertificate)
return;
- serverCertFile = props.getProperty("serverSslCert");
+ serverCertFile = options.serverSslCert;
InputStream inStream;
if (serverCertFile.startsWith("-----BEGIN CERTIFICATE-----")) {
@@ -152,90 +153,53 @@ public X509Certificate[] getAcceptedIssuers() {
}
}
-public class MySQLProtocol {
+public class MySQLProtocol implements Protocol {
private final static Logger log = Logger.getLogger(MySQLProtocol.class.getName());
+ protected final ReentrantReadWriteLock lock;
private boolean connected = false;
- private Socket socket;
- private PacketOutputStream writer;
+ private boolean explicitClosed = false;
+ protected Socket socket;
+ protected PacketOutputStream writer;
private String version;
- private boolean readOnly = false;
+ protected boolean readOnly = false;
private String database;
private final String username;
private final String password;
private int maxRows; /* max rows returned by a statement */
- private SyncPacketFetcher packetFetcher;
- private final Properties info;
+ protected SyncPacketFetcher packetFetcher;
private long serverThreadId;
public boolean moreResults = false;
public boolean hasWarnings = false;
public StreamingSelectResult activeResult= null;
public int datatypeMappingFlags;
public short serverStatus;
- JDBCUrl jdbcUrl;
- HostAddress currentHost;
-
+ protected final JDBCUrl jdbcUrl;
+ protected HostAddress currentHost;
+ protected FailoverProxy proxy;
private int majorVersion;
private int minorVersion;
private int patchVersion;
-
- boolean hostFailed;
- long failTimestamp;
- int reconnectCount;
- int queriesSinceFailover;
+ private int maxAllowedPacket;
private byte serverLanguage;
+ private int transactionIsolationLevel=0;
+ boolean hostFailed;
/* =========================== HA parameters ========================================= */
- /**
- * Should the driver try to re-establish stale and/or dead connections?
- * NOTE: exceptions will still be thrown, yet the next retry will repair the connection
- */
- private boolean autoReconnect = false;
-
- /**
- * Maximum number of reconnects to attempt if autoReconnect is true, default is 3
- */
- private int maxReconnects=3;
- /**
- * When using loadbalancing, the number of times the driver should cycle through available hosts, attempting to connect.
- * Between cycles, the driver will pause for 250ms if no servers are available. 120
- */
- int retriesAllDown = 120;
- /**
- * If autoReconnect is enabled, the initial time to wait between re-connect attempts (in seconds, defaults to 2)
- */
- int initialTimeout = 2;
- /**
- * When autoReconnect is enabled, and failoverReadonly is false, should we pick hosts to connect to on a round-robin
- * basis?
- */
-
- boolean roundRobinLoadBalance = false;
- /**
- * Number of queries to issue before falling back to master when failed over (when using multi-host failover).
- * Whichever condition is met first, 'queriesBeforeRetryMaster' or 'secondsBeforeRetryMaster' will cause an
- * attempt to be made to reconnect to the master. Defaults to 50
- */
- int queriesBeforeRetryMaster = 50;
-
- /**
- * How long should the driver wait, when failed over, before attempting 30
- */
- int secondsBeforeRetryMaster = 30;
- private InputStream localInfileInputStream;
+ private InputStream localInfileInputStream;
private SSLSocketFactory getSSLSocketFactory(boolean trustServerCertificate) throws QueryException
{
- if (info.getProperty("trustServerCertificate") == null
- && info.getProperty("serverSslCert") == null) {
+ if (jdbcUrl.getOptions().trustServerCertificate
+ && jdbcUrl.getOptions().serverSslCert == null) {
return (SSLSocketFactory)SSLSocketFactory.getDefault();
}
try {
SSLContext sslContext = SSLContext.getInstance("TLS");
- X509TrustManager[] m = {new MyX509TrustManager(info)};
+ X509TrustManager[] m = {new MyX509TrustManager(jdbcUrl.getOptions())};
sslContext.init(null, m ,null);
- return sslContext.getSocketFactory();
+ return sslContext.getSocketFactory();
} catch (Exception e) {
throw new QueryException(e.getMessage(),0, "HY000", e);
}
@@ -243,275 +207,190 @@ private SSLSocketFactory getSSLSocketFactory(boolean trustServerCertificate) th
}
/**
* Get a protocol instance
- * @param url connection URL
- * @param username the username
- * @param password the password
- * @param info
- * @throws org.mariadb.jdbc.internal.common.QueryException
- * if there is a problem reading / sending the packets
- * @throws SQLException
+ * @param jdbcUrl connection URL infos
+ * @param lock the lock for thread synchronisation
*/
- public MySQLProtocol(JDBCUrl url,
- final String username,
- final String password,
- Properties info)
- throws QueryException, SQLException {
- String fractionalSeconds = info.getProperty("useFractionalSeconds", "true");
- if ("true".equalsIgnoreCase(fractionalSeconds)) {
- info.setProperty("useFractionalSeconds", "true");
- }
- if ("true".equalsIgnoreCase(info.getProperty("pinGlobalTxToPhysicalConnection", "false"))) {
- info.setProperty("pinGlobalTxToPhysicalConnection", "true");
- }
- this.info = info;
- this.jdbcUrl = url;
- this.database = (jdbcUrl.getDatabase() == null ? "" : jdbcUrl.getDatabase());
- this.username = (username == null ? "" : username);
- this.password = (password == null ? "" : password);
-
- String logLevel = info.getProperty("MySQLProtocolLogLevel");
- if (logLevel != null)
- log.setLevel(Level.parse(logLevel));
- else
- log.setLevel(Level.OFF);
+ public MySQLProtocol(final JDBCUrl jdbcUrl, final ReentrantReadWriteLock lock) {
+ this.lock = lock;
+ this.jdbcUrl = jdbcUrl;
+ this.database = (jdbcUrl.getDatabase() == null ? "" : jdbcUrl.getDatabase());
+ this.username = (jdbcUrl.getUsername() == null ? "" : jdbcUrl.getUsername());
+ this.password = (jdbcUrl.getPassword() == null ? "" : jdbcUrl.getPassword());
setDatatypeMappingFlags();
- parseHAOptions();
- connect();
- }
-
- private void parseHAOptions() {
- String s = info.getProperty("autoReconnect");
- if (s != null && s.equals("true"))
- autoReconnect = true;
- s = info.getProperty("maxReconnects");
- if (s != null)
- maxReconnects = Integer.parseInt(s);
- s = info.getProperty("queriesBeforeRetryMaster");
- if (s != null)
- queriesBeforeRetryMaster = Integer.parseInt(s);
- s = info.getProperty("secondsBeforeRetryMaster");
- if (s != null)
- secondsBeforeRetryMaster = Integer.parseInt(s);
}
+
/**
* Connect the client and perform handshake
*
* @throws QueryException : handshake error, e.g wrong user or password
* @throws IOException : connection error (host/port not available)
- * @throws SQLException
*/
- void connect(String host, int port) throws QueryException, IOException, SQLException{
+ private void connect(String host, int port) throws QueryException, IOException{
+
SocketFactory socketFactory = null;
- String socketFactoryName = info.getProperty("socketFactory");
+ String socketFactoryName = jdbcUrl.getOptions().socketFactory;
if (socketFactoryName != null) {
try {
socketFactory = (SocketFactory) (Class.forName(socketFactoryName).newInstance());
} catch (Exception sfex){
- log.info("Failed to create socket factory " + socketFactoryName);
+ log.fine("Failed to create socket factory " + socketFactoryName);
socketFactory = SocketFactory.getDefault();
}
} else {
socketFactory = SocketFactory.getDefault();
}
- // Extract connectTimeout URL parameter
- String connectTimeoutString = info.getProperty("connectTimeout");
- Integer connectTimeout = null;
- if (connectTimeoutString != null) {
- try {
- connectTimeout = Integer.valueOf(connectTimeoutString);
- } catch (Exception e) {
- connectTimeout = null;
- }
- }
// Create socket with timeout if required
- if (info.getProperty("pipe") != null) {
- socket = new org.mariadb.jdbc.internal.mysql.NamedPipeSocket(host, info.getProperty("pipe"));
- } else if(info.getProperty("localSocket") != null){
+ if (jdbcUrl.getOptions().pipe != null) {
+ socket = new org.mariadb.jdbc.internal.mysql.NamedPipeSocket(host, jdbcUrl.getOptions().pipe);
+ } else if(jdbcUrl.getOptions().localSocket != null){
try {
- socket = new org.mariadb.jdbc.internal.mysql.UnixDomainSocket(info.getProperty("localSocket"));
+ socket = new org.mariadb.jdbc.internal.mysql.UnixDomainSocket(jdbcUrl.getOptions().localSocket);
} catch( RuntimeException re) {
- // could be e.g library loading error
+ // could be e.g library loading error
throw new IOException(re.getMessage(),re.getCause());
}
- } else if(info.getProperty("sharedMemory")!= null) {
+ } else if(jdbcUrl.getOptions().sharedMemory != null) {
try {
- socket = new SharedMemorySocket(info.getProperty("sharedMemory"));
+ socket = new SharedMemorySocket(jdbcUrl.getOptions().sharedMemory);
} catch( RuntimeException re) {
- // could be e.g library loading error
- throw new IOException(re.getMessage(),re.getCause());
+ // could be e.g library loading error
+ throw new IOException(re.getMessage(),re.getCause());
}
} else {
socket = socketFactory.createSocket();
}
try {
- String value = info.getProperty("tcpNoDelay", "false");
- if (value.equalsIgnoreCase("true"))
- socket.setTcpNoDelay(true);
-
- value = info.getProperty("tcpKeepAlive", "false");
- if (value.equalsIgnoreCase("true"))
- socket.setKeepAlive(true);
-
- value = info.getProperty("tcpRcvBuf");
- if (value != null)
- socket.setReceiveBufferSize(Integer.parseInt(value));
-
- value = info.getProperty("tcpSndBuf");
- if (value != null)
- socket.setSendBufferSize(Integer.parseInt(value));
-
- value = info.getProperty("tcpAbortiveClose","false");
- if (value.equalsIgnoreCase("true"))
- socket.setSoLinger(true, 0);
-
- } catch (Exception e) {
- log.finest("Failed to set socket option: " + e.getLocalizedMessage());
- }
-
- // Bind the socket to a particular interface if the connection property
- // localSocketAddress has been defined.
- String localHost = info.getProperty("localSocketAddress");
- if (localHost != null) {
- InetSocketAddress localAddress = new InetSocketAddress(localHost, 0);
- socket.bind(localAddress);
- }
-
- if (!socket.isConnected()) {
+ if (jdbcUrl.getOptions().tcpNoDelay) socket.setTcpNoDelay(true);
+ if (jdbcUrl.getOptions().tcpKeepAlive) socket.setKeepAlive(true);
+ if (jdbcUrl.getOptions().tcpRcvBuf != null) socket.setReceiveBufferSize(jdbcUrl.getOptions().tcpRcvBuf);
+ if (jdbcUrl.getOptions().tcpSndBuf != null) socket.setSendBufferSize(jdbcUrl.getOptions().tcpSndBuf);
+ if (jdbcUrl.getOptions().tcpAbortiveClose) socket.setSoLinger(true, 0);
+ } catch (Exception e) {
+ if (log.isLoggable(Level.FINE))log.fine("Failed to set socket option: " + e.getLocalizedMessage());
+ }
+
+ // Bind the socket to a particular interface if the connection property
+ // localSocketAddress has been defined.
+ if (jdbcUrl.getOptions().localSocketAddress != null) {
+ InetSocketAddress localAddress = new InetSocketAddress(jdbcUrl.getOptions().localSocketAddress, 0);
+ socket.bind(localAddress);
+ }
+
+ if (!socket.isConnected()) {
InetSocketAddress sockAddr = new InetSocketAddress(host, port);
- if (connectTimeout != null) {
- socket.connect(sockAddr, connectTimeout);
+ if (jdbcUrl.getOptions().connectTimeout != null) {
+ socket.connect(sockAddr, jdbcUrl.getOptions().connectTimeout);
} else {
socket.connect(sockAddr);
}
- }
+ }
- // Extract socketTimeout URL parameter
- String socketTimeoutString = info.getProperty("socketTimeout");
- Integer socketTimeout = null;
- if (socketTimeoutString != null) {
- try {
- socketTimeout = Integer.valueOf(socketTimeoutString);
- } catch (Exception e) {
- socketTimeout = null;
- }
- }
- if (socketTimeout != null)
- socket.setSoTimeout(socketTimeout);
-
- try {
- InputStream reader;
- reader = new BufferedInputStream(socket.getInputStream(), 32768);
- packetFetcher = new SyncPacketFetcher(reader);
- writer = new PacketOutputStream(socket.getOutputStream());
- RawPacket packet = packetFetcher.getRawPacket();
- if (ReadUtil.isErrorPacket(packet)) {
- reader.close();
- ErrorPacket errorPacket = (ErrorPacket)ResultPacketFactory.createResultPacket(packet);
- throw new QueryException(errorPacket.getMessage());
- }
- final MySQLGreetingReadPacket greetingPacket = new MySQLGreetingReadPacket(packet);
- this.serverThreadId = greetingPacket.getServerThreadID();
- this.serverLanguage = greetingPacket.getServerLanguage();
- boolean useCompression = false;
-
- log.finest("Got greeting packet");
- this.version = greetingPacket.getServerVersion();
- parseVersion();
- byte packetSeq = 1;
- int capabilities =
- MySQLServerCapabilities.LONG_PASSWORD |
- MySQLServerCapabilities.IGNORE_SPACE |
- MySQLServerCapabilities.CLIENT_PROTOCOL_41|
- MySQLServerCapabilities.TRANSACTIONS|
- MySQLServerCapabilities.SECURE_CONNECTION|
- MySQLServerCapabilities.LOCAL_FILES|
- MySQLServerCapabilities.MULTI_RESULTS|
- MySQLServerCapabilities.FOUND_ROWS;
-
-
-
- if(info.getProperty("allowMultiQueries") != null
- || (info.getProperty("rewriteBatchedStatements") != null
- && "true".equalsIgnoreCase(info.getProperty("rewriteBatchedStatements")))) {
- capabilities |= MySQLServerCapabilities.MULTI_STATEMENTS;
- }
- if(info.getProperty("useCompression") != null) {
- capabilities |= MySQLServerCapabilities.COMPRESS;
- useCompression = true;
- }
- if(info.getProperty("interactiveClient") != null) {
- capabilities |= MySQLServerCapabilities.CLIENT_INTERACTIVE;
- }
- // If a database is given, but createDB is not defined or is false,
- // then just try to connect to the given database
- if (database != null && !createDB())
- capabilities |= MySQLServerCapabilities.CONNECT_WITH_DB;
-
- if(info.getProperty("useSSL") != null &&
- (greetingPacket.getServerCapabilities() & MySQLServerCapabilities.SSL) != 0 ) {
- capabilities |= MySQLServerCapabilities.SSL;
- AbbreviatedMySQLClientAuthPacket amcap = new AbbreviatedMySQLClientAuthPacket(capabilities);
- amcap.send(writer);
-
- boolean trustServerCertificate = info.getProperty("trustServerCertificate") != null;
-
- SSLSocketFactory f = getSSLSocketFactory(trustServerCertificate);
- SSLSocket sslSocket = (SSLSocket)f.createSocket(socket,
- socket.getInetAddress().getHostAddress(), socket.getPort(), false);
-
- sslSocket.setEnabledProtocols(new String [] {"TLSv1"});
- sslSocket.setUseClientMode(true);
- sslSocket.startHandshake();
- socket = sslSocket;
- writer = new PacketOutputStream(socket.getOutputStream());
- reader = new BufferedInputStream(socket.getInputStream(), 32768);
- packetFetcher = new SyncPacketFetcher(reader);
-
- packetSeq++;
- } else if(info.getProperty("useSSL") != null){
- throw new QueryException("Trying to connect with ssl, but ssl not enabled in the server");
- }
+ // Extract socketTimeout URL parameter
+ if (jdbcUrl.getOptions().socketTimeout != null) socket.setSoTimeout(jdbcUrl.getOptions().socketTimeout);
+
+ try {
+ InputStream reader;
+ reader = new BufferedInputStream(socket.getInputStream(), 32768);
+ packetFetcher = new SyncPacketFetcher(reader);
+ writer = new PacketOutputStream(socket.getOutputStream());
+ RawPacket packet = packetFetcher.getRawPacket();
+ if (ReadUtil.isErrorPacket(packet)) {
+ reader.close();
+ ErrorPacket errorPacket = (ErrorPacket)ResultPacketFactory.createResultPacket(packet);
+ throw new QueryException(errorPacket.getMessage());
+ }
+ final MySQLGreetingReadPacket greetingPacket = new MySQLGreetingReadPacket(packet);
+ this.serverThreadId = greetingPacket.getServerThreadID();
+ this.serverLanguage = greetingPacket.getServerLanguage();
+
+ this.version = greetingPacket.getServerVersion();
+ parseVersion();
+ byte packetSeq = 1;
+ int capabilities =
+ MySQLServerCapabilities.LONG_PASSWORD |
+ MySQLServerCapabilities.IGNORE_SPACE |
+ MySQLServerCapabilities.CLIENT_PROTOCOL_41|
+ MySQLServerCapabilities.TRANSACTIONS|
+ MySQLServerCapabilities.SECURE_CONNECTION|
+ MySQLServerCapabilities.LOCAL_FILES|
+ MySQLServerCapabilities.MULTI_RESULTS|
+ MySQLServerCapabilities.FOUND_ROWS;
+
+
+ if(jdbcUrl.getOptions().allowMultiQueries || (jdbcUrl.getOptions().rewriteBatchedStatements)) {
+ capabilities |= MySQLServerCapabilities.MULTI_STATEMENTS;
+ }
- final MySQLClientAuthPacket cap = new MySQLClientAuthPacket(this.username,
- this.password,
- database,
- capabilities,
- decideLanguage(),
- greetingPacket.getSeed(),
- packetSeq);
- cap.send(writer);
- log.finest("Sending auth packet");
+ if(jdbcUrl.getOptions().useCompression) capabilities |= MySQLServerCapabilities.COMPRESS;
+ if(jdbcUrl.getOptions().interactiveClient) capabilities |= MySQLServerCapabilities.CLIENT_INTERACTIVE;
+
+ // If a database is given, but createDatabaseIfNotExist is not defined or is false,
+ // then just try to connect to the given database
+ if (database != null && !jdbcUrl.getOptions().createDatabaseIfNotExist)
+ capabilities |= MySQLServerCapabilities.CONNECT_WITH_DB;
+
+ if(jdbcUrl.getOptions().useSSL &&
+ (greetingPacket.getServerCapabilities() & MySQLServerCapabilities.SSL) != 0 ) {
+ capabilities |= MySQLServerCapabilities.SSL;
+ AbbreviatedMySQLClientAuthPacket amcap = new AbbreviatedMySQLClientAuthPacket(capabilities);
+ amcap.send(writer);
+
+ SSLSocketFactory f = getSSLSocketFactory(jdbcUrl.getOptions().trustServerCertificate);
+ SSLSocket sslSocket = (SSLSocket)f.createSocket(socket,
+ socket.getInetAddress().getHostAddress(), socket.getPort(), false);
+
+ sslSocket.setEnabledProtocols(new String [] {"TLSv1"});
+ sslSocket.setUseClientMode(true);
+ sslSocket.startHandshake();
+ socket = sslSocket;
+ writer = new PacketOutputStream(socket.getOutputStream());
+ reader = new BufferedInputStream(socket.getInputStream(), 32768);
+ packetFetcher = new SyncPacketFetcher(reader);
+
+ packetSeq++;
+ } else if(jdbcUrl.getOptions().useSSL){
+ throw new QueryException("Trying to connect with ssl, but ssl not enabled in the server");
+ }
- RawPacket rp = packetFetcher.getRawPacket();
+ final MySQLClientAuthPacket cap = new MySQLClientAuthPacket(this.username,
+ this.password,
+ database,
+ capabilities,
+ decideLanguage(),
+ greetingPacket.getSeed(),
+ packetSeq);
+ cap.send(writer);
- if ((rp.getByteBuffer().get(0) & 0xFF) == 0xFE) { // Server asking for old format password
- final MySQLClientOldPasswordAuthPacket oldPassPacket = new MySQLClientOldPasswordAuthPacket(
- this.password, Utils.copyWithLength(greetingPacket.getSeed(),
- 8), rp.getPacketSeq() + 1);
- oldPassPacket.send(writer);
+ RawPacket rp = packetFetcher.getRawPacket();
- rp = packetFetcher.getRawPacket();
- }
+ if ((rp.getByteBuffer().get(0) & 0xFF) == 0xFE) { // Server asking for old format password
+ final MySQLClientOldPasswordAuthPacket oldPassPacket = new MySQLClientOldPasswordAuthPacket(
+ this.password, Utils.copyWithLength(greetingPacket.getSeed(),
+ 8), rp.getPacketSeq() + 1);
+ oldPassPacket.send(writer);
- checkErrorPacket(rp);
- ResultPacket resultPacket = ResultPacketFactory.createResultPacket(rp);
- OKPacket ok = (OKPacket)resultPacket;
- serverStatus = ok.getServerStatus();
+ rp = packetFetcher.getRawPacket();
+ }
- if (useCompression) {
- writer = new PacketOutputStream(new CompressOutputStream(socket.getOutputStream()));
- packetFetcher = new SyncPacketFetcher(new DecompressInputStream(socket.getInputStream()));
- }
+ checkErrorPacket(rp);
+ ResultPacket resultPacket = ResultPacketFactory.createResultPacket(rp);
+ OKPacket ok = (OKPacket)resultPacket;
+ serverStatus = ok.getServerStatus();
- // In JDBC, connection must start in autocommit mode.
- if ((serverStatus & ServerStatus.AUTOCOMMIT) == 0) {
- executeQuery(new MySQLQuery("set autocommit=1"));
- }
+ if (jdbcUrl.getOptions().useCompression) {
+ writer = new PacketOutputStream(new CompressOutputStream(socket.getOutputStream()));
+ packetFetcher = new SyncPacketFetcher(new DecompressInputStream(socket.getInputStream()));
+ }
+
+ // In JDBC, connection must start in autocommit mode.
+ if ((serverStatus & ServerStatus.AUTOCOMMIT) == 0) {
+ executeQuery(new MySQLQuery("set autocommit=1"));
+ }
SelectQueryResult qr = null;
try {
qr = (SelectQueryResult) executeQuery(new MySQLQuery("show variables like 'max_allowed_packet'"));
@@ -521,47 +400,50 @@ void connect(String host, int port) throws QueryException, IOException, SQLExcep
if (qr != null)qr.close();
}
- String sessionVariables = info.getProperty("sessionVariables");
- if (sessionVariables != null) {
- executeQuery(new MySQLQuery("set session " + sessionVariables));
- }
+ if (jdbcUrl.getOptions().sessionVariables != null) executeQuery(new MySQLQuery("set session " + jdbcUrl.getOptions().sessionVariables));
- // At this point, the driver is connected to the database, if createDB is true,
- // then just try to create the database and to use it
- if (createDB()) {
- // Try to create the database if it does not exist
- String quotedDB = MySQLConnection.quoteIdentifier(this.database);
- executeQuery(new MySQLQuery("CREATE DATABASE IF NOT EXISTS " + quotedDB));
- executeQuery(new MySQLQuery("USE " + quotedDB));
- }
+ // At this point, the driver is connected to the database, if createDB is true,
+ // then just try to create the database and to use it
+ if (checkIfMaster()) {
+ if (jdbcUrl.getOptions().createDatabaseIfNotExist) {
+ // Try to create the database if it does not exist
+ String quotedDB = MySQLConnection.quoteIdentifier(this.database);
+ executeQuery(new MySQLQuery("CREATE DATABASE IF NOT EXISTS " + quotedDB));
+ executeQuery(new MySQLQuery("USE " + quotedDB));
+ }
+ }
+
+ activeResult = null;
+ moreResults = false;
+ hasWarnings = false;
+ connected = true;
+ hostFailed = false;
+ } catch (IOException e) {
+ throw new QueryException("Could not connect to " + host + ":" +
+ port + ": " + e.getMessage(),
+ -1,
+ SQLExceptionMapper.SQLStates.CONNECTION_EXCEPTION.getSqlState(),
+ e);
+ }
+
+ }
+
+ public boolean checkIfMaster() throws QueryException {
+ return isMasterConnection();
+ }
- activeResult = null;
- moreResults = false;
- hasWarnings = false;
- connected = true;
- hostFailed = false; // Prevent reconnects
- } catch (IOException e) {
- throw new QueryException("Could not connect to " + host + ":" +
- port + ": " + e.getMessage(),
- -1,
- SQLExceptionMapper.SQLStates.CONNECTION_EXCEPTION.getSqlState(),
- e);
- }
-
- }
-
private boolean isServerLanguageUTF8MB4(byte serverLanguage) {
- Byte[] utf8mb4Languages = {
- (byte)45,(byte)46,(byte)224,(byte)225,(byte)226,(byte)227,(byte)228,
- (byte)229,(byte)230,(byte)231,(byte)232,(byte)233,(byte)234,(byte)235,
- (byte)236,(byte)237,(byte)238,(byte)239,(byte)240,(byte)241,(byte)242,
- (byte)243,(byte)245
- };
- return Arrays.asList(utf8mb4Languages).contains(serverLanguage);
+ Byte[] utf8mb4Languages = {
+ (byte)45,(byte)46,(byte)224,(byte)225,(byte)226,(byte)227,(byte)228,
+ (byte)229,(byte)230,(byte)231,(byte)232,(byte)233,(byte)234,(byte)235,
+ (byte)236,(byte)237,(byte)238,(byte)239,(byte)240,(byte)241,(byte)242,
+ (byte)243,(byte)245
+ };
+ return Arrays.asList(utf8mb4Languages).contains(serverLanguage);
}
private byte decideLanguage() {
- byte result = (byte) (isServerLanguageUTF8MB4(this.serverLanguage) ? this.serverLanguage : 33);
- return result;
+ byte result = (byte) (isServerLanguageUTF8MB4(this.serverLanguage) ? this.serverLanguage : 33);
+ return result;
}
void checkErrorPacket(RawPacket rp) throws QueryException{
@@ -571,34 +453,34 @@ void checkErrorPacket(RawPacket rp) throws QueryException{
throw new QueryException("Could not connect: " + message);
}
}
-
-
+
+
void readEOFPacket() throws QueryException, IOException {
RawPacket rp = packetFetcher.getRawPacket();
checkErrorPacket(rp);
ResultPacket resultPacket = ResultPacketFactory.createResultPacket(rp);
if (resultPacket.getResultType() != ResultPacket.ResultType.EOF) {
- throw new QueryException("Unexpected packet type " + resultPacket.getResultType() +
+ throw new QueryException("Unexpected packet type " + resultPacket.getResultType() +
"insted of EOF");
}
EOFPacket eof = (EOFPacket)resultPacket;
this.hasWarnings = eof.getWarningCount() > 0;
this.serverStatus = eof.getStatusFlags();
}
-
+
void readOKPacket() throws QueryException, IOException {
RawPacket rp = packetFetcher.getRawPacket();
checkErrorPacket(rp);
ResultPacket resultPacket = ResultPacketFactory.createResultPacket(rp);
if (resultPacket.getResultType() != ResultPacket.ResultType.OK) {
- throw new QueryException("Unexpected packet type " + resultPacket.getResultType() +
+ throw new QueryException("Unexpected packet type " + resultPacket.getResultType() +
"insted of OK");
}
OKPacket ok = (OKPacket)resultPacket;
this.hasWarnings = ok.getWarnings() > 0;
this.serverStatus = ok.getServerStatus();
}
-
+
public class PrepareResult {
public int statementId;
public MySQLColumnInformation[] columns;
@@ -610,13 +492,14 @@ public PrepareResult(int statementId, MySQLColumnInformation[] columns, MySQLCo
}
}
+ @Override
public PrepareResult prepare(String sql) throws QueryException {
try {
writer.startPacket(0);
writer.write(0x16);
writer.write(sql.getBytes("UTF8"));
writer.finishPacket();
-
+
RawPacket rp = packetFetcher.getRawPacket();
checkErrorPacket(rp);
byte b = rp.getByteBuffer().get(0);
@@ -643,7 +526,7 @@ public PrepareResult prepare(String sql) throws QueryException {
}
readEOFPacket();
}
-
+
return new PrepareResult(statementId,columns,params);
} else {
throw new QueryException("Unexpected packet returned by server, first byte " + b);
@@ -651,153 +534,202 @@ public PrepareResult prepare(String sql) throws QueryException {
} catch (IOException e) {
throw new QueryException(e.getMessage(), -1,
SQLExceptionMapper.SQLStates.CONNECTION_EXCEPTION.getSqlState(),
- e);
+ e);
}
}
-
- public synchronized void closePreparedStatement(int statementId) throws QueryException{
+
+ @Override
+ public void closePreparedStatement(int statementId) throws QueryException {
+ lock.writeLock().lock();
try {
writer.startPacket(0);
writer.write(0x19); /*COM_STMT_CLOSE*/
- writer.write(statementId);
+ writer.write(statementId);
writer.finishPacket();
} catch(IOException e) {
throw new QueryException(e.getMessage(), -1,
SQLExceptionMapper.SQLStates.CONNECTION_EXCEPTION.getSqlState(),
e);
+ } finally {
+ lock.writeLock().unlock();
}
}
- public void setHostFailed() {
+
+ public void setHostFailedWithoutProxy() {
hostFailed = true;
- failTimestamp = System.currentTimeMillis();
+ close();
}
+ public JDBCUrl getJdbcUrl() {
+ return jdbcUrl;
+ }
- public boolean shouldReconnect() {
- return (!inTransaction() && hostFailed && autoReconnect && reconnectCount < maxReconnects);
+ public static MySQLProtocol getNewProtocol(FailoverProxy proxy, JDBCUrl jdbcUrl) {
+ MySQLProtocol newProtocol = new MySQLProtocol(jdbcUrl, proxy.lock);
+ newProtocol.setProxy(proxy);
+ return newProtocol;
}
+ @Override
public boolean getAutocommit() {
- return ((serverStatus & ServerStatus.AUTOCOMMIT) != 0);
+ lock.readLock().lock();
+ try {
+ return ((serverStatus & ServerStatus.AUTOCOMMIT) != 0);
+ } finally {
+ lock.readLock().unlock();
+ }
+
}
+ public boolean isMasterConnection() {
+ return ParameterConstant.TYPE_MASTER.equals(currentHost.type);
+ }
+
+ public boolean mustBeMasterConnection() { return true; }
+
+ @Override
public boolean noBackslashEscapes() {
- return ((serverStatus & ServerStatus.NO_BACKSLASH_ESCAPES) != 0);
- }
- public void reconnectToMaster() throws IOException,QueryException, SQLException {
- SyncPacketFetcher saveFetcher = this.packetFetcher;
- PacketOutputStream saveWriter = this.writer;
- Socket saveSocket = this.socket;
- HostAddress[] addrs = jdbcUrl.getHostAddresses();
- boolean success = false;
+ lock.readLock().lock();
try {
- connect(addrs[0].host, addrs[0].port);
- try {
- close(saveFetcher, saveWriter, saveSocket);
- } catch (Exception e) {
- }
- success = true;
+ return ((serverStatus & ServerStatus.NO_BACKSLASH_ESCAPES) != 0);
} finally {
- if (!success) {
- failTimestamp = System.currentTimeMillis();
- queriesSinceFailover = 0;
- this.packetFetcher = saveFetcher;
- this.writer = saveWriter;
- this.socket = saveSocket;
- }
+ lock.readLock().unlock();
}
}
- public void connect() throws QueryException, SQLException {
+
+ public void connect() throws QueryException {
if (!isClosed()) {
close();
}
+ try {
+ connect(currentHost.host, currentHost.port);
+ return;
+ } catch (IOException e) {
+ throw new QueryException("Could not connect to " + currentHost + "." + e.getMessage(), -1, SQLExceptionMapper.SQLStates.CONNECTION_EXCEPTION.getSqlState(), e);
+ }
+ }
- HostAddress[] addrs = jdbcUrl.getHostAddresses();
+ public void connectWithoutProxy() throws QueryException {
+ if (!isClosed()) {
+ close();
+ }
+
+ List addrs = jdbcUrl.getHostAddresses();
// There could be several addresses given in the URL spec, try all of them, and throw exception if all hosts
// fail.
- for(int i = 0; i < addrs.length; i++) {
- currentHost = addrs[i];
+ for(int i = 0; i < addrs.size(); i++) {
+ currentHost = addrs.get(i);
try {
connect(currentHost.host, currentHost.port);
return;
} catch (IOException e) {
- if (i == addrs.length - 1) {
+ if (i == addrs.size() - 1) {
throw new QueryException("Could not connect to " + HostAddress.toString(addrs) +
- " : " + e.getMessage(), -1, SQLExceptionMapper.SQLStates.CONNECTION_EXCEPTION.getSqlState(), e);
+ " : " + e.getMessage(), -1, SQLExceptionMapper.SQLStates.CONNECTION_EXCEPTION.getSqlState(), e);
}
}
}
}
- public boolean isMasterConnection() {
- return currentHost == jdbcUrl.getHostAddresses()[0];
+ public boolean shouldReconnectWithoutProxy() {
+ return (!inTransaction() && hostFailed && jdbcUrl.getOptions().autoReconnect);
}
/**
- * Check if fail back to master connection is desired,
- * @return
+ * loop until found the failed connection.
+ *
+ * @param listener current listener
+ * @param addresses list of HostAddress to loop
+ * @param blacklist current blacklist
+ * @param searchFilter search parameter
+ * @throws QueryException if not found
*/
- public boolean shouldTryFailback() {
- if (isMasterConnection())
- return false;
+ public static void loop(Listener listener, final List addresses, Map blacklist, SearchFilter searchFilter) throws QueryException {
+ if (log.isLoggable(Level.FINE)) {
+ log.fine("searching for master:" + searchFilter.isSearchForMaster() + " replica:" + searchFilter.isSearchForSlave() + " addresses:" + addresses );
+ }
- if (inTransaction())
- return false;
- if (reconnectCount >= maxReconnects)
- return false;
+ MySQLProtocol protocol;
+ List loopAddresses = new LinkedList<>(addresses);
+ int maxConnectionTry = listener.getRetriesAllDown();
+ QueryException lastQueryException = null;
+ while (!loopAddresses.isEmpty() || (!searchFilter.isUniqueLoop() && maxConnectionTry > 0)) {
+ protocol = getNewProtocol(listener.getProxy(), listener.getJdbcUrl());
- long now = System.currentTimeMillis();
- if ((now - failTimestamp)/1000 > secondsBeforeRetryMaster)
- return true;
- if (queriesSinceFailover > queriesBeforeRetryMaster)
- return true;
- return false;
+ if (listener.isExplicitClosed()) return;
+ maxConnectionTry--;
+
+ try {
+ protocol.setHostAddress(loopAddresses.get(0));
+ loopAddresses.remove(0);
+
+ if (log.isLoggable(Level.FINE)) log.fine("trying to connect to " + protocol.getHostAddress());
+ protocol.connect();
+ blacklist.remove(protocol.getHostAddress());
+ if (log.isLoggable(Level.FINE)) log.fine("connected to primary " + protocol.getHostAddress());
+ listener.foundActiveMaster(protocol);
+ return;
+
+ } catch (QueryException e) {
+ blacklist.put(protocol.getHostAddress(), System.currentTimeMillis());
+ if (log.isLoggable(Level.FINE)) log.fine("Could not connect to " + protocol.getHostAddress() + " searching: " + searchFilter + " error: " + e.getMessage());
+ lastQueryException = e;
+ }
+
+ //loop is set so
+ if (loopAddresses.isEmpty() && !searchFilter.isUniqueLoop() && maxConnectionTry > 0) {
+ loopAddresses = new LinkedList<>(addresses);
+ }
+ }
+ if (lastQueryException != null) {
+ throw new QueryException("No active connection found for master", lastQueryException.getErrorCode(), lastQueryException.getSqlState(), lastQueryException);
+ }
+ throw new QueryException("No active connection found for master");
}
- public boolean inTransaction()
- {
- return ((serverStatus & ServerStatus.IN_TRANSACTION) != 0);
+ @Override
+ public boolean inTransaction() {
+ lock.readLock().lock();
+ try {
+ return ((serverStatus & ServerStatus.IN_TRANSACTION) != 0);
+ } finally {
+ lock.readLock().unlock();
+ }
}
private void setDatatypeMappingFlags() {
datatypeMappingFlags = 0;
- String tinyInt1isBit = info.getProperty("tinyInt1isBit");
- String yearIsDateType = info.getProperty("yearIsDateType");
-
- if (tinyInt1isBit == null || tinyInt1isBit.equals("1") || tinyInt1isBit.equals("true")) {
- datatypeMappingFlags |= MySQLValueObject.TINYINT1_IS_BIT;
- }
- if (yearIsDateType == null || yearIsDateType.equals("1") || yearIsDateType.equals("true")) {
- datatypeMappingFlags |= MySQLValueObject.YEAR_IS_DATE_TYPE;
- }
+ if (jdbcUrl.getOptions().tinyInt1isBit) datatypeMappingFlags |= MySQLValueObject.TINYINT1_IS_BIT;
+ if (jdbcUrl.getOptions().yearIsDateType) datatypeMappingFlags |= MySQLValueObject.YEAR_IS_DATE_TYPE;
}
- public Properties getInfo() {
- return info;
+ @Override
+ public Options getOptions() {
+ return jdbcUrl.getOptions();
}
+
void skip() throws IOException, QueryException{
- if (activeResult != null) {
- activeResult.close();
- }
+ if (activeResult != null) {
+ activeResult.close();
+ }
- while (moreResults) {
+ while (moreResults) {
getMoreResults(true);
- }
+ }
}
+ @Override
public boolean hasMoreResults() {
return moreResults;
}
- private static void close(PacketFetcher fetcher, PacketOutputStream packetOutputStream, Socket socket)
- throws QueryException
- {
+ protected static void close(PacketFetcher fetcher, PacketOutputStream packetOutputStream, Socket socket) throws QueryException {
ClosePacket closePacket = new ClosePacket();
try {
try {
- closePacket.send(packetOutputStream);
+ closePacket.send(packetOutputStream);
socket.shutdownOutput();
socket.setSoTimeout(3);
InputStream is = socket.getInputStream();
@@ -807,10 +739,10 @@ private static void close(PacketFetcher fetcher, PacketOutputStream packetOutput
packetOutputStream.close();
fetcher.close();
} catch (IOException e) {
- throw new QueryException("Could not close connection: " + e.getMessage(),
- -1,
- SQLExceptionMapper.SQLStates.CONNECTION_EXCEPTION.getSqlState(),
- e);
+ throw new QueryException("Could not close connection: " + e.getMessage(),
+ -1,
+ SQLExceptionMapper.SQLStates.CONNECTION_EXCEPTION.getSqlState(),
+ e);
} finally {
try {
socket.close();
@@ -819,32 +751,53 @@ private static void close(PacketFetcher fetcher, PacketOutputStream packetOutput
}
}
}
+
+
+ public void closeExplicit() {
+ this.explicitClosed = true;
+ close();
+ }
/**
* Closes socket and stream readers/writers
* Attempts graceful shutdown.
*/
+ @Override
public void close() {
+ if (lock != null) lock.writeLock().lock();
try {
- /* If a streaming result set is open, close it.*/
+ /* If a streaming result set is open, close it.*/
skip();
} catch (Exception e) {
/* eat exception */
}
try {
- close(packetFetcher,writer, socket);
- }
- catch (Exception e) {
+ if (log.isLoggable(Level.FINEST)) log.finest("Closing connection " + currentHost);
+ close(packetFetcher, writer, socket);
+ } catch (Exception e) {
// socket is closed, so it is ok to ignore exception
- log.info("got exception " + e + " while closing connection");
- }
- finally {
+ log.fine("got exception " + e + " while closing connection");
+ } finally {
this.connected = false;
+
+ if (lock != null) lock.writeLock().unlock();
+ }
+ }
+
+ public void rollback() {
+ lock.writeLock().lock();
+ try {
+ if (inTransaction()) executeQuery(new MySQLQuery("ROLLBACK"));
+ } catch (Exception e) {
+ /* eat exception */
+ } finally {
+ lock.writeLock().unlock();
}
}
/**
* @return true if the connection is closed
*/
+ @Override
public boolean isClosed() {
return !this.connected;
}
@@ -865,77 +818,114 @@ private SelectQueryResult createQueryResult(final ResultSetPacket packet, boolea
return CachedSelectResult.createCachedSelectResult(streamingResult);
}
- public void selectDB(final String database) throws QueryException {
+ @Override
+ public void setCatalog(final String database) throws QueryException {
+ lock.writeLock().lock();
log.finest("Selecting db " + database);
final SelectDBPacket packet = new SelectDBPacket(database);
try {
packet.send(writer);
final RawPacket rawPacket = packetFetcher.getRawPacket();
- ResultPacketFactory.createResultPacket(rawPacket);
+ ResultPacket rs = ResultPacketFactory.createResultPacket(rawPacket);
+ if (rs.getResultType() == ResultPacket.ResultType.ERROR) {
+ throw new QueryException("Could not select database '" + database +"' : "+ ((ErrorPacket) rs).getMessage());
+ }
+ this.database = database;
} catch (IOException e) {
- throw new QueryException("Could not select database: " + e.getMessage(),
+ throw new QueryException("Could not select database '" + database +"' :"+ e.getMessage(),
-1,
SQLExceptionMapper.SQLStates.CONNECTION_EXCEPTION.getSqlState(),
e);
+ } finally {
+ lock.writeLock().unlock();
}
- this.database = database;
}
+ @Override
public String getServerVersion() {
return version;
}
+ @Override
public void setReadonly(final boolean readOnly) {
this.readOnly = readOnly;
}
+ @Override
public boolean getReadonly() {
return readOnly;
}
+ @Override
+ public HostAddress getHostAddress() {
+ return currentHost;
+ }
+ public void setHostAddress(HostAddress host) {
+ this.currentHost = host;
+ this.readOnly = ParameterConstant.TYPE_SLAVE.equals(this.currentHost.type);
+ }
+ @Override
public String getHost() {
return currentHost.host;
}
+ public void setProxy(FailoverProxy proxy) {
+ this.proxy = proxy;
+ }
+ public FailoverProxy getProxy() {
+ return proxy;
+ }
+ @Override
public int getPort() {
return currentHost.port;
}
+ @Override
public String getDatabase() {
return database;
}
+ @Override
public String getUsername() {
return username;
}
+ @Override
public String getPassword() {
return password;
}
+ @Override
public boolean ping() throws QueryException {
- final MySQLPingPacket pingPacket = new MySQLPingPacket();
+ lock.writeLock().lock();
try {
- pingPacket.send(writer);
- log.finest("Sent ping packet");
- final RawPacket rawPacket = packetFetcher.getRawPacket();
- return ResultPacketFactory.createResultPacket(rawPacket).getResultType() == ResultPacket.ResultType.OK;
- } catch (IOException e) {
- throw new QueryException("Could not ping: " + e.getMessage(),
- -1,
- SQLExceptionMapper.SQLStates.CONNECTION_EXCEPTION.getSqlState(),
- e);
+ final MySQLPingPacket pingPacket = new MySQLPingPacket();
+ try {
+ pingPacket.send(writer);
+ if (log.isLoggable(Level.FINEST))log.finest("Sent ping packet");
+ final RawPacket rawPacket = packetFetcher.getRawPacket();
+ return ResultPacketFactory.createResultPacket(rawPacket).getResultType() == ResultPacket.ResultType.OK;
+ } catch (IOException e) {
+ throw new QueryException("Could not ping: " + e.getMessage(),
+ -1,
+ SQLExceptionMapper.SQLStates.CONNECTION_EXCEPTION.getSqlState(),
+ e);
+ }
+ } finally {
+ lock.writeLock().unlock();
}
}
- public QueryResult executeQuery(Query dQuery) throws QueryException, SQLException {
- return executeQuery(dQuery, false);
+ @Override
+ public QueryResult executeQuery(Query dQuery) throws QueryException {
+ return executeQuery(dQuery, false);
}
+ @Override
+ public QueryResult getResult(List dQueries, boolean streaming) throws QueryException {
- public QueryResult getResult(List dQueries, boolean streaming) throws QueryException{
- RawPacket rawPacket;
+ RawPacket rawPacket;
ResultPacket resultPacket;
try {
rawPacket = packetFetcher.getRawPacket();
@@ -943,39 +933,38 @@ public QueryResult getResult(List dQueries, boolean streaming) throws Que
if (resultPacket.getResultType() == ResultPacket.ResultType.LOCALINFILE) {
// Server request the local file (LOCAL DATA LOCAL INFILE)
- // We do accept general URLs, too. If the localInfileStream is
- // set, use that.
+ // We do accept general URLs, too. If the localInfileStream is
+ // set, use that.
InputStream is;
if (localInfileInputStream == null) {
- LocalInfilePacket localInfilePacket= (LocalInfilePacket)resultPacket;
- log.fine("sending local file " + localInfilePacket.getFileName());
+ LocalInfilePacket localInfilePacket = (LocalInfilePacket) resultPacket;
+ if (log.isLoggable(Level.FINEST)) log.finest("sending local file " + localInfilePacket.getFileName());
String localInfile = localInfilePacket.getFileName();
try {
- URL u = new URL(localInfile);
- is = u.openStream();
- } catch (IOException ioe) {
- is = new FileInputStream(localInfile);
+ URL u = new URL(localInfile);
+ is = u.openStream();
+ } catch (IOException ioe) {
+ is = new FileInputStream(localInfile);
}
} else {
- is = localInfileInputStream;
- localInfileInputStream = null;
+ is = localInfileInputStream;
+ localInfileInputStream = null;
}
- writer.sendFile(is, rawPacket.getPacketSeq()+1);
+ writer.sendFile(is, rawPacket.getPacketSeq() + 1);
is.close();
rawPacket = packetFetcher.getRawPacket();
resultPacket = ResultPacketFactory.createResultPacket(rawPacket);
}
} catch (SocketTimeoutException ste) {
- this.close();
- throw new QueryException("Could not read resultset: " + ste.getMessage(),
+ this.close();
+ throw new QueryException("Could not read resultset: " + ste.getMessage(),
-1,
SQLExceptionMapper.SQLStates.CONNECTION_EXCEPTION.getSqlState(),
ste);
- }
- catch (IOException e) {
+ } catch (IOException e) {
throw new QueryException("Could not read resultset: " + e.getMessage(),
-1,
SQLExceptionMapper.SQLStates.CONNECTION_EXCEPTION.getSqlState(),
@@ -992,7 +981,7 @@ public QueryResult getResult(List dQueries, boolean streaming) throws Que
} else {
log.warning("Got error from server: " + ((ErrorPacket) resultPacket).getMessage());
}
- throw new QueryException(ep.getMessage(), ep.getErrorNumber(), ep.getSqlState());
+ throw new QueryException(ep.getMessage(), ep.getErrorNumber(), ep.getSqlState());
case OK:
final OKPacket okpacket = (OKPacket) resultPacket;
@@ -1003,12 +992,11 @@ public QueryResult getResult(List dQueries, boolean streaming) throws Que
okpacket.getWarnings(),
okpacket.getMessage(),
okpacket.getInsertId());
- log.fine("OK, " + okpacket.getAffectedRows());
+ if (log.isLoggable(Level.FINEST)) log.finest("OK, " + okpacket.getAffectedRows());
return updateResult;
case RESULTSET:
this.hasWarnings = false;
- log.fine("SELECT executed, fetching result set");
- ResultSetPacket resultSetPacket = (ResultSetPacket)resultPacket;
+ ResultSetPacket resultSetPacket = (ResultSetPacket) resultPacket;
try {
return this.createQueryResult(resultSetPacket, streaming);
} catch (IOException e) {
@@ -1022,20 +1010,21 @@ public QueryResult getResult(List dQueries, boolean streaming) throws Que
log.severe("Could not parse result..." + resultPacket.getResultType());
throw new QueryException("Could not parse result", (short) -1, SQLExceptionMapper.SQLStates.INTERRUPTED_EXCEPTION.getSqlState());
}
+
}
- public QueryResult executeQuery(final Query query, boolean streaming) throws QueryException, SQLException {
+ @Override
+ public QueryResult executeQuery(final Query query, boolean streaming) throws QueryException {
List queries = new ArrayList();
queries.add(query);
return executeQuery(queries, streaming, false, 0);
}
- public QueryResult executeQuery(final List dQueries, boolean streaming, boolean isRewritable, int rewriteOffset) throws QueryException, SQLException {
+ public QueryResult executeQuery(final List dQueries, boolean streaming, boolean isRewritable, int rewriteOffset) throws QueryException {
for (Query query : dQueries) query.validate();
this.moreResults = false;
final StreamedQueryPacket packet = new StreamedQueryPacket(dQueries, isRewritable, rewriteOffset);
-
try {
packet.send(writer);
} catch (MaxAllowedPacketException e) {
@@ -1044,14 +1033,12 @@ public QueryResult executeQuery(final List dQueries, boolean streaming, b
} catch (IOException e) {
throw new QueryException("Could not send query: " + e.getMessage(), -1, SQLExceptionMapper.SQLStates.CONNECTION_EXCEPTION.getSqlState(), e);
}
- if (!isMasterConnection())
- queriesSinceFailover++;
+
try {
return getResult(dQueries, streaming);
} catch (QueryException qex) {
if (qex.getCause() instanceof SocketTimeoutException) {
- close();
- throw SQLExceptionMapper.getSQLException("Connection timed out");
+ throw new QueryException("Connection timed out", -1, SQLExceptionMapper.SQLStates.CONNECTION_EXCEPTION.getSqlState(), qex);
} else {
throw qex;
}
@@ -1059,7 +1046,9 @@ public QueryResult executeQuery(final List dQueries, boolean streaming, b
}
- public String getServerVariable(String variable) throws QueryException, SQLException {
+
+
+ private String getServerVariable(String variable) throws QueryException {
CachedSelectResult qr = (CachedSelectResult) executeQuery(new MySQLQuery("select @@" + variable));
try {
if (!qr.next()) {
@@ -1070,7 +1059,6 @@ public String getServerVariable(String variable) throws QueryException, SQLExcep
throw new QueryException(ioe.getMessage(), 0, "HYOOO", ioe);
}
-
try {
String value = qr.getValueObject(0).getString();
return value;
@@ -1082,27 +1070,21 @@ public String getServerVariable(String variable) throws QueryException, SQLExcep
/**
* cancels the current query - clones the current protocol and executes a query using the new connection
- *
- * thread safe
*
- * @throws QueryException
- * @throws SQLException
+ * @throws QueryException never thrown
+ * @throws IOException if Host is not responding
*/
- public void cancelCurrentQuery() throws QueryException, IOException, SQLException {
- MySQLProtocol copiedProtocol = new MySQLProtocol(jdbcUrl, username, password, info);
+ @Override
+ public void cancelCurrentQuery() throws QueryException, IOException {
+ MySQLProtocol copiedProtocol = new MySQLProtocol(jdbcUrl, null);
+ copiedProtocol.setHostAddress(getHostAddress());
+ copiedProtocol.connect();
+ //no lock, because there is already a query running that possessed the lock.
copiedProtocol.executeQuery(new MySQLQuery("KILL QUERY " + serverThreadId));
copiedProtocol.close();
}
- public boolean createDB() {
- String alias = info.getProperty("createDatabaseIfNotExist");
- return info != null
- && (info.getProperty("createDB", "").equalsIgnoreCase("true")
- || (alias != null && alias.equalsIgnoreCase("true")));
- }
-
-
-
+ @Override
public QueryResult getMoreResults(boolean streaming) throws QueryException {
if(!moreResults)
return null;
@@ -1129,11 +1111,19 @@ public static String hexdump(ByteBuffer bb, int offset) {
}
+ @Override
public boolean hasUnreadData() {
- return (activeResult != null);
+ lock.readLock().lock();
+ try {
+ return (activeResult != null);
+ } finally {
+ lock.readLock().unlock();
+ }
+
}
- public void setMaxRows(int max) throws QueryException, SQLException{
+ @Override
+ public void setMaxRows(int max) throws QueryException {
if (maxRows != max) {
if (max == 0) {
executeQuery(new MySQLQuery("set @@SQL_SELECT_LIMIT=DEFAULT"));
@@ -1143,77 +1133,167 @@ public void setMaxRows(int max) throws QueryException, SQLException{
maxRows = max;
}
}
-
+ public void setInternalMaxRows(int max) {
+ if (maxRows != max) {
+ maxRows = max;
+ }
+ }
+
+ public int getMaxRows() {
+ return maxRows;
+ }
+
void parseVersion() {
- String[] a = version.split("[^0-9]");
- if (a.length > 0)
- majorVersion = Integer.parseInt(a[0]);
- if (a.length > 1)
- minorVersion = Integer.parseInt(a[1]);
- if (a.length > 2)
- patchVersion = Integer.parseInt(a[2]);
- }
-
+ String[] a = version.split("[^0-9]");
+ if (a.length > 0)
+ majorVersion = Integer.parseInt(a[0]);
+ if (a.length > 1)
+ minorVersion = Integer.parseInt(a[1]);
+ if (a.length > 2)
+ patchVersion = Integer.parseInt(a[2]);
+ }
+
+ @Override
public int getMajorServerVersion() {
- return majorVersion;
-
+ return majorVersion;
+
}
+ @Override
public int getMinorServerVersion() {
- return minorVersion;
+ return minorVersion;
}
-
+
+ @Override
public boolean versionGreaterOrEqual(int major, int minor, int patch) {
- if (this.majorVersion > major)
- return true;
- if (this.majorVersion < major)
- return false;
+ if (this.majorVersion > major)
+ return true;
+ if (this.majorVersion < major)
+ return false;
/*
* Major versions are equal, compare minor versions
*/
- if (this.minorVersion > minor)
- return true;
- if (this.minorVersion < minor)
- return false;
-
+ if (this.minorVersion > minor)
+ return true;
+ if (this.minorVersion < minor)
+ return false;
+
/*
* Minor versions are equal, compare patch version
*/
- if (this.patchVersion > patch)
- return true;
- if (this.patchVersion < patch)
- return false;
-
+ if (this.patchVersion > patch)
+ return true;
+ if (this.patchVersion < patch)
+ return false;
+
/* Patch versions are equal => versions are equal */
- return true;
+ return true;
+ }
+ @Override
+ public void setLocalInfileInputStream(InputStream inputStream) {
+ this.localInfileInputStream = inputStream;
+ }
+
+ public int getMaxAllowedPacket() {
+ return this.maxAllowedPacket;
}
- public void setLocalInfileInputStream(InputStream inputStream) {
- this.localInfileInputStream = inputStream;
- }
-
public void setMaxAllowedPacket(int maxAllowedPacket) {
+ this.maxAllowedPacket = maxAllowedPacket;
writer.setMaxAllowedPacket(maxAllowedPacket);
}
-
+
/**
* Sets the connection timeout.
* @param timeout the timeout, in milliseconds
- * @throws SocketException
+ * @throws SocketException if there is an error in the underlying protocol, such as a TCP error.
*/
- public void setTimeout(int timeout) throws SocketException {
- this.socket.setSoTimeout(timeout);
- }
- /**
- * Returns the connection timeout in milliseconds.
- * @return
- * @throws SocketException
- */
- public int getTimeout() throws SocketException {
- return this.socket.getSoTimeout();
- }
+ @Override
+ public void setTimeout(int timeout) throws SocketException {
+ lock.writeLock().lock();
+ try {
+ this.getOptions().socketTimeout = timeout;
+ this.socket.setSoTimeout(timeout);
+ } finally {
+ lock.writeLock().unlock();
+ }
+ }
+ /**
+ * Returns the connection timeout in milliseconds.
+ * @return the connection timeout in milliseconds.
+ * @throws SocketException if there is an error in the underlying protocol, such as a TCP error.
+ */
+ @Override
+ public int getTimeout() throws SocketException {
+ return this.socket.getSoTimeout();
+ }
+
+ @Override
+ public boolean getPinGlobalTxToPhysicalConnection() {
+ return this.jdbcUrl.getOptions().pinGlobalTxToPhysicalConnection;
+ }
+
+
+ public void setTransactionIsolation(final int level) throws QueryException {
+ lock.writeLock().lock();
+ try {
+ String query = "SET SESSION TRANSACTION ISOLATION LEVEL";
+ switch (level) {
+ case Connection.TRANSACTION_READ_UNCOMMITTED:
+ query += " READ UNCOMMITTED";
+ break;
+ case Connection.TRANSACTION_READ_COMMITTED:
+ query += " READ COMMITTED";
+ break;
+ case Connection.TRANSACTION_REPEATABLE_READ:
+ query += " REPEATABLE READ";
+ break;
+ case Connection.TRANSACTION_SERIALIZABLE:
+ query += " SERIALIZABLE";
+ break;
+ default:
+ throw new QueryException("Unsupported transaction isolation level");
+ }
+ executeQuery(new MySQLQuery(query));
+ transactionIsolationLevel = level;
+ } finally {
+ lock.writeLock().unlock();
+ }
+ }
+ public int getTransactionIsolationLevel() {
+ return transactionIsolationLevel;
+ }
+
+ public boolean hasWarnings() {
+ lock.readLock().lock();
+ try {
+ return hasWarnings;
+ } finally {
+ lock.readLock().unlock();
+ }
+ }
+
+ public boolean isConnected() {
+ lock.readLock().lock();
+ try {
+ return connected;
+ } finally {
+ lock.readLock().unlock();
+ }
+ }
+
+ public long getServerThreadId(){
+ return serverThreadId;
+ }
+ public int getDatatypeMappingFlags() {
+ return datatypeMappingFlags;
+ }
+
+ public void closeIfActiveResult() {
+ if (activeResult != null) activeResult.close();
+ }
+
+ public boolean isExplicitClosed() {
+ return explicitClosed;
+ }
- public String getPinGlobalTxToPhysicalConnection() {
- return this.info.getProperty("pinGlobalTxToPhysicalConnection", "false");
- }
-
}
diff --git a/src/main/java/org/mariadb/jdbc/internal/mysql/Protocol.java b/src/main/java/org/mariadb/jdbc/internal/mysql/Protocol.java
new file mode 100644
index 000000000..4bc2af1d5
--- /dev/null
+++ b/src/main/java/org/mariadb/jdbc/internal/mysql/Protocol.java
@@ -0,0 +1,158 @@
+/*
+MariaDB Client for Java
+
+Copyright (c) 2012 Monty Program Ab.
+
+This library is free software; you can redistribute it and/or modify it under
+the terms of the GNU Lesser General Public License as published by the Free
+Software Foundation; either version 2.1 of the License, or (at your option)
+any later version.
+
+This library 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 Lesser General Public License
+for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this library; if not, write to Monty Program Ab info@montyprogram.com.
+
+This particular MariaDB Client for Java file is work
+derived from a Drizzle-JDBC. Drizzle-JDBC file which is covered by subject to
+the following copyright and notice provisions:
+
+Copyright (c) 2009-2011, Marcus Eriksson
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+Redistributions of source code must retain the above copyright notice, this list
+of conditions and the following disclaimer.
+
+Redistributions in binary form must reproduce the above copyright notice, this
+list of conditions and the following disclaimer in the documentation and/or
+other materials provided with the distribution.
+
+Neither the name of the driver nor the names of its contributors may not be
+used to endorse or promote products derived from this software without specific
+prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+OF SUCH DAMAGE.
+*/
+package org.mariadb.jdbc.internal.mysql;
+
+import org.mariadb.jdbc.HostAddress;
+import org.mariadb.jdbc.JDBCUrl;
+import org.mariadb.jdbc.internal.common.Options;
+import org.mariadb.jdbc.internal.common.QueryException;
+import org.mariadb.jdbc.internal.common.query.Query;
+import org.mariadb.jdbc.internal.common.queryresults.QueryResult;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.SocketException;
+import java.util.List;
+
+public interface Protocol {
+ MySQLProtocol.PrepareResult prepare(String sql) throws QueryException;
+
+ void closePreparedStatement(int statementId) throws QueryException;
+
+ boolean getAutocommit();
+
+ boolean noBackslashEscapes() throws QueryException;
+
+ void connect() throws QueryException;
+
+ JDBCUrl getJdbcUrl();
+ boolean inTransaction();
+ void setProxy(FailoverProxy proxy);
+ FailoverProxy getProxy();
+ void setHostAddress(HostAddress hostAddress);
+
+ Options getOptions();
+
+ boolean hasMoreResults();
+
+ void close();
+ void closeExplicit();
+
+ boolean isClosed();
+
+ void setCatalog(String database) throws QueryException;
+
+ String getServerVersion();
+
+ void setReadonly(boolean readOnly) throws QueryException;
+ boolean isConnected();
+ boolean getReadonly();
+ boolean isMasterConnection();
+ boolean mustBeMasterConnection();
+ HostAddress getHostAddress();
+ String getHost();
+
+ int getPort();
+ void rollback();
+ String getDatabase();
+
+ String getUsername();
+
+ String getPassword();
+
+ boolean ping() throws QueryException;
+
+ QueryResult executeQuery(Query dQuery) throws QueryException;
+ QueryResult executeQuery(final List dQueries, boolean streaming, boolean isRewritable, int rewriteOffset) throws QueryException;
+ QueryResult getResult(List dQuery, boolean streaming) throws QueryException;
+
+ QueryResult executeQuery(Query dQuery, boolean streaming) throws QueryException;
+
+ void cancelCurrentQuery() throws QueryException, IOException;
+
+ QueryResult getMoreResults(boolean streaming) throws QueryException;
+
+ boolean hasUnreadData();
+ boolean checkIfMaster() throws QueryException ;
+ boolean hasWarnings();
+ int getDatatypeMappingFlags();
+
+
+ void setMaxRows(int max) throws QueryException;
+ void setInternalMaxRows(int max);
+ int getMaxRows();
+
+ int getMajorServerVersion();
+
+ int getMinorServerVersion();
+
+ boolean versionGreaterOrEqual(int major, int minor, int patch);
+
+ void setLocalInfileInputStream(InputStream inputStream);
+
+ int getMaxAllowedPacket();
+
+ void setMaxAllowedPacket(int maxAllowedPacket);
+
+ void setTimeout(int timeout) throws SocketException;
+
+ int getTimeout() throws SocketException;
+
+ boolean getPinGlobalTxToPhysicalConnection();
+ long getServerThreadId();
+ void setTransactionIsolation(int level) throws QueryException;
+ int getTransactionIsolationLevel();
+ boolean isExplicitClosed();
+ void closeIfActiveResult();
+
+ void connectWithoutProxy() throws QueryException ;
+ boolean shouldReconnectWithoutProxy();
+ void setHostFailedWithoutProxy();
+
+}
diff --git a/src/main/java/org/mariadb/jdbc/internal/mysql/listener/AbstractMastersListener.java b/src/main/java/org/mariadb/jdbc/internal/mysql/listener/AbstractMastersListener.java
new file mode 100644
index 000000000..952bbeb49
--- /dev/null
+++ b/src/main/java/org/mariadb/jdbc/internal/mysql/listener/AbstractMastersListener.java
@@ -0,0 +1,359 @@
+package org.mariadb.jdbc.internal.mysql.listener;
+
+/*
+MariaDB Client for Java
+
+Copyright (c) 2012 Monty Program Ab.
+
+This library is free software; you can redistribute it and/or modify it under
+the terms of the GNU Lesser General Public License as published by the Free
+Software Foundation; either version 2.1 of the License, or (at your option)
+any later version.
+
+This library 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 Lesser General Public License
+for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this library; if not, write to Monty Program Ab info@montyprogram.com.
+
+This particular MariaDB Client for Java file is work
+derived from a Drizzle-JDBC. Drizzle-JDBC file which is covered by subject to
+the following copyright and notice provisions:
+
+Copyright (c) 2009-2011, Marcus Eriksson
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+Redistributions of source code must retain the above copyright notice, this list
+of conditions and the following disclaimer.
+
+Redistributions in binary form must reproduce the above copyright notice, this
+list of conditions and the following disclaimer in the documentation and/or
+other materials provided with the distribution.
+
+Neither the name of the driver nor the names of its contributors may not be
+used to endorse or promote products derived from this software without specific
+prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+OF SUCH DAMAGE.
+*/
+
+import org.mariadb.jdbc.HostAddress;
+import org.mariadb.jdbc.JDBCUrl;
+import org.mariadb.jdbc.internal.common.QueryException;
+import org.mariadb.jdbc.internal.common.UrlHAMode;
+import org.mariadb.jdbc.internal.common.query.MySQLQuery;
+import org.mariadb.jdbc.internal.common.query.Query;
+import org.mariadb.jdbc.internal.mysql.FailoverProxy;
+import org.mariadb.jdbc.internal.mysql.HandleErrorResult;
+import org.mariadb.jdbc.internal.mysql.Protocol;
+import org.mariadb.jdbc.internal.mysql.listener.tools.SearchFilter;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.sql.SQLException;
+import java.util.*;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+public abstract class AbstractMastersListener implements Listener {
+ private final static Logger log = Logger.getLogger(AbstractMastersListener.class.getName());
+
+ /* =========================== Failover variables ========================================= */
+ public final JDBCUrl jdbcUrl;
+ private AtomicLong masterHostFailTimestamp = new AtomicLong();
+
+ protected AtomicInteger currentConnectionAttempts = new AtomicInteger();
+ protected AtomicBoolean currentReadOnlyAsked=new AtomicBoolean();
+ private AtomicBoolean masterHostFail = new AtomicBoolean();
+ protected AtomicBoolean isLooping = new AtomicBoolean();
+ protected ScheduledFuture scheduledFailover = null;
+
+ protected Protocol currentProtocol = null;
+
+ protected FailoverProxy proxy;
+
+ protected long lastRetry = 0;
+ protected boolean explicitClosed = false;
+
+ protected ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
+
+ protected AbstractMastersListener(JDBCUrl jdbcUrl) {
+ this.jdbcUrl = jdbcUrl;
+ this.masterHostFail.set(true);
+ }
+
+ /**
+ * list the recent failedConnection
+ */
+ protected static Map blacklist = new HashMap<>();
+
+ public void setProxy(FailoverProxy proxy) {
+ this.proxy = proxy;
+ }
+ public FailoverProxy getProxy() { return this.proxy; }
+
+ public Map getBlacklist() {
+ return blacklist;
+ }
+
+ public HandleErrorResult handleFailover(Method method, Object[] args) throws Throwable {
+ if (explicitClosed) throw new QueryException("Connection has been closed !");
+ if (setMasterHostFail()) {
+ log.warning("SQL Primary node [" + this.currentProtocol.getHostAddress().toString() + "] connection fail ");
+ addToBlacklist(currentProtocol.getHostAddress());
+ }
+ return primaryFail(method, args);
+ }
+
+ /**
+ * After a failover, put the hostAddress in a static list so the other connection will not take this host in account for a time
+ * @param hostAddress the HostAddress to add to blacklist
+ */
+ public void addToBlacklist(HostAddress hostAddress) {
+ if (hostAddress != null) {
+ if (log.isLoggable(Level.FINE))log.finest("host " + hostAddress+" added to blacklist");
+ blacklist.put(hostAddress, System.currentTimeMillis());
+ }
+ }
+
+ /**
+ * Permit to remove Host to blacklist after loadBalanceBlacklistTimeout seconds
+ */
+ public void resetOldsBlackListHosts() {
+ long currentTime = System.currentTimeMillis();
+ Set currentBlackListkeys = new HashSet(blacklist.keySet());
+ for (HostAddress blackListHost : currentBlackListkeys) {
+ if (blacklist.get(blackListHost) < currentTime - jdbcUrl.getOptions().loadBalanceBlacklistTimeout * 1000) {
+ if (log.isLoggable(Level.FINE)) log.finest("host " + blackListHost+" remove of blacklist");
+ blacklist.remove(blackListHost);
+ }
+ }
+ }
+
+ protected void resetMasterFailoverData() {
+ if (masterHostFail.compareAndSet(true, false)) masterHostFailTimestamp.set(0);
+ }
+
+ /**
+ * private class to permit a timer reconnection loop
+ */
+ protected class FailLoop implements Runnable {
+ Listener listener;
+ public FailLoop(Listener listener) {
+ log.finest("launched FailLoop");
+ this.listener = listener;
+ }
+
+ public void run() {
+ if (hasHostFail()) {
+ if (log.isLoggable(Level.FINEST)) log.finest("failLoop , listener.shouldReconnect() : "+listener.shouldReconnect());
+ if (listener.shouldReconnect()) {
+ try {
+ if (currentConnectionAttempts.get() >= jdbcUrl.getOptions().failoverLoopRetries)
+ throw new QueryException("Too many reconnection attempts (" + jdbcUrl.getOptions().retriesAllDown + ")");
+ SearchFilter filter = getFilterForFailedHost();
+ filter.setUniqueLoop(true);
+ listener.reconnectFailedConnection(filter);
+ //reconnection done !
+ stopFailover();
+ } catch (Exception e) {
+ log.log(Level.FINEST, "FailLoop search connection failed", e);
+ }
+ } else {
+ if (currentConnectionAttempts.get() > jdbcUrl.getOptions().retriesAllDown) {
+ log.fine("stopping failover after too many attemps ("+currentConnectionAttempts+")");
+ stopFailover();
+ }
+ }
+ } else {
+ stopFailover();
+ }
+ log.finest("end launched FailLoop");
+ }
+ }
+
+ protected void setSessionReadOnly(boolean readOnly) throws QueryException {
+ if (this.currentProtocol.versionGreaterOrEqual(10, 0, 0)) {
+ this.currentProtocol.executeQuery(new MySQLQuery("SET SESSION TRANSACTION "+(readOnly?"READ ONLY":"READ WRITE")));
+ }
+ }
+
+ protected void stopFailover() {
+ if (isLooping.compareAndSet(true, false)) {
+ log.finest("stopping failover");
+ if (scheduledFailover!=null)scheduledFailover.cancel(false);
+ }
+ }
+
+ /**
+ * launch the scheduler loop every 250 milliseconds, to reconnect a failed connection.
+ * Will verify if there is an existing scheduler
+ * @param now now will launch the loop immediatly, 250ms after if false
+ */
+ protected void launchFailLoopIfNotlaunched(boolean now) {
+ if (isLooping.compareAndSet(false, true) && jdbcUrl.getOptions().failoverLoopRetries != 0) {
+ scheduledFailover = Executors.newSingleThreadScheduledExecutor().scheduleWithFixedDelay(new FailLoop(this), now ? 0 : 250, 250, TimeUnit.MILLISECONDS);
+ }
+ }
+
+ public Protocol getCurrentProtocol() {
+ return currentProtocol;
+ }
+
+ public long getMasterHostFailTimestamp() {
+ return masterHostFailTimestamp.get();
+ }
+
+ public boolean setMasterHostFail() {
+ if (masterHostFail.compareAndSet(false, true)) {
+ masterHostFailTimestamp.set(System.currentTimeMillis());
+ currentConnectionAttempts.set(0);
+ return true;
+ }
+ return false;
+ }
+
+ public boolean isMasterHostFail() {
+ return masterHostFail.get();
+ }
+
+ public boolean hasHostFail() {
+ return masterHostFail.get();
+ }
+
+ public SearchFilter getFilterForFailedHost() {
+ return new SearchFilter(isMasterHostFail(), false);
+ }
+
+ /**
+ * After a failover that has bean done, relaunche the operation that was in progress.
+ * In case of special operation that crash serveur, doesn't relaunched it;
+ * @param method the methode accessed
+ * @param args the parameters
+ * @return An object that indicate the result or that the exception as to be thrown
+ * @throws IllegalAccessException if the initial call is not permit
+ * @throws InvocationTargetException if there is any error relaunching initial method
+ */
+ public HandleErrorResult relaunchOperation(Method method, Object[] args) throws IllegalAccessException, InvocationTargetException{
+ HandleErrorResult handleErrorResult = new HandleErrorResult(true);
+ if (method != null) {
+ if ("executeQuery".equals(method.getName())) {
+ String query = ((Query)args[0]).getQuery().toUpperCase();
+ if (!query.equals("ALTER SYSTEM CRASH")
+ && !query.startsWith("KILL")) {
+ handleErrorResult.resultObject = method.invoke(currentProtocol, args);
+ handleErrorResult.mustThrowError = false;
+ }
+ } else {
+ handleErrorResult.resultObject = method.invoke(currentProtocol, args);
+ handleErrorResult.mustThrowError = false;
+ }
+ }
+ return handleErrorResult;
+ }
+
+ public Object invoke(Method method, Object[] args) throws Throwable {
+ return method.invoke(currentProtocol, args);
+ }
+
+
+ /**
+ * when switching between 2 connections, report existing connection parameter to the new used connection
+ * @param from used connection
+ * @param to will-be-current connection
+ * @throws QueryException if catalog cannot be set
+ */
+ public void syncConnection(Protocol from, Protocol to) throws QueryException {
+
+ if (from != null) {
+ proxy.lock.writeLock().lock();
+
+ try {
+ to.setMaxAllowedPacket(from.getMaxAllowedPacket());
+ to.setMaxRows(from.getMaxRows());
+ to.setInternalMaxRows(from.getMaxRows());
+ if (from.getTransactionIsolationLevel() != 0) {
+ to.setTransactionIsolation(from.getTransactionIsolationLevel());
+ }
+ if (from.getDatabase() != null && !"".equals(from.getDatabase()) && !from.getDatabase().equals(to.getDatabase())) {
+ to.setCatalog(from.getDatabase());
+ }
+ if (from.getAutocommit() != to.getAutocommit()) {
+ to.executeQuery(new MySQLQuery("set autocommit=" + (from.getAutocommit() ? "1" : "0")));
+ }
+ } finally {
+ proxy.lock.writeLock().unlock();
+ }
+
+ }
+ }
+ public boolean isClosed() {
+ return currentProtocol.isClosed();
+ }
+
+ public boolean isReadOnly() {
+ return currentReadOnlyAsked.get();
+ }
+
+ public boolean isExplicitClosed() {
+ return explicitClosed;
+ }
+
+ public void setExplicitClosed(boolean explicitClosed) {
+ this.explicitClosed = explicitClosed;
+ }
+
+ public int getRetriesAllDown() {
+ return jdbcUrl.getOptions().retriesAllDown;
+ }
+
+ public int getInitialTimeout() {
+ return jdbcUrl.getOptions().initialTimeout;
+ }
+
+ public boolean isAutoReconnect() {
+ return jdbcUrl.getOptions().autoReconnect;
+ }
+
+ public JDBCUrl getJdbcUrl() {
+ return jdbcUrl;
+ }
+
+ public abstract void initializeConnection() throws QueryException;
+
+ public abstract void preExecute() throws QueryException;
+
+ public abstract void preClose() throws SQLException;
+
+ public abstract boolean shouldReconnect() ;
+
+ public abstract void reconnectFailedConnection(SearchFilter filter) throws QueryException ;
+
+ public abstract void switchReadOnlyConnection(Boolean readonly) throws QueryException;
+
+ public abstract HandleErrorResult primaryFail(Method method, Object[] args) throws Throwable ;
+
+ public abstract void throwFailoverMessage(QueryException queryException, boolean reconnected) throws QueryException;
+
+ public abstract void reconnect() throws QueryException;
+
+}
diff --git a/src/main/java/org/mariadb/jdbc/internal/mysql/listener/AbstractMastersSlavesListener.java b/src/main/java/org/mariadb/jdbc/internal/mysql/listener/AbstractMastersSlavesListener.java
new file mode 100644
index 000000000..d0f78a12e
--- /dev/null
+++ b/src/main/java/org/mariadb/jdbc/internal/mysql/listener/AbstractMastersSlavesListener.java
@@ -0,0 +1,147 @@
+package org.mariadb.jdbc.internal.mysql.listener;
+
+/*
+MariaDB Client for Java
+
+Copyright (c) 2012 Monty Program Ab.
+
+This library is free software; you can redistribute it and/or modify it under
+the terms of the GNU Lesser General Public License as published by the Free
+Software Foundation; either version 2.1 of the License, or (at your option)
+any later version.
+
+This library 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 Lesser General Public License
+for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this library; if not, write to Monty Program Ab info@montyprogram.com.
+
+This particular MariaDB Client for Java file is work
+derived from a Drizzle-JDBC. Drizzle-JDBC file which is covered by subject to
+the following copyright and notice provisions:
+
+Copyright (c) 2009-2011, Marcus Eriksson
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+Redistributions of source code must retain the above copyright notice, this list
+of conditions and the following disclaimer.
+
+Redistributions in binary form must reproduce the above copyright notice, this
+list of conditions and the following disclaimer in the documentation and/or
+other materials provided with the distribution.
+
+Neither the name of the driver nor the names of its contributors may not be
+used to endorse or promote products derived from this software without specific
+prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+OF SUCH DAMAGE.
+*/
+
+import org.mariadb.jdbc.JDBCUrl;
+import org.mariadb.jdbc.internal.common.QueryException;
+import org.mariadb.jdbc.internal.mysql.FailoverProxy;
+import org.mariadb.jdbc.internal.mysql.HandleErrorResult;
+import org.mariadb.jdbc.internal.mysql.Protocol;
+import org.mariadb.jdbc.internal.mysql.listener.tools.SearchFilter;
+
+import java.lang.reflect.Method;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.logging.Logger;
+
+public abstract class AbstractMastersSlavesListener extends AbstractMastersListener {
+ private final static Logger log = Logger.getLogger(AbstractMastersSlavesListener.class.getName());
+
+ /* =========================== Failover variables ========================================= */
+ private AtomicLong secondaryHostFailTimestamp = new AtomicLong();
+ private AtomicBoolean secondaryHostFail = new AtomicBoolean();
+ protected AtomicInteger queriesSinceFailover = new AtomicInteger();
+
+ protected AbstractMastersSlavesListener(JDBCUrl jdbcUrl) {
+ super(jdbcUrl);
+ this.secondaryHostFail.set(true);
+ }
+
+ public HandleErrorResult handleFailover(Method method, Object[] args) throws Throwable {
+ if (explicitClosed) throw new QueryException("Connection has been closed !");
+ if (currentProtocol.mustBeMasterConnection()) {
+ if (setMasterHostFail()) {
+ log.warning("SQL Primary node [" + this.currentProtocol.getHostAddress().toString() + "] connection fail ");
+ addToBlacklist(currentProtocol.getHostAddress());
+ if (FailoverProxy.METHOD_EXECUTE_QUERY.equals(method.getName())) queriesSinceFailover.incrementAndGet();
+ }
+ return primaryFail(method, args);
+ } else {
+ if (setSecondaryHostFail()) {
+ log.warning("SQL Secondary node [" + this.currentProtocol.getHostAddress().toString() + "] connection fail ");
+ addToBlacklist(currentProtocol.getHostAddress());
+ if (FailoverProxy.METHOD_EXECUTE_QUERY.equals(method.getName())) queriesSinceFailover.incrementAndGet();
+ }
+ return secondaryFail(method, args);
+ }
+ }
+
+ @Override
+ protected void resetMasterFailoverData() {
+ super.resetMasterFailoverData();
+
+ //if all connection are up, reset failovers timers
+ if (!secondaryHostFail.get()) {
+ currentConnectionAttempts.set(0);
+ lastRetry = 0;
+ queriesSinceFailover.set(0);;
+ }
+ }
+
+ protected void resetSecondaryFailoverData() {
+ if (secondaryHostFail.compareAndSet(true, false)) secondaryHostFailTimestamp.set(0);
+
+ //if all connection are up, reset failovers timers
+ if (!isMasterHostFail()) {
+ currentConnectionAttempts.set(0);
+ lastRetry = 0;
+ queriesSinceFailover.set(0);
+ }
+ }
+
+ public long getSecondaryHostFailTimestamp() {
+ return secondaryHostFailTimestamp.get();
+ }
+
+ public boolean setSecondaryHostFail() {
+ if (secondaryHostFail.compareAndSet(false, true)) {
+ secondaryHostFailTimestamp.set(System.currentTimeMillis());
+ currentConnectionAttempts.set(0);
+ return true;
+ }
+ return false;
+ }
+
+ public boolean isSecondaryHostFail() {
+ return secondaryHostFail.get();
+ }
+
+ public boolean hasHostFail() {
+ return isMasterHostFail() || isSecondaryHostFail();
+ }
+ public SearchFilter getFilterForFailedHost() {
+ return new SearchFilter(isMasterHostFail(), isSecondaryHostFail());
+ }
+
+ public abstract HandleErrorResult secondaryFail(Method method, Object[] args) throws Throwable;
+ public abstract void foundActiveSecondary(Protocol newSecondaryProtocol);
+
+}
diff --git a/src/main/java/org/mariadb/jdbc/internal/mysql/listener/Listener.java b/src/main/java/org/mariadb/jdbc/internal/mysql/listener/Listener.java
new file mode 100644
index 000000000..313bb4f59
--- /dev/null
+++ b/src/main/java/org/mariadb/jdbc/internal/mysql/listener/Listener.java
@@ -0,0 +1,89 @@
+/*
+MariaDB Client for Java
+
+Copyright (c) 2012 Monty Program Ab.
+
+This library is free software; you can redistribute it and/or modify it under
+the terms of the GNU Lesser General Public License as published by the Free
+Software Foundation; either version 2.1 of the License, or (at your option)
+any later version.
+
+This library 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 Lesser General Public License
+for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this library; if not, write to Monty Program Ab info@montyprogram.com.
+
+This particular MariaDB Client for Java file is work
+derived from a Drizzle-JDBC. Drizzle-JDBC file which is covered by subject to
+the following copyright and notice provisions:
+
+Copyright (c) 2009-2011, Marcus Eriksson
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+Redistributions of source code must retain the above copyright notice, this list
+of conditions and the following disclaimer.
+
+Redistributions in binary form must reproduce the above copyright notice, this
+list of conditions and the following disclaimer in the documentation and/or
+other materials provided with the distribution.
+
+Neither the name of the driver nor the names of its contributors may not be
+used to endorse or promote products derived from this software without specific
+prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+OF SUCH DAMAGE.
+*/
+
+package org.mariadb.jdbc.internal.mysql.listener;
+
+import org.mariadb.jdbc.HostAddress;
+import org.mariadb.jdbc.JDBCUrl;
+import org.mariadb.jdbc.internal.common.QueryException;
+import org.mariadb.jdbc.internal.mysql.FailoverProxy;
+import org.mariadb.jdbc.internal.mysql.HandleErrorResult;
+import org.mariadb.jdbc.internal.mysql.Protocol;
+import org.mariadb.jdbc.internal.mysql.listener.tools.SearchFilter;
+
+import java.lang.reflect.Method;
+import java.sql.SQLException;
+import java.util.Map;
+
+public interface Listener {
+ void setProxy(FailoverProxy proxy);
+ FailoverProxy getProxy();
+ void initializeConnection() throws QueryException;
+ void preExecute() throws QueryException;
+ void preClose() throws SQLException;
+ boolean shouldReconnect();
+ void reconnectFailedConnection(SearchFilter filter) throws QueryException;
+ void switchReadOnlyConnection(Boolean readonly) throws QueryException ;
+ HandleErrorResult primaryFail(Method method, Object[] args) throws Throwable;
+ Object invoke(Method method, Object[] args) throws Throwable;
+ HandleErrorResult handleFailover(Method method, Object[] args) throws Throwable;
+ void foundActiveMaster(Protocol protocol) throws QueryException;
+ Map getBlacklist();
+ void syncConnection(Protocol from, Protocol to) throws QueryException;
+ JDBCUrl getJdbcUrl();
+ void throwFailoverMessage(QueryException queryException, boolean reconnected) throws QueryException;
+ int getInitialTimeout();
+ boolean isAutoReconnect();
+ int getRetriesAllDown();
+ boolean isExplicitClosed();
+ void setExplicitClosed(boolean explicitClosed);
+ void reconnect() throws QueryException;
+ boolean isReadOnly();
+ boolean isClosed();
+}
diff --git a/src/main/java/org/mariadb/jdbc/internal/mysql/listener/impl/AuroraListener.java b/src/main/java/org/mariadb/jdbc/internal/mysql/listener/impl/AuroraListener.java
new file mode 100644
index 000000000..38326a443
--- /dev/null
+++ b/src/main/java/org/mariadb/jdbc/internal/mysql/listener/impl/AuroraListener.java
@@ -0,0 +1,242 @@
+/*
+MariaDB Client for Java
+
+Copyright (c) 2012 Monty Program Ab.
+
+This library is free software; you can redistribute it and/or modify it under
+the terms of the GNU Lesser General Public License as published by the Free
+Software Foundation; either version 2.1 of the License, or (at your option)
+any later version.
+
+This library 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 Lesser General Public License
+for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this library; if not, write to Monty Program Ab info@montyprogram.com.
+
+This particular MariaDB Client for Java file is work
+derived from a Drizzle-JDBC. Drizzle-JDBC file which is covered by subject to
+the following copyright and notice provisions:
+
+Copyright (c) 2009-2011, Marcus Eriksson
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+Redistributions of source code must retain the above copyright notice, this list
+of conditions and the following disclaimer.
+
+Redistributions in binary form must reproduce the above copyright notice, this
+list of conditions and the following disclaimer in the documentation and/or
+other materials provided with the distribution.
+
+Neither the name of the driver nor the names of its contributors may not be
+used to endorse or promote products derived from this software without specific
+prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+OF SUCH DAMAGE.
+*/
+
+package org.mariadb.jdbc.internal.mysql.listener.impl;
+
+import org.mariadb.jdbc.HostAddress;
+import org.mariadb.jdbc.JDBCUrl;
+import org.mariadb.jdbc.internal.common.QueryException;
+import org.mariadb.jdbc.internal.common.query.MySQLQuery;
+import org.mariadb.jdbc.internal.common.queryresults.SelectQueryResult;
+import org.mariadb.jdbc.internal.mysql.*;
+import org.mariadb.jdbc.internal.mysql.listener.tools.SearchFilter;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+public class AuroraListener extends MastersSlavesListener {
+ private final static Logger log = Logger.getLogger(AuroraListener.class.getName());
+
+ public AuroraListener(JDBCUrl jdbcUrl) {
+ super(jdbcUrl);
+ masterProtocol = null;
+ secondaryProtocol = null;
+ lastQueryTime = System.currentTimeMillis();
+ }
+
+ @Override
+ public void initializeConnection() throws QueryException {
+ if (jdbcUrl.getOptions().validConnectionTimeout != 0)
+ scheduledPing = executorService.scheduleWithFixedDelay(new PingLoop(this), jdbcUrl.getOptions().validConnectionTimeout, jdbcUrl.getOptions().validConnectionTimeout, TimeUnit.SECONDS);
+ try {
+ reconnectFailedConnection(new SearchFilter(true, true, true));
+ } catch (QueryException e) {
+ log.log(Level.FINEST, "initializeConnection failed", e);
+ checkInitialConnection();
+ throw e;
+ }
+ }
+
+ /**
+ * search a valid connection for failed one.
+ * A Node can be a master or a replica depending on the cluster state.
+ * so search for each host until found all the failed connection.
+ * By default, search for the host not down, and recheck the down one after if not found valid connections.
+ *
+ * @throws QueryException if a connection asked is not found
+ */
+ @Override
+ public void reconnectFailedConnection(SearchFilter searchFilter) throws QueryException {
+ if (log.isLoggable(Level.FINEST)) log.finest("search connection searchFilter=" + searchFilter);
+ currentConnectionAttempts.incrementAndGet();
+ resetOldsBlackListHosts();
+
+ //put the list in the following order
+ // - random order not connected host
+ // - random order blacklist host
+ // - random order connected host
+ List loopAddress = new LinkedList<>(jdbcUrl.getHostAddresses());
+ loopAddress.removeAll(blacklist.keySet());
+ Collections.shuffle(loopAddress);
+ List blacklistShuffle = new LinkedList<>(blacklist.keySet());
+ Collections.shuffle(blacklistShuffle);
+ loopAddress.addAll(blacklistShuffle);
+
+ //put connected at end
+ if (masterProtocol != null && !isMasterHostFail()) {
+ loopAddress.remove(masterProtocol.getHostAddress());
+ //loopAddress.add(masterProtocol.getHostAddress());
+ }
+
+ if (!isSecondaryHostFail()) {
+ if (secondaryProtocol != null) {
+ loopAddress.remove(secondaryProtocol.getHostAddress());
+ //loopAddress.add(secondaryProtocol.getHostAddress());
+ }
+ if (isMasterHostFail()) {
+ log.fine("searching probableMaster");
+ HostAddress probableMaster = searchByStartName(secondaryProtocol, loopAddress);
+
+ if (probableMaster != null) {
+ loopAddress.remove(probableMaster);
+ loopAddress.add(0, probableMaster);
+ } else if (log.isLoggable(Level.FINEST)) log.finest("probableMaster not found");
+ }
+ }
+
+ if (((searchFilter.isSearchForMaster() && isMasterHostFail())|| (searchFilter.isSearchForSlave() && isSecondaryHostFail())) || searchFilter.isInitialConnection()) {
+ AuroraProtocol.loop(this, loopAddress, blacklist, searchFilter);
+ }
+ }
+
+
+ /**
+ * Aurora replica doesn't have the master endpoint but the master instance name.
+ * since the end point normally use the instance name like "instancename.some_ugly_string.region.rds.amazonaws.com", if an endpoint start with this instance name, it will be checked first.
+ * @return the probable master address or null if not found
+ * @param secondaryProtocol the current secondary protocol
+ * @param loopAddress list of possible hosts
+ */
+ public HostAddress searchByStartName(Protocol secondaryProtocol, List loopAddress) {
+ if (!isSecondaryHostFail()) {
+ SelectQueryResult queryResult = null;
+ try {
+ proxy.lock.writeLock().lock();
+ try {
+ queryResult = (SelectQueryResult) secondaryProtocol.executeQuery(new MySQLQuery("select server_id from information_schema.replica_host_status where session_id = 'MASTER_SESSION_ID'"));
+ queryResult.next();
+ } finally {
+ proxy.lock.writeLock().unlock();
+ }
+ String masterHostName = queryResult.getValueObject(0).getString();
+ for (int i = 0; i < loopAddress.size(); i++) {
+ if (loopAddress.get(i).host.startsWith(masterHostName)) {
+ if (log.isLoggable(Level.FINEST)) log.finest("master probably " + loopAddress.get(i));
+ return loopAddress.get(i);
+ }
+ }
+ } catch (IOException ioe) {
+ log.log(Level.FINEST, "searchByStartName failed", ioe);
+ //eat exception
+ } catch (QueryException qe) {
+ if (proxy.hasToHandleFailover(qe)) {
+ if (setSecondaryHostFail()) {
+ log.warning("SQL Secondary node [" + this.currentProtocol.getHostAddress().toString() + "] connection fail ");
+ addToBlacklist(currentProtocol.getHostAddress());
+ }
+ }
+ } finally {
+ if (queryResult != null) {
+ queryResult.close();
+ }
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public void checkIfTypeHaveChanged(SearchFilter searchFilter) throws QueryException {
+ if (!isMasterHostFail()) {
+ try {
+ if (masterProtocol != null && !masterProtocol.checkIfMaster()) {
+ //master has been demote, is now secondary
+ setMasterHostFail();
+ if (isSecondaryHostFail()) foundActiveSecondary(masterProtocol);
+ if (searchFilter != null) searchFilter.setSearchForSlave(false);
+ launchFailLoopIfNotlaunched(false);
+ }
+ } catch (QueryException e) {
+ try {
+ masterProtocol.ping();
+ } catch (QueryException ee) {
+ proxy.lock.writeLock().lock();
+ try {
+ masterProtocol.close();
+ } finally {
+ proxy.lock.writeLock().unlock();
+ }
+ if (setMasterHostFail()) addToBlacklist(masterProtocol.getHostAddress());
+ }
+ launchFailLoopIfNotlaunched(false);
+ }
+ }
+
+ if (!isSecondaryHostFail()) {
+ try {
+ if (secondaryProtocol != null && secondaryProtocol.checkIfMaster()) {
+ //secondary has been promoted to master
+ setSecondaryHostFail();
+ if (isMasterHostFail()) foundActiveMaster(secondaryProtocol);
+ if (searchFilter != null) searchFilter.setSearchForMaster(false);
+ launchFailLoopIfNotlaunched(false);
+ }
+ } catch (QueryException e) {
+ try {
+ this.secondaryProtocol.ping();
+ } catch (Exception ee) {
+ proxy.lock.writeLock().lock();
+ try {
+ secondaryProtocol.close();
+ } finally {
+ proxy.lock.writeLock().unlock();
+ }
+ if (setSecondaryHostFail()) addToBlacklist(this.secondaryProtocol.getHostAddress());
+ launchFailLoopIfNotlaunched(false);
+ }
+
+ }
+ }
+ }
+
+}
diff --git a/src/main/java/org/mariadb/jdbc/internal/mysql/listener/impl/MastersFailoverListener.java b/src/main/java/org/mariadb/jdbc/internal/mysql/listener/impl/MastersFailoverListener.java
new file mode 100644
index 000000000..32a7269b9
--- /dev/null
+++ b/src/main/java/org/mariadb/jdbc/internal/mysql/listener/impl/MastersFailoverListener.java
@@ -0,0 +1,293 @@
+/*
+MariaDB Client for Java
+
+Copyright (c) 2012 Monty Program Ab.
+
+This library is free software; you can redistribute it and/or modify it under
+the terms of the GNU Lesser General Public License as published by the Free
+Software Foundation; either version 2.1 of the License, or (at your option)
+any later version.
+
+This library 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 Lesser General Public License
+for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this library; if not, write to Monty Program Ab info@montyprogram.com.
+
+This particular MariaDB Client for Java file is work
+derived from a Drizzle-JDBC. Drizzle-JDBC file which is covered by subject to
+the following copyright and notice provisions:
+
+Copyright (c) 2009-2011, Marcus Eriksson
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+Redistributions of source code must retain the above copyright notice, this list
+of conditions and the following disclaimer.
+
+Redistributions in binary form must reproduce the above copyright notice, this
+list of conditions and the following disclaimer in the documentation and/or
+other materials provided with the distribution.
+
+Neither the name of the driver nor the names of its contributors may not be
+used to endorse or promote products derived from this software without specific
+prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+OF SUCH DAMAGE.
+*/
+
+package org.mariadb.jdbc.internal.mysql.listener.impl;
+
+import org.mariadb.jdbc.HostAddress;
+import org.mariadb.jdbc.JDBCUrl;
+import org.mariadb.jdbc.internal.SQLExceptionMapper;
+import org.mariadb.jdbc.internal.common.QueryException;
+import org.mariadb.jdbc.internal.common.UrlHAMode;
+import org.mariadb.jdbc.internal.mysql.HandleErrorResult;
+import org.mariadb.jdbc.internal.mysql.MySQLProtocol;
+import org.mariadb.jdbc.internal.mysql.Protocol;
+import org.mariadb.jdbc.internal.mysql.listener.AbstractMastersListener;
+import org.mariadb.jdbc.internal.mysql.listener.tools.SearchFilter;
+
+import java.lang.reflect.Method;
+import java.sql.SQLException;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+
+public class MastersFailoverListener extends AbstractMastersListener {
+ private final static Logger log = Logger.getLogger(MastersFailoverListener.class.getName());
+ private final UrlHAMode mode;
+
+ public MastersFailoverListener(final JDBCUrl jdbcUrl) {
+ super(jdbcUrl);
+ this.mode = jdbcUrl.getHaMode();
+
+ }
+
+ public void initializeConnection() throws QueryException {
+ this.currentProtocol = null;
+ log.finest("launching initial loop");
+ reconnectFailedConnection(new SearchFilter(true, false));
+ log.finest("launching initial loop end");
+
+ }
+
+ public void preExecute() throws QueryException {
+ //if connection is closed or failed on slave
+ if (this.currentProtocol != null && this.currentProtocol.isClosed()) {
+ if (!isExplicitClosed() && jdbcUrl.getOptions().autoReconnect) {
+ try {
+ reconnectFailedConnection(new SearchFilter(isMasterHostFail(), false, !currentReadOnlyAsked.get(), currentReadOnlyAsked.get()));
+ } catch (QueryException e) {
+ }
+ } else
+ throw new QueryException("Connection is closed", (short) -1, SQLExceptionMapper.SQLStates.CONNECTION_EXCEPTION.getSqlState());
+ }
+ }
+
+ public boolean shouldReconnect() {
+ return isMasterHostFail();
+ }
+
+ @Override
+ public void preClose() throws SQLException {
+ setExplicitClosed(true);
+ proxy.lock.writeLock().lock();
+ try {
+ if (currentProtocol != null && this.currentProtocol.isConnected()) this.currentProtocol.close();
+ } finally {
+ if (!UrlHAMode.NONE.equals(mode)) {
+ proxy.lock.writeLock().unlock();
+ if (scheduledFailover != null) {
+ scheduledFailover.cancel(true);
+ isLooping.set(false);
+ }
+ executorService.shutdownNow();
+ try {
+ executorService.awaitTermination(15, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ log.finest("executorService interrupted");
+ }
+ }
+ }
+ log.finest("preClose connections");
+ }
+
+ @Override
+ public HandleErrorResult primaryFail(Method method, Object[] args) throws Throwable {
+ boolean alreadyClosed = !currentProtocol.isConnected();
+ try {
+ if (currentProtocol != null && currentProtocol.isConnected() && currentProtocol.ping()) {
+ if (log.isLoggable(Level.FINE))
+ log.fine("Primary node [" + currentProtocol.getHostAddress().toString() + "] connection re-established");
+
+ // if in transaction cannot be sure that the last query has been received by server of not, so rollback.
+ if (currentProtocol.inTransaction()) {
+ currentProtocol.rollback();
+ }
+ return new HandleErrorResult(true);
+ }
+ } catch (QueryException e) {
+ proxy.lock.writeLock().lock();
+ try {
+ currentProtocol.close();
+ } finally {
+ proxy.lock.writeLock().unlock();
+ }
+ if (setMasterHostFail()) addToBlacklist(currentProtocol.getHostAddress());
+ }
+
+ try {
+ reconnectFailedConnection(new SearchFilter(true, false));
+ if (!UrlHAMode.NONE.equals(mode)) launchFailLoopIfNotlaunched(true);
+ if (alreadyClosed) return relaunchOperation(method, args);
+ return new HandleErrorResult(true);
+ } catch (Exception e) {
+ if (!UrlHAMode.NONE.equals(mode)) launchFailLoopIfNotlaunched(true);
+ return new HandleErrorResult();
+ }
+ }
+
+ /**
+ * Loop to connect
+ *
+ * @throws QueryException if there is any error during reconnection
+ * @throws QueryException sqlException
+ */
+ @Override
+ public void reconnectFailedConnection(SearchFilter searchFilter) throws QueryException {
+ if (log.isLoggable(Level.FINEST)) log.finest("search connection searchFilter=" + searchFilter);
+ currentConnectionAttempts.incrementAndGet();
+ resetOldsBlackListHosts();
+
+ List loopAddress = new LinkedList<>(jdbcUrl.getHostAddresses());
+ if (UrlHAMode.FAILOVER.equals(mode)) {
+ //put the list in the following order
+ // - random order not connected host
+ // - random order blacklist host
+ // - random order connected host
+ loopAddress.removeAll(blacklist.keySet());
+ Collections.shuffle(loopAddress);
+ List blacklistShuffle = new LinkedList<>(blacklist.keySet());
+ Collections.shuffle(blacklistShuffle);
+ loopAddress.addAll(blacklistShuffle);
+ } else {
+ //order in sequence
+ loopAddress.removeAll(blacklist.keySet());
+ loopAddress.addAll(blacklist.keySet());
+ }
+
+ //put connected at end
+ if (currentProtocol != null && !isMasterHostFail()) {
+ loopAddress.remove(currentProtocol.getHostAddress());
+ //loopAddress.add(currentProtocol.getHostAddress());
+ }
+
+ MySQLProtocol.loop(this, loopAddress, blacklist, searchFilter);
+
+ //if no error, reset failover variables
+ resetMasterFailoverData();
+ }
+
+
+ public void switchReadOnlyConnection(Boolean mustBeReadOnly) throws QueryException {
+ if (currentReadOnlyAsked.compareAndSet(!mustBeReadOnly, mustBeReadOnly)) {
+ setSessionReadOnly(mustBeReadOnly);
+ }
+ }
+
+ /**
+ * method called when a new Master connection is found after a fallback
+ * @param protocol the new active connection
+ */
+ @Override
+ public void foundActiveMaster(Protocol protocol) throws QueryException {
+ if (isExplicitClosed()) {
+ proxy.lock.writeLock().lock();
+ try {
+ protocol.close();
+ } finally {
+ proxy.lock.writeLock().unlock();
+ }
+ return;
+ }
+ syncConnection(this.currentProtocol, protocol);
+ proxy.lock.writeLock().lock();
+ try {
+ if (currentProtocol != null && !currentProtocol.isClosed()) currentProtocol.close();
+ currentProtocol = protocol;
+ } finally {
+ proxy.lock.writeLock().unlock();
+ }
+
+ if (currentReadOnlyAsked.get()) {
+ setSessionReadOnly(true);
+ }
+
+ if (log.isLoggable(Level.FINE)) {
+ if (getMasterHostFailTimestamp() > 0) {
+ log.fine("new primary node [" + currentProtocol.getHostAddress().toString() + "] connection established after " + (System.currentTimeMillis() - getMasterHostFailTimestamp()));
+ } else log.fine("new primary node [" + currentProtocol.getHostAddress().toString() + "] connection established");
+ }
+
+ resetMasterFailoverData();
+ stopFailover();
+ }
+
+
+ /**
+ * Throw a human readable message after a failoverException
+ *
+ * @param queryException internal error
+ * @param reconnected connection status
+ * @throws QueryException error with failover information
+ */
+ @Override
+ public void throwFailoverMessage(QueryException queryException, boolean reconnected) throws QueryException {
+ HostAddress hostAddress = (currentProtocol != null) ? currentProtocol.getHostAddress() : null;
+
+ String firstPart = "Communications link failure with primary" + ((hostAddress != null) ? " host " + hostAddress.host + ":" + hostAddress.port : "") + ". ";
+ String error = "";
+ if (jdbcUrl.getOptions().autoReconnect) {
+ if (isMasterHostFail())
+ error += " Driver will reconnect automatically in a few millisecond or during next query if append before";
+ else error += " Driver as successfully reconnect connection";
+ } else {
+ if (reconnected) {
+ error += " Driver as reconnect connection";
+ } else {
+ if (shouldReconnect()) {
+ error += " Driver will try to reconnect automatically in a few millisecond or during next query if append before";
+ }
+ }
+ }
+ if (queryException == null) {
+ throw new QueryException(firstPart + error, (short) -1, SQLExceptionMapper.SQLStates.CONNECTION_EXCEPTION.getSqlState());
+ } else {
+ error = queryException.getMessage() + ". " + error;
+ queryException.setMessage(firstPart + error);
+ throw queryException;
+ }
+ }
+
+
+ public void reconnect() throws QueryException {
+ reconnectFailedConnection(new SearchFilter(true, false));
+ }
+}
diff --git a/src/main/java/org/mariadb/jdbc/internal/mysql/listener/impl/MastersSlavesListener.java b/src/main/java/org/mariadb/jdbc/internal/mysql/listener/impl/MastersSlavesListener.java
new file mode 100644
index 000000000..3e9b245c0
--- /dev/null
+++ b/src/main/java/org/mariadb/jdbc/internal/mysql/listener/impl/MastersSlavesListener.java
@@ -0,0 +1,685 @@
+/*
+MariaDB Client for Java
+
+Copyright (c) 2012 Monty Program Ab.
+
+This library is free software; you can redistribute it and/or modify it under
+the terms of the GNU Lesser General Public License as published by the Free
+Software Foundation; either version 2.1 of the License, or (at your option)
+any later version.
+
+This library 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 Lesser General Public License
+for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this library; if not, write to Monty Program Ab info@montyprogram.com.
+
+This particular MariaDB Client for Java file is work
+derived from a Drizzle-JDBC. Drizzle-JDBC file which is covered by subject to
+the following copyright and notice provisions:
+
+Copyright (c) 2009-2011, Marcus Eriksson
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+Redistributions of source code must retain the above copyright notice, this list
+of conditions and the following disclaimer.
+
+Redistributions in binary form must reproduce the above copyright notice, this
+list of conditions and the following disclaimer in the documentation and/or
+other materials provided with the distribution.
+
+Neither the name of the driver nor the names of its contributors may not be
+used to endorse or promote products derived from this software without specific
+prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+OF SUCH DAMAGE.
+*/
+
+package org.mariadb.jdbc.internal.mysql.listener.impl;
+
+import org.mariadb.jdbc.HostAddress;
+import org.mariadb.jdbc.JDBCUrl;
+import org.mariadb.jdbc.internal.SQLExceptionMapper;
+import org.mariadb.jdbc.internal.common.QueryException;
+import org.mariadb.jdbc.internal.mysql.HandleErrorResult;
+import org.mariadb.jdbc.internal.mysql.Protocol;
+import org.mariadb.jdbc.internal.mysql.MastersSlavesProtocol;
+import org.mariadb.jdbc.internal.mysql.listener.AbstractMastersSlavesListener;
+import org.mariadb.jdbc.internal.mysql.listener.tools.SearchFilter;
+
+import java.lang.reflect.Method;
+import java.sql.SQLException;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * this class handle the operation when multiple hosts.
+ */
+public class MastersSlavesListener extends AbstractMastersSlavesListener {
+ private final static Logger log = Logger.getLogger(MastersSlavesListener.class.getName());
+
+ protected Protocol masterProtocol;
+ protected Protocol secondaryProtocol;
+ protected long lastQueryTime = 0;
+ protected ScheduledFuture scheduledPing = null;
+
+ public MastersSlavesListener(final JDBCUrl jdbcUrl) {
+ super(jdbcUrl);
+ masterProtocol = null;
+ secondaryProtocol = null;
+ lastQueryTime = System.currentTimeMillis();
+
+ }
+
+ public void initializeConnection() throws QueryException {
+ if (jdbcUrl.getOptions().validConnectionTimeout != 0)
+ scheduledPing = executorService.scheduleWithFixedDelay(new PingLoop(this), jdbcUrl.getOptions().validConnectionTimeout, jdbcUrl.getOptions().validConnectionTimeout, TimeUnit.SECONDS);
+ try {
+ reconnectFailedConnection(new SearchFilter(true, true, true));
+ } catch (QueryException e) {
+ log.log(Level.FINEST, "initializeConnection failed", e);
+ checkInitialConnection();
+ throwFailoverMessage(e, false);
+ }
+ }
+
+ protected void checkInitialConnection() {
+ if (this.masterProtocol != null && !this.masterProtocol.isConnected()) {
+ setMasterHostFail();
+ }
+ if (this.secondaryProtocol != null && !this.secondaryProtocol.isConnected()) {
+ setSecondaryHostFail();
+ }
+ launchFailLoopIfNotlaunched(false);
+ }
+
+ public void preClose() throws SQLException {
+ setExplicitClosed(true);
+ log.finest("preClose connections");
+ proxy.lock.writeLock().lock();
+ try {
+ if (masterProtocol != null && this.masterProtocol.isConnected()) this.masterProtocol.close();
+ if (secondaryProtocol != null && this.secondaryProtocol.isConnected()) this.secondaryProtocol.close();
+ } finally {
+ proxy.lock.writeLock().unlock();
+ if (scheduledPing != null) scheduledPing.cancel(true);
+
+ if (scheduledFailover != null) {
+ scheduledFailover.cancel(true);
+ isLooping.set(false);
+ }
+ executorService.shutdownNow();
+ try {
+ executorService.awaitTermination(15, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ log.finest("executorService interrupted");
+ }
+ }
+ log.finest("preClose connections end");
+ }
+
+ @Override
+ public void preExecute() throws QueryException {
+ //if connection is closed or failed on slave
+ if (this.currentProtocol != null &&
+ (this.currentProtocol.isClosed() ||
+
+ (!currentReadOnlyAsked.get() && !currentProtocol.isMasterConnection()))) {
+ queriesSinceFailover.incrementAndGet();
+ if (!isExplicitClosed() && jdbcUrl.getOptions().autoReconnect) {
+ try {
+ reconnectFailedConnection(new SearchFilter(isMasterHostFail(), isSecondaryHostFail(), !currentReadOnlyAsked.get(), currentReadOnlyAsked.get()));
+ } catch (QueryException e) {
+ }
+ } else
+ throw new QueryException("Connection is closed", (short) -1, SQLExceptionMapper.SQLStates.CONNECTION_EXCEPTION.getSqlState());
+ }
+ if (isMasterHostFail() || isSecondaryHostFail()) {
+ queriesSinceFailover.incrementAndGet();
+ }
+
+ if (jdbcUrl.getOptions().validConnectionTimeout != 0) lastQueryTime = System.currentTimeMillis();
+ }
+
+
+ /**
+ * When failing to a different type of host, when to retry
+ * So he doesn't appear here.
+ *
+ * @return true if should reconnect.
+ */
+ public boolean shouldReconnect() {
+ if (isMasterHostFail() || isSecondaryHostFail()) {
+ if (currentConnectionAttempts.get() > jdbcUrl.getOptions().retriesAllDown) return false;
+ long now = System.currentTimeMillis();
+
+ if (isMasterHostFail()) {
+ if (jdbcUrl.getOptions().queriesBeforeRetryMaster > 0 && queriesSinceFailover.get() >= jdbcUrl.getOptions().queriesBeforeRetryMaster)
+ return true;
+ if (jdbcUrl.getOptions().secondsBeforeRetryMaster > 0 && (now - getMasterHostFailTimestamp()) >= jdbcUrl.getOptions().secondsBeforeRetryMaster * 1000)
+ return true;
+ }
+
+ if (isSecondaryHostFail()) {
+ if (jdbcUrl.getOptions().secondsBeforeRetryMaster > 0 && (now - getSecondaryHostFailTimestamp()) >= jdbcUrl.getOptions().secondsBeforeRetryMaster * 1000)
+ return true;
+ }
+ }
+ return false;
+ }
+
+
+ /**
+ * Loop to connect
+ *
+ * @throws QueryException if there is any error during reconnection
+ * @throws QueryException sqlException
+ */
+ public void reconnectFailedConnection(SearchFilter searchFilter) throws QueryException {
+ if (log.isLoggable(Level.FINEST)) log.fine("search connection searchFilter=" + searchFilter);
+ currentConnectionAttempts.incrementAndGet();
+ resetOldsBlackListHosts();
+
+ //put the list in the following order
+ // - random order not connected host
+ // - random order blacklist host
+ // - random order connected host
+ List loopAddress = new LinkedList<>(jdbcUrl.getHostAddresses());
+ loopAddress.removeAll(blacklist.keySet());
+ Collections.shuffle(loopAddress);
+ List blacklistShuffle = new LinkedList<>(blacklist.keySet());
+ Collections.shuffle(blacklistShuffle);
+ loopAddress.addAll(blacklistShuffle);
+
+ //put connected at end
+ if (masterProtocol != null && !isMasterHostFail()) {
+ loopAddress.remove(masterProtocol.getHostAddress());
+ //loopAddress.add(masterProtocol.getHostAddress());
+ }
+
+ if (secondaryProtocol != null && !isSecondaryHostFail()) {
+ loopAddress.remove(secondaryProtocol.getHostAddress());
+ //loopAddress.add(secondaryProtocol.getHostAddress());
+ }
+
+ if (((searchFilter.isSearchForMaster() && isMasterHostFail()) || (searchFilter.isSearchForSlave() && isSecondaryHostFail())) || searchFilter.isInitialConnection()) {
+ MastersSlavesProtocol.loop(this, loopAddress, blacklist, searchFilter);
+ }
+ }
+
+ /**
+ * method called when a new Master connection is found after a fallback
+ *
+ * @param newMasterProtocol the new active connection
+ */
+ public void foundActiveMaster(Protocol newMasterProtocol) {
+ if (isExplicitClosed()) {
+ newMasterProtocol.close();
+ return;
+ }
+ log.finest("log $$1 " + proxy.lock.getReadLockCount() + " " + proxy.lock.getWriteHoldCount());
+
+ proxy.lock.writeLock().lock();
+ try {
+ log.finest("log $$2 " + proxy.lock.getReadLockCount()+ " " + proxy.lock.getWriteHoldCount());
+ if (masterProtocol != null && !masterProtocol.isClosed()) masterProtocol.close();
+ this.masterProtocol = (MastersSlavesProtocol) newMasterProtocol;
+ if (!currentReadOnlyAsked.get() || isSecondaryHostFail()) {
+ //actually on a secondary read-only because master was unknown.
+ //So select master as currentConnection
+ try {
+ syncConnection(currentProtocol, this.masterProtocol);
+ } catch (Exception e) {
+ log.log(Level.FINE, "Some error append during connection parameter synchronisation : ", e);
+ }
+ log.finest("switching current connection to master connection");
+ currentProtocol = this.masterProtocol;
+ }
+
+ if (log.isLoggable(Level.FINE)) {
+ if (getMasterHostFailTimestamp() > 0) {
+ log.fine("new primary node [" + newMasterProtocol.getHostAddress().toString() + "] connection established after " + (System.currentTimeMillis() - getMasterHostFailTimestamp()));
+ } else
+ log.fine("new primary node [" + newMasterProtocol.getHostAddress().toString() + "] connection established");
+ }
+ resetMasterFailoverData();
+ if (!isSecondaryHostFail()) stopFailover();
+ } finally {
+ proxy.lock.writeLock().unlock();
+ log.finest("log $$3 " + proxy.lock.getReadLockCount()+ " " + proxy.lock.getWriteHoldCount());
+
+ }
+
+ }
+
+
+ /**
+ * method called when a new secondary connection is found after a fallback
+ *
+ * @param newSecondaryProtocol the new active connection
+ */
+ public void foundActiveSecondary(Protocol newSecondaryProtocol) {
+ if (isExplicitClosed()) {
+ newSecondaryProtocol.close();
+ return;
+ }
+
+ proxy.lock.writeLock().lock();
+ try {
+ if (secondaryProtocol != null && !secondaryProtocol.isClosed()) secondaryProtocol.close();
+
+ log.finest("found active secondary connection");
+ this.secondaryProtocol = newSecondaryProtocol;
+
+ //if asked to be on read only connection, switching to this new connection
+ if (currentReadOnlyAsked.get() || (jdbcUrl.getOptions().failOnReadOnly && !currentReadOnlyAsked.get() && isMasterHostFail())) {
+ try {
+ syncConnection(currentProtocol, this.secondaryProtocol);
+ } catch (Exception e) {
+ log.log(Level.FINE, "Some error append during connection parameter synchronisation : ", e);
+ }
+ currentProtocol = this.secondaryProtocol;
+ }
+
+ if (log.isLoggable(Level.FINE)) {
+ if (getSecondaryHostFailTimestamp() > 0) {
+ log.fine("new active secondary node [" + newSecondaryProtocol.getHostAddress().toString() + "] connection established after " + (System.currentTimeMillis() - getSecondaryHostFailTimestamp()));
+ } else
+ log.fine("new active secondary node [" + newSecondaryProtocol.getHostAddress().toString() + "] connection established");
+
+ }
+ resetSecondaryFailoverData();
+ if (!isMasterHostFail()) stopFailover();
+ } finally {
+ proxy.lock.writeLock().unlock();
+ }
+ }
+
+ /**
+ * switch to a read-only(secondary) or read and write connection(master)
+ *
+ * @param mustBeReadOnly the read-only status asked
+ * @throws QueryException if operation hasn't change protocol
+ */
+ @Override
+ public void switchReadOnlyConnection(Boolean mustBeReadOnly) throws QueryException {
+ if (log.isLoggable(Level.FINEST)) log.fine("switching to mustBeReadOnly = " + mustBeReadOnly + " mode");
+
+ if (mustBeReadOnly != currentReadOnlyAsked.get() && currentProtocol.inTransaction()) {
+ throw new QueryException("Trying to set to read-only mode during a transaction");
+ }
+ if (currentReadOnlyAsked.compareAndSet(!mustBeReadOnly, mustBeReadOnly)) {
+ if (currentReadOnlyAsked.get()) {
+ if (currentProtocol.isMasterConnection()) {
+ //must change to replica connection
+ if (!isSecondaryHostFail()) {
+ proxy.lock.writeLock().lock();
+ try {
+
+ log.finest("switching to secondary connection");
+ syncConnection(this.masterProtocol, this.secondaryProtocol);
+
+ currentProtocol = this.secondaryProtocol;
+ setSessionReadOnly(true);
+
+ log.finest("current connection is now secondary");
+ return;
+ } catch (QueryException e) {
+ log.log(Level.FINEST, "switching to secondary connection failed", e);
+ if (setSecondaryHostFail()) {
+ addToBlacklist(secondaryProtocol.getHostAddress());
+ }
+ } finally {
+ proxy.lock.writeLock().unlock();
+ }
+ }
+ launchFailLoopIfNotlaunched(false);
+ throwFailoverMessage(new QueryException("master " + masterProtocol.getHostAddress() + " connection failed"), false);
+ }
+ } else {
+ if (!currentProtocol.isMasterConnection()) {
+ //must change to master connection
+ if (!isMasterHostFail()) {
+
+ proxy.lock.writeLock().lock();
+ try {
+ log.finest("switching to master connection");
+
+ syncConnection(this.secondaryProtocol, this.masterProtocol);
+ currentProtocol = this.masterProtocol;
+
+ log.fine("current connection is now master");
+ return;
+ } catch (QueryException e) {
+ log.log(Level.FINE, "switching to master connection failed", e);
+ if (setMasterHostFail()) {
+ addToBlacklist(masterProtocol.getHostAddress());
+ }
+ } finally {
+ proxy.lock.writeLock().unlock();
+ }
+ }
+ if (jdbcUrl.getOptions().autoReconnect) {
+ reconnectFailedConnection(new SearchFilter(false, true, false, true));
+ //connection established, no need to send Exception !
+ log.finest("switching to master connection");
+ proxy.lock.writeLock().lock();
+ try {
+ syncConnection(this.secondaryProtocol, this.masterProtocol);
+ currentProtocol = this.masterProtocol;
+ } finally {
+ proxy.lock.writeLock().unlock();
+ }
+ log.fine("current connection is now master");
+ return;
+ }
+ launchFailLoopIfNotlaunched(false);
+ throwFailoverMessage(new QueryException("master " + masterProtocol.getHostAddress() + " connection failed"), false);
+ }
+ }
+ }
+ }
+
+ /**
+ * to handle the newly detected failover on the master connection
+ *
+ * @param method the initial called method
+ * @param args the initial args
+ * @return an object to indicate if the previous Exception must be thrown, or the object resulting if a failover worked
+ * @throws Throwable if failover has not been catch
+ */
+ public HandleErrorResult primaryFail(Method method, Object[] args) throws Throwable {
+ boolean alreadyClosed = !masterProtocol.isConnected();
+
+ //try to reconnect automatically only time before looping
+ try {
+ if (masterProtocol != null && masterProtocol.isConnected() && masterProtocol.ping()) {
+ if (log.isLoggable(Level.FINE))
+ log.fine("Primary node [" + masterProtocol.getHostAddress().toString() + "] connection re-established");
+
+ // if in transaction cannot be sure that the last query has been received by server of not, so rollback.
+ if (masterProtocol.inTransaction()) {
+ masterProtocol.rollback();
+ }
+ return new HandleErrorResult(true);
+ }
+ } catch (QueryException e) {
+ proxy.lock.writeLock().lock();
+ try {
+ masterProtocol.close();
+ } finally {
+ proxy.lock.writeLock().unlock();
+ }
+
+ if (setMasterHostFail()) addToBlacklist(masterProtocol.getHostAddress());
+ }
+
+ //fail on slave if parameter permit so
+ if (jdbcUrl.getOptions().failOnReadOnly) {
+ //in multiHost, switch to secondary if active, even if in a current transaction -> will throw an exception
+ if (!isSecondaryHostFail()) {
+ try {
+ if (this.secondaryProtocol != null && this.secondaryProtocol.ping()) {
+ log.finest("switching to secondary connection");
+ syncConnection(masterProtocol, this.secondaryProtocol);
+ proxy.lock.writeLock().lock();
+ try {
+ currentProtocol = this.secondaryProtocol;
+ } finally {
+ proxy.lock.writeLock().unlock();
+ }
+ launchFailLoopIfNotlaunched(false);
+ try {
+ return relaunchOperation(method, args);
+ } catch (Exception e) {
+ log.log(Level.FINEST, "relaunchOperation failed", e);
+ }
+ return new HandleErrorResult();
+ } else log.finest("ping failed on secondary");
+ } catch (Exception e) {
+ if (setSecondaryHostFail()) addToBlacklist(this.secondaryProtocol.getHostAddress());
+ if (secondaryProtocol.isConnected()) {
+ proxy.lock.writeLock().lock();
+ try {
+ secondaryProtocol.close();
+ } finally {
+ proxy.lock.writeLock().unlock();
+ }
+ }
+ log.log(Level.FINEST, "ping on secondary failed");
+ }
+ }
+ }
+
+ try {
+ reconnectFailedConnection(new SearchFilter(true, jdbcUrl.getOptions().failOnReadOnly, true, jdbcUrl.getOptions().failOnReadOnly));
+ if (isMasterHostFail()) launchFailLoopIfNotlaunched(true);
+ if (alreadyClosed) return relaunchOperation(method, args);
+ return new HandleErrorResult(true);
+ } catch (Exception e) {
+ launchFailLoopIfNotlaunched(true);
+ return new HandleErrorResult();
+ }
+ }
+
+
+ public void reconnect() throws QueryException {
+ SearchFilter filter;
+ if (currentReadOnlyAsked.get()) {
+ filter = new SearchFilter(true, true, true, true);
+ } else {
+ filter = new SearchFilter(true, jdbcUrl.getOptions().failOnReadOnly, true, jdbcUrl.getOptions().failOnReadOnly);
+ }
+ reconnectFailedConnection(filter);
+ }
+
+
+ /**
+ * to handle the newly detected failover on the secondary connection
+ *
+ * @param method the initial called method
+ * @param args the initial args
+ * @return an object to indicate if the previous Exception must be thrown, or the object resulting if a failover worked
+ * @throws Throwable if failover has not catch error
+ */
+ public HandleErrorResult secondaryFail(Method method, Object[] args) throws Throwable {
+ try {
+ if (this.secondaryProtocol != null && secondaryProtocol.isConnected() && this.secondaryProtocol.ping()) {
+ if (log.isLoggable(Level.FINE))
+ log.fine("Secondary node [" + this.secondaryProtocol.getHostAddress().toString() + "] connection re-established");
+ return relaunchOperation(method, args);
+ }
+ } catch (Exception e) {
+ log.finest("ping fail on secondary");
+ proxy.lock.writeLock().lock();
+ try {
+ secondaryProtocol.close();
+ } finally {
+ proxy.lock.writeLock().unlock();
+ }
+
+ if (setSecondaryHostFail()) addToBlacklist(this.secondaryProtocol.getHostAddress());
+ }
+
+ if (!isMasterHostFail()) {
+ try {
+ if (masterProtocol != null) {
+ this.masterProtocol.ping(); //check that master is on before switching to him
+ log.finest("switching to master connection");
+ syncConnection(secondaryProtocol, masterProtocol);
+ proxy.lock.writeLock().lock();
+ try {
+ currentProtocol = masterProtocol;
+ } finally {
+ proxy.lock.writeLock().unlock();
+ }
+ launchFailLoopIfNotlaunched(true); //launch reconnection loop
+ return relaunchOperation(method, args); //now that we are on master, relaunched result if the result was not crashing the master
+ }
+ } catch (Exception e) {
+ log.finest("ping fail on master");
+ if (setMasterHostFail()) {
+ addToBlacklist(masterProtocol.getHostAddress());
+ if (masterProtocol.isConnected()) {
+ proxy.lock.writeLock().lock();
+ try {
+ masterProtocol.close();
+ } finally {
+ proxy.lock.writeLock().unlock();
+ }
+ }
+ }
+ }
+ }
+
+ try {
+ reconnectFailedConnection(new SearchFilter(true, true, true, true));
+ if (!isSecondaryHostFail()) {
+ if (log.isLoggable(Level.FINE))
+ log.fine("SQL Secondary node [" + this.masterProtocol.getHostAddress().toString() + "] connection re-established");
+ } else {
+ log.finest("switching to master connection");
+ syncConnection(this.secondaryProtocol, this.masterProtocol);
+ proxy.lock.writeLock().lock();
+ try {
+ currentProtocol = this.masterProtocol;
+ } finally {
+ proxy.lock.writeLock().unlock();
+ }
+ }
+ return relaunchOperation(method, args); //now that we are reconnect, relaunched result if the result was not crashing the node
+ } catch (Exception ee) {
+ launchFailLoopIfNotlaunched(false);
+ return new HandleErrorResult();
+ }
+ }
+
+
+ /**
+ * private class to chech of currents connections are still ok.
+ */
+ protected class PingLoop implements Runnable {
+ MastersSlavesListener listener;
+
+ public PingLoop(MastersSlavesListener listener) {
+ this.listener = listener;
+ }
+
+ public void run() {
+ if (lastQueryTime + jdbcUrl.getOptions().validConnectionTimeout * 1000 < System.currentTimeMillis()) {
+ log.finest("PingLoop run ");
+ if (!isMasterHostFail()) {
+ log.finest("PingLoop run, master not seen failed");
+ boolean masterFail = false;
+ try {
+
+ if (masterProtocol != null && masterProtocol.isConnected()) {
+ checkIfTypeHaveChanged(null);
+ } else {
+ masterFail = true;
+ }
+ } catch (QueryException e) {
+ log.log(Level.FINEST, "PingLoop ping to master error", e);
+ masterFail = true;
+ }
+
+ if (masterFail) {
+ log.finest("PingLoop master failed -> will loop to found it");
+ if (setMasterHostFail()) {
+ try {
+ listener.primaryFail(null, null);
+ } catch (Throwable t) {
+ //do nothing
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ public void checkIfTypeHaveChanged(SearchFilter searchFilter) throws QueryException {
+ if (masterProtocol.ping()) {
+ log.finest("PingLoop master ping ok");
+ }
+ }
+
+
+ /**
+ * Throw a human readable message after a failoverException
+ *
+ * @param queryException internal error
+ * @param reconnected connection status
+ * @throws QueryException error with failover information
+ */
+ @Override
+ public void throwFailoverMessage(QueryException queryException, boolean reconnected) throws QueryException {
+ boolean connectionTypeMaster = true;
+ HostAddress hostAddress = (masterProtocol != null) ? masterProtocol.getHostAddress() : null;
+ if (currentReadOnlyAsked.get()) {
+ connectionTypeMaster = false;
+ hostAddress = (secondaryProtocol != null) ? secondaryProtocol.getHostAddress() : null;
+ }
+
+ String firstPart = "Communications link failure with " + (connectionTypeMaster ? "primary" : "secondary") + ((hostAddress != null) ? " host " + hostAddress.host + ":" + hostAddress.port : "") + ". ";
+ String error = "";
+ if (jdbcUrl.getOptions().autoReconnect || (!isMasterHostFail() && !isSecondaryHostFail())) {
+ if ((connectionTypeMaster && isMasterHostFail()) || (!connectionTypeMaster && isSecondaryHostFail()))
+ error += " Driver will reconnect automatically in a few millisecond or during next query if append before";
+ else error += " Driver as successfully reconnect connection";
+ } else {
+ if (reconnected) {
+ error += " Driver as reconnect connection";
+ } else {
+ if (currentConnectionAttempts.get() > jdbcUrl.getOptions().retriesAllDown) {
+ error += " Driver will not try to reconnect (too much failure > " + jdbcUrl.getOptions().retriesAllDown + ")";
+ } else {
+ if (shouldReconnect()) {
+ error += " Driver will try to reconnect automatically in a few millisecond or during next query if append before";
+ } else {
+ long longestFail = isMasterHostFail() ? (isSecondaryHostFail() ? Math.min(getMasterHostFailTimestamp(), getSecondaryHostFailTimestamp()) : getMasterHostFailTimestamp()) : getSecondaryHostFailTimestamp();
+ long nextReconnectionTime = jdbcUrl.getOptions().secondsBeforeRetryMaster * 1000 - (System.currentTimeMillis() - longestFail);
+ if (jdbcUrl.getOptions().secondsBeforeRetryMaster > 0) {
+ if (jdbcUrl.getOptions().queriesBeforeRetryMaster > 0) {
+ error += " Driver will try to reconnect " + (connectionTypeMaster ? "primary" : "secondary") + " after " + nextReconnectionTime + " milliseconds or after " + (jdbcUrl.getOptions().queriesBeforeRetryMaster - queriesSinceFailover.get()) + " query(s)";
+ } else {
+ error += " Driver will try to reconnect " + (connectionTypeMaster ? "primary" : "secondary") + " after " + nextReconnectionTime + " milliseconds";
+ }
+ } else {
+ if (jdbcUrl.getOptions().queriesBeforeRetryMaster > 0) {
+ error += " Driver will try to reconnect " + (connectionTypeMaster ? "primary" : "secondary") + " after " + (jdbcUrl.getOptions().queriesBeforeRetryMaster - queriesSinceFailover.get()) + " query(s)";
+ } else {
+ error += " Driver will not try to reconnect automatically";
+ }
+ }
+ }
+
+ }
+ }
+ }
+ if (queryException == null) {
+ throw new QueryException(firstPart + error, (short) -1, SQLExceptionMapper.SQLStates.CONNECTION_EXCEPTION.getSqlState());
+ } else {
+ error = queryException.getMessage() + ". " + error;
+ queryException.setMessage(firstPart + error);
+ throw queryException;
+ }
+ }
+}
diff --git a/src/main/java/org/mariadb/jdbc/internal/mysql/listener/tools/SearchFilter.java b/src/main/java/org/mariadb/jdbc/internal/mysql/listener/tools/SearchFilter.java
new file mode 100644
index 000000000..ec6d3d1f0
--- /dev/null
+++ b/src/main/java/org/mariadb/jdbc/internal/mysql/listener/tools/SearchFilter.java
@@ -0,0 +1,136 @@
+/*
+MariaDB Client for Java
+
+Copyright (c) 2012 Monty Program Ab.
+
+This library is free software; you can redistribute it and/or modify it under
+the terms of the GNU Lesser General Public License as published by the Free
+Software Foundation; either version 2.1 of the License, or (at your option)
+any later version.
+
+This library 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 Lesser General Public License
+for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this library; if not, write to Monty Program Ab info@montyprogram.com.
+
+This particular MariaDB Client for Java file is work
+derived from a Drizzle-JDBC. Drizzle-JDBC file which is covered by subject to
+the following copyright and notice provisions:
+
+Copyright (c) 2009-2011, Marcus Eriksson
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+Redistributions of source code must retain the above copyright notice, this list
+of conditions and the following disclaimer.
+
+Redistributions in binary form must reproduce the above copyright notice, this
+list of conditions and the following disclaimer in the documentation and/or
+other materials provided with the distribution.
+
+Neither the name of the driver nor the names of its contributors may not be
+used to endorse or promote products derived from this software without specific
+prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+OF SUCH DAMAGE.
+*/
+package org.mariadb.jdbc.internal.mysql.listener.tools;
+
+public class SearchFilter {
+ boolean searchForMaster;
+ boolean searchForSlave;
+ boolean fineIfFoundOnlyMaster;
+ boolean fineIfFoundOnlySlave;
+ boolean initialConnection;
+ boolean uniqueLoop;
+
+ public SearchFilter (boolean searchForMaster, boolean searchForSlave) {
+ this.searchForMaster = searchForMaster;
+ this.searchForSlave = searchForSlave;
+ }
+
+ public SearchFilter(boolean searchForMaster, boolean searchForSlave, boolean fineIfFoundOnlyMaster, boolean fineIfFoundOnlySlave) {
+ this.searchForMaster = searchForMaster;
+ this.searchForSlave = searchForSlave;
+ this.fineIfFoundOnlyMaster = fineIfFoundOnlyMaster;
+ this.fineIfFoundOnlySlave = fineIfFoundOnlySlave;
+ }
+
+ public SearchFilter(boolean searchForMaster, boolean searchForSlave, boolean initialConnection) {
+ this.searchForMaster = searchForMaster;
+ this.searchForSlave = searchForSlave;
+ this.initialConnection = initialConnection;
+ }
+
+ public boolean isInitialConnection() {
+ return initialConnection;
+ }
+
+ public void setInitialConnection(boolean initialConnection) {
+ this.initialConnection = initialConnection;
+ }
+
+ public boolean isFineIfFoundOnlyMaster() {
+ return fineIfFoundOnlyMaster;
+ }
+
+ public void setFineIfFoundOnlyMaster(boolean fineIfFoundOnlyMaster) {
+ this.fineIfFoundOnlyMaster = fineIfFoundOnlyMaster;
+ }
+
+ public boolean isFineIfFoundOnlySlave() {
+ return fineIfFoundOnlySlave;
+ }
+
+ public void setFineIfFoundOnlySlave(boolean fineIfFoundOnlySlave) {
+ this.fineIfFoundOnlySlave = fineIfFoundOnlySlave;
+ }
+
+ public boolean isSearchForMaster() {
+ return searchForMaster;
+ }
+
+ public void setSearchForMaster(boolean searchForMaster) {
+ this.searchForMaster = searchForMaster;
+ }
+
+ public boolean isSearchForSlave() {
+ return searchForSlave;
+ }
+
+ public void setSearchForSlave(boolean searchForSlave) {
+ this.searchForSlave = searchForSlave;
+ }
+
+ public boolean isUniqueLoop() {
+ return uniqueLoop;
+ }
+
+ public void setUniqueLoop(boolean uniqueLoop) {
+ this.uniqueLoop = uniqueLoop;
+ }
+
+ @Override
+ public String toString() {
+ return "SearchFilter{" +
+ "searchForMaster=" + searchForMaster +
+ ", searchForSlave=" + searchForSlave +
+ ", fineIfFoundOnlyMaster=" + fineIfFoundOnlyMaster +
+ ", fineIfFoundOnlySlave=" + fineIfFoundOnlySlave +
+ ", initialConnection=" + initialConnection +
+ ", uniqueLoop=" + uniqueLoop +
+ '}';
+ }
+}
diff --git a/src/main/resources/Version.java.template b/src/main/resources/Version.java.template
index dd657a5d6..a8ea7dd89 100644
--- a/src/main/resources/Version.java.template
+++ b/src/main/resources/Version.java.template
@@ -1,7 +1,10 @@
package org.mariadb.jdbc;
public final class Version {
- public static final String build_time="@buildtime@";
- public static final String pomversion="@pomversion@";
+ public static final String version = "@version";
+ public static final int majorVersion = @majorVersion;
+ public static final int minorVersion = @minorVersion;
+ public static final int patchVersion = @patchVersion;
+ public static final String qualifier = "@qualifier";
}
\ No newline at end of file
diff --git a/src/test/java/org/mariadb/jdbc/BaseTest.java b/src/test/java/org/mariadb/jdbc/BaseTest.java
index 7cd2efd06..8dd4d2e24 100644
--- a/src/test/java/org/mariadb/jdbc/BaseTest.java
+++ b/src/test/java/org/mariadb/jdbc/BaseTest.java
@@ -1,13 +1,16 @@
package org.mariadb.jdbc;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.BeforeClass;
-import org.junit.Ignore;
+import org.junit.*;
+import org.junit.rules.TestRule;
+import org.junit.rules.TestWatcher;
+import org.junit.runner.Description;
+import org.mariadb.jdbc.failover.BaseMultiHostTest;
+import org.mariadb.jdbc.internal.mysql.Protocol;
import java.io.PrintWriter;
import java.io.StringWriter;
+import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.sql.*;
@@ -23,7 +26,7 @@
@Ignore
public class BaseTest {
- protected static Logger log = Logger.getLogger("org.maria.jdbc");
+ protected static Logger log = Logger.getLogger("org.mariadb.jdbc");
protected Connection connection;
protected static String connU;
protected static String connURI;
@@ -34,45 +37,54 @@ public class BaseTest {
protected static String password;
protected static String parameters;
protected static final String mDefUrl = "jdbc:mysql://localhost:3306/test?user=root";
+ protected static boolean testSingleHost;
@BeforeClass
public static void beforeClassBaseTest() {
String url = System.getProperty("dbUrl", mDefUrl);
+ testSingleHost = Boolean.parseBoolean(System.getProperty("testSingleHost", "true"));
JDBCUrl jdbcUrl = JDBCUrl.parse(url);
- String logLevel = System.getProperty("logLevel");
- if (logLevel != null) {
- if (log.getHandlers().length == 0) {
- ConsoleHandler consoleHandler = new ConsoleHandler();
- consoleHandler.setFormatter(new CustomFormatter());
- consoleHandler.setLevel(Level.parse(logLevel));
- log.addHandler(consoleHandler);
- log.setLevel(Level.FINE);
- }
- }
-
- hostname = jdbcUrl.getHostname();
- port = jdbcUrl.getPort();
+ hostname = jdbcUrl.getHostAddresses().get(0).host;
+ port = jdbcUrl.getHostAddresses().get(0).port;
database = jdbcUrl.getDatabase();
username = jdbcUrl.getUsername();
password = jdbcUrl.getPassword();
log.fine("Properties parsed from JDBC URL - hostname: " + hostname + ", port: " + port + ", database: " + database + ", username: " + username + ", password: " + password);
-
- if (database != null && "".equals(username)) {
- String[] tokens = database.contains("?") ? database.split("\\?") : null;
- if (tokens != null) {
- database = tokens[0];
- String[] paramTokens = tokens[1].split("&");
- username = paramTokens[0].startsWith("user=") ? paramTokens[0].substring(5) : null;
- if (paramTokens.length > 1) {
- password = paramTokens[0].startsWith("password=") ? paramTokens[1].substring(9) : null;
- }
- }
- }
+
setURI();
}
-
+
+ @Before
+ public void init() throws SQLException {
+ Assume.assumeTrue(testSingleHost);
+ }
+
+ @Rule
+ public TestRule watcher = new TestWatcher() {
+ protected void starting(Description description) {
+ log.fine("Starting test: " + description.getMethodName());
+ }
+
+ protected void finished(Description description) {
+ log.fine("finished test: " + description.getMethodName());
+ }
+ };
+
+ public void assureBlackList(Connection connection) {
+ try {
+ Protocol protocol = getProtocolFromConnection(connection);
+ protocol.getProxy().getListener().getBlacklist().clear();
+ } catch (Throwable e) { }
+ }
+
+ protected Protocol getProtocolFromConnection(Connection conn) throws Throwable {
+
+ Method getProtocol = MySQLConnection.class.getDeclaredMethod("getProtocol", new Class[0]);
+ getProtocol.setAccessible(true);
+ return (Protocol) getProtocol.invoke(conn);
+ }
private static void setURI() {
connU = "jdbc:mysql://" + hostname + ":" + port + "/" + database;
connURI = connU + "?user=" + username
@@ -207,7 +219,6 @@ boolean checkMaxAllowedPacketMore40m(String testName) throws SQLException {
return true;
}
-
//does the user have super privileges or not?
boolean hasSuperPrivilege(String testName) throws SQLException
{
@@ -285,37 +296,3 @@ boolean isMariadbServer() throws SQLException {
}
}
-
-class CustomFormatter extends Formatter {
- private static final String format = "[%1$tT] %4$s: %2$s - %5$s %6$s%n";
- private final java.util.Date dat = new java.util.Date();
- public synchronized String format(LogRecord record) {
- dat.setTime(record.getMillis());
- String source;
- if (record.getSourceClassName() != null) {
- source = record.getSourceClassName();
- if (record.getSourceMethodName() != null) {
- source += " " + record.getSourceMethodName();
- }
- } else {
- source = record.getLoggerName();
- }
- String message = formatMessage(record);
- String throwable = "";
- if (record.getThrown() != null) {
- StringWriter sw = new StringWriter();
- PrintWriter pw = new PrintWriter(sw);
- pw.println();
- record.getThrown().printStackTrace(pw);
- pw.close();
- throwable = sw.toString();
- }
- return String.format(format,
- dat,
- source,
- record.getLoggerName(),
- record.getLevel().getName(),
- message,
- throwable);
- }
-}
\ No newline at end of file
diff --git a/src/test/java/org/mariadb/jdbc/CallableStatementTest.java b/src/test/java/org/mariadb/jdbc/CallableStatementTest.java
index 7d028de38..19755448b 100644
--- a/src/test/java/org/mariadb/jdbc/CallableStatementTest.java
+++ b/src/test/java/org/mariadb/jdbc/CallableStatementTest.java
@@ -6,7 +6,7 @@
import java.sql.*;
-import static junit.framework.Assert.*;
+import static org.junit.Assert.*;
import static org.junit.Assert.assertThat;
import static org.hamcrest.CoreMatchers.*;
@@ -134,7 +134,7 @@ public void withStrangeParameter() throws SQLException {
ResultSet rs = stmt.executeQuery();
assertTrue(rs.next());
double res = rs.getDouble(1);
- assertEquals(expected, res);
+ assertEquals(expected, res, 0);
// now fail due to three decimals
double tooMuch = 34.987;
stmt.setDouble("a", tooMuch);
diff --git a/src/test/java/org/mariadb/jdbc/CancelTest.java b/src/test/java/org/mariadb/jdbc/CancelTest.java
index fb2fc3a8b..61f9fdede 100644
--- a/src/test/java/org/mariadb/jdbc/CancelTest.java
+++ b/src/test/java/org/mariadb/jdbc/CancelTest.java
@@ -1,9 +1,13 @@
package org.mariadb.jdbc;
+import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import java.sql.*;
+import java.util.Properties;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
import static org.junit.Assert.assertEquals;
@@ -12,15 +16,28 @@ public class CancelTest extends BaseTest {
public void cancelSupported() throws SQLException {
requireMinimumVersion(5,0);
}
- @Test(expected = SQLTransientException.class)
+ @Test
public void cancelTest() throws SQLException{
+ Connection tmpConnection = null;
+ try {
+ tmpConnection = openNewConnection(connURI, new Properties());
+ Statement stmt = tmpConnection.createStatement();
+ ExecutorService exec = Executors.newFixedThreadPool(1);
+ //check blacklist shared
+ exec.execute(new CancelThread(stmt));
+ stmt.execute("select * from information_schema.columns, information_schema.tables");
- Statement stmt = connection.createStatement();
- new CancelThread(stmt).start();
- stmt.execute("select * from information_schema.columns, information_schema.tables, information_schema.table_constraints");
+ //wait for thread endings
+ exec.shutdown();
+ Assert.fail();
+ } catch (SQLException e) {
+
+ }finally {
+ tmpConnection.close();
+ }
}
- private static class CancelThread extends Thread {
+ private static class CancelThread implements Runnable {
private final Statement stmt;
public CancelThread(Statement stmt) {
diff --git a/src/test/java/org/mariadb/jdbc/CatalogTest.java b/src/test/java/org/mariadb/jdbc/CatalogTest.java
index c73f6aea9..b3cfdfcfa 100644
--- a/src/test/java/org/mariadb/jdbc/CatalogTest.java
+++ b/src/test/java/org/mariadb/jdbc/CatalogTest.java
@@ -7,7 +7,7 @@
import java.util.logging.Level;
import java.util.logging.Logger;
-import static junit.framework.Assert.assertEquals;
+import static org.junit.Assert.assertEquals;
public class CatalogTest extends BaseTest {
static { Logger.getLogger("").setLevel(Level.FINEST); }
diff --git a/src/test/java/org/mariadb/jdbc/ConnectionPoolTest.java b/src/test/java/org/mariadb/jdbc/ConnectionPoolTest.java
index 1a2a572f0..d3ed27c7e 100644
--- a/src/test/java/org/mariadb/jdbc/ConnectionPoolTest.java
+++ b/src/test/java/org/mariadb/jdbc/ConnectionPoolTest.java
@@ -40,6 +40,95 @@ public void testConnectionWithApacheDBCP() throws SQLException {
connection.close();
dataSource.close();
}
+
+ /*
+ *
+
+ @Test
+ public void testTimeoutsInPool() throws SQLException, InterruptedException {
+ org.apache.commons.dbcp.BasicDataSource dataSource;
+ dataSource = new org.apache.commons.dbcp.BasicDataSource();
+ dataSource.setUrl("jdbc:mysql://" + hostname + ":"+port+"/test?useCursorFetch=true&useTimezone=true&useLegacyDatetimeCode=false&serverTimezone=UTC");
+ dataSource.setUsername(username);
+ dataSource.setPassword(password);
+ // dataSource.setMaxActive(10);
+ // dataSource.setMinIdle(10); //keep 10 connections open
+ // dataSource.setValidationQuery("SELECT 1");
+ dataSource.setMaxActive(50);
+ dataSource.setLogAbandoned(true);
+ dataSource.setRemoveAbandoned(true);
+ dataSource.setRemoveAbandonedTimeout(300);
+ dataSource.setAccessToUnderlyingConnectionAllowed(true);
+ dataSource.setMinEvictableIdleTimeMillis(1800000);
+ dataSource.setTimeBetweenEvictionRunsMillis(-1);
+ dataSource.setNumTestsPerEvictionRun(3);
+
+ // adjust server wait timeout to 1 second
+ // Statement stmt1 = conn1.createStatement();
+ // stmt1.execute("set session wait_timeout=1");
+
+
+ try {
+ Connection conn = dataSource.getConnection();
+ log.fine("autocommit: " + conn.getAutoCommit());
+ Statement stmt = conn.createStatement();
+ stmt.executeUpdate("drop table if exists t3");
+ stmt.executeUpdate("create table t3(message text)");
+ conn.close();
+ } catch (SQLException e1) {
+ e1.printStackTrace();
+ }
+
+ InsertThread ins1 = new InsertThread(10000, dataSource);
+ Thread thread1 = new Thread(ins1);
+ thread1.start();
+ InsertThread ins2 = new InsertThread(10000, dataSource);
+ Thread thread2 = new Thread(ins2);
+ thread2.start();
+ InsertThread ins3 = new InsertThread(10000, dataSource);
+ Thread thread3 = new Thread(ins3);
+ thread3.start();
+ InsertThread ins4 = new InsertThread(10000, dataSource);
+ Thread thread4 = new Thread(ins4);
+ thread4.start();
+ InsertThread ins5 = new InsertThread(10000, dataSource);
+ Thread thread5 = new Thread(ins5);
+ thread5.start();
+ InsertThread ins6 = new InsertThread(10000, dataSource);
+ Thread thread6 = new Thread(ins6);
+ thread6.start();
+ InsertThread ins7 = new InsertThread(10000, dataSource);
+ Thread thread7 = new Thread(ins7);
+ thread7.start();
+ InsertThread ins8 = new InsertThread(10000, dataSource);
+ Thread thread8 = new Thread(ins8);
+ thread8.start();
+ InsertThread ins9 = new InsertThread(10000, dataSource);
+ Thread thread9 = new Thread(ins9);
+ thread9.start();
+ InsertThread ins10 = new InsertThread(10000, dataSource);
+ Thread thread10 = new Thread(ins10);
+ thread10.start();
+
+ // wait for threads to finish
+ while (thread1.isAlive() || thread2.isAlive() || thread3.isAlive() || thread4.isAlive() || thread5.isAlive() || thread6.isAlive() || thread7.isAlive() || thread8.isAlive() || thread9.isAlive() || thread10.isAlive())
+ {
+ //keep on waiting for threads to finish
+ }
+
+ // wait for 70 seconds so that the server times out the connections
+ Thread.sleep(70000); // Wait for the server to kill the connections
+
+ // do something
+ Statement stmt1 = dataSource.getConnection().createStatement();
+ stmt1.execute("SELECT COUNT(*) FROM t3");
+
+ // close data source
+ dataSource.close();
+
+ } */
+
+
/**
* This test case simulates how the Apache DBCP connection pools works. It is written so it
@@ -52,7 +141,7 @@ public void testConnectionWithSimululatedApacheDBCP() throws SQLException {
Properties props = new Properties();
props.put("user", username);
- props.put("password", password);
+ props.put("password", (password==null)?"":password);
//A connection pool typically has a connection factor that stored everything needed to
//create a Connection. Here I create a factory that stores URL, username and password.
diff --git a/src/test/java/org/mariadb/jdbc/ConnectionTest.java b/src/test/java/org/mariadb/jdbc/ConnectionTest.java
index 689aa6250..72cce69e6 100644
--- a/src/test/java/org/mariadb/jdbc/ConnectionTest.java
+++ b/src/test/java/org/mariadb/jdbc/ConnectionTest.java
@@ -15,7 +15,9 @@
import org.junit.Assume;
import org.junit.Test;
import org.mariadb.jdbc.internal.common.query.MySQLQuery;
+import org.mariadb.jdbc.internal.mysql.FailoverProxy;
import org.mariadb.jdbc.internal.mysql.MySQLProtocol;
+import org.mariadb.jdbc.internal.mysql.Protocol;
public class ConnectionTest extends BaseTest {
@@ -235,34 +237,4 @@ public void isValid_connectionThatTimesOutByServer() throws SQLException,
statement.close();
}
- /**
- * CONJ-120 Fix Connection.isValid method
- *
- * @throws Exception
- */
- @Test
- public void isValid_connectionThatIsKilledExternally() throws Exception {
- long threadId = getServerThreadId();
- Connection killerConnection = openNewConnection();
- Statement killerStatement = killerConnection.createStatement();
- killerStatement.execute("KILL CONNECTION " + threadId);
- killerConnection.close();
- boolean isValid = connection.isValid(0);
- assertFalse(isValid);
- }
-
- /**
- * Reflection magic to extract the connection thread id assigned by the
- * server
- */
- private long getServerThreadId() throws Exception {
- Field protocolField = org.mariadb.jdbc.MySQLConnection.class.getDeclaredField("protocol");
- protocolField.setAccessible(true);
- MySQLProtocol protocol = (MySQLProtocol) protocolField.get(connection);
- Field serverThreadIdField = MySQLProtocol.class.getDeclaredField("serverThreadId");
- serverThreadIdField.setAccessible(true);
- long threadId = serverThreadIdField.getLong(protocol);
- return threadId;
- }
-
}
diff --git a/src/test/java/org/mariadb/jdbc/DataSourceTest.java b/src/test/java/org/mariadb/jdbc/DataSourceTest.java
index e3763ac13..38513246f 100644
--- a/src/test/java/org/mariadb/jdbc/DataSourceTest.java
+++ b/src/test/java/org/mariadb/jdbc/DataSourceTest.java
@@ -1,5 +1,7 @@
package org.mariadb.jdbc;
+import org.junit.Assert;
+import org.junit.Assume;
import org.junit.BeforeClass;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@@ -11,7 +13,7 @@
import org.junit.Test;
public class DataSourceTest extends BaseTest {
- protected static final String defConnectToIP = "127.0.0.1";
+ protected static final String defConnectToIP = null;
protected static String connectToIP;
@BeforeClass
@@ -39,7 +41,20 @@ public void testDataSource2() throws SQLException {
connection.close();
}
}
-
+
+ @Test
+ public void testDataSourceEmpty() throws SQLException {
+ MySQLDataSource ds = new MySQLDataSource();
+ ds.setDatabaseName(database);
+ ds.setPort(port);
+ ds.setServerName(hostname);
+ Connection connection = ds.getConnection(username, password);
+ try {
+ assertEquals(connection.isValid(0),true);
+ }finally {
+ connection.close();
+ }
+ }
/**
* CONJ-80
* @throws SQLException
@@ -56,37 +71,50 @@ public void setDatabaseNameTest() throws SQLException {
connection.createStatement().execute("DROP DATABASE IF EXISTS test2");
connection.close();
}
-
+
/**
* CONJ-80
* @throws SQLException
*/
@Test
public void setServerNameTest() throws SQLException {
+ Assume.assumeTrue(connectToIP != null);
MySQLDataSource ds = new MySQLDataSource(hostname, port, database);
Connection connection = ds.getConnection(username, password);
ds.setServerName(connectToIP);
connection = ds.getConnection(username, password);
connection.close();
}
-
+
/**
* CONJ-80
* @throws SQLException
*/
- @Test(expected=SQLException.class) // unless port 3307 can be used
+ @Test // unless port 3307 can be used
public void setPortTest() throws SQLException {
+
+
MySQLDataSource ds = new MySQLDataSource(hostname, port, database);
- Connection connection = ds.getConnection(username, password);
- ds.setPort(3307);
- connection = ds.getConnection(username, password);
- connection.close();
+ Connection connection2 = ds.getConnection(username, password);
+ //delete blacklist, because can failover on 3306 is filled
+ assureBlackList(connection2);
+ connection2.close();
+
+ ds.setPort(3307);
+
+ //must throw SQLException
+ try {
+ ds.getConnection(username, password);
+ Assert.fail();
+ } catch (SQLException e) {
+ log.fine("port error : " +e.getMessage());
+ }
}
-
+
/**
* CONJ-123:
* Session variables lost and exception if set via MySQLDataSource.setProperties/setURL
- * @throws SQLException
+ * @throws SQLException
*/
@Test
public void setPropertiesTest() throws SQLException {
diff --git a/src/test/java/org/mariadb/jdbc/DatabaseMetadataTest.java b/src/test/java/org/mariadb/jdbc/DatabaseMetadataTest.java
index 88465b8ac..208927e72 100644
--- a/src/test/java/org/mariadb/jdbc/DatabaseMetadataTest.java
+++ b/src/test/java/org/mariadb/jdbc/DatabaseMetadataTest.java
@@ -7,8 +7,8 @@
import java.util.logging.Level;
import java.util.logging.Logger;
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.fail;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
diff --git a/src/test/java/org/mariadb/jdbc/DatatypeTest.java b/src/test/java/org/mariadb/jdbc/DatatypeTest.java
index 3b4011f1f..0b141e377 100644
--- a/src/test/java/org/mariadb/jdbc/DatatypeTest.java
+++ b/src/test/java/org/mariadb/jdbc/DatatypeTest.java
@@ -9,7 +9,7 @@
import java.sql.ResultSet;
import java.sql.Types;
-import static junit.framework.Assert.assertEquals;
+import static org.junit.Assert.assertEquals;
public class DatatypeTest extends BaseTest {
diff --git a/src/test/java/org/mariadb/jdbc/DateTest.java b/src/test/java/org/mariadb/jdbc/DateTest.java
index 92daffac6..afc23aec7 100644
--- a/src/test/java/org/mariadb/jdbc/DateTest.java
+++ b/src/test/java/org/mariadb/jdbc/DateTest.java
@@ -1,7 +1,7 @@
package org.mariadb.jdbc;
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
import java.sql.Date;
import java.sql.PreparedStatement;
@@ -17,7 +17,7 @@
import java.util.logging.Level;
import java.util.logging.Logger;
-import junit.framework.Assert;
+import org.junit.Assert;
import org.junit.Assume;
import org.junit.Before;
import org.junit.BeforeClass;
@@ -196,6 +196,8 @@ public void javaUtilDateInPreparedStatementAsTimeStamp() throws Exception {
@Test
public void nullTimestampTest() throws SQLException {
+ connection.createStatement().execute("drop table if exists dtest");
+ connection.createStatement().execute("create table dtest (d date)");
PreparedStatement ps = connection.prepareStatement("insert into dtest values(null)");
ps.executeUpdate();
ResultSet rs = connection.createStatement().executeQuery("select * from dtest where d is null");
@@ -207,6 +209,8 @@ public void nullTimestampTest() throws SQLException {
@SuppressWarnings( "deprecation" )
@Test
public void javaUtilDateInPreparedStatementAsDate() throws Exception {
+ connection.createStatement().execute("drop table if exists dtest");
+ connection.createStatement().execute("create table dtest (d date)");
java.util.Date d = Calendar.getInstance(TimeZone.getDefault()).getTime();
PreparedStatement ps = connection.prepareStatement("insert into dtest values(?)");
ps.setObject(1, d, Types.DATE);
@@ -255,7 +259,8 @@ public void serverTimezone() throws Exception {
java.sql.Timestamp ts = rs.getTimestamp(1);
long differenceToGMT = ts.getTime() - now.getTime();
long diff = Math.abs(differenceToGMT - offset);
- assertTrue(diff < 2000); /* query take less than a second */
+ log.fine("diff : "+diff);
+ assertTrue(diff < 5000); /* query take less than a second but taking in accout server and client time second diff ... */
ps = connection.prepareStatement("select utc_timestamp(), ?");
ps.setObject(1,now);
@@ -263,7 +268,8 @@ public void serverTimezone() throws Exception {
rs.next();
ts = rs.getTimestamp(1);
java.sql.Timestamp ts2 = rs.getTimestamp(2);
- assertTrue(Math.abs(ts.getTime() - ts2.getTime()) < 1000); /* query take less than a second */
+ long diff2 = Math.abs(ts.getTime() - ts2.getTime());
+ assertTrue(diff2 < 5000); /* query take less than a second */
}
/**
diff --git a/src/test/java/org/mariadb/jdbc/DriverTest.java b/src/test/java/org/mariadb/jdbc/DriverTest.java
index 2079bf969..de439f2ff 100644
--- a/src/test/java/org/mariadb/jdbc/DriverTest.java
+++ b/src/test/java/org/mariadb/jdbc/DriverTest.java
@@ -15,7 +15,7 @@
import java.util.logging.Level;
import java.util.logging.Logger;
-import static junit.framework.Assert.*;
+import static org.junit.Assert.*;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -303,14 +303,14 @@ public void testConnectNoDB() throws Exception{
@Test
public void testConnectorJURL() {
JDBCUrl url = JDBCUrl.parse("jdbc:mysql://localhost/test");
- assertEquals("localhost", url.getHostname());
+ assertEquals("localhost", url.getHostAddresses().get(0).host);
assertEquals("test", url.getDatabase());
- assertEquals(3306,url.getPort());
+ assertEquals(3306,url.getHostAddresses().get(0).port);
url = JDBCUrl.parse("jdbc:mysql://localhost:3307/test");
- assertEquals("localhost", url.getHostname());
+ assertEquals("localhost", url.getHostAddresses().get(0).host);
assertEquals("test", url.getDatabase());
- assertEquals(3307,url.getPort());
+ assertEquals(3307,url.getHostAddresses().get(0).port);
}
@@ -1116,7 +1116,7 @@ public void testConnectWithDB() throws SQLException {
} catch (Exception e) {}
String oldDb = database;
String oldParams = parameters;
- setParameters("&createDB=true");
+ setParameters("&createDatabaseIfNotExist=true");
setDatabase("test_testdrop");
DatabaseMetaData dbmd = connection.getMetaData();
ResultSet rs = dbmd.getCatalogs();
@@ -1308,34 +1308,6 @@ public void unsignedTest() throws Exception {
assertNotNull(rs.getLong("unsignedtest.a"));
}
- @Test
- public void autoreconnect() throws Exception {
- setConnection("&autoReconnect=true");
- ResultSet rs= connection.createStatement().executeQuery("select connection_id()");
- rs.next();
- long connectionId = rs.getLong(1);
- rs.close();
-
- connection.createStatement().execute("set wait_timeout=1");
- Thread.sleep(3000);
-
- boolean success = false;
- for (int i=0; i < 2; i++) {
- try {
- rs = connection.createStatement().executeQuery("select 1");
- rs.close();
- success = true;
- break;
- } catch (Exception e) {
- // eat exception
- }
- }
- assertTrue(success);
- rs = connection.createStatement().executeQuery("select connection_id()");
- rs.next();
- long connectionId2 = rs.getLong(1);
- assertNotSame(connectionId, connectionId2);
- }
@Test
public void useSSL() throws Exception {
@@ -1557,7 +1529,7 @@ public void localSocket() throws Exception {
ResultSet rs = st.executeQuery("select @@version_compile_os,@@socket");
if (!rs.next())
return;
-
+ log.info ("os:"+rs.getString(1) + " path:"+rs.getString(2));
String os = rs.getString(1);
if (os.toLowerCase().startsWith("win"))
return;
@@ -1640,7 +1612,7 @@ public void emptyBatch() throws Exception {
@Test
public void createDbWithSpacesTest() throws SQLException {
String oldDb = database;
- setParameters("&createDB=true");
+ setParameters("&createDatabaseIfNotExist=true");
setDatabase("test with spaces");
DatabaseMetaData dbmd = connection.getMetaData();
ResultSet rs = dbmd.getCatalogs();
diff --git a/src/test/java/org/mariadb/jdbc/JdbcParserTest.java b/src/test/java/org/mariadb/jdbc/JdbcParserTest.java
new file mode 100644
index 000000000..ce4ef85f2
--- /dev/null
+++ b/src/test/java/org/mariadb/jdbc/JdbcParserTest.java
@@ -0,0 +1,226 @@
+package org.mariadb.jdbc;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.mariadb.jdbc.internal.common.UrlHAMode;
+
+import java.util.Properties;
+
+public class JdbcParserTest {
+
+ @Test
+ public void testOptionTakeDefault() throws Throwable {
+ JDBCUrl jdbc = JDBCUrl.parse("jdbc:mysql://localhost/test");
+ Assert.assertNull(jdbc.getOptions().connectTimeout);
+ Assert.assertTrue(jdbc.getOptions().validConnectionTimeout == 120);
+ Assert.assertFalse(jdbc.getOptions().autoReconnect);
+ Assert.assertNull(jdbc.getOptions().user);
+ Assert.assertFalse(jdbc.getOptions().createDatabaseIfNotExist);
+ Assert.assertNull(jdbc.getOptions().socketTimeout);
+
+ }
+
+ @Test
+ public void testOptionTakeDefaultAurora() throws Throwable {
+ JDBCUrl jdbc = JDBCUrl.parse("jdbc:mysql:aurora://localhost/test");
+ Assert.assertNull(jdbc.getOptions().connectTimeout);
+ Assert.assertTrue(jdbc.getOptions().validConnectionTimeout == 120);
+ Assert.assertFalse(jdbc.getOptions().autoReconnect);
+ Assert.assertNull(jdbc.getOptions().user);
+ Assert.assertFalse(jdbc.getOptions().createDatabaseIfNotExist);
+ Assert.assertTrue(jdbc.getOptions().socketTimeout.intValue() == 10000);
+ }
+
+ @Test
+ public void testOptionParse() throws Throwable {
+ JDBCUrl jdbc = JDBCUrl.parse("jdbc:mysql://localhost/test?user=root&password=toto&createDB=true&autoReconnect=true&validConnectionTimeout=2&connectTimeout=5");
+ Assert.assertTrue(jdbc.getOptions().connectTimeout == 5);
+ Assert.assertTrue(jdbc.getOptions().validConnectionTimeout == 2);
+ Assert.assertTrue(jdbc.getOptions().autoReconnect);
+ Assert.assertTrue(jdbc.getOptions().createDatabaseIfNotExist);
+
+ Assert.assertTrue("root".equals(jdbc.getOptions().user));
+ Assert.assertTrue("root".equals(jdbc.getUsername()));
+
+ Assert.assertTrue("toto".equals(jdbc.getOptions().password));
+ Assert.assertTrue("toto".equals(jdbc.getPassword()));
+ }
+
+ @Test
+ public void testOptionParseSlash() throws Throwable {
+ JDBCUrl jdbc = JDBCUrl.parse("jdbc:mysql://127.0.0.1:3306/colleo?user=root&password=toto&localSocket=/var/run/mysqld/mysqld.sock");
+ Assert.assertTrue("/var/run/mysqld/mysqld.sock".equals(jdbc.getOptions().localSocket));
+
+ Assert.assertTrue("root".equals(jdbc.getOptions().user));
+ Assert.assertTrue("root".equals(jdbc.getUsername()));
+
+ Assert.assertTrue("toto".equals(jdbc.getOptions().password));
+ Assert.assertTrue("toto".equals(jdbc.getPassword()));
+ }
+ @Test
+ public void testOptionParseIntegerMinimum() throws Throwable {
+ JDBCUrl jdbc = JDBCUrl.parse("jdbc:mysql://localhost/test?user=root&autoReconnect=true&validConnectionTimeout=0&connectTimeout=5");
+ Assert.assertTrue(jdbc.getOptions().connectTimeout == 5);
+ Assert.assertTrue(jdbc.getOptions().validConnectionTimeout == 0);
+ Assert.assertTrue(jdbc.getOptions().autoReconnect);
+ Assert.assertTrue("root".equals(jdbc.getOptions().user));
+ }
+
+ @Test(expected = IllegalArgumentException.class )
+ public void testOptionParseIntegerNotPossible() throws Throwable {
+ JDBCUrl.parse("jdbc:mysql://localhost/test?user=root&autoReconnect=true&validConnectionTimeout=-2&connectTimeout=5");
+ Assert.fail();
+ }
+
+ @Test()
+ public void testJDBCParserSimpleIPv4basic() {
+ String url = "jdbc:mysql://master:3306,slave1:3307,slave2:3308/database";
+ JDBCUrl jdbcUrl = JDBCUrl.parse(url);
+ }
+ @Test
+ public void testJDBCParserSimpleIPv4basicError() {
+ try {
+ JDBCUrl.parse(null);
+ Assert.fail();
+ }catch (IllegalArgumentException e) {
+ Assert.assertTrue(true);
+ }
+ }
+ @Test
+ public void testJDBCParserSimpleIPv4basicwithoutDatabase() {
+ String url = "jdbc:mysql://master:3306,slave1:3307,slave2:3308/";
+ JDBCUrl jdbcUrl = JDBCUrl.parse(url);
+ Assert.assertNull(jdbcUrl.getDatabase());
+ Assert.assertNull(jdbcUrl.getUsername());
+ Assert.assertNull(jdbcUrl.getPassword());
+ Assert.assertTrue(jdbcUrl.getHostAddresses().size() == 3);
+ Assert.assertTrue(new HostAddress("master", 3306).equals(jdbcUrl.getHostAddresses().get(0)));
+ Assert.assertTrue(new HostAddress("slave1", 3307).equals(jdbcUrl.getHostAddresses().get(1)));
+ Assert.assertTrue(new HostAddress("slave2", 3308).equals(jdbcUrl.getHostAddresses().get(2)));
+ }
+
+ @Test
+ public void testJDBCParserSimpleIPv4Properties() {
+ String url = "jdbc:mysql://master:3306,slave1:3307,slave2:3308/database?autoReconnect=true";
+ Properties prop = new Properties();
+ prop.setProperty("user","greg");
+ prop.setProperty("password","pass");
+
+ JDBCUrl jdbcUrl = JDBCUrl.parse(url, prop);
+ Assert.assertTrue("database".equals(jdbcUrl.getDatabase()));
+ Assert.assertTrue("greg".equals(jdbcUrl.getUsername()));
+ Assert.assertTrue("pass".equals(jdbcUrl.getPassword()));
+ Assert.assertTrue(jdbcUrl.getOptions().autoReconnect);
+ Assert.assertTrue(jdbcUrl.getHostAddresses().size() == 3);
+ Assert.assertTrue(new HostAddress("master", 3306).equals(jdbcUrl.getHostAddresses().get(0)));
+ Assert.assertTrue(new HostAddress("slave1", 3307).equals(jdbcUrl.getHostAddresses().get(1)));
+ Assert.assertTrue(new HostAddress("slave2", 3308).equals(jdbcUrl.getHostAddresses().get(2)));
+ }
+
+ @Test
+ public void testJDBCParserSimpleIPv4() {
+ String url = "jdbc:mysql://master:3306,slave1:3307,slave2:3308/database?user=greg&password=pass";
+ JDBCUrl jdbcUrl = JDBCUrl.parse(url);
+ Assert.assertTrue("database".equals(jdbcUrl.getDatabase()));
+ Assert.assertTrue("greg".equals(jdbcUrl.getUsername()));
+ Assert.assertTrue("pass".equals(jdbcUrl.getPassword()));
+ Assert.assertTrue(jdbcUrl.getHostAddresses().size() == 3);
+ Assert.assertTrue(new HostAddress("master", 3306).equals(jdbcUrl.getHostAddresses().get(0)));
+ Assert.assertTrue(new HostAddress("slave1", 3307).equals(jdbcUrl.getHostAddresses().get(1)));
+ Assert.assertTrue(new HostAddress("slave2", 3308).equals(jdbcUrl.getHostAddresses().get(2)));
+ }
+
+
+ @Test
+ public void testJDBCParserSimpleIPv6() {
+ String url = "jdbc:mysql://[2001:0660:7401:0200:0000:0000:0edf:bdd7]:3306,[2001:660:7401:200::edf:bdd7]:3307/database?user=greg&password=pass";
+ JDBCUrl jdbcUrl = JDBCUrl.parse(url);
+ Assert.assertTrue("database".equals(jdbcUrl.getDatabase()));
+ Assert.assertTrue("greg".equals(jdbcUrl.getUsername()));
+ Assert.assertTrue("pass".equals(jdbcUrl.getPassword()));
+ Assert.assertTrue(jdbcUrl.getHostAddresses().size() == 2);
+ Assert.assertTrue(new HostAddress("2001:0660:7401:0200:0000:0000:0edf:bdd7", 3306).equals(jdbcUrl.getHostAddresses().get(0)));
+ Assert.assertTrue(new HostAddress("2001:660:7401:200::edf:bdd7", 3307).equals(jdbcUrl.getHostAddresses().get(1)));
+ }
+
+
+ @Test
+ public void testJDBCParserParameter() {
+ String url = "jdbc:mysql://address=(type=master)(port=3306)(host=master1),address=(port=3307)(type=master)(host=master2),address=(type=slave)(host=slave1)(port=3308)/database?user=greg&password=pass";
+ JDBCUrl jdbcUrl = JDBCUrl.parse(url);
+ Assert.assertTrue("database".equals(jdbcUrl.getDatabase()));
+ Assert.assertTrue("greg".equals(jdbcUrl.getUsername()));
+ Assert.assertTrue("pass".equals(jdbcUrl.getPassword()));
+ Assert.assertTrue(jdbcUrl.getHostAddresses().size() == 3);
+ Assert.assertTrue(new HostAddress("master1", 3306, "master").equals(jdbcUrl.getHostAddresses().get(0)));
+ Assert.assertTrue(new HostAddress("master2", 3307, "master").equals(jdbcUrl.getHostAddresses().get(1)));
+ Assert.assertTrue(new HostAddress("slave1", 3308, "slave").equals(jdbcUrl.getHostAddresses().get(2)));
+ }
+
+ @Test
+ public void testJDBCParserParameterError() {
+ try {
+ JDBCUrl.parse(null);
+ Assert.fail();
+ }catch (IllegalArgumentException e) {
+ Assert.assertTrue(true);
+ }
+ }
+
+ @Test
+ public void testJDBCParserParameterErrorEqual() {
+ String url = "jdbc:mysql://address=(type=)(port=3306)(host=master1),address=(port=3307)(type=master)(host=master2),address=(type=slave)(host=slave1)(port=3308)/database?user=greg&password=pass";
+ try {
+ JDBCUrl.parse(null);
+ Assert.fail();
+ }catch (IllegalArgumentException e) {
+ Assert.assertTrue(true);
+ }
+ }
+
+ @Test
+ public void testJDBCParserHAModeNone() {
+ String url = "jdbc:mysql://localhost/database";
+ JDBCUrl jdbc = JDBCUrl.parse(url);
+ Assert.assertTrue(jdbc.getHaMode().equals(UrlHAMode.NONE));
+ }
+
+ @Test
+ public void testJDBCParserHAModeLoadReplication() {
+ String url = "jdbc:mysql:replication://localhost/database";
+ JDBCUrl jdbc = JDBCUrl.parse(url);
+ Assert.assertTrue(jdbc.getHaMode().equals(UrlHAMode.REPLICATION));
+ }
+
+ @Test
+ public void testJDBCParserReplicationParameter() {
+ String url = "jdbc:mysql:replication://address=(type=master)(port=3306)(host=master1),address=(port=3307)(type=master)(host=master2),address=(type=slave)(host=slave1)(port=3308)/database?user=greg&password=pass";
+ JDBCUrl jdbcUrl = JDBCUrl.parse(url);
+ Assert.assertTrue("database".equals(jdbcUrl.getDatabase()));
+ Assert.assertTrue("greg".equals(jdbcUrl.getUsername()));
+ Assert.assertTrue("pass".equals(jdbcUrl.getPassword()));
+ Assert.assertTrue(jdbcUrl.getHostAddresses().size() == 3);
+ Assert.assertTrue(new HostAddress("master1", 3306, "master").equals(jdbcUrl.getHostAddresses().get(0)));
+ Assert.assertTrue(new HostAddress("master2", 3307, "master").equals(jdbcUrl.getHostAddresses().get(1)));
+ Assert.assertTrue(new HostAddress("slave1", 3308, "slave").equals(jdbcUrl.getHostAddresses().get(2)));
+ }
+
+ @Test
+ public void testJDBCParserReplicationParameterWithoutType() {
+ String url = "jdbc:mysql:replication://master1,slave1,slave2/database";
+ JDBCUrl jdbcUrl = JDBCUrl.parse(url);
+ Assert.assertTrue("database".equals(jdbcUrl.getDatabase()));
+ Assert.assertTrue(jdbcUrl.getHostAddresses().size() == 3);
+ Assert.assertTrue(new HostAddress("master1", 3306, "master").equals(jdbcUrl.getHostAddresses().get(0)));
+ Assert.assertTrue(new HostAddress("slave1", 3306, "slave").equals(jdbcUrl.getHostAddresses().get(1)));
+ Assert.assertTrue(new HostAddress("slave2", 3306, "slave").equals(jdbcUrl.getHostAddresses().get(2)));
+ }
+
+ @Test
+ public void testJDBCParserHAModeLoadAurora() {
+ String url = "jdbc:mysql:aurora://localhost/database";
+ JDBCUrl jdbc = JDBCUrl.parse(url);
+ Assert.assertTrue(jdbc.getHaMode().equals(UrlHAMode.AURORA));
+ }
+
+}
diff --git a/src/test/java/org/mariadb/jdbc/MultiTest.java b/src/test/java/org/mariadb/jdbc/MultiTest.java
index f41b83d9f..54dac43a9 100644
--- a/src/test/java/org/mariadb/jdbc/MultiTest.java
+++ b/src/test/java/org/mariadb/jdbc/MultiTest.java
@@ -7,14 +7,14 @@
import java.util.logging.Level;
import java.util.logging.Logger;
-import junit.framework.Assert;
+import org.junit.Assert;
import org.mariadb.jdbc.internal.common.packet.PacketOutputStream;
import static org.junit.Assert.*;
public class MultiTest extends BaseTest {
- private static Connection connection;
+ private static Connection connectionMulti;
public MultiTest() throws SQLException {
}
@@ -23,8 +23,8 @@ public MultiTest() throws SQLException {
public static void beforeClassMultiTest() throws SQLException {
BaseTest baseTest = new BaseTest();
baseTest.setConnection("&allowMultiQueries=true");
- connection = baseTest.connection;
- Statement st = connection.createStatement();
+ connectionMulti = baseTest.connection;
+ Statement st = connectionMulti.createStatement();
st.executeUpdate("drop table if exists t1");
st.executeUpdate("drop table if exists t2");
st.executeUpdate("drop table if exists t3");
@@ -46,7 +46,7 @@ public static void beforeClassMultiTest() throws SQLException {
@AfterClass
public static void afterClass() throws SQLException {
try {
- Statement st = connection.createStatement();
+ Statement st = connectionMulti.createStatement();
st.executeUpdate("drop table if exists t1");
st.executeUpdate("drop table if exists t2");
st.executeUpdate("drop table if exists t3");
@@ -55,7 +55,7 @@ public static void afterClass() throws SQLException {
// eat
} finally {
try {
- connection.close();
+ connectionMulti.close();
} catch (Exception e) {
}
@@ -65,7 +65,8 @@ public static void afterClass() throws SQLException {
@Test
public void basicTest() throws SQLException {
- Statement statement = connection.createStatement();
+ log.fine("basicTest begin");
+ Statement statement = connectionMulti.createStatement();
ResultSet rs = statement.executeQuery("select * from t1;select * from t2;");
int count = 0;
while (rs.next()) {
@@ -80,11 +81,13 @@ public void basicTest() throws SQLException {
}
assertTrue(count > 0);
assertFalse(statement.getMoreResults());
+ log.fine("basicTest end");
}
@Test
public void updateTest() throws SQLException {
- Statement statement = connection.createStatement();
+ log.fine("updateTest begin");
+ Statement statement = connectionMulti.createStatement();
statement.execute("update t5 set test='a " + System.currentTimeMillis() + "' where id = 2;select * from t2;");
int updateNb = statement.getUpdateCount();
log.fine("statement.getUpdateCount() " + updateNb);
@@ -97,11 +100,13 @@ public void updateTest() throws SQLException {
}
assertTrue(count > 0);
assertFalse(statement.getMoreResults());
+ log.fine("updateTest end");
}
@Test
public void updateTest2() throws SQLException {
- Statement statement = connection.createStatement();
+ log.fine("updateTest2 begin");
+ Statement statement = connectionMulti.createStatement();
statement.execute("select * from t2;update t5 set test='a " + System.currentTimeMillis() + "' where id = 2;");
ResultSet rs = statement.getResultSet();
int count = 0;
@@ -114,11 +119,13 @@ public void updateTest2() throws SQLException {
int updateNb = statement.getUpdateCount();
log.fine("statement.getUpdateCount() " + updateNb);
assertEquals(2, updateNb);
+ log.fine("updateTest2 end");
}
@Test
public void selectTest() throws SQLException {
- Statement statement = connection.createStatement();
+ log.fine("selectTest begin");
+ Statement statement = connectionMulti.createStatement();
statement.execute("select * from t2;select * from t1;");
ResultSet rs = statement.getResultSet();
int count = 0;
@@ -132,11 +139,13 @@ public void selectTest() throws SQLException {
count++;
}
assertTrue(count > 0);
+ log.fine("selectTest end");
}
@Test
public void setMaxRowsMulti() throws Exception {
- Statement st = connection.createStatement();
+ log.fine("setMaxRowsMulti begin");
+ Statement st = connectionMulti.createStatement();
assertEquals(0, st.getMaxRows());
st.setMaxRows(1);
@@ -163,6 +172,7 @@ public void setMaxRowsMulti() throws Exception {
}
rs.close();
assertEquals(1, cnt);
+ log.fine("setMaxRowsMulti end");
}
/**
@@ -172,20 +182,26 @@ public void setMaxRowsMulti() throws Exception {
*/
@Test
public void rewriteBatchedStatementsDisabledInsertionTest() throws SQLException {
+ log.fine("rewriteBatchedStatementsDisabledInsertionTest begin");
verifyInsertBehaviorBasedOnRewriteBatchedStatements(Boolean.FALSE, 3000);
+ log.fine("rewriteBatchedStatementsDisabledInsertionTest end");
}
@Test
public void rewriteBatchedStatementsEnabledInsertionTest() throws SQLException {
+ log.fine("rewriteBatchedStatementsEnabledInsertionTest begin");
//On batch mode, single insert query will be sent to MariaDB server.
verifyInsertBehaviorBasedOnRewriteBatchedStatements(Boolean.TRUE, 1);
+ log.fine("rewriteBatchedStatementsEnabledInsertionTest end");
}
private void verifyInsertBehaviorBasedOnRewriteBatchedStatements(Boolean rewriteBatchedStatements, int totalInsertCommands) throws SQLException {
Properties props = new Properties();
props.setProperty("rewriteBatchedStatements", rewriteBatchedStatements.toString());
- Connection tmpConnection = openNewConnection(connURI, props);
+ props.setProperty("allowMultiQueries", "true");
+ Connection tmpConnection = null;
try {
+ tmpConnection = openNewConnection(connURI, props);
verifyInsertCount(tmpConnection, 0);
int cycles = 3000;
tmpConnection.createStatement().execute("TRUNCATE t1");
@@ -231,11 +247,14 @@ private int retrieveSessionVariableFromServer(Connection tmpConnection, String v
*/
@Test
public void rewriteBatchedStatementsSemicolon() throws SQLException {
+ log.fine("rewriteBatchedStatementsSemicolon begin");
// set the rewrite batch statements parameter
Properties props = new Properties();
props.setProperty("rewriteBatchedStatements", "true");
- Connection tmpConnection = openNewConnection(connURI, props);
+ props.setProperty("allowMultiQueries", "true");
+ Connection tmpConnection = null;
try {
+ tmpConnection = openNewConnection(connURI, props);
tmpConnection.createStatement().execute("TRUNCATE t3");
PreparedStatement sqlInsert = tmpConnection.prepareStatement("INSERT INTO t3 (message) VALUES (?)");
@@ -274,7 +293,8 @@ public void rewriteBatchedStatementsSemicolon() throws SQLException {
verifyInsertCount(tmpConnection, 5);
tmpConnection.commit();
} finally {
- tmpConnection.close();
+ log.fine("rewriteBatchedStatementsSemicolon end");
+ if (tmpConnection != null) tmpConnection.close();
}
}
@@ -299,11 +319,14 @@ private PreparedStatement prepareStatementBatch(Connection tmpConnection, int si
*/
@Test
public void rewriteBatchedStatementsUpdateTest() throws SQLException {
+ log.fine("rewriteBatchedStatementsUpdateTest begin");
// set the rewrite batch statements parameter
Properties props = new Properties();
props.setProperty("rewriteBatchedStatements", "true");
- Connection tmpConnection = openNewConnection(connURI, props);
+ props.setProperty("allowMultiQueries", "true");
+ Connection tmpConnection = null;
try {
+ tmpConnection = openNewConnection(connURI, props);
tmpConnection.setClientInfo(props);
verifyUpdateCount(tmpConnection, 0);
tmpConnection.createStatement().execute("TRUNCATE t1");
@@ -326,7 +349,8 @@ public void rewriteBatchedStatementsUpdateTest() throws SQLException {
verifyUpdateCount(tmpConnection, cycles); //1000 update commande launched
assertEquals(cycles * 2, totalUpdates); // 2000 rows updates
} finally {
- tmpConnection.close();
+ log.fine("rewriteBatchedStatementsUpdateTest end");
+ if (tmpConnection != null) tmpConnection.close();
}
}
@@ -336,11 +360,14 @@ public void rewriteBatchedStatementsUpdateTest() throws SQLException {
*/
@Test
public void testMultipleExecuteBatch() throws SQLException {
+ log.fine("testMultipleExecuteBatch begin");
// set the rewrite batch statements parameter
Properties props = new Properties();
props.setProperty("rewriteBatchedStatements", "true");
- Connection tmpConnection = openNewConnection(connURI, props);
+ props.setProperty("allowMultiQueries", "true");
+ Connection tmpConnection = null;
try {
+ tmpConnection = openNewConnection(connURI, props);
tmpConnection.setClientInfo(props);
verifyUpdateCount(tmpConnection, 0);
tmpConnection.createStatement().execute("TRUNCATE t1");
@@ -364,16 +391,20 @@ public void testMultipleExecuteBatch() throws SQLException {
updateCounts = preparedStatement.executeBatch();
assertEquals(1, updateCounts.length);
} finally {
- tmpConnection.close();
+ log.fine("testMultipleExecuteBatch end");
+ if (tmpConnection != null) tmpConnection.close();
}
}
@Test
public void rewriteBatchedStatementsInsertWithDuplicateRecordsTest() throws SQLException {
+ log.fine("rewriteBatchedStatementsInsertWithDuplicateRecordsTest begin");
Properties props = new Properties();
props.setProperty("rewriteBatchedStatements", "true");
- Connection tmpConnection = openNewConnection(connURI, props);
+ props.setProperty("allowMultiQueries", "true");
+ Connection tmpConnection = null;
try {
+ tmpConnection = openNewConnection(connURI, props);
verifyInsertCount(tmpConnection, 0);
tmpConnection.createStatement().execute("TRUNCATE reWriteDuplicateTestTable");
Statement statement = tmpConnection.createStatement();
@@ -391,16 +422,20 @@ public void rewriteBatchedStatementsInsertWithDuplicateRecordsTest() throws SQLE
verifyInsertCount(tmpConnection, 1);
verifyUpdateCount(tmpConnection, 0);
} finally {
- tmpConnection.close();
+ log.fine("rewriteBatchedStatementsInsertWithDuplicateRecordsTest end");
+ if (tmpConnection != null) tmpConnection.close();
}
}
@Test
public void updateCountTest() throws SQLException {
+ log.fine("updateCountTest begin");
Properties props = new Properties();
props.setProperty("rewriteBatchedStatements", "true");
- Connection tmpConnection = openNewConnection(connURI, props);
+ props.setProperty("allowMultiQueries", "true");
+ Connection tmpConnection = null;
try {
+ tmpConnection = openNewConnection(connURI, props);
PreparedStatement sqlInsert = tmpConnection.prepareStatement("INSERT IGNORE INTO t4 (id,test) VALUES (?,?)");
sqlInsert.setInt(1, 1);
sqlInsert.setString(2, "value1");
@@ -442,7 +477,8 @@ public void updateCountTest() throws SQLException {
Assert.assertEquals(0, updateCounts[1]);
Assert.assertEquals(2, updateCounts[2]);
} finally {
- tmpConnection.close();
+ log.fine("updateCountTest end");
+ if (tmpConnection != null) tmpConnection.close();
}
}
diff --git a/src/test/java/org/mariadb/jdbc/MySQLDriverTest.java b/src/test/java/org/mariadb/jdbc/MySQLDriverTest.java
index a8cc8d5a1..dab532a1e 100644
--- a/src/test/java/org/mariadb/jdbc/MySQLDriverTest.java
+++ b/src/test/java/org/mariadb/jdbc/MySQLDriverTest.java
@@ -17,8 +17,8 @@
import java.util.Arrays;
import java.util.Properties;
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertFalse;
@@ -615,7 +615,7 @@ public void executeStatementAfterConnectionClose() throws Exception {
try {
st.execute("select 1");
} catch (SQLException sqle) {
- assertTrue(sqle.getMessage().contains("closed connection"));
+ assertTrue(sqle.getMessage().contains("execute() is called on closed connection"));
}
}
@@ -647,7 +647,7 @@ public void connectToDbWithDashInName() throws Exception {
public void connectCreateDB() throws Exception {
String oldParams = parameters;
String oldDb = database;
- setParameters("&createDB=true");
+ setParameters("&createDatabaseIfNotExist=true");
setDatabase("no-such-db");
try {
assertEquals("no-such-db",connection.getCatalog());
diff --git a/src/test/java/org/mariadb/jdbc/ParserTest.java b/src/test/java/org/mariadb/jdbc/ParserTest.java
index 26e293809..5a3927111 100644
--- a/src/test/java/org/mariadb/jdbc/ParserTest.java
+++ b/src/test/java/org/mariadb/jdbc/ParserTest.java
@@ -1,15 +1,25 @@
package org.mariadb.jdbc;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
import java.sql.ResultSet;
+import java.sql.SQLClientInfoException;
import java.sql.SQLException;
import java.sql.Statement;
+import java.util.Properties;
+import org.junit.Assert.*;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
+import org.mariadb.jdbc.internal.common.Options;
+import org.mariadb.jdbc.internal.common.UrlHAMode;
+import org.mariadb.jdbc.internal.common.query.IllegalParameterException;
+import org.mariadb.jdbc.internal.mysql.Protocol;
public class ParserTest extends BaseTest {
private Statement statement;
@@ -29,6 +39,25 @@ public void cleanup() {
}
}
+ @Test
+ public void addProperties() throws Exception {
+ Field field = MySQLConnection.class.getDeclaredField("options");
+ field.setAccessible(true);
+ Options options = (Options) field.get(connection);
+ assertFalse(options.useSSL);
+ connection.setClientInfo("useSSL", "true");
+
+ options = (Options) field.get(connection);
+ assertTrue(options.useSSL);
+
+ Properties prop = new Properties();
+ prop.put("autoReconnect", "true");
+ prop.put("useSSL", "false");
+ connection.setClientInfo(prop);
+ assertFalse(options.useSSL);
+ assertTrue(options.autoReconnect);
+ }
+
@Test
public void libreOfficeBase() {
String sql;
diff --git a/src/test/java/org/mariadb/jdbc/PooledConnectionTest.java b/src/test/java/org/mariadb/jdbc/PooledConnectionTest.java
index 98a3e2efe..7fe792199 100644
--- a/src/test/java/org/mariadb/jdbc/PooledConnectionTest.java
+++ b/src/test/java/org/mariadb/jdbc/PooledConnectionTest.java
@@ -1,15 +1,12 @@
package org.mariadb.jdbc;
+import org.junit.Assert;
import org.junit.Test;
import javax.sql.*;
import java.sql.*;
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertTrue;
-
-class MyEventListener implements ConnectionEventListener,StatementEventListener
-{
+class MyEventListener implements ConnectionEventListener,StatementEventListener {
public SQLException sqlException;
public boolean closed;
public boolean connectionErrorOccured;
@@ -51,7 +48,7 @@ public void testPooledConnectionClosed() throws Exception {
MyEventListener listener = new MyEventListener();
pc.addConnectionEventListener(listener);
c.close();
- assertTrue(listener.closed);
+ Assert.assertTrue(listener.closed);
/* Verify physical connection is still ok */
c.createStatement().execute("select 1");
@@ -60,7 +57,7 @@ public void testPooledConnectionClosed() throws Exception {
/* Now verify physical connection is gone */
try {
c.createStatement().execute("select 1");
- assertFalse("should never get there", true);
+ Assert.assertFalse("should never get there", true);
} catch(Exception e) {
}
@@ -85,12 +82,8 @@ public void testPooledConnectionException() throws Exception {
/* Try to read after server side closed the connection */
try {
c.createStatement().execute("SELECT 1");
-
- assertTrue("should never get there", false);
- }
- catch (Exception e) {
- /* Check that listener was actually called*/
- assertTrue(listener.sqlException instanceof SQLNonTransientConnectionException);
+ Assert.assertTrue("should never get there", false);
+ } catch (SQLException e) {
}
pc.close();
//assertTrue(listener.closed);
@@ -108,13 +101,13 @@ public void testPooledConnectionStatementError() throws Exception
PreparedStatement ps = c.prepareStatement("zzzz");
try {
ps.execute();
- assertTrue("should never get there", false);
+ Assert.assertTrue("should never get there", false);
}
catch(Exception e) {
- assertTrue(listener.statementErrorOccured && listener.sqlException instanceof SQLSyntaxErrorException);
+ Assert.assertTrue(listener.statementErrorOccured && listener.sqlException instanceof SQLSyntaxErrorException);
}
ps.close();
- assertTrue(listener.statementClosed);
+ Assert.assertTrue(listener.statementClosed);
pc.close();
}
}
diff --git a/src/test/java/org/mariadb/jdbc/ResultSetMetaDataTest.java b/src/test/java/org/mariadb/jdbc/ResultSetMetaDataTest.java
index 6bcebad5b..83a0d2840 100644
--- a/src/test/java/org/mariadb/jdbc/ResultSetMetaDataTest.java
+++ b/src/test/java/org/mariadb/jdbc/ResultSetMetaDataTest.java
@@ -7,7 +7,7 @@
import java.util.logging.Level;
import java.util.logging.Logger;
-import static junit.framework.Assert.assertEquals;
+import static org.junit.Assert.assertEquals;
public class ResultSetMetaDataTest extends BaseTest {
static {
diff --git a/src/test/java/org/mariadb/jdbc/TimeoutTest.java b/src/test/java/org/mariadb/jdbc/TimeoutTest.java
index cd181e709..797840223 100644
--- a/src/test/java/org/mariadb/jdbc/TimeoutTest.java
+++ b/src/test/java/org/mariadb/jdbc/TimeoutTest.java
@@ -11,6 +11,7 @@
import java.sql.Statement;
import java.util.Random;
+import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
@@ -84,50 +85,42 @@ public void socketTimeoutTest() throws SQLException {
ps = connection.prepareStatement("SELECT sleep(1)");
// a timeout should occur here
- try
- {
+ try {
rs = ps.executeQuery();
+ Assert.fail();
} catch (SQLException e) {
// check that it's a timeout that occurs
if (e.getMessage().contains("timed out"))
exceptionCount++;
}
-
- ps = connection.prepareStatement("SELECT 2");
- // connection should be closed here, exception expected
- try
- {
- rs = ps.executeQuery();
- rs.next();
- } catch (SQLException e) {
- // check that it's a execute "called on closed" exception that occurs
- if (e.getMessage().contains("called on closed"))
- exceptionCount++;
+ try {
+ ps = connection.prepareStatement("SELECT 2");
+ ps.execute();
+ Assert.fail();
+ } catch (Exception e){
+
}
- // the connection should be closed
+ // the connection should be closed
assertTrue(connection.isClosed());
- // there should have been two exceptions
- assertTrue(exceptionCount == 2);
}
@Test
public void waitTimeoutStatementTest() throws SQLException, InterruptedException {
Statement statement = connection.createStatement();
statement.execute("set session wait_timeout=1");
- Thread.sleep(3000); // Wait for the server to kill the connection
+ Thread.sleep(2000); // Wait for the server to kill the connection
logInfo(connection.toString());
// here a SQLNonTransientConnectionException is expected
// "Could not read resultset: unexpected end of stream, ..."
- try
- {
+ try {
statement.execute("SELECT 1");
+ Assert.fail();
} catch (SQLException e) {
- // verify that the correct type of exception is thrown
- assertTrue(e.getMessage().contains("Could not read resultset"));
+
}
statement.close();
@@ -147,13 +140,10 @@ public void waitTimeoutResultSetTest() throws SQLException, InterruptedException
// here a SQLNonTransientConnectionException is expected
// "Could not read resultset: unexpected end of stream, ..."
- try
- {
+ try {
rs = stmt.executeQuery("SELECT 2");
rs.next();
} catch (SQLException e) {
- // verify that the correct type of exception is thrown
- assertTrue(e.getMessage().contains("Could not read resultset"));
}
}
diff --git a/src/test/java/org/mariadb/jdbc/TimezoneDaylightSavingTimeTest.java b/src/test/java/org/mariadb/jdbc/TimezoneDaylightSavingTimeTest.java
index 981fd564e..19b4ec749 100644
--- a/src/test/java/org/mariadb/jdbc/TimezoneDaylightSavingTimeTest.java
+++ b/src/test/java/org/mariadb/jdbc/TimezoneDaylightSavingTimeTest.java
@@ -56,9 +56,9 @@ public void setUp() throws SQLException {
@After
public void tearDown() {
//Reset the FORMAT locate so other test cases are not disturbed.
- Locale.setDefault(previousFormatLocale);
+ if (previousFormatLocale!=null) Locale.setDefault(previousFormatLocale);
//Reset the timezone so so other test cases are not disturbed.
- TimeZone.setDefault(previousTimeZone);
+ if (previousTimeZone != null ) TimeZone.setDefault(previousTimeZone);
}
diff --git a/src/test/java/org/mariadb/jdbc/UnicodeTest.java b/src/test/java/org/mariadb/jdbc/UnicodeTest.java
index 2c595b949..fdc6136f2 100644
--- a/src/test/java/org/mariadb/jdbc/UnicodeTest.java
+++ b/src/test/java/org/mariadb/jdbc/UnicodeTest.java
@@ -6,7 +6,7 @@
import java.util.logging.Logger;
import java.util.logging.Level;
-import static junit.framework.Assert.assertEquals;
+import static org.junit.Assert.assertEquals;
public class UnicodeTest extends BaseTest {
diff --git a/src/test/java/org/mariadb/jdbc/UtilTest.java b/src/test/java/org/mariadb/jdbc/UtilTest.java
index bf17a25d2..08c00e0b7 100644
--- a/src/test/java/org/mariadb/jdbc/UtilTest.java
+++ b/src/test/java/org/mariadb/jdbc/UtilTest.java
@@ -5,7 +5,7 @@
import java.sql.SQLException;
-import static junit.framework.Assert.assertEquals;
+import static org.junit.Assert.assertEquals;
public class UtilTest {
diff --git a/src/test/java/org/mariadb/jdbc/XA.java b/src/test/java/org/mariadb/jdbc/XA.java
index 05bbfe9e8..749f89917 100644
--- a/src/test/java/org/mariadb/jdbc/XA.java
+++ b/src/test/java/org/mariadb/jdbc/XA.java
@@ -1,9 +1,9 @@
package org.mariadb.jdbc;
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
import java.sql.Connection;
import java.sql.ResultSet;
diff --git a/src/test/java/org/mariadb/jdbc/failover/AuroraFailoverTest.java b/src/test/java/org/mariadb/jdbc/failover/AuroraFailoverTest.java
new file mode 100644
index 000000000..0754360df
--- /dev/null
+++ b/src/test/java/org/mariadb/jdbc/failover/AuroraFailoverTest.java
@@ -0,0 +1,700 @@
+package org.mariadb.jdbc.failover;
+
+import org.junit.*;
+import org.junit.Test;
+import org.mariadb.jdbc.internal.mysql.FailoverProxy;
+
+import java.sql.*;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.Assert.assertTrue;
+
+public class AuroraFailoverTest extends BaseMultiHostTest {
+ private Connection connection;
+ private long testBeginTime;
+
+ @Before
+ public void init() throws SQLException {
+ initialUrl = initialAuroraUrl;
+ proxyUrl = proxyAuroraUrl;
+ currentType = TestType.AURORA;
+ Assume.assumeTrue(initialAuroraUrl != null);
+ connection = null;
+ testBeginTime = System.currentTimeMillis();
+ }
+
+ @After
+ public void after() throws SQLException {
+ assureProxy();
+ if (connection != null) {
+ connection.close();
+ assureBlackList(connection);
+ }
+ log.fine("test time : " + (System.currentTimeMillis() - testBeginTime) + "ms");
+ }
+
+ @Test
+ public void testWriteOnMaster() throws SQLException {
+ connection = getNewConnection(false);
+ Statement stmt = connection.createStatement();
+ stmt.execute("drop table if exists multinode");
+ stmt.execute("create table multinode (id int not null primary key auto_increment, test VARCHAR(10))");
+ }
+
+ @Test
+ public void testErrorWriteOnReplica() throws SQLException {
+ connection = getNewConnection(false);
+ connection.setReadOnly(true);
+ Statement stmt = connection.createStatement();
+ Assert.assertTrue(connection.isReadOnly());
+ try {
+ stmt.execute("drop table if exists multinode4");
+ log.severe("ERROR - > must not be able to write on slave --> check if you database is start with --read-only");
+ Assert.fail();
+ } catch (SQLException e) {
+ }
+ }
+
+ @Test
+ public void testReplication() throws SQLException, InterruptedException {
+ connection = getNewConnection(false);
+ Statement stmt = connection.createStatement();
+ stmt.execute("drop table if exists multinodeReadSlave");
+ stmt.execute("create table multinodeReadSlave (id int not null primary key auto_increment, test VARCHAR(10))");
+
+ //wait to be sure slave have replicate data
+ Thread.sleep(200);
+
+ connection.setReadOnly(true);
+
+ ResultSet rs = stmt.executeQuery("Select count(*) from multinodeReadSlave");
+ Assert.assertTrue(rs.next());
+ }
+
+
+ @Test
+ public void randomConnection() throws Throwable {
+ Map connectionMap = new HashMap();
+ int masterId = -1;
+ for (int i = 0; i < 20; i++) {
+ connection = getNewConnection(false);
+ int serverId = getServerId(connection);
+ log.fine("master server found " + serverId);
+ if (i > 0) Assert.assertTrue(masterId == serverId);
+ masterId = serverId;
+ connection.setReadOnly(true);
+ int replicaId = getServerId(connection);
+ log.fine("++++++++++++slave server found " + replicaId);
+ MutableInt count = connectionMap.get(String.valueOf(replicaId));
+ if (count == null) {
+ connectionMap.put(String.valueOf(replicaId), new MutableInt());
+ } else {
+ count.increment();
+ }
+ connection.close();
+ }
+
+ Assert.assertTrue(connectionMap.size() >= 2);
+ for (String key : connectionMap.keySet()) {
+ Integer connectionCount = connectionMap.get(key).get();
+ log.fine(" ++++ Server " + key + " : " + connectionCount + " connections ");
+ Assert.assertTrue(connectionCount > 1);
+ }
+ log.fine("randomConnection OK");
+ }
+
+
+ @Test
+ public void failoverSlaveToMaster() throws Throwable {
+ connection = getNewConnection("&retriesAllDown=1", true);
+ int masterServerId = getServerId(connection);
+ connection.setReadOnly(true);
+ int slaveServerId = getServerId(connection);
+ Assert.assertFalse(masterServerId == slaveServerId);
+ stopProxy(slaveServerId);
+ connection.createStatement().execute("SELECT 1");
+ int currentServerId = getServerId(connection);
+
+ log.fine("masterServerId = " + masterServerId + "/currentServerId = " + currentServerId);
+ Assert.assertTrue(masterServerId == currentServerId);
+
+ Assert.assertFalse(connection.isReadOnly());
+ }
+
+
+ @Test
+ public void failoverSlaveToMasterFail() throws Throwable {
+ connection = getNewConnection("&retriesAllDown=1&secondsBeforeRetryMaster=1", true);
+ int masterServerId = getServerId(connection);
+ connection.setReadOnly(true);
+ int slaveServerId = getServerId(connection);
+ Assert.assertTrue(slaveServerId != masterServerId);
+
+ connection.setCatalog("mysql"); //to be sure there will be a query, and so an error when switching connection
+ stopProxy(masterServerId);
+ try {
+ //must not throw error until there is a query
+ connection.setReadOnly(false);
+ Assert.fail();
+ } catch (SQLException e) {
+ }
+ }
+
+ @Test
+ public void pingReconnectAfterRestart() throws Throwable {
+ connection = getNewConnection("&retriesAllDown=1&secondsBeforeRetryMaster=1&failOnReadOnly=false&queriesBeforeRetryMaster=50000", true);
+ Statement st = connection.createStatement();
+ int masterServerId = getServerId(connection);
+ stopProxy(masterServerId);
+
+ long stoppedTime = System.currentTimeMillis();
+ try {
+ st.execute("SELECT 1");
+ } catch (SQLException e) {
+ }
+ restartProxy(masterServerId);
+ long restartTime = System.currentTimeMillis();
+
+ boolean loop = true;
+ while (loop) {
+ if (!connection.isClosed()) {
+ log.fine("reconnection with failover loop after : " + (System.currentTimeMillis() - stoppedTime) + "ms");
+ loop = false;
+ }
+ if (System.currentTimeMillis() - restartTime > 15 * 1000) Assert.fail();
+ Thread.sleep(250);
+ }
+ }
+
+
+ @Test
+ public void failoverDuringMasterSetReadOnly() throws Throwable {
+ int masterServerId = -1;
+ connection = getNewConnection("&retriesAllDown=1", true);
+ masterServerId = getServerId(connection);
+
+ stopProxy(masterServerId);
+
+ connection.setReadOnly(true);
+
+ int slaveServerId = getServerId(connection);
+
+ Assert.assertFalse(slaveServerId == masterServerId);
+ Assert.assertTrue(connection.isReadOnly());
+ }
+
+ @Test
+ public void failoverDuringSlaveSetReadOnly() throws Throwable {
+ connection = getNewConnection(true);
+ connection.setReadOnly(true);
+ int slaveServerId = getServerId(connection);
+
+ stopProxy(slaveServerId, 2000);
+
+ connection.setReadOnly(false);
+
+ int masterServerId = getServerId(connection);
+
+ Assert.assertFalse(slaveServerId == masterServerId);
+ Assert.assertFalse(connection.isReadOnly());
+ }
+
+ @Test()
+ public void failoverSlaveAndMasterWithoutAutoConnect() throws Throwable {
+ connection = getNewConnection("&retriesAllDown=1", true);
+ int masterServerId = getServerId(connection);
+ log.fine("master server_id = " + masterServerId);
+ connection.setReadOnly(true);
+ int firstSlaveId = getServerId(connection);
+ log.fine("slave1 server_id = " + firstSlaveId);
+
+ stopProxy(masterServerId);
+ stopProxy(firstSlaveId);
+
+
+ try {
+ connection.createStatement().executeQuery("SELECT CONNECTION_ID()");
+ } catch (SQLException e) {
+ Assert.fail();
+ }
+ }
+
+ @Test
+ public void reconnectSlaveAndMasterWithAutoConnect() throws Throwable {
+ connection = getNewConnection("&retriesAllDown=1&autoReconnect=true", true);
+
+ //search actual server_id for master and slave
+ int masterServerId = getServerId(connection);
+ log.fine("master server_id = " + masterServerId);
+
+ connection.setReadOnly(true);
+
+ int firstSlaveId = getServerId(connection);
+ log.fine("slave1 server_id = " + firstSlaveId);
+
+ stopProxy(masterServerId);
+ stopProxy(firstSlaveId);
+
+ //must reconnect to the second slave without error
+ connection.createStatement().execute("SELECT 1");
+ int currentSlaveId = getServerId(connection);
+ log.fine("currentSlaveId server_id = " + currentSlaveId);
+ Assert.assertTrue(currentSlaveId != firstSlaveId);
+ Assert.assertTrue(currentSlaveId != masterServerId);
+ }
+
+
+ @Test
+ public void failOnSlaveAndMasterWithAutoConnect() throws Throwable {
+ connection = getNewConnection("&retriesAllDown=1&autoReconnect=true&failOnReadOnly=true", true);
+
+ //search actual server_id for master and slave
+ int masterServerId = getServerId(connection);
+ log.fine("master server_id = " + masterServerId);
+
+ connection.setReadOnly(true);
+
+ int firstSlaveId = getServerId(connection);
+ log.fine("slave1 server_id = " + firstSlaveId);
+
+ stopProxy(masterServerId);
+ stopProxy(firstSlaveId);
+
+ //must reconnect to the second slave without error
+ connection.createStatement().execute("SELECT 1");
+ int currentSlaveId = getServerId(connection);
+ log.fine("currentSlaveId server_id = " + currentSlaveId);
+ Assert.assertTrue(currentSlaveId != firstSlaveId);
+ Assert.assertTrue(currentSlaveId != masterServerId);
+ }
+
+
+ @Test
+ public void failoverMasterWithAutoConnect() throws Throwable {
+ connection = getNewConnection("&retriesAllDown=1&autoReconnect=true", true);
+ int masterServerId = getServerId(connection);
+
+ stopProxy(masterServerId, 250);
+ //with autoreconnect, the connection must reconnect automatically
+ int currentServerId = getServerId(connection);
+
+ Assert.assertTrue(currentServerId == masterServerId);
+ Assert.assertFalse(connection.isReadOnly());
+ }
+
+ @Test
+ public void checkReconnectionToMasterAfterQueryNumber() throws Throwable {
+ connection = getNewConnection("&retriesAllDown=1&secondsBeforeRetryMaster=3000&queriesBeforeRetryMaster=10&failOnReadOnly=true", true);
+ Statement st = connection.createStatement();
+ int masterServerId = getServerId(connection);
+ stopProxy(masterServerId);
+ try {
+ st.execute("SELECT 1");
+ } catch (SQLException e) {
+ Assert.fail();
+ }
+ Assert.assertTrue(connection.isReadOnly());
+
+ restartProxy(masterServerId);
+ long stoppedTime = System.currentTimeMillis();
+
+ //not in autoreconnect mode, so must wait for query more than queriesBeforeRetryMaster
+ for (int i = 1; i < 10; i++) {
+ try {
+ st.execute("SELECT 1");
+ log.fine("i=" + i);
+ Assert.assertTrue(connection.isReadOnly());
+ } catch (SQLException e) {
+ Assert.fail();
+ }
+ }
+
+ boolean loop = true;
+ while (loop) {
+ try {
+ Thread.sleep(250);
+ st.execute("SELECT 1");
+ if (!connection.isReadOnly()) {
+ log.fine("reconnection with failover loop after : " + (System.currentTimeMillis() - stoppedTime) + "ms");
+ loop = false;
+ }
+ } catch (SQLException e) {
+ log.fine("not reconnected ... ");
+ }
+ if (System.currentTimeMillis() - stoppedTime > 20 * 1000) Assert.fail();
+ }
+ }
+
+ @Test
+ public void reconnectMasterAfterFailover() throws Throwable {
+ connection = getNewConnection("&retriesAllDown=1", true);
+ //if super user can write on slave
+ Assume.assumeTrue(!hasSuperPrivilege(connection, "reconnectMasterAfterFailover"));
+ Statement st = connection.createStatement();
+ st.execute("drop table if exists multinode2");
+ st.execute("create table multinode2 (id int not null primary key , amount int not null) ENGINE = InnoDB");
+ st.execute("insert into multinode2 (id, amount) VALUE (1 , 100)");
+
+ int masterServerId = getServerId(connection);
+ long stopTime = System.currentTimeMillis();
+ stopProxy(masterServerId, 10000);
+ try {
+ st.execute("insert into multinode2 (id, amount) VALUE (2 , 100)");
+ Assert.assertTrue(System.currentTimeMillis() - stopTime > 10);
+ Assert.assertTrue(System.currentTimeMillis() - stopTime < 20);
+ } catch (SQLException e) {
+ }
+ }
+
+ @Test
+ public void writeToSlaveAfterFailover() throws Throwable {
+ connection = getNewConnection("&retriesAllDown=1", true);
+ //if super user can write on slave
+ Assume.assumeTrue(!hasSuperPrivilege(connection, "writeToSlaveAfterFailover"));
+ Statement st = connection.createStatement();
+ st.execute("drop table if exists multinode2");
+ st.execute("create table multinode2 (id int not null primary key , amount int not null) ENGINE = InnoDB");
+ st.execute("insert into multinode2 (id, amount) VALUE (1 , 100)");
+
+ int masterServerId = getServerId(connection);
+
+ stopProxy(masterServerId);
+ try {
+ st.execute("insert into multinode2 (id, amount) VALUE (2 , 100)");
+ Assert.fail();
+ } catch (SQLException e) {
+ }
+ }
+
+ @Test
+ public void checkBackOnMasterOnSlaveFail() throws Throwable {
+ connection = getNewConnection("&retriesAllDown=1&secondsBeforeRetryMaster=1&failOnReadOnly=true", true);
+ Statement st = connection.createStatement();
+ int masterServerId = getServerId(connection);
+ stopProxy(masterServerId);
+
+ try {
+ st.execute("SELECT 1");
+ Assert.assertTrue(connection.isReadOnly());
+ } catch (SQLException e) {
+ Assert.fail();
+ }
+
+ long stoppedTime = System.currentTimeMillis();
+ restartProxy(masterServerId);
+ boolean loop = true;
+ while (loop) {
+ Thread.sleep(250);
+ try {
+ if (!connection.isReadOnly()) {
+ log.fine("reconnection to master with failover loop after : " + (System.currentTimeMillis() - stoppedTime) + "ms");
+ loop = false;
+ }
+ } catch (SQLException e) {
+ }
+ if (System.currentTimeMillis() - stoppedTime > 15 * 1000) Assert.fail();
+ }
+ }
+
+ @Test()
+ public void checkNoSwitchConnectionDuringTransaction() throws Throwable {
+ connection = getNewConnection("&retriesAllDown=1&autoReconnect=true", false);
+ Statement st = connection.createStatement();
+
+ st.execute("drop table if exists multinodeTransaction2");
+ st.execute("create table multinodeTransaction2 (id int not null primary key , amount int not null) ENGINE = InnoDB");
+ connection.setAutoCommit(false);
+ st.execute("insert into multinodeTransaction2 (id, amount) VALUE (1 , 100)");
+
+ try {
+ //in transaction, so must trow an error
+ connection.setReadOnly(true);
+ Assert.fail();
+ } catch (SQLException e) {
+ }
+ }
+
+ @Test
+ public void failoverMasterWithAutoConnectAndTransaction() throws Throwable {
+ connection = getNewConnection("&autoReconnect=true&retriesAllDown=1", true);
+ Statement st = connection.createStatement();
+
+ int masterServerId = getServerId(connection);
+ st.execute("drop table if exists multinodeTransaction");
+ st.execute("create table multinodeTransaction (id int not null primary key , amount int not null) ENGINE = InnoDB");
+ connection.setAutoCommit(false);
+ st.execute("insert into multinodeTransaction (id, amount) VALUE (1 , 100)");
+ stopProxy(masterServerId);
+ Assert.assertTrue(inTransaction(connection));
+ try {
+ // will to execute the query. if there is a connection error, try a ping, if ok, good, query relaunched. If not good, transaction is considered be lost
+ st.execute("insert into multinodeTransaction (id, amount) VALUE (2 , 10)");
+ Assert.fail();
+ } catch (SQLException e) {
+ log.finest("normal error : " + e.getMessage());
+ }
+ restartProxy(masterServerId);
+ try {
+ st = connection.createStatement();
+ st.execute("insert into multinodeTransaction (id, amount) VALUE (2 , 10)");
+ } catch (SQLException e) {
+ e.printStackTrace();
+ Assert.fail();
+ }
+ }
+
+ @Test
+ public void testFailMaster() throws Throwable {
+ connection = getNewConnection("&retriesAllDown=1&autoReconnect=true&failOnReadOnly=false", true);
+ Statement stmt = connection.createStatement();
+ int masterServerId = getServerId(connection);
+ stopProxy(masterServerId);
+ long stopTime = System.currentTimeMillis();
+ try {
+ stmt.execute("SELECT 1");
+ Assert.fail();
+ } catch (SQLException e) {
+ //normal error
+ }
+ Assert.assertTrue(!connection.isReadOnly());
+ Assert.assertTrue(System.currentTimeMillis() - stopTime < 20 * 1000);
+ }
+
+ @Test
+ public void testAutoReconnectMasterFailSlave() throws Throwable {
+ connection = getNewConnection("&retriesAllDown=1&autoReconnect=true&failOnReadOnly=true", true);
+ Statement stmt = connection.createStatement();
+ int masterServerId = getServerId(connection);
+ stopProxy(masterServerId);
+ try {
+ stmt.execute("SELECT 1");
+ } catch (SQLException e) {
+ Assert.fail();
+ }
+ Assert.assertTrue(connection.isReadOnly());
+ }
+
+ class MutableInt {
+ int value = 1; // note that we start at 1 since we're counting
+
+ public void increment() {
+ ++value;
+ }
+
+ public int get() {
+ return value;
+ }
+ }
+
+
+
+ /**
+ * CONJ-79
+ *
+ * @throws SQLException
+ */
+ @Test
+ public void socketTimeoutTest() throws SQLException {
+ int exceptionCount = 0;
+ // set a short connection timeout
+ connection = getNewConnection("&socketTimeout=4000", false);
+
+ PreparedStatement ps = connection.prepareStatement("SELECT 1");
+ ResultSet rs = ps.executeQuery();
+ rs.next();
+
+ // wait for the connection to time out
+ ps = connection.prepareStatement("SELECT sleep(5)");
+
+ // a timeout should occur here
+ try {
+ rs = ps.executeQuery();
+ Assert.fail();
+ } catch (SQLException e) {
+ // check that it's a timeout that occurs
+ Assert.assertTrue(e.getMessage().contains("timed out"));
+ }
+ try {
+ ps = connection.prepareStatement("SELECT 2");
+ ps.execute();
+ } catch (Exception e){
+ Assert.fail();
+ }
+
+ try {
+ rs = ps.executeQuery();
+ } catch (SQLException e) {
+ Assert.fail();
+ }
+
+ // the connection should not be closed
+ assertTrue(!connection.isClosed());
+ }
+// @Test
+// public void testFailoverMaster() throws Throwable {
+// connection = getNewConnection("&validConnectionTimeout=1&secondsBeforeRetryMaster=1&autoReconnect=true", false);
+// Statement stmt = connection.createStatement();
+// ResultSet rs;
+// stmt.execute("ALTER SYSTEM SIMULATE 100 PERCENT DISK FAILURE FOR INTERVAL 60 SECOND");
+// int initMaster = getServerId(connection);
+// log.fine("master is " + initMaster);
+// FailoverProxy proxy = getProtocolFromConnection(connection).getProxy();
+// log.fine("lock : "+proxy.lock.getReadHoldCount() + " " + proxy.lock.getWriteHoldCount());
+// long failInitTime = 0;
+// long launchInit = System.currentTimeMillis();
+// while (System.currentTimeMillis() - launchInit < 120000) {
+// try {
+// Thread.sleep(2000);
+// if (stmt.isClosed()) {
+// stmt = connection.createStatement();
+// }
+// rs = stmt.executeQuery("show global variables like 'innodb_read_only'");
+//
+// rs.next();
+//
+// if (failInitTime != 0 && "OFF".equals(rs.getString(2))) {
+// long endFailover = System.currentTimeMillis() - launchInit;
+// Assert.assertTrue(initMaster != getServerId(connection));
+// log.fine("End failover after " + endFailover + " new master is " + getServerId(connection));
+// //wait 15s for others tests may not be disturb by replica restart
+// Thread.sleep(15000);
+// return;
+// }
+// } catch (SQLException e) {
+// log.fine("error : " + e.getMessage());
+// if (failInitTime == 0) {
+// failInitTime = System.currentTimeMillis();
+// log.fine("start failover master was " + getServerId(connection));
+// }
+// }
+// }
+// Assert.fail();
+// }
+//
+// @Test
+// public void testFailoverMasterPing() throws Throwable {
+// connection = getNewConnection("&failOnReadOnly=false&validConnectionTimeout=2&autoReconnect=true", false);
+// Statement stmt = connection.createStatement();
+// stmt.execute("ALTER SYSTEM SIMULATE 100 PERCENT DISK FAILURE FOR INTERVAL 35 SECOND");
+// int initMaster = getServerId(connection);
+// log.fine("master is " + initMaster);
+// long launchInit = System.currentTimeMillis();
+// while (System.currentTimeMillis() - launchInit < 180000) {
+// Thread.sleep(250);
+// int currentMaster = getServerId(connection);
+//
+// //master must change
+// if (currentMaster != initMaster) {
+// long endFailover = System.currentTimeMillis() - launchInit;
+// log.fine("Master automatically change after failover after " + endFailover + "ms. new master is " + currentMaster);
+//
+// //wait 15s for others tests may not be disturb by replica restart
+// Thread.sleep(15000);
+// return;
+// } else {
+// log.fine("++++++++ping in testFailoverMasterPing ");
+// }
+// }
+// Assert.fail();
+// }
+//
+//
+// @Test
+// public void FailoverWithAutoMasterSet () throws Throwable {
+// connection = getNewConnection("&validConnectionTimeout=2&secondsBeforeRetryMaster=1", false);
+// int initMaster = getServerId(connection);
+// connection.setReadOnly(true);
+// int connection1SlaveId = getServerId(connection);
+// int connection2SlaveId = connection1SlaveId;
+// FailoverProxy proxy = getProtocolFromConnection(connection).getProxy();
+// Connection connection2 = null;
+// try {
+// while (connection2SlaveId == connection1SlaveId) {
+// connection2 = getNewConnection("&validConnectionTimeout=5&secondsBeforeRetryMaster=1", false);
+// connection2.setReadOnly(true);
+// connection2SlaveId = getServerId(connection2);
+// if (connection2SlaveId == connection1SlaveId) connection2.close();
+// }
+//
+// log.fine("master is " + initMaster);
+// log.fine("connection 1 slave is " + connection1SlaveId);
+// log.fine("connection 2 slave is " + connection2SlaveId);
+//
+// connection.setReadOnly(false);
+// connection2.setReadOnly(false);
+//
+// // now whe know the master and every slave.
+// // one of those replica will become a master
+// // the goal of this test is to check that he become silently master.
+// Statement stmt = connection.createStatement();
+// stmt.execute("ALTER SYSTEM SIMULATE 100 PERCENT DISK FAILURE FOR INTERVAL 35 SECOND");
+//
+// boolean validationConnection1 = false;
+// boolean validationConnection2 = false;
+//
+// //this permit to launched a failover is less than 15s after and every second, ping will test that master is ok.
+// long launchInit = System.currentTimeMillis();
+// while (System.currentTimeMillis() - launchInit < 180000) {
+// Thread.sleep(250);
+//
+// int currentMaster1 = getServerId(connection);
+// int currentMaster2 = getServerId(connection2);
+//
+// //master must change
+// if (currentMaster1 != initMaster || currentMaster2 != initMaster) {
+// //connection master 1 has changed
+// if (!validationConnection1 && currentMaster1 != initMaster) {
+// //master has changed
+// if (currentMaster1 == connection1SlaveId) {
+// //master was old replica -> check that old replica has changed
+// try {
+// connection.setReadOnly(true);
+// int currentSlave = getServerId(connection);
+//
+// if (currentSlave != currentMaster1) {
+// Thread.sleep(15000); //give time to detect salve too
+// currentSlave = getServerId(connection);
+// }
+//
+// Assert.assertTrue(currentSlave != currentMaster1);
+// validationConnection1 = true;
+// } catch (SQLException e) {}
+// } else validationConnection1 = true;
+// }
+//
+// //connection master 1 has changed
+// if (!validationConnection2 && currentMaster2 != initMaster) {
+// //master has changed
+// if (currentMaster2 == connection2SlaveId) {
+// //master was old replica -> check that old replica has changed
+// try {
+// connection2.setReadOnly(true);
+// connection2.createStatement().execute("SELECT 1");
+// int currentSlave = getServerId(connection2);
+//
+// if (currentSlave != currentMaster2) {
+// Thread.sleep(15000); //give time to detect salve too
+// currentSlave = getServerId(connection2);
+// }
+//
+// Assert.assertTrue(currentSlave != currentMaster2);
+// validationConnection2 = true;
+// } catch (SQLException e) {}
+// } else validationConnection2 = true;
+// }
+// if (validationConnection1 && validationConnection2) {
+// //wait 15s for others tests may not be disturb by replica restart
+// log.finest("validated");
+// Thread.sleep(15000);
+// return;
+// }
+// }
+// }
+// Assert.assertTrue(validationConnection1 && validationConnection2);
+// } finally {
+// connection2.close();
+// }
+// }
+
+}
diff --git a/src/test/java/org/mariadb/jdbc/failover/BaseMultiHostTest.java b/src/test/java/org/mariadb/jdbc/failover/BaseMultiHostTest.java
new file mode 100644
index 000000000..53e4155a1
--- /dev/null
+++ b/src/test/java/org/mariadb/jdbc/failover/BaseMultiHostTest.java
@@ -0,0 +1,240 @@
+package org.mariadb.jdbc.failover;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.rules.TestRule;
+import org.junit.rules.TestWatcher;
+import org.junit.runner.Description;
+import org.mariadb.jdbc.HostAddress;
+import org.mariadb.jdbc.JDBCUrl;
+import org.mariadb.jdbc.MySQLConnection;
+import org.mariadb.jdbc.internal.common.UrlHAMode;
+import org.mariadb.jdbc.internal.mysql.*;
+
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.sql.*;
+import java.util.HashMap;
+import java.util.List;
+import java.util.logging.*;
+
+/**
+ * Base util class.
+ * For testing
+ * example mvn test -DdbUrl=jdbc:mysql://localhost:3306,localhost:3307/test?user=root -DlogLevel=FINEST
+ *
+ * specific parameters :
+ * defaultMultiHostUrl :
+ *
+ *
+ */
+@Ignore
+public class BaseMultiHostTest {
+ protected static Logger log = Logger.getLogger("org.mariadb.jdbc");
+
+ protected static String initialGaleraUrl;
+ protected static String initialAuroraUrl;
+ protected static String initialReplicationUrl;
+ protected static String initialUrl;
+
+
+ protected static String proxyGaleraUrl;
+ protected static String proxyAuroraUrl;
+ protected static String proxyReplicationUrl;
+ protected static String proxyUrl;
+
+ protected static String username;
+ private static String hostname;
+ public enum TestType {
+ AURORA, REPLICATION, GALERA, NONE
+ }
+ public TestType currentType;
+
+ //hosts
+ private static HashMap proxySet = new HashMap<>();
+
+ @Rule
+ public TestRule watcher = new TestWatcher() {
+ protected void starting(Description description) {
+ log.fine("Starting test: " + description.getMethodName());
+ }
+
+ protected void finished(Description description) {
+ log.fine("finished test: " + description.getMethodName());
+ }
+ };
+
+ @BeforeClass
+ public static void beforeClass() throws SQLException, IOException {
+
+ initialUrl = System.getProperty("dbUrl");
+ initialGaleraUrl = System.getProperty("defaultGaleraUrl");
+ initialReplicationUrl = System.getProperty("defaultReplicationUrl");
+ initialAuroraUrl = System.getProperty("defaultAuroraUrl");
+
+ if (initialUrl != null) proxyUrl=createProxies(initialUrl, TestType.NONE);
+ if (initialReplicationUrl != null) proxyReplicationUrl=createProxies(initialReplicationUrl, TestType.REPLICATION);
+ if (initialGaleraUrl != null) proxyGaleraUrl=createProxies(initialGaleraUrl, TestType.GALERA);
+ if (initialAuroraUrl != null) proxyAuroraUrl=createProxies(initialAuroraUrl, TestType.AURORA);
+ }
+
+ public static boolean requireMinimumVersion(Connection connection, int major, int minor) throws SQLException {
+ DatabaseMetaData md = connection.getMetaData();
+ int dbMajor = md.getDatabaseMajorVersion();
+ int dbMinor = md.getDatabaseMinorVersion();
+ return (dbMajor > major ||
+ (dbMajor == major && dbMinor >= minor));
+ }
+
+ private static String createProxies(String tmpUrl, TestType proxyType) {
+ JDBCUrl tmpJdbcUrl = JDBCUrl.parse(tmpUrl);
+ TcpProxy[] tcpProxies = new TcpProxy[tmpJdbcUrl.getHostAddresses().size()];
+ username = tmpJdbcUrl.getUsername();
+ hostname = tmpJdbcUrl.getHostAddresses().get(0).host;
+ String sockethosts = "";
+ HostAddress hostAddress;
+ for (int i=0;i localhost:" + tcpProxies[i].getLocalPort());
+ sockethosts+=",address=(host=localhost)(port="+tcpProxies[i].getLocalPort()+")"+((hostAddress.type != null)?"(type="+hostAddress.type+")":"");
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ proxySet.put(proxyType, tcpProxies);
+ if (tmpJdbcUrl.getHaMode().equals(UrlHAMode.NONE)) {
+ return "jdbc:mysql://"+sockethosts.substring(1)+"/"+tmpUrl.split("/")[3];
+ } else {
+ return "jdbc:mysql:"+tmpJdbcUrl.getHaMode().toString().toLowerCase()+"://"+sockethosts.substring(1)+"/"+tmpUrl.split("/")[3];
+ }
+
+ }
+
+
+ protected Connection getNewConnection() throws SQLException {
+ return getNewConnection(null, false);
+ }
+
+ protected Connection getNewConnection(boolean proxy) throws SQLException {
+ return getNewConnection(null, proxy);
+ }
+
+ protected Connection getNewConnection(String additionnalConnectionData, boolean proxy) throws SQLException {
+ return getNewConnection(additionnalConnectionData, proxy, false);
+ }
+
+ protected Connection getNewConnection(String additionnalConnectionData, boolean proxy, boolean forceNewProxy) throws SQLException {
+ if (proxy) {
+ String tmpProxyUrl = proxyUrl;
+ if (forceNewProxy) {
+ tmpProxyUrl = createProxies(initialUrl, currentType);
+ }
+ if (additionnalConnectionData == null) {
+ return DriverManager.getConnection(tmpProxyUrl);
+ } else {
+ return DriverManager.getConnection(tmpProxyUrl + additionnalConnectionData);
+ }
+ } else {
+ if (additionnalConnectionData == null) {
+ return DriverManager.getConnection(initialUrl);
+ } else {
+ return DriverManager.getConnection(initialUrl + additionnalConnectionData);
+ }
+ }
+ }
+
+ @AfterClass
+ public static void afterClass() throws SQLException {
+ if (proxySet !=null) {
+ for (TcpProxy[] tcpProxies : proxySet.values()) {
+ for (TcpProxy tcpProxy : tcpProxies) {
+ try {
+ tcpProxy.stop();
+ } catch (Exception e) {}
+ }
+ }
+ }
+ }
+
+ public void stopProxy(int hostNumber, long millissecond) {
+ log.fine("stopping host "+hostNumber);
+ proxySet.get(currentType)[hostNumber - 1].restart(millissecond);
+ }
+
+ public void stopProxy(int hostNumber) {
+ log.fine("stopping host "+hostNumber);
+ proxySet.get(currentType)[hostNumber - 1].stop();
+ }
+
+ public void restartProxy(int hostNumber) {
+ log.fine("restart host "+hostNumber);
+ if (hostNumber != -1) proxySet.get(currentType)[hostNumber - 1].restart();
+ }
+ public void assureProxy() {
+ for (TcpProxy[] tcpProxies : proxySet.values()) {
+ for (TcpProxy tcpProxy : tcpProxies) {
+ tcpProxy.assureProxyOk();
+ }
+ }
+ }
+
+ public void assureBlackList(Connection connection) {
+ try {
+ Protocol protocol = getProtocolFromConnection(connection);
+ protocol.getProxy().getListener().getBlacklist().clear();
+ } catch (Throwable e) { }
+ }
+
+
+ //does the user have super privileges or not?
+ public boolean hasSuperPrivilege(Connection connection, String testName) throws SQLException{
+ boolean superPrivilege = false;
+ Statement st = connection.createStatement();
+
+ // first test for specific user and host combination
+ ResultSet rs = st.executeQuery("SELECT Super_Priv FROM mysql.user WHERE user = '" + username + "' AND host = '" + hostname + "'");
+ if (rs.next()) {
+ superPrivilege = (rs.getString(1).equals("Y") ? true : false);
+ } else
+ {
+ // then check for user on whatever (%) host
+ rs = st.executeQuery("SELECT Super_Priv FROM mysql.user WHERE user = '" + username + "' AND host = '%'");
+ if (rs.next())
+ superPrivilege = (rs.getString(1).equals("Y") ? true : false);
+ }
+
+ rs.close();
+
+ if (superPrivilege)
+ log.fine("test '" + testName + "' skipped because user '" + username + "' has SUPER privileges");
+
+ return superPrivilege;
+ }
+
+ protected Protocol getProtocolFromConnection(Connection conn) throws Throwable {
+
+ Method getProtocol = MySQLConnection.class.getDeclaredMethod("getProtocol", new Class[0]);
+ getProtocol.setAccessible(true);
+ return (Protocol) getProtocol.invoke(conn);
+ }
+
+ public int getServerId(Connection connection) throws Throwable {
+ Protocol protocol = getProtocolFromConnection(connection);
+ HostAddress hostAddress = protocol.getHostAddress();
+ List hostAddressList = protocol.getJdbcUrl().getHostAddresses();
+ return hostAddressList.indexOf(hostAddress) + 1;
+ }
+
+ public boolean inTransaction(Connection connection) throws Throwable {
+ Protocol protocol = getProtocolFromConnection(connection);
+ return protocol.inTransaction();
+ }
+ boolean isMariadbServer(Connection connection) throws SQLException {
+ DatabaseMetaData md = connection.getMetaData();
+ return md.getDatabaseProductVersion().indexOf("MariaDB") != -1;
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/org/mariadb/jdbc/failover/CancelTest.java b/src/test/java/org/mariadb/jdbc/failover/CancelTest.java
new file mode 100644
index 000000000..d5ade0af8
--- /dev/null
+++ b/src/test/java/org/mariadb/jdbc/failover/CancelTest.java
@@ -0,0 +1,60 @@
+package org.mariadb.jdbc.failover;
+
+import org.junit.*;
+import org.mariadb.jdbc.BaseTest;
+
+import java.sql.*;
+import java.util.Properties;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import static org.junit.Assert.assertEquals;
+
+public class CancelTest extends BaseMultiHostTest {
+ private Connection connection;
+
+ @Before
+ public void init() throws SQLException {
+ currentType = BaseMultiHostTest.TestType.GALERA;
+ initialUrl = initialGaleraUrl;
+ proxyUrl = proxyGaleraUrl;
+ Assume.assumeTrue(initialGaleraUrl != null);
+ connection = null;
+ }
+
+ @After
+ public void after() throws SQLException {
+ assureProxy();
+ assureBlackList(connection);
+ if (connection != null) connection.close();
+ }
+
+
+
+ @Test (expected = SQLTimeoutException.class)
+ public void timeoutSleep() throws Exception{
+ connection = getNewConnection(false);
+ PreparedStatement stmt = connection.prepareStatement("select sleep(100)");
+ stmt.setQueryTimeout(1);
+ stmt.execute();
+ }
+
+ @Test
+ public void NoTimeoutSleep() throws Exception{
+ connection = getNewConnection(false);
+ Statement stmt = connection.createStatement();
+ stmt.setQueryTimeout(1);
+ stmt.execute("select sleep(0.5)");
+
+ }
+
+ @Test
+ public void CancelIdleStatement() throws Exception {
+ connection = getNewConnection(false);
+ Statement stmt = connection.createStatement();
+ stmt.cancel();
+ ResultSet rs = stmt.executeQuery("select 1");
+ rs.next();
+ assertEquals(rs.getInt(1),1);
+ }
+}
diff --git a/src/test/java/org/mariadb/jdbc/failover/GaleraFailoverTest.java b/src/test/java/org/mariadb/jdbc/failover/GaleraFailoverTest.java
new file mode 100644
index 000000000..acd62adb0
--- /dev/null
+++ b/src/test/java/org/mariadb/jdbc/failover/GaleraFailoverTest.java
@@ -0,0 +1,259 @@
+package org.mariadb.jdbc.failover;
+
+import org.junit.*;
+import org.mariadb.jdbc.HostAddress;
+import org.mariadb.jdbc.JDBCUrl;
+import org.mariadb.jdbc.internal.mysql.Protocol;
+
+import java.sql.*;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * test for galera
+ * The node must be configure with specific names :
+ * node 1 : wsrep_node_name = "galera1"
+ * ...
+ * node x : wsrep_node_name = "galerax"
+ * exemple mvn test -DdbUrl=jdbc:mysql://localhost:3306,localhost:3307/test?user=root
+ */
+public class GaleraFailoverTest extends BaseMultiHostTest {
+ private Connection connection;
+
+ @Before
+ public void init() throws SQLException {
+ currentType = TestType.GALERA;
+ initialUrl = initialGaleraUrl;
+ proxyUrl = proxyGaleraUrl;
+ Assume.assumeTrue(initialGaleraUrl != null);
+ connection = null;
+ }
+
+ @After
+ public void after() throws SQLException {
+ assureProxy();
+ assureBlackList(connection);
+ if (connection != null) connection.close();
+ }
+
+ @Test
+ public void sequenceConnection() throws Throwable {
+ Assume.assumeTrue(!initialGaleraUrl.contains("failover"));
+ JDBCUrl jdbcUrl = JDBCUrl.parse(initialGaleraUrl);
+ for (int i = 0; i < jdbcUrl.getHostAddresses().size(); i++) {
+ connection = getNewConnection(true);
+ int serverNb = getServerId(connection);
+ Assert.assertTrue(serverNb == i + 1);
+ connection.close();
+ stopProxy(serverNb);
+ }
+ }
+
+ @Test
+ public void randomConnection() throws Throwable {
+ Assume.assumeTrue(initialGaleraUrl.contains("failover"));
+ Map connectionMap = new HashMap<>();
+ for (int i = 0; i < 20; i++) {
+ connection = getNewConnection(false);
+ int serverId = getServerId(connection);
+ log.fine("master server found " + serverId);
+ MutableInt count = connectionMap.get(String.valueOf(serverId));
+ if (count == null) {
+ connectionMap.put(String.valueOf(serverId), new MutableInt());
+ } else {
+ count.increment();
+ }
+ connection.close();
+ }
+
+ Assert.assertTrue(connectionMap.size() >= 2);
+ for (String key : connectionMap.keySet()) {
+ Integer connectionCount = connectionMap.get(key).get();
+ log.fine(" ++++ Server " + key + " : " + connectionCount + " connections ");
+ Assert.assertTrue(connectionCount > 1);
+ }
+ log.fine("randomConnection OK");
+ }
+
+ @Test
+ public void checkStaticBlacklist() throws Throwable {
+ try {
+ connection = getNewConnection("&loadBalanceBlacklistTimeout=500", true);
+ Statement st = connection.createStatement();
+
+ int firstServerId = getServerId(connection);
+ stopProxy(firstServerId);
+
+ try {
+ st.execute("SELECT 1");
+ Assert.fail();
+ } catch (SQLException e) {
+ //normal exception that permit to blacklist the failing connection.
+ }
+
+ //check blacklist size
+ try {
+ Protocol protocol = getProtocolFromConnection(connection);
+ log.fine("backlist size : " + protocol.getProxy().getListener().getBlacklist().size());
+ Assert.assertTrue(protocol.getProxy().getListener().getBlacklist().size() == 1);
+
+ //replace proxified HostAddress by normal one
+ JDBCUrl jdbcUrl = JDBCUrl.parse(initialUrl);
+ protocol.getProxy().getListener().getBlacklist().put(jdbcUrl.getHostAddresses().get(firstServerId - 1), System.currentTimeMillis());
+ } catch (Throwable e) {
+ e.printStackTrace();
+ Assert.fail();
+ }
+
+ //add first Host to blacklist
+ Protocol protocol = getProtocolFromConnection(connection);
+ protocol.getProxy().getListener().getBlacklist().size();
+
+ ExecutorService exec = Executors.newFixedThreadPool(2);
+
+ //check blacklist shared
+ exec.execute(new CheckBlacklist(firstServerId, protocol.getProxy().getListener().getBlacklist()));
+ exec.execute(new CheckBlacklist(firstServerId, protocol.getProxy().getListener().getBlacklist()));
+ //wait for thread endings
+
+ exec.shutdown();
+ try {
+ exec.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
+ } catch (InterruptedException e) {
+ }
+ } catch (Throwable e) {
+ e.printStackTrace();
+ Assert.fail();
+ }
+ }
+
+ protected class CheckBlacklist implements Runnable {
+ int firstServerId;
+ Map blacklist;
+
+ public CheckBlacklist(int firstServerId, Map blacklist) {
+ this.firstServerId = firstServerId;
+ this.blacklist = blacklist;
+ }
+
+ public void run() {
+ Connection connection2 = null;
+ try {
+ connection2 = getNewConnection();
+ int otherServerId = getServerId(connection2);
+ log.fine("connected to server " + otherServerId);
+ Assert.assertTrue(otherServerId != firstServerId);
+ Protocol protocol = getProtocolFromConnection(connection2);
+ Assert.assertTrue(blacklist.keySet().toArray()[0].equals(protocol.getProxy().getListener().getBlacklist().keySet().toArray()[0]));
+
+ } catch (Throwable e) {
+ e.printStackTrace();
+ Assert.fail();
+ } finally {
+ if (connection2 != null) {
+ try {
+ connection2.close();
+ } catch (SQLException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+ }
+
+ @Test
+ public void testMultiHostWriteOnMaster() throws Throwable {
+ Assume.assumeTrue(initialGaleraUrl != null);
+ Connection connection = null;
+ log.fine("testMultiHostWriteOnMaster begin");
+ try {
+ connection = getNewConnection();
+ Statement stmt = connection.createStatement();
+ stmt.execute("drop table if exists multinode");
+ stmt.execute("create table multinode (id int not null primary key auto_increment, test VARCHAR(10))");
+ log.fine("testMultiHostWriteOnMaster OK");
+ } finally {
+ assureProxy();
+ assureBlackList(connection);
+ log.fine("testMultiHostWriteOnMaster done");
+ if (connection != null) connection.close();
+ }
+ }
+
+ @Test
+ public void pingReconnectAfterRestart() throws Throwable {
+ connection = getNewConnection("&retriesAllDown=1&secondsBeforeRetryMaster=1&failOnReadOnly=false&queriesBeforeRetryMaster=50000", true);
+ Statement st = connection.createStatement();
+ int masterServerId = getServerId(connection);
+ stopProxy(masterServerId);
+ long stoppedTime = System.currentTimeMillis();
+
+ try {
+ st.execute("SELECT 1");
+ } catch (SQLException e) {
+ }
+ restartProxy(masterServerId);
+ long restartTime = System.currentTimeMillis();
+ boolean loop = true;
+ while (loop) {
+ if (!connection.isClosed()) {
+ log.fine("reconnection with failover loop after : " + (System.currentTimeMillis() - stoppedTime) + "ms");
+ loop = false;
+ }
+ if (System.currentTimeMillis() - restartTime > 15 * 1000) Assert.fail();
+ Thread.sleep(250);
+ }
+ }
+
+
+
+ @Test
+ public void socketTimeoutTest() throws SQLException {
+
+ // set a short connection timeout
+ connection = getNewConnection("&socketTimeout=15000&retriesAllDown=1", false);
+
+ PreparedStatement ps = connection.prepareStatement("SELECT 1");
+ ResultSet rs = ps.executeQuery();
+ rs.next();
+
+ // wait for the connection to time out
+ ps = connection.prepareStatement("SELECT sleep(16)");
+
+ // a timeout should occur here
+ try {
+ ps.executeQuery();
+ Assert.fail();
+ } catch (SQLException e) {
+ Assert.assertTrue(e.getMessage().contains("timed out"));
+ }
+ try {
+ ps = connection.prepareStatement("SELECT 2");
+ ps.execute();
+ } catch (Exception e){
+ Assert.fail();
+ }
+
+ // the connection should not be closed
+ assertTrue(!connection.isClosed());
+ }
+
+ class MutableInt {
+ int value = 1; // note that we start at 1 since we're counting
+
+ public void increment() {
+ ++value;
+ }
+
+ public int get() {
+ return value;
+ }
+ }
+
+}
diff --git a/src/test/java/org/mariadb/jdbc/failover/MonoServerFailoverTest.java b/src/test/java/org/mariadb/jdbc/failover/MonoServerFailoverTest.java
new file mode 100644
index 000000000..9854911e2
--- /dev/null
+++ b/src/test/java/org/mariadb/jdbc/failover/MonoServerFailoverTest.java
@@ -0,0 +1,190 @@
+package org.mariadb.jdbc.failover;
+
+import org.junit.*;
+import org.mariadb.jdbc.JDBCUrl;
+import org.mariadb.jdbc.internal.mysql.Protocol;
+
+import java.sql.*;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.Assert.assertFalse;
+
+public class MonoServerFailoverTest extends BaseMultiHostTest {
+ private Connection connection;
+
+ @Before
+ public void init() throws SQLException {
+ Assume.assumeTrue(initialUrl != null);
+ currentType = TestType.NONE;
+ }
+
+ @After
+ public void after() throws SQLException {
+ assureProxy();
+ assureBlackList(connection);
+ if (connection != null) connection.close();
+ }
+
+ @Test
+ public void checkClosedConnectionAfterFailover() throws Throwable {
+ connection = getNewConnection("&autoReconnect=true&retriesAllDown=1", true);
+
+ Statement st = connection.createStatement();
+ int masterServerId = getServerId(connection);
+ stopProxy(masterServerId);
+ try {
+ st.execute("SELECT 1");
+ Assert.fail();
+ } catch (SQLException e) {
+ }
+ Assert.assertTrue(st.isClosed());
+ restartProxy(masterServerId);
+ try {
+ st = connection.createStatement();
+ st.execute("SELECT 1");
+ } catch (SQLException e) {
+ Assert.fail();
+ }
+
+ }
+
+ @Test
+ public void checkErrorAfterDeconnection() throws Throwable {
+ connection = getNewConnection("&retriesAllDown=1", true);
+
+ Statement st = connection.createStatement();
+ int masterServerId = getServerId(connection);
+ stopProxy(masterServerId);
+ try {
+ st.execute("SELECT 1");
+ Assert.fail();
+ } catch (SQLException e) {
+ }
+
+ restartProxy(masterServerId);
+ try {
+ st.execute("SELECT 1");
+ Assert.fail();
+ } catch (SQLException e) {
+ //statement must be closed -> error
+ }
+ Assert.assertTrue(connection.isClosed());
+
+ }
+
+
+ @Test
+ public void checkAutoReconnectDeconnection() throws Throwable {
+ connection = getNewConnection("&autoReconnect=true&retriesAllDown=1", true);
+
+ Statement st = connection.createStatement();
+ int masterServerId = getServerId(connection);
+ stopProxy(masterServerId);
+ try {
+ st.execute("SELECT 1");
+ Assert.fail();
+ } catch (SQLException e) {
+ }
+
+ restartProxy(masterServerId);
+ try {
+ //with autoreconnect -> not closed
+ st = connection.createStatement();
+ st.execute("SELECT 1");
+ } catch (SQLException e) {
+ Assert.fail();
+ }
+ Assert.assertFalse(connection.isClosed());
+
+
+ }
+
+
+ /**
+ * CONJ-120 Fix Connection.isValid method
+ *
+ * @throws Exception
+ */
+ @Test
+ public void isValid_connectionThatIsKilledExternally() throws Throwable {
+ Connection killerConnection = null;
+ try {
+ connection = getNewConnection();
+ connection.setCatalog("mysql");
+ Protocol protocol = getProtocolFromConnection(connection);
+ killerConnection = getNewConnection();
+ Statement killerStatement = killerConnection.createStatement();
+ long threadId = protocol.getServerThreadId();
+ killerStatement.execute("KILL CONNECTION " + threadId);
+ killerConnection.close();
+ boolean isValid = connection.isValid(0);
+ assertFalse(isValid);
+ } finally {
+ killerConnection.close();
+ }
+ }
+
+ @Test
+ public void checkPrepareStatement() throws Throwable {
+ connection = getNewConnection("&autoReconnect=true&retriesAllDown=1", true);
+ Statement stmt = connection.createStatement();
+ stmt.execute("drop table if exists failt1");
+ stmt.execute("create table failt1 (id int not null primary key auto_increment, tt int)");
+
+
+ PreparedStatement preparedStatement = connection.prepareStatement("insert into failt1(id, tt) values (?,?)");
+
+ int masterServerId = getServerId(connection);
+ stopProxy(masterServerId);
+
+ preparedStatement.setInt(1, 1);
+ preparedStatement.setInt(2, 1);
+ preparedStatement.addBatch();
+ try {
+ preparedStatement.executeBatch();
+ Assert.fail();
+ } catch (SQLException e) {
+
+ }
+ restartProxy(masterServerId);
+ try {
+ preparedStatement.executeBatch();
+ } catch (SQLException e) {
+ Assert.fail();
+ }
+ }
+
+/*
+ @Test
+ public void failoverDuringStreamStatement() throws Throwable {
+ connection = getNewConnection("&autoReconnect=true", true);
+ Statement stmt = connection.createStatement();
+ stmt.execute("drop table if exists failt2");
+ stmt.execute("create table failt2 (tt int)");
+
+ PreparedStatement preparedStatement = connection.prepareStatement("insert into failt2(tt) values (?)");
+ for (int i=0; i<100;i++) {
+ preparedStatement.setInt(1, i);
+ preparedStatement.addBatch();
+ }
+ preparedStatement.executeBatch();
+ stmt = connection.createStatement(java.sql.ResultSet.TYPE_FORWARD_ONLY, java.sql.ResultSet.CONCUR_READ_ONLY);
+ stmt.setFetchSize(Integer.MIN_VALUE);
+ ResultSet rs = stmt.executeQuery("SELECT * FROM failt2");
+ int masterServerId = getServerId(connection);
+ stopProxy(masterServerId);
+
+ int nbRead = 0;
+ try {
+ while (rs.next()) {
+ nbRead++;
+ log.fine("nbRead = "+nbRead + " rs="+rs.getInt(1));
+ }
+ Assert.fail();
+ } catch (SQLException e) {
+ Assert.assertTrue(nbRead == 10);
+ }
+ }
+*/
+}
diff --git a/src/test/java/org/mariadb/jdbc/failover/OldFailoverTest.java b/src/test/java/org/mariadb/jdbc/failover/OldFailoverTest.java
new file mode 100644
index 000000000..2b1b03d6a
--- /dev/null
+++ b/src/test/java/org/mariadb/jdbc/failover/OldFailoverTest.java
@@ -0,0 +1,49 @@
+package org.mariadb.jdbc.failover;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.mariadb.jdbc.BaseTest;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.sql.Statement;
+
+import static org.junit.Assert.assertFalse;
+
+public class OldFailoverTest extends BaseTest {
+
+ /**
+ * check old connection way before multihost was handle
+ * @throws Exception
+ */
+ @Test
+ public void isOldConfigurationValid() throws Exception {
+ String falseUrl = "jdbc:mysql://localhost:1111," + hostname + ":" + port + "/" + database+"?user=" + username
+ + (password != null && !"".equals(password) ? "&password=" + password : "")
+ + (parameters != null ? parameters : "");
+
+ try {
+ //the first host doesn't exist, so with the random host selection, verifying that we connect to the good host
+ for (int i=0;i<10;i++) {
+ Connection tmpConnection = openNewConnection(falseUrl);
+ Statement tmpStatement = tmpConnection.createStatement();
+ tmpStatement.execute("SELECT 1");
+ }
+ } catch (Exception e) {
+ Assert.fail();
+ }
+ }
+
+ @Test
+ public void errorUrl() throws Exception {
+ String falseUrl = "jdbc:mysql://localhost:1111/test";
+
+ try {
+ Connection tmpConnection = openNewConnection(falseUrl);
+ Assert.fail();
+ } catch (Exception e) {
+ }
+ }
+
+
+}
diff --git a/src/test/java/org/mariadb/jdbc/failover/ReplicationFailoverTest.java b/src/test/java/org/mariadb/jdbc/failover/ReplicationFailoverTest.java
new file mode 100644
index 000000000..e5a2bcd22
--- /dev/null
+++ b/src/test/java/org/mariadb/jdbc/failover/ReplicationFailoverTest.java
@@ -0,0 +1,458 @@
+package org.mariadb.jdbc.failover;
+
+import org.junit.After;
+import static org.junit.Assert.*;
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.HashMap;
+import java.util.Map;
+
+public class ReplicationFailoverTest extends BaseMultiHostTest {
+ private Connection connection;
+ private long testBeginTime;
+
+ @Before
+ public void init() throws SQLException {
+ initialUrl = initialReplicationUrl;
+ proxyUrl = proxyReplicationUrl;
+ Assume.assumeTrue(initialReplicationUrl != null);
+ connection = null;
+ currentType = TestType.REPLICATION;
+ testBeginTime=System.currentTimeMillis();
+ }
+
+ @After
+ public void after() throws SQLException {
+ assureProxy();
+ assureBlackList(connection);
+ if (connection != null) connection.close();
+
+ log.fine("test time : "+(System.currentTimeMillis() - testBeginTime) + "ms");
+ }
+
+ @Test
+ public void testWriteOnMaster() throws SQLException {
+ connection = getNewConnection(false);
+ Statement stmt = connection.createStatement();
+ stmt.execute("drop table if exists multinode");
+ stmt.execute("create table multinode (id int not null primary key auto_increment, test VARCHAR(10))");
+ }
+
+ @Test
+ public void testErrorWriteOnSlave() throws SQLException {
+ connection = getNewConnection(false);
+ connection.setReadOnly(true);
+ Statement stmt = connection.createStatement();
+ assertTrue(connection.isReadOnly());
+ try {
+ if (!isMariadbServer(connection) || !requireMinimumVersion(connection, 10, 0)) {
+ //on version > 10 use SESSION READ-ONLY, before no control
+ Assume.assumeTrue(false);
+ }
+ stmt.execute("drop table if exists multinode4");
+ log.severe("ERROR - > must not be able to write on slave ");
+ fail();
+ } catch (SQLException e) {
+ }
+ }
+
+ @Test
+ public void randomConnection() throws Throwable {
+ Map connectionMap = new HashMap();
+ int masterId = -1;
+ for (int i = 0; i < 20; i++) {
+ connection = getNewConnection("&retriesAllDown=1", false);
+ int serverId = getServerId(connection);
+ log.fine("master server found " + serverId);
+ if (i > 0) assertTrue(masterId == serverId);
+ masterId = serverId;
+ connection.setReadOnly(true);
+ int replicaId = getServerId(connection);
+ log.fine("++++++++++++slave server found " + replicaId);
+ MutableInt count = connectionMap.get(String.valueOf(replicaId));
+ if (count == null) {
+ connectionMap.put(String.valueOf(replicaId), new MutableInt());
+ } else {
+ count.increment();
+ }
+ connection.close();
+ }
+
+ assertTrue(connectionMap.size() >= 2);
+ for (String key : connectionMap.keySet()) {
+ Integer connectionCount = connectionMap.get(key).get();
+ log.fine(" ++++ Server " + key + " : " + connectionCount + " connections ");
+ assertTrue(connectionCount > 1);
+ }
+ }
+
+ @Test
+ public void failoverSlaveToMaster() throws Throwable {
+ connection = getNewConnection("&retriesAllDown=1", true);
+ int masterServerId = getServerId(connection);
+ connection.setReadOnly(true);
+ int slaveServerId = getServerId(connection);
+ assertFalse(masterServerId == slaveServerId);
+ stopProxy(slaveServerId);
+ connection.createStatement().execute("SELECT 1");
+ int currentServerId = getServerId(connection);
+
+ log.fine("masterServerId = " + masterServerId + "/currentServerId = " + currentServerId);
+ assertTrue(masterServerId == currentServerId);
+
+ assertFalse(connection.isReadOnly());
+ }
+ @Test
+ public void failoverSlaveToMasterFail() throws Throwable {
+ connection = getNewConnection("&retriesAllDown=1&failOnReadOnly=true", true);
+ int masterServerId = getServerId(connection);
+ connection.setReadOnly(true);
+ int slaveServerId = getServerId(connection);
+ assertTrue(slaveServerId != masterServerId);
+ connection.setReadOnly(false);
+ stopProxy(masterServerId);
+
+ long failTime = System.currentTimeMillis();
+ connection.createStatement().execute("SELECT 1");
+ assertTrue(System.currentTimeMillis() - failTime < 100);
+ assertTrue(slaveServerId == getServerId(connection) );
+ }
+
+ @Test
+ public void pingReconnectAfterFailover() throws Throwable {
+ connection = getNewConnection("&retriesAllDown=1&secondsBeforeRetryMaster=5&failOnReadOnly=false&queriesBeforeRetryMaster=50000", true);
+ Statement st = connection.createStatement();
+ int masterServerId = getServerId(connection);
+ stopProxy(masterServerId);
+
+ try {
+ st.execute("SELECT 1");
+ } catch (SQLException e) {}
+
+ connection.setReadOnly(true);
+ st = connection.createStatement();
+ restartProxy(masterServerId);
+ try {
+ connection.setReadOnly(false);
+ fail();
+ } catch (SQLException e) {}
+
+ long stoppedTime = System.currentTimeMillis();
+
+ boolean loop = true;
+ while (loop) {
+ try {
+ Thread.sleep(250);
+ log.fine("time : " + (System.currentTimeMillis() - stoppedTime) + "ms");
+ int currentHost = getServerId(connection);
+ if (masterServerId == currentHost) {
+ log.fine("reconnection with failover loop after : " + (System.currentTimeMillis() - stoppedTime) + "ms");
+ assertTrue((System.currentTimeMillis() - stoppedTime) > 5 * 1000);
+ loop = false;
+ }
+ } catch (SQLException e) {
+ }
+ if (System.currentTimeMillis() - stoppedTime > 20 * 1000) fail();
+ }
+ }
+
+ @Test
+ public void failoverDuringMasterSetReadOnly() throws Throwable {
+ connection = getNewConnection("&retriesAllDown=1", true);
+ int masterServerId = getServerId(connection);
+ stopProxy(masterServerId);
+ connection.setReadOnly(true);
+ int slaveServerId = getServerId(connection);
+ assertFalse(slaveServerId == masterServerId);
+ assertTrue(connection.isReadOnly());
+ }
+
+ @Test
+ public void failoverDuringSlaveSetReadOnly() throws Throwable {
+ connection = getNewConnection(true);
+ connection.setReadOnly(true);
+ int slaveServerId = getServerId(connection);
+ stopProxy(slaveServerId, 2000);
+ connection.setReadOnly(false);
+ int masterServerId = getServerId(connection);
+ assertFalse(slaveServerId == masterServerId);
+ assertFalse(connection.isReadOnly());
+ }
+ @Test()
+ public void changeSlave() throws Throwable {
+ connection = getNewConnection("&retriesAllDown=1", true);
+ int masterServerId = getServerId(connection);
+ log.fine("master server_id = " + masterServerId);
+ connection.setReadOnly(true);
+ int firstSlaveId = getServerId(connection);
+ log.fine("slave1 server_id = " + firstSlaveId);
+
+ stopProxy(masterServerId);
+ stopProxy(firstSlaveId);
+
+ try {
+ connection.createStatement().executeQuery("SELECT CONNECTION_ID()");
+ } catch (SQLException e) {
+ fail();
+ }
+ }
+
+ @Test()
+ public void masterWithoutFailover() throws Throwable {
+ connection = getNewConnection("&retriesAllDown=1", true);
+ int masterServerId = getServerId(connection);
+ log.fine("master server_id = " + masterServerId);
+ connection.setReadOnly(true);
+ int firstSlaveId = getServerId(connection);
+ log.fine("slave1 server_id = " + firstSlaveId);
+ connection.setReadOnly(false);
+
+ stopProxy(masterServerId);
+ stopProxy(firstSlaveId);
+
+ try {
+ connection.createStatement().executeQuery("SELECT CONNECTION_ID()");
+ fail();
+ } catch (SQLException e) {
+ assertTrue(true);
+ }
+ }
+
+ @Test
+ public void failoverSlaveAndMasterWithAutoConnect() throws Throwable {
+ connection = getNewConnection("&autoReconnect=true&retriesAllDown=1", true);
+
+ //search actual server_id for master and slave
+ int masterServerId = getServerId(connection);
+ log.fine("master server_id = " + masterServerId);
+
+ connection.setReadOnly(true);
+
+ int firstSlaveId = getServerId(connection);
+ log.fine("slave1 server_id = " + firstSlaveId);
+
+ stopProxy(masterServerId);
+ stopProxy(firstSlaveId);
+
+ //must reconnect to the second slave without error
+ connection.createStatement().execute("SELECT 1");
+ int currentSlaveId = getServerId(connection);
+ log.fine("currentSlaveId server_id = " + currentSlaveId);
+ assertTrue(currentSlaveId != firstSlaveId);
+ assertTrue(currentSlaveId != masterServerId);
+ }
+
+ @Test
+ public void failoverMasterWithAutoConnect() throws Throwable {
+ connection = getNewConnection("&autoReconnect=true&retriesAllDown=1", true);
+ int masterServerId = getServerId(connection);
+
+ stopProxy(masterServerId, 250);
+ //with autoreconnect, the connection must reconnect automatically
+ int currentServerId = getServerId(connection);
+
+ assertTrue(currentServerId == masterServerId);
+ assertFalse(connection.isReadOnly());
+ }
+
+ @Test
+ public void checkReconnectionToMasterAfterQueryNumber() throws Throwable {
+ connection = getNewConnection("&retriesAllDown=1&secondsBeforeRetryMaster=3000&queriesBeforeRetryMaster=10&failOnReadOnly=true", true);
+ Statement st = connection.createStatement();
+ int masterServerId = getServerId(connection);
+ stopProxy(masterServerId);
+ try {
+ st.execute("SELECT 1");
+ } catch (SQLException e) {
+ fail();
+ }
+ assertTrue(connection.isReadOnly());
+
+ restartProxy(masterServerId);
+
+ //not in autoreconnect mode, so must wait for query more than queriesBeforeRetryMaster
+ for (int i = 1; i < 10; i++) {
+ try {
+ st.execute("SELECT 1");
+ log.fine("i=" + i);
+ assertTrue(connection.isReadOnly());
+ } catch (SQLException e) {
+ fail();
+ }
+ }
+ Thread.sleep(5000);
+ long startTime = System.currentTimeMillis();
+ connection.setReadOnly(false);
+ log.fine(" time = " + (System.currentTimeMillis() - startTime));
+ assertTrue(System.currentTimeMillis() - startTime < 4000);
+
+ }
+
+ @Test
+ public void reconnectMasterAfterFailover() throws Throwable {
+ connection = getNewConnection("&retriesAllDown=1", true);
+ //if super user can write on slave
+ Assume.assumeTrue(!hasSuperPrivilege(connection, "reconnectMasterAfterFailover"));
+ Statement st = connection.createStatement();
+ st.execute("drop table if exists multinode2");
+ st.execute("create table multinode2 (id int not null primary key , amount int not null) ENGINE = InnoDB");
+ st.execute("insert into multinode2 (id, amount) VALUE (1 , 100)");
+
+ int masterServerId = getServerId(connection);
+ long stopTime = System.currentTimeMillis();
+ stopProxy(masterServerId, 10000);
+ try {
+ st.execute("insert into multinode2 (id, amount) VALUE (2 , 100)");
+ assertTrue(System.currentTimeMillis() - stopTime > 10);
+ assertTrue(System.currentTimeMillis() - stopTime < 20);
+ } catch (SQLException e) {
+ }
+ }
+
+ @Test
+ public void writeToSlaveAfterFailover() throws Throwable {
+ connection = getNewConnection("&retriesAllDown=1",true);
+ //if super user can write on slave
+ Assume.assumeTrue(!hasSuperPrivilege(connection, "writeToSlaveAfterFailover"));
+ Statement st = connection.createStatement();
+ st.execute("drop table if exists multinode2");
+ st.execute("create table multinode2 (id int not null primary key , amount int not null) ENGINE = InnoDB");
+ st.execute("insert into multinode2 (id, amount) VALUE (1 , 100)");
+
+ int masterServerId = getServerId(connection);
+
+ stopProxy(masterServerId);
+ try {
+ st.execute("insert into multinode2 (id, amount) VALUE (2 , 100)");
+ fail();
+ } catch (SQLException e) {
+ }
+ }
+
+
+ @Test
+ public void checkBackOnMasterOnSlaveFail() throws Throwable {
+ connection = getNewConnection("&retriesAllDown=1&secondsBeforeRetryMaster=10&failOnReadOnly=true", true);
+ Statement st = connection.createStatement();
+ int masterServerId = getServerId(connection);
+ stopProxy(masterServerId);
+
+ try {
+ st.execute("SELECT 1");
+ assertTrue(connection.isReadOnly());
+ } catch (SQLException e) {
+ fail();
+ }
+
+ long stoppedTime = System.currentTimeMillis();
+ restartProxy(masterServerId);
+ boolean loop = true;
+ while (loop) {
+ Thread.sleep(250);
+ try {
+ if (!connection.isReadOnly()) {
+ log.fine("reconnection to master with failover loop after : " + (System.currentTimeMillis() - stoppedTime) + "ms");
+ assertTrue((System.currentTimeMillis() - stoppedTime) > 10 * 1000);
+ loop = false;
+ }
+ } catch (SQLException e) {
+ }
+ if (System.currentTimeMillis() - stoppedTime > 30 * 1000) fail();
+ }
+ }
+
+
+ @Test()
+ public void checkNoSwitchConnectionDuringTransaction() throws Throwable {
+ connection = getNewConnection("&retriesAllDown=1&autoReconnect=true", false);
+ Statement st = connection.createStatement();
+
+ st.execute("drop table if exists multinodeTransaction2");
+ st.execute("create table multinodeTransaction2 (id int not null primary key , amount int not null) ENGINE = InnoDB");
+ connection.setAutoCommit(false);
+ st.execute("insert into multinodeTransaction2 (id, amount) VALUE (1 , 100)");
+
+ try {
+ //in transaction, so must trow an error
+ connection.setReadOnly(true);
+ fail();
+ } catch (SQLException e) {
+ }
+ }
+
+ @Test
+ public void failoverMasterWithAutoConnectAndTransaction() throws Throwable {
+ connection = getNewConnection("&retriesAllDown=1&autoReconnect=true", true);
+ Statement st = connection.createStatement();
+
+ int masterServerId = getServerId(connection);
+ st.execute("drop table if exists multinodeTransaction");
+ st.execute("create table multinodeTransaction (id int not null primary key , amount int not null) ENGINE = InnoDB");
+ connection.setAutoCommit(false);
+ st.execute("insert into multinodeTransaction (id, amount) VALUE (1 , 100)");
+ stopProxy(masterServerId);
+ assertTrue(inTransaction(connection));
+ try {
+ //with autoreconnect but in transaction, query must throw an error
+ st.execute("insert into multinodeTransaction (id, amount) VALUE (2 , 10)");
+ fail();
+ } catch (SQLException e) {
+ }
+ restartProxy(masterServerId);
+ try {
+ st = connection.createStatement();
+ // will try a ping, if ok, if not, transaction is considered be lost
+ st.execute("insert into multinodeTransaction (id, amount) VALUE (2 , 10)");
+ } catch (SQLException e) {
+ fail();
+ }
+ }
+
+ @Test
+ public void testFailNotOnSlave() throws Throwable {
+ connection = getNewConnection("&retriesAllDown=1&autoReconnectMaster=true&failOnReadOnly=false", true);
+ Statement stmt = connection.createStatement();
+ int masterServerId = getServerId(connection);
+ stopProxy(masterServerId);
+ try {
+ stmt.execute("SELECT 1");
+ fail();
+ } catch (SQLException e) {
+ //normal error
+ }
+ assertTrue(!connection.isReadOnly());
+ }
+
+ @Test
+ public void testAutoReconnectMasterFailSlave() throws Throwable {
+ connection = getNewConnection("&retriesAllDown=1&failOnReadOnly=true", true);
+ Statement stmt = connection.createStatement();
+ int masterServerId = getServerId(connection);
+ stopProxy(masterServerId);
+ try {
+ stmt.execute("SELECT 1");
+ } catch (SQLException e) {
+ fail();
+ }
+ assertTrue(connection.isReadOnly());
+ }
+
+ class MutableInt {
+ int value = 1; // note that we start at 1 since we're counting
+
+ public void increment() {
+ ++value;
+ }
+
+ public int get() {
+ return value;
+ }
+ }
+
+}
diff --git a/src/test/java/org/mariadb/jdbc/failover/TcpProxy.java b/src/test/java/org/mariadb/jdbc/failover/TcpProxy.java
new file mode 100644
index 000000000..64ea86b75
--- /dev/null
+++ b/src/test/java/org/mariadb/jdbc/failover/TcpProxy.java
@@ -0,0 +1,51 @@
+package org.mariadb.jdbc.failover;
+
+import java.io.*;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.util.concurrent.Executors;
+import java.util.concurrent.RunnableScheduledFuture;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Logger;
+
+public class TcpProxy {
+ protected static Logger log = Logger.getLogger("org.maria.jdbc");
+
+ String host;
+ int remoteport;
+ TcpProxySocket socket;
+
+ public TcpProxy(String host, int remoteport) throws IOException {
+ this.host = host;
+ this.remoteport = remoteport;
+ socket = new TcpProxySocket(host, remoteport);
+ Executors.newSingleThreadScheduledExecutor().schedule(socket, 0, TimeUnit.MILLISECONDS);
+ }
+
+ public void restart(long sleepTime) {
+ socket.kill();
+ Executors.newSingleThreadScheduledExecutor().schedule(socket, sleepTime, TimeUnit.MILLISECONDS);
+ }
+
+ public void stop() {
+ socket.kill();
+ }
+
+ public void restart() {
+ Executors.newSingleThreadExecutor().execute(socket);
+ try {
+ Thread.sleep(10);
+ }catch(InterruptedException e) {}
+ }
+ public void assureProxyOk() {
+ if (socket.isClosed()) {
+ restart();
+ }
+ }
+
+ public int getLocalPort() {
+ return socket.getLocalPort();
+ }
+
+}
diff --git a/src/test/java/org/mariadb/jdbc/failover/TcpProxySocket.java b/src/test/java/org/mariadb/jdbc/failover/TcpProxySocket.java
new file mode 100644
index 000000000..b68f8619a
--- /dev/null
+++ b/src/test/java/org/mariadb/jdbc/failover/TcpProxySocket.java
@@ -0,0 +1,129 @@
+package org.mariadb.jdbc.failover;
+
+import java.io.*;
+import java.net.BindException;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.util.concurrent.Executors;
+import java.util.concurrent.RunnableScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Logger;
+
+public class TcpProxySocket implements Runnable {
+ protected static Logger log = Logger.getLogger("org.maria.jdbc");
+
+ String host;
+ int remoteport;
+ int localport;
+ boolean stop = false;
+ Socket client = null, server = null;
+ ServerSocket ss;
+
+ public TcpProxySocket(String host, int remoteport) throws IOException {
+ this.host = host;
+ this.remoteport = remoteport;
+ ss = new ServerSocket(0);
+ this.localport = ss.getLocalPort();
+ }
+
+ public int getLocalPort() {
+ return ss.getLocalPort();
+ }
+
+ public boolean isClosed() {
+ return ss.isClosed();
+ }
+
+ public void kill() {
+ stop = true;
+ try {
+ if (server != null) server.close();
+ } catch (IOException e) { }
+ try {
+ if (client != null) client.close();
+ } catch (IOException e) { }
+ try {
+ ss.close();
+ } catch (IOException e) { }
+ }
+
+ @Override
+ public void run() {
+
+ stop = false;
+ try {
+ try {
+ if (ss.isClosed()) ss = new ServerSocket(localport);
+ } catch (BindException b) {
+ //in case for testing crash and reopen too quickly
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException i) { }
+ if (ss.isClosed()) ss = new ServerSocket(localport);
+ }
+ final byte[] request = new byte[1024];
+ byte[] reply = new byte[4096];
+ while (!stop) {
+ try {
+ client = ss.accept();
+ final InputStream from_client = client.getInputStream();
+ final OutputStream to_client = client.getOutputStream();
+ try {
+ server = new Socket(host, remoteport);
+ } catch (IOException e) {
+ PrintWriter out = new PrintWriter(new OutputStreamWriter(to_client));
+ out.println("Proxy server cannot connect to " + host + ":" +
+ remoteport + ":\n" + e);
+ out.flush();
+ client.close();
+ continue;
+ }
+ final InputStream from_server = server.getInputStream();
+ final OutputStream to_server = server.getOutputStream();
+ new Thread() {
+ public void run() {
+ int bytes_read;
+ try {
+ while ((bytes_read = from_client.read(request)) != -1) {
+ to_server.write(request, 0, bytes_read);
+ log.finest(bytes_read + "to_server--->" + new String(request, "UTF-8") + "<---");
+ to_server.flush();
+ }
+ } catch (IOException e) {
+ }
+ try {
+ to_server.close();
+ } catch (IOException e) { }
+ }
+ }.start();
+ int bytes_read;
+ try {
+ while ((bytes_read = from_server.read(reply)) != -1) {
+ try {
+ Thread.sleep(1);
+ log.finest(bytes_read + " to_client--->" + new String(reply, "UTF-8") + "<---");
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ to_client.write(reply, 0, bytes_read);
+ to_client.flush();
+ }
+ } catch (IOException e) {
+ }
+ to_client.close();
+ } catch (IOException e) {
+ //System.err.println("ERROR socket : "+e);
+ }
+ finally {
+ try {
+ if (server != null) server.close();
+ if (client != null) client.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+ } catch ( IOException e) {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/src/test/resources/logging.properties b/src/test/resources/logging.properties
new file mode 100644
index 000000000..f02449094
--- /dev/null
+++ b/src/test/resources/logging.properties
@@ -0,0 +1,6 @@
+handlers = java.util.logging.ConsoleHandler
+java.util.logging.ConsoleHandler.level = FINE
+java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
+# Pattern works since Java 7
+java.util.logging.SimpleFormatter.format = [%1$tc] %4$s: %2$s - %5$s %6$s%n
+org.mariadb.jdbc.level=FINE
\ No newline at end of file