package io.mycat.backend.jdbc;
import io.mycat.backend.BackendConnection;
import io.mycat.net.BufferArray;
import io.mycat.net.NetSystem;
import io.mycat.route.RouteResultsetNode;
import io.mycat.server.ErrorCode;
import io.mycat.server.Isolations;
import io.mycat.server.MySQLFrontConnection;
import io.mycat.server.executors.ConnectionHeartBeatHandler;
import io.mycat.server.executors.ResponseHandler;
import io.mycat.server.packet.*;
import io.mycat.server.parser.ServerParse;
import io.mycat.server.response.ShowVariables;
import io.mycat.util.ResultSetUtil;
import io.mycat.util.StringUtil;
import io.mycat.util.TimeUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
public class JDBCConnection implements BackendConnection {
protected static final Logger LOGGER = LoggerFactory
.getLogger(JDBCConnection.class);
private JDBCDatasource pool;
private volatile String schema;
private volatile String dbType;
private volatile String oldSchema;
private byte packetId;
private int txIsolation;
private volatile boolean running = false;
private volatile boolean borrowed;
private long id = 0;
private String host;
private int port;
private Connection con;
private ResponseHandler respHandler;
private volatile Object attachement;
boolean headerOutputed = false;
private volatile boolean modifiedSQLExecuted;
private final long startTime;
private long lastTime;
private boolean isSpark = false;
public JDBCConnection() {
startTime = System.currentTimeMillis();
}
public Connection getCon() {
return con;
}
public void setCon(Connection con) {
this.con = con;
}
@Override
public void close(String reason) {
try {
con.close();
} catch (SQLException e) {
}
}
public void setId(long id) {
this.id = id;
}
public JDBCDatasource getPool() {
return pool;
}
public void setPool(JDBCDatasource pool) {
this.pool = pool;
}
public void setHost(String host) {
this.host = host;
}
public void setPort(int port) {
this.port = port;
}
@Override
public boolean isClosed() {
try {
return con == null || con.isClosed();
} catch (SQLException e) {
return true;
}
}
@Override
public void idleCheck() {
if (TimeUtil.currentTimeMillis() > lastTime
+ pool.getConfig().getIdleTimeout()) {
close(" idle check");
}
}
@Override
public long getStartupTime() {
return startTime;
}
public String getHost() {
return this.host;
}
public int getPort() {
return this.port;
}
public int getLocalPort() {
return 0;
}
public long getNetInBytes() {
return 0;
}
public long getNetOutBytes() {
return 0;
}
@Override
public boolean isModifiedSQLExecuted() {
return modifiedSQLExecuted;
}
@Override
public boolean isFromSlaveDB() {
return false;
}
public String getDbType() {
return this.dbType;
}
public void setDbType(String newDbType) {
this.dbType = newDbType.toUpperCase();
this.isSpark = dbType.equals("SPARK");
}
@Override
public String getSchema() {
return this.schema;
}
@Override
public void setSchema(String newSchema) {
this.oldSchema = this.schema;
this.schema = newSchema;
}
@Override
public long getLastTime() {
return lastTime;
}
@Override
public boolean isClosedOrQuit() {
return this.isClosed();
}
@Override
public void setAttachment(Object attachment) {
this.attachement = attachment;
}
@Override
public void quit() {
this.close("client quit");
}
@Override
public void setLastTime(long currentTimeMillis) {
this.lastTime = currentTimeMillis;
}
@Override
public void release() {
modifiedSQLExecuted = false;
setResponseHandler(null);
pool.releaseChannel(this);
}
public void setRunning(boolean running) {
this.running = running;
}
@Override
public void setResponseHandler(ResponseHandler commandHandler) {
respHandler = commandHandler;
}
@Override
public void commit() {
try {
con.commit();
this.respHandler.okResponse(OkPacket.OK, this);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
private int convertNativeIsolationToJDBC(int nativeIsolation) {
if (nativeIsolation == Isolations.REPEATED_READ) {
return Connection.TRANSACTION_REPEATABLE_READ;
} else if (nativeIsolation == Isolations.SERIALIZABLE) {
return Connection.TRANSACTION_SERIALIZABLE;
} else {
return nativeIsolation;
}
}
private void syncIsolation(int nativeIsolation) {
int jdbcIsolation = convertNativeIsolationToJDBC(nativeIsolation);
int srcJdbcIsolation = getTxIsolation();
if (jdbcIsolation == srcJdbcIsolation)
return;
if ("oracle".equalsIgnoreCase(getDbType())
&& jdbcIsolation != Connection.TRANSACTION_READ_COMMITTED
&& jdbcIsolation != Connection.TRANSACTION_SERIALIZABLE) {
// oracle 只支持2个级别 ,且只能更改一次隔离级别,否则会报 ORA-01453
return;
}
try {
con.setTransactionIsolation(jdbcIsolation);
} catch (SQLException e) {
LOGGER.warn("set txisolation error:", e);
}
}
private void executeSQL(RouteResultsetNode rrn, MySQLFrontConnection sc,
boolean autocommit) throws IOException {
String orgin = rrn.getStatement();
// String sql = rrn.getStatement().toLowerCase();
// LOGGER.info("JDBC SQL:"+orgin+"|"+sc.toString());
if (!modifiedSQLExecuted && rrn.isModifySQL()) {
modifiedSQLExecuted = true;
}
try {
syncIsolation(sc.getTxIsolation());
if (!this.schema.equals(this.oldSchema)) {
con.setCatalog(schema);
this.oldSchema = schema;
}
if (!this.isSpark) {
con.setAutoCommit(autocommit);
}
int sqlType = rrn.getSqlType();
if (sqlType == ServerParse.SELECT || sqlType == ServerParse.SHOW) {
if ((sqlType == ServerParse.SHOW) && (!dbType.equals("MYSQL"))) {
// showCMD(sc, orgin);
// ShowVariables.execute(sc, orgin);
ShowVariables.execute(sc);
// } else if ("SELECT CONNECTION_ID()".equalsIgnoreCase(orgin)) {
// // ShowVariables.justReturnValue(sc,String.valueOf(sc.getId()));
// ShowVariables.justReturnValue(sc,
// String.valueOf(sc.getId()), this);
} else
{
ouputResultSet(sc, orgin);
}
} else {
executeddl(sc, orgin);
}
} catch (SQLException e) {
String msg = e.getMessage();
ErrorPacket error = new ErrorPacket();
error.packetId = ++packetId;
error.errno = e.getErrorCode();
error.message = msg.getBytes();
this.respHandler.errorResponse(error.writeToBytes(), this);
} catch (Exception e) {
String msg = e.getMessage();
ErrorPacket error = new ErrorPacket();
error.packetId = ++packetId;
error.errno = ErrorCode.ER_UNKNOWN_ERROR;
error.message = msg.getBytes();
this.respHandler.errorResponse(error.writeToBytes(), this);
} finally {
this.running = false;
}
}
private void executeddl(MySQLFrontConnection sc, String sql)
throws SQLException {
Statement stmt = null;
try {
stmt = con.createStatement();
int count = stmt.executeUpdate(sql);
OkPacket okPck = new OkPacket();
okPck.affectedRows = count;
okPck.insertId = 0;
okPck.packetId = ++packetId;
okPck.message = " OK!".getBytes();
this.respHandler.okResponse(okPck.writeToBytes(), this);
} finally {
if (stmt != null) {
try {
stmt.close();
} catch (SQLException e) {
}
}
}
}
private void ouputResultSet(MySQLFrontConnection sc, String sql)
throws SQLException {
ResultSet rs = null;
Statement stmt = null;
try {
stmt = con.createStatement();
rs = stmt.executeQuery(sql);
List<FieldPacket> fieldPks = new LinkedList<FieldPacket>();
ResultSetUtil.resultSetToFieldPacket(sc.getCharset(), fieldPks, rs,
this.isSpark);
int colunmCount = fieldPks.size();
BufferArray bufferArray = NetSystem.getInstance().getBufferPool()
.allocateArray();
ResultSetHeaderPacket headerPkg = new ResultSetHeaderPacket();
headerPkg.fieldCount = fieldPks.size();
headerPkg.packetId = ++packetId;
headerPkg.write(bufferArray);
byte[] header =bufferArray.writeToByteArrayAndRecycle();
List<byte[]> fields = new ArrayList<byte[]>(fieldPks.size());
Iterator<FieldPacket> itor = fieldPks.iterator();
while (itor.hasNext()) {
bufferArray = NetSystem.getInstance().getBufferPool()
.allocateArray();
FieldPacket curField = itor.next();
curField.packetId = ++packetId;
curField.write(bufferArray);
byte[] field = bufferArray.writeToByteArrayAndRecycle();
fields.add(field);
itor.remove();
}
bufferArray = NetSystem.getInstance().getBufferPool()
.allocateArray();
EOFPacket eofPckg = new EOFPacket();
eofPckg.packetId = ++packetId;
eofPckg.write(bufferArray);
byte[] eof = bufferArray.writeToByteArrayAndRecycle();
this.respHandler.fieldEofResponse(header, fields, eof, this);
// output row
while (rs.next()) {
bufferArray = NetSystem.getInstance().getBufferPool()
.allocateArray();
RowDataPacket curRow = new RowDataPacket(colunmCount);
for (int i = 0; i < colunmCount; i++) {
int j = i + 1;
curRow.add(StringUtil.encode(rs.getString(j),
sc.getCharset()));
}
curRow.packetId = ++packetId;
curRow.write(bufferArray);
byte[] row =bufferArray.writeToByteArrayAndRecycle();
this.respHandler.rowResponse(row, this);
}
// end row
bufferArray = NetSystem.getInstance().getBufferPool()
.allocateArray();
eofPckg = new EOFPacket();
eofPckg.packetId = ++packetId;
eofPckg.write(bufferArray);
eof = bufferArray.writeToByteArrayAndRecycle();
this.respHandler.rowEofResponse(eof, this);
} finally {
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
}
}
if (stmt != null) {
try {
stmt.close();
} catch (SQLException e) {
}
}
}
}
@Override
public void query(final String sql) throws UnsupportedEncodingException {
if (respHandler instanceof ConnectionHeartBeatHandler) {
justForHeartbeat(sql);
} else {
throw new UnsupportedEncodingException("unsupported yet ");
}
}
private void justForHeartbeat(String sql) {
Statement stmt = null;
try {
stmt = con.createStatement();
stmt.execute(sql);
if (!isAutocommit()) { // 如果在写库上,如果是事务方式的连接,需要进行手动commit
con.commit();
}
this.respHandler.okResponse(OkPacket.OK, this);
} catch (Exception e) {
String msg = e.getMessage();
ErrorPacket error = new ErrorPacket();
error.packetId = ++packetId;
error.errno = ErrorCode.ER_UNKNOWN_ERROR;
error.message = msg.getBytes();
this.respHandler.errorResponse(error.writeToBytes(), this);
} finally {
if (stmt != null) {
try {
stmt.close();
} catch (SQLException e) {
}
}
}
}
@Override
public Object getAttachment() {
return this.attachement;
}
@Override
public String getCharset() {
return null;
}
@Override
public void execute(final RouteResultsetNode node,
final MySQLFrontConnection source, final boolean autocommit)
throws IOException {
Runnable runnable = new Runnable() {
@Override
public void run() {
try {
executeSQL(node, source, autocommit);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
};
NetSystem.getInstance().getExecutor().execute(runnable);
}
@Override
public boolean syncAndExcute() {
return true;
}
@Override
public void rollback() {
try {
con.rollback();
this.respHandler.okResponse(OkPacket.OK, this);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
public boolean isRunning() {
return this.running;
}
@Override
public boolean isBorrowed() {
return this.borrowed;
}
@Override
public void setBorrowed(boolean borrowed) {
this.borrowed = borrowed;
}
@Override
public int getTxIsolation() {
if (con != null) {
try {
return con.getTransactionIsolation();
} catch (SQLException e) {
return 0;
}
} else {
return -1;
}
}
@Override
public boolean isAutocommit() {
if (con == null) {
return true;
} else {
try {
return con.getAutoCommit();
} catch (SQLException e) {
}
}
return true;
}
@Override
public long getId() {
return id;
}
@Override
public String toString() {
return "JDBCConnection [id=" + id + ",autocommit="
+ this.isAutocommit() + ",pool=" + pool + ", schema=" + schema
+ ", dbType=" + dbType + ", oldSchema=" + oldSchema
+ ", packetId=" + packetId + ", txIsolation=" + txIsolation
+ ", running=" + running + ", borrowed=" + borrowed + ", host="
+ host + ", port=" + port + ", con=" + con + ", respHandler="
+ respHandler + ", attachement=" + attachement
+ ", headerOutputed=" + headerOutputed
+ ", modifiedSQLExecuted=" + modifiedSQLExecuted
+ ", startTime=" + startTime + ", lastTime=" + lastTime
+ ", isSpark=" + isSpark+"]";
}
}