/* * Copyright (c) 2013, OpenCloudDB/MyCAT and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software;Designed and Developed mainly by many Chinese * opensource volunteers. you can redistribute it and/or modify it under the * terms of the GNU General Public License version 2 only, as published by the * Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Any questions about this component can be directed to it's project Web address * https://code.google.com/p/opencloudb/. * */ package org.opencloudb.mysql.nio; import java.io.UnsupportedEncodingException; import java.nio.channels.AsynchronousSocketChannel; import java.security.NoSuchAlgorithmException; import java.util.concurrent.atomic.AtomicBoolean; import org.apache.log4j.Logger; import org.opencloudb.config.Capabilities; import org.opencloudb.config.ErrorCode; import org.opencloudb.config.Isolations; import org.opencloudb.exception.UnknownTxIsolationException; import org.opencloudb.mysql.CharsetUtil; import org.opencloudb.mysql.SecurityUtil; import org.opencloudb.mysql.nio.handler.ResponseHandler; import org.opencloudb.net.BackendAIOConnection; import org.opencloudb.net.mysql.AuthPacket; import org.opencloudb.net.mysql.CommandPacket; import org.opencloudb.net.mysql.HandshakePacket; import org.opencloudb.net.mysql.MySQLPacket; import org.opencloudb.net.mysql.QuitPacket; import org.opencloudb.route.RouteResultsetNode; import org.opencloudb.server.ServerConnection; import org.opencloudb.server.parser.ServerParse; import org.opencloudb.util.TimeUtil; /** * @author mycat */ public class MySQLConnection extends BackendAIOConnection { private static final Logger LOGGER = Logger .getLogger(MySQLConnection.class); private static final long CLIENT_FLAGS = initClientFlags(); private final AtomicBoolean isRunning = new AtomicBoolean(); private volatile long lastTime; // QS_TODO private volatile String schema = ""; private volatile String oldSchema; private volatile boolean borrowed = false; private volatile boolean modifiedSQLExecuted = false; 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; // 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; } 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(); 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(); } private MySQLDataSource pool; private boolean fromSlaveDB; private long threadId; private HandshakePacket handshake; private volatile int charsetIndex; private volatile int oldCharsetIndex; private volatile String charset; private volatile String oldCharset; private volatile int txIsolation; private volatile int oldTxIsolation; private volatile boolean autocommit; private volatile boolean oldAutoCommit; private long clientFlags; private boolean isAuthenticated; private String user; private String password; private Object attachment; private ResponseHandler respHandler; private final AtomicBoolean isQuit; private volatile StatusSync statusSync; public MySQLConnection(AsynchronousSocketChannel 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 void onConnectFailed(Throwable e) { this.error(ErrorCode.ERR_CONNECT_SOCKET, e); } public String getSchema() { return this.schema; } public void setSchema(String newSchema) { this.oldSchema = schema; this.schema = newSchema; } public MySQLDataSource getPool() { return pool; } public void setPool(MySQLDataSource pool) { this.pool = pool; } public String getUser() { return user; } public void setUser(String user) { this.user = user; } public void setPassword(String password) { this.password = password; } public HandshakePacket getHandshake() { return handshake; } public void setHandshake(HandshakePacket handshake) { this.handshake = handshake; } public void setCharsetIndex(int charsetIndex) { this.charsetIndex = charsetIndex; this.oldCharsetIndex = charsetIndex; } public long getThreadId() { return threadId; } public void setThreadId(long threadId) { this.threadId = threadId; } public String getCharset() { return charset; } public void setCharset(String charset) { this.charset = charset; this.oldCharset = charset; } public boolean isAuthenticated() { return isAuthenticated; } public void setAuthenticated(boolean isAuthenticated) { this.isAuthenticated = isAuthenticated; } public String getPassword() { return password; } public void authenticate() { AuthPacket packet = new AuthPacket(); packet.packetId = 1; packet.clientFlags = clientFlags; packet.maxPacketSize = maxPacketSize; packet.charsetIndex = charsetIndex; packet.user = user; try { packet.password = passwd(password, handshake); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e.getMessage()); } packet.database = schema; packet.write(this); } public void setRunning(boolean running) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("set running " + running + " for " + this); } isRunning.set(running); } public boolean isRunning() { return isRunning.get(); } 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 class StatusSync { private final RouteResultsetNode rrn; private final MySQLConnection 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(MySQLConnection conn, RouteResultsetNode rrn, int scCharIndex, int scTxtIsolation, boolean autocommit) { this.conn = conn; this.rrn = rrn; this.charIndex = scCharIndex; this.schema = conn.schema; this.schemaCmd = !schema.equals(conn.oldSchema) ? getChangeSchemaCommand(schema) : null; this.charCmd = conn.charsetIndex != charIndex ? getCharsetCommand(charIndex) : null; this.txIsolation = scTxtIsolation; this.isoCmd = conn.txIsolation != txIsolation ? getTxIsolationCommand(txIsolation) : null; if (!conn.modifiedSQLExecuted || conn.isFromSlaveDB()) { // never executed modify sql,so auto commit this.autocommit = true; } else { this.autocommit = autocommit; } this.acCmd = conn.autocommit != this.autocommit ? (autocommit ? _AUTOCOMMIT_ON : _AUTOCOMMIT_OFF) : null; 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.schema = "snyn..."; updater = new Runnable() { @Override public void run() { conn.schema = schema; conn.oldSchema = conn.schema; } }; cmd = schemaCmd; schemaCmd = null; cmd.write(conn); // System.out.println("syn schema "+conn+" schema "+schema); return true; } if (charCmd != null) { conn.charsetIndex = conn.oldCharsetIndex; conn.charset = conn.oldCharset; updater = new Runnable() { @Override public void run() { int ci = StatusSync.this.charIndex; conn.charsetIndex = ci; conn.charset = CharsetUtil.getCharset(ci); conn.oldCharsetIndex = ci; conn.oldCharset = CharsetUtil.getCharset(ci); } }; cmd = charCmd; charCmd = null; cmd.write(conn); // System.out.println("syn charCmd "+conn); return true; } if (isoCmd != null) { conn.txIsolation = conn.oldTxIsolation; updater = new Runnable() { @Override public void run() { conn.txIsolation = StatusSync.this.txIsolation; conn.oldTxIsolation = conn.txIsolation; } }; cmd = isoCmd; isoCmd = null; cmd.write(conn); // 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; } }; cmd = acCmd; acCmd = null; cmd.write( conn); // 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; } } /** * @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, ServerConnection sc, boolean autocommit) throws UnsupportedEncodingException { if (!modifiedSQLExecuted && rrn.isModifySQL()) { modifiedSQLExecuted = true; } StatusSync sync = new StatusSync(this, rrn, sc.getCharsetIndex(), sc.getTxIsolation(), autocommit); doExecute(sync); } /** * 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); StatusSync sync = new StatusSync(this, rrn, this.charsetIndex, this.txIsolation, true); doExecute(sync); } private void doExecute(StatusSync sync) { statusSync = sync; if (sync.isSync() || !sync.sync()) { sync.execute(); } } public long getLastTime() { return lastTime; } public void setLastTime(long lastTime) { this.lastTime = lastTime; } public void quit() { if (isQuit.compareAndSet(false, true) && !isClosed()) { if (isAuthenticated) { // QS_TODO check write(writeToBuffer(QuitPacket.QUIT, allocate())); write(allocate()); } else { close("normal"); } } } @Override public void close(String reason) { isQuit.set(true); super.close(reason); if (isClosed.get()) { if (this.respHandler != null) { this.respHandler.connectionClose(this, reason); respHandler = null; } } } public void commit() { _COMMIT.write(this); } public void rollback() { _ROLLBACK.write(this); } public void release() { attachment = null; statusSync = null; modifiedSQLExecuted = false; setResponseHandler(null); pool.releaseChannel(this); } @Override public void error(int errCode, Throwable t) { switch (errCode) { case ErrorCode.ERR_HANDLE_DATA: LOGGER.warn(" error code: " + errCode + " exception: " + t + " con: " + this, t); break; case ErrorCode.ERR_PUT_WRITE_QUEUE: LOGGER.warn(" error code: " + errCode + " exception: " + t + " con: " + this, t); break; case ErrorCode.ERR_CONNECT_SOCKET: if (handler == null) { LOGGER.warn(" error code: " + errCode + " exception: " + t + " con: " + this); return; } if (handler instanceof MySQLConnectionHandler) { MySQLConnectionHandler theHandler = (MySQLConnectionHandler) handler; theHandler.connectionError(t); } else { ((MySQLConnectionAuthenticator) handler).connectionError(this, t); } break; } } public boolean setResponseHandler(ResponseHandler queryHandler) { if (handler instanceof MySQLConnectionHandler) { ((MySQLConnectionHandler) handler).setResponseHandler(queryHandler); respHandler = queryHandler; return true; } else if (queryHandler != null) { LOGGER.warn("set not MySQLConnectionHandler " + queryHandler.getClass().getCanonicalName()); } return false; } /** * 写队列为空,可以继续写数据 */ public void writeQueueAvailable() { if (respHandler != null) { respHandler.writeQueueAvailable(); } } /** * 记录sql执行信息 */ public void recordSql(String host, String schema, String stmt) { final long now = TimeUtil.currentTimeMillis(); if (now > this.lastTime) { // long time = now - this.lastTime; // SQLRecorder sqlRecorder = this.pool.getSqlRecorder(); // if (sqlRecorder.check(time)) { // SQLRecord recorder = new SQLRecord(); // recorder.host = host; // recorder.schema = schema; // recorder.statement = stmt; // recorder.startTime = lastTime; // recorder.executeTime = time; // recorder.dataNode = pool.getName(); // recorder.dataNodeIndex = pool.getIndex(); // sqlRecorder.add(recorder); // } } this.lastTime = now; } private static byte[] passwd(String pass, HandshakePacket hs) throws NoSuchAlgorithmException { if (pass == null || pass.length() == 0) { return null; } byte[] passwd = pass.getBytes(); int sl1 = hs.seed.length; int sl2 = hs.restOfScrambleBuff.length; byte[] seed = new byte[sl1 + sl2]; System.arraycopy(hs.seed, 0, seed, 0, sl1); System.arraycopy(hs.restOfScrambleBuff, 0, seed, sl1, sl2); return SecurityUtil.scramble411(passwd, seed); } @Override public boolean isFromSlaveDB() { return fromSlaveDB; } @Override public boolean isBorrowed() { return borrowed; } @Override public void setBorrowed(boolean borrowed) { this.borrowed = borrowed; } @Override public String toString() { return "MySQLConnection [id=" + id + ", isRunning=" + isRunning + ", lastTime=" + lastTime + ", schema=" + schema + ", 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() == null) ? 0 : getWriteQueue() .snapshotSize()) + ", modifiedSQLExecuted=" + modifiedSQLExecuted + "]"; } @Override public boolean isModifiedSQLExecuted() { return modifiedSQLExecuted; } @Override public int getTxIsolation() { return txIsolation; } }