This is an automated email from the ASF dual-hosted git repository.

morningman pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/master by this push:
     new d4cc96a5cf5 [fix](protocol) Support CLIENT_DEPRECATE_EOF to fix empty 
result with MySQL driver 9.5.0 (#61050)
d4cc96a5cf5 is described below

commit d4cc96a5cf5be0f3a106fd1dc524e34243b58c8c
Author: Mingyu Chen (Rayner) <[email protected]>
AuthorDate: Wed Mar 4 22:45:58 2026 -0800

    [fix](protocol) Support CLIENT_DEPRECATE_EOF to fix empty result with MySQL 
driver 9.5.0 (#61050)
    
    ### What problem does this PR solve?
    
    Issue Number: close #60634
    
    Problem Summary:
    
    When using MySQL Connector/J 9.5.0+ with useServerPrepStmts=true,
    queries return empty result sets. This is caused by a behavior change in
    the driver's BinaryResultsetReader that no longer consumes the
    intermediate EOF packet after column definitions when no cursor exists.
    
    The fix properly implements the CLIENT_DEPRECATE_EOF capability:
    
    1. Advertise CLIENT_DEPRECATE_EOF in server handshake capability flags
    (MysqlCapability.java), so the driver negotiates it and uses the
    isEOFDeprecated()=true code path, bypassing the buggy 9.5.0 logic.
    
    2. Skip intermediate EOF packets after column definitions when
    CLIENT_DEPRECATE_EOF is negotiated (StmtExecutor.sendMetaData,
    sendFields, sendStmtPrepareOK).
    
    3. Send a 'ResultSet OK' packet (0xFE header, payload > 5 bytes) as the
    final end-of-result-set marker instead of the traditional EOF packet, so
    the driver's isResultSetOKPacket() correctly identifies it
    (MysqlResultSetEndPacket.java, ConnectProcessor.getResultPacket).
    
    4. Always write the info field in MysqlOkPacket (even when empty) to
    prevent ArrayIndexOutOfBoundsException when the driver parses OK packets
    via OkPacket.parse() which unconditionally reads STRING_LENENC.
---
 .../org/apache/doris/mysql/MysqlCapability.java    |   7 +-
 .../java/org/apache/doris/mysql/MysqlOkPacket.java |   9 +-
 .../doris/mysql/MysqlResultSetEndPacket.java       |  61 +++++++++++
 .../java/org/apache/doris/qe/ConnectProcessor.java |  19 +++-
 .../java/org/apache/doris/qe/FEOpExecutor.java     |   4 +
 .../java/org/apache/doris/qe/StmtExecutor.java     |  49 +++++----
 .../apache/doris/mysql/MysqlCapabilityTest.java    |   3 +-
 .../org/apache/doris/mysql/MysqlOkPacketTest.java  |  23 +++-
 .../doris/mysql/MysqlResultSetEndPacketTest.java   | 120 +++++++++++++++++++++
 gensrc/thrift/FrontendService.thrift               |   2 +
 10 files changed, 266 insertions(+), 31 deletions(-)

diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/mysql/MysqlCapability.java 
b/fe/fe-core/src/main/java/org/apache/doris/mysql/MysqlCapability.java
index cbfa88038be..d58f046eb53 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/mysql/MysqlCapability.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/MysqlCapability.java
@@ -74,13 +74,14 @@ public class MysqlCapability {
 
     private static final int DEFAULT_FLAGS = 
Flag.CLIENT_PROTOCOL_41.getFlagBit()
             | Flag.CLIENT_CONNECT_WITH_DB.getFlagBit() | 
Flag.CLIENT_SECURE_CONNECTION.getFlagBit()
-            | Flag.CLIENT_PLUGIN_AUTH.getFlagBit() | 
Flag.CLIENT_LOCAL_FILES.getFlagBit() | Flag.CLIENT_LONG_FLAG
-            .getFlagBit();
+            | Flag.CLIENT_PLUGIN_AUTH.getFlagBit() | 
Flag.CLIENT_LOCAL_FILES.getFlagBit()
+            | Flag.CLIENT_LONG_FLAG.getFlagBit() | 
Flag.CLIENT_DEPRECATE_EOF.getFlagBit();
 
     private static final int SSL_FLAGS = Flag.CLIENT_PROTOCOL_41.getFlagBit()
             | Flag.CLIENT_CONNECT_WITH_DB.getFlagBit() | 
Flag.CLIENT_SECURE_CONNECTION.getFlagBit()
             | Flag.CLIENT_PLUGIN_AUTH.getFlagBit() | 
Flag.CLIENT_LOCAL_FILES.getFlagBit()
-            | Flag.CLIENT_LONG_FLAG.getFlagBit() | 
Flag.CLIENT_SSL.getFlagBit();
+            | Flag.CLIENT_LONG_FLAG.getFlagBit() | Flag.CLIENT_SSL.getFlagBit()
+            | Flag.CLIENT_DEPRECATE_EOF.getFlagBit();
 
     public static final MysqlCapability DEFAULT_CAPABILITY = new 
MysqlCapability(DEFAULT_FLAGS);
     public static final MysqlCapability SSL_CAPABILITY = new 
MysqlCapability(SSL_FLAGS);
diff --git a/fe/fe-core/src/main/java/org/apache/doris/mysql/MysqlOkPacket.java 
b/fe/fe-core/src/main/java/org/apache/doris/mysql/MysqlOkPacket.java
index 778dfd8c3c4..4fa80102317 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/mysql/MysqlOkPacket.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/mysql/MysqlOkPacket.java
@@ -59,8 +59,13 @@ public class MysqlOkPacket extends MysqlPacket {
             // if ((STATUS_FLAGS & 
MysqlStatusFlag.SERVER_SESSION_STATE_CHANGED) != 0) {
             // }
         } else {
-            if (!Strings.isNullOrEmpty(infoMessage)) {
-                // NOTE: in datasheet, use EOF string, but in the code, mysql 
use length encoded string
+            // Always write the info field as a length-encoded string.
+            // When CLIENT_DEPRECATE_EOF is negotiated, the driver's 
OkPacket.parse()
+            // unconditionally reads STRING_LENENC for info, so an empty 
string must
+            // still be written (as a single 0x00 byte representing length 0).
+            if (Strings.isNullOrEmpty(infoMessage)) {
+                serializer.writeVInt(0);
+            } else {
                 serializer.writeLenEncodedString(infoMessage);
             }
         }
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/mysql/MysqlResultSetEndPacket.java 
b/fe/fe-core/src/main/java/org/apache/doris/mysql/MysqlResultSetEndPacket.java
new file mode 100644
index 00000000000..5543b85c361
--- /dev/null
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/mysql/MysqlResultSetEndPacket.java
@@ -0,0 +1,61 @@
+// 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.doris.mysql;
+
+import org.apache.doris.qe.QueryState;
+
+/**
+ * MySQL OK packet with 0xFE header, used as the end-of-result-set marker
+ * when CLIENT_DEPRECATE_EOF is set.
+ *
+ * When CLIENT_DEPRECATE_EOF capability is negotiated, the traditional EOF 
packet (0xFE, ≤5 bytes)
+ * is replaced by an OK packet that also uses 0xFE as its header byte but has 
a payload larger
+ * than 5 bytes. This is called a "ResultSet OK" packet in the MySQL protocol 
documentation.
+ *
+ * See: 
https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_basic_ok_packet.html
+ */
+public class MysqlResultSetEndPacket extends MysqlPacket {
+    // 0xFE header to distinguish from regular OK (0x00)
+    private static final int RESULTSET_OK_INDICATOR = 0xFE;
+    private static final long AFFECTED_ROWS = 0;
+    private static final long LAST_INSERT_ID = 0;
+
+    private int serverStatus = 0;
+    private int warningCount = 0;
+
+    public MysqlResultSetEndPacket(QueryState state) {
+        this.serverStatus = state.serverStatus;
+    }
+
+    @Override
+    public void writeTo(MysqlSerializer serializer) {
+        // header: 0xFE (same as EOF, but the payload length > 5 distinguishes 
it)
+        serializer.writeInt1(RESULTSET_OK_INDICATOR);
+        // affected_rows: int<lenenc>
+        serializer.writeVInt(AFFECTED_ROWS);
+        // last_insert_id: int<lenenc>
+        serializer.writeVInt(LAST_INSERT_ID);
+        // status_flags: int<2>
+        serializer.writeInt2(serverStatus);
+        // warnings: int<2>
+        serializer.writeInt2(warningCount);
+        // info: string<lenenc> (empty string, written as a single 0x00 byte 
for length 0)
+        // The driver's OkPacket.parse() unconditionally reads this field.
+        serializer.writeVInt(0);
+    }
+}
diff --git a/fe/fe-core/src/main/java/org/apache/doris/qe/ConnectProcessor.java 
b/fe/fe-core/src/main/java/org/apache/doris/qe/ConnectProcessor.java
index d3e5a4652a2..571ce990663 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/qe/ConnectProcessor.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/qe/ConnectProcessor.java
@@ -44,6 +44,7 @@ import org.apache.doris.metric.MetricRepo;
 import org.apache.doris.mysql.MysqlChannel;
 import org.apache.doris.mysql.MysqlCommand;
 import org.apache.doris.mysql.MysqlPacket;
+import org.apache.doris.mysql.MysqlResultSetEndPacket;
 import org.apache.doris.mysql.MysqlSerializer;
 import org.apache.doris.mysql.MysqlServerStatusFlag;
 import org.apache.doris.nereids.SqlCacheContext;
@@ -553,7 +554,17 @@ public abstract class ConnectProcessor {
     // only Mysql protocol
     protected ByteBuffer getResultPacket() {
         Preconditions.checkState(connectType.equals(ConnectType.MYSQL));
-        MysqlPacket packet = ctx.getState().toResponsePacket();
+        MysqlPacket packet;
+        // When CLIENT_DEPRECATE_EOF is set and the state is EOF (end of 
result set),
+        // we need to send a "ResultSet OK" packet (0xFE header with payload > 
5 bytes)
+        // instead of the traditional EOF packet. This is required by the 
MySQL protocol
+        // and expected by MySQL Connector/J 9.5.0+.
+        if (ctx.getState().getStateType() == QueryState.MysqlStateType.EOF
+                && ctx.getMysqlChannel().clientDeprecatedEOF()) {
+            packet = new MysqlResultSetEndPacket(ctx.getState());
+        } else {
+            packet = ctx.getState().toResponsePacket();
+        }
         if (packet == null) {
             // possible two cases:
             // 1. handler has send request
@@ -643,6 +654,12 @@ public abstract class ConnectProcessor {
         // set compute group
         
ctx.setComputeGroup(Env.getCurrentEnv().getAuth().getComputeGroup(ctx.getQualifiedUser()));
 
+        // Propagate the client's CLIENT_DEPRECATE_EOF capability to the proxy 
channel.
+        // This ensures the master generates packets matching the original 
client's protocol.
+        if (request.isSetClientDeprecatedEOF() && 
request.isClientDeprecatedEOF()) {
+            ctx.getMysqlChannel().setClientDeprecatedEOF();
+        }
+
         ctx.setThreadLocalInfo();
         StmtExecutor executor = null;
         try {
diff --git a/fe/fe-core/src/main/java/org/apache/doris/qe/FEOpExecutor.java 
b/fe/fe-core/src/main/java/org/apache/doris/qe/FEOpExecutor.java
index 9dcd379f913..6a461cb5c94 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/qe/FEOpExecutor.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/qe/FEOpExecutor.java
@@ -206,6 +206,10 @@ public class FEOpExecutor {
             }
         }
 
+        // Propagate the client's CLIENT_DEPRECATE_EOF capability so the 
master FE
+        // generates packets matching the original client's protocol 
expectations.
+        
params.setClientDeprecatedEOF(ctx.getMysqlChannel().clientDeprecatedEOF());
+
         return params;
     }
 
diff --git a/fe/fe-core/src/main/java/org/apache/doris/qe/StmtExecutor.java 
b/fe/fe-core/src/main/java/org/apache/doris/qe/StmtExecutor.java
index f2afbc47085..5480c2933b1 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/qe/StmtExecutor.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/qe/StmtExecutor.java
@@ -67,7 +67,6 @@ import org.apache.doris.mysql.FieldInfo;
 import org.apache.doris.mysql.MysqlChannel;
 import org.apache.doris.mysql.MysqlCommand;
 import org.apache.doris.mysql.MysqlEofPacket;
-import org.apache.doris.mysql.MysqlOkPacket;
 import org.apache.doris.mysql.MysqlSerializer;
 import org.apache.doris.mysql.ProxyMysqlChannel;
 import org.apache.doris.nereids.NereidsPlanner;
@@ -1595,11 +1594,15 @@ public class StmtExecutor {
             }
             context.getMysqlChannel().sendOnePacket(serializer.toByteBuffer());
         }
-        // send EOF
-        serializer.reset();
-        MysqlEofPacket eofPacket = new MysqlEofPacket(context.getState());
-        eofPacket.writeTo(serializer);
-        context.getMysqlChannel().sendOnePacket(serializer.toByteBuffer());
+        // When CLIENT_DEPRECATE_EOF is set, the server should not send the 
intermediate
+        // EOF packet after column definitions. The client will go directly 
from column
+        // definitions to reading data rows.
+        if (!context.getMysqlChannel().clientDeprecatedEOF()) {
+            serializer.reset();
+            MysqlEofPacket eofPacket = new MysqlEofPacket(context.getState());
+            eofPacket.writeTo(serializer);
+            context.getMysqlChannel().sendOnePacket(serializer.toByteBuffer());
+        }
     }
 
     private List<PrimitiveType> exprToStringType(List<Expr> exprs) {
@@ -1641,15 +1644,15 @@ public class StmtExecutor {
                 serializer.writeField(colNames.get(i), Type.STRING);
                 
context.getMysqlChannel().sendOnePacket(serializer.toByteBuffer());
             }
-            serializer.reset();
+            // When CLIENT_DEPRECATE_EOF is set, no EOF/OK packet should be 
sent after
+            // parameter definitions. The driver knows how many params to 
expect from the
+            // prepare OK packet and simply stops reading after that count.
             if (!context.getMysqlChannel().clientDeprecatedEOF()) {
+                serializer.reset();
                 MysqlEofPacket eofPacket = new 
MysqlEofPacket(context.getState());
                 eofPacket.writeTo(serializer);
-            } else {
-                MysqlOkPacket okPacket = new MysqlOkPacket(context.getState());
-                okPacket.writeTo(serializer);
+                
context.getMysqlChannel().sendOnePacket(serializer.toByteBuffer());
             }
-            context.getMysqlChannel().sendOnePacket(serializer.toByteBuffer());
         }
         if (numColumns > 0) {
             for (Slot slot : output) {
@@ -1668,15 +1671,15 @@ public class StmtExecutor {
                 }
                 
context.getMysqlChannel().sendOnePacket(serializer.toByteBuffer());
             }
-            serializer.reset();
+            // When CLIENT_DEPRECATE_EOF is set, no EOF/OK packet should be 
sent after
+            // column definitions. The driver knows how many columns to expect 
from the
+            // prepare OK packet and simply stops reading after that count.
             if (!context.getMysqlChannel().clientDeprecatedEOF()) {
+                serializer.reset();
                 MysqlEofPacket eofPacket = new 
MysqlEofPacket(context.getState());
                 eofPacket.writeTo(serializer);
-            } else {
-                MysqlOkPacket okPacket = new MysqlOkPacket(context.getState());
-                okPacket.writeTo(serializer);
+                
context.getMysqlChannel().sendOnePacket(serializer.toByteBuffer());
             }
-            context.getMysqlChannel().sendOnePacket(serializer.toByteBuffer());
         }
         context.getMysqlChannel().flush();
         context.getState().setNoop();
