This is an automated email from the ASF dual-hosted git repository.
terrymanu pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/shardingsphere.git
The following commit(s) were added to refs/heads/master by this push:
new 6997e3926a0 Fix MySQL COM_QUERY binary string literal corruption
(#39433)
6997e3926a0 is described below
commit 6997e3926a0303decf8c02d28676d28929254ff2
Author: Liang Zhang <[email protected]>
AuthorDate: Thu Aug 13 16:30:37 2026 +0800
Fix MySQL COM_QUERY binary string literal corruption (#39433)
* Refactor MySQL COM_QUERY backend handler creation
- move SQL parsing and handler selection into a dedicated factory
- keep the packet executor focused on response processing
- separate factory and executor unit tests
* Refactor MySQL COM_QUERY backend handler creation
- move SQL parsing and handler selection into a dedicated factory
- keep the packet executor focused on response processing
- separate factory and executor unit tests
* Refactor MySQL COM_QUERY backend handler creation
- move SQL parsing and handler selection into a dedicated factory
- keep the packet executor focused on response processing
- separate factory and executor unit tests
* Refactor MySQL COM_QUERY backend handler creation
- move SQL parsing and handler selection into a dedicated factory
- keep the packet executor focused on response processing
- separate factory and executor unit tests
---
RELEASE-NOTES.md | 1 +
.../query/text/query/MySQLComQueryPacket.java | 32 +-
.../query/text/query/MySQLComQueryPacketTest.java | 26 +-
.../query/MySQLComQueryBackendHandlerFactory.java | 117 +++++++
.../MySQLComQueryBinaryParameterExtractor.java | 350 +++++++++++++++++++++
.../text/query/MySQLComQueryPacketExecutor.java | 35 +--
.../command/MySQLCommandPacketFactoryTest.java | 5 +-
...=> MySQLComQueryBackendHandlerFactoryTest.java} | 177 ++++++-----
.../MySQLComQueryBinaryParameterExtractorTest.java | 128 ++++++++
.../query/MySQLComQueryPacketExecutorTest.java | 101 +-----
10 files changed, 760 insertions(+), 212 deletions(-)
diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md
index 5e0af8310b6..39655a7baba 100644
--- a/RELEASE-NOTES.md
+++ b/RELEASE-NOTES.md
@@ -54,6 +54,7 @@
1. Proxy: Fix incorrect openGauss composite column type OIDs in simple and
extended query row descriptions -
[#39253](https://github.com/apache/shardingsphere/pull/39253)
1. Proxy: Fix MySQL CLOB and NCLOB result set handling -
[#39338](https://github.com/apache/shardingsphere/pull/39338)
1. Proxy: Fix MySQL BLOB result set handling in text and binary protocols -
[#39340](https://github.com/apache/shardingsphere/pull/39340)
+1. Proxy: Fix MySQL COM_QUERY binary string literal corruption -
[#39433](https://github.com/apache/shardingsphere/pull/39433)
1. JDBC & Proxy: Remove default MySQL prepared statement query properties when
creating data sources -
[#38593](https://github.com/apache/shardingsphere/pull/38593)
1. Mode: Fix rule metadata not removed from memory after dropping rules in
Etcd cluster mode -
[#38561](https://github.com/apache/shardingsphere/pull/38561)
1. Agent: Fix wrong target class name in StaticMethodAdviceExecutor error logs
- [#39077](https://github.com/apache/shardingsphere/pull/39077)
diff --git
a/database/protocol/dialect/mysql/src/main/java/org/apache/shardingsphere/database/protocol/mysql/packet/command/query/text/query/MySQLComQueryPacket.java
b/database/protocol/dialect/mysql/src/main/java/org/apache/shardingsphere/database/protocol/mysql/packet/command/query/text/query/MySQLComQueryPacket.java
index 46dfbfc0f5a..f60e753954c 100644
---
a/database/protocol/dialect/mysql/src/main/java/org/apache/shardingsphere/database/protocol/mysql/packet/command/query/text/query/MySQLComQueryPacket.java
+++
b/database/protocol/dialect/mysql/src/main/java/org/apache/shardingsphere/database/protocol/mysql/packet/command/query/text/query/MySQLComQueryPacket.java
@@ -25,6 +25,8 @@ import
org.apache.shardingsphere.database.protocol.packet.sql.SQLReceivedPacket;
import org.apache.shardingsphere.infra.hint.HintValueContext;
import org.apache.shardingsphere.infra.hint.SQLHintUtils;
+import java.util.Optional;
+
/**
* COM_QUERY command packet for MySQL.
*
@@ -32,8 +34,14 @@ import org.apache.shardingsphere.infra.hint.SQLHintUtils;
*/
public final class MySQLComQueryPacket extends MySQLCommandPacket implements
SQLReceivedPacket {
+ private static final String BINARY_INTRODUCER = "_binary";
+
+ private static final char MALFORMED_INPUT_REPLACEMENT = (char) 0xFFFD;
+
private final String sql;
+ private final byte[] originalSQLBytes;
+
@Getter
private final HintValueContext hintValueContext;
@@ -41,13 +49,26 @@ public final class MySQLComQueryPacket extends
MySQLCommandPacket implements SQL
super(MySQLCommandPacketType.COM_QUERY);
hintValueContext = SQLHintUtils.extractHint(sql);
this.sql = SQLHintUtils.removeHint(sql);
+ originalSQLBytes = null;
}
public MySQLComQueryPacket(final MySQLPacketPayload payload) {
super(MySQLCommandPacketType.COM_QUERY);
- String originSQL = payload.readStringEOF();
+ byte[] sqlBytes = payload.readStringEOFByBytes();
+ String originSQL = new String(sqlBytes, payload.getCharset());
hintValueContext = SQLHintUtils.extractHint(originSQL);
sql = SQLHintUtils.removeHint(originSQL);
+ originalSQLBytes = requiresBinaryLiteralInspection(originSQL) ?
sqlBytes : null;
+ }
+
+ private static boolean requiresBinaryLiteralInspection(final String sql) {
+ for (int i = 0; i < sql.length(); i++) {
+ char current = sql.charAt(i);
+ if (MALFORMED_INPUT_REPLACEMENT == current || '_' == current &&
sql.regionMatches(true, i, BINARY_INTRODUCER, 0, BINARY_INTRODUCER.length())) {
+ return true;
+ }
+ }
+ return false;
}
@Override
@@ -59,4 +80,13 @@ public final class MySQLComQueryPacket extends
MySQLCommandPacket implements SQL
public String getSQL() {
return sql;
}
+
+ /**
+ * Find original SQL bytes retained for binary literal inspection.
+ *
+ * @return original SQL bytes when binary literal inspection is required
+ */
+ public Optional<byte[]> findOriginalSQLBytes() {
+ return Optional.ofNullable(originalSQLBytes);
+ }
}
diff --git
a/database/protocol/dialect/mysql/src/test/java/org/apache/shardingsphere/database/protocol/mysql/packet/command/query/text/query/MySQLComQueryPacketTest.java
b/database/protocol/dialect/mysql/src/test/java/org/apache/shardingsphere/database/protocol/mysql/packet/command/query/text/query/MySQLComQueryPacketTest.java
index 5fea6f5f085..70ea2448140 100644
---
a/database/protocol/dialect/mysql/src/test/java/org/apache/shardingsphere/database/protocol/mysql/packet/command/query/text/query/MySQLComQueryPacketTest.java
+++
b/database/protocol/dialect/mysql/src/test/java/org/apache/shardingsphere/database/protocol/mysql/packet/command/query/text/query/MySQLComQueryPacketTest.java
@@ -27,10 +27,13 @@ import org.junit.jupiter.params.provider.MethodSource;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
+import java.nio.charset.StandardCharsets;
import java.util.stream.Stream;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -46,15 +49,36 @@ class MySQLComQueryPacketTest {
MySQLComQueryPacket actual = new MySQLComQueryPacket(inputSQL);
assertThat(actual.getSQL(), is(expectedSQL));
assertThat(actual.getHintValueContext().isWriteRouteOnly(),
is(expectedWriteRouteOnly));
+ assertFalse(actual.findOriginalSQLBytes().isPresent());
}
@ParameterizedTest(name = "{0}")
@MethodSource("newWithPayloadArguments")
void assertNewWithPayload(final String name, final String inputSQL, final
String expectedSQL, final boolean expectedWriteRouteOnly) {
- when(payload.readStringEOF()).thenReturn(inputSQL);
+
when(payload.readStringEOFByBytes()).thenReturn(inputSQL.getBytes(StandardCharsets.UTF_8));
+ when(payload.getCharset()).thenReturn(StandardCharsets.UTF_8);
MySQLComQueryPacket actual = new MySQLComQueryPacket(payload);
assertThat(actual.getSQL(), is(expectedSQL));
assertThat(actual.getHintValueContext().isWriteRouteOnly(),
is(expectedWriteRouteOnly));
+ assertFalse(actual.findOriginalSQLBytes().isPresent());
+ }
+
+ @Test
+ void assertRetainBinaryLiteralOriginalSQLBytes() {
+ byte[] sql = "SELECT _binary'foo'".getBytes(StandardCharsets.UTF_8);
+ when(payload.readStringEOFByBytes()).thenReturn(sql);
+ when(payload.getCharset()).thenReturn(StandardCharsets.UTF_8);
+ MySQLComQueryPacket actual = new MySQLComQueryPacket(payload);
+ assertArrayEquals(sql, actual.findOriginalSQLBytes().get());
+ }
+
+ @Test
+ void assertRetainMalformedOriginalSQLBytes() {
+ byte[] sql = {'S', 'E', 'L', 'E', 'C', 'T', ' ', '\'', (byte) 0xFF,
'\''};
+ when(payload.readStringEOFByBytes()).thenReturn(sql);
+ when(payload.getCharset()).thenReturn(StandardCharsets.UTF_8);
+ MySQLComQueryPacket actual = new MySQLComQueryPacket(payload);
+ assertArrayEquals(sql, actual.findOriginalSQLBytes().get());
}
@Test
diff --git
a/proxy/frontend/dialect/mysql/src/main/java/org/apache/shardingsphere/proxy/frontend/mysql/command/query/text/query/MySQLComQueryBackendHandlerFactory.java
b/proxy/frontend/dialect/mysql/src/main/java/org/apache/shardingsphere/proxy/frontend/mysql/command/query/text/query/MySQLComQueryBackendHandlerFactory.java
new file mode 100644
index 00000000000..6f0fd34004d
--- /dev/null
+++
b/proxy/frontend/dialect/mysql/src/main/java/org/apache/shardingsphere/proxy/frontend/mysql/command/query/text/query/MySQLComQueryBackendHandlerFactory.java
@@ -0,0 +1,117 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package
org.apache.shardingsphere.proxy.frontend.mysql.command.query.text.query;
+
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import org.apache.shardingsphere.database.connector.core.type.DatabaseType;
+import
org.apache.shardingsphere.database.exception.core.exception.syntax.sql.DialectSQLParsingException;
+import org.apache.shardingsphere.database.protocol.constant.CommonConstants;
+import
org.apache.shardingsphere.database.protocol.mysql.constant.MySQLConstants;
+import
org.apache.shardingsphere.database.protocol.mysql.packet.command.admin.MySQLComSetOptionPacket;
+import
org.apache.shardingsphere.database.protocol.mysql.packet.command.query.text.query.MySQLComQueryPacket;
+import org.apache.shardingsphere.infra.binder.context.aware.ParameterAware;
+import
org.apache.shardingsphere.infra.binder.context.statement.SQLStatementContext;
+import org.apache.shardingsphere.infra.binder.engine.SQLBindEngine;
+import org.apache.shardingsphere.infra.hint.SQLHintUtils;
+import org.apache.shardingsphere.infra.metadata.ShardingSphereMetaData;
+import org.apache.shardingsphere.infra.session.query.QueryContext;
+import org.apache.shardingsphere.infra.spi.type.typed.TypedSPILoader;
+import org.apache.shardingsphere.proxy.backend.context.ProxyContext;
+import org.apache.shardingsphere.proxy.backend.handler.ProxyBackendHandler;
+import
org.apache.shardingsphere.proxy.backend.handler.ProxyBackendHandlerFactory;
+import org.apache.shardingsphere.proxy.backend.handler.ProxySQLComQueryParser;
+import org.apache.shardingsphere.proxy.backend.session.ConnectionSession;
+import
org.apache.shardingsphere.proxy.frontend.mysql.command.query.text.query.MySQLComQueryBinaryParameterExtractor.ExtractionResult;
+import
org.apache.shardingsphere.sql.parser.statement.core.statement.SQLStatement;
+import
org.apache.shardingsphere.sql.parser.statement.core.statement.type.dml.DeleteStatement;
+import
org.apache.shardingsphere.sql.parser.statement.core.statement.type.dml.InsertStatement;
+import
org.apache.shardingsphere.sql.parser.statement.core.statement.type.dml.UpdateStatement;
+import
org.apache.shardingsphere.sql.parser.statement.core.util.MultiSQLSplitter;
+
+import java.sql.SQLException;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * Backend handler factory for MySQL COM_QUERY.
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+final class MySQLComQueryBackendHandlerFactory {
+
+ static ProxyBackendHandler newInstance(final MySQLComQueryPacket packet,
final ConnectionSession connectionSession) throws SQLException {
+ DatabaseType databaseType =
TypedSPILoader.getService(DatabaseType.class, "MySQL");
+ String sql = packet.getSQL();
+ SQLStatement sqlStatement = ProxySQLComQueryParser.parse(sql,
databaseType, connectionSession);
+ Optional<ProxyBackendHandler> binaryLiteralHandler =
tryCreateBinaryLiteralHandler(databaseType, sql, packet, connectionSession);
+ if (binaryLiteralHandler.isPresent()) {
+ return binaryLiteralHandler.get();
+ }
+ return areMultiStatements(connectionSession, sqlStatement, sql)
+ ? new
MySQLMultiStatementsProxyBackendHandler(connectionSession, sqlStatement, sql)
+ : ProxyBackendHandlerFactory.newInstance(databaseType, sql,
sqlStatement, connectionSession, packet.getHintValueContext());
+ }
+
+ private static Optional<ProxyBackendHandler>
tryCreateBinaryLiteralHandler(final DatabaseType databaseType, final String
sql, final MySQLComQueryPacket packet,
+
final ConnectionSession connectionSession) throws SQLException {
+ Optional<byte[]> originalSQLBytes = packet.findOriginalSQLBytes();
+ if (!originalSQLBytes.isPresent() || 1 !=
MultiSQLSplitter.split(sql).size()) {
+ return Optional.empty();
+ }
+ ExtractionResult extractionResult =
MySQLComQueryBinaryParameterExtractor.extract(
+ originalSQLBytes.get(),
connectionSession.getAttributeMap().attr(CommonConstants.CHARSET_ATTRIBUTE_KEY).get());
+ if (extractionResult.getBinaryLiteralValues().isEmpty()) {
+ return Optional.empty();
+ }
+ String parameterizedSQL =
SQLHintUtils.removeHint(extractionResult.getSql());
+ List<Object> binaryLiteralValues =
extractionResult.getBinaryLiteralValues();
+ SQLStatement parameterizedSQLStatement;
+ try {
+ parameterizedSQLStatement =
ProxySQLComQueryParser.parse(parameterizedSQL, databaseType, connectionSession);
+ } catch (final DialectSQLParsingException ignored) {
+ return Optional.empty();
+ }
+ ShardingSphereMetaData metaData =
ProxyContext.getInstance().getContextManager().getMetaDataContexts().getMetaData();
+ SQLStatementContext sqlStatementContext = new SQLBindEngine(metaData,
connectionSession.getCurrentDatabaseName(),
packet.getHintValueContext()).bind(parameterizedSQLStatement);
+ if (sqlStatementContext instanceof ParameterAware) {
+ ((ParameterAware)
sqlStatementContext).bindParameters(binaryLiteralValues);
+ }
+ QueryContext queryContext = new QueryContext(
+ sqlStatementContext, parameterizedSQL, binaryLiteralValues,
packet.getHintValueContext(), connectionSession.getConnectionContext(),
metaData, true);
+ return
Optional.of(ProxyBackendHandlerFactory.newInstance(databaseType, queryContext,
connectionSession, true));
+ }
+
+ private static boolean areMultiStatements(final ConnectionSession
connectionSession, final SQLStatement sqlStatement, final String sql) {
+ return isMultiStatementsEnabled(connectionSession)
+ && isSuitableMultiStatementsSQLStatement(sqlStatement)
+ && MultiSQLSplitter.hasSameTypeMultiStatements(sqlStatement,
MultiSQLSplitter.split(sql));
+ }
+
+ private static boolean isMultiStatementsEnabled(final ConnectionSession
connectionSession) {
+ return
connectionSession.getAttributeMap().hasAttr(MySQLConstants.OPTION_MULTI_STATEMENTS_ATTRIBUTE_KEY)
+ && MySQLComSetOptionPacket.MYSQL_OPTION_MULTI_STATEMENTS_ON ==
connectionSession.getAttributeMap().attr(MySQLConstants.OPTION_MULTI_STATEMENTS_ATTRIBUTE_KEY).get();
+ }
+
+ private static boolean isSuitableMultiStatementsSQLStatement(final
SQLStatement sqlStatement) {
+ return containsInsertOnDuplicateKey(sqlStatement) || sqlStatement
instanceof UpdateStatement || sqlStatement instanceof DeleteStatement;
+ }
+
+ private static boolean containsInsertOnDuplicateKey(final SQLStatement
sqlStatement) {
+ return sqlStatement instanceof InsertStatement && ((InsertStatement)
sqlStatement).getOnDuplicateKeyColumns().isPresent();
+ }
+}
diff --git
a/proxy/frontend/dialect/mysql/src/main/java/org/apache/shardingsphere/proxy/frontend/mysql/command/query/text/query/MySQLComQueryBinaryParameterExtractor.java
b/proxy/frontend/dialect/mysql/src/main/java/org/apache/shardingsphere/proxy/frontend/mysql/command/query/text/query/MySQLComQueryBinaryParameterExtractor.java
new file mode 100644
index 00000000000..157e979fedd
--- /dev/null
+++
b/proxy/frontend/dialect/mysql/src/main/java/org/apache/shardingsphere/proxy/frontend/mysql/command/query/text/query/MySQLComQueryBinaryParameterExtractor.java
@@ -0,0 +1,350 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package
org.apache.shardingsphere.proxy.frontend.mysql.command.query.text.query;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+import javax.sql.rowset.serial.SerialBlob;
+import java.io.ByteArrayOutputStream;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetDecoder;
+import java.nio.charset.CodingErrorAction;
+import java.nio.charset.CoderResult;
+import java.nio.charset.StandardCharsets;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * Binary parameter extractor for MySQL COM_QUERY.
+ *
+ * <p>Rewrite unambiguous single-quoted literals containing malformed bytes to
placeholders and preserve their values as {@link SerialBlob} parameters.
+ * Backslash escapes use MySQL's default escaping rules; literals with
grammar-dependent semantics are left unchanged.</p>
+ */
+final class MySQLComQueryBinaryParameterExtractor {
+
+ private static final byte[] BINARY_INTRODUCER =
"_binary".getBytes(StandardCharsets.US_ASCII);
+
+ private final byte[] sql;
+
+ private final Charset charset;
+
+ private final CharsetDecoder decoder;
+
+ private final ByteBuffer input;
+
+ private final CharBuffer output;
+
+ private MySQLComQueryBinaryParameterExtractor(final byte[] sql, final
Charset charset) {
+ this.sql = sql;
+ this.charset = charset;
+ decoder =
charset.newDecoder().onMalformedInput(CodingErrorAction.REPORT).onUnmappableCharacter(CodingErrorAction.REPORT);
+ input = ByteBuffer.wrap(sql);
+ output = CharBuffer.allocate(2);
+ }
+
+ static ExtractionResult extract(final byte[] sql, final Charset charset)
throws SQLException {
+ return new MySQLComQueryBinaryParameterExtractor(sql,
charset).extract();
+ }
+
+ private ExtractionResult extract() throws SQLException {
+ Collection<QuotedLiteral> parameterLiterals = findParameterLiterals();
+ if (parameterLiterals.isEmpty()) {
+ return new ExtractionResult(new String(sql, charset),
Collections.emptyList());
+ }
+ ByteArrayOutputStream sql = new ByteArrayOutputStream(this.sql.length);
+ List<Object> binaryLiteralValues = new
ArrayList<>(parameterLiterals.size());
+ int offset = 0;
+ for (QuotedLiteral each : parameterLiterals) {
+ int replacementStart = findBinaryIntroducerStart(each.startIndex);
+ replacementStart = 0 <= replacementStart ? replacementStart :
each.startIndex;
+ sql.write(this.sql, offset, replacementStart - offset);
+ sql.write('?');
+ binaryLiteralValues.add(new SerialBlob(unescape(each.startIndex +
1, each.endIndex)));
+ offset = each.endIndex + 1;
+ }
+ sql.write(this.sql, offset, this.sql.length - offset);
+ return new ExtractionResult(new String(sql.toByteArray(), charset),
binaryLiteralValues);
+ }
+
+ private Collection<QuotedLiteral> findParameterLiterals() {
+ Collection<QuotedLiteral> result = new LinkedList<>();
+ QuotedLiteral previousLiteral = null;
+ boolean previousLiteralIsParameter = false;
+ for (int i = 0; i < sql.length;) {
+ if ('#' == sql[i]) {
+ i = skipLineComment(i + 1);
+ } else if (isDashComment(i)) {
+ i = skipLineComment(i + 2);
+ } else if (isBlockComment(i)) {
+ i = skipBlockComment(i + 2);
+ } else if ('`' == sql[i] || '"' == sql[i] || '\'' == sql[i]) {
+ QuotedLiteral literal = findQuotedLiteral(i, sql[i]);
+ if ('\'' == sql[i] && !literal.isClosed()) {
+ return Collections.emptyList();
+ }
+ boolean parameterLiteral = isParameterLiteral(sql[i], literal);
+ if (null != previousLiteral && (previousLiteralIsParameter ||
parameterLiteral) && areAdjacent(previousLiteral, literal)) {
+ return Collections.emptyList();
+ }
+ if (parameterLiteral) {
+ result.add(literal);
+ }
+ previousLiteral = literal;
+ previousLiteralIsParameter = parameterLiteral;
+ i = literal.isClosed() ? literal.endIndex + 1 : sql.length;
+ } else {
+ int characterLength = readCharacterLength(i);
+ i += 0 < characterLength ? characterLength : 1;
+ }
+ }
+ return result;
+ }
+
+ private boolean isParameterLiteral(final byte quote, final QuotedLiteral
literal) {
+ return '\'' == quote && literal.containsMalformedBytes;
+ }
+
+ private QuotedLiteral findQuotedLiteral(final int startIndex, final byte
quote) {
+ boolean containsMalformedBytes = false;
+ for (int i = startIndex + 1; i < sql.length;) {
+ int characterLength = readCharacterLength(i);
+ if (0 > characterLength) {
+ containsMalformedBytes = true;
+ i++;
+ } else if (1 < characterLength) {
+ i += characterLength;
+ } else if ('\\' == sql[i]) {
+ int escapedCharacterLength = i + 1 < sql.length ?
readCharacterLength(i + 1) : 0;
+ if (0 > escapedCharacterLength) {
+ containsMalformedBytes = true;
+ }
+ i += 1 + Math.max(1, escapedCharacterLength);
+ } else if (quote != sql[i]) {
+ i++;
+ } else if (i + 1 < sql.length && quote == sql[i + 1]) {
+ i += 2;
+ } else {
+ return new QuotedLiteral(startIndex, i,
containsMalformedBytes);
+ }
+ }
+ return new QuotedLiteral(startIndex, -1, containsMalformedBytes);
+ }
+
+ private boolean areAdjacent(final QuotedLiteral previousLiteral, final
QuotedLiteral currentLiteral) {
+ return containsOnlyTrivia(previousLiteral.endIndex + 1,
findLiteralTokenStart(currentLiteral.startIndex));
+ }
+
+ private int findLiteralTokenStart(final int quoteIndex) {
+ int prefixEnd = quoteIndex;
+ while (0 < prefixEnd && isWhitespace(sql[prefixEnd - 1])) {
+ prefixEnd--;
+ }
+ int prefixStart = prefixEnd;
+ while (0 < prefixStart && isIdentifierCharacter(prefixStart - 1)) {
+ prefixStart--;
+ }
+ boolean hasCharacterSetIntroducer = prefixStart < prefixEnd && ('_' ==
sql[prefixStart] || prefixEnd - prefixStart == 1 && 'n' ==
toLowerCase(sql[prefixStart]));
+ return hasCharacterSetIntroducer ? prefixStart : quoteIndex;
+ }
+
+ private boolean containsOnlyTrivia(final int startIndex, final int
endIndex) {
+ for (int i = startIndex; i < endIndex;) {
+ if (isWhitespace(sql[i])) {
+ i++;
+ } else if ('#' == sql[i]) {
+ i = skipLineComment(i + 1);
+ } else if (isDashComment(i)) {
+ i = skipLineComment(i + 2);
+ } else if (isBlockComment(i)) {
+ i = skipBlockComment(i + 2);
+ } else {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private int readCharacterLength(final int index) {
+ if (0 <= sql[index]) {
+ return 1;
+ }
+ decoder.reset();
+ input.limit(sql.length);
+ input.position(index);
+ output.clear();
+ for (int limit = index + 1; limit <= sql.length; limit++) {
+ input.limit(limit);
+ CoderResult decodeResult = decoder.decode(input, output, false);
+ if (decodeResult.isError()) {
+ return -1;
+ }
+ if (0 < output.position()) {
+ return input.position() - index;
+ }
+ }
+ CoderResult decodeResult = decoder.decode(input, output, true);
+ return decodeResult.isError() || 0 == output.position() ? -1 :
input.position() - index;
+ }
+
+ private int findBinaryIntroducerStart(final int quoteIndex) {
+ int introducerEnd = quoteIndex;
+ while (0 < introducerEnd && isWhitespace(sql[introducerEnd - 1])) {
+ introducerEnd--;
+ }
+ int introducerStart = introducerEnd - BINARY_INTRODUCER.length;
+ if (0 > introducerStart || isIdentifierCharacter(introducerStart - 1))
{
+ return -1;
+ }
+ for (int i = 0; i < BINARY_INTRODUCER.length; i++) {
+ if (toLowerCase(sql[introducerStart + i]) != BINARY_INTRODUCER[i])
{
+ return -1;
+ }
+ }
+ return introducerStart;
+ }
+
+ private boolean isIdentifierCharacter(final int index) {
+ if (0 > index) {
+ return false;
+ }
+ int value = Byte.toUnsignedInt(sql[index]);
+ byte lowerCaseValue = toLowerCase(sql[index]);
+ return 0x80 <= value || '_' == value || '$' == value || '0' <= value
&& '9' >= value || 'a' <= lowerCaseValue && 'z' >= lowerCaseValue;
+ }
+
+ private static byte toLowerCase(final byte value) {
+ return 'A' <= value && 'Z' >= value ? (byte) (value + 'a' - 'A') :
value;
+ }
+
+ private static boolean isWhitespace(final byte value) {
+ return ' ' == value || '\t' == value || '\n' == value || '\r' == value
|| '\f' == value;
+ }
+
+ private boolean isDashComment(final int index) {
+ return index + 2 < sql.length && '-' == sql[index] && '-' == sql[index
+ 1] && Byte.toUnsignedInt(sql[index + 2]) <= ' ';
+ }
+
+ private boolean isBlockComment(final int index) {
+ return index + 1 < sql.length && '/' == sql[index] && '*' == sql[index
+ 1];
+ }
+
+ private int skipLineComment(final int startIndex) {
+ int result = startIndex;
+ while (result < sql.length && '\n' != sql[result] && '\r' !=
sql[result]) {
+ result++;
+ }
+ return result;
+ }
+
+ private int skipBlockComment(final int startIndex) {
+ for (int i = startIndex; i + 1 < sql.length; i++) {
+ if ('*' == sql[i] && '/' == sql[i + 1]) {
+ return i + 2;
+ }
+ }
+ return sql.length;
+ }
+
+ private byte[] unescape(final int startIndex, final int endIndex) {
+ ByteArrayOutputStream result = new ByteArrayOutputStream(endIndex -
startIndex);
+ for (int i = startIndex; i < endIndex;) {
+ int characterLength = readCharacterLength(i);
+ if (1 < characterLength) {
+ result.write(sql, i, characterLength);
+ i += characterLength;
+ } else if ('\'' == sql[i] && i + 1 < endIndex && '\'' == sql[i +
1]) {
+ result.write('\'');
+ i += 2;
+ } else if ('\\' != sql[i] || i + 1 >= endIndex) {
+ result.write(sql[i]);
+ i++;
+ } else {
+ int escapedCharacterLength = readCharacterLength(i + 1);
+ if (1 < escapedCharacterLength) {
+ result.write(sql, i + 1, escapedCharacterLength);
+ i += escapedCharacterLength + 1;
+ } else {
+ writeEscapedByte(result, sql[i + 1]);
+ i += 2;
+ }
+ }
+ }
+ return result.toByteArray();
+ }
+
+ private static void writeEscapedByte(final ByteArrayOutputStream output,
final byte value) {
+ switch (value) {
+ case '0':
+ output.write(0);
+ break;
+ case 'b':
+ output.write('\b');
+ break;
+ case 'n':
+ output.write('\n');
+ break;
+ case 'r':
+ output.write('\r');
+ break;
+ case 't':
+ output.write('\t');
+ break;
+ case 'Z':
+ output.write(0x1A);
+ break;
+ case '%':
+ case '_':
+ output.write('\\');
+ output.write(value);
+ break;
+ default:
+ output.write(value);
+ break;
+ }
+ }
+
+ @RequiredArgsConstructor
+ @Getter
+ static final class ExtractionResult {
+
+ private final String sql;
+
+ private final List<Object> binaryLiteralValues;
+ }
+
+ @RequiredArgsConstructor
+ private static final class QuotedLiteral {
+
+ private final int startIndex;
+
+ private final int endIndex;
+
+ private final boolean containsMalformedBytes;
+
+ private boolean isClosed() {
+ return 0 <= endIndex;
+ }
+ }
+}
diff --git
a/proxy/frontend/dialect/mysql/src/main/java/org/apache/shardingsphere/proxy/frontend/mysql/command/query/text/query/MySQLComQueryPacketExecutor.java
b/proxy/frontend/dialect/mysql/src/main/java/org/apache/shardingsphere/proxy/frontend/mysql/command/query/text/query/MySQLComQueryPacketExecutor.java
index 5c52e5fe1be..9769d580174 100644
---
a/proxy/frontend/dialect/mysql/src/main/java/org/apache/shardingsphere/proxy/frontend/mysql/command/query/text/query/MySQLComQueryPacketExecutor.java
+++
b/proxy/frontend/dialect/mysql/src/main/java/org/apache/shardingsphere/proxy/frontend/mysql/command/query/text/query/MySQLComQueryPacketExecutor.java
@@ -18,17 +18,12 @@
package
org.apache.shardingsphere.proxy.frontend.mysql.command.query.text.query;
import lombok.Getter;
-import org.apache.shardingsphere.database.connector.core.type.DatabaseType;
import
org.apache.shardingsphere.database.protocol.mysql.constant.MySQLConstants;
import org.apache.shardingsphere.database.protocol.mysql.packet.MySQLPacket;
-import
org.apache.shardingsphere.database.protocol.mysql.packet.command.admin.MySQLComSetOptionPacket;
import
org.apache.shardingsphere.database.protocol.mysql.packet.command.query.text.MySQLTextResultSetRowPacket;
import
org.apache.shardingsphere.database.protocol.mysql.packet.command.query.text.query.MySQLComQueryPacket;
import org.apache.shardingsphere.database.protocol.packet.DatabasePacket;
-import org.apache.shardingsphere.infra.spi.type.typed.TypedSPILoader;
import org.apache.shardingsphere.proxy.backend.handler.ProxyBackendHandler;
-import
org.apache.shardingsphere.proxy.backend.handler.ProxyBackendHandlerFactory;
-import org.apache.shardingsphere.proxy.backend.handler.ProxySQLComQueryParser;
import org.apache.shardingsphere.proxy.backend.response.header.ResponseHeader;
import
org.apache.shardingsphere.proxy.backend.response.header.query.QueryResponseHeader;
import
org.apache.shardingsphere.proxy.backend.response.header.update.MultiStatementsUpdateResponseHeader;
@@ -38,11 +33,6 @@ import
org.apache.shardingsphere.proxy.frontend.command.executor.QueryCommandExe
import org.apache.shardingsphere.proxy.frontend.command.executor.ResponseType;
import
org.apache.shardingsphere.proxy.frontend.mysql.command.ServerStatusFlagCalculator;
import
org.apache.shardingsphere.proxy.frontend.mysql.command.query.builder.ResponsePacketBuilder;
-import
org.apache.shardingsphere.sql.parser.statement.core.statement.SQLStatement;
-import
org.apache.shardingsphere.sql.parser.statement.core.statement.type.dml.DeleteStatement;
-import
org.apache.shardingsphere.sql.parser.statement.core.statement.type.dml.InsertStatement;
-import
org.apache.shardingsphere.sql.parser.statement.core.statement.type.dml.UpdateStatement;
-import
org.apache.shardingsphere.sql.parser.statement.core.util.MultiSQLSplitter;
import java.sql.SQLException;
import java.util.Collection;
@@ -64,33 +54,10 @@ public final class MySQLComQueryPacketExecutor implements
QueryCommandExecutor {
public MySQLComQueryPacketExecutor(final MySQLComQueryPacket packet, final
ConnectionSession connectionSession) throws SQLException {
this.connectionSession = connectionSession;
- DatabaseType databaseType =
TypedSPILoader.getService(DatabaseType.class, "MySQL");
- SQLStatement sqlStatement =
ProxySQLComQueryParser.parse(packet.getSQL(), databaseType, connectionSession);
- proxyBackendHandler = areMultiStatements(connectionSession,
sqlStatement, packet.getSQL())
- ? new
MySQLMultiStatementsProxyBackendHandler(connectionSession, sqlStatement,
packet.getSQL())
- : ProxyBackendHandlerFactory.newInstance(databaseType,
packet.getSQL(), sqlStatement, connectionSession, packet.getHintValueContext());
+ proxyBackendHandler =
MySQLComQueryBackendHandlerFactory.newInstance(packet, connectionSession);
characterSet =
connectionSession.getAttributeMap().attr(MySQLConstants.CHARACTER_SET_ATTRIBUTE_KEY).get().getId();
}
- private boolean areMultiStatements(final ConnectionSession
connectionSession, final SQLStatement sqlStatement, final String sql) {
- return isMultiStatementsEnabled(connectionSession)
- && isSuitableMultiStatementsSQLStatement(sqlStatement)
- && MultiSQLSplitter.hasSameTypeMultiStatements(sqlStatement,
MultiSQLSplitter.split(sql));
- }
-
- private boolean isMultiStatementsEnabled(final ConnectionSession
connectionSession) {
- return
connectionSession.getAttributeMap().hasAttr(MySQLConstants.OPTION_MULTI_STATEMENTS_ATTRIBUTE_KEY)
- && MySQLComSetOptionPacket.MYSQL_OPTION_MULTI_STATEMENTS_ON ==
connectionSession.getAttributeMap().attr(MySQLConstants.OPTION_MULTI_STATEMENTS_ATTRIBUTE_KEY).get();
- }
-
- private boolean isSuitableMultiStatementsSQLStatement(final SQLStatement
sqlStatement) {
- return containsInsertOnDuplicateKey(sqlStatement) || sqlStatement
instanceof UpdateStatement || sqlStatement instanceof DeleteStatement;
- }
-
- private boolean containsInsertOnDuplicateKey(final SQLStatement
sqlStatement) {
- return sqlStatement instanceof InsertStatement && ((InsertStatement)
sqlStatement).getOnDuplicateKeyColumns().isPresent();
- }
-
@Override
public Collection<DatabasePacket> execute() throws SQLException {
ResponseHeader responseHeader = proxyBackendHandler.execute();
diff --git
a/proxy/frontend/dialect/mysql/src/test/java/org/apache/shardingsphere/proxy/frontend/mysql/command/MySQLCommandPacketFactoryTest.java
b/proxy/frontend/dialect/mysql/src/test/java/org/apache/shardingsphere/proxy/frontend/mysql/command/MySQLCommandPacketFactoryTest.java
index 9339532b2de..0fce0268df9 100644
---
a/proxy/frontend/dialect/mysql/src/test/java/org/apache/shardingsphere/proxy/frontend/mysql/command/MySQLCommandPacketFactoryTest.java
+++
b/proxy/frontend/dialect/mysql/src/test/java/org/apache/shardingsphere/proxy/frontend/mysql/command/MySQLCommandPacketFactoryTest.java
@@ -48,6 +48,8 @@ import org.mockito.Answers;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
+import java.nio.charset.StandardCharsets;
+
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.isA;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -81,7 +83,8 @@ class MySQLCommandPacketFactoryTest {
@Test
void assertNewInstanceWithComQueryPacket() {
- when(payload.readStringEOF()).thenReturn("SHOW TABLES");
+ when(payload.readStringEOFByBytes()).thenReturn("SHOW
TABLES".getBytes(StandardCharsets.UTF_8));
+ when(payload.getCharset()).thenReturn(StandardCharsets.UTF_8);
assertThat(MySQLCommandPacketFactory.newInstance(MySQLCommandPacketType.COM_QUERY,
payload, connectionSession), isA(MySQLComQueryPacket.class));
}
diff --git
a/proxy/frontend/dialect/mysql/src/test/java/org/apache/shardingsphere/proxy/frontend/mysql/command/query/text/query/MySQLComQueryPacketExecutorTest.java
b/proxy/frontend/dialect/mysql/src/test/java/org/apache/shardingsphere/proxy/frontend/mysql/command/query/text/query/MySQLComQueryBackendHandlerFactoryTest.java
similarity index 53%
copy from
proxy/frontend/dialect/mysql/src/test/java/org/apache/shardingsphere/proxy/frontend/mysql/command/query/text/query/MySQLComQueryPacketExecutorTest.java
copy to
proxy/frontend/dialect/mysql/src/test/java/org/apache/shardingsphere/proxy/frontend/mysql/command/query/text/query/MySQLComQueryBackendHandlerFactoryTest.java
index f727c87ab58..1e9259bef69 100644
---
a/proxy/frontend/dialect/mysql/src/test/java/org/apache/shardingsphere/proxy/frontend/mysql/command/query/text/query/MySQLComQueryPacketExecutorTest.java
+++
b/proxy/frontend/dialect/mysql/src/test/java/org/apache/shardingsphere/proxy/frontend/mysql/command/query/text/query/MySQLComQueryBackendHandlerFactoryTest.java
@@ -18,20 +18,20 @@
package
org.apache.shardingsphere.proxy.frontend.mysql.command.query.text.query;
import org.apache.shardingsphere.database.connector.core.type.DatabaseType;
-import
org.apache.shardingsphere.database.protocol.mysql.constant.MySQLCharacterSets;
+import org.apache.shardingsphere.database.protocol.constant.CommonConstants;
import
org.apache.shardingsphere.database.protocol.mysql.constant.MySQLConstants;
-import
org.apache.shardingsphere.database.protocol.mysql.packet.command.query.text.MySQLTextResultSetRowPacket;
+import
org.apache.shardingsphere.database.protocol.mysql.packet.command.admin.MySQLComSetOptionPacket;
import
org.apache.shardingsphere.database.protocol.mysql.packet.command.query.text.query.MySQLComQueryPacket;
-import
org.apache.shardingsphere.database.protocol.mysql.packet.generic.MySQLOKPacket;
-import org.apache.shardingsphere.database.protocol.packet.DatabasePacket;
import org.apache.shardingsphere.infra.config.props.ConfigurationProperties;
import org.apache.shardingsphere.infra.config.props.ConfigurationPropertyKey;
+import org.apache.shardingsphere.infra.hint.HintValueContext;
import
org.apache.shardingsphere.infra.metadata.database.ShardingSphereDatabase;
import
org.apache.shardingsphere.infra.metadata.database.resource.ResourceMetaData;
import org.apache.shardingsphere.infra.metadata.database.rule.RuleMetaData;
import
org.apache.shardingsphere.infra.metadata.database.schema.model.ShardingSphereColumn;
import
org.apache.shardingsphere.infra.metadata.database.schema.model.ShardingSphereSchema;
import
org.apache.shardingsphere.infra.metadata.database.schema.model.ShardingSphereTable;
+import org.apache.shardingsphere.infra.session.query.QueryContext;
import org.apache.shardingsphere.infra.spi.type.typed.TypedSPILoader;
import org.apache.shardingsphere.mode.manager.ContextManager;
import org.apache.shardingsphere.mode.metadata.MetaDataContexts;
@@ -39,50 +39,50 @@ import org.apache.shardingsphere.parser.rule.SQLParserRule;
import
org.apache.shardingsphere.parser.rule.builder.DefaultSQLParserRuleConfigurationBuilder;
import org.apache.shardingsphere.proxy.backend.context.ProxyContext;
import org.apache.shardingsphere.proxy.backend.handler.ProxyBackendHandler;
-import
org.apache.shardingsphere.proxy.backend.response.header.query.QueryHeader;
-import
org.apache.shardingsphere.proxy.backend.response.header.query.QueryResponseHeader;
-import
org.apache.shardingsphere.proxy.backend.response.header.update.MultiStatementsUpdateResponseHeader;
-import
org.apache.shardingsphere.proxy.backend.response.header.update.UpdateResponseHeader;
+import
org.apache.shardingsphere.proxy.backend.handler.ProxyBackendHandlerFactory;
import org.apache.shardingsphere.proxy.backend.session.ConnectionSession;
-import org.apache.shardingsphere.proxy.frontend.command.executor.ResponseType;
import
org.apache.shardingsphere.sql.parser.statement.core.statement.SQLStatement;
import
org.apache.shardingsphere.sql.parser.statement.core.value.identifier.IdentifierValue;
import org.apache.shardingsphere.sqltranslator.rule.SQLTranslatorRule;
import
org.apache.shardingsphere.sqltranslator.rule.builder.DefaultSQLTranslatorRuleConfigurationBuilder;
import
org.apache.shardingsphere.test.infra.framework.extension.mock.AutoMockExtension;
+import
org.apache.shardingsphere.test.infra.framework.extension.mock.ConstructionMockSettings;
import
org.apache.shardingsphere.test.infra.framework.extension.mock.StaticMockSettings;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Answers;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
-import org.mockito.internal.configuration.plugins.Plugins;
import org.mockito.junit.jupiter.MockitoSettings;
-import org.mockito.plugins.MemberAccessor;
import org.mockito.quality.Strictness;
+import java.nio.charset.StandardCharsets;
+import java.sql.Blob;
import java.sql.SQLException;
import java.sql.Types;
import java.util.Arrays;
-import java.util.Collection;
import java.util.Collections;
-import java.util.Iterator;
+import java.util.Optional;
import java.util.Properties;
-import static org.hamcrest.Matchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.isA;
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyBoolean;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ExtendWith(AutoMockExtension.class)
-@StaticMockSettings(ProxyContext.class)
+@StaticMockSettings({ProxyContext.class, ProxyBackendHandlerFactory.class})
+@ConstructionMockSettings(MySQLMultiStatementsProxyBackendHandler.class)
@MockitoSettings(strictness = Strictness.LENIENT)
-class MySQLComQueryPacketExecutorTest {
+class MySQLComQueryBackendHandlerFactoryTest {
private final DatabaseType databaseType =
TypedSPILoader.getService(DatabaseType.class, "MySQL");
@@ -96,69 +96,96 @@ class MySQLComQueryPacketExecutorTest {
private ConnectionSession connectionSession;
@BeforeEach
- void setUp() {
+ void setUp() throws SQLException {
when(packet.getSQL()).thenReturn("");
-
when(connectionSession.getAttributeMap().attr(MySQLConstants.CHARACTER_SET_ATTRIBUTE_KEY).get()).thenReturn(MySQLCharacterSets.UTF8MB4_GENERAL_CI);
+ when(packet.findOriginalSQLBytes()).thenReturn(Optional.empty());
+ when(packet.getHintValueContext()).thenReturn(new HintValueContext());
+ when(ProxyBackendHandlerFactory.newInstance(eq(databaseType),
anyString(), any(SQLStatement.class), eq(connectionSession),
any())).thenReturn(proxyBackendHandler);
+ when(ProxyBackendHandlerFactory.newInstance(eq(databaseType),
any(QueryContext.class), eq(connectionSession),
anyBoolean())).thenReturn(proxyBackendHandler);
+ mockProxyContext();
}
@Test
- void assertIsQueryResponse() throws SQLException, NoSuchFieldException,
IllegalAccessException {
- MySQLComQueryPacketExecutor mysqlComQueryPacketExecutor = new
MySQLComQueryPacketExecutor(packet, connectionSession);
-
Plugins.getMemberAccessor().set(MySQLComQueryPacketExecutor.class.getDeclaredField("proxyBackendHandler"),
mysqlComQueryPacketExecutor, proxyBackendHandler);
- QueryHeader queryHeader = mock(QueryHeader.class);
- when(queryHeader.getColumnTypeName()).thenReturn("VARCHAR");
- when(proxyBackendHandler.execute()).thenReturn(new
QueryResponseHeader(Collections.singletonList(queryHeader)));
- mysqlComQueryPacketExecutor.execute();
- assertThat(mysqlComQueryPacketExecutor.getResponseType(),
is(ResponseType.QUERY));
+ void assertCreateStandardSQLHandler() throws SQLException {
+ when(packet.getSQL()).thenReturn("SELECT 1");
+ ProxyBackendHandler actual =
MySQLComQueryBackendHandlerFactory.newInstance(packet, connectionSession);
+ assertThat(actual, is(proxyBackendHandler));
}
@Test
- void assertIsUpdateResponse() throws SQLException, NoSuchFieldException,
IllegalAccessException {
- MySQLComQueryPacketExecutor mysqlComQueryPacketExecutor = new
MySQLComQueryPacketExecutor(packet, connectionSession);
-
Plugins.getMemberAccessor().set(MySQLComQueryPacketExecutor.class.getDeclaredField("proxyBackendHandler"),
mysqlComQueryPacketExecutor, proxyBackendHandler);
- when(proxyBackendHandler.execute()).thenReturn(new
UpdateResponseHeader(mock(SQLStatement.class)));
- mysqlComQueryPacketExecutor.execute();
- assertThat(mysqlComQueryPacketExecutor.getResponseType(),
is(ResponseType.UPDATE));
+ void assertCreateMultiStatementsHandlerForUpdate() throws SQLException {
+ enableMultiStatements();
+ when(packet.getSQL()).thenReturn("UPDATE t SET v=v+1 WHERE id=1;UPDATE
t SET v=v+1 WHERE id=2");
+ ProxyBackendHandler actual =
MySQLComQueryBackendHandlerFactory.newInstance(packet, connectionSession);
+ assertThat(actual, isA(MySQLMultiStatementsProxyBackendHandler.class));
}
@Test
- void assertExecuteMultiUpdateStatements() throws SQLException,
NoSuchFieldException, IllegalAccessException {
+ void assertCreateMultiStatementsHandlerForInsertOnDuplicateKey() throws
SQLException {
+ enableMultiStatements();
+ when(packet.getSQL()).thenReturn("INSERT INTO t (id, v) VALUES(1,1) ON
DUPLICATE KEY UPDATE v=2;INSERT INTO t (id, v) VALUES(2,1) ON DUPLICATE KEY
UPDATE v=3");
+ ProxyBackendHandler actual =
MySQLComQueryBackendHandlerFactory.newInstance(packet, connectionSession);
+ assertThat(actual, isA(MySQLMultiStatementsProxyBackendHandler.class));
+ }
+
+ private void enableMultiStatements() {
when(connectionSession.getAttributeMap().hasAttr(MySQLConstants.OPTION_MULTI_STATEMENTS_ATTRIBUTE_KEY)).thenReturn(true);
-
when(connectionSession.getAttributeMap().attr(MySQLConstants.OPTION_MULTI_STATEMENTS_ATTRIBUTE_KEY).get()).thenReturn(0);
- when(connectionSession.getCurrentDatabaseName()).thenReturn("foo_db");
- when(packet.getSQL()).thenReturn("UPDATE t SET v=v+1 WHERE id=1;UPDATE
t SET v=v+1 WHERE id=2;UPDATE t SET v=v+1 WHERE id=3");
- ContextManager contextManager = mock(ContextManager.class);
- MetaDataContexts metaDataContexts = mockMetaDataContexts();
-
when(contextManager.getMetaDataContexts()).thenReturn(metaDataContexts);
-
when(ProxyContext.getInstance().getContextManager()).thenReturn(contextManager);
- MySQLComQueryPacketExecutor actual = new
MySQLComQueryPacketExecutor(packet, connectionSession);
-
Plugins.getMemberAccessor().set(MySQLComQueryPacketExecutor.class.getDeclaredField("proxyBackendHandler"),
actual, proxyBackendHandler);
- when(proxyBackendHandler.execute()).thenReturn(new
UpdateResponseHeader(mock(SQLStatement.class)));
- Collection<DatabasePacket> actualPackets = actual.execute();
- assertThat(actualPackets.size(), is(1));
- assertThat(actualPackets.iterator().next(), isA(MySQLOKPacket.class));
+
when(connectionSession.getAttributeMap().attr(MySQLConstants.OPTION_MULTI_STATEMENTS_ATTRIBUTE_KEY).get()).thenReturn(MySQLComSetOptionPacket.MYSQL_OPTION_MULTI_STATEMENTS_ON);
}
@Test
- void assertExecuteMultiInsertOnDuplicateKeyStatements() throws
SQLException, NoSuchFieldException, IllegalAccessException {
-
when(connectionSession.getAttributeMap().hasAttr(MySQLConstants.OPTION_MULTI_STATEMENTS_ATTRIBUTE_KEY)).thenReturn(true);
-
when(connectionSession.getAttributeMap().attr(MySQLConstants.OPTION_MULTI_STATEMENTS_ATTRIBUTE_KEY).get()).thenReturn(0);
+ void assertCreatePreparedHandlerForBinaryLiteral() throws SQLException {
+ when(packet.getSQL()).thenReturn("INSERT INTO t (id, v) VALUES (1,
_binary'�')");
+ when(packet.findOriginalSQLBytes()).thenReturn(Optional.of(sql(
+ "/* SHARDINGSPHERE_HINT: WRITE_ROUTE_ONLY=true */ INSERT INTO
t (id, v) VALUES (1, _binary'", (byte) 0xFF, "')")));
+
when(connectionSession.getAttributeMap().attr(CommonConstants.CHARSET_ATTRIBUTE_KEY).get()).thenReturn(StandardCharsets.UTF_8);
when(connectionSession.getCurrentDatabaseName()).thenReturn("foo_db");
- when(packet.getSQL()).thenReturn("INSERT INTO t (id, v) VALUES(1,1) ON
DUPLICATE KEY UPDATE v=2;INSERT INTO t (id, v) VALUES(2,1) ON DUPLICATE KEY
UPDATE v=3");
- ContextManager contextManager = mock(ContextManager.class,
RETURNS_DEEP_STUBS);
+
when(connectionSession.getConnectionContext().getCurrentDatabaseName()).thenReturn(Optional.of("foo_db"));
+ ArgumentCaptor<QueryContext> queryContextCaptor =
ArgumentCaptor.forClass(QueryContext.class);
+ when(ProxyBackendHandlerFactory.newInstance(eq(databaseType),
queryContextCaptor.capture(), eq(connectionSession),
eq(true))).thenReturn(proxyBackendHandler);
+ MySQLComQueryBackendHandlerFactory.newInstance(packet,
connectionSession);
+ QueryContext actual = queryContextCaptor.getValue();
+ assertThat(actual.getSql(), is("INSERT INTO t (id, v) VALUES (1, ?)"));
+ assertThat(actual.getParameters().size(), is(1));
+ Blob blob = (Blob) actual.getParameters().get(0);
+ assertArrayEquals(new byte[]{(byte) 0xFF}, blob.getBytes(1, (int)
blob.length()));
+ }
+
+ @Test
+ void assertCreateMultiStatementsHandlerForMalformedBinaryLiteral() throws
SQLException {
+ enableMultiStatements();
+ when(packet.getSQL()).thenReturn("UPDATE t SET v = _binary'�' WHERE id
= 1; UPDATE t SET v = _binary'bar' WHERE id = 2");
+ when(packet.findOriginalSQLBytes()).thenReturn(Optional.of(sql(
+ "UPDATE t SET v = _binary'", (byte) 0xFF, "' WHERE id = 1;
UPDATE t SET v = _binary'bar' WHERE id = 2")));
+
when(connectionSession.getAttributeMap().attr(CommonConstants.CHARSET_ATTRIBUTE_KEY).get()).thenReturn(StandardCharsets.UTF_8);
+ ProxyBackendHandler actual =
MySQLComQueryBackendHandlerFactory.newInstance(packet, connectionSession);
+ assertThat(actual, isA(MySQLMultiStatementsProxyBackendHandler.class));
+ }
+
+ @Test
+ void assertCreateStandardHandlerForStructuralBinaryLiteral() throws
SQLException {
+ when(packet.getSQL()).thenReturn("SHOW TABLES LIKE _binary'�'");
+ when(packet.findOriginalSQLBytes()).thenReturn(Optional.of(sql("SHOW
TABLES LIKE _binary'", (byte) 0xFF, "'")));
+
when(connectionSession.getAttributeMap().attr(CommonConstants.CHARSET_ATTRIBUTE_KEY).get()).thenReturn(StandardCharsets.UTF_8);
+ ProxyBackendHandler actual =
MySQLComQueryBackendHandlerFactory.newInstance(packet, connectionSession);
+ assertThat(actual, is(proxyBackendHandler));
+ }
+
+ private static byte[] sql(final String prefix, final byte content, final
String suffix) {
+ byte[] prefixBytes = prefix.getBytes(StandardCharsets.UTF_8);
+ byte[] suffixBytes = suffix.getBytes(StandardCharsets.UTF_8);
+ byte[] result = new byte[prefixBytes.length + 1 + suffixBytes.length];
+ System.arraycopy(prefixBytes, 0, result, 0, prefixBytes.length);
+ result[prefixBytes.length] = content;
+ System.arraycopy(suffixBytes, 0, result, prefixBytes.length + 1,
suffixBytes.length);
+ return result;
+ }
+
+ private void mockProxyContext() {
+ ContextManager contextManager = mock(ContextManager.class);
MetaDataContexts metaDataContexts = mockMetaDataContexts();
when(contextManager.getMetaDataContexts()).thenReturn(metaDataContexts);
when(ProxyContext.getInstance().getContextManager()).thenReturn(contextManager);
- MySQLComQueryPacketExecutor actual = new
MySQLComQueryPacketExecutor(packet, connectionSession);
- MemberAccessor accessor = Plugins.getMemberAccessor();
-
accessor.set(MySQLComQueryPacketExecutor.class.getDeclaredField("proxyBackendHandler"),
actual, proxyBackendHandler);
- when(proxyBackendHandler.execute())
- .thenReturn(new
MultiStatementsUpdateResponseHeader(Arrays.asList(new
UpdateResponseHeader(mock(SQLStatement.class)), new
UpdateResponseHeader(mock(SQLStatement.class)))));
- Collection<DatabasePacket> actualPackets = actual.execute();
- assertThat(actualPackets.size(), is(2));
- Iterator<DatabasePacket> iterator = actualPackets.iterator();
- assertThat(iterator.next(), isA(MySQLOKPacket.class));
- assertThat(iterator.next(), isA(MySQLOKPacket.class));
}
private MetaDataContexts mockMetaDataContexts() {
@@ -184,26 +211,4 @@ class MySQLComQueryPacketExecutorTest {
return new ShardingSphereDatabase("foo_db", databaseType, new
ResourceMetaData(Collections.emptyMap()), new
RuleMetaData(Collections.emptyList()), Collections.singleton(schema),
new ConfigurationProperties(new Properties()));
}
-
- @Test
- void assertNext() throws SQLException, NoSuchFieldException,
IllegalAccessException {
- MySQLComQueryPacketExecutor actual = new
MySQLComQueryPacketExecutor(packet, connectionSession);
-
Plugins.getMemberAccessor().set(MySQLComQueryPacketExecutor.class.getDeclaredField("proxyBackendHandler"),
actual, proxyBackendHandler);
- when(proxyBackendHandler.next()).thenReturn(true, false);
- assertTrue(actual.next());
- assertFalse(actual.next());
- }
-
- @Test
- void assertGetQueryRowPacket() throws SQLException {
- assertThat(new MySQLComQueryPacketExecutor(packet,
connectionSession).getQueryRowPacket(), isA(MySQLTextResultSetRowPacket.class));
- }
-
- @Test
- void assertClose() throws SQLException, NoSuchFieldException,
IllegalAccessException {
- MySQLComQueryPacketExecutor actual = new
MySQLComQueryPacketExecutor(packet, connectionSession);
-
Plugins.getMemberAccessor().set(MySQLComQueryPacketExecutor.class.getDeclaredField("proxyBackendHandler"),
actual, proxyBackendHandler);
- actual.close();
- verify(proxyBackendHandler).close();
- }
}
diff --git
a/proxy/frontend/dialect/mysql/src/test/java/org/apache/shardingsphere/proxy/frontend/mysql/command/query/text/query/MySQLComQueryBinaryParameterExtractorTest.java
b/proxy/frontend/dialect/mysql/src/test/java/org/apache/shardingsphere/proxy/frontend/mysql/command/query/text/query/MySQLComQueryBinaryParameterExtractorTest.java
new file mode 100644
index 00000000000..ef5563d2c52
--- /dev/null
+++
b/proxy/frontend/dialect/mysql/src/test/java/org/apache/shardingsphere/proxy/frontend/mysql/command/query/text/query/MySQLComQueryBinaryParameterExtractorTest.java
@@ -0,0 +1,128 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package
org.apache.shardingsphere.proxy.frontend.mysql.command.query.text.query;
+
+import org.junit.jupiter.api.Test;
+
+import java.io.ByteArrayOutputStream;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.sql.Blob;
+import java.sql.SQLException;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.empty;
+import static org.hamcrest.Matchers.is;
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+
+class MySQLComQueryBinaryParameterExtractorTest {
+
+ @Test
+ void assertExtractConnectorJBinaryLiteral() throws SQLException {
+ byte[] content = {'\\', '0', '\\', '\'', '\\', '\\', (byte) 0xFF};
+ MySQLComQueryBinaryParameterExtractor.ExtractionResult actual =
MySQLComQueryBinaryParameterExtractor.extract(
+ sql("INSERT INTO t VALUES (_binary'", content, "')"),
StandardCharsets.UTF_8);
+ assertThat(actual.getSql(), is("INSERT INTO t VALUES (?)"));
+ assertArrayEquals(new byte[]{0, '\'', '\\', (byte) 0xFF},
getBlobBytes(actual.getBinaryLiteralValues().get(0)));
+ }
+
+ @Test
+ void assertExtractBackslashEscapedMalformedLiteral() throws SQLException {
+ byte[] content = {'\\', 'b', '\\', 'n', '\\', 'r', '\\', 't', '\\',
'Z', '\\', 'q', '\\', '%', '\\', '_', (byte) 0xFF};
+ MySQLComQueryBinaryParameterExtractor.ExtractionResult actual =
MySQLComQueryBinaryParameterExtractor.extract(
+ sql("INSERT INTO t VALUES ('", content, "')"),
StandardCharsets.UTF_8);
+ assertThat(actual.getSql(), is("INSERT INTO t VALUES (?)"));
+ assertArrayEquals(new byte[]{'\b', '\n', '\r', '\t', 0x1A, 'q', '\\',
'%', '\\', '_', (byte) 0xFF},
getBlobBytes(actual.getBinaryLiteralValues().get(0)));
+ }
+
+ @Test
+ void assertExtractMalformedLiteral() throws SQLException {
+ byte[] content = {'f', 'o', '\'', '\'', (byte) 0xFF};
+ MySQLComQueryBinaryParameterExtractor.ExtractionResult actual =
MySQLComQueryBinaryParameterExtractor.extract(
+ sql("INSERT INTO t VALUES ('", content, "')"),
StandardCharsets.UTF_8);
+ assertThat(actual.getSql(), is("INSERT INTO t VALUES (?)"));
+ assertArrayEquals(new byte[]{'f', 'o', '\'', (byte) 0xFF},
getBlobBytes(actual.getBinaryLiteralValues().get(0)));
+ }
+
+ @Test
+ void assertExtractOnlyMalformedLiteral() throws SQLException {
+ byte[] sql = sql("SELECT _BiNaRy 'fo''o', '", new byte[]{(byte) 0xFF},
"'");
+ MySQLComQueryBinaryParameterExtractor.ExtractionResult actual =
MySQLComQueryBinaryParameterExtractor.extract(sql, StandardCharsets.UTF_8);
+ assertThat(actual.getSql(), is("SELECT _BiNaRy 'fo''o', ?"));
+ assertThat(actual.getBinaryLiteralValues().size(), is(1));
+ assertArrayEquals(new byte[]{(byte) 0xFF},
getBlobBytes(actual.getBinaryLiteralValues().get(0)));
+ }
+
+ @Test
+ void assertSkipValidLiteral() throws SQLException {
+ String sql = "SELECT '😀', _binary'foo'";
+ MySQLComQueryBinaryParameterExtractor.ExtractionResult actual =
MySQLComQueryBinaryParameterExtractor.extract(sql.getBytes(StandardCharsets.UTF_8),
StandardCharsets.UTF_8);
+ assertThat(actual.getBinaryLiteralValues(), empty());
+ }
+
+ @Test
+ void assertIgnoreNonLiteralBinaryTokens() throws SQLException {
+ String sql = "SELECT '_binary', `_binary'ignored`,
\"_binary'ignored'\", 1 /* _binary'x' */ -- _binary'y'\n";
+ MySQLComQueryBinaryParameterExtractor.ExtractionResult actual =
MySQLComQueryBinaryParameterExtractor.extract(sql.getBytes(StandardCharsets.UTF_8),
StandardCharsets.UTF_8);
+ assertThat(actual.getBinaryLiteralValues(), empty());
+ }
+
+ @Test
+ void assertExtractMalformedLiteralWithMultibyteTrailBackslash() throws
SQLException {
+ Charset charset = Charset.forName("Shift_JIS");
+ byte[] content = {(byte) 0x83, '\\', (byte) 0xFF};
+ MySQLComQueryBinaryParameterExtractor.ExtractionResult actual =
MySQLComQueryBinaryParameterExtractor.extract(sql("SELECT _binary'", content,
"'"), charset);
+ assertArrayEquals(content,
getBlobBytes(actual.getBinaryLiteralValues().get(0)));
+ }
+
+ @Test
+ void assertKeepMalformedSQLForParser() throws SQLException {
+ byte[] sql = "SELECT _binary'foo".getBytes(StandardCharsets.UTF_8);
+ MySQLComQueryBinaryParameterExtractor.ExtractionResult actual =
MySQLComQueryBinaryParameterExtractor.extract(sql, StandardCharsets.UTF_8);
+ assertThat(actual.getBinaryLiteralValues(), empty());
+ }
+
+ @Test
+ void assertSkipDoubleQuotedLiteral() throws SQLException {
+ MySQLComQueryBinaryParameterExtractor.ExtractionResult actual =
MySQLComQueryBinaryParameterExtractor.extract(
+ sql("SELECT _binary\"", new byte[]{(byte) 0xFF}, "\""),
StandardCharsets.UTF_8);
+ assertThat(actual.getBinaryLiteralValues(), empty());
+ }
+
+ @Test
+ void assertSkipAdjacentLiteral() throws SQLException {
+ MySQLComQueryBinaryParameterExtractor.ExtractionResult actual =
MySQLComQueryBinaryParameterExtractor.extract(
+ sql("SELECT _binary'", new byte[]{(byte) 0xFF}, "' 'foo'"),
StandardCharsets.UTF_8);
+ assertThat(actual.getBinaryLiteralValues(), empty());
+ }
+
+ private static byte[] sql(final String prefix, final byte[] content, final
String suffix) {
+ byte[] prefixBytes = prefix.getBytes(StandardCharsets.US_ASCII);
+ byte[] suffixBytes = suffix.getBytes(StandardCharsets.US_ASCII);
+ ByteArrayOutputStream result = new
ByteArrayOutputStream(prefixBytes.length + content.length + suffixBytes.length);
+ result.write(prefixBytes, 0, prefixBytes.length);
+ result.write(content, 0, content.length);
+ result.write(suffixBytes, 0, suffixBytes.length);
+ return result.toByteArray();
+ }
+
+ private static byte[] getBlobBytes(final Object value) throws SQLException
{
+ Blob blob = (Blob) value;
+ return blob.getBytes(1, (int) blob.length());
+ }
+}
diff --git
a/proxy/frontend/dialect/mysql/src/test/java/org/apache/shardingsphere/proxy/frontend/mysql/command/query/text/query/MySQLComQueryPacketExecutorTest.java
b/proxy/frontend/dialect/mysql/src/test/java/org/apache/shardingsphere/proxy/frontend/mysql/command/query/text/query/MySQLComQueryPacketExecutorTest.java
index f727c87ab58..b401129d71f 100644
---
a/proxy/frontend/dialect/mysql/src/test/java/org/apache/shardingsphere/proxy/frontend/mysql/command/query/text/query/MySQLComQueryPacketExecutorTest.java
+++
b/proxy/frontend/dialect/mysql/src/test/java/org/apache/shardingsphere/proxy/frontend/mysql/command/query/text/query/MySQLComQueryPacketExecutorTest.java
@@ -17,28 +17,14 @@
package
org.apache.shardingsphere.proxy.frontend.mysql.command.query.text.query;
-import org.apache.shardingsphere.database.connector.core.type.DatabaseType;
import
org.apache.shardingsphere.database.protocol.mysql.constant.MySQLCharacterSets;
import
org.apache.shardingsphere.database.protocol.mysql.constant.MySQLConstants;
import
org.apache.shardingsphere.database.protocol.mysql.packet.command.query.text.MySQLTextResultSetRowPacket;
import
org.apache.shardingsphere.database.protocol.mysql.packet.command.query.text.query.MySQLComQueryPacket;
import
org.apache.shardingsphere.database.protocol.mysql.packet.generic.MySQLOKPacket;
import org.apache.shardingsphere.database.protocol.packet.DatabasePacket;
-import org.apache.shardingsphere.infra.config.props.ConfigurationProperties;
-import org.apache.shardingsphere.infra.config.props.ConfigurationPropertyKey;
-import
org.apache.shardingsphere.infra.metadata.database.ShardingSphereDatabase;
-import
org.apache.shardingsphere.infra.metadata.database.resource.ResourceMetaData;
-import org.apache.shardingsphere.infra.metadata.database.rule.RuleMetaData;
-import
org.apache.shardingsphere.infra.metadata.database.schema.model.ShardingSphereColumn;
-import
org.apache.shardingsphere.infra.metadata.database.schema.model.ShardingSphereSchema;
-import
org.apache.shardingsphere.infra.metadata.database.schema.model.ShardingSphereTable;
-import org.apache.shardingsphere.infra.spi.type.typed.TypedSPILoader;
-import org.apache.shardingsphere.mode.manager.ContextManager;
-import org.apache.shardingsphere.mode.metadata.MetaDataContexts;
-import org.apache.shardingsphere.parser.rule.SQLParserRule;
-import
org.apache.shardingsphere.parser.rule.builder.DefaultSQLParserRuleConfigurationBuilder;
-import org.apache.shardingsphere.proxy.backend.context.ProxyContext;
import org.apache.shardingsphere.proxy.backend.handler.ProxyBackendHandler;
+import org.apache.shardingsphere.proxy.backend.response.data.QueryResponseRow;
import
org.apache.shardingsphere.proxy.backend.response.header.query.QueryHeader;
import
org.apache.shardingsphere.proxy.backend.response.header.query.QueryResponseHeader;
import
org.apache.shardingsphere.proxy.backend.response.header.update.MultiStatementsUpdateResponseHeader;
@@ -46,9 +32,6 @@ import
org.apache.shardingsphere.proxy.backend.response.header.update.UpdateResp
import org.apache.shardingsphere.proxy.backend.session.ConnectionSession;
import org.apache.shardingsphere.proxy.frontend.command.executor.ResponseType;
import
org.apache.shardingsphere.sql.parser.statement.core.statement.SQLStatement;
-import
org.apache.shardingsphere.sql.parser.statement.core.value.identifier.IdentifierValue;
-import org.apache.shardingsphere.sqltranslator.rule.SQLTranslatorRule;
-import
org.apache.shardingsphere.sqltranslator.rule.builder.DefaultSQLTranslatorRuleConfigurationBuilder;
import
org.apache.shardingsphere.test.infra.framework.extension.mock.AutoMockExtension;
import
org.apache.shardingsphere.test.infra.framework.extension.mock.StaticMockSettings;
import org.junit.jupiter.api.BeforeEach;
@@ -56,36 +39,29 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Answers;
import org.mockito.Mock;
-import org.mockito.internal.configuration.plugins.Plugins;
import org.mockito.junit.jupiter.MockitoSettings;
-import org.mockito.plugins.MemberAccessor;
import org.mockito.quality.Strictness;
import java.sql.SQLException;
-import java.sql.Types;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
-import java.util.Properties;
-import static org.hamcrest.Matchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.isA;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
-import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ExtendWith(AutoMockExtension.class)
-@StaticMockSettings(ProxyContext.class)
+@StaticMockSettings(MySQLComQueryBackendHandlerFactory.class)
@MockitoSettings(strictness = Strictness.LENIENT)
class MySQLComQueryPacketExecutorTest {
- private final DatabaseType databaseType =
TypedSPILoader.getService(DatabaseType.class, "MySQL");
-
@Mock
private ProxyBackendHandler proxyBackendHandler;
@@ -96,15 +72,15 @@ class MySQLComQueryPacketExecutorTest {
private ConnectionSession connectionSession;
@BeforeEach
- void setUp() {
- when(packet.getSQL()).thenReturn("");
+ void setUp() throws SQLException {
when(connectionSession.getAttributeMap().attr(MySQLConstants.CHARACTER_SET_ATTRIBUTE_KEY).get()).thenReturn(MySQLCharacterSets.UTF8MB4_GENERAL_CI);
+ when(MySQLComQueryBackendHandlerFactory.newInstance(packet,
connectionSession)).thenReturn(proxyBackendHandler);
+ when(proxyBackendHandler.getRowData()).thenReturn(new
QueryResponseRow(Collections.emptyList()));
}
@Test
- void assertIsQueryResponse() throws SQLException, NoSuchFieldException,
IllegalAccessException {
+ void assertIsQueryResponse() throws SQLException {
MySQLComQueryPacketExecutor mysqlComQueryPacketExecutor = new
MySQLComQueryPacketExecutor(packet, connectionSession);
-
Plugins.getMemberAccessor().set(MySQLComQueryPacketExecutor.class.getDeclaredField("proxyBackendHandler"),
mysqlComQueryPacketExecutor, proxyBackendHandler);
QueryHeader queryHeader = mock(QueryHeader.class);
when(queryHeader.getColumnTypeName()).thenReturn("VARCHAR");
when(proxyBackendHandler.execute()).thenReturn(new
QueryResponseHeader(Collections.singletonList(queryHeader)));
@@ -113,45 +89,18 @@ class MySQLComQueryPacketExecutorTest {
}
@Test
- void assertIsUpdateResponse() throws SQLException, NoSuchFieldException,
IllegalAccessException {
- MySQLComQueryPacketExecutor mysqlComQueryPacketExecutor = new
MySQLComQueryPacketExecutor(packet, connectionSession);
-
Plugins.getMemberAccessor().set(MySQLComQueryPacketExecutor.class.getDeclaredField("proxyBackendHandler"),
mysqlComQueryPacketExecutor, proxyBackendHandler);
- when(proxyBackendHandler.execute()).thenReturn(new
UpdateResponseHeader(mock(SQLStatement.class)));
- mysqlComQueryPacketExecutor.execute();
- assertThat(mysqlComQueryPacketExecutor.getResponseType(),
is(ResponseType.UPDATE));
- }
-
- @Test
- void assertExecuteMultiUpdateStatements() throws SQLException,
NoSuchFieldException, IllegalAccessException {
-
when(connectionSession.getAttributeMap().hasAttr(MySQLConstants.OPTION_MULTI_STATEMENTS_ATTRIBUTE_KEY)).thenReturn(true);
-
when(connectionSession.getAttributeMap().attr(MySQLConstants.OPTION_MULTI_STATEMENTS_ATTRIBUTE_KEY).get()).thenReturn(0);
- when(connectionSession.getCurrentDatabaseName()).thenReturn("foo_db");
- when(packet.getSQL()).thenReturn("UPDATE t SET v=v+1 WHERE id=1;UPDATE
t SET v=v+1 WHERE id=2;UPDATE t SET v=v+1 WHERE id=3");
- ContextManager contextManager = mock(ContextManager.class);
- MetaDataContexts metaDataContexts = mockMetaDataContexts();
-
when(contextManager.getMetaDataContexts()).thenReturn(metaDataContexts);
-
when(ProxyContext.getInstance().getContextManager()).thenReturn(contextManager);
+ void assertExecuteUpdateResponse() throws SQLException {
MySQLComQueryPacketExecutor actual = new
MySQLComQueryPacketExecutor(packet, connectionSession);
-
Plugins.getMemberAccessor().set(MySQLComQueryPacketExecutor.class.getDeclaredField("proxyBackendHandler"),
actual, proxyBackendHandler);
when(proxyBackendHandler.execute()).thenReturn(new
UpdateResponseHeader(mock(SQLStatement.class)));
Collection<DatabasePacket> actualPackets = actual.execute();
+ assertThat(actual.getResponseType(), is(ResponseType.UPDATE));
assertThat(actualPackets.size(), is(1));
assertThat(actualPackets.iterator().next(), isA(MySQLOKPacket.class));
}
@Test
- void assertExecuteMultiInsertOnDuplicateKeyStatements() throws
SQLException, NoSuchFieldException, IllegalAccessException {
-
when(connectionSession.getAttributeMap().hasAttr(MySQLConstants.OPTION_MULTI_STATEMENTS_ATTRIBUTE_KEY)).thenReturn(true);
-
when(connectionSession.getAttributeMap().attr(MySQLConstants.OPTION_MULTI_STATEMENTS_ATTRIBUTE_KEY).get()).thenReturn(0);
- when(connectionSession.getCurrentDatabaseName()).thenReturn("foo_db");
- when(packet.getSQL()).thenReturn("INSERT INTO t (id, v) VALUES(1,1) ON
DUPLICATE KEY UPDATE v=2;INSERT INTO t (id, v) VALUES(2,1) ON DUPLICATE KEY
UPDATE v=3");
- ContextManager contextManager = mock(ContextManager.class,
RETURNS_DEEP_STUBS);
- MetaDataContexts metaDataContexts = mockMetaDataContexts();
-
when(contextManager.getMetaDataContexts()).thenReturn(metaDataContexts);
-
when(ProxyContext.getInstance().getContextManager()).thenReturn(contextManager);
+ void assertExecuteMultiStatementsUpdateResponse() throws SQLException {
MySQLComQueryPacketExecutor actual = new
MySQLComQueryPacketExecutor(packet, connectionSession);
- MemberAccessor accessor = Plugins.getMemberAccessor();
-
accessor.set(MySQLComQueryPacketExecutor.class.getDeclaredField("proxyBackendHandler"),
actual, proxyBackendHandler);
when(proxyBackendHandler.execute())
.thenReturn(new
MultiStatementsUpdateResponseHeader(Arrays.asList(new
UpdateResponseHeader(mock(SQLStatement.class)), new
UpdateResponseHeader(mock(SQLStatement.class)))));
Collection<DatabasePacket> actualPackets = actual.execute();
@@ -161,34 +110,9 @@ class MySQLComQueryPacketExecutorTest {
assertThat(iterator.next(), isA(MySQLOKPacket.class));
}
- private MetaDataContexts mockMetaDataContexts() {
- MetaDataContexts result = mock(MetaDataContexts.class,
RETURNS_DEEP_STUBS);
- ShardingSphereDatabase database = createDatabase();
- when(result.getMetaData().getDatabase("foo_db")).thenReturn(database);
- when(result.getMetaData().getDatabase(new
IdentifierValue("foo_db"))).thenReturn(database);
- RuleMetaData globalRuleMetaData = new RuleMetaData(
- Arrays.asList(new SQLParserRule(new
DefaultSQLParserRuleConfigurationBuilder().build()), new SQLTranslatorRule(new
DefaultSQLTranslatorRuleConfigurationBuilder().build())));
-
when(result.getMetaData().getGlobalRuleMetaData()).thenReturn(globalRuleMetaData);
- Properties props = new Properties();
-
props.setProperty(ConfigurationPropertyKey.KERNEL_EXECUTOR_SIZE.getKey(), "1");
- when(result.getMetaData().getProps()).thenReturn(new
ConfigurationProperties(props));
- when(result.getMetaData().containsDatabase("foo_db")).thenReturn(true);
- when(result.getMetaData().containsDatabase(new
IdentifierValue("foo_db"))).thenReturn(true);
- return result;
- }
-
- private ShardingSphereDatabase createDatabase() {
- ShardingSphereTable table = new ShardingSphereTable("t",
Arrays.asList(new ShardingSphereColumn("id", Types.BIGINT, true, false, false,
false, true, false),
- new ShardingSphereColumn("v", Types.INTEGER, false, false,
false, false, true, false)), Collections.emptyList(), Collections.emptyList());
- ShardingSphereSchema schema = new ShardingSphereSchema("foo_db",
databaseType, Collections.singleton(table), Collections.emptyList());
- return new ShardingSphereDatabase("foo_db", databaseType, new
ResourceMetaData(Collections.emptyMap()), new
RuleMetaData(Collections.emptyList()), Collections.singleton(schema),
- new ConfigurationProperties(new Properties()));
- }
-
@Test
- void assertNext() throws SQLException, NoSuchFieldException,
IllegalAccessException {
+ void assertNext() throws SQLException {
MySQLComQueryPacketExecutor actual = new
MySQLComQueryPacketExecutor(packet, connectionSession);
-
Plugins.getMemberAccessor().set(MySQLComQueryPacketExecutor.class.getDeclaredField("proxyBackendHandler"),
actual, proxyBackendHandler);
when(proxyBackendHandler.next()).thenReturn(true, false);
assertTrue(actual.next());
assertFalse(actual.next());
@@ -200,9 +124,8 @@ class MySQLComQueryPacketExecutorTest {
}
@Test
- void assertClose() throws SQLException, NoSuchFieldException,
IllegalAccessException {
+ void assertClose() throws SQLException {
MySQLComQueryPacketExecutor actual = new
MySQLComQueryPacketExecutor(packet, connectionSession);
-
Plugins.getMemberAccessor().set(MySQLComQueryPacketExecutor.class.getDeclaredField("proxyBackendHandler"),
actual, proxyBackendHandler);
actual.close();
verify(proxyBackendHandler).close();
}