package io.mycat.backend.nio;
import io.mycat.MycatServer;
import io.mycat.backend.BackendConnection;
import io.mycat.backend.MySQLDataSource;
import io.mycat.route.RouteResultsetNode;
import io.mycat.server.Capabilities;
import io.mycat.server.GenalMySQLConnection;
import io.mycat.server.Isolations;
import io.mycat.server.MySQLFrontConnection;
import io.mycat.server.exception.UnknownTxIsolationException;
import io.mycat.server.executors.ResponseHandler;
import io.mycat.server.packet.CommandPacket;
import io.mycat.server.packet.MySQLPacket;
import io.mycat.server.packet.QuitPacket;
import io.mycat.server.packet.ResultStatus;
import io.mycat.server.packet.util.CharsetUtil;
import io.mycat.server.parser.ServerParse;
import io.mycat.util.TimeUtil;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
public class MySQLBackendConnection extends GenalMySQLConnection implements
BackendConnection {
private static final CommandPacket _READ_UNCOMMITTED = new CommandPacket();
private static final CommandPacket _READ_COMMITTED = new CommandPacket();
private static final CommandPacket _REPEATED_READ = new CommandPacket();
private static final CommandPacket _SERIALIZABLE = new CommandPacket();
private static final CommandPacket _AUTOCOMMIT_ON = new CommandPacket();
private static final CommandPacket _AUTOCOMMIT_OFF = new CommandPacket();
private static final CommandPacket _COMMIT = new CommandPacket();
private static final CommandPacket _ROLLBACK = new CommandPacket();
private static final long CLIENT_FLAGS = initClientFlags();
private volatile boolean borrowed = false;
private volatile long lastTime;
private volatile boolean modifiedSQLExecuted = false;
private volatile StatusSync statusSync;
private volatile boolean metaDataSyned = true;
private volatile int xaStatus = 0;
private volatile int batchCmdCount = 0;
private MySQLDataSource pool;
private boolean fromSlaveDB;
private long threadId;
private final ResultStatus sqlResultStatus = new ResultStatus();
private Object attachment; // RouteResultsetNode
private volatile ResponseHandler respHandler;
private final AtomicBoolean isQuit;
private static long initClientFlags() {
int flag = 0;
flag |= Capabilities.CLIENT_LONG_PASSWORD;
flag |= Capabilities.CLIENT_FOUND_ROWS;
flag |= Capabilities.CLIENT_LONG_FLAG;
flag |= Capabilities.CLIENT_CONNECT_WITH_DB;
// flag |= Capabilities.CLIENT_NO_SCHEMA;
boolean usingCompress = MycatServer.getInstance().getConfig()
.getSystem().getUseCompression() == 1;
if (usingCompress) {
flag |= Capabilities.CLIENT_COMPRESS;
}
flag |= Capabilities.CLIENT_ODBC;
flag |= Capabilities.CLIENT_LOCAL_FILES;
flag |= Capabilities.CLIENT_IGNORE_SPACE;
flag |= Capabilities.CLIENT_PROTOCOL_41;
flag |= Capabilities.CLIENT_INTERACTIVE;
// flag |= Capabilities.CLIENT_SSL;
flag |= Capabilities.CLIENT_IGNORE_SIGPIPE;
flag |= Capabilities.CLIENT_TRANSACTIONS;
// flag |= Capabilities.CLIENT_RESERVED;
flag |= Capabilities.CLIENT_SECURE_CONNECTION;
// client extension
flag |= Capabilities.CLIENT_MULTI_STATEMENTS;
flag |= Capabilities.CLIENT_MULTI_RESULTS;
return flag;
}
static {
_READ_UNCOMMITTED.packetId = 0;
_READ_UNCOMMITTED.command = MySQLPacket.COM_QUERY;
_READ_UNCOMMITTED.arg = "SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED"
.getBytes();
_READ_COMMITTED.packetId = 0;
_READ_COMMITTED.command = MySQLPacket.COM_QUERY;
_READ_COMMITTED.arg = "SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED"
.getBytes();
_REPEATED_READ.packetId = 0;
_REPEATED_READ.command = MySQLPacket.COM_QUERY;
_REPEATED_READ.arg = "SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ"
.getBytes();
_SERIALIZABLE.packetId = 0;
_SERIALIZABLE.command = MySQLPacket.COM_QUERY;
_SERIALIZABLE.arg = "SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE"
.getBytes();
_AUTOCOMMIT_ON.packetId = 0;
_AUTOCOMMIT_ON.command = MySQLPacket.COM_QUERY;
_AUTOCOMMIT_ON.arg = "SET autocommit=1".getBytes();
_AUTOCOMMIT_OFF.packetId = 0;
_AUTOCOMMIT_OFF.command = MySQLPacket.COM_QUERY;
_AUTOCOMMIT_OFF.arg = "SET autocommit=0".getBytes();
_COMMIT.packetId = 0;
_COMMIT.command = MySQLPacket.COM_QUERY;
_COMMIT.arg = "commit".getBytes();
_ROLLBACK.packetId = 0;
_ROLLBACK.command = MySQLPacket.COM_QUERY;
_ROLLBACK.arg = "rollback".getBytes();
}
public MySQLBackendConnection(SocketChannel channel, boolean fromSlaveDB) {
super(channel);
this.clientFlags = CLIENT_FLAGS;
this.lastTime = TimeUtil.currentTimeMillis();
this.isQuit = new AtomicBoolean(false);
this.autocommit = true;
this.fromSlaveDB = fromSlaveDB;
}
public int getXaStatus() {
return xaStatus;
}
public void setXaStatus(int xaStatus) {
this.xaStatus = xaStatus;
}
// public void onConnectFailed(Throwable t) {
// if (handler instanceof MySQLConnectionHandler) {
// MySQLConnectionHandler theHandler = (MySQLConnectionHandler) handler;
// theHandler.connectionError(t);
// } else {
// ((MySQLConnectionAuthenticator) handler).connectionError(this, t);
// }
// }
public ResultStatus getSqlResultStatus() {
return sqlResultStatus;
}
public MySQLDataSource getPool() {
return pool;
}
public void setPool(MySQLDataSource pool) {
this.pool = pool;
}
public long getThreadId() {
return threadId;
}
public void setThreadId(long threadId) {
this.threadId = threadId;
}
public boolean isAutocommit() {
return autocommit;
}
public Object getAttachment() {
return attachment;
}
public void setAttachment(Object attachment) {
this.attachment = attachment;
}
public boolean isClosedOrQuit() {
return isClosed() || isQuit.get();
}
protected void sendQueryCmd(String query) {
CommandPacket packet = new CommandPacket();
packet.packetId = 0;
packet.command = MySQLPacket.COM_QUERY;
try {
packet.arg = query.getBytes(charset);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
lastTime = TimeUtil.currentTimeMillis();
packet.write(this);
}
private static void getCharsetCommand(StringBuilder sb, int clientCharIndex) {
sb.append("SET names ").append(CharsetUtil.getCharset(clientCharIndex))
.append(";");
}
private static void getTxIsolationCommand(StringBuilder sb, int txIsolation) {
switch (txIsolation) {
case Isolations.READ_UNCOMMITTED:
sb.append("SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;");
return;
case Isolations.READ_COMMITTED:
sb.append("SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;");
return;
case Isolations.REPEATED_READ:
sb.append("SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;");
return;
case Isolations.SERIALIZABLE:
sb.append("SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;");
return;
default:
throw new UnknownTxIsolationException("txIsolation:" + txIsolation);
}
}
private void getAutocommitCommand(StringBuilder sb, boolean autoCommit) {
if (autoCommit) {
sb.append("SET autocommit=1;");
} else {
sb.append("SET autocommit=0;");
}
}
public ResponseHandler getRespHandler() {
return respHandler;
}
private static class StatusSync {
private final String schema;
private final Integer charsetIndex;
private final Integer txtIsolation;
private final Boolean autocommit;
private final AtomicInteger synCmdCount;
private final boolean xaStarted;
public StatusSync(boolean xaStarted, String schema,
Integer charsetIndex, Integer txtIsolation, Boolean autocommit,
int synCount) {
super();
this.xaStarted = xaStarted;
this.schema = schema;
this.charsetIndex = charsetIndex;
this.txtIsolation = txtIsolation;
this.autocommit = autocommit;
this.synCmdCount = new AtomicInteger(synCount);
}
public boolean synAndExecuted(MySQLBackendConnection conn) {
int remains = synCmdCount.decrementAndGet();
if (remains == 0) {// syn command finished
this.updateConnectionInfo(conn);
conn.metaDataSyned = true;
return false;
} else if (remains < 0) {
return true;
}
return false;
}
private void updateConnectionInfo(MySQLBackendConnection conn)
{
conn.xaStatus = (xaStarted) ? 1 : 0;
if (schema != null) {
conn.schema = schema;
conn.oldSchema = conn.schema;
}
if (charsetIndex != null) {
conn.setCharset(CharsetUtil.getCharset(charsetIndex));
}
if (txtIsolation != null) {
conn.txIsolation = txtIsolation;
}
if (autocommit != null) {
conn.autocommit = autocommit;
}
}
}
/**
* @return if synchronization finished and execute-sql has already been sent
* before
*/
public boolean syncAndExcute() {
StatusSync sync = this.statusSync;
if (sync == null) {
return true;
} else {
boolean executed = sync.synAndExecuted(this);
if (executed) {
statusSync = null;
}
return executed;
}
}
public void execute(RouteResultsetNode rrn, MySQLFrontConnection sc,
boolean autocommit) throws UnsupportedEncodingException {
if (!modifiedSQLExecuted && rrn.isModifySQL()) {
modifiedSQLExecuted = true;
}
String xaTXID = sc.getSession2().getXaTXID();
synAndDoExecute(xaTXID, rrn, sc.getCharsetIndex(), sc.getTxIsolation(),
autocommit);
}
private void synAndDoExecute(String xaTxID, RouteResultsetNode rrn,
int clientCharSetIndex, int clientTxIsoLation,
boolean clientAutoCommit) {
String xaCmd = null;
boolean conAutoComit = this.autocommit;
String conSchema = this.schema;
// never executed modify sql,so auto commit
boolean expectAutocommit = !modifiedSQLExecuted || isFromSlaveDB()
|| clientAutoCommit;
if (!expectAutocommit && xaTxID != null && xaStatus == 0) {
clientTxIsoLation = Isolations.SERIALIZABLE;
xaCmd = "XA START " + xaTxID + ';';
}
int schemaSyn = conSchema.equals(oldSchema) ? 0 : 1;
int charsetSyn = (this.charsetIndex == clientCharSetIndex) ? 0 : 1;
int txIsoLationSyn = (txIsolation == clientTxIsoLation) ? 0 : 1;
int autoCommitSyn = (conAutoComit == expectAutocommit) ? 0 : 1;
int synCount = schemaSyn + charsetSyn + txIsoLationSyn + autoCommitSyn;
if (synCount == 0) {
// not need syn connection
sendQueryCmd(rrn.getStatement());
return;
}
CommandPacket schemaCmd = null;
StringBuilder sb = new StringBuilder();
if (schemaSyn == 1) {
schemaCmd = getChangeSchemaCommand(conSchema);
}
if (charsetSyn == 1) {
getCharsetCommand(sb, clientCharSetIndex);
}
if (txIsoLationSyn == 1) {
getTxIsolationCommand(sb, clientTxIsoLation);
}
if (autoCommitSyn == 1) {
getAutocommitCommand(sb, expectAutocommit);
}
if (xaCmd != null) {
sb.append(xaCmd);
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("con need syn ,total syn cmd " + synCount
+ " commands " + sb.toString() + "schema change:"
+ (schemaCmd != null) + " con:" + this);
}
metaDataSyned = false;
statusSync = new StatusSync(xaCmd != null, conSchema,
clientCharSetIndex, clientTxIsoLation, expectAutocommit,
synCount);
// syn schema
if (schemaCmd != null) {
schemaCmd.write(this);
}
// and our query sql to multi command at last
sb.append(rrn.getStatement());
// syn and execute others
this.sendQueryCmd(sb.toString());
// waiting syn result...
}
private static CommandPacket getChangeSchemaCommand(String schema) {
CommandPacket cmd = new CommandPacket();
cmd.packetId = 0;
cmd.command = MySQLPacket.COM_INIT_DB;
cmd.arg = schema.getBytes();
return cmd;
}
/**
* by wuzh ,execute a query and ignore transaction settings for performance
*
* @param sql
* @throws UnsupportedEncodingException
*/
public void query(String query) throws UnsupportedEncodingException {
RouteResultsetNode rrn = new RouteResultsetNode("default",
ServerParse.SELECT, query);
synAndDoExecute(null, rrn, this.charsetIndex, this.txIsolation, true);
}
public long getLastTime() {
return lastTime;
}
public void setLastTime(long lastTime) {
this.lastTime = lastTime;
}
public void quit() {
if (isQuit.compareAndSet(false, true) && !isClosed()) {
if (isAuthenticated) {
write(QuitPacket.QUIT);
write(ByteBuffer.allocate(10));
} else {
close("normal");
}
}
}
@Override
public void close(String reason) {
if (!isClosed) {
isQuit.set(true);
super.close(reason);
pool.connectionClosed(this);
if (this.respHandler != null) {
this.respHandler.connectionClose(this, reason);
respHandler = null;
}
}
}
public void commit() {
_COMMIT.write(this);
}
public boolean batchCmdFinished() {
batchCmdCount--;
return (batchCmdCount == 0);
}
public void execCmd(String cmd) {
this.sendQueryCmd(cmd);
}
public void execBatchCmd(String[] batchCmds) {
// "XA END "+xaID+";"+"XA PREPARE "+xaID
this.batchCmdCount = batchCmds.length;
StringBuilder sb = new StringBuilder();
for (String sql : batchCmds) {
sb.append(sql).append(';');
}
this.sendQueryCmd(sb.toString());
}
public void rollback() {
_ROLLBACK.write(this);
}
public void release() {
if (!metaDataSyned) {// indicate connection not normalfinished
// ,and
// we can't know it's syn status ,so
// close
// it
LOGGER.warn("can't sure connection syn result,so close it " + this);
this.respHandler = null;
this.close("syn status unkown ");
return;
}
metaDataSyned = true;
attachment = null;
statusSync = null;
modifiedSQLExecuted = false;
setResponseHandler(null);
pool.releaseChannel(this);
}
public void setResponseHandler(ResponseHandler queryHandler) {
respHandler = queryHandler;
}
public boolean isFromSlaveDB() {
return fromSlaveDB;
}
public boolean isBorrowed() {
return borrowed;
}
public void setBorrowed(boolean borrowed) {
this.lastTime = TimeUtil.currentTimeMillis();
this.borrowed = borrowed;
}
@Override
public String toString() {
return "MySQLConnection [id=" + id + ", lastTime=" + lastTime
+ ", schema=" + schema + ", old shema=" + oldSchema
+ ", borrowed=" + borrowed + ", fromSlaveDB=" + fromSlaveDB
+ ", threadId=" + threadId + ", charset=" + charset
+ ", txIsolation=" + txIsolation + ", autocommit=" + autocommit
+ ", attachment=" + attachment + ", respHandler=" + respHandler
+ ", host=" + host + ", port=" + port + ", statusSync="
+ statusSync + ", writeQueue=" + this.getWriteQueue().size()
+ ", modifiedSQLExecuted=" + modifiedSQLExecuted + "]";
}
public boolean isModifiedSQLExecuted() {
return modifiedSQLExecuted;
}
@Override
public int getTxIsolation() {
return txIsolation;
}
}