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 d20750a5270 Refactor MySQL COM_QUERY binary parameter scanning (#40335)
d20750a5270 is described below
commit d20750a5270d74e895109e1ec3307f80eafe2171
Author: Liang Zhang <[email protected]>
AuthorDate: Thu Oct 22 19:00:22 2026 +0800
Refactor MySQL COM_QUERY binary parameter scanning (#40335)
---
.../MySQLComQueryBinaryParameterExtractor.java | 88 ++++++++++++++--------
.../MySQLComQueryBinaryParameterExtractorTest.java | 56 ++++++++++++++
2 files changed, 112 insertions(+), 32 deletions(-)
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
index 929b50be802..22227b82b5f 100644
---
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
@@ -92,43 +92,49 @@ final class MySQLComQueryBinaryParameterExtractor {
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;
+ int index = findNextQuotedLiteralStart(0);
+ while (index < sql.length) {
+ QuotedLiteral literal = findQuotedLiteral(index, sql[index]);
+ if ('\'' == sql[index] && !literal.isClosed()) {
+ return Collections.emptyList();
+ }
+ boolean parameterLiteral = isParameterLiteral(sql[index], literal);
+ if (null != previousLiteral && (previousLiteralIsParameter ||
parameterLiteral) && areAdjacent(previousLiteral, literal)) {
+ return Collections.emptyList();
+ }
+ if (parameterLiteral) {
+ result.add(literal);
}
+ previousLiteral = literal;
+ previousLiteralIsParameter = parameterLiteral;
+ index = findNextQuotedLiteralStart(literal.isClosed() ?
literal.endIndex + 1 : sql.length);
}
return result;
}
- private boolean isParameterLiteral(final byte quote, final QuotedLiteral
literal) {
- return '\'' == quote && literal.containsMalformedBytes;
+ private int findNextQuotedLiteralStart(final int startIndex) {
+ int result = startIndex;
+ while (result < sql.length) {
+ if ('#' == sql[result]) {
+ result = skipLineComment(result + 1);
+ } else if (isDashComment(result)) {
+ result = skipLineComment(result + 2);
+ } else if (isBlockComment(result)) {
+ result = skipBlockComment(result + 2);
+ } else if ('`' == sql[result] || '"' == sql[result] || '\'' ==
sql[result]) {
+ return result;
+ } else {
+ int characterLength = readCharacterLength(result);
+ result += 0 < characterLength ? characterLength : 1;
+ }
+ }
+ return result;
}
private QuotedLiteral findQuotedLiteral(final int startIndex, final byte
quote) {
boolean containsMalformedBytes = false;
- for (int i = startIndex + 1; i < sql.length;) {
+ int i = startIndex + 1;
+ while (i < sql.length) {
int characterLength = readCharacterLength(i);
if (0 > characterLength) {
containsMalformedBytes = true;
@@ -152,6 +158,10 @@ final class MySQLComQueryBinaryParameterExtractor {
return new QuotedLiteral(startIndex, -1, containsMalformedBytes);
}
+ private boolean isParameterLiteral(final byte quote, final QuotedLiteral
literal) {
+ return '\'' == quote && literal.containsMalformedBytes;
+ }
+
private boolean areAdjacent(final QuotedLiteral previousLiteral, final
QuotedLiteral currentLiteral) {
return containsOnlyTrivia(previousLiteral.endIndex + 1,
findLiteralTokenStart(currentLiteral.startIndex));
}
@@ -170,7 +180,8 @@ final class MySQLComQueryBinaryParameterExtractor {
}
private boolean containsOnlyTrivia(final int startIndex, final int
endIndex) {
- for (int i = startIndex; i < endIndex;) {
+ int i = startIndex;
+ while (i < endIndex) {
if (isWhitespace(sql[i])) {
i++;
} else if ('#' == sql[i]) {
@@ -230,8 +241,11 @@ final class MySQLComQueryBinaryParameterExtractor {
return false;
}
int value = Byte.toUnsignedInt(sql[index]);
+ if (0x80 <= value || '_' == value || '$' == value) {
+ return true;
+ }
byte lowerCaseValue = toLowerCase(sql[index]);
- return 0x80 <= value || '_' == value || '$' == value || '0' <= value
&& '9' >= value || 'a' <= lowerCaseValue && 'z' >= lowerCaseValue;
+ return '0' <= value && '9' >= value || 'a' <= lowerCaseValue && 'z' >=
lowerCaseValue;
}
private static byte toLowerCase(final byte value) {
@@ -239,7 +253,16 @@ final class MySQLComQueryBinaryParameterExtractor {
}
private static boolean isWhitespace(final byte value) {
- return ' ' == value || '\t' == value || '\n' == value || '\r' == value
|| '\f' == value;
+ switch (value) {
+ case ' ':
+ case '\t':
+ case '\n':
+ case '\r':
+ case '\f':
+ return true;
+ default:
+ return false;
+ }
}
private boolean isDashComment(final int index) {
@@ -269,7 +292,8 @@ final class MySQLComQueryBinaryParameterExtractor {
private byte[] unescape(final int startIndex, final int endIndex) {
ByteArrayOutputStream result = new ByteArrayOutputStream(endIndex -
startIndex);
- for (int i = startIndex; i < endIndex;) {
+ int i = startIndex;
+ while (i < endIndex) {
int characterLength = readCharacterLength(i);
if (1 < characterLength) {
result.write(sql, i, characterLength);
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
index eb02508c177..ecef0fe08be 100644
---
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
@@ -18,12 +18,17 @@
package
org.apache.shardingsphere.proxy.frontend.mysql.command.query.text.query;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.junit.jupiter.params.provider.ValueSource;
import java.io.ByteArrayOutputStream;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.sql.Blob;
import java.sql.SQLException;
+import java.util.stream.Stream;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
@@ -67,6 +72,16 @@ class MySQLComQueryBinaryParameterExtractorTest {
assertThat(getBlobBytes(actual.getBinaryLiteralValues().get(0)),
is(new byte[]{(byte) 0xFF}));
}
+ @Test
+ void assertExtractMultipleMalformedLiterals() throws SQLException {
+ byte[] content = {(byte) 0xFF, '\'', ',', ' ', '\'', '\\', 'n', (byte)
0xFE};
+ MySQLComQueryBinaryParameterExtractor.ExtractionResult actual =
MySQLComQueryBinaryParameterExtractor.extract(sql("SELECT _binary'", content,
"'"), StandardCharsets.UTF_8);
+ assertThat(actual.getSql(), is("SELECT ?, ?"));
+ assertThat(actual.getBinaryLiteralValues().size(), is(2));
+ assertThat(getBlobBytes(actual.getBinaryLiteralValues().get(0)),
is(new byte[]{(byte) 0xFF}));
+ assertThat(getBlobBytes(actual.getBinaryLiteralValues().get(1)),
is(new byte[]{'\n', (byte) 0xFE}));
+ }
+
@Test
void assertSkipValidLiteral() throws SQLException {
String sql = "SELECT '😀', _binary'foo'";
@@ -96,6 +111,14 @@ class MySQLComQueryBinaryParameterExtractorTest {
assertTrue(actual.getBinaryLiteralValues().isEmpty());
}
+ @Test
+ void assertDiscardParametersBeforeUnclosedLiteral() throws SQLException {
+ byte[] sql = sql("SELECT _binary'", new byte[]{(byte) 0xFF}, "',
'foo");
+ MySQLComQueryBinaryParameterExtractor.ExtractionResult actual =
MySQLComQueryBinaryParameterExtractor.extract(sql, StandardCharsets.UTF_8);
+ assertThat(actual.getSql(), is(new String(sql,
StandardCharsets.UTF_8)));
+ assertTrue(actual.getBinaryLiteralValues().isEmpty());
+ }
+
@Test
void assertSkipDoubleQuotedLiteral() throws SQLException {
MySQLComQueryBinaryParameterExtractor.ExtractionResult actual =
MySQLComQueryBinaryParameterExtractor.extract(
@@ -110,6 +133,39 @@ class MySQLComQueryBinaryParameterExtractorTest {
assertTrue(actual.getBinaryLiteralValues().isEmpty());
}
+ @ParameterizedTest(name = "{index}: separator={0}")
+ @ValueSource(strings = {" ", "\t\n\r\f", " # foo\n ", " -- foo\n ", " /*
foo */ "})
+ void assertSkipAdjacentMalformedLiteral(final String separator) throws
SQLException {
+ byte[] sql = sql("SELECT 'foo'" + separator + "'", new byte[]{(byte)
0xFF}, "'");
+ MySQLComQueryBinaryParameterExtractor.ExtractionResult actual =
MySQLComQueryBinaryParameterExtractor.extract(sql, StandardCharsets.UTF_8);
+ assertThat(actual.getSql(), is(new String(sql,
StandardCharsets.UTF_8)));
+ assertTrue(actual.getBinaryLiteralValues().isEmpty());
+ }
+
+ @ParameterizedTest(name = "{index}: {0}")
+ @MethodSource("getMalformedLiteralAfterIgnoredQuoteArgs")
+ void assertExtractMalformedLiteralAfterIgnoredQuote(final String prefix,
final String suffix, final String expectedSql) throws SQLException {
+ byte[] prefixBytes = sql(prefix, new byte[]{(byte) 0xFF}, suffix);
+ ByteArrayOutputStream sql = new ByteArrayOutputStream();
+ sql.write(prefixBytes, 0, prefixBytes.length);
+ sql.write('\'');
+ sql.write(0xFE);
+ sql.write('\'');
+ MySQLComQueryBinaryParameterExtractor.ExtractionResult actual =
MySQLComQueryBinaryParameterExtractor.extract(sql.toByteArray(),
StandardCharsets.UTF_8);
+ assertThat(actual.getSql(), is(expectedSql));
+ assertThat(actual.getBinaryLiteralValues().size(), is(1));
+ assertThat(getBlobBytes(actual.getBinaryLiteralValues().get(0)),
is(new byte[]{(byte) 0xFE}));
+ }
+
+ private static Stream<Arguments>
getMalformedLiteralAfterIgnoredQuoteArgs() {
+ return Stream.of(
+ Arguments.of("SELECT # '", "'\n", "SELECT # '�'\n?"),
+ Arguments.of("SELECT -- '", "'\n", "SELECT -- '�'\n?"),
+ Arguments.of("SELECT /* '", "' */ ", "SELECT /* '�' */ ?"),
+ Arguments.of("SELECT \"'", "'\", ", "SELECT \"'�'\", ?"),
+ Arguments.of("SELECT `'", "'`, ", "SELECT `'�'`, ?"));
+ }
+
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);