blob: a90aaeeb52573b73d69eda8ad4fcb0c546b8b865 [file] [log] [blame]
// SPDX-License-Identifier: LGPL-2.1-or-later
// Copyright (c) 2012-2014 Monty Program Ab
// Copyright (c) 2015-2021 MariaDB Corporation Ab
package org.mariadb.jdbc.export;
import java.sql.*;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.mariadb.jdbc.Configuration;
import org.mariadb.jdbc.Connection;
import org.mariadb.jdbc.HostAddress;
import org.mariadb.jdbc.MariaDbPoolConnection;
import org.mariadb.jdbc.client.Completion;
import org.mariadb.jdbc.message.server.OkPacket;
/**
* Exception factory. This permit common error logging, with thread id, dump query, and specific
* dead-lock additional information
*/
public class ExceptionFactory {
private static final Set<Integer> LOCK_DEADLOCK_ERROR_CODES =
new HashSet<>(Arrays.asList(1205, 1213, 1614));
private final Configuration conf;
private final HostAddress hostAddress;
private Connection connection;
private MariaDbPoolConnection poolConnection;
private long threadId;
private Statement statement;
/**
* Connection Exception factory constructor
*
* @param conf configuration
* @param hostAddress current host
*/
public ExceptionFactory(Configuration conf, HostAddress hostAddress) {
this.conf = conf;
this.hostAddress = hostAddress;
}
private ExceptionFactory(
Connection connection,
MariaDbPoolConnection poolConnection,
Configuration conf,
HostAddress hostAddress,
long threadId,
Statement statement) {
this.connection = connection;
this.poolConnection = poolConnection;
this.conf = conf;
this.hostAddress = hostAddress;
this.threadId = threadId;
this.statement = statement;
}
private static String buildMsgText(
String initialMessage,
long threadId,
Configuration conf,
String sql,
int errorCode,
Connection connection) {
StringBuilder msg = new StringBuilder();
if (threadId != 0L) {
msg.append("(conn=").append(threadId).append(") ");
}
msg.append(initialMessage);
if (conf.dumpQueriesOnException() && sql != null) {
if (conf.maxQuerySizeToLog() != 0 && sql.length() > conf.maxQuerySizeToLog() - 3) {
msg.append("\nQuery is: ").append(sql, 0, conf.maxQuerySizeToLog() - 3).append("...");
} else {
msg.append("\nQuery is: ").append(sql);
}
}
if (conf.includeInnodbStatusInDeadlockExceptions()
&& LOCK_DEADLOCK_ERROR_CODES.contains(errorCode)
&& connection != null) {
Statement stmt = connection.createStatement();
try {
ResultSet rs = stmt.executeQuery("SHOW ENGINE INNODB STATUS");
rs.next();
msg.append("\ndeadlock information: ").append(rs.getString(3));
} catch (SQLException sqle) {
// eat
}
}
if (conf.includeThreadDumpInDeadlockExceptions()
&& LOCK_DEADLOCK_ERROR_CODES.contains(errorCode)) {
msg.append("\nthread name: ").append(Thread.currentThread().getName());
msg.append("\ncurrent threads: ");
Thread.getAllStackTraces()
.forEach(
(thread, traces) -> {
msg.append("\n name:\"")
.append(thread.getName())
.append("\" pid:")
.append(thread.getId())
.append(" status:")
.append(thread.getState());
for (StackTraceElement trace : traces) {
msg.append("\n ").append(trace);
}
});
}
return msg.toString();
}
/**
* Set connection
*
* @param oldExceptionFactory previous connection exception factory
*/
public void setConnection(ExceptionFactory oldExceptionFactory) {
this.connection = oldExceptionFactory.connection;
}
/**
* Set connection to factory
*
* @param connection connection
* @return this {@link ExceptionFactory}
*/
public ExceptionFactory setConnection(Connection connection) {
this.connection = connection;
return this;
}
/**
* Set pool connection to factory
*
* @param internalPoolConnection internal pool connection
* @return this {@link ExceptionFactory}
*/
public ExceptionFactory setPoolConnection(MariaDbPoolConnection internalPoolConnection) {
this.poolConnection = internalPoolConnection;
return this;
}
/**
* Set connection thread id
*
* @param threadId connection thread id
*/
public void setThreadId(long threadId) {
this.threadId = threadId;
}
/**
* Create a BatchUpdateException, filling successful updates
*
* @param res completion list
* @param length expected size
* @param sqle exception
* @return BatchUpdateException object
*/
public BatchUpdateException createBatchUpdate(
List<Completion> res, int length, SQLException sqle) {
int[] updateCounts = new int[length];
for (int i = 0; i < length; i++) {
if (i < res.size()) {
if (res.get(i) instanceof OkPacket) {
updateCounts[i] = (int) ((OkPacket) res.get(i)).getAffectedRows();
} else {
updateCounts[i] = org.mariadb.jdbc.Statement.SUCCESS_NO_INFO;
}
} else {
updateCounts[i] = org.mariadb.jdbc.Statement.EXECUTE_FAILED;
}
}
return new BatchUpdateException(
sqle.getMessage(), sqle.getSQLState(), sqle.getErrorCode(), updateCounts, sqle);
}
/**
* Create a BatchUpdateException, filling successful updates
*
* @param res completion list
* @param length expected length
* @param responseMsg successful response
* @param sqle exception
* @return BatchUpdateException object
*/
public BatchUpdateException createBatchUpdate(
List<Completion> res, int length, int[] responseMsg, SQLException sqle) {
int[] updateCounts = new int[length];
int responseIncrement = 0;
for (int i = 0; i < length; i++) {
if (i >= responseMsg.length) {
Arrays.fill(updateCounts, i, length, Statement.EXECUTE_FAILED);
break;
}
int MsgResponseNo = responseMsg[i];
if (MsgResponseNo < 1) {
updateCounts[responseIncrement++] = Statement.EXECUTE_FAILED;
return new BatchUpdateException(updateCounts, sqle);
} else if (MsgResponseNo == 1 && res.size() > i && res.get(i) instanceof OkPacket) {
updateCounts[i] = (int) ((OkPacket) res.get(i)).getAffectedRows();
} else {
// unknown.
updateCounts[i] = Statement.SUCCESS_NO_INFO;
}
}
return new BatchUpdateException(
sqle.getMessage(), sqle.getSQLState(), sqle.getErrorCode(), updateCounts, sqle);
}
/**
* Construct an Exception factory from this + adding current statement
*
* @param statement current statement
* @return new Exception factory
*/
public ExceptionFactory of(Statement statement) {
return new ExceptionFactory(
this.connection,
this.poolConnection,
this.conf,
this.hostAddress,
this.threadId,
statement);
}
/**
* Construct an Exception factory from this + adding current SQL
*
* @param sql current sql command
* @return new Exception factory
*/
public ExceptionFactory withSql(String sql) {
return new SqlExceptionFactory(
this.connection,
this.poolConnection,
this.conf,
this.hostAddress,
this.threadId,
statement,
sql);
}
private SQLException createException(
String initialMessage, String sqlState, int errorCode, Exception cause) {
String msg = buildMsgText(initialMessage, threadId, conf, getSql(), errorCode, connection);
if ("70100".equals(sqlState)) { // ER_QUERY_INTERRUPTED
return new SQLTimeoutException(msg, sqlState, errorCode);
}
// 4166 : mariadb load data infile disable
// 1148 : 10.2 mariadb load data infile disable
// 3948 : mysql load data infile disable
if ((errorCode == 4166 || errorCode == 3948 || errorCode == 1148) && !conf.allowLocalInfile()) {
return new SQLException(
"Local infile is disabled by connector. Enable `allowLocalInfile` to allow local infile"
+ " commands",
sqlState,
errorCode,
cause);
}
SQLException returnEx;
String sqlClass = sqlState == null ? "42" : sqlState.substring(0, 2);
switch (sqlClass) {
case "0A":
returnEx = new SQLFeatureNotSupportedException(msg, sqlState, errorCode, cause);
break;
case "22":
case "26":
case "2F":
case "20":
case "42":
case "XA":
returnEx = new SQLSyntaxErrorException(msg, sqlState, errorCode, cause);
break;
case "25":
case "28":
returnEx = new SQLInvalidAuthorizationSpecException(msg, sqlState, errorCode, cause);
break;
case "21":
case "23":
returnEx = new SQLIntegrityConstraintViolationException(msg, sqlState, errorCode, cause);
break;
case "08":
returnEx = new SQLNonTransientConnectionException(msg, sqlState, errorCode, cause);
break;
case "40":
returnEx = new SQLTransactionRollbackException(msg, sqlState, errorCode, cause);
break;
case "HY":
returnEx = new SQLException(msg, sqlState, errorCode, cause);
break;
default:
returnEx = new SQLTransientConnectionException(msg, sqlState, errorCode, cause);
break;
}
if (poolConnection != null) {
if (statement != null && statement instanceof PreparedStatement) {
poolConnection.fireStatementErrorOccurred((PreparedStatement) statement, returnEx);
}
if (returnEx instanceof SQLNonTransientConnectionException
|| returnEx instanceof SQLTransientConnectionException) {
poolConnection.fireConnectionErrorOccurred(returnEx);
}
}
return returnEx;
}
/**
* fast creation of SQLFeatureNotSupportedException exception
*
* @param message error message
* @return exception to be thrown
*/
public SQLException notSupported(String message) {
return createException(message, "0A000", -1, null);
}
/**
* Creation of an exception
*
* @param message error message
* @return exception to be thrown
*/
public SQLException create(String message) {
return createException(message, "42000", -1, null);
}
/**
* Creation of an exception
*
* @param message error message
* @param sqlState sql state
* @return exception to be thrown
*/
public SQLException create(String message, String sqlState) {
return createException(message, sqlState, -1, null);
}
/**
* Creation of an exception
*
* @param message error message
* @param sqlState sql state
* @param cause initial exception
* @return exception to be thrown
*/
public SQLException create(String message, String sqlState, Exception cause) {
return createException(message, sqlState, -1, cause);
}
/**
* Creation of an exception
*
* @param message error message
* @param sqlState sql state
* @param errorCode error code
* @return exception to be thrown
*/
public SQLException create(String message, String sqlState, int errorCode) {
return createException(message, sqlState, errorCode, null);
}
/**
* get SQL command
*
* @return sql command
*/
public String getSql() {
return null;
}
/** Exception with SQL command */
public class SqlExceptionFactory extends ExceptionFactory {
private final String sql;
/**
* Constructor of Exception factory with SQL
*
* @param connection connection
* @param poolConnection pool connection
* @param conf configuration
* @param hostAddress host
* @param threadId connection thread id
* @param statement statement
* @param sql sql
*/
public SqlExceptionFactory(
Connection connection,
MariaDbPoolConnection poolConnection,
Configuration conf,
HostAddress hostAddress,
long threadId,
Statement statement,
String sql) {
super(connection, poolConnection, conf, hostAddress, threadId, statement);
this.sql = sql;
}
@Override
public String getSql() {
return sql;
}
}
}