Populate settings from properties and add MariaDB alt styles

This commit is contained in:
Tyler Benson 2019-06-10 12:02:14 -07:00
parent ec60d679d6
commit 0807598d16
8 changed files with 564 additions and 413 deletions

View File

@ -0,0 +1,17 @@
package datadog.trace.instrumentation.jdbc;
import lombok.Builder;
import lombok.Data;
@Data
@Builder(builderClassName = "Builder", toBuilder = true)
public class DBInfo {
public static DBInfo DEFAULT = new Builder().type("database").build();
private final String type;
private final String url;
private final String user;
private final String instance;
private final String db;
private final String host;
private final Integer port;
}

View File

@ -38,8 +38,9 @@ public final class DriverInstrumentation extends Instrumenter.Default {
final JDBCConnectionUrlParser[] parsers = JDBCConnectionUrlParser.values();
final List<String> parserClasses = new ArrayList<>(parsers.length + 3);
parserClasses.add(packageName + ".DBInfo");
parserClasses.add(packageName + ".DBInfo$Builder");
parserClasses.add(packageName + ".JDBCMaps");
parserClasses.add(packageName + ".JDBCMaps$DBInfo");
parserClasses.add(packageName + ".JDBCConnectionUrlParser");
for (final JDBCConnectionUrlParser parser : parsers) {
@ -64,7 +65,7 @@ public final class DriverInstrumentation extends Instrumenter.Default {
@Advice.Argument(0) final String url,
@Advice.Argument(1) final Properties props,
@Advice.Return final Connection connection) {
final JDBCMaps.DBInfo dbInfo = JDBCConnectionUrlParser.parse(url, props);
final DBInfo dbInfo = JDBCConnectionUrlParser.parse(url, props);
JDBCMaps.connectionInfo.put(connection, dbInfo);
}
}

View File

@ -1,11 +1,12 @@
package datadog.trace.instrumentation.jdbc;
import static datadog.trace.instrumentation.jdbc.JDBCMaps.DBInfo.DEFAULT;
import static datadog.trace.instrumentation.jdbc.DBInfo.DEFAULT;
import datadog.trace.bootstrap.ExceptionLogger;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URLDecoder;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
@ -14,124 +15,125 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Structured as an enum instead of a class hierarchy to allow iterating through the parsers, plus
* the added benefit of only a single class to inject.
* Structured as an enum instead of a class hierarchy to allow iterating through the parsers
* automatically without having to maintain a separate list of parsers.
*/
public enum JDBCConnectionUrlParser {
GENERIC_URL_LIKE() {
@Override
JDBCMaps.DBInfo doParse(final String jdbcUrl, final Properties props) {
DBInfo.Builder doParse(final String jdbcUrl, final DBInfo.Builder builder) {
try {
// Attempt generic parsing
final URI uri = new URI(jdbcUrl);
String user = uri.getUserInfo();
String databaseName = null;
if (uri.getQuery() != null) {
final Map<String, String> queryParams = splitQuery(uri.getQuery(), "&");
populateStandardProperties(builder, splitQuery(uri.getQuery(), "&"));
if (user == null) {
user = queryParams.get("user");
}
databaseName = queryParams.get("databasename");
final String user = uri.getUserInfo();
if (user != null) {
builder.user(user);
}
String path = uri.getPath();
if (path.startsWith("/")) {
path = path.substring(1);
}
if (!path.isEmpty()) {
builder.db(path);
}
return new JDBCMaps.DBInfo(
uri.getScheme(), null, user, path, databaseName, uri.getHost(), uri.getPort());
if (uri.getHost() != null) {
builder.host(uri.getHost());
}
if (uri.getPort() > 0) {
builder.port(uri.getPort());
}
return builder.type(uri.getScheme());
} catch (final Exception e) {
return DEFAULT;
return builder;
}
}
},
MODIFIED_URL_LIKE() {
private static final String DEFAULT_HOST = "localhost";
@Override
JDBCMaps.DBInfo doParse(final String jdbcUrl, final Properties props) {
try {
DBInfo.Builder doParse(final String jdbcUrl, final DBInfo.Builder builder) {
final String type;
String serverName = "";
Integer port = null;
String instanceName = null;
final String user = null;
final String type;
String serverName = "";
Integer port = null;
String databaseName = null;
String instanceName = null;
String user = null;
final int hostIndex = jdbcUrl.indexOf("://");
final int hostIndex = jdbcUrl.indexOf("://");
if (hostIndex <= 0) {
return DEFAULT;
}
type = jdbcUrl.substring(0, hostIndex);
final String[] split;
if (type.equals("db2") || type.equals("as400")) {
if (jdbcUrl.contains("=")) {
final int paramLoc = jdbcUrl.lastIndexOf(":");
split = new String[] {jdbcUrl.substring(0, paramLoc), jdbcUrl.substring(paramLoc + 1)};
} else {
split = new String[] {jdbcUrl};
}
} else {
split = jdbcUrl.split(";", 2);
}
if (split.length > 1) {
final Map<String, String> urlProps = splitQuery(split[1], ";");
if (urlProps.containsKey("servername")) {
serverName = urlProps.get("servername");
}
if (urlProps.containsKey("instancename")) {
instanceName = urlProps.get("instancename");
}
if (urlProps.containsKey("databasename")) {
databaseName = urlProps.get("databasename");
}
if (urlProps.containsKey("user")) {
user = urlProps.get("user");
}
}
final String urlServerName = split[0].substring(hostIndex + 3);
if (!urlServerName.isEmpty()) {
serverName = urlServerName;
}
int instanceLoc = serverName.indexOf("/");
if (instanceLoc > 1) {
instanceName = serverName.substring(instanceLoc + 1);
serverName = serverName.substring(0, instanceLoc);
}
final int portLoc = serverName.indexOf(":");
if (portLoc > 1) {
port = Integer.parseInt(serverName.substring(portLoc + 1));
serverName = serverName.substring(0, portLoc);
}
instanceLoc = serverName.indexOf("\\");
if (instanceLoc > 1) {
instanceName = serverName.substring(instanceLoc + 1);
serverName = serverName.substring(0, instanceLoc);
}
if (serverName.isEmpty()) {
serverName = DEFAULT_HOST;
}
return new JDBCMaps.DBInfo(
type, null, user, instanceName, null /* databaseName */, serverName, port);
} catch (final UnsupportedEncodingException e) {
return DEFAULT;
if (hostIndex <= 0) {
return builder;
}
type = jdbcUrl.substring(0, hostIndex);
final String[] split;
if (type.equals("db2") || type.equals("as400")) {
if (jdbcUrl.contains("=")) {
final int paramLoc = jdbcUrl.lastIndexOf(":");
split = new String[] {jdbcUrl.substring(0, paramLoc), jdbcUrl.substring(paramLoc + 1)};
} else {
split = new String[] {jdbcUrl};
}
} else {
split = jdbcUrl.split(";", 2);
}
if (split.length > 1) {
final Map<String, String> props = splitQuery(split[1], ";");
populateStandardProperties(builder, props);
if (props.containsKey("servername")) {
serverName = props.get("servername");
}
}
final String urlServerName = split[0].substring(hostIndex + 3);
if (!urlServerName.isEmpty()) {
serverName = urlServerName;
}
int instanceLoc = serverName.indexOf("/");
if (instanceLoc > 1) {
instanceName = serverName.substring(instanceLoc + 1);
serverName = serverName.substring(0, instanceLoc);
}
final int portLoc = serverName.indexOf(":");
if (portLoc > 1) {
port = Integer.parseInt(serverName.substring(portLoc + 1));
serverName = serverName.substring(0, portLoc);
}
instanceLoc = serverName.indexOf("\\");
if (instanceLoc > 1) {
instanceName = serverName.substring(instanceLoc + 1);
serverName = serverName.substring(0, instanceLoc);
}
if (instanceName != null) {
builder.instance(instanceName);
}
if (!serverName.isEmpty()) {
builder.host(serverName);
}
if (port != null) {
builder.port(port);
}
if (user != null) {
builder.user(user);
}
return builder.type(type);
}
},
@ -140,18 +142,15 @@ public enum JDBCConnectionUrlParser {
private static final int DEFAULT_PORT = 5432;
@Override
JDBCMaps.DBInfo doParse(final String jdbcUrl, final Properties props) {
final JDBCMaps.DBInfo dbInfo = GENERIC_URL_LIKE.doParse(jdbcUrl, props);
final String host = dbInfo.getHost() == null ? DEFAULT_HOST : dbInfo.getHost();
final int port = dbInfo.getPort() <= 0 ? DEFAULT_PORT : dbInfo.getPort();
return new JDBCMaps.DBInfo(
dbInfo.getType(),
dbInfo.getUrl(),
dbInfo.getUser(),
dbInfo.getInstance(),
null,
host,
port);
DBInfo.Builder doParse(final String jdbcUrl, final DBInfo.Builder builder) {
final DBInfo dbInfo = builder.build();
if (dbInfo.getHost() == null) {
builder.host(DEFAULT_HOST);
}
if (dbInfo.getPort() == null) {
builder.port(DEFAULT_PORT);
}
return GENERIC_URL_LIKE.doParse(jdbcUrl, builder);
}
},
@ -160,18 +159,123 @@ public enum JDBCConnectionUrlParser {
private static final int DEFAULT_PORT = 3306;
@Override
JDBCMaps.DBInfo doParse(final String jdbcUrl, final Properties props) {
final JDBCMaps.DBInfo dbInfo = GENERIC_URL_LIKE.doParse(jdbcUrl, props);
final String host = dbInfo.getHost() == null ? DEFAULT_HOST : dbInfo.getHost();
final int port = dbInfo.getPort() <= 0 ? DEFAULT_PORT : dbInfo.getPort();
return new JDBCMaps.DBInfo(
dbInfo.getType(),
dbInfo.getUrl(),
dbInfo.getUser(),
dbInfo.getInstance(),
null,
host,
port);
DBInfo.Builder doParse(final String jdbcUrl, final DBInfo.Builder builder) {
final DBInfo dbInfo = builder.build();
if (dbInfo.getHost() == null) {
builder.host(DEFAULT_HOST);
}
if (dbInfo.getPort() == null) {
builder.port(DEFAULT_PORT);
}
final int protoLoc = jdbcUrl.indexOf("://");
final int typeEndLoc = dbInfo.getType().length();
if (protoLoc > typeEndLoc) {
return MARIA_SUBPROTO
.doParse(jdbcUrl.substring(protoLoc + 3), builder)
.type(jdbcUrl.substring(0, protoLoc));
}
if (protoLoc > 0) {
return GENERIC_URL_LIKE.doParse(jdbcUrl, builder);
}
final int hostEndLoc;
final int portLoc = jdbcUrl.indexOf(":", typeEndLoc + 1);
final int dbLoc = jdbcUrl.indexOf("/", typeEndLoc);
final int paramLoc = jdbcUrl.indexOf("?", dbLoc);
if (paramLoc > 0) {
populateStandardProperties(builder, splitQuery(jdbcUrl.substring(paramLoc + 1), "&"));
builder.db(jdbcUrl.substring(dbLoc + 1, paramLoc));
} else {
builder.db(jdbcUrl.substring(dbLoc + 1));
}
if (portLoc > 0) {
hostEndLoc = portLoc;
try {
builder.port(Integer.parseInt(jdbcUrl.substring(portLoc + 1, dbLoc)));
} catch (final NumberFormatException e) {
}
} else {
hostEndLoc = dbLoc;
}
builder.host(jdbcUrl.substring(typeEndLoc + 1, hostEndLoc));
return builder;
}
},
MARIA_SUBPROTO() {
@Override
DBInfo.Builder doParse(final String jdbcUrl, final DBInfo.Builder builder) {
final int hostEndLoc;
final int clusterSepLoc = jdbcUrl.indexOf(",");
final int ipv6End = jdbcUrl.startsWith("[") ? jdbcUrl.indexOf("]") : -1;
int portLoc = jdbcUrl.indexOf(":", Math.max(0, ipv6End));
portLoc = clusterSepLoc < portLoc ? -1 : portLoc;
final int dbLoc = jdbcUrl.indexOf("/", Math.max(portLoc, clusterSepLoc));
final int paramLoc = jdbcUrl.indexOf("?", dbLoc);
if (paramLoc > 0) {
populateStandardProperties(builder, splitQuery(jdbcUrl.substring(paramLoc + 1), "&"));
builder.db(jdbcUrl.substring(dbLoc + 1, paramLoc));
} else {
builder.db(jdbcUrl.substring(dbLoc + 1));
}
if (jdbcUrl.startsWith("address=")) {
return MARIA_ADDRESS.doParse(jdbcUrl, builder);
}
if (portLoc > 0) {
hostEndLoc = portLoc;
final int portEndLoc = clusterSepLoc > 0 ? clusterSepLoc : dbLoc;
try {
builder.port(Integer.parseInt(jdbcUrl.substring(portLoc + 1, portEndLoc)));
} catch (final NumberFormatException e) {
}
} else {
hostEndLoc = clusterSepLoc > 0 ? clusterSepLoc : dbLoc;
}
if (ipv6End > 0) {
builder.host(jdbcUrl.substring(1, ipv6End));
} else {
builder.host(jdbcUrl.substring(0, hostEndLoc));
}
return builder;
}
},
MARIA_ADDRESS() {
private final Pattern HOST_REGEX = Pattern.compile("\\(\\s*host\\s*=\\s*([^ )]+)\\s*\\)");
private final Pattern PORT_REGEX = Pattern.compile("\\(\\s*port\\s*=\\s*([\\d]+)\\s*\\)");
private final Pattern USER_REGEX = Pattern.compile("\\(\\s*user\\s*=\\s*([^ )]+)\\s*\\)");
@Override
DBInfo.Builder doParse(String jdbcUrl, final DBInfo.Builder builder) {
final int addressEnd = jdbcUrl.indexOf(",address=");
if (addressEnd > 0) {
jdbcUrl = jdbcUrl.substring(0, addressEnd);
}
final Matcher hostMatcher = HOST_REGEX.matcher(jdbcUrl);
if (hostMatcher.find()) {
builder.host(hostMatcher.group(1));
}
final Matcher portMatcher = PORT_REGEX.matcher(jdbcUrl);
if (portMatcher.find()) {
builder.port(Integer.parseInt(portMatcher.group(1)));
}
final Matcher userMatcher = USER_REGEX.matcher(jdbcUrl);
if (userMatcher.find()) {
builder.user(userMatcher.group(1));
}
return builder;
}
},
@ -179,43 +283,40 @@ public enum JDBCConnectionUrlParser {
private static final String DEFAULT_HOST = "localhost";
@Override
JDBCMaps.DBInfo doParse(final String jdbcUrl, final Properties props) {
final JDBCMaps.DBInfo dbInfo = GENERIC_URL_LIKE.doParse(jdbcUrl, props);
final String host = dbInfo.getHost() == null ? DEFAULT_HOST : dbInfo.getHost();
return new JDBCMaps.DBInfo(
dbInfo.getType(),
dbInfo.getUrl(),
dbInfo.getUser(),
dbInfo.getDb(),
null,
host,
dbInfo.getPort());
DBInfo.Builder doParse(final String jdbcUrl, final DBInfo.Builder builder) {
final DBInfo dbInfo = builder.build();
if (dbInfo.getHost() == null) {
builder.host(DEFAULT_HOST);
}
return GENERIC_URL_LIKE.doParse(jdbcUrl, builder);
}
},
MSSQLSERVER("microsoft", "sqlserver") {
private static final String DEFAULT_HOST = "localhost";
private static final int DEFAULT_PORT = 1433;
private static final String DEFAULT_INSTANCE = "MSSQLSERVER";
@Override
JDBCMaps.DBInfo doParse(String jdbcUrl, final Properties props) {
DBInfo.Builder doParse(String jdbcUrl, final DBInfo.Builder builder) {
if (jdbcUrl.startsWith("microsoft:")) {
jdbcUrl = jdbcUrl.substring("microsoft:".length());
}
if (!jdbcUrl.startsWith("sqlserver://")) {
return DEFAULT;
return builder;
}
final JDBCMaps.DBInfo dbInfo = MODIFIED_URL_LIKE.doParse(jdbcUrl, props);
return new JDBCMaps.DBInfo(
"sqlserver",
dbInfo.getUrl(),
dbInfo.getUser(),
dbInfo.getInstance() == null ? DEFAULT_INSTANCE : dbInfo.getInstance(),
dbInfo.getDb(),
dbInfo.getHost(),
dbInfo.getPort() == null ? DEFAULT_PORT : dbInfo.getPort());
builder.type("sqlserver");
final DBInfo dbInfo = builder.build();
if (dbInfo.getInstance() == null) {
builder.instance(DEFAULT_INSTANCE);
}
if (dbInfo.getHost() == null) {
builder.host(DEFAULT_HOST);
}
if (dbInfo.getPort() == null) {
builder.port(DEFAULT_PORT);
}
return MODIFIED_URL_LIKE.doParse(jdbcUrl, builder);
}
},
@ -223,18 +324,12 @@ public enum JDBCConnectionUrlParser {
private static final int DEFAULT_PORT = 50000;
@Override
JDBCMaps.DBInfo doParse(final String jdbcUrl, final Properties props) {
final JDBCMaps.DBInfo dbInfo = MODIFIED_URL_LIKE.doParse(jdbcUrl, props);
return dbInfo.getPort() != null
? dbInfo
: new JDBCMaps.DBInfo(
dbInfo.getType(),
dbInfo.getUrl(),
dbInfo.getUser(),
dbInfo.getInstance(),
dbInfo.getDb(),
dbInfo.getHost(),
DEFAULT_PORT);
DBInfo.Builder doParse(final String jdbcUrl, final DBInfo.Builder builder) {
final DBInfo dbInfo = builder.build();
if (dbInfo.getPort() == null) {
builder.port(DEFAULT_PORT);
}
return MODIFIED_URL_LIKE.doParse(jdbcUrl, builder);
}
},
@ -242,31 +337,28 @@ public enum JDBCConnectionUrlParser {
private static final int DEFAULT_PORT = 1521;
@Override
JDBCMaps.DBInfo doParse(String jdbcUrl, final Properties props) {
DBInfo.Builder doParse(String jdbcUrl, final DBInfo.Builder builder) {
final int typeEndIndex = jdbcUrl.indexOf(":", "oracle:".length());
final String type = jdbcUrl.substring(0, typeEndIndex);
jdbcUrl = jdbcUrl.substring(typeEndIndex + 1);
final JDBCMaps.DBInfo dbInfo;
if (jdbcUrl.contains("@")) {
dbInfo = ORACLE_AT.doParse(jdbcUrl, props);
} else {
dbInfo = ORACLE_CONNECT_INFO.doParse(jdbcUrl, props);
builder.type(type);
final DBInfo dbInfo = builder.build();
if (dbInfo.getPort() == null) {
builder.port(DEFAULT_PORT);
}
if (jdbcUrl.contains("@")) {
return ORACLE_AT.doParse(jdbcUrl, builder);
} else {
return ORACLE_CONNECT_INFO.doParse(jdbcUrl, builder);
}
return new JDBCMaps.DBInfo(
type,
dbInfo.getUrl(),
dbInfo.getUser(),
dbInfo.getInstance(),
dbInfo.getDb(),
dbInfo.getHost(),
dbInfo.getPort() == null ? DEFAULT_PORT : dbInfo.getPort());
}
},
ORACLE_CONNECT_INFO() {
@Override
JDBCMaps.DBInfo doParse(final String jdbcUrl, final Properties props) {
DBInfo.Builder doParse(final String jdbcUrl, final DBInfo.Builder builder) {
final String host;
final Integer port;
@ -307,7 +399,7 @@ public enum JDBCConnectionUrlParser {
instance = jdbcUrl.substring(instanceLoc + 1);
} else {
if (jdbcUrl.isEmpty()) {
return DEFAULT;
return builder;
} else {
host = null;
port = null;
@ -315,15 +407,21 @@ public enum JDBCConnectionUrlParser {
}
}
}
return new JDBCMaps.DBInfo(null, null, null, instance, null, host, port);
if (host != null) {
builder.host(host);
}
if (port != null) {
builder.port(port);
}
return builder.instance(instance);
}
},
ORACLE_AT() {
@Override
JDBCMaps.DBInfo doParse(final String jdbcUrl, final Properties props) {
DBInfo.Builder doParse(final String jdbcUrl, final DBInfo.Builder builder) {
if (jdbcUrl.contains("@(description")) {
return ORACLE_AT_DESCRIPTION.doParse(jdbcUrl, props);
return ORACLE_AT_DESCRIPTION.doParse(jdbcUrl, builder);
}
final String user;
@ -345,20 +443,17 @@ public enum JDBCConnectionUrlParser {
} else {
hostStart = 0;
}
final JDBCMaps.DBInfo dbInfo =
ORACLE_CONNECT_INFO.doParse(connectInfo.substring(hostStart), props);
return new JDBCMaps.DBInfo(
null,
dbInfo.getUrl(),
user,
dbInfo.getInstance(),
dbInfo.getDb(),
dbInfo.getHost(),
dbInfo.getPort());
if (user != null) {
builder.user(user);
}
return ORACLE_CONNECT_INFO.doParse(connectInfo.substring(hostStart), builder);
}
},
/**
* This parser can locate incorrect data if multiple addresses are defined but not everything is
* defined in the first block. (It would locate data from subsequent address blocks.
*/
ORACLE_AT_DESCRIPTION() {
private final Pattern HOST_REGEX = Pattern.compile("\\(\\s*host\\s*=\\s*([^ )]+)\\s*\\)");
private final Pattern PORT_REGEX = Pattern.compile("\\(\\s*port\\s*=\\s*([\\d]+)\\s*\\)");
@ -366,41 +461,30 @@ public enum JDBCConnectionUrlParser {
Pattern.compile("\\(\\s*service_name\\s*=\\s*([^ )]+)\\s*\\)");
@Override
JDBCMaps.DBInfo doParse(final String jdbcUrl, final Properties props) {
final String user;
final String host;
final Integer port;
final String instance;
DBInfo.Builder doParse(final String jdbcUrl, final DBInfo.Builder builder) {
final String[] atSplit = jdbcUrl.split("@", 2);
final int userInfoLoc = atSplit[0].indexOf("/");
if (userInfoLoc > 0) {
user = atSplit[0].substring(0, userInfoLoc);
} else {
user = null;
builder.user(atSplit[0].substring(0, userInfoLoc));
}
final String description = atSplit[1];
final Matcher hostMatcher = HOST_REGEX.matcher(description);
final Matcher portMatcher = PORT_REGEX.matcher(description);
final Matcher instanceMatcher = INSTANCE_REGEX.matcher(description);
final Matcher hostMatcher = HOST_REGEX.matcher(atSplit[1]);
if (hostMatcher.find()) {
host = hostMatcher.group(1);
} else {
host = null;
builder.host(hostMatcher.group(1));
}
final Matcher portMatcher = PORT_REGEX.matcher(atSplit[1]);
if (portMatcher.find()) {
port = Integer.parseInt(portMatcher.group(1));
} else {
port = null;
builder.port(Integer.parseInt(portMatcher.group(1)));
}
final Matcher instanceMatcher = INSTANCE_REGEX.matcher(atSplit[1]);
if (instanceMatcher.find()) {
instance = instanceMatcher.group(1);
} else {
instance = null;
builder.instance(instanceMatcher.group(1));
}
return new JDBCMaps.DBInfo(null, null, user, instance, null, host, port);
return builder;
}
},
@ -408,9 +492,9 @@ public enum JDBCConnectionUrlParser {
private static final int DEFAULT_PORT = 8082;
@Override
JDBCMaps.DBInfo doParse(final String jdbcUrl, final Properties props) {
DBInfo.Builder doParse(final String jdbcUrl, final DBInfo.Builder builder) {
String type = "h2";
String instance = null;
final String instance;
final String h2Url = jdbcUrl.substring("h2:".length());
if (h2Url.startsWith("mem:")) {
@ -438,27 +522,17 @@ public enum JDBCConnectionUrlParser {
instance = h2Url.substring("zip:".length());
}
} else if (h2Url.startsWith("tcp:")) {
final JDBCMaps.DBInfo dbInfo = MODIFIED_URL_LIKE.doParse(jdbcUrl, props);
return new JDBCMaps.DBInfo(
"h2:tcp",
dbInfo.getUrl(),
dbInfo.getUser(),
dbInfo.getInstance(),
dbInfo.getDb(),
dbInfo.getHost(),
dbInfo.getPort() == null ? DEFAULT_PORT : dbInfo.getPort());
final DBInfo dbInfo = builder.build();
if (dbInfo.getPort() == null) {
builder.port(DEFAULT_PORT);
}
return MODIFIED_URL_LIKE.doParse(jdbcUrl, builder).type("h2:tcp");
} else if (h2Url.startsWith("ssl:")) {
final JDBCMaps.DBInfo dbInfo = MODIFIED_URL_LIKE.doParse(jdbcUrl, props);
return new JDBCMaps.DBInfo(
"h2:ssl",
dbInfo.getUrl(),
dbInfo.getUser(),
dbInfo.getInstance(),
dbInfo.getDb(),
dbInfo.getHost(),
dbInfo.getPort() == null ? DEFAULT_PORT : dbInfo.getPort());
final DBInfo dbInfo = builder.build();
if (dbInfo.getPort() == null) {
builder.port(DEFAULT_PORT);
}
return MODIFIED_URL_LIKE.doParse(jdbcUrl, builder).type("h2:ssl");
} else {
type = "h2:file";
final int propLoc = h2Url.indexOf(";");
@ -468,7 +542,10 @@ public enum JDBCConnectionUrlParser {
instance = h2Url;
}
}
return new JDBCMaps.DBInfo(type, null, null, instance, null, null, null);
if (!instance.isEmpty()) {
builder.instance(instance);
}
return builder.type(type);
}
},
@ -477,9 +554,13 @@ public enum JDBCConnectionUrlParser {
private static final int DEFAULT_PORT = 9001;
@Override
JDBCMaps.DBInfo doParse(final String jdbcUrl, final Properties props) {
DBInfo.Builder doParse(final String jdbcUrl, final DBInfo.Builder builder) {
String type = "hsqldb";
String instance = null;
final DBInfo dbInfo = builder.build();
if (dbInfo.getUser() == null) {
builder.user(DEFAULT_USER);
}
final String hsqlUrl = jdbcUrl.substring("hsqldb:".length());
if (hsqlUrl.startsWith("mem:")) {
type = "hsqldb:mem";
@ -491,58 +572,30 @@ public enum JDBCConnectionUrlParser {
type = "hsqldb:res";
instance = hsqlUrl.substring("res:".length());
} else if (hsqlUrl.startsWith("hsql:")) {
final JDBCMaps.DBInfo dbInfo = MODIFIED_URL_LIKE.doParse(jdbcUrl, props);
return new JDBCMaps.DBInfo(
"hsqldb:hsql",
dbInfo.getUrl(),
dbInfo.getUser() == null ? DEFAULT_USER : dbInfo.getUser(),
dbInfo.getInstance(),
dbInfo.getDb(),
dbInfo.getHost(),
dbInfo.getPort() == null ? DEFAULT_PORT : dbInfo.getPort());
if (dbInfo.getPort() == null) {
builder.port(DEFAULT_PORT);
}
return MODIFIED_URL_LIKE.doParse(jdbcUrl, builder).type("hsqldb:hsql");
} else if (hsqlUrl.startsWith("hsqls:")) {
final JDBCMaps.DBInfo dbInfo = MODIFIED_URL_LIKE.doParse(jdbcUrl, props);
return new JDBCMaps.DBInfo(
"hsqldb:hsqls",
dbInfo.getUrl(),
dbInfo.getUser() == null ? DEFAULT_USER : dbInfo.getUser(),
dbInfo.getInstance(),
dbInfo.getDb(),
dbInfo.getHost(),
dbInfo.getPort() == null ? DEFAULT_PORT : dbInfo.getPort());
if (dbInfo.getPort() == null) {
builder.port(DEFAULT_PORT);
}
return MODIFIED_URL_LIKE.doParse(jdbcUrl, builder).type("hsqldb:hsqls");
} else if (hsqlUrl.startsWith("http:")) {
final JDBCMaps.DBInfo dbInfo = MODIFIED_URL_LIKE.doParse(jdbcUrl, props);
return new JDBCMaps.DBInfo(
"hsqldb:http",
dbInfo.getUrl(),
dbInfo.getUser() == null ? DEFAULT_USER : dbInfo.getUser(),
dbInfo.getInstance(),
dbInfo.getDb(),
dbInfo.getHost(),
dbInfo.getPort() == null ? 80 : dbInfo.getPort());
if (dbInfo.getPort() == null) {
builder.port(80);
}
return MODIFIED_URL_LIKE.doParse(jdbcUrl, builder).type("hsqldb:http");
} else if (hsqlUrl.startsWith("https:")) {
final JDBCMaps.DBInfo dbInfo = MODIFIED_URL_LIKE.doParse(jdbcUrl, props);
return new JDBCMaps.DBInfo(
"hsqldb:https",
dbInfo.getUrl(),
dbInfo.getUser() == null ? DEFAULT_USER : dbInfo.getUser(),
dbInfo.getInstance(),
dbInfo.getDb(),
dbInfo.getHost(),
dbInfo.getPort() == null ? 443 : dbInfo.getPort());
if (dbInfo.getPort() == null) {
builder.port(443);
}
return MODIFIED_URL_LIKE.doParse(jdbcUrl, builder).type("hsqldb:https");
} else {
type = "hsqldb:mem";
instance = hsqlUrl;
}
return new JDBCMaps.DBInfo(type, null, DEFAULT_USER, instance, null, null, null);
return builder.type(type).instance(instance);
}
},
@ -551,37 +604,21 @@ public enum JDBCConnectionUrlParser {
private static final int DEFAULT_PORT = 1527;
@Override
JDBCMaps.DBInfo doParse(final String jdbcUrl, final Properties props) {
String type = "derby";
DBInfo.Builder doParse(final String jdbcUrl, final DBInfo.Builder builder) {
final String type;
String instance = null;
String host = null;
Integer port = null;
String user = DEFAULT_USER;
if (props != null) {
instance = props.getProperty("databasename", instance);
user = props.getProperty("user", user);
final DBInfo dbInfo = builder.build();
if (dbInfo.getUser() == null) {
builder.user(DEFAULT_USER);
}
final String derbyUrl = jdbcUrl.substring("derby:".length());
final String[] split = derbyUrl.split(";", 2);
if (split.length > 1) {
try {
final Map<String, String> urlProps = splitQuery(split[1], ";");
if (urlProps.containsKey("databasename")) {
instance = urlProps.get("databasename");
}
if (urlProps.containsKey("user")) {
user = urlProps.get("user");
}
if (urlProps.containsKey("servername")) {
host = urlProps.get("servername");
}
if (urlProps.containsKey("portnumber")) {
port = Integer.parseInt(urlProps.get("portnumber"));
}
} catch (final Exception e) {
}
populateStandardProperties(builder, splitQuery(split[1], ";"));
}
if (split[0].startsWith("memory:")) {
@ -610,6 +647,9 @@ public enum JDBCConnectionUrlParser {
}
} else if (split[0].startsWith("//")) {
type = "derby:network";
if (dbInfo.getPort() == null) {
builder.port(DEFAULT_PORT);
}
String url = split[0].substring("//".length());
final int instanceLoc = url.indexOf("/");
if (instanceLoc >= 0) {
@ -623,10 +663,9 @@ public enum JDBCConnectionUrlParser {
final int portLoc = url.indexOf(":");
if (portLoc > 0) {
host = url.substring(0, portLoc);
port = Integer.parseInt(url.substring(portLoc + 1));
builder.port(Integer.parseInt(url.substring(portLoc + 1)));
} else {
host = url;
port = DEFAULT_PORT;
}
} else {
type = "derby:directory";
@ -636,7 +675,10 @@ public enum JDBCConnectionUrlParser {
}
}
return new JDBCMaps.DBInfo(type, null, user, instance, null, host, port);
if (host != null) {
builder.host(host);
}
return builder.type(type).instance(instance);
}
};
@ -656,9 +698,9 @@ public enum JDBCConnectionUrlParser {
this.typeKeys = typeKeys;
}
abstract JDBCMaps.DBInfo doParse(String jdbcUrl, final Properties props);
abstract DBInfo.Builder doParse(String jdbcUrl, final DBInfo.Builder builder);
public static JDBCMaps.DBInfo parse(String connectionUrl, final Properties props) {
public static DBInfo parse(String connectionUrl, final Properties props) {
if (connectionUrl == null) {
return DEFAULT;
}
@ -679,34 +721,84 @@ public enum JDBCConnectionUrlParser {
final String baseType = jdbcUrl.substring(0, typeLoc);
final DBInfo.Builder parsedProps = DEFAULT.toBuilder().type(baseType);
populateStandardProperties(parsedProps, props);
try {
if (typeParsers.containsKey(baseType)) {
// Delegate to specific parser
return typeParsers.get(baseType).doParse(jdbcUrl, props);
return typeParsers.get(baseType).doParse(jdbcUrl, parsedProps).build();
}
return GENERIC_URL_LIKE.doParse(connectionUrl, props);
return GENERIC_URL_LIKE.doParse(connectionUrl, parsedProps).build();
} catch (final Exception e) {
ExceptionLogger.LOGGER.debug("Error parsing URL", e);
return DEFAULT;
return parsedProps.build();
}
}
// Source: https://stackoverflow.com/a/13592567
private static Map<String, String> splitQuery(final String query, final String separator)
throws UnsupportedEncodingException {
private static Map<String, String> splitQuery(final String query, final String separator) {
if (query == null || query.isEmpty()) {
return Collections.emptyMap();
}
final Map<String, String> query_pairs = new LinkedHashMap<>();
final String[] pairs = query.split(separator);
for (final String pair : pairs) {
final int idx = pair.indexOf("=");
final String key = idx > 0 ? URLDecoder.decode(pair.substring(0, idx), "UTF-8") : pair;
if (!query_pairs.containsKey(key)) {
final String value =
idx > 0 && pair.length() > idx + 1
? URLDecoder.decode(pair.substring(idx + 1), "UTF-8")
: null;
query_pairs.put(key, value);
try {
final int idx = pair.indexOf("=");
final String key = idx > 0 ? URLDecoder.decode(pair.substring(0, idx), "UTF-8") : pair;
if (!query_pairs.containsKey(key)) {
final String value =
idx > 0 && pair.length() > idx + 1
? URLDecoder.decode(pair.substring(idx + 1), "UTF-8")
: null;
query_pairs.put(key, value);
}
} catch (final UnsupportedEncodingException e) {
// Ignore.
}
}
return query_pairs;
}
private static void populateStandardProperties(
final DBInfo.Builder builder, final Map<? extends Object, ? extends Object> props) {
if (props != null && !props.isEmpty()) {
if (props.containsKey("user")) {
builder.user((String) props.get("user"));
}
if (props.containsKey("databasename")) {
builder.db((String) props.get("databasename"));
}
if (props.containsKey("databaseName")) {
builder.db((String) props.get("databaseName"));
}
if (props.containsKey("servername")) {
builder.host((String) props.get("servername"));
}
if (props.containsKey("serverName")) {
builder.host((String) props.get("serverName"));
}
if (props.containsKey("portnumber")) {
final String portNumber = (String) props.get("portnumber");
try {
builder.port(Integer.parseInt(portNumber));
} catch (final NumberFormatException e) {
ExceptionLogger.LOGGER.debug("Error parsing portnumber property: " + portNumber, e);
}
}
if (props.containsKey("portNumber")) {
final String portNumber = (String) props.get("portNumber");
try {
builder.port(Integer.parseInt(portNumber));
} catch (final NumberFormatException e) {
ExceptionLogger.LOGGER.debug("Error parsing portNumber property: " + portNumber, e);
}
}
}
}
}

View File

@ -10,9 +10,11 @@ import java.sql.DatabaseMetaData;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class JDBCDecorator extends DatabaseClientDecorator<JDBCMaps.DBInfo> {
public class JDBCDecorator extends DatabaseClientDecorator<DBInfo> {
public static final JDBCDecorator DECORATE = new JDBCDecorator();
private static final String DB_QUERY = "DB Query";
@Override
protected String[] instrumentationNames() {
return new String[] {"jdbc"};
@ -39,17 +41,17 @@ public class JDBCDecorator extends DatabaseClientDecorator<JDBCMaps.DBInfo> {
}
@Override
protected String dbUser(final JDBCMaps.DBInfo info) {
protected String dbUser(final DBInfo info) {
return info.getUser();
}
@Override
protected String dbInstance(final JDBCMaps.DBInfo info) {
protected String dbInstance(final DBInfo info) {
return info.getInstance();
}
public Span onConnection(final Span span, final Connection connection) {
JDBCMaps.DBInfo dbInfo = JDBCMaps.connectionInfo.get(connection);
DBInfo dbInfo = JDBCMaps.connectionInfo.get(connection);
/**
* Logic to get the DBInfo from a JDBC Connection, if the connection was not created via
* Driver.connect, or it has never seen before, the connectionInfo map will return null and will
@ -65,10 +67,10 @@ public class JDBCDecorator extends DatabaseClientDecorator<JDBCMaps.DBInfo> {
if (url != null) {
dbInfo = JDBCConnectionUrlParser.parse(url, connection.getClientInfo());
} else {
dbInfo = JDBCMaps.DBInfo.DEFAULT;
dbInfo = DBInfo.DEFAULT;
}
} catch (final SQLException se) {
dbInfo = JDBCMaps.DBInfo.DEFAULT;
dbInfo = DBInfo.DEFAULT;
}
JDBCMaps.connectionInfo.put(connection, dbInfo);
}
@ -83,7 +85,7 @@ public class JDBCDecorator extends DatabaseClientDecorator<JDBCMaps.DBInfo> {
@Override
public Span onStatement(final Span span, final String statement) {
final String resourceName = statement == null ? JDBCMaps.DB_QUERY : statement;
final String resourceName = statement == null ? DB_QUERY : statement;
span.setTag(DDTags.RESOURCE_NAME, resourceName);
Tags.COMPONENT.set(span, "java-jdbc-statement");
return super.onStatement(span, statement);
@ -91,7 +93,7 @@ public class JDBCDecorator extends DatabaseClientDecorator<JDBCMaps.DBInfo> {
public Span onPreparedStatement(final Span span, final PreparedStatement statement) {
final String sql = JDBCMaps.preparedStatements.get(statement);
final String resourceName = sql == null ? JDBCMaps.DB_QUERY : sql;
final String resourceName = sql == null ? DB_QUERY : sql;
span.setTag(DDTags.RESOURCE_NAME, resourceName);
Tags.COMPONENT.set(span, "java-jdbc-prepared_statement");
return super.onStatement(span, sql);

View File

@ -5,7 +5,6 @@ import static datadog.trace.bootstrap.WeakMap.Provider.newWeakMap;
import datadog.trace.bootstrap.WeakMap;
import java.sql.Connection;
import java.sql.PreparedStatement;
import lombok.Data;
/**
* JDBC instrumentation shares a global map of connection info.
@ -15,18 +14,4 @@ import lombok.Data;
public class JDBCMaps {
public static final WeakMap<Connection, DBInfo> connectionInfo = newWeakMap();
public static final WeakMap<PreparedStatement, String> preparedStatements = newWeakMap();
public static final String DB_QUERY = "DB Query";
@Data
public static class DBInfo {
public static DBInfo DEFAULT = new DBInfo("database", null, null, null, null, null, null);
private final String type;
private final String url;
private final String user;
private final String instance;
private final String db;
private final String host;
private final Integer port;
}
}

View File

@ -45,9 +45,10 @@ public final class PreparedStatementInstrumentation extends Instrumenter.Default
final JDBCConnectionUrlParser[] parsers = JDBCConnectionUrlParser.values();
final List<String> parserClasses = new ArrayList<>(parsers.length + 8);
parserClasses.add(packageName + ".DBInfo");
parserClasses.add(packageName + ".DBInfo$Builder");
parserClasses.add(packageName + ".JDBCUtils");
parserClasses.add(packageName + ".JDBCMaps");
parserClasses.add(packageName + ".JDBCMaps$DBInfo");
parserClasses.add(packageName + ".JDBCConnectionUrlParser");
parserClasses.add("datadog.trace.agent.decorator.BaseDecorator");

View File

@ -45,9 +45,10 @@ public final class StatementInstrumentation extends Instrumenter.Default {
final JDBCConnectionUrlParser[] parsers = JDBCConnectionUrlParser.values();
final List<String> parserClasses = new ArrayList<>(parsers.length + 8);
parserClasses.add(packageName + ".DBInfo");
parserClasses.add(packageName + ".DBInfo$Builder");
parserClasses.add(packageName + ".JDBCUtils");
parserClasses.add(packageName + ".JDBCMaps");
parserClasses.add(packageName + ".JDBCMaps$DBInfo");
parserClasses.add(packageName + ".JDBCConnectionUrlParser");
parserClasses.add("datadog.trace.agent.decorator.BaseDecorator");

View File

@ -1,13 +1,30 @@
import datadog.trace.instrumentation.jdbc.JDBCMaps
import datadog.trace.instrumentation.jdbc.DBInfo
import spock.lang.Shared
import spock.lang.Specification
import static datadog.trace.instrumentation.jdbc.JDBCConnectionUrlParser.parse
class JDBCConnectionUrlParserTest extends Specification {
@Shared
def stdProps = {
def prop = new Properties()
// https://download.oracle.com/otn-pub/jcp/jdbc-4_1-mrel-spec/jdbc4.1-fr-spec.pdf
prop.setProperty("databaseName", "stdDatabaseName")
prop.setProperty("dataSourceName", "stdDatasourceName")
prop.setProperty("description", "Some description")
prop.setProperty("networkProtocol", "stdProto")
prop.setProperty("password", "PASSWORD!")
prop.setProperty("portNumber", "9999")
prop.setProperty("roleName", "stdRoleName")
prop.setProperty("serverName", "stdServerName")
prop.setProperty("user", "stdUserName")
return prop
}()
def "invalid url returns default"() {
expect:
parse(url, null) == JDBCMaps.DBInfo.DEFAULT
parse(url, null) == DBInfo.DEFAULT
where:
url | _
@ -18,7 +35,7 @@ class JDBCConnectionUrlParserTest extends Specification {
def "verify #format parsing of #url"() {
setup:
def info = parse(url, null)
def info = parse(url, props)
expect:
info.url == expected.url
@ -31,97 +48,132 @@ class JDBCConnectionUrlParserTest extends Specification {
info == expected
where:
url | format | user | host | port | instance
url | props | format | user | host | port | instance | db
// https://jdbc.postgresql.org/documentation/94/connect.html
"jdbc:postgresql:///" | "postgresql" | null | "localhost" | 5432 | ""
"jdbc:postgresql://pghost" | "postgresql" | null | "pghost" | 5432 | ""
"jdbc:postgresql://pghost:11/pgdb?user=pguser&password=PW" | "postgresql" | "pguser" | "pghost" | 11 | "pgdb"
"jdbc:postgresql:///" | null | "postgresql" | null | "localhost" | 5432 | null | null
"jdbc:postgresql:///" | stdProps | "postgresql" | "stdUserName" | "stdServerName" | 9999 | null | "stdDatabaseName"
"jdbc:postgresql://pg.host" | null | "postgresql" | null | "pg.host" | 5432 | null | null
"jdbc:postgresql://pg.host:11/pgdb?user=pguser&password=PW" | null | "postgresql" | "pguser" | "pg.host" | 11 | null | "pgdb"
"jdbc:postgresql://pg.host:11/pgdb?user=pguser&password=PW" | stdProps | "postgresql" | "pguser" | "pg.host" | 11 | null | "pgdb"
// https://dev.mysql.com/doc/connector-j/8.0/en/connector-j-reference-jdbc-url-format.html
// https://dev.mysql.com/doc/connector-j/8.0/en/connector-j-reference-configuration-properties.html
"jdbc:mysql:///" | "mysql" | null | "localhost" | 3306 | ""
"jdbc:mysql://myhost" | "mysql" | null | "myhost" | 3306 | ""
"jdbc:mysql://myhost?user=myuser&password=PW" | "mysql" | "myuser" | "myhost" | 3306 | ""
"jdbc:mysql://myhost:22/mydb?user=myuser&password=PW" | "mysql" | "myuser" | "myhost" | 22 | "mydb"
"jdbc:mariadb://mdbhost:33/mdbdb?user=mdbuser&password=PW" | "mariadb" | "mdbuser" | "mdbhost" | 33 | "mdbdb"
"jdbc:mysql:///" | null | "mysql" | null | "localhost" | 3306 | null | null
"jdbc:mysql:///" | stdProps | "mysql" | "stdUserName" | "stdServerName" | 9999 | null | "stdDatabaseName"
"jdbc:mysql://my.host" | null | "mysql" | null | "my.host" | 3306 | null | null
"jdbc:mysql://my.host?user=myuser&password=PW" | null | "mysql" | "myuser" | "my.host" | 3306 | null | null
"jdbc:mysql://my.host:22/mydb?user=myuser&password=PW" | null | "mysql" | "myuser" | "my.host" | 22 | null | "mydb"
"jdbc:mysql://127.0.0.1:22/mydb?user=myuser&password=PW" | stdProps | "mysql" | "myuser" | "127.0.0.1" | 22 | null | "mydb"
// https://mariadb.com/kb/en/library/about-mariadb-connector-j/#connection-strings
"jdbc:mariadb:127.0.0.1:33/mdbdb" | null | "mariadb" | null | "127.0.0.1" | 33 | null | "mdbdb"
"jdbc:mariadb:localhost/mdbdb" | null | "mariadb" | null | "localhost" | 3306 | null | "mdbdb"
"jdbc:mariadb:localhost/mdbdb?user=mdbuser&password=PW" | stdProps | "mariadb" | "mdbuser" | "localhost" | 9999 | null | "mdbdb"
"jdbc:mariadb:localhost:33/mdbdb" | stdProps | "mariadb" | "stdUserName" | "localhost" | 33 | null | "mdbdb"
"jdbc:mariadb://mdb.host:33/mdbdb?user=mdbuser&password=PW" | null | "mariadb" | "mdbuser" | "mdb.host" | 33 | null | "mdbdb"
"jdbc:mariadb:aurora://mdb.host/mdbdb" | null | "mariadb:aurora" | null | "mdb.host" | 3306 | null | "mdbdb"
"jdbc:mysql:aurora://mdb.host/mdbdb" | null | "mysql:aurora" | null | "mdb.host" | 3306 | null | "mdbdb"
"jdbc:mysql:failover://localhost/mdbdb?autoReconnect=true" | null | "mysql:failover" | null | "localhost" | 3306 | null | "mdbdb"
"jdbc:mariadb:failover://mdb.host1:33,mdb.host/mdbdb?characterEncoding=utf8" | null | "mariadb:failover" | null | "mdb.host1" | 33 | null | "mdbdb"
"jdbc:mariadb:sequential://mdb.host1,mdb.host2:33/mdbdb" | null | "mariadb:sequential" | null | "mdb.host1" | 3306 | null | "mdbdb"
"jdbc:mariadb:loadbalance://127.0.0.1:33,mdb.host/mdbdb" | null | "mariadb:loadbalance" | null | "127.0.0.1" | 33 | null | "mdbdb"
"jdbc:mariadb:loadbalance://[2001:0660:7401:0200:0000:0000:0edf:bdd7]:33,mdb.host/mdbdb" | null | "mariadb:loadbalance" | null | "2001:0660:7401:0200:0000:0000:0edf:bdd7" | 33 | null | "mdbdb"
"jdbc:mysql:loadbalance://127.0.0.1,127.0.0.1:3306/mdbdb?user=mdbuser&password=PW" | null | "mysql:loadbalance" | "mdbuser" | "127.0.0.1" | 3306 | null | "mdbdb"
"jdbc:mariadb:replication://localhost:33,anotherhost:3306/mdbdb" | null | "mariadb:replication" | null | "localhost" | 33 | null | "mdbdb"
"jdbc:mysql:replication://address=(HOST=127.0.0.1)(port=33)(user=mdbuser)(password=PW)," +
"address=(host=mdb.host)(port=3306)(user=otheruser)(password=PW)/mdbdb?user=wrong&password=PW" | null | "mysql:replication" | "mdbuser" | "127.0.0.1" | 33 | null | "mdbdb"
"jdbc:mysql:replication://address=(HOST=mdb.host)," +
"address=(host=anotherhost)(port=3306)(user=wrong)(password=PW)/mdbdb?user=mdbuser&password=PW" | null | "mysql:replication" | "mdbuser" | "mdb.host" | 3306 | null | "mdbdb"
//https://docs.microsoft.com/en-us/sql/connect/jdbc/building-the-connection-url
"jdbc:microsoft:sqlserver://;" | "sqlserver" | null | "localhost" | 1433 | "MSSQLSERVER"
"jdbc:sqlserver://sshost\\ssinstance:44;databaseName=ssdb;user=ssuser;password=pw" | "sqlserver" | "ssuser" | "sshost" | 44 | "ssinstance"
"jdbc:sqlserver://;serverName=sshost\\ssinstance:44;DatabaseName=;" | "sqlserver" | null | "sshost" | 44 | "ssinstance"
"jdbc:sqlserver://sshost;serverName=althost;DatabaseName=ssdb;" | "sqlserver" | null | "sshost" | 1433 | "MSSQLSERVER"
"jdbc:microsoft:sqlserver://sshost:44;DatabaseName=ssdb;user=ssuser;password=pw;user=ssuser2;" | "sqlserver" | "ssuser" | "sshost" | 44 | "MSSQLSERVER"
"jdbc:microsoft:sqlserver://;" | null | "sqlserver" | null | "localhost" | 1433 | "MSSQLSERVER" | null
"jdbc:microsoft:sqlserver://;" | stdProps | "sqlserver" | "stdUserName" | "stdServerName" | 9999 | "MSSQLSERVER" | "stdDatabaseName"
"jdbc:sqlserver://ss.host\\ssinstance:44;databaseName=ssdb;user=ssuser;password=pw" | null | "sqlserver" | "ssuser" | "ss.host" | 44 | "ssinstance" | "ssdb"
"jdbc:sqlserver://;serverName=ss.host\\ssinstance:44;DatabaseName=;" | null | "sqlserver" | null | "ss.host" | 44 | "ssinstance" | null
"jdbc:sqlserver://ss.host;serverName=althost;DatabaseName=ssdb;" | null | "sqlserver" | null | "ss.host" | 1433 | "MSSQLSERVER" | "ssdb"
"jdbc:microsoft:sqlserver://ss.host:44;DatabaseName=ssdb;user=ssuser;password=pw;user=ssuser2;" | null | "sqlserver" | "ssuser" | "ss.host" | 44 | "MSSQLSERVER" | "ssdb"
// https://docs.oracle.com/cd/B28359_01/java.111/b31224/urls.htm
// https://docs.oracle.com/cd/B28359_01/java.111/b31224/jdbcthin.htm
"jdbc:oracle:thin:orcluser/PW@localhost:55:orclsn" | "oracle:thin" | "orcluser" | "localhost" | 55 | "orclsn"
"jdbc:oracle:thin:orcluser/PW@//orclhost:55/orclsn" | "oracle:thin" | "orcluser" | "orclhost" | 55 | "orclsn"
"jdbc:oracle:thin:orcluser/PW@127.0.0.1:orclsn" | "oracle:thin" | "orcluser" | "127.0.0.1" | 1521 | "orclsn"
"jdbc:oracle:thin:orcluser/PW@//orclhost/orclsn" | "oracle:thin" | "orcluser" | "orclhost" | 1521 | "orclsn"
"jdbc:oracle:thin:@//orclhost:55/orclsn" | "oracle:thin" | null | "orclhost" | 55 | "orclsn"
"jdbc:oracle:thin:@ldap://orclhost:55/some,cn=OracleContext,dc=com" | "oracle:thin" | null | "orclhost" | 55 | "some,cn=oraclecontext,dc=com"
"jdbc:oracle:thin:127.0.0.1:orclsn" | "oracle:thin" | null | "127.0.0.1" | 1521 | "orclsn"
"jdbc:oracle:thin:orcluser/PW@localhost:55:orclsn" | null | "oracle:thin" | "orcluser" | "localhost" | 55 | "orclsn" | null
"jdbc:oracle:thin:orcluser/PW@//orcl.host:55/orclsn" | null | "oracle:thin" | "orcluser" | "orcl.host" | 55 | "orclsn" | null
"jdbc:oracle:thin:orcluser/PW@127.0.0.1:orclsn" | null | "oracle:thin" | "orcluser" | "127.0.0.1" | 1521 | "orclsn" | null
"jdbc:oracle:thin:orcluser/PW@//orcl.host/orclsn" | null | "oracle:thin" | "orcluser" | "orcl.host" | 1521 | "orclsn" | null
"jdbc:oracle:thin:@//orcl.host:55/orclsn" | null | "oracle:thin" | null | "orcl.host" | 55 | "orclsn" | null
"jdbc:oracle:thin:@ldap://orcl.host:55/some,cn=OracleContext,dc=com" | null | "oracle:thin" | null | "orcl.host" | 55 | "some,cn=oraclecontext,dc=com" | null
"jdbc:oracle:thin:127.0.0.1:orclsn" | null | "oracle:thin" | null | "127.0.0.1" | 1521 | "orclsn" | null
"jdbc:oracle:thin:orcl.host:orclsn" | stdProps | "oracle:thin" | "stdUserName" | "orcl.host" | 9999 | "orclsn" | "stdDatabaseName"
"jdbc:oracle:thin:@(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST= 127.0.0.1 )(POR T= 666))" +
"(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=orclsn)))" | "oracle:thin" | null | "127.0.0.1" | 1521 | "orclsn"
"(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=orclsn)))" | null | "oracle:thin" | null | "127.0.0.1" | 1521 | "orclsn" | null
// https://docs.oracle.com/cd/B28359_01/java.111/b31224/instclnt.htm
"jdbc:oracle:drivertype:orcluser/PW@orclhost:55/orclsn" | "oracle:drivertype" | "orcluser" | "orclhost" | 55 | "orclsn"
"jdbc:oracle:oci8:@" | "oracle:oci8" | null | null | 1521 | null
"jdbc:oracle:oci8:@orclsn" | "oracle:oci8" | null | null | 1521 | "orclsn"
"jdbc:oracle:oci:@(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)( HOST = orclhost )" +
"( PORT = 55 ))(CONNECT_DATA=(SERVICE_NAME =orclsn )))" | "oracle:oci" | null | "orclhost" | 55 | "orclsn"
"jdbc:oracle:drivertype:orcluser/PW@orcl.host:55/orclsn" | null | "oracle:drivertype" | "orcluser" | "orcl.host" | 55 | "orclsn" | null
"jdbc:oracle:oci8:@" | null | "oracle:oci8" | null | null | 1521 | null | null
"jdbc:oracle:oci8:@" | stdProps | "oracle:oci8" | "stdUserName" | "stdServerName" | 9999 | null | "stdDatabaseName"
"jdbc:oracle:oci8:@orclsn" | null | "oracle:oci8" | null | null | 1521 | "orclsn" | null
"jdbc:oracle:oci:@(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)( HOST = orcl.host )" +
"( PORT = 55 ))(CONNECT_DATA=(SERVICE_NAME =orclsn )))" | null | "oracle:oci" | null | "orcl.host" | 55 | "orclsn" | null
// https://www.ibm.com/support/knowledgecenter/en/SSEPEK_10.0.0/java/src/tpc/imjcc_tjvjcccn.html
// https://www.ibm.com/support/knowledgecenter/en/SSEPGG_10.5.0/com.ibm.db2.luw.apdv.java.doc/src/tpc/imjcc_r0052342.html
"jdbc:as400://ashost:66/asdb:user=asuser;password=PW;" | "as400" | "asuser" | "ashost" | 66 | "asdb"
"jdbc:db2://db2host:77/db2db:user=db2user;password=PW;" | "db2" | "db2user" | "db2host" | 77 | "db2db"
"jdbc:db2://db2.host" | null | "db2" | null | "db2.host" | 50000 | null | null
"jdbc:db2://db2.host" | stdProps | "db2" | "stdUserName" | "db2.host" | 9999 | null | "stdDatabaseName"
"jdbc:db2://db2.host:77/db2db:user=db2user;password=PW;" | null | "db2" | "db2user" | "db2.host" | 77 | "db2db" | null
"jdbc:db2://db2.host:77/db2db:user=db2user;password=PW;" | stdProps | "db2" | "db2user" | "db2.host" | 77 | "db2db" | "stdDatabaseName"
"jdbc:as400://ashost:66/asdb:user=asuser;password=PW;" | null | "as400" | "asuser" | "ashost" | 66 | "asdb" | null
// https://help.sap.com/viewer/0eec0d68141541d1b07893a39944924e/2.0.03/en-US/ff15928cf5594d78b841fbbe649f04b4.html
"jdbc:sap://saphost:88/?databaseName=sapdb&user=sapuser&password=PW" | "sap" | "sapuser" | "saphost" | 88 | "sapdb"
"jdbc:sap://sap.host" | null | "sap" | null | "sap.host" | null | null | null
"jdbc:sap://sap.host" | stdProps | "sap" | "stdUserName" | "sap.host" | 9999 | null | "stdDatabaseName"
"jdbc:sap://sap.host:88/?databaseName=sapdb&user=sapuser&password=PW" | null | "sap" | "sapuser" | "sap.host" | 88 | null | "sapdb"
// TODO:
// "jdbc:informix-sqli://infxhost:99/infxdb:INFORMIXSERVER=infxsn;user=infxuser;password=PW" | "informix-sqli" | "infxuser" | "infxhost" | 99 | "infxdb"
// "jdbc:informix-direct://infxdb:999;user=infxuser;password=PW" | "informix-direct" | "infxuser" | "infxhost" | 999 | "infxdb"
// "jdbc:informix-sqli://infxhost:99/infxdb:INFORMIXSERVER=infxsn;user=infxuser;password=PW" | null | "informix-sqli" | "infxuser" | "infxhost" | 99 | "infxdb"| null
// "jdbc:informix-direct://infxdb:999;user=infxuser;password=PW" | null | "informix-direct" | "infxuser" | "infxhost" | 999 | "infxdb"| null
// http://www.h2database.com/html/features.html#database_url
"jdbc:h2:mem:" | "h2:mem" | null | null | null | ""
"jdbc:h2:mem:h2db" | "h2:mem" | null | null | null | "h2db"
"jdbc:h2:tcp://h2host:111/path/h2db;user=h2user;password=PW" | "h2:tcp" | "h2user" | "h2host" | 111 | "path/h2db"
"jdbc:h2:ssl://h2host:111/path/h2db;user=h2user;password=PW" | "h2:ssl" | "h2user" | "h2host" | 111 | "path/h2db"
"jdbc:h2:/data/h2file" | "h2:file" | null | null | null | "/data/h2file"
"jdbc:h2:file:~/h2file;USER=h2user;PASSWORD=PW" | "h2:file" | null | null | null | "~/h2file"
"jdbc:h2:file:/data/h2file" | "h2:file" | null | null | null | "/data/h2file"
"jdbc:h2:file:C:/data/h2file" | "h2:file" | null | null | null | "c:/data/h2file"
"jdbc:h2:zip:~/db.zip!/h2zip" | "h2:zip" | null | null | null | "~/db.zip!/h2zip"
"jdbc:h2:mem:" | null | "h2:mem" | null | null | null | null | null
"jdbc:h2:mem:" | stdProps | "h2:mem" | "stdUserName" | "stdServerName" | 9999 | null | "stdDatabaseName"
"jdbc:h2:mem:h2db" | null | "h2:mem" | null | null | null | "h2db" | null
"jdbc:h2:tcp://h2.host:111/path/h2db;user=h2user;password=PW" | null | "h2:tcp" | "h2user" | "h2.host" | 111 | "path/h2db" | null
"jdbc:h2:ssl://h2.host:111/path/h2db;user=h2user;password=PW" | null | "h2:ssl" | "h2user" | "h2.host" | 111 | "path/h2db" | null
"jdbc:h2:/data/h2file" | null | "h2:file" | null | null | null | "/data/h2file" | null
"jdbc:h2:file:~/h2file;USER=h2user;PASSWORD=PW" | null | "h2:file" | null | null | null | "~/h2file" | null
"jdbc:h2:file:/data/h2file" | null | "h2:file" | null | null | null | "/data/h2file" | null
"jdbc:h2:file:C:/data/h2file" | null | "h2:file" | null | null | null | "c:/data/h2file" | null
"jdbc:h2:zip:~/db.zip!/h2zip" | null | "h2:zip" | null | null | null | "~/db.zip!/h2zip" | null
// http://hsqldb.org/doc/2.0/guide/dbproperties-chapt.html
"jdbc:hsqldb:hsdb" | "hsqldb:mem" | "SA" | null | null | "hsdb"
"jdbc:hsqldb:mem:hsdb" | "hsqldb:mem" | "SA" | null | null | "hsdb"
"jdbc:hsqldb:file:hsdb" | "hsqldb:file" | "SA" | null | null | "hsdb"
"jdbc:hsqldb:file:/loc/hsdb" | "hsqldb:file" | "SA" | null | null | "/loc/hsdb"
"jdbc:hsqldb:file:C:/hsdb" | "hsqldb:file" | "SA" | null | null | "c:/hsdb"
"jdbc:hsqldb:res:hsdb" | "hsqldb:res" | "SA" | null | null | "hsdb"
"jdbc:hsqldb:res:/cp/hsdb" | "hsqldb:res" | "SA" | null | null | "/cp/hsdb"
"jdbc:hsqldb:hsql://hshost:333/hsdb" | "hsqldb:hsql" | "SA" | "hshost" | 333 | "hsdb"
"jdbc:hsqldb:hsqls://hshost/hsdb" | "hsqldb:hsqls" | "SA" | "hshost" | 9001 | "hsdb"
"jdbc:hsqldb:http://hshost" | "hsqldb:http" | "SA" | "hshost" | 80 | null
"jdbc:hsqldb:http://hshost:333/hsdb" | "hsqldb:http" | "SA" | "hshost" | 333 | "hsdb"
"jdbc:hsqldb:https://127.0.0.1/hsdb" | "hsqldb:https" | "SA" | "127.0.0.1" | 443 | "hsdb"
"jdbc:hsqldb:hsdb" | null | "hsqldb:mem" | "SA" | null | null | "hsdb" | null
"jdbc:hsqldb:hsdb" | stdProps | "hsqldb:mem" | "stdUserName" | "stdServerName" | 9999 | "hsdb" | "stdDatabaseName"
"jdbc:hsqldb:mem:hsdb" | null | "hsqldb:mem" | "SA" | null | null | "hsdb" | null
"jdbc:hsqldb:file:hsdb" | null | "hsqldb:file" | "SA" | null | null | "hsdb" | null
"jdbc:hsqldb:file:/loc/hsdb" | null | "hsqldb:file" | "SA" | null | null | "/loc/hsdb" | null
"jdbc:hsqldb:file:C:/hsdb" | null | "hsqldb:file" | "SA" | null | null | "c:/hsdb" | null
"jdbc:hsqldb:res:hsdb" | null | "hsqldb:res" | "SA" | null | null | "hsdb" | null
"jdbc:hsqldb:res:/cp/hsdb" | null | "hsqldb:res" | "SA" | null | null | "/cp/hsdb" | null
"jdbc:hsqldb:hsql://hs.host:333/hsdb" | null | "hsqldb:hsql" | "SA" | "hs.host" | 333 | "hsdb" | null
"jdbc:hsqldb:hsqls://hs.host/hsdb" | null | "hsqldb:hsqls" | "SA" | "hs.host" | 9001 | "hsdb" | null
"jdbc:hsqldb:http://hs.host" | null | "hsqldb:http" | "SA" | "hs.host" | 80 | null | null
"jdbc:hsqldb:http://hs.host:333/hsdb" | null | "hsqldb:http" | "SA" | "hs.host" | 333 | "hsdb" | null
"jdbc:hsqldb:https://127.0.0.1/hsdb" | null | "hsqldb:https" | "SA" | "127.0.0.1" | 443 | "hsdb" | null
// https://db.apache.org/derby/papers/DerbyClientSpec.html#Connection+URL+Format
// https://db.apache.org/derby/docs/10.8/devguide/cdevdvlp34964.html
"jdbc:derby:derbydb" | "derby:directory" | "APP" | null | null | "derbydb"
"jdbc:derby:derbydb;user=derbyuser;password=pw" | "derby:directory" | "derbyuser" | null | null | "derbydb"
"jdbc:derby:memory:derbydb" | "derby:memory" | "APP" | null | null | "derbydb"
"jdbc:derby:memory:;databaseName=derbydb" | "derby:memory" | "APP" | null | null | "derbydb"
"jdbc:derby:memory:derbydb;databaseName=altdb" | "derby:memory" | "APP" | null | null | "derbydb"
"jdbc:derby:memory:derbydb;user=derbyuser;password=pw" | "derby:memory" | "derbyuser" | null | null | "derbydb"
"jdbc:derby://derbyhost:222/memory:derbydb;create=true" | "derby:network" | "APP" | "derbyhost" | 222 | "derbydb"
"jdbc:derby://derbyhost/memory:derbydb;create=true;user=derbyuser;password=pw" | "derby:network" | "derbyuser" | "derbyhost" | 1527 | "derbydb"
"jdbc:derby://127.0.0.1:1527/memory:derbydb;create=true;user=derbyuser;password=pw" | "derby:network" | "derbyuser" | "127.0.0.1" | 1527 | "derbydb"
"jdbc:derby:directory:derbydb;user=derbyuser;password=pw" | "derby:directory" | "derbyuser" | null | null | "derbydb"
"jdbc:derby:classpath:/some/derbydb;user=derbyuser;password=pw" | "derby:classpath" | "derbyuser" | null | null | "/some/derbydb"
"jdbc:derby:jar:/derbydb;user=derbyuser;password=pw" | "derby:jar" | "derbyuser" | null | null | "/derbydb"
"jdbc:derby:jar:(~/path/to/db.jar)/other/derbydb;user=derbyuser;password=pw" | "derby:jar" | "derbyuser" | null | null | "(~/path/to/db.jar)/other/derbydb"
"jdbc:derby:derbydb" | null | "derby:directory" | "APP" | null | null | "derbydb" | null
"jdbc:derby:derbydb" | stdProps | "derby:directory" | "stdUserName" | "stdServerName" | 9999 | "derbydb" | "stdDatabaseName"
"jdbc:derby:derbydb;user=derbyuser;password=pw" | null | "derby:directory" | "derbyuser" | null | null | "derbydb" | null
"jdbc:derby:memory:derbydb" | null | "derby:memory" | "APP" | null | null | "derbydb" | null
"jdbc:derby:memory:;databaseName=derbydb" | null | "derby:memory" | "APP" | null | null | null | "derbydb"
"jdbc:derby:memory:derbydb;databaseName=altdb" | null | "derby:memory" | "APP" | null | null | "derbydb" | "altdb"
"jdbc:derby:memory:derbydb;user=derbyuser;password=pw" | null | "derby:memory" | "derbyuser" | null | null | "derbydb" | null
"jdbc:derby://derby.host:222/memory:derbydb;create=true" | null | "derby:network" | "APP" | "derby.host" | 222 | "derbydb" | null
"jdbc:derby://derby.host/memory:derbydb;create=true;user=derbyuser;password=pw" | null | "derby:network" | "derbyuser" | "derby.host" | 1527 | "derbydb" | null
"jdbc:derby://127.0.0.1:1527/memory:derbydb;create=true;user=derbyuser;password=pw" | null | "derby:network" | "derbyuser" | "127.0.0.1" | 1527 | "derbydb" | null
"jdbc:derby:directory:derbydb;user=derbyuser;password=pw" | null | "derby:directory" | "derbyuser" | null | null | "derbydb" | null
"jdbc:derby:classpath:/some/derbydb;user=derbyuser;password=pw" | null | "derby:classpath" | "derbyuser" | null | null | "/some/derbydb" | null
"jdbc:derby:jar:/derbydb;user=derbyuser;password=pw" | null | "derby:jar" | "derbyuser" | null | null | "/derbydb" | null
"jdbc:derby:jar:(~/path/to/db.jar)/other/derbydb;user=derbyuser;password=pw" | null | "derby:jar" | "derbyuser" | null | null | "(~/path/to/db.jar)/other/derbydb" | null
expected = new JDBCMaps.DBInfo(format, null, user, instance, null, host, port)
expected = new DBInfo.Builder().type(format).user(user).instance(instance).db(db).host(host).port(port).build()
}
}