package org.opencloudb.net.mysql; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import java.io.UnsupportedEncodingException; import java.util.concurrent.atomic.AtomicBoolean; import org.apache.log4j.Logger; import org.opencloudb.backend.BackendConnection; import org.opencloudb.config.Isolations; import org.opencloudb.exception.UnknownTxIsolationException; import org.opencloudb.mysql.CharsetUtil; import org.opencloudb.mysql.nio.handler.ResponseHandler; import org.opencloudb.net.ConnectionInfo; import org.opencloudb.net.FrontSession; import org.opencloudb.route.RouteResultsetNode; import org.opencloudb.server.parser.ServerParse; import org.opencloudb.util.TimeUtil; public class BackMysqlConnection implements BackendConnection { private static final Logger LOGGER = Logger .getLogger(BackMysqlConnection.class); private ResponseHandler respHandler; private final BackMySQLConnectionDataSource pool; private volatile StatusSync statusSync; private volatile boolean oldAutoCommit; private final AtomicBoolean isClosed = new AtomicBoolean(); private final boolean fromeSlave; private HandshakePacket handshake; private final ConnectionInfo conInfo; private volatile boolean autocommit; private boolean authenticated; private volatile long lastTime; // QS_TODO private volatile String oldSchema; private volatile boolean borrowed = false; private volatile boolean modifiedSQLExecuted = false; private volatile boolean txSetCmdExecuted = false; 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 ChannelHandlerContext ctx; private Object attachment; 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 ChannelHandlerContext getCtx() { return ctx; } public void setCtx(ChannelHandlerContext ctx) { this.ctx = ctx; } public long getThreadId() { return conInfo.getThreadId(); } public ConnectionInfo getConInfo() { return conInfo; } public void setAutocommit(boolean autocommit) { this.autocommit = autocommit; } public void setThreadId(long threadId) { this.conInfo.setThreadId(threadId); } public HandshakePacket getHandshake() { return handshake; } public void setHandshake(HandshakePacket handshake) { this.handshake = handshake; } public boolean isFromeSlave() { return fromeSlave; } public boolean isBorrowed() { return borrowed; } public void setBorrowed(boolean borrowed) { this.borrowed = borrowed; } public BackMysqlConnection(ConnectionInfo conInfo, BackMySQLConnectionDataSource pool) { super(); this.conInfo = conInfo; this.fromeSlave = pool.isReadNode(); this.pool = pool; } public String getPassword() { return conInfo.getPassword(); } public void setPassword(String password) { this.conInfo.setPassword(password); } public String getUser() { return conInfo.getUser(); } public void setUser(String user) { this.conInfo.setUser(user); } public void setHost(String host) { this.conInfo.setHost(host); } public ResponseHandler getRespHandler() { return respHandler; } protected void sendQueryCmd(String query) { CommandPacket packet = new CommandPacket(); packet.packetId = 0; packet.command = MySQLPacket.COM_QUERY; try { packet.arg = query.getBytes(this.conInfo.getCharset()); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } lastTime = TimeUtil.currentTimeMillis(); this.writeCmdPkg(packet); } public void setResponseHandler(ResponseHandler respHandler) { this.respHandler = respHandler; } public boolean isAuthenticated() { return authenticated; } public void setPort(int port) { this.conInfo.setPort(port); } @Override public String getCharset() { return conInfo.getCharset(); } public void close(String reason) { if (!isClosed.get()) { ctx.close(); pool.connectionClosed(this); if (this.respHandler != null) { this.respHandler.connectionClose(this, reason); respHandler = null; } } } @Override public boolean isClosed() { return this.isClosed.get(); } @Override public void idleCheck() { // TODO Auto-generated method stub } @Override public long getStartupTime() { // TODO Auto-generated method stub return 0; } @Override public String getHost() { return conInfo.getHost(); } @Override public int getPort() { return conInfo.getPort(); } @Override public int getLocalPort() { return conInfo.getLocalPort(); } @Override public long getNetInBytes() { // TODO Auto-generated method stub return 0; } @Override public long getNetOutBytes() { // TODO Auto-generated method stub return 0; } @Override public boolean isModifiedSQLExecuted() { return this.modifiedSQLExecuted; } @Override public boolean isFromSlaveDB() { return this.fromeSlave; } @Override public String getSchema() { return conInfo.getSchema(); } @Override public void setSchema(String newSchema) { conInfo.setSchema(newSchema); } @Override public long getLastTime() { return this.lastTime; } @Override public void setAttachment(Object attachment) { this.attachment = attachment; } @Override public void setLastTime(long currentTimeMillis) { lastTime = currentTimeMillis; } @Override public Object getAttachment() { return attachment; } @Override public void recordSql(String host, String schema, String statement) { // TODO Auto-generated method stub } public void writeCmdPkg(CommandPacket cmdPkg) { ByteBuf buf = ctx.alloc().ioBuffer(cmdPkg.calcPacketSize() + 4); cmdPkg.write(buf); ctx.writeAndFlush(buf); // System.out.println("write query command "+cmdPkg); } @Override public boolean isAutocommit() { return autocommit; } @Override public long getId() { return conInfo.getId(); } public void query(String query) throws UnsupportedEncodingException { RouteResultsetNode rrn = new RouteResultsetNode("default", ServerParse.SELECT, query); StatusSync sync = new StatusSync(this, rrn, this.conInfo.getCharsetIndex(), this.conInfo.getTxIsolation(), true); doExecute(sync); } private void doExecute(StatusSync sync) { statusSync = sync; if (sync.isSync() || !sync.sync()) { sync.execute(); } } public void quit() { if (!this.isClosed()) { if (this.authenticated) { NettyUtil.writeBytes(ctx, QuitPacket.QUIT); // QS_TODO check ctx.writeAndFlush(ctx.alloc().ioBuffer(1)); } else { close("normal"); } } } /** * @return if synchronization finished and execute-sql has already been sent * before */ public boolean syncAndExcute() { StatusSync sync = statusSync; if (sync.isExecuted()) { return true; } if (sync.isSync()) { sync.update(); sync.execute(); } else { sync.update(); sync.sync(); } return false; } public void execute(RouteResultsetNode rrn, FrontSession session, boolean autocommit) throws UnsupportedEncodingException { if (!modifiedSQLExecuted && rrn.isModifySQL()) { modifiedSQLExecuted = true; } StatusSync sync = new StatusSync(this, rrn, this.conInfo.getCharsetIndex(), session.getConInfo() .getTxIsolation(), autocommit); doExecute(sync); } public void commit() { ByteBuf buf = ctx.alloc().ioBuffer(128); _COMMIT.write(buf); ctx.writeAndFlush(buf); txSetCmdExecuted = false; } public void rollback() { ByteBuf buf = ctx.alloc().ioBuffer(128); _ROLLBACK.write(buf); ctx.writeAndFlush(buf); txSetCmdExecuted = false; } public void release() { attachment = null; statusSync = null; modifiedSQLExecuted = false; setResponseHandler(null); pool.releaseChannel(this); txSetCmdExecuted = false; } public void setAuthenticated(boolean b) { authenticated = b; } public BackMySQLConnectionDataSource getPool() { return pool; } private static class StatusSync { private final RouteResultsetNode rrn; private final BackMysqlConnection conn; private CommandPacket schemaCmd; private CommandPacket charCmd; private CommandPacket isoCmd; private CommandPacket acCmd; private final String schema; private final int charIndex; private final int txIsolation; private final boolean autocommit; private volatile boolean executed; public StatusSync(BackMysqlConnection conn, RouteResultsetNode rrn, int scCharIndex, int scTxtIsolation, boolean autocommit) { this.conn = conn; this.rrn = rrn; this.charIndex = scCharIndex; this.schema = conn.getConInfo().getSchema(); this.schemaCmd = !schema.equals(conn.oldSchema) ? getChangeSchemaCommand(schema) : null; this.charCmd = conn.getConInfo().getCharsetIndex() != charIndex ? getCharsetCommand(charIndex) : null; this.txIsolation = scTxtIsolation; this.isoCmd = conn.getConInfo().getTxIsolation() != txIsolation ? getTxIsolationCommand(txIsolation) : null; if (!conn.modifiedSQLExecuted || conn.isFromSlaveDB()) { // never executed modify sql,so auto commit this.autocommit = true; } else { this.autocommit = autocommit; } if (this.autocommit) { this.acCmd = (conn.autocommit == true) ? null : _AUTOCOMMIT_ON; } else {// transaction if (!conn.txSetCmdExecuted) { this.acCmd = _AUTOCOMMIT_OFF; } } if (LOGGER.isDebugEnabled()) { StringBuilder inf = new StringBuilder(); if (schemaCmd != null) { inf.append(" need syn schemaCmd " + schemaCmd + "\r\n"); } if (charCmd != null) { inf.append(" need syn charCmd " + charCmd + "\r\n"); } if (isoCmd != null) { inf.append(" need syn txIsolationCmd " + isoCmd + "\r\n"); } if (acCmd != null) { inf.append(" need syn autcommitCmd " + acCmd + "\r\n"); } if (inf.length() > 0) { LOGGER.debug(this.conn + "\r\n" + inf); } } } private Runnable updater; public boolean isExecuted() { return executed; } public boolean isSync() { return schemaCmd == null && charCmd == null && isoCmd == null && acCmd == null; } public void update() { Runnable updater = this.updater; if (updater != null) { updater.run(); } } /** * @return false if sync complete */ public boolean sync() { CommandPacket cmd; if (schemaCmd != null) { conn.conInfo.setSchema("snyn..."); updater = new Runnable() { @Override public void run() { conn.conInfo.setSchema(schema); conn.oldSchema = schema; } }; cmd = schemaCmd; schemaCmd = null; conn.writeCmdPkg(cmd); // System.out.println("syn schema "+conn+" schema "+schema); return true; } if (charCmd != null) { updater = new Runnable() { @Override public void run() { int ci = StatusSync.this.charIndex; conn.getConInfo().setCharsetIndex(ci); } }; cmd = charCmd; charCmd = null; conn.writeCmdPkg(cmd); // System.out.println("syn charCmd "+conn); return true; } if (isoCmd != null) { updater = new Runnable() { @Override public void run() { conn.getConInfo().setTxIsolation( StatusSync.this.txIsolation); } }; cmd = isoCmd; isoCmd = null; conn.writeCmdPkg(cmd); // System.out.println("syn iso "+conn); return true; } if (acCmd != null) { conn.autocommit = conn.oldAutoCommit; updater = new Runnable() { @Override public void run() { conn.autocommit = StatusSync.this.autocommit; conn.oldAutoCommit = autocommit; if (StatusSync.this.autocommit == false) { conn.txSetCmdExecuted = true; } } }; cmd = acCmd; acCmd = null; conn.writeCmdPkg(cmd); // System.out.println("syn autocomit "+conn); return true; } return false; } public void execute() { executed = true; if (rrn.getStatement() != null) { conn.sendQueryCmd(rrn.getStatement()); } } @Override public String toString() { return "StatusSync [schemaCmd=" + schemaCmd + ", charCmd=" + charCmd + ", isoCmd=" + isoCmd + ", acCmd=" + acCmd + ", executed=" + executed + "]"; } private static CommandPacket getTxIsolationCommand(int txIsolation) { switch (txIsolation) { case Isolations.READ_UNCOMMITTED: return _READ_UNCOMMITTED; case Isolations.READ_COMMITTED: return _READ_COMMITTED; case Isolations.REPEATED_READ: return _REPEATED_READ; case Isolations.SERIALIZABLE: return _SERIALIZABLE; default: throw new UnknownTxIsolationException("txIsolation:" + txIsolation); } } private static CommandPacket getCharsetCommand(int ci) { String charset = CharsetUtil.getCharset(ci); StringBuilder s = new StringBuilder(); s.append("SET names ").append(charset); CommandPacket cmd = new CommandPacket(); cmd.packetId = 0; cmd.command = MySQLPacket.COM_QUERY; cmd.arg = s.toString().getBytes(); return cmd; } private static CommandPacket getChangeSchemaCommand(String schema) { StringBuilder s = new StringBuilder(); s.append(schema); CommandPacket cmd = new CommandPacket(); cmd.packetId = 0; cmd.command = MySQLPacket.COM_INIT_DB; cmd.arg = s.toString().getBytes(); return cmd; } } @Override public String toString() { return "BackMysqlConnection [ conInfo=" + conInfo+",respHandler=" + respHandler + ", fromeSlave=" + fromeSlave + ", autocommit=" + autocommit + ", authenticated=" + authenticated + ", borrowed=" + borrowed + ", modifiedSQLExecuted=" + modifiedSQLExecuted + "]"; } }