@@ -1726,11 +1729,15 @@ public class StmtExecutor {
                 
context.getMysqlChannel().sendOnePacket(serializer.toByteBuffer());
             }
         }
-        // send EOF
-        serializer.reset();
-        MysqlEofPacket eofPacket = new MysqlEofPacket(context.getState());
-        eofPacket.writeTo(serializer);
-        context.getMysqlChannel().sendOnePacket(serializer.toByteBuffer());
+        // When CLIENT_DEPRECATE_EOF is set, the server should not send the 
intermediate
+        // EOF packet after column definitions. The client will go directly 
from column
+        // definitions to reading data rows.
+        if (!context.getMysqlChannel().clientDeprecatedEOF()) {
+            serializer.reset();
+            MysqlEofPacket eofPacket = new MysqlEofPacket(context.getState());
+            eofPacket.writeTo(serializer);
+            context.getMysqlChannel().sendOnePacket(serializer.toByteBuffer());
+        }
     }
 
     public void sendResultSet(ResultSet resultSet) throws IOException {
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/mysql/MysqlCapabilityTest.java 
b/fe/fe-core/src/test/java/org/apache/doris/mysql/MysqlCapabilityTest.java
index f3b4385b960..78197804b63 100644
--- a/fe/fe-core/src/test/java/org/apache/doris/mysql/MysqlCapabilityTest.java
+++ b/fe/fe-core/src/test/java/org/apache/doris/mysql/MysqlCapabilityTest.java
@@ -44,8 +44,9 @@ public class MysqlCapabilityTest {
     public void testDefaultFlags() {
         MysqlCapability capability = MysqlCapability.DEFAULT_CAPABILITY;
         Assert.assertEquals("CLIENT_LONG_FLAG | CLIENT_CONNECT_WITH_DB | 
CLIENT_LOCAL_FILES | CLIENT_PROTOCOL_41"
-                + " | CLIENT_SECURE_CONNECTION | CLIENT_PLUGIN_AUTH",
+                + " | CLIENT_SECURE_CONNECTION | CLIENT_PLUGIN_AUTH | 
CLIENT_DEPRECATE_EOF",
                 capability.toString());
         Assert.assertTrue(capability.supportClientLocalFile());
+        Assert.assertTrue(capability.isDeprecatedEOF());
     }
 }
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/mysql/MysqlOkPacketTest.java 
b/fe/fe-core/src/test/java/org/apache/doris/mysql/MysqlOkPacketTest.java
index 6cb157ee252..9fe47adbf5e 100644
--- a/fe/fe-core/src/test/java/org/apache/doris/mysql/MysqlOkPacketTest.java
+++ b/fe/fe-core/src/test/java/org/apache/doris/mysql/MysqlOkPacketTest.java
@@ -56,10 +56,27 @@ public class MysqlOkPacketTest {
         // assert warnings, int2: 0
         Assert.assertEquals(0x00, MysqlProto.readInt2(buffer));
 
-        // assert info, eof string: "OK"
-        // Assert.assertEquals("OK", new 
String(MysqlProto.readEofString(buffer)));
-
+        // When infoMessage is empty, an empty len-encoded string (0x00) 
should still be written.
+        // This is required because OkPacket.parse() in MySQL Connector/J 
unconditionally reads
+        // STRING_LENENC for info. Without this byte, the driver throws
+        // ArrayIndexOutOfBoundsException when CLIENT_DEPRECATE_EOF is 
negotiated.
+        Assert.assertEquals(0x00, MysqlProto.readVInt(buffer));
         Assert.assertEquals(0, buffer.remaining());
     }
 
+    @Test
+    public void testWritePayloadSizeGreaterThan5() {
+        // When CLIENT_DEPRECATE_EOF is negotiated, the driver distinguishes 
between
+        // EOF packets (payload <= 5) and ResultSet OK packets (payload > 5).
+        // MysqlOkPacket payload must be > 5 to avoid being misidentified as 
EOF.
+        // Payload: 0x00(1) + affected_rows(1) + last_insert_id(1) + status(2) 
+ warnings(2) + info_len(1) = 8
+        MysqlOkPacket packet = new MysqlOkPacket(new QueryState());
+        MysqlSerializer serializer = MysqlSerializer.newInstance(capability);
+        packet.writeTo(serializer);
+
+        ByteBuffer buffer = serializer.toByteBuffer();
+        int payloadLength = buffer.remaining();
+        Assert.assertTrue("OK packet payload should be > 5 for 
CLIENT_DEPRECATE_EOF compatibility, got: "
+                + payloadLength, payloadLength > 5);
+    }
 }
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/mysql/MysqlResultSetEndPacketTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/mysql/MysqlResultSetEndPacketTest.java
new file mode 100644
index 00000000000..8dc0d18877f
--- /dev/null
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/mysql/MysqlResultSetEndPacketTest.java
@@ -0,0 +1,120 @@
+// 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.doris.mysql;
+
+import org.apache.doris.qe.QueryState;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.nio.ByteBuffer;
+
+public class MysqlResultSetEndPacketTest {
+    private MysqlCapability capability;
+
+    @Before
+    public void setUp() {
+        capability = new 
MysqlCapability(MysqlCapability.Flag.CLIENT_PROTOCOL_41.getFlagBit());
+    }
+
+    @Test
+    public void testWriteHeader() {
+        // The ResultSet OK packet must start with 0xFE (same as EOF)
+        MysqlResultSetEndPacket packet = new MysqlResultSetEndPacket(new 
QueryState());
+        MysqlSerializer serializer = MysqlSerializer.newInstance(capability);
+        packet.writeTo(serializer);
+
+        ByteBuffer buffer = serializer.toByteBuffer();
+
+        // assert header: 0xFE
+        Assert.assertEquals(0xFE, MysqlProto.readInt1(buffer));
+    }
+
+    @Test
+    public void testWriteFields() {
+        // Verify all fields are written correctly
+        MysqlResultSetEndPacket packet = new MysqlResultSetEndPacket(new 
QueryState());
+        MysqlSerializer serializer = MysqlSerializer.newInstance(capability);
+        packet.writeTo(serializer);
+
+        ByteBuffer buffer = serializer.toByteBuffer();
+
+        // header: 0xFE
+        Assert.assertEquals(0xFE, MysqlProto.readInt1(buffer));
+
+        // affected_rows: int<lenenc> = 0
+        Assert.assertEquals(0, MysqlProto.readVInt(buffer));
+
+        // last_insert_id: int<lenenc> = 0
+        Assert.assertEquals(0, MysqlProto.readVInt(buffer));
+
+        // status_flags: int<2> = 0
+        Assert.assertEquals(0, MysqlProto.readInt2(buffer));
+
+        // warnings: int<2> = 0
+        Assert.assertEquals(0, MysqlProto.readInt2(buffer));
+
+        // info: string<lenenc> (empty, length = 0)
+        Assert.assertEquals(0, MysqlProto.readVInt(buffer));
+
+        // no remaining bytes
+        Assert.assertEquals(0, buffer.remaining());
+    }
+
+    @Test
+    public void testPayloadSizeGreaterThan5() {
+        // When CLIENT_DEPRECATE_EOF is negotiated, the MySQL driver uses 
isResultSetOKPacket()
+        // to detect end-of-result-set. This method requires:
+        //   (byteBuffer[0] & 0xff) == 0xFE && payloadLength > 5
+        // The traditional EOF packet has payloadLength == 5, so the ResultSet 
OK packet
+        // MUST have payloadLength > 5 to be correctly identified.
+        MysqlResultSetEndPacket packet = new MysqlResultSetEndPacket(new 
QueryState());
+        MysqlSerializer serializer = MysqlSerializer.newInstance(capability);
+        packet.writeTo(serializer);
+
+        ByteBuffer buffer = serializer.toByteBuffer();
+        int payloadLength = buffer.remaining();
+
+        // Payload: 0xFE(1) + affected_rows(1) + last_insert_id(1) + status(2) 
+ warnings(2) + info(1) = 8
+        Assert.assertTrue("ResultSet OK packet payload must be > 5 for 
isResultSetOKPacket(), got: "
+                + payloadLength, payloadLength > 5);
+    }
+
+    @Test
+    public void testDiffersFromEofPacket() {
+        // A traditional EOF packet has exactly 5 bytes payload.
+        // The ResultSet OK packet must have > 5 bytes to be distinguishable.
+        MysqlEofPacket eofPacket = new MysqlEofPacket(new QueryState());
+        MysqlSerializer eofSerializer = 
MysqlSerializer.newInstance(capability);
+        eofPacket.writeTo(eofSerializer);
+        int eofPayloadLength = eofSerializer.toByteBuffer().remaining();
+
+        MysqlResultSetEndPacket rsEndPacket = new MysqlResultSetEndPacket(new 
QueryState());
+        MysqlSerializer rsEndSerializer = 
MysqlSerializer.newInstance(capability);
+        rsEndPacket.writeTo(rsEndSerializer);
+        int rsEndPayloadLength = rsEndSerializer.toByteBuffer().remaining();
+
+        // EOF payload should be <= 5
+        Assert.assertTrue("EOF packet payload should be <= 5, got: " + 
eofPayloadLength,
+                eofPayloadLength <= 5);
+        // ResultSet OK payload should be > 5
+        Assert.assertTrue("ResultSet OK packet payload should be > 5, got: " + 
rsEndPayloadLength,
+                rsEndPayloadLength > 5);
+    }
+}
diff --git a/gensrc/thrift/FrontendService.thrift 
b/gensrc/thrift/FrontendService.thrift
index 82a2d40454f..7e520751ee1 100644
--- a/gensrc/thrift/FrontendService.thrift
+++ b/gensrc/thrift/FrontendService.thrift
@@ -402,6 +402,8 @@ struct TMasterOpRequest {
 
     // temporary table
     1002: optional string sessionId
+    // propagate client's CLIENT_DEPRECATE_EOF capability for proxy forwarding
+    1003: optional bool clientDeprecatedEOF
 }
 
 struct TColumnDefinition {


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to