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);

Reply via email to