package io.mycat.server;
import io.mycat.MycatServer;
import io.mycat.net.NetSystem;
import io.mycat.route.RouteResultset;
import io.mycat.server.config.node.SchemaConfig;
import io.mycat.server.packet.HandshakePacket;
import io.mycat.server.packet.MySQLMessage;
import io.mycat.server.packet.OkPacket;
import io.mycat.server.parser.ServerParse;
import io.mycat.server.sqlhandler.BeginHandler;
import io.mycat.server.sqlhandler.ExplainHandler;
import io.mycat.server.sqlhandler.KillHandler;
import io.mycat.server.sqlhandler.SavepointHandler;
import io.mycat.server.sqlhandler.SelectHandler;
import io.mycat.server.sqlhandler.ServerLoadDataInfileHandler;
import io.mycat.server.sqlhandler.ServerPrepareHandler;
import io.mycat.server.sqlhandler.SetHandler;
import io.mycat.server.sqlhandler.ShowHandler;
import io.mycat.server.sqlhandler.StartHandler;
import io.mycat.server.sqlhandler.UseHandler;
import io.mycat.util.RandomUtil;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.InetSocketAddress;
import java.nio.channels.SocketChannel;
import java.util.Set;
/**
* MySQL Front connection
*
* @author wuzhih
*
*/
public class MySQLFrontConnection extends GenalMySQLConnection {
protected FrontendPrivileges privileges;
protected FrontendPrepareHandler prepareHandler;
protected LoadDataInfileHandler loadDataInfileHandler;
private final NonBlockingSession session;
private boolean readOnlyUser = false;
protected int getServerCapabilities() {
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 |= ServerDefs.CLIENT_RESERVED;
flag |= Capabilities.CLIENT_SECURE_CONNECTION;
return flag;
}
public MySQLFrontConnection(SocketChannel channel) throws IOException {
super(channel);
session = new NonBlockingSession(this);
InetSocketAddress remoteAddr = null;
InetSocketAddress localAddr = (InetSocketAddress) channel
.getLocalAddress();
remoteAddr = (InetSocketAddress) ((SocketChannel) channel)
.getRemoteAddress();
this.host = remoteAddr.getHostString();
this.port = remoteAddr.getPort();
this.localPort = localAddr.getPort();
loadDataInfileHandler = new ServerLoadDataInfileHandler(this);
prepareHandler = new ServerPrepareHandler(this);
}
public void sendAuthPackge() throws IOException {
// 生成认证数据
byte[] rand1 = RandomUtil.randomBytes(8);
byte[] rand2 = RandomUtil.randomBytes(12);
// 保存认证数据
byte[] seed = new byte[rand1.length + rand2.length];
System.arraycopy(rand1, 0, seed, 0, rand1.length);
System.arraycopy(rand2, 0, seed, rand1.length, rand2.length);
this.seed = seed;
// 发送握手数据包
HandshakePacket hs = new HandshakePacket();
hs.packetId = 0;
hs.protocolVersion = Versions.PROTOCOL_VERSION;
hs.serverVersion = Versions.SERVER_VERSION;
hs.threadId = id;
hs.seed = rand1;
hs.serverCapabilities = getServerCapabilities();
hs.serverCharsetIndex = (byte) (charsetIndex & 0xff);
hs.serverStatus = 2;
hs.restOfScrambleBuff = rand2;
hs.write(this);
// asynread response
this.asynRead();
}
/**
* 设置是否需要中断当前事务
*/
public void setTxInterrupt(String txInterrputMsg) {
if (!autocommit && !txInterrupted) {
txInterrupted = true;
this.txInterrputMsg = txInterrputMsg;
}
}
public FrontendPrivileges getPrivileges() {
return privileges;
}
public void setPrivileges(FrontendPrivileges privileges) {
this.privileges = privileges;
}
public boolean isTxInterrupted() {
return txInterrupted;
}
public NonBlockingSession getSession2() {
return this.session;
}
public void initDB(byte[] data) {
MySQLMessage mm = new MySQLMessage(data);
mm.position(5);
String db = mm.readString();
// 检查schema的有效性
if (db == null || !privileges.schemaExists(db)) {
writeErrMessage(ErrorCode.ER_BAD_DB_ERROR, "Unknown database '"
+ db + "'");
return;
}
if (!privileges.userExists(user, host)) {
writeErrMessage(ErrorCode.ER_ACCESS_DENIED_ERROR,
"Access denied for user '" + user + "'");
return;
}
readOnlyUser = privileges.isReadOnly(user);
Set<String> schemas = privileges.getUserSchemas(user);
if (schemas == null || schemas.size() == 0 || schemas.contains(db)) {
this.schema = db;
write(OkPacket.OK);
} else {
String s = "Access denied for user '" + user + "' to database '"
+ db + "'";
writeErrMessage(ErrorCode.ER_DBACCESS_DENIED_ERROR, s);
}
}
public void loadDataInfileStart(String sql) {
if (loadDataInfileHandler != null) {
try {
loadDataInfileHandler.start(sql);
} catch (Exception e) {
LOGGER.error("load data error", e);
writeErrMessage(ErrorCode.ERR_HANDLE_DATA, e.getMessage());
}
} else {
writeErrMessage(ErrorCode.ER_UNKNOWN_COM_ERROR,
"load data infile sql is not unsupported!");
}
}
public void loadDataInfileData(byte[] data) {
if (loadDataInfileHandler != null) {
try {
loadDataInfileHandler.handle(data);
} catch (Exception e) {
LOGGER.error("load data error", e);
writeErrMessage(ErrorCode.ERR_HANDLE_DATA, e.getMessage());
}
} else {
writeErrMessage(ErrorCode.ER_UNKNOWN_COM_ERROR,
"load data infile data is not unsupported!");
}
}
public void loadDataInfileEnd(byte packID) {
if (loadDataInfileHandler != null) {
try {
loadDataInfileHandler.end(packID);
} catch (Exception e) {
LOGGER.error("load data error", e);
writeErrMessage(ErrorCode.ERR_HANDLE_DATA, e.getMessage());
}
} else {
writeErrMessage(ErrorCode.ER_UNKNOWN_COM_ERROR,
"load data infile end is not unsupported!");
}
}
public void query(byte[] data) {
if (this.isClosed()) {
LOGGER.warn("ignore execute ,server connection is closed " + this);
return;
}
// 状态检查
if (txInterrupted) {
writeErrMessage(ErrorCode.ER_YES,
"Transaction error, need to rollback." + txInterrputMsg);
return;
}
// 取得语句
MySQLMessage mm = new MySQLMessage(data);
mm.position(5);
String sql = null;
try {
sql = mm.readString(charset);
} catch (UnsupportedEncodingException e) {
writeErrMessage(ErrorCode.ER_UNKNOWN_CHARACTER_SET,
"Unknown charset '" + charset + "'");
return;
}
query(sql);
}
public void query(String sql) {
if (sql == null || sql.length() == 0) {
writeErrMessage(ErrorCode.ER_NOT_ALLOWED_COMMAND, "Empty SQL");
return;
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(new StringBuilder().append(this).append(" ")
.append(sql).toString());
}
// sql = StringUtil.replace(sql, "`", "");
// remove last ';'
if (sql.endsWith(";")) {
sql = sql.substring(0, sql.length() - 1);
}
// 执行查询
int rs = ServerParse.parse(sql);
int sqlType = rs & 0xff;
// 检查当前使用的DB
String db = this.schema;
if (db == null
&& sqlType!=ServerParse.USE
&& sqlType!=ServerParse.HELP
&& sqlType!=ServerParse.SET
&& sqlType!=ServerParse.SHOW
&& sqlType!=ServerParse.KILL
&& sqlType!=ServerParse.KILL_QUERY
&& sqlType!=ServerParse.MYSQL_COMMENT
&& sqlType!=ServerParse.MYSQL_CMD_COMMENT) {
writeErrMessage(ErrorCode.ERR_BAD_LOGICDB, "No MyCAT Database selected");
return;
}
switch (sqlType) {
case ServerParse.EXPLAIN:
ExplainHandler.handle(sql, this, rs >>> 8);
break;
case ServerParse.SET:
SetHandler.handle(sql, this, rs >>> 8);
break;
case ServerParse.SHOW:
ShowHandler.handle(sql, this, rs >>> 8);
break;
case ServerParse.SELECT:
SelectHandler.handle(sql, this, rs >>> 8);
break;
case ServerParse.START:
StartHandler.handle(sql, this, rs >>> 8);
break;
case ServerParse.BEGIN:
BeginHandler.handle(sql, this);
break;
case ServerParse.SAVEPOINT:
SavepointHandler.handle(sql, this);
break;
case ServerParse.KILL:
KillHandler.handle(sql, rs >>> 8, this);
break;
case ServerParse.KILL_QUERY:
LOGGER.warn(new StringBuilder().append("Unsupported command:")
.append(sql).toString());
writeErrMessage(ErrorCode.ER_UNKNOWN_COM_ERROR,
"Unsupported command");
break;
case ServerParse.USE:
UseHandler.handle(sql, this, rs >>> 8);
break;
case ServerParse.COMMIT:
commit();
break;
case ServerParse.ROLLBACK:
rollback();
break;
case ServerParse.HELP:
LOGGER.warn(new StringBuilder().append("Unsupported command:")
.append(sql).toString());
writeErrMessage(ErrorCode.ER_SYNTAX_ERROR, "Unsupported command");
break;
case ServerParse.MYSQL_CMD_COMMENT:
write(OkPacket.OK);
break;
case ServerParse.MYSQL_COMMENT:
write(OkPacket.OK);
break;
case ServerParse.LOAD_DATA_INFILE_SQL:
loadDataInfileStart(sql);
break;
default:
if (this.isReadOnlyUser()) {
LOGGER.warn(new StringBuilder().append("User readonly:")
.append(sql).toString());
writeErrMessage(ErrorCode.ER_USER_READ_ONLY, "User readonly");
break;
}
execute(sql, rs & 0xff);
}
}
public boolean isReadOnlyUser() {
return readOnlyUser;
}
public void execute(String sql, int type) {
SchemaConfig schema = MycatServer.getInstance().getConfig()
.getSchemas().get(this.schema);
if (schema == null) {
writeErrMessage(ErrorCode.ERR_BAD_LOGICDB,
"Unknown MyCAT Database '" + schema + "'");
return;
}
routeEndExecuteSQL(sql, type, schema);
}
public void routeEndExecuteSQL(String sql, int type, SchemaConfig schema) {
// 路由计算
RouteResultset rrs = null;
try {
rrs = MycatServer
.getInstance()
.getRouterservice()
.route(MycatServer.getInstance().getConfig().getSystem(),
schema, type, sql, this.charset, this);
} catch (Exception e) {
StringBuilder s = new StringBuilder();
LOGGER.warn(
s.append(this).append(sql).toString() + " err:"
+ e.toString(), e);
String msg = e.getMessage();
writeErrMessage(ErrorCode.ER_PARSE_ERROR, msg == null ? e
.getClass().getSimpleName() : msg);
return;
}
if (rrs != null) {
// session执行
session.execute(rrs, type);
}
}
public void stmtPrepare(byte[] data) {
if (prepareHandler != null) {
// 取得语句
MySQLMessage mm = new MySQLMessage(data);
mm.position(5);
String sql = null;
try {
sql = mm.readString(charset);
} catch (UnsupportedEncodingException e) {
writeErrMessage(ErrorCode.ER_UNKNOWN_CHARACTER_SET,
"Unknown charset '" + charset + "'");
return;
}
if (sql == null || sql.length() == 0) {
writeErrMessage(ErrorCode.ER_NOT_ALLOWED_COMMAND, "Empty SQL");
return;
}
// 执行预处理
prepareHandler.prepare(sql);
} else {
writeErrMessage(ErrorCode.ER_UNKNOWN_COM_ERROR,
"Prepare unsupported!");
}
}
public void stmtExecute(byte[] data) {
if (prepareHandler != null) {
prepareHandler.execute(data);
} else {
writeErrMessage(ErrorCode.ER_UNKNOWN_COM_ERROR,
"Prepare unsupported!");
}
}
public void stmtClose(byte[] data) {
if (prepareHandler != null) {
prepareHandler.close(data);
} else {
writeErrMessage(ErrorCode.ER_UNKNOWN_COM_ERROR,
"Prepare unsupported!");
}
}
public RouteResultset routeSQL(String sql, int type) {
// 检查当前使用的DB
String db = this.schema;
if (db == null) {
writeErrMessage(ErrorCode.ERR_BAD_LOGICDB,
"No MyCAT Database selected");
return null;
}
SchemaConfig schema = MycatServer.getInstance().getConfig()
.getSchemas().get(db);
if (schema == null) {
writeErrMessage(ErrorCode.ERR_BAD_LOGICDB,
"Unknown MyCAT Database '" + db + "'");
return null;
}
// 路由计算
RouteResultset rrs = null;
try {
rrs = MycatServer
.getInstance()
.getRouterservice()
.route(MycatServer.getInstance().getConfig().getSystem(),
schema, type, sql, this.charset, this);
} catch (Exception e) {
StringBuilder s = new StringBuilder();
LOGGER.warn(
s.append(this).append(sql).toString() + " err:"
+ e.toString(), e);
String msg = e.getMessage();
writeErrMessage(ErrorCode.ER_PARSE_ERROR, msg == null ? e
.getClass().getSimpleName() : msg);
return null;
}
return rrs;
}
/**
* 提交事务
*/
public void commit() {
if (txInterrupted) {
writeErrMessage(ErrorCode.ER_YES,
"Transaction error, need to rollback.");
} else {
session.commit();
}
}
/**
* 回滚事务
*/
public void rollback() {
// 状态检查
if (txInterrupted) {
txInterrupted = false;
}
// 执行回滚
session.rollback();
}
/**
* 撤销执行中的语句
*
* @param sponsor
* 发起者为null表示是自己
*/
public void cancel(final MySQLFrontConnection sponsor) {
NetSystem.getInstance().getExecutor().execute(new Runnable() {
@Override
public void run() {
session.cancel(sponsor);
}
});
}
@Override
public void close(String reason) {
super.close(reason);
session.terminate();
if (getLoadDataInfileHandler() != null) {
getLoadDataInfileHandler().clear();
}
if(getPrepareHandler() != null) {
getPrepareHandler().clear();
}
}
public LoadDataInfileHandler getLoadDataInfileHandler() {
return loadDataInfileHandler;
}
public FrontendPrepareHandler getPrepareHandler() {
return prepareHandler;
}
public void ping() {
write(OkPacket.OK);
}
public void heartbeat(byte[] data) {
write(OkPacket.OK);
}
public void kill(byte[] data) {
writeErrMessage(ErrorCode.ER_UNKNOWN_COM_ERROR, "Unknown command");
}
public void unknown(byte[] data) {
writeErrMessage(ErrorCode.ER_UNKNOWN_COM_ERROR, "Unknown command");
}
}