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

RaigorJiang 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 d75a844d8d2 Add connection attributes for ConnectionSession (#39028)
d75a844d8d2 is described below

commit d75a844d8d28882aeb85f6b8ade5e66da65b814a
Author: jiangML <[email protected]>
AuthorDate: Wed Jul 8 10:29:54 2026 +0800

    Add connection attributes for ConnectionSession (#39028)
    
    * Add connection attributes for ConnectionSession
    
    * Add test for MySQLAuthenticationEngine
    
    * Improve MySQLHandshakeResponse41Packet
---
 .../mysql/constant/MySQLCapabilityFlag.java        |  2 +-
 .../protocol/mysql/constant/MySQLConstants.java    |  3 ++
 .../handshake/MySQLHandshakeResponse41Packet.java  | 53 +++++++++++++++++++++-
 .../mysql/constant/MySQLCapabilityFlagTest.java    |  2 +-
 .../MySQLHandshakeResponse41PacketTest.java        | 41 +++++++++++++++++
 .../result/AuthenticationResult.java               |  4 ++
 .../result/AuthenticationResultBuilder.java        | 15 ++++--
 .../result/AuthenticationResultBuilderTest.java    |  6 ++-
 .../proxy/backend/session/ConnectionSession.java   |  4 ++
 .../netty/FrontendChannelInboundHandler.java       |  1 +
 .../netty/FrontendChannelInboundHandlerTest.java   |  5 +-
 .../FirebirdAuthenticationEngine.java              |  5 +-
 .../FirebirdAuthenticationEngineTest.java          |  6 +--
 .../authentication/MySQLAuthenticationEngine.java  | 13 ++++--
 .../MySQLAuthenticationEngineTest.java             | 22 ++++++---
 .../OpenGaussAuthenticationEngine.java             |  5 +-
 .../PostgreSQLAuthenticationEngine.java            |  5 +-
 17 files changed, 163 insertions(+), 29 deletions(-)

diff --git 
a/database/protocol/dialect/mysql/src/main/java/org/apache/shardingsphere/database/protocol/mysql/constant/MySQLCapabilityFlag.java
 
b/database/protocol/dialect/mysql/src/main/java/org/apache/shardingsphere/database/protocol/mysql/constant/MySQLCapabilityFlag.java
index 424db2c6b87..6507ba53038 100644
--- 
a/database/protocol/dialect/mysql/src/main/java/org/apache/shardingsphere/database/protocol/mysql/constant/MySQLCapabilityFlag.java
+++ 
b/database/protocol/dialect/mysql/src/main/java/org/apache/shardingsphere/database/protocol/mysql/constant/MySQLCapabilityFlag.java
@@ -97,7 +97,7 @@ public enum MySQLCapabilityFlag {
      * @return handshake capability flags upper bit
      */
     public static int calculateHandshakeCapabilityFlagsUpper() {
-        return calculateCapabilityFlags(CLIENT_MULTI_STATEMENTS, 
CLIENT_PLUGIN_AUTH, CLIENT_MULTI_RESULTS, CLIENT_PS_MULTI_RESULTS) >> 16;
+        return calculateCapabilityFlags(CLIENT_MULTI_STATEMENTS, 
CLIENT_PLUGIN_AUTH, CLIENT_MULTI_RESULTS, CLIENT_PS_MULTI_RESULTS, 
CLIENT_CONNECT_ATTRS) >> 16;
     }
     
     /**
diff --git 
a/database/protocol/dialect/mysql/src/main/java/org/apache/shardingsphere/database/protocol/mysql/constant/MySQLConstants.java
 
b/database/protocol/dialect/mysql/src/main/java/org/apache/shardingsphere/database/protocol/mysql/constant/MySQLConstants.java
index 4d2a18582ff..eabf7c40731 100644
--- 
a/database/protocol/dialect/mysql/src/main/java/org/apache/shardingsphere/database/protocol/mysql/constant/MySQLConstants.java
+++ 
b/database/protocol/dialect/mysql/src/main/java/org/apache/shardingsphere/database/protocol/mysql/constant/MySQLConstants.java
@@ -21,6 +21,7 @@ import io.netty.util.AttributeKey;
 import lombok.AccessLevel;
 import lombok.NoArgsConstructor;
 
+import java.util.Map;
 import java.util.concurrent.atomic.AtomicInteger;
 
 /**
@@ -35,6 +36,8 @@ public final class MySQLConstants {
     
     public static final AttributeKey<Integer> 
OPTION_MULTI_STATEMENTS_ATTRIBUTE_KEY = 
AttributeKey.valueOf("MYSQL_OPTION_MULTI_STATEMENTS");
     
+    public static final AttributeKey<Map<String, String>> 
CONNECTION_ATTRIBUTES_ATTRIBUTE_KEY = 
AttributeKey.valueOf("MYSQL_CONNECTION_ATTRIBUTES");
+    
     /**
      * Protocol version is always 0x0A.
      */
diff --git 
a/database/protocol/dialect/mysql/src/main/java/org/apache/shardingsphere/database/protocol/mysql/packet/handshake/MySQLHandshakeResponse41Packet.java
 
b/database/protocol/dialect/mysql/src/main/java/org/apache/shardingsphere/database/protocol/mysql/packet/handshake/MySQLHandshakeResponse41Packet.java
index f9713d0f02a..38aa111442b 100644
--- 
a/database/protocol/dialect/mysql/src/main/java/org/apache/shardingsphere/database/protocol/mysql/packet/handshake/MySQLHandshakeResponse41Packet.java
+++ 
b/database/protocol/dialect/mysql/src/main/java/org/apache/shardingsphere/database/protocol/mysql/packet/handshake/MySQLHandshakeResponse41Packet.java
@@ -26,6 +26,9 @@ 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.payload.MySQLPacketPayload;
 
+import java.util.HashMap;
+import java.util.Map;
+
 /**
  * Handshake response above MySQL 4.1 packet protocol.
  * 
@@ -52,6 +55,8 @@ public final class MySQLHandshakeResponse41Packet extends 
MySQLPacket {
     
     private int multiStatementsOption;
     
+    private Map<String, String> connectionAttributes;
+    
     public MySQLHandshakeResponse41Packet(final MySQLPacketPayload payload) {
         capabilityFlags = payload.readInt4();
         multiStatementsOption = readMultiStatementsOption(capabilityFlags);
@@ -62,6 +67,7 @@ public final class MySQLHandshakeResponse41Packet extends 
MySQLPacket {
         authResponse = readAuthResponse(payload);
         database = readDatabase(payload);
         authPluginName = readAuthPluginName(payload);
+        connectionAttributes = readConnectionAttributes(payload);
     }
     
     private int readMultiStatementsOption(final int capabilityFlags) {
@@ -85,7 +91,52 @@ public final class MySQLHandshakeResponse41Packet extends 
MySQLPacket {
     }
     
     private String readAuthPluginName(final MySQLPacketPayload payload) {
-        return 0 == (capabilityFlags & 
MySQLCapabilityFlag.CLIENT_PLUGIN_AUTH.getValue()) ? null : 
payload.readStringNul();
+        if (0 == (capabilityFlags & 
MySQLCapabilityFlag.CLIENT_PLUGIN_AUTH.getValue())) {
+            return null;
+        }
+        String result = payload.readStringNul();
+        return result.isEmpty() && isKnownAuthPluginName(payload) ? 
payload.readStringNul() : result;
+    }
+    
+    private boolean isKnownAuthPluginName(final MySQLPacketPayload payload) {
+        for (MySQLAuthenticationMethod each : 
MySQLAuthenticationMethod.values()) {
+            if (isAuthPluginName(payload, each.getMethodName())) {
+                return true;
+            }
+        }
+        return false;
+    }
+    
+    private boolean isAuthPluginName(final MySQLPacketPayload payload, final 
String authPluginName) {
+        byte[] authPluginNameBytes = 
authPluginName.getBytes(payload.getCharset());
+        if (payload.getByteBuf().readableBytes() <= 
authPluginNameBytes.length) {
+            return false;
+        }
+        int readerIndex = payload.getByteBuf().readerIndex();
+        for (int i = 0; i < authPluginNameBytes.length; i++) {
+            if (authPluginNameBytes[i] != 
payload.getByteBuf().getByte(readerIndex + i)) {
+                return false;
+            }
+        }
+        return 0 == payload.getByteBuf().getByte(readerIndex + 
authPluginNameBytes.length);
+    }
+    
+    private Map<String, String> readConnectionAttributes(final 
MySQLPacketPayload payload) {
+        if (0 == (capabilityFlags & 
MySQLCapabilityFlag.CLIENT_CONNECT_ATTRS.getValue()) || 
!payload.getByteBuf().isReadable()) {
+            return new HashMap<>();
+        }
+        int attributeLength = (int) payload.readIntLenenc();
+        if (attributeLength > payload.getByteBuf().readableBytes()) {
+            return new HashMap<>();
+        }
+        Map<String, String> result = new HashMap<>();
+        int endIndex = payload.getByteBuf().readerIndex() + attributeLength;
+        while (payload.getByteBuf().readerIndex() < endIndex) {
+            String key = new String(payload.readStringLenencByBytes(), 
payload.getCharset());
+            String value = new String(payload.readStringLenencByBytes(), 
payload.getCharset());
+            result.put(key, value);
+        }
+        return result;
     }
     
     /**
diff --git 
a/database/protocol/dialect/mysql/src/test/java/org/apache/shardingsphere/database/protocol/mysql/constant/MySQLCapabilityFlagTest.java
 
b/database/protocol/dialect/mysql/src/test/java/org/apache/shardingsphere/database/protocol/mysql/constant/MySQLCapabilityFlagTest.java
index b8fe2fc143c..af25b442f9c 100644
--- 
a/database/protocol/dialect/mysql/src/test/java/org/apache/shardingsphere/database/protocol/mysql/constant/MySQLCapabilityFlagTest.java
+++ 
b/database/protocol/dialect/mysql/src/test/java/org/apache/shardingsphere/database/protocol/mysql/constant/MySQLCapabilityFlagTest.java
@@ -36,6 +36,6 @@ class MySQLCapabilityFlagTest {
     
     @Test
     void assertCalculateHandshakeCapabilityFlagsUpper() {
-        
assertThat(MySQLCapabilityFlag.calculateHandshakeCapabilityFlagsUpper(), 
is(0x000f));
+        
assertThat(MySQLCapabilityFlag.calculateHandshakeCapabilityFlagsUpper(), 
is(0x001f));
     }
 }
diff --git 
a/database/protocol/dialect/mysql/src/test/java/org/apache/shardingsphere/database/protocol/mysql/packet/handshake/MySQLHandshakeResponse41PacketTest.java
 
b/database/protocol/dialect/mysql/src/test/java/org/apache/shardingsphere/database/protocol/mysql/packet/handshake/MySQLHandshakeResponse41PacketTest.java
index 3e72b9ea303..abf0cb75b52 100644
--- 
a/database/protocol/dialect/mysql/src/test/java/org/apache/shardingsphere/database/protocol/mysql/packet/handshake/MySQLHandshakeResponse41PacketTest.java
+++ 
b/database/protocol/dialect/mysql/src/test/java/org/apache/shardingsphere/database/protocol/mysql/packet/handshake/MySQLHandshakeResponse41PacketTest.java
@@ -17,6 +17,7 @@
 
 package org.apache.shardingsphere.database.protocol.mysql.packet.handshake;
 
+import io.netty.buffer.Unpooled;
 import 
org.apache.shardingsphere.database.protocol.mysql.constant.MySQLAuthenticationMethod;
 import 
org.apache.shardingsphere.database.protocol.mysql.constant.MySQLCapabilityFlag;
 import 
org.apache.shardingsphere.database.protocol.mysql.constant.MySQLConstants;
@@ -27,6 +28,9 @@ import org.junit.jupiter.api.extension.ExtendWith;
 import org.mockito.Mock;
 import org.mockito.junit.jupiter.MockitoExtension;
 
+import java.nio.charset.StandardCharsets;
+import java.util.Collections;
+
 import static org.hamcrest.Matchers.is;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.jupiter.api.Assertions.assertNull;
@@ -125,6 +129,22 @@ class MySQLHandshakeResponse41PacketTest {
         verify(payload).skipReserved(23);
     }
     
+    @Test
+    void assertNewWithPayloadWithConnectionAttributes() {
+        MySQLPacketPayload payload = new MySQLPacketPayload(Unpooled.buffer(), 
StandardCharsets.UTF_8);
+        payload.writeInt4(MySQLCapabilityFlag.CLIENT_CONNECT_ATTRS.getValue());
+        payload.writeInt4(1000);
+        payload.writeInt1(MySQLConstants.DEFAULT_CHARSET.getId());
+        payload.writeReserved(23);
+        payload.writeStringNul("root");
+        payload.writeStringNul("");
+        payload.writeIntLenenc("program_name".length() + "mysql".length() + 
2L);
+        payload.writeStringLenenc("program_name");
+        payload.writeStringLenenc("mysql");
+        MySQLHandshakeResponse41Packet actual = new 
MySQLHandshakeResponse41Packet(payload);
+        assertThat(actual.getConnectionAttributes(), 
is(Collections.singletonMap("program_name", "mysql")));
+    }
+    
     @Test
     void assertWriteWithDatabase() {
         MySQLHandshakeResponse41Packet actual = new 
MySQLHandshakeResponse41Packet(100, MySQLConstants.DEFAULT_CHARSET.getId(), 
"root");
@@ -183,4 +203,25 @@ class MySQLHandshakeResponse41PacketTest {
         verify(payload).writeInt1(1);
         verify(payload).writeBytes(new byte[]{1});
     }
+    
+    @Test
+    void assertNewWithPayloadWithEmptyAuthResponseAndAuthPluginName() {
+        MySQLPacketPayload payload = new MySQLPacketPayload(Unpooled.buffer(), 
StandardCharsets.UTF_8);
+        
payload.writeInt4(MySQLCapabilityFlag.calculateCapabilityFlags(MySQLCapabilityFlag.CLIENT_SECURE_CONNECTION,
 MySQLCapabilityFlag.CLIENT_PLUGIN_AUTH,
+                MySQLCapabilityFlag.CLIENT_CONNECT_ATTRS, 
MySQLCapabilityFlag.CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA));
+        payload.writeInt4(1000);
+        payload.writeInt1(MySQLConstants.DEFAULT_CHARSET.getId());
+        payload.writeReserved(23);
+        payload.writeStringNul("root");
+        payload.writeInt1(0);
+        payload.writeInt1(0);
+        
payload.writeStringNul(MySQLAuthenticationMethod.CACHING_SHA2_PASSWORD.getMethodName());
+        payload.writeIntLenenc("_client_name".length() + "MariaDB 
Connector/J".length() + 2L);
+        payload.writeStringLenenc("_client_name");
+        payload.writeStringLenenc("MariaDB Connector/J");
+        MySQLHandshakeResponse41Packet actual = new 
MySQLHandshakeResponse41Packet(payload);
+        assertThat(actual.getAuthResponse(), is(new byte[]{}));
+        assertThat(actual.getAuthPluginName(), 
is(MySQLAuthenticationMethod.CACHING_SHA2_PASSWORD.getMethodName()));
+        assertThat(actual.getConnectionAttributes(), 
is(Collections.singletonMap("_client_name", "MariaDB Connector/J")));
+    }
 }
diff --git 
a/kernel/authority/core/src/main/java/org/apache/shardingsphere/authentication/result/AuthenticationResult.java
 
b/kernel/authority/core/src/main/java/org/apache/shardingsphere/authentication/result/AuthenticationResult.java
index c860ef2ece0..ad43f2afd53 100644
--- 
a/kernel/authority/core/src/main/java/org/apache/shardingsphere/authentication/result/AuthenticationResult.java
+++ 
b/kernel/authority/core/src/main/java/org/apache/shardingsphere/authentication/result/AuthenticationResult.java
@@ -20,6 +20,8 @@ package org.apache.shardingsphere.authentication.result;
 import lombok.Getter;
 import lombok.RequiredArgsConstructor;
 
+import java.util.Map;
+
 /**
  * Authentication result.
  */
@@ -34,4 +36,6 @@ public final class AuthenticationResult {
     private final String database;
     
     private final boolean finished;
+    
+    private final Map<String, String> connectionAttributes;
 }
diff --git 
a/kernel/authority/core/src/main/java/org/apache/shardingsphere/authentication/result/AuthenticationResultBuilder.java
 
b/kernel/authority/core/src/main/java/org/apache/shardingsphere/authentication/result/AuthenticationResultBuilder.java
index 69d72cbff5a..204ec74f2dd 100644
--- 
a/kernel/authority/core/src/main/java/org/apache/shardingsphere/authentication/result/AuthenticationResultBuilder.java
+++ 
b/kernel/authority/core/src/main/java/org/apache/shardingsphere/authentication/result/AuthenticationResultBuilder.java
@@ -20,6 +20,9 @@ package org.apache.shardingsphere.authentication.result;
 import lombok.AccessLevel;
 import lombok.NoArgsConstructor;
 
+import java.util.Collections;
+import java.util.Map;
+
 /**
  * Authentication result builder.
  */
@@ -32,10 +35,11 @@ public final class AuthenticationResultBuilder {
      * @param username username
      * @param hostname hostname
      * @param database database
+     * @param attributes attributes
      * @return finished authentication result
      */
-    public static AuthenticationResult finished(final String username, final 
String hostname, final String database) {
-        return new AuthenticationResult(username, hostname, database, true);
+    public static AuthenticationResult finished(final String username, final 
String hostname, final String database, final Map<String, String> attributes) {
+        return new AuthenticationResult(username, hostname, database, true, 
attributes);
     }
     
     /**
@@ -44,7 +48,7 @@ public final class AuthenticationResultBuilder {
      * @return continued authentication result
      */
     public static AuthenticationResult continued() {
-        return new AuthenticationResult(null, null, null, false);
+        return new AuthenticationResult(null, null, null, false, 
Collections.emptyMap());
     }
     
     /**
@@ -53,9 +57,10 @@ public final class AuthenticationResultBuilder {
      * @param username username
      * @param hostname hostname
      * @param database database
+     * @param attributes attributes
      * @return continued authentication result
      */
-    public static AuthenticationResult continued(final String username, final 
String hostname, final String database) {
-        return new AuthenticationResult(username, hostname, database, false);
+    public static AuthenticationResult continued(final String username, final 
String hostname, final String database, final Map<String, String> attributes) {
+        return new AuthenticationResult(username, hostname, database, false, 
attributes);
     }
 }
diff --git 
a/kernel/authority/core/src/test/java/org/apache/shardingsphere/authentication/result/AuthenticationResultBuilderTest.java
 
b/kernel/authority/core/src/test/java/org/apache/shardingsphere/authentication/result/AuthenticationResultBuilderTest.java
index c5cca6d7206..d9805dffe44 100644
--- 
a/kernel/authority/core/src/test/java/org/apache/shardingsphere/authentication/result/AuthenticationResultBuilderTest.java
+++ 
b/kernel/authority/core/src/test/java/org/apache/shardingsphere/authentication/result/AuthenticationResultBuilderTest.java
@@ -19,6 +19,8 @@ package org.apache.shardingsphere.authentication.result;
 
 import org.junit.jupiter.api.Test;
 
+import java.util.Collections;
+
 import static org.hamcrest.Matchers.is;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.jupiter.api.Assertions.assertFalse;
@@ -37,7 +39,7 @@ class AuthenticationResultBuilderTest {
     
     @Test
     void assertContinued() {
-        AuthenticationResult actual = 
AuthenticationResultBuilder.continued("username", "127.0.0.1", "database");
+        AuthenticationResult actual = 
AuthenticationResultBuilder.continued("username", "127.0.0.1", "database", 
Collections.emptyMap());
         assertThat(actual.getUsername(), is("username"));
         assertThat(actual.getHostname(), is("127.0.0.1"));
         assertThat(actual.getDatabase(), is("database"));
@@ -46,7 +48,7 @@ class AuthenticationResultBuilderTest {
     
     @Test
     void assertFinished() {
-        AuthenticationResult actual = 
AuthenticationResultBuilder.finished("username", "127.0.0.1", "database");
+        AuthenticationResult actual = 
AuthenticationResultBuilder.finished("username", "127.0.0.1", "database", 
Collections.emptyMap());
         assertThat(actual.getUsername(), is("username"));
         assertThat(actual.getHostname(), is("127.0.0.1"));
         assertThat(actual.getDatabase(), is("database"));
diff --git 
a/proxy/backend/core/src/main/java/org/apache/shardingsphere/proxy/backend/session/ConnectionSession.java
 
b/proxy/backend/core/src/main/java/org/apache/shardingsphere/proxy/backend/session/ConnectionSession.java
index cc273f83486..33b8dfe0ad0 100644
--- 
a/proxy/backend/core/src/main/java/org/apache/shardingsphere/proxy/backend/session/ConnectionSession.java
+++ 
b/proxy/backend/core/src/main/java/org/apache/shardingsphere/proxy/backend/session/ConnectionSession.java
@@ -31,6 +31,8 @@ import 
org.apache.shardingsphere.proxy.backend.connector.jdbc.statement.JDBCBack
 import 
org.apache.shardingsphere.proxy.backend.session.transaction.TransactionStatus;
 import 
org.apache.shardingsphere.sql.parser.statement.core.enums.TransactionIsolationLevel;
 
+import java.util.Collections;
+import java.util.Map;
 import java.util.Optional;
 import java.util.concurrent.atomic.AtomicReference;
 
@@ -77,6 +79,8 @@ public final class ConnectionSession {
     
     private volatile String processId;
     
+    private volatile Map<String, String> connectionAttributes = 
Collections.emptyMap();
+    
     private QueryContext queryContext;
     
     public ConnectionSession(final DatabaseType protocolType, final 
AttributeMap attributeMap) {
diff --git 
a/proxy/frontend/core/src/main/java/org/apache/shardingsphere/proxy/frontend/netty/FrontendChannelInboundHandler.java
 
b/proxy/frontend/core/src/main/java/org/apache/shardingsphere/proxy/frontend/netty/FrontendChannelInboundHandler.java
index 79e30f51cf5..3909913cc95 100644
--- 
a/proxy/frontend/core/src/main/java/org/apache/shardingsphere/proxy/frontend/netty/FrontendChannelInboundHandler.java
+++ 
b/proxy/frontend/core/src/main/java/org/apache/shardingsphere/proxy/frontend/netty/FrontendChannelInboundHandler.java
@@ -85,6 +85,7 @@ public final class FrontendChannelInboundHandler extends 
ChannelInboundHandlerAd
                 connectionSession.setGrantee(new 
Grantee(authResult.getUsername(), authResult.getHostname()));
                 
connectionSession.setCurrentDatabaseName(authResult.getDatabase());
                 
connectionSession.setProcessId(processEngine.connect(connectionSession.getUsedDatabaseName(),
 connectionSession.getConnectionContext().getGrantee()));
+                
connectionSession.setConnectionAttributes(authResult.getConnectionAttributes());
             }
             return authResult.isFinished();
             // CHECKSTYLE:OFF
diff --git 
a/proxy/frontend/core/src/test/java/org/apache/shardingsphere/proxy/frontend/netty/FrontendChannelInboundHandlerTest.java
 
b/proxy/frontend/core/src/test/java/org/apache/shardingsphere/proxy/frontend/netty/FrontendChannelInboundHandlerTest.java
index 87bca9ce096..d40f9e9dcf4 100644
--- 
a/proxy/frontend/core/src/test/java/org/apache/shardingsphere/proxy/frontend/netty/FrontendChannelInboundHandlerTest.java
+++ 
b/proxy/frontend/core/src/test/java/org/apache/shardingsphere/proxy/frontend/netty/FrontendChannelInboundHandlerTest.java
@@ -64,6 +64,7 @@ import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashSet;
+import java.util.Map;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.atomic.AtomicBoolean;
 
@@ -129,11 +130,13 @@ class FrontendChannelInboundHandlerTest {
     @Test
     void assertChannelReadNotAuthenticated() throws Exception {
         channel.register();
-        AuthenticationResult authenticationResult = 
AuthenticationResultBuilder.finished("username", "hostname", "database");
+        Map<String, String> connectionAttributes = 
Collections.singletonMap("program_name", "mysql");
+        AuthenticationResult authenticationResult = 
AuthenticationResultBuilder.finished("username", "hostname", "database", 
connectionAttributes);
         
when(authenticationEngine.authenticate(any(ChannelHandlerContext.class), 
any(PacketPayload.class))).thenReturn(authenticationResult);
         channel.writeInbound(Unpooled.EMPTY_BUFFER);
         assertThat(connectionSession.getConnectionContext().getGrantee(), 
is(new Grantee("username", "hostname")));
         assertThat(connectionSession.getUsedDatabaseName(), is("database"));
+        assertThat(connectionSession.getConnectionAttributes(), 
is(connectionAttributes));
     }
     
     @Test
diff --git 
a/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/authentication/FirebirdAuthenticationEngine.java
 
b/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/authentication/FirebirdAuthenticationEngine.java
index 0f34c239f88..c0b1250a960 100644
--- 
a/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/authentication/FirebirdAuthenticationEngine.java
+++ 
b/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/authentication/FirebirdAuthenticationEngine.java
@@ -55,6 +55,7 @@ import 
org.apache.shardingsphere.proxy.frontend.firebird.command.query.statement
 import 
org.apache.shardingsphere.proxy.frontend.firebird.command.query.transaction.FirebirdTransactionIdGenerator;
 
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.Optional;
 
 /**
@@ -104,7 +105,7 @@ public final class FirebirdAuthenticationEngine implements 
AuthenticationEngine
         
context.channel().attr(CommonConstants.CHARSET_ATTRIBUTE_KEY).set(FirebirdCharacterSets.findCharacterSet(attachPacket.getEncoding()));
         login(currentAuthResult.getDatabase(), 
currentAuthResult.getUsername(), attachPacket, rule);
         context.writeAndFlush(new FirebirdGenericResponsePacket());
-        return 
AuthenticationResultBuilder.finished(currentAuthResult.getUsername(), "", 
currentAuthResult.getDatabase());
+        return 
AuthenticationResultBuilder.finished(currentAuthResult.getUsername(), "", 
currentAuthResult.getDatabase(), currentAuthResult.getConnectionAttributes());
     }
     
     private void login(final String databaseName, final String username, final 
FirebirdAttachPacket attachPacket, final AuthorityRule rule) {
@@ -137,7 +138,7 @@ public final class FirebirdAuthenticationEngine implements 
AuthenticationEngine
             acceptPacket.setAcceptDataPacket(authData.getSalt(), 
authData.getPublicKeyHex(), plugin, 0, "");
         }
         context.writeAndFlush(acceptPacket);
-        currentAuthResult = AuthenticationResultBuilder.continued(username, 
connectPacket.getHost(), connectPacket.getDatabase());
+        currentAuthResult = AuthenticationResultBuilder.continued(username, 
connectPacket.getHost(), connectPacket.getDatabase(), Collections.emptyMap());
         return currentAuthResult;
     }
     
diff --git 
a/proxy/frontend/dialect/firebird/src/test/java/org/apache/shardingsphere/proxy/frontend/firebird/authentication/FirebirdAuthenticationEngineTest.java
 
b/proxy/frontend/dialect/firebird/src/test/java/org/apache/shardingsphere/proxy/frontend/firebird/authentication/FirebirdAuthenticationEngineTest.java
index 25356587f5b..bf79c1155fa 100644
--- 
a/proxy/frontend/dialect/firebird/src/test/java/org/apache/shardingsphere/proxy/frontend/firebird/authentication/FirebirdAuthenticationEngineTest.java
+++ 
b/proxy/frontend/dialect/firebird/src/test/java/org/apache/shardingsphere/proxy/frontend/firebird/authentication/FirebirdAuthenticationEngineTest.java
@@ -227,7 +227,7 @@ class FirebirdAuthenticationEngineTest {
         Attribute<Charset> charsetAttr = mock(Attribute.class);
         
when(context.channel().attr(CommonConstants.CHARSET_ATTRIBUTE_KEY)).thenReturn(charsetAttr);
         
Plugins.getMemberAccessor().set(FirebirdAuthenticationEngine.class.getDeclaredField("currentAuthResult"),
 authenticationEngine,
-                AuthenticationResultBuilder.continued(currentUsername, "", 
currentDatabase));
+                AuthenticationResultBuilder.continued(currentUsername, "", 
currentDatabase, Collections.emptyMap()));
         FirebirdSRPAuthenticationData authData = 
mock(FirebirdSRPAuthenticationData.class);
         
Plugins.getMemberAccessor().set(FirebirdAuthenticationEngine.class.getDeclaredField("authData"),
 authenticationEngine, authData);
         FirebirdPacketPayload payload = 
mockFirebirdPayload(FirebirdCommandPacketType.ATTACH);
@@ -299,7 +299,7 @@ class FirebirdAuthenticationEngineTest {
         Attribute<Charset> charsetAttr = mock(Attribute.class);
         
when(context.channel().attr(CommonConstants.CHARSET_ATTRIBUTE_KEY)).thenReturn(charsetAttr);
         
Plugins.getMemberAccessor().set(FirebirdAuthenticationEngine.class.getDeclaredField("currentAuthResult"),
 authenticationEngine,
-                AuthenticationResultBuilder.continued("absent", "", "db"));
+                AuthenticationResultBuilder.continued("absent", "", "db", 
Collections.emptyMap()));
         FirebirdPacketPayload payload = 
mockFirebirdPayload(FirebirdCommandPacketType.ATTACH);
         try (MockedConstruction<FirebirdAttachPacket> ignored = 
mockConstruction(FirebirdAttachPacket.class, (attachPacket, construction) -> 
when(attachPacket.getEncoding()).thenReturn("UTF8"))) {
             assertThrows(AccessDeniedException.class, () -> 
authenticationEngine.authenticate(context, payload));
@@ -317,7 +317,7 @@ class FirebirdAuthenticationEngineTest {
         Attribute<Charset> charsetAttr = mock(Attribute.class);
         
when(context.channel().attr(CommonConstants.CHARSET_ATTRIBUTE_KEY)).thenReturn(charsetAttr);
         
Plugins.getMemberAccessor().set(FirebirdAuthenticationEngine.class.getDeclaredField("currentAuthResult"),
 authenticationEngine,
-                AuthenticationResultBuilder.continued("root", "", "db"));
+                AuthenticationResultBuilder.continued("root", "", "db", 
Collections.emptyMap()));
         FirebirdSRPAuthenticationData authData = 
mock(FirebirdSRPAuthenticationData.class);
         
Plugins.getMemberAccessor().set(FirebirdAuthenticationEngine.class.getDeclaredField("authData"),
 authenticationEngine, authData);
         FirebirdAuthenticator authenticator = 
mock(FirebirdAuthenticator.class);
diff --git 
a/proxy/frontend/dialect/mysql/src/main/java/org/apache/shardingsphere/proxy/frontend/mysql/authentication/MySQLAuthenticationEngine.java
 
b/proxy/frontend/dialect/mysql/src/main/java/org/apache/shardingsphere/proxy/frontend/mysql/authentication/MySQLAuthenticationEngine.java
index 67f7c249e25..c8878003404 100644
--- 
a/proxy/frontend/dialect/mysql/src/main/java/org/apache/shardingsphere/proxy/frontend/mysql/authentication/MySQLAuthenticationEngine.java
+++ 
b/proxy/frontend/dialect/mysql/src/main/java/org/apache/shardingsphere/proxy/frontend/mysql/authentication/MySQLAuthenticationEngine.java
@@ -59,6 +59,7 @@ import 
org.apache.shardingsphere.proxy.frontend.ssl.ProxySSLContext;
 
 import java.net.InetSocketAddress;
 import java.net.SocketAddress;
+import java.util.Map;
 import java.util.Optional;
 
 /**
@@ -104,7 +105,7 @@ public final class MySQLAuthenticationEngine implements 
AuthenticationEngine {
         ShardingSpherePreconditions.checkState(authorizeDatabase(rule, 
grantee, currentAuthResult.getDatabase()),
                 () -> new 
DatabaseAccessDeniedException(currentAuthResult.getUsername(), 
grantee.getHostname(), currentAuthResult.getDatabase()));
         writeOKPacket(context);
-        return AuthenticationResultBuilder.finished(grantee.getUsername(), 
grantee.getHostname(), currentAuthResult.getDatabase());
+        return AuthenticationResultBuilder.finished(grantee.getUsername(), 
grantee.getHostname(), currentAuthResult.getDatabase(), 
currentAuthResult.getConnectionAttributes());
     }
     
     private AuthenticationResult authenticatePhaseFastPath(final 
ChannelHandlerContext context, final PacketPayload payload, final AuthorityRule 
rule) {
@@ -120,6 +121,7 @@ public final class MySQLAuthenticationEngine implements 
AuthenticationEngine {
         authResponse = handshakeResponsePacket.getAuthResponse();
         setMultiStatementsOption(context, handshakeResponsePacket);
         setCharacterSet(context, handshakeResponsePacket);
+        setConnectionAttributes(context, handshakeResponsePacket);
         String database = handshakeResponsePacket.getDatabase();
         ShardingSpherePreconditions.checkState(Strings.isNullOrEmpty(database) 
|| 
ProxyContext.getInstance().getContextManager().getMetaDataContexts().getMetaData().containsDatabase(database),
                 () -> new UnknownDatabaseException(database));
@@ -127,12 +129,13 @@ public final class MySQLAuthenticationEngine implements 
AuthenticationEngine {
         String hostname = getHostAddress(context);
         ShardingSphereUser user = rule.findUser(new Grantee(username, 
hostname)).orElseGet(() -> new ShardingSphereUser(username, "", hostname));
         Authenticator authenticator = new 
AuthenticatorFactory<>(MySQLAuthenticatorType.class, rule).newInstance(user);
+        Map<String, String> connectionAttributes = 
handshakeResponsePacket.getConnectionAttributes();
         if (0 == authResponse.length || 
isClientPluginAuthenticate(handshakeResponsePacket) && 
!authenticator.getAuthenticationMethodName().equals(handshakeResponsePacket.getAuthPluginName()))
 {
             connectionPhase = 
MySQLConnectionPhase.AUTHENTICATION_METHOD_MISMATCH;
             context.writeAndFlush(new 
MySQLAuthSwitchRequestPacket(authenticator.getAuthenticationMethodName(), 
authPluginData));
-            return AuthenticationResultBuilder.continued(username, hostname, 
database);
+            return AuthenticationResultBuilder.continued(username, hostname, 
database, connectionAttributes);
         }
-        return AuthenticationResultBuilder.finished(username, hostname, 
database);
+        return AuthenticationResultBuilder.finished(username, hostname, 
database, connectionAttributes);
     }
     
     private void setMultiStatementsOption(final ChannelHandlerContext context, 
final MySQLHandshakeResponse41Packet handshakeResponsePacket) {
@@ -145,6 +148,10 @@ public final class MySQLAuthenticationEngine implements 
AuthenticationEngine {
         
context.channel().attr(MySQLConstants.CHARACTER_SET_ATTRIBUTE_KEY).set(characterSet);
     }
     
+    private void setConnectionAttributes(final ChannelHandlerContext context, 
final MySQLHandshakeResponse41Packet handshakeResponsePacket) {
+        
context.channel().attr(MySQLConstants.CONNECTION_ATTRIBUTES_ATTRIBUTE_KEY).set(handshakeResponsePacket.getConnectionAttributes());
+    }
+    
     private boolean isClientPluginAuthenticate(final 
MySQLHandshakeResponse41Packet packet) {
         return 0 != (packet.getCapabilityFlags() & 
MySQLCapabilityFlag.CLIENT_PLUGIN_AUTH.getValue());
     }
diff --git 
a/proxy/frontend/dialect/mysql/src/test/java/org/apache/shardingsphere/proxy/frontend/mysql/authentication/MySQLAuthenticationEngineTest.java
 
b/proxy/frontend/dialect/mysql/src/test/java/org/apache/shardingsphere/proxy/frontend/mysql/authentication/MySQLAuthenticationEngineTest.java
index 01aca06a270..d6fa81aaf40 100644
--- 
a/proxy/frontend/dialect/mysql/src/test/java/org/apache/shardingsphere/proxy/frontend/mysql/authentication/MySQLAuthenticationEngineTest.java
+++ 
b/proxy/frontend/dialect/mysql/src/test/java/org/apache/shardingsphere/proxy/frontend/mysql/authentication/MySQLAuthenticationEngineTest.java
@@ -153,6 +153,7 @@ class MySQLAuthenticationEngineTest {
         
when(channel.attr(CommonConstants.CHARSET_ATTRIBUTE_KEY)).thenReturn(mock(Attribute.class));
         
when(channel.attr(MySQLConstants.CHARACTER_SET_ATTRIBUTE_KEY)).thenReturn(mock(Attribute.class));
         
when(channel.attr(MySQLConstants.OPTION_MULTI_STATEMENTS_ATTRIBUTE_KEY)).thenReturn(mock(Attribute.class));
+        
when(channel.attr(MySQLConstants.CONNECTION_ATTRIBUTES_ATTRIBUTE_KEY)).thenReturn(mock(Attribute.class));
         when(channelHandlerContext.channel()).thenReturn(channel);
         when(payload.readInt1()).thenReturn(1);
         
when(payload.readInt4()).thenReturn(MySQLCapabilityFlag.CLIENT_PLUGIN_AUTH.getValue());
@@ -172,14 +173,20 @@ class MySQLAuthenticationEngineTest {
         ContextManager contextManager = mockContextManager(rule);
         
when(ProxyContext.getInstance().getContextManager()).thenReturn(contextManager);
         ChannelHandlerContext context = mockChannelHandlerContext();
-        MySQLPacketPayload payload = mock(MySQLPacketPayload.class);
-        when(payload.readInt4()).thenReturn(0);
-        when(payload.readInt1()).thenReturn(1);
-        when(payload.readStringNul()).thenReturn("root");
-        when(payload.readStringNulByBytes()).thenReturn(new byte[0]);
+        MySQLPacketPayload payload = new MySQLPacketPayload(Unpooled.buffer(), 
StandardCharsets.UTF_8);
+        payload.writeInt4(MySQLCapabilityFlag.CLIENT_CONNECT_ATTRS.getValue());
+        payload.writeInt4(1000);
+        payload.writeInt1(MySQLConstants.DEFAULT_CHARSET.getId());
+        payload.writeReserved(23);
+        payload.writeStringNul("root");
+        payload.writeStringNul("");
+        payload.writeIntLenenc("program_name".length() + "mysql".length() + 
2L);
+        payload.writeStringLenenc("program_name");
+        payload.writeStringLenenc("mysql");
         AuthenticationResult actual = 
authenticationEngine.authenticate(context, payload);
         assertFalse(actual.isFinished());
         assertThat(getConnectionPhase(), 
is(MySQLConnectionPhase.AUTHENTICATION_METHOD_MISMATCH));
+        assertThat(actual.getConnectionAttributes(), 
is(Collections.singletonMap("program_name", "mysql")));
     }
     
     @Test
@@ -213,7 +220,8 @@ class MySQLAuthenticationEngineTest {
     
     @SneakyThrows(ReflectiveOperationException.class)
     private void setAuthenticationResult() {
-        
Plugins.getMemberAccessor().set(MySQLAuthenticationEngine.class.getDeclaredField("currentAuthResult"),
 authenticationEngine, AuthenticationResultBuilder.continued("root", "", 
"foo_db"));
+        
Plugins.getMemberAccessor().set(MySQLAuthenticationEngine.class.getDeclaredField("currentAuthResult"),
 authenticationEngine,
+                AuthenticationResultBuilder.continued("root", "", "foo_db", 
Collections.emptyMap()));
     }
     
     private ShardingSpherePrivileges mockPrivileges() {
@@ -377,6 +385,7 @@ class MySQLAuthenticationEngineTest {
         
when(channel.attr(MySQLConstants.CHARACTER_SET_ATTRIBUTE_KEY)).thenReturn(mock(Attribute.class));
         
when(channel.attr(MySQLConstants.SEQUENCE_ID_ATTRIBUTE_KEY)).thenReturn(mock(Attribute.class));
         
when(channel.attr(MySQLConstants.OPTION_MULTI_STATEMENTS_ATTRIBUTE_KEY)).thenReturn(mock(Attribute.class));
+        
when(channel.attr(MySQLConstants.CONNECTION_ATTRIBUTES_ATTRIBUTE_KEY)).thenReturn(mock(Attribute.class));
         doReturn(channel).when(result).channel();
         return result;
     }
@@ -389,6 +398,7 @@ class MySQLAuthenticationEngineTest {
         
when(result.attr(MySQLConstants.CHARACTER_SET_ATTRIBUTE_KEY)).thenReturn(mock(Attribute.class));
         
when(result.attr(MySQLConstants.SEQUENCE_ID_ATTRIBUTE_KEY)).thenReturn(mock(Attribute.class));
         
when(result.attr(MySQLConstants.OPTION_MULTI_STATEMENTS_ATTRIBUTE_KEY)).thenReturn(mock(Attribute.class));
+        
when(result.attr(MySQLConstants.CONNECTION_ATTRIBUTES_ATTRIBUTE_KEY)).thenReturn(mock(Attribute.class));
         return result;
     }
     
diff --git 
a/proxy/frontend/dialect/opengauss/src/main/java/org/apache/shardingsphere/proxy/frontend/opengauss/authentication/OpenGaussAuthenticationEngine.java
 
b/proxy/frontend/dialect/opengauss/src/main/java/org/apache/shardingsphere/proxy/frontend/opengauss/authentication/OpenGaussAuthenticationEngine.java
index d91f2683343..2245d022d22 100644
--- 
a/proxy/frontend/dialect/opengauss/src/main/java/org/apache/shardingsphere/proxy/frontend/opengauss/authentication/OpenGaussAuthenticationEngine.java
+++ 
b/proxy/frontend/dialect/opengauss/src/main/java/org/apache/shardingsphere/proxy/frontend/opengauss/authentication/OpenGaussAuthenticationEngine.java
@@ -63,6 +63,7 @@ import 
org.apache.shardingsphere.proxy.frontend.connection.ConnectionIdGenerator
 import 
org.apache.shardingsphere.proxy.frontend.opengauss.authentication.authenticator.OpenGaussAuthenticatorType;
 import org.apache.shardingsphere.proxy.frontend.ssl.ProxySSLContext;
 
+import java.util.Collections;
 import java.util.Optional;
 
 /**
@@ -127,7 +128,7 @@ public final class OpenGaussAuthenticationEngine implements 
AuthenticationEngine
         context.write(new PostgreSQLParameterStatusPacket("server_encoding", 
"UTF8"));
         context.write(new PostgreSQLParameterStatusPacket("integer_datetimes", 
"on"));
         
context.writeAndFlush(PostgreSQLReadyForQueryPacket.NOT_IN_TRANSACTION);
-        return 
AuthenticationResultBuilder.finished(currentAuthResult.getUsername(), "", 
currentAuthResult.getDatabase());
+        return 
AuthenticationResultBuilder.finished(currentAuthResult.getUsername(), "", 
currentAuthResult.getDatabase(), currentAuthResult.getConnectionAttributes());
     }
     
     private void login(final AuthorityRule rule, final String digest) {
@@ -158,7 +159,7 @@ public final class OpenGaussAuthenticationEngine implements 
AuthenticationEngine
         String username = startupPacket.getUsername();
         ShardingSpherePreconditions.checkNotEmpty(username, 
EmptyUsernameException::new);
         context.writeAndFlush(getIdentifierPacket(username, rule, 
startupPacket.getVersion()));
-        currentAuthResult = AuthenticationResultBuilder.continued(username, 
"", startupPacket.getDatabase());
+        currentAuthResult = AuthenticationResultBuilder.continued(username, 
"", startupPacket.getDatabase(), Collections.emptyMap());
         return currentAuthResult;
     }
     
diff --git 
a/proxy/frontend/dialect/postgresql/src/main/java/org/apache/shardingsphere/proxy/frontend/postgresql/authentication/PostgreSQLAuthenticationEngine.java
 
b/proxy/frontend/dialect/postgresql/src/main/java/org/apache/shardingsphere/proxy/frontend/postgresql/authentication/PostgreSQLAuthenticationEngine.java
index 4cc200b501e..be7800c01e2 100644
--- 
a/proxy/frontend/dialect/postgresql/src/main/java/org/apache/shardingsphere/proxy/frontend/postgresql/authentication/PostgreSQLAuthenticationEngine.java
+++ 
b/proxy/frontend/dialect/postgresql/src/main/java/org/apache/shardingsphere/proxy/frontend/postgresql/authentication/PostgreSQLAuthenticationEngine.java
@@ -63,6 +63,7 @@ import 
org.apache.shardingsphere.proxy.frontend.postgresql.authentication.authen
 import org.apache.shardingsphere.proxy.frontend.ssl.ProxySSLContext;
 
 import java.nio.charset.StandardCharsets;
+import java.util.Collections;
 import java.util.Locale;
 import java.util.Optional;
 
@@ -120,7 +121,7 @@ public final class PostgreSQLAuthenticationEngine 
implements AuthenticationEngin
         context.write(new PostgreSQLParameterStatusPacket("integer_datetimes", 
"on"));
         context.write(new 
PostgreSQLParameterStatusPacket("standard_conforming_strings", "on"));
         
context.writeAndFlush(PostgreSQLReadyForQueryPacket.NOT_IN_TRANSACTION);
-        return 
AuthenticationResultBuilder.finished(currentAuthResult.getUsername(), "", 
currentAuthResult.getDatabase());
+        return 
AuthenticationResultBuilder.finished(currentAuthResult.getUsername(), "", 
currentAuthResult.getDatabase(), currentAuthResult.getConnectionAttributes());
     }
     
     private void login(final String databaseName, final String username, final 
byte[] md5Salt, final String digest, final AuthorityRule rule) {
@@ -142,7 +143,7 @@ public final class PostgreSQLAuthenticationEngine 
implements AuthenticationEngin
         ShardingSpherePreconditions.checkNotEmpty(username, 
EmptyUsernameException::new);
         startupMessageReceived = true;
         context.writeAndFlush(getIdentifierPacket(username, rule));
-        currentAuthResult = AuthenticationResultBuilder.continued(username, 
"", startupPacket.getDatabase());
+        currentAuthResult = AuthenticationResultBuilder.continued(username, 
"", startupPacket.getDatabase(), Collections.emptyMap());
         return currentAuthResult;
     }
     


Reply via email to