Skip to content

Commit 27655df

Browse files
committed
WL#12459, DevAPI: Support connection-attributes.
1 parent 11218a4 commit 27655df

File tree

8 files changed

+368
-29
lines changed

8 files changed

+368
-29
lines changed

CHANGES

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
Version 8.0.16
55

6+
- WL#12459, DevAPI: Support connection-attributes.
7+
68
- Fix for Bug#25650385, GETBYTE() RETURNS ERROR FOR BINARY() FLD.
79

810
- Fix for Bug#27784363, MYSQL 8.0 JDBC DRIVER THROWS NUMBERFORMATEXCEPTION FOR TEXT DATA

src/main/core-api/java/com/mysql/cj/conf/PropertyDefinitions.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -708,8 +708,6 @@ public enum AuthMech { // xdevapi.auth
708708
new BooleanPropertyDefinition(PropertyKey.readOnlyPropagatesToServer, DEFAULT_VALUE_TRUE, RUNTIME_MODIFIABLE,
709709
Messages.getString("ConnectionProperties.readOnlyPropagatesToServer"), "5.1.35", CATEGORY_PERFORMANCE, Integer.MIN_VALUE),
710710

711-
// TODO improve X DevAPI properties descriptions
712-
713711
new BooleanPropertyDefinition(PropertyKey.xdevapiUseAsyncProtocol, DEFAULT_VALUE_FALSE, RUNTIME_NOT_MODIFIABLE,
714712
Messages.getString("ConnectionProperties.useAsyncProtocol"), "6.0.0", CATEGORY_XDEVAPI, Integer.MIN_VALUE),
715713
new EnumPropertyDefinition<>(PropertyKey.xdevapiSSLMode, XdevapiSslMode.REQUIRED, RUNTIME_MODIFIABLE,
@@ -726,6 +724,8 @@ public enum AuthMech { // xdevapi.auth
726724
"8.0.8", CATEGORY_XDEVAPI, Integer.MIN_VALUE),
727725
new IntegerPropertyDefinition(PropertyKey.xdevapiConnectTimeout, 10000, RUNTIME_MODIFIABLE,
728726
Messages.getString("ConnectionProperties.xdevapiConnectTimeout"), "8.0.13", CATEGORY_XDEVAPI, Integer.MIN_VALUE, 0, Integer.MAX_VALUE),
727+
new StringPropertyDefinition(PropertyKey.xdevapiConnectionAttributes, DEFAULT_VALUE_NULL_STRING, RUNTIME_NOT_MODIFIABLE,
728+
Messages.getString("ConnectionProperties.xdevapiConnectionAttributes"), "8.0.16", CATEGORY_XDEVAPI, Integer.MIN_VALUE),
729729

730730
new BooleanPropertyDefinition(PropertyKey.enableEscapeProcessing, DEFAULT_VALUE_TRUE, RUNTIME_MODIFIABLE,
731731
Messages.getString("ConnectionProperties.enableEscapeProcessing"), "6.0.1", CATEGORY_PERFORMANCE, Integer.MIN_VALUE),

src/main/core-api/java/com/mysql/cj/conf/PropertyKey.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
33
*
44
* This program is free software; you can redistribute it and/or modify it under
55
* the terms of the GNU General Public License, version 2.0, as published by the
@@ -239,6 +239,7 @@ public enum PropertyKey {
239239
xdevapiAsyncResponseTimeout("xdevapi.asyncResponseTimeout", "xdevapiAsyncResponseTimeout", true), //
240240
xdevapiAuth("xdevapi.auth", "xdevapiAuth", true), //
241241
xdevapiConnectTimeout("xdevapi.connect-timeout", "xdevapiConnectTimeout", true), //
242+
xdevapiConnectionAttributes("xdevapi.connection-attributes", "xdevapiConnectionAttributes", true), //
242243
xdevapiSSLMode("xdevapi.ssl-mode", "xdevapiSSLMode", true), //
243244
xdevapiSSLTrustStoreUrl("xdevapi.ssl-truststore", "xdevapiSSLTruststore", true), //
244245
xdevapiSSLTrustStoreType("xdevapi.ssl-truststore-type", "xdevapiSSLTruststoreType", true), //

src/main/protocol-impl/java/com/mysql/cj/protocol/x/XMessageBuilder.java

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2015, 2019, Oracle and/or its affiliates. All rights reserved.
33
*
44
* This program is free software; you can redistribute it and/or modify it under
55
* the terms of the GNU General Public License, version 2.0, as published by the
@@ -69,6 +69,7 @@
6969
import com.mysql.cj.x.protobuf.MysqlxCrud.Update;
7070
import com.mysql.cj.x.protobuf.MysqlxCrud.UpdateOperation;
7171
import com.mysql.cj.x.protobuf.MysqlxCrud.UpdateOperation.UpdateType;
72+
import com.mysql.cj.x.protobuf.MysqlxDatatypes;
7273
import com.mysql.cj.x.protobuf.MysqlxDatatypes.Any;
7374
import com.mysql.cj.x.protobuf.MysqlxDatatypes.Object.Builder;
7475
import com.mysql.cj.x.protobuf.MysqlxDatatypes.Object.ObjectField;
@@ -96,11 +97,24 @@ public XMessage buildCapabilitiesGet() {
9697
return new XMessage(CapabilitiesGet.getDefaultInstance());
9798
}
9899

99-
public XMessage buildCapabilitiesSet(String name, Object value) {
100-
Any v = ExprUtil.argObjectToScalarAny(value);
101-
Capability cap = Capability.newBuilder().setName(name).setValue(v).build();
102-
Capabilities caps = Capabilities.newBuilder().addCapabilities(cap).build();
103-
return new XMessage(CapabilitiesSet.newBuilder().setCapabilities(caps).build());
100+
@SuppressWarnings("unchecked")
101+
public XMessage buildCapabilitiesSet(Map<String, Object> keyValuePair) {
102+
Capabilities.Builder capsB = Capabilities.newBuilder();
103+
keyValuePair.forEach((k, v) -> {
104+
Any val;
105+
if (XServerCapabilities.KEY_SESSION_CONNECT_ATTRS.equals(k)) {
106+
MysqlxDatatypes.Object.Builder attrB = MysqlxDatatypes.Object.newBuilder();
107+
((Map<String, String>) v)
108+
.forEach((name, value) -> attrB.addFld(ObjectField.newBuilder().setKey(name).setValue(ExprUtil.buildAny(value)).build()));
109+
val = Any.newBuilder().setType(Any.Type.OBJECT).setObj(attrB).build();
110+
111+
} else {
112+
val = ExprUtil.argObjectToScalarAny(v);
113+
}
114+
Capability cap = Capability.newBuilder().setName(k).setValue(val).build();
115+
capsB.addCapabilities(cap);
116+
});
117+
return new XMessage(CapabilitiesSet.newBuilder().setCapabilities(capsB).build());
104118
}
105119

106120
public XMessage buildDocInsert(String schemaName, String collectionName, List<String> json, boolean upsert) {

src/main/protocol-impl/java/com/mysql/cj/protocol/x/XProtocol.java

Lines changed: 89 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,15 @@
3636
import java.io.InputStream;
3737
import java.nio.channels.CompletionHandler;
3838
import java.util.ArrayList;
39+
import java.util.HashMap;
3940
import java.util.LinkedList;
4041
import java.util.List;
4142
import java.util.Map;
4243
import java.util.concurrent.CompletableFuture;
4344

4445
import com.mysql.cj.CharsetMapping;
46+
import com.mysql.cj.Constants;
47+
import com.mysql.cj.Messages;
4548
import com.mysql.cj.QueryResult;
4649
import com.mysql.cj.Session;
4750
import com.mysql.cj.TransactionEventHandler;
@@ -61,6 +64,7 @@
6164
import com.mysql.cj.exceptions.ExceptionInterceptor;
6265
import com.mysql.cj.exceptions.FeatureNotAvailableException;
6366
import com.mysql.cj.exceptions.SSLParamsException;
67+
import com.mysql.cj.exceptions.WrongArgumentException;
6468
import com.mysql.cj.protocol.AbstractProtocol;
6569
import com.mysql.cj.protocol.ColumnDefinition;
6670
import com.mysql.cj.protocol.ExportControlled;
@@ -110,6 +114,8 @@ public class XProtocol extends AbstractProtocol<XMessage> implements Protocol<XM
110114

111115
public String defaultSchemaName;
112116

117+
private Map<String, Object> clientCapabilities = new HashMap<>();
118+
113119
public XProtocol(String host, int port, String defaultSchema, PropertySet propertySet) {
114120

115121
this.defaultSchemaName = defaultSchema;
@@ -168,16 +174,14 @@ public ServerSession getServerSession() {
168174
}
169175

170176
/**
171-
* Set a capability of current session. Must be done before authentication ({@link #changeUser(String, String, String)}).
177+
* Set client capabilities of current session. Must be done before authentication ({@link #changeUser(String, String, String)}).
172178
*
173-
* @param name
174-
* capability name
175-
* @param value
176-
* capability value
179+
* @param keyValuePair
180+
* capabilities name/value map
177181
*/
178-
public void setCapability(String name, Object value) {
179-
((XServerCapabilities) getServerSession().getCapabilities()).setCapability(name, value);
180-
this.sender.send(((XMessageBuilder) this.messageBuilder).buildCapabilitiesSet(name, value));
182+
public void sendCapabilities(Map<String, Object> keyValuePair) {
183+
keyValuePair.forEach((k, v) -> ((XServerCapabilities) getServerSession().getCapabilities()).setCapability(k, v));
184+
this.sender.send(((XMessageBuilder) this.messageBuilder).buildCapabilitiesSet(keyValuePair));
181185
readOk();
182186
}
183187

@@ -187,13 +191,29 @@ public void negotiateSSLConnection(int packLength) {
187191
throw new CJConnectionFeatureNotAvailableException();
188192
}
189193

190-
if (!((XServerCapabilities) this.serverSession.getCapabilities()).hasCapability("tls")) {
194+
if (!((XServerCapabilities) this.serverSession.getCapabilities()).hasCapability(XServerCapabilities.KEY_TLS)) {
191195
throw new CJCommunicationsException("A secure connection is required but the server is not configured with SSL.");
192196
}
193197

194198
// the message reader is async and is always "reading". we need to stop it to use the socket for the TLS handshake
195199
this.reader.stopAfterNextMessage();
196-
setCapability("tls", true);
200+
201+
this.clientCapabilities.put(XServerCapabilities.KEY_TLS, true);
202+
203+
try {
204+
sendCapabilities(this.clientCapabilities);
205+
} catch (XProtocolError e) {
206+
// XProtocolError: ERROR 5002 (HY000) Capability 'session_connect_attrs' doesn't exist
207+
// happens when connecting to xplugin which doesn't support this feature.
208+
// Just ignore this error and retry with reduced clientCapabilities
209+
if (e.getErrorCode() != 5002 && !e.getMessage().contains(XServerCapabilities.KEY_SESSION_CONNECT_ATTRS)) {
210+
throw e;
211+
}
212+
this.clientCapabilities.remove(XServerCapabilities.KEY_SESSION_CONNECT_ATTRS);
213+
this.reader.start();
214+
this.reader.stopAfterNextMessage();
215+
sendCapabilities(this.clientCapabilities);
216+
}
197217

198218
try {
199219
this.socketConnection.performTlsHandshake(null); //(this.serverSession);
@@ -237,6 +257,13 @@ public void beforeHandshake() {
237257

238258
this.serverSession.setCapabilities(readServerCapabilities());
239259

260+
// connection attributes
261+
String attributes = this.propertySet.getStringProperty(PropertyKey.xdevapiConnectionAttributes).getValue();
262+
if (attributes == null || !attributes.equalsIgnoreCase("false")) {
263+
Map<String, String> attMap = getConnectionAttributesMap("true".equalsIgnoreCase(attributes) ? "" : attributes);
264+
this.clientCapabilities.put(XServerCapabilities.KEY_SESSION_CONNECT_ATTRS, attMap);
265+
}
266+
240267
// Override common SSL properties with xdevapi ones to provide unified logic in ExportControlled via common SSL properties
241268
RuntimeProperty<XdevapiSslMode> xdevapiSslMode = this.propertySet.<XdevapiSslMode>getEnumProperty(PropertyKey.xdevapiSSLMode);
242269
if (xdevapiSslMode.isExplicitlySet()) {
@@ -277,9 +304,53 @@ public void beforeHandshake() {
277304

278305
if (xdevapiSslMode.getValue() != XdevapiSslMode.DISABLED) {
279306
negotiateSSLConnection(0);
307+
} else if (this.clientCapabilities.size() > 0) {
308+
try {
309+
sendCapabilities(this.clientCapabilities);
310+
} catch (XProtocolError e) {
311+
// XProtocolError: ERROR 5002 (HY000) Capability 'session_connect_attrs' doesn't exist
312+
// happens when connecting to xplugin which doesn't support this feature. Just ignore this error.
313+
if (e.getErrorCode() != 5002 && !e.getMessage().contains(XServerCapabilities.KEY_SESSION_CONNECT_ATTRS)) {
314+
throw e;
315+
}
316+
this.clientCapabilities.remove(XServerCapabilities.KEY_SESSION_CONNECT_ATTRS);
317+
}
280318
}
281319
}
282320

321+
private Map<String, String> getConnectionAttributesMap(String attStr) {
322+
Map<String, String> attMap = new HashMap<>();
323+
324+
if (attStr != null) {
325+
if (attStr.startsWith("[") && attStr.endsWith("]")) {
326+
attStr = attStr.substring(1, attStr.length() - 1);
327+
}
328+
if (!StringUtils.isNullOrEmpty(attStr)) {
329+
String[] pairs = attStr.split(",");
330+
for (String pair : pairs) {
331+
String[] kv = pair.split("=");
332+
String key = kv[0].trim();
333+
String value = kv.length > 1 ? kv[1].trim() : "";
334+
if (key.startsWith("_")) {
335+
throw ExceptionFactory.createException(WrongArgumentException.class, Messages.getString("Protocol.WrongAttributeName"));
336+
} else if (attMap.put(key, value) != null) {
337+
throw ExceptionFactory.createException(WrongArgumentException.class,
338+
Messages.getString("Protocol.DuplicateAttribute", new Object[] { key }));
339+
}
340+
}
341+
}
342+
}
343+
344+
attMap.put("_platform", Constants.OS_ARCH);
345+
attMap.put("_os", Constants.OS_NAME + "-" + Constants.OS_VERSION);
346+
attMap.put("_client_name", Constants.CJ_NAME);
347+
attMap.put("_client_version", Constants.CJ_VERSION);
348+
attMap.put("_client_license", Constants.CJ_LICENSE);
349+
attMap.put("_runtime_version", Constants.JVM_VERSION);
350+
attMap.put("_runtime_vendor", Constants.JVM_VENDOR);
351+
return attMap;
352+
}
353+
283354
private String currUser = null, currPassword = null, currDatabase = null; // TODO remove these variables after implementing mysql_reset_connection() in reset() method
284355

285356
@Override
@@ -616,7 +687,14 @@ public void reset() {
616687
send(((XMessageBuilder) this.messageBuilder).buildSessionReset(), 0);
617688
readOk();
618689

619-
// TODO changeUser() call should be removed after proper implementation of session reset in xplugin
690+
// TODO should be removed after proper implementation of session reset in xplugin
691+
if (this.clientCapabilities.containsKey(XServerCapabilities.KEY_SESSION_CONNECT_ATTRS)) {
692+
Map<String, Object> reducedClientCapabilities = new HashMap<>();
693+
reducedClientCapabilities.put(XServerCapabilities.KEY_SESSION_CONNECT_ATTRS,
694+
this.clientCapabilities.get(XServerCapabilities.KEY_SESSION_CONNECT_ATTRS));
695+
sendCapabilities(reducedClientCapabilities);
696+
}
697+
// TODO should be removed after proper implementation of session reset in xplugin
620698
this.authProvider.changeUser(null, this.currUser, this.currPassword, this.currDatabase);
621699
}
622700

src/main/protocol-impl/java/com/mysql/cj/protocol/x/XServerCapabilities.java

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
33
*
44
* This program is free software; you can redistribute it and/or modify it under
55
* the terms of the GNU General Public License, version 2.0, as published by the
@@ -42,37 +42,46 @@ public class XServerCapabilities implements ServerCapabilities {
4242

4343
private Map<String, Any> capabilities;
4444

45+
static String KEY_SESSION_CONNECT_ATTRS = "session_connect_attrs";
46+
static String KEY_TLS = "tls";
47+
static String KEY_NODE_TYPE = "node_type";
48+
static String KEY_CLIENT_PWD_EXPIRE_OK = "client.pwd_expire_ok";
49+
static String KEY_AUTHENTICATION_MECHANISMS = "authentication.mechanisms";
50+
static String KEY_DOC_FORMATS = "doc.formats";
51+
4552
public XServerCapabilities(Map<String, Any> capabilities) {
4653
this.capabilities = capabilities;
4754
}
4855

4956
public void setCapability(String name, Object value) {
50-
this.capabilities.put(name, ExprUtil.argObjectToScalarAny(value));
57+
if (!KEY_SESSION_CONNECT_ATTRS.equals(name)) {
58+
this.capabilities.put(name, ExprUtil.argObjectToScalarAny(value));
59+
}
5160
}
5261

5362
public boolean hasCapability(String name) {
5463
return this.capabilities.containsKey(name);
5564
}
5665

5766
public String getNodeType() {
58-
return this.capabilities.get("node_type").getScalar().getVString().getValue().toStringUtf8();
67+
return this.capabilities.get(KEY_NODE_TYPE).getScalar().getVString().getValue().toStringUtf8();
5968
}
6069

6170
public boolean getTls() {
62-
return hasCapability("tls") ? this.capabilities.get("tls").getScalar().getVBool() : false;
71+
return hasCapability(KEY_TLS) ? this.capabilities.get(KEY_TLS).getScalar().getVBool() : false;
6372
}
6473

6574
public boolean getClientPwdExpireOk() {
66-
return this.capabilities.get("client.pwd_expire_ok").getScalar().getVBool();
75+
return this.capabilities.get(KEY_CLIENT_PWD_EXPIRE_OK).getScalar().getVBool();
6776
}
6877

6978
public List<String> getAuthenticationMechanisms() {
70-
return this.capabilities.get("authentication.mechanisms").getArray().getValueList().stream()
79+
return this.capabilities.get(KEY_AUTHENTICATION_MECHANISMS).getArray().getValueList().stream()
7180
.map(v -> v.getScalar().getVString().getValue().toStringUtf8()).collect(Collectors.toList());
7281
}
7382

7483
public String getDocFormats() {
75-
return this.capabilities.get("doc.formats").getScalar().getVString().getValue().toStringUtf8();
84+
return this.capabilities.get(KEY_DOC_FORMATS).getScalar().getVString().getValue().toStringUtf8();
7685
}
7786

7887
@Override

src/main/resources/com/mysql/cj/LocalizedErrorMessages.properties

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -346,7 +346,8 @@ Protocol.7=' :\n\n
346346
Protocol.8=Invalid socket timeout value or state
347347
Protocol.SlowQuery=Slow query (exceeded {0} {1}, duration: {2} {1}):
348348
Protocol.ServerSlowQuery=The server processing the query has indicated that the query was marked "slow".
349-
349+
Protocol.DuplicateAttribute=Duplicate key "{0}" used in "xdevapi.connection-attributes".
350+
Protocol.WrongAttributeName=Key names in "xdevapi.connection-attributes" cannot start with "_".
350351

351352
RandomBalanceStrategy.0=No hosts configured
352353

@@ -908,6 +909,7 @@ ConnectionProperties.asyncResponseTimeout=Timeout (in seconds) for getting serve
908909
ConnectionProperties.auth=Authentication mechanism to use with the X Protocol. Allowed values are "SHA256_MEMORY", "MYSQL41", "PLAIN", and "EXTERNAL". Value is case insensitive. If the property is not set, the mechanism is chosen depending on the connection type: "PLAIN" is used for TLS connections and "SHA256_MEMORY" or "MYSQL41" is used for unencrypted connections.
909910

910911
ConnectionProperties.xdevapiConnectTimeout=X DevAPI specific timeout for socket connect (in milliseconds), with '0' being no timeout. Defaults to '10000'. If "xdevapi.connect-timeout" is not set explicitly and "connectTimeout" is, "xdevapi.connect-timeout" takes up the value of "connectTimeout". If "xdevapi.useAsyncProtocol=true", both "xdevapi.connect-timeout" and "connectTimeout" are ignored."
912+
ConnectionProperties.xdevapiConnectionAttributes=An X DevAPI-specific comma-delimited list of user-defined key=value pairs (in addition to standard X Protocol-defined key=value pairs) to be passed to MySQL Server for display as connection attributes in PERFORMANCE_SCHEMA tables session_account_connect_attrs and session_connect_attrs. Example usage: xdevapi.connection-attributes=key1=value1,key2=value2 or xdevapi.connection-attributes=[key1=value1,key2=value2]. This functionality is available for use with MySQL Server version 8.0.16 or later only. Earlier versions of X Protocol do not support connection attributes, causing this configuration option to be ignored. For situations where Session creation/initialization speed is critical, setting xdevapi.connection-attributes=false will cause connection attribute processing to be bypassed.
911913

912914
ConnectionProperties.unknown=Property is not defined in Connector/J but used in connection URL.
913915

0 commit comments

Comments
 (0)