You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@shardingsphere.apache.org by zh...@apache.org on 2026/08/13 12:38:09 UTC

(shardingsphere) branch master updated: Refactor MySQL COM_QUERY original SQL byte handling (#39444)

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 113a5739477 Refactor MySQL COM_QUERY original SQL byte handling (#39444)
113a5739477 is described below

commit 113a5739477a69a0f082e968ab4c13aa0fc8064f
Author: Liang Zhang <zh...@apache.org>
AuthorDate: Thu Aug 13 20:38:02 2026 +0800

    Refactor MySQL COM_QUERY original SQL byte handling (#39444)
    
    * Refine gen-ut quality gates
    
    - keep semantic decisions out of mechanical rule scans
    - harden coverage and task-scope verification
    - remove unsafe gate-result reuse and add regression tests
    
    * Refine gen-ut quality gates
    
    - keep semantic decisions out of mechanical rule scans
    - harden coverage and task-scope verification
    - remove unsafe gate-result reuse and add regression tests
    
    * Refine gen-ut quality gates
    
    - keep semantic decisions out of mechanical rule scans
    - harden coverage and task-scope verification
    - remove unsafe gate-result reuse and add regression tests
    
    * Refine gen-ut quality gates
    
    - keep semantic decisions out of mechanical rule scans
    - harden coverage and task-scope verification
    - remove unsafe gate-result reuse and add regression tests
    
    * Refactor MySQL COM_QUERY original SQL byte handling
---
 .../query/text/query/MySQLComQueryPacket.java      | 20 +++------------
 .../query/text/query/MySQLComQueryPacketTest.java  | 11 +-------
 .../query/MySQLComQueryBackendHandlerFactory.java  | 16 +++++++++++-
 .../MySQLComQueryBackendHandlerFactoryTest.java    | 30 +++++++++++++++++++---
 4 files changed, 46 insertions(+), 31 deletions(-)

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 f60e753954c..c06f63d00e1 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
@@ -34,10 +34,6 @@ import java.util.Optional;
  */
 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;
@@ -58,17 +54,7 @@ public final class MySQLComQueryPacket extends MySQLCommandPacket implements SQL
         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;
+        originalSQLBytes = sqlBytes;
     }
     
     @Override
@@ -82,9 +68,9 @@ public final class MySQLComQueryPacket extends MySQLCommandPacket implements SQL
     }
     
     /**
-     * Find original SQL bytes retained for binary literal inspection.
+     * Find original SQL bytes received from packet payload.
      *
-     * @return original SQL bytes when binary literal inspection is required
+     * @return original SQL bytes if constructed from packet payload
      */
     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 70ea2448140..baa2b461b07 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
@@ -60,16 +60,7 @@ class MySQLComQueryPacketTest {
         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());
+        assertArrayEquals(inputSQL.getBytes(StandardCharsets.UTF_8), 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
index 6f0fd34004d..e97fdf9cb91 100644
--- 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
@@ -54,6 +54,10 @@ import java.util.Optional;
 @NoArgsConstructor(access = AccessLevel.PRIVATE)
 final class MySQLComQueryBackendHandlerFactory {
     
+    private static final String BINARY_INTRODUCER = "_binary";
+    
+    private static final char MALFORMED_INPUT_REPLACEMENT = (char) 0xFFFD;
+    
     static ProxyBackendHandler newInstance(final MySQLComQueryPacket packet, final ConnectionSession connectionSession) throws SQLException {
         DatabaseType databaseType = TypedSPILoader.getService(DatabaseType.class, "MySQL");
         String sql = packet.getSQL();
@@ -70,7 +74,7 @@ final class MySQLComQueryBackendHandlerFactory {
     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()) {
+        if (!originalSQLBytes.isPresent() || !requiresBinaryLiteralInspection(sql) || 1 != MultiSQLSplitter.split(sql).size()) {
             return Optional.empty();
         }
         ExtractionResult extractionResult = MySQLComQueryBinaryParameterExtractor.extract(
@@ -96,6 +100,16 @@ final class MySQLComQueryBackendHandlerFactory {
         return Optional.of(ProxyBackendHandlerFactory.newInstance(databaseType, queryContext, connectionSession, true));
     }
     
+    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;
+    }
+    
     private static boolean areMultiStatements(final ConnectionSession connectionSession, final SQLStatement sqlStatement, final String sql) {
         return isMultiStatementsEnabled(connectionSession)
                 && isSuitableMultiStatementsSQLStatement(sqlStatement)
diff --git a/proxy/frontend/dialect/mysql/src/test/java/org/apache/shardingsphere/proxy/frontend/mysql/command/query/text/query/MySQLComQueryBackendHandlerFactoryTest.java b/proxy/frontend/dialect/mysql/src/test/java/org/apache/shardingsphere/proxy/frontend/mysql/command/query/text/query/MySQLComQueryBackendHandlerFactoryTest.java
index 1e9259bef69..fa6ad265962 100644
--- a/proxy/frontend/dialect/mysql/src/test/java/org/apache/shardingsphere/proxy/frontend/mysql/command/query/text/query/MySQLComQueryBackendHandlerFactoryTest.java
+++ b/proxy/frontend/dialect/mysql/src/test/java/org/apache/shardingsphere/proxy/frontend/mysql/command/query/text/query/MySQLComQueryBackendHandlerFactoryTest.java
@@ -98,7 +98,7 @@ class MySQLComQueryBackendHandlerFactoryTest {
     @BeforeEach
     void setUp() throws SQLException {
         when(packet.getSQL()).thenReturn("");
-        when(packet.findOriginalSQLBytes()).thenReturn(Optional.empty());
+        when(packet.findOriginalSQLBytes()).thenReturn(Optional.of(new byte[0]));
         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);
@@ -107,7 +107,17 @@ class MySQLComQueryBackendHandlerFactoryTest {
     
     @Test
     void assertCreateStandardSQLHandler() throws SQLException {
+        String sql = "SELECT 1";
+        when(packet.getSQL()).thenReturn(sql);
+        when(packet.findOriginalSQLBytes()).thenReturn(Optional.of(sql.getBytes(StandardCharsets.UTF_8)));
+        ProxyBackendHandler actual = MySQLComQueryBackendHandlerFactory.newInstance(packet, connectionSession);
+        assertThat(actual, is(proxyBackendHandler));
+    }
+    
+    @Test
+    void assertCreateStandardSQLHandlerWithoutOriginalSQLBytes() throws SQLException {
         when(packet.getSQL()).thenReturn("SELECT 1");
+        when(packet.findOriginalSQLBytes()).thenReturn(Optional.empty());
         ProxyBackendHandler actual = MySQLComQueryBackendHandlerFactory.newInstance(packet, connectionSession);
         assertThat(actual, is(proxyBackendHandler));
     }
@@ -115,7 +125,9 @@ class MySQLComQueryBackendHandlerFactoryTest {
     @Test
     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");
+        String sql = "UPDATE t SET v=v+1 WHERE id=1;UPDATE t SET v=v+1 WHERE id=2";
+        when(packet.getSQL()).thenReturn(sql);
+        when(packet.findOriginalSQLBytes()).thenReturn(Optional.of(sql.getBytes(StandardCharsets.UTF_8)));
         ProxyBackendHandler actual = MySQLComQueryBackendHandlerFactory.newInstance(packet, connectionSession);
         assertThat(actual, isA(MySQLMultiStatementsProxyBackendHandler.class));
     }
@@ -123,7 +135,9 @@ class MySQLComQueryBackendHandlerFactoryTest {
     @Test
     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");
+        String sql = "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";
+        when(packet.getSQL()).thenReturn(sql);
+        when(packet.findOriginalSQLBytes()).thenReturn(Optional.of(sql.getBytes(StandardCharsets.UTF_8)));
         ProxyBackendHandler actual = MySQLComQueryBackendHandlerFactory.newInstance(packet, connectionSession);
         assertThat(actual, isA(MySQLMultiStatementsProxyBackendHandler.class));
     }
@@ -151,6 +165,16 @@ class MySQLComQueryBackendHandlerFactoryTest {
         assertArrayEquals(new byte[]{(byte) 0xFF}, blob.getBytes(1, (int) blob.length()));
     }
     
+    @Test
+    void assertCreateStandardHandlerForValidBinaryLiteral() throws SQLException {
+        String sql = "SELECT _binary'foo'";
+        when(packet.getSQL()).thenReturn(sql);
+        when(packet.findOriginalSQLBytes()).thenReturn(Optional.of(sql.getBytes(StandardCharsets.UTF_8)));
+        when(connectionSession.getAttributeMap().attr(CommonConstants.CHARSET_ATTRIBUTE_KEY).get()).thenReturn(StandardCharsets.UTF_8);
+        ProxyBackendHandler actual = MySQLComQueryBackendHandlerFactory.newInstance(packet, connectionSession);
+        assertThat(actual, is(proxyBackendHandler));
+    }
+    
     @Test
     void assertCreateMultiStatementsHandlerForMalformedBinaryLiteral() throws SQLException {
         enableMultiStatements();