diff --git a/src/main/java/com/actiontech/dble/backend/mysql/ByteUtil.java b/src/main/java/com/actiontech/dble/backend/mysql/ByteUtil.java index b29b7c7fa..fc3267864 100644 --- a/src/main/java/com/actiontech/dble/backend/mysql/ByteUtil.java +++ b/src/main/java/com/actiontech/dble/backend/mysql/ByteUtil.java @@ -5,6 +5,8 @@ */ package com.actiontech.dble.backend.mysql; +import java.nio.ByteBuffer; + /** * @author mycat */ @@ -106,4 +108,17 @@ public final class ByteUtil { public static void writeUB3(byte[] packet, int length) { writeUB3(packet, length, 0); } + + public static void writeUB3(ByteBuffer buffer, int val, int offset) { + buffer.put(offset, (byte) (val & 0xff)); + buffer.put(offset + 1, (byte) (val >>> 8)); + buffer.put(offset + 2, (byte) (val >>> 16)); + } + + public static void writeUB4(byte[] packet, long l, int offset) { + packet[offset] = (byte) (l & 0xff); + packet[offset + 1] = (byte) (l >>> 8); + packet[offset + 2] = (byte) (l >>> 16); + packet[offset + 3] = (byte) (l >>> 24); + } } diff --git a/src/main/java/com/actiontech/dble/net/handler/BackEndRecycleRunnable.java b/src/main/java/com/actiontech/dble/net/handler/BackEndRecycleRunnable.java index 5ddae94c7..08c2d25cd 100644 --- a/src/main/java/com/actiontech/dble/net/handler/BackEndRecycleRunnable.java +++ b/src/main/java/com/actiontech/dble/net/handler/BackEndRecycleRunnable.java @@ -66,6 +66,7 @@ public class BackEndRecycleRunnable implements Runnable, BackEndCleaner { lock.lock(); try { service.setRowDataFlowing(false); + service.setPrepare(false); condRelease.signal(); } finally { lock.unlock(); diff --git a/src/main/java/com/actiontech/dble/net/mysql/PreparedClosePacket.java b/src/main/java/com/actiontech/dble/net/mysql/PreparedClosePacket.java new file mode 100644 index 000000000..bd44cf072 --- /dev/null +++ b/src/main/java/com/actiontech/dble/net/mysql/PreparedClosePacket.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2016-2022 ActionTech. + * based on code by MyCATCopyrightHolder Copyright (c) 2013, OpenCloudDB/MyCAT. + * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher. + */ +package com.actiontech.dble.net.mysql; + +import com.actiontech.dble.backend.mysql.BufferUtil; +import com.actiontech.dble.net.connection.AbstractConnection; +import com.actiontech.dble.net.service.AbstractService; + +import java.nio.ByteBuffer; + +/** + *
+ * COM_STMT_CLOSE deallocates a prepared statement + * + * No response is sent back to the client. + * + * COM_STMT_CLOSE: + * Bytes Name + * ----- ---- + * 1 [19] COM_STMT_CLOSE + * 4 statement-id + * + * @see https://dev.mysql.com/doc/internals/en/com-stmt-close.html + *+ * + * @author collapsar + */ +public class PreparedClosePacket extends MySQLPacket { + + private long statementId; + + public PreparedClosePacket(long statementId) { + this.statementId = statementId; + } + + @Override + public ByteBuffer write(ByteBuffer buffer, AbstractService service, boolean writeSocketIfFull) { + int size = calcPacketSize(); + buffer = service.checkWriteBuffer(buffer, PACKET_HEADER_SIZE + size, writeSocketIfFull); + BufferUtil.writeUB3(buffer, size); + buffer.put(packetId); + buffer.put((byte) 0x19); + BufferUtil.writeUB4(buffer, statementId); + return buffer; + } + + @Override + public void bufferWrite(AbstractConnection connection) { + int size = calcPacketSize(); + ByteBuffer buffer = connection.allocate(PACKET_HEADER_SIZE + size); + BufferUtil.writeUB3(buffer, size); + buffer.put(packetId); + buffer.put((byte) 0x19); + BufferUtil.writeUB4(buffer, statementId); + connection.write(buffer); + } + + @Override + public int calcPacketSize() { + return 5; + } + + @Override + protected String getPacketInfo() { + return "MySQL Prepared Close Packet"; + } + + @Override + public boolean isEndOfQuery() { + return true; + } + + public long getStatementId() { + return statementId; + } + + public void setStatementId(long statementId) { + this.statementId = statementId; + } + +} diff --git a/src/main/java/com/actiontech/dble/rwsplit/RWSplitNonBlockingSession.java b/src/main/java/com/actiontech/dble/rwsplit/RWSplitNonBlockingSession.java index beb87724a..6b12e7b8e 100644 --- a/src/main/java/com/actiontech/dble/rwsplit/RWSplitNonBlockingSession.java +++ b/src/main/java/com/actiontech/dble/rwsplit/RWSplitNonBlockingSession.java @@ -2,11 +2,11 @@ package com.actiontech.dble.rwsplit; import com.actiontech.dble.backend.datasource.PhysicalDbGroup; import com.actiontech.dble.backend.datasource.PhysicalDbInstance; +import com.actiontech.dble.backend.mysql.ByteUtil; import com.actiontech.dble.config.ErrorCode; import com.actiontech.dble.net.connection.BackendConnection; -import com.actiontech.dble.services.rwsplit.Callback; -import com.actiontech.dble.services.rwsplit.RWSplitHandler; -import com.actiontech.dble.services.rwsplit.RWSplitService; +import com.actiontech.dble.net.mysql.MySQLPacket; +import com.actiontech.dble.services.rwsplit.*; import com.actiontech.dble.singleton.RouteService; import com.actiontech.dble.util.StringUtil; import org.jetbrains.annotations.Nullable; @@ -72,12 +72,23 @@ public class RWSplitNonBlockingSession { } @Nullable - private RWSplitHandler getRwSplitHandler(byte[] originPacket, Callback callback) throws SQLSyntaxErrorException { + private RWSplitHandler getRwSplitHandler(byte[] originPacket, Callback callback) throws SQLSyntaxErrorException, IOException { RWSplitHandler handler = new RWSplitHandler(rwSplitService, originPacket, callback, false); if (conn != null && !conn.isClosed()) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("select bind conn[id={}]", conn.getId()); } + // for ps needs to send master + if ((originPacket != null && originPacket.length > 4 && originPacket[4] == MySQLPacket.COM_STMT_EXECUTE)) { + long statementId = ByteUtil.readUB4(originPacket, 5); + PreparedStatementHolder holder = rwSplitService.getPrepareStatement(statementId); + if (holder.isMustMaster() && conn.getInstance().isReadInstance()) { + holder.setExecuteOrigin(originPacket); + PSHandler psHandler = new PSHandler(rwSplitService, holder); + psHandler.execute(rwGroup); + return null; + } + } checkDest(!conn.getInstance().isReadInstance()); handler.execute(conn); return null; @@ -170,7 +181,7 @@ public class RWSplitNonBlockingSession { public void unbindIfSafe() { if (rwSplitService.isAutocommit() && !rwSplitService.isTxStart() && !rwSplitService.isLocked() && !rwSplitService.isInLoadData() && - !rwSplitService.isInPrepare() && this.conn != null) { + !rwSplitService.isInPrepare() && this.conn != null && rwSplitService.getPsHolder().isEmpty()) { this.conn.release(); this.conn = null; } diff --git a/src/main/java/com/actiontech/dble/services/BusinessService.java b/src/main/java/com/actiontech/dble/services/BusinessService.java index 07b6ec560..06c2074a8 100644 --- a/src/main/java/com/actiontech/dble/services/BusinessService.java +++ b/src/main/java/com/actiontech/dble/services/BusinessService.java @@ -15,6 +15,8 @@ public abstract class BusinessService extends FrontEndService { protected volatile boolean txStarted; protected final AtomicLong queriesCounter = new AtomicLong(0); protected final AtomicLong transactionsCounter = new AtomicLong(0); + private volatile boolean isLockTable; + public BusinessService(AbstractConnection connection) { super(connection); @@ -61,6 +63,15 @@ public abstract class BusinessService extends FrontEndService { transactionsCounter.set(Long.MIN_VALUE); } + public boolean isLockTable() { + return isLockTable; + } + + public void setLockTable(boolean locked) { + isLockTable = locked; + } + + public void executeContextSetTask(MysqlVariable[] contextTask) { MysqlVariable autocommitItem = null; diff --git a/src/main/java/com/actiontech/dble/services/mysqlsharding/MySQLResponseService.java b/src/main/java/com/actiontech/dble/services/mysqlsharding/MySQLResponseService.java index 315cdddec..ae1c596da 100644 --- a/src/main/java/com/actiontech/dble/services/mysqlsharding/MySQLResponseService.java +++ b/src/main/java/com/actiontech/dble/services/mysqlsharding/MySQLResponseService.java @@ -37,6 +37,7 @@ import org.slf4j.LoggerFactory; import java.io.UnsupportedEncodingException; import java.math.BigDecimal; +import java.nio.ByteBuffer; import java.util.*; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.Executor; @@ -86,6 +87,9 @@ public class MySQLResponseService extends VariablesService { protected BackendConnection connection; + private volatile boolean prepare = false; + + static { COMMIT.setPacketId(0); COMMIT.setCommand(MySQLPacket.COM_QUERY); @@ -147,7 +151,9 @@ public class MySQLResponseService extends VariablesService { } return; } - if (prepareOK) { + if (prepareOK && prepare) { + baseLogicHandler.handleInnerData(data); + } else if (prepareOK) { prepareLogicHandler.handleInnerData(data); } else if (statisticResponse) { statisticsLogicHandler.handleInnerData(data); @@ -537,6 +543,7 @@ public class MySQLResponseService extends VariablesService { statusSync = null; isDDL = false; testing = false; + setPrepare(false); setResponseHandler(null); setSession(null); logResponse.set(false); @@ -647,6 +654,18 @@ public class MySQLResponseService extends VariablesService { } } + // only for com_stmt_execute + public void execute(ByteBuffer buffer) { + setPrepare(true); + writeDirectly(buffer); + } + + // only for com_stmt_execute + public void execute(byte[] originPacket) { + setPrepare(true); + writeDirectly(originPacket); + } + // the purpose is to set old schema to null private void changeUser() { DbInstanceConfig config = connection.getInstance().getConfig(); @@ -850,6 +869,10 @@ public class MySQLResponseService extends VariablesService { isExecuting = executing; } + public void setPrepare(boolean prepare) { + this.prepare = prepare; + } + public String toString() { return "MySQLResponseService[isExecuting = " + isExecuting + " attachment = " + attachment + " autocommitSynced = " + autocommitSynced + " isolationSynced = " + isolationSynced + " xaStatus = " + xaStatus + " isDDL = " + isDDL + " complexQuery = " + complexQuery + "] with response handler [" + responseHandler + "] with rrs = [" + diff --git a/src/main/java/com/actiontech/dble/services/rwsplit/PSHandler.java b/src/main/java/com/actiontech/dble/services/rwsplit/PSHandler.java new file mode 100644 index 000000000..829be817d --- /dev/null +++ b/src/main/java/com/actiontech/dble/services/rwsplit/PSHandler.java @@ -0,0 +1,137 @@ +package com.actiontech.dble.services.rwsplit; + +import com.actiontech.dble.backend.datasource.PhysicalDbGroup; +import com.actiontech.dble.backend.datasource.PhysicalDbInstance; +import com.actiontech.dble.backend.mysql.ByteUtil; +import com.actiontech.dble.backend.mysql.nio.handler.PreparedResponseHandler; +import com.actiontech.dble.backend.mysql.nio.handler.ResponseHandler; +import com.actiontech.dble.config.ErrorCode; +import com.actiontech.dble.net.connection.BackendConnection; +import com.actiontech.dble.net.mysql.FieldPacket; +import com.actiontech.dble.net.mysql.PreparedClosePacket; +import com.actiontech.dble.net.mysql.RowDataPacket; +import com.actiontech.dble.net.service.AbstractService; +import com.actiontech.dble.services.mysqlsharding.MySQLResponseService; +import com.actiontech.dble.net.connection.AbstractConnection; + + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.List; + +public class PSHandler implements ResponseHandler, PreparedResponseHandler { + + private final RWSplitService rwSplitService; + private final AbstractConnection frontedConnection; + private final PreparedStatementHolder holder; + private boolean write2Client = false; + private long originStatementId; + + public PSHandler(RWSplitService service, PreparedStatementHolder holder) { + this.rwSplitService = service; + this.frontedConnection = service.getConnection(); + this.holder = holder; + } + + public void execute(PhysicalDbGroup rwGroup) throws IOException { + PhysicalDbInstance instance = rwGroup.select(true, true); + instance.getConnection(rwSplitService.getSchema(), this, null, true); + } + + @Override + public void connectionAcquired(BackendConnection conn) { + MySQLResponseService mysqlService = conn.getBackendService(); + mysqlService.setResponseHandler(this); + mysqlService.execute(rwSplitService, holder.getPrepareOrigin()); + } + + @Override + public void preparedOkResponse(byte[] ok, List