in apm-agent-plugins/apm-jdbc-plugin/src/main/java/co/elastic/apm/agent/jdbc/helper/ConnectionMetaData.java [642:767]
static void parseMySqlFlavor(String vendorUrl, Builder builder) {
// https://dev.mysql.com/doc/connector-j/8.0/en/connector-j-reference-jdbc-url-format.html
// General structure:
// protocol//[hosts][/database][?properties]
// Single host:
// jdbc:mysql://host1:33060/sakila
// jdbc:mysql://host1:33060/sakila?prop=val
// jdbc:mysql://host1:33060?prop=val
// jdbc:mysql://address=(host=myhost)(port=1111)(key1=value1)/db
// jdbc:mysql://(host=myhost,port=1111,key1=value1)/db
// Multiple hosts:
// jdbc:mysql://myhost1:1111,myhost2:2222/db
// jdbc:mysql://address=(host=myhost1)(port=1111)(key1=value1),address=(host=myhost2)(port=2222)(key2=value2)/db
// jdbc:mysql://(host=myhost1,port=1111,key1=value1),(host=myhost2,port=2222,key2=value2)/db
// jdbc:mysql://myhost1:1111,(host=myhost2,port=2222,key2=value2)/db
// jdbc:mysql://sandy:secret@[myhost1:1111,myhost2:2222]/db
// jdbc:mysql://sandy:secret@[address=(host=myhost1)(port=1111)(key1=value1),address=(host=myhost2)(port=2222)(key2=value2)]/db
// jdbc:mysql://sandy:secret@[myhost1:1111,address=(host=myhost2)(port=2222)(key2=value2)]/db
vendorUrl = vendorUrl.toLowerCase().trim();
final Pattern pattern = Pattern.compile("//([^/?]+)(.*)$");
Matcher matcher = pattern.matcher(vendorUrl);
if (matcher.find()) {
String hostsPart = matcher.group(1);
String afterHost = matcher.group(2);
if (afterHost.startsWith("/")) {
int propertiesStart = afterHost.indexOf('?');
String db = afterHost.substring(1, propertiesStart < 0 ? afterHost.length() : propertiesStart);
builder.withInstance(db);
}
// splitting to hosts, watching out from the "key-value" form: (host=myhost1,port=1111,key1=value1)
List<String> hosts = new ArrayList<>();
String[] parts = hostsPart.toLowerCase().trim().split(",");
StringBuilder sb = new StringBuilder();
for (String part : parts) {
if (part.lastIndexOf(')') < part.lastIndexOf('(')) {
// we are in the middle of a "key-value" form, we need to concatenate the next part
sb.append(part).append(',');
} else {
boolean isWithinKeyValuePart = sb.length() > 0;
sb.append(part);
if (isWithinKeyValuePart && !part.contains(")")) {
// key-value part not finished yet
sb.append(',');
continue;
}
hosts.add(sb.toString());
sb.setLength(0);
}
}
String firstHost = hosts.get(0);
// the "address-equals" form: address=(host=myhost1)(port=1111)(key1=value1)
String addressKey = "address=";
int indexOfAddress = firstHost.indexOf(addressKey);
if (indexOfAddress >= 0) {
String tmp = firstHost.substring(indexOfAddress + addressKey.length());
Matcher hostMatcher = Pattern.compile("\\s*host\\s*=\\s*([^)]+)\\s*").matcher(tmp);
if (hostMatcher.find()) {
String host = hostMatcher.group(1).trim();
int port = -1;
Matcher portMatcher = Pattern.compile("\\s*port\\s*=\\s*([^)]+)\\s*").matcher(tmp);
if (portMatcher.find()) {
port = toNumericPort(vendorUrl, portMatcher.group(1).trim());
}
builder.withHost(host).withPort(port);
return;
} else {
logger.warn("Failed to parse address from a connection URL: {}", vendorUrl);
builder.withParsingError();
}
}
// the "key-value" form: (host=myhost1,port=1111,key1=value1) - address form shouldn't arrive here
Matcher keyValueMatcher = Pattern.compile("\\(([^)]+)\\)").matcher(firstHost);
if (keyValueMatcher.find()) {
String keyValuePart = keyValueMatcher.group(1).trim();
parts = keyValuePart.split(",");
String host = null;
int port = -1;
for (String part : parts) {
String[] keyValue = part.split("=");
if (keyValue.length == 2) {
if (keyValue[0].trim().equals("host")) {
host = keyValue[1].trim();
} else if (keyValue[0].trim().equals("port")) {
port = toNumericPort(vendorUrl, keyValue[1].trim());
}
}
}
if (host != null) {
builder.withHost(host).withPort(port);
return;
} else {
logger.warn("Failed to parse address from a connection URL: {}", vendorUrl);
builder.withParsingError();
return;
}
}
int indexOfSquareBracket = firstHost.indexOf('[');
if (indexOfSquareBracket >= 0) {
if (!firstHost.contains("]") || firstHost.lastIndexOf('[') != indexOfSquareBracket) {
// not IPv6, probably the "sublist" format, trim up to it
firstHost = firstHost.substring(indexOfSquareBracket + 1);
}
}
// trim user part, if set
int indexOfUserDetailsEnd = firstHost.indexOf('@');
if (indexOfUserDetailsEnd >= 0) {
if (firstHost.length() > indexOfUserDetailsEnd + 1) {
firstHost = firstHost.substring(indexOfUserDetailsEnd + 1).trim();
} else {
return;
}
}
parseAuthority(firstHost.trim(), builder);
}
}