Fixes
Connection pools were creating proxy connection objects, so the proxy objects were cached. By unwrapping the proxies, the connection objects will be correctly cached and identified.
Refactored tests to use connection pools, and hsqldb connections only on statements (since they will not make a USER() query in a getMetaData call).
MODIFY
dd-java-agent/instrumentation/jdbc/jdbc.gradle
dd-java-agent/instrumentation/jdbc/jdbc.gradle
apply from: "${rootDir}/gradle/java.gradle"
+apply plugin: 'org.unbroken-dome.test-sets'
+
+testSets {
+ latestDepTest {
+ dirName = 'test'
+ }
+}
+
dependencies {
compile project(':dd-java-agent:agent-tooling')
@@ -15,4 +23,23 @@ dependencies {
// not using hsqldb 2.4 because org/hsqldb/jdbc/JDBCDriver : Unsupported major.minor version 52.0
testCompile group: 'org.hsqldb', name: 'hsqldb', version: '2.3.+'
+ testCompile group: 'org.apache.tomcat', name: 'tomcat-jdbc', version: '7.0.19'
+ // tomcat needs this to run
+ testCompile group: 'org.apache.tomcat', name: 'tomcat-juli', version: '7.0.19'
+ testCompile group: 'com.zaxxer', name: 'HikariCP', version: '2.4.0'
+ testCompile group: 'com.mchange', name: 'c3p0', version: '0.9.5'
+}
+
+configurations.latestDepTestCompile {
+ resolutionStrategy {
+ force group: 'com.h2database', name: 'h2', version: '+'
+ force group: 'org.apache.derby', name: 'derby', version: '+'
+ // not using hsqldb 2.4 because org/hsqldb/jdbc/JDBCDriver : Unsupported major.minor version 52.0
+ force group: 'org.hsqldb', name: 'hsqldb', version: '+'
+
+ force group: 'org.apache.tomcat', name: 'tomcat-jdbc', version: '+'
+ force group: 'org.apache.tomcat', name: 'tomcat-juli', version: '+'
+ force group: 'com.zaxxer', name: 'HikariCP', version: '+'
+ force group: 'com.mchange', name: 'c3p0', version: '+'
+ }
}

MODIFY
dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/PreparedStatementInstrumentation.java
dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/PreparedStatementInstrumentation.java
@@ -56,9 +56,13 @@ public final class PreparedStatementInstrumentation extends Instrumenter.Default
return null;
}
final String sql = JDBCMaps.preparedStatements.get(statement);
- final Connection connection;
+ Connection connection;
try {
connection = statement.getConnection();
+ // unwrap the connection to cache the underlying actual connection and to not cache proxy objects
+ if (connection.isWrapperFor(Connection.class)) {
+ connection = connection.unwrap(Connection.class);
+ }
} catch (final Throwable e) {
// Had some problem getting the connection.
return NoopScope.INSTANCE;

MODIFY
dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/StatementInstrumentation.java
dd-java-agent/instrumentation/jdbc/src/main/java/datadog/trace/instrumentation/jdbc/StatementInstrumentation.java
@@ -57,9 +57,13 @@ public final class StatementInstrumentation extends Instrumenter.Default {
if (callDepth > 0) {
return null;
}
- final Connection connection;
+ Connection connection;
try {
connection = statement.getConnection();
+ if (connection.isWrapperFor(Connection.class)) {
+ // unwrap the connection to cache the underlying actual connection and to not cache proxy objects
+ connection = connection.unwrap(Connection.class);
+ }
} catch (final Throwable e) {
// Had some problem getting the connection.
return NoopScope.INSTANCE;

MODIFY
dd-java-agent/instrumentation/jdbc/src/test/groovy/JDBCInstrumentationTest.groovy
dd-java-agent/instrumentation/jdbc/src/test/groovy/JDBCInstrumentationTest.groovy
+import com.mchange.v2.c3p0.ComboPooledDataSource
+import com.zaxxer.hikari.HikariConfig
+import com.zaxxer.hikari.HikariDataSource
import datadog.trace.agent.test.AgentTestRunner
+import datadog.trace.api.DDSpanTypes
+import io.opentracing.tag.Tags
import org.apache.derby.jdbc.EmbeddedDriver
import org.h2.Driver
import org.hsqldb.jdbc.JDBCDriver
import spock.lang.Shared
import spock.lang.Unroll
+import javax.sql.DataSource
import java.sql.Connection
import java.sql.PreparedStatement
import java.sql.ResultSet
import java.sql.Statement
+import static datadog.trace.agent.test.ListWriterAssert.assertTraces
+
class JDBCInstrumentationTest extends AgentTestRunner {
+
@Shared
def dbName = "jdbcUnitTest"
+ @Shared
+ private Map<String, String> jdbcUrls = [
+ h2 : "jdbc:h2:mem:" + dbName,
+ derby : "jdbc:derby:memory:" + dbName,
+ hsqldb: "jdbc:hsqldb:mem:" + dbName
+ ]
+
+ @Shared
+ private Map<String, String> jdbcDriverClassNames = [
+ h2 : "org.h2.Driver",
+ derby : "org.apache.derby.jdbc.EmbeddedDriver",
+ hsqldb: "org.hsqldb.jdbc.JDBCDriver"
+ ]
+
@Shared
private Map<String, Connection> connections
+ @Shared
+ private Map<String, String> jdbcUserNames = [
+ h2 : null,
+ derby : "APP",
+ hsqldb: "SA"
+ ]
+
+ // JDBC Connection pool name (i.e. HikariCP) -> Map<dbName, Datasource>
+ @Shared
+ private Map<String, Map<String, DataSource>> cpDatasources = new HashMap<>()
+
+ def prepareConnectionPoolDatasources() {
+ String[] connectionPoolNames = [
+ "tomcat", "hikari", "c3p0"
+ ]
+ connectionPoolNames.each {
+ cpName ->
+ Map<String, DataSource> dbDSMapping = new HashMap<>()
+ jdbcUrls.each {
+ dbType, jdbcUrl ->
+ dbDSMapping.put(dbType, createDS(cpName, dbType, jdbcUrl))
+ }
+ cpDatasources.put(cpName, dbDSMapping)
+ }
+ }
+
+ def createTomcatDS(String dbType, String jdbcUrl) {
+ DataSource ds = new org.apache.tomcat.jdbc.pool.DataSource()
+ def jdbcUrlToSet = dbType == "derby" ? jdbcUrl + ";create=true" : jdbcUrl
+ ds.setUrl(jdbcUrlToSet)
+ ds.setDriverClassName(jdbcDriverClassNames.get(dbType))
+ String username = jdbcUserNames.get(dbType)
+ if (username != null) {
+ ds.setUsername(username)
+ }
+ ds.setPassword("")
+ ds.setMaxActive(1) // to test proper caching, having > 1 max active connection will be hard to
+ // determine whether the connection is properly cached
+ return ds
+ }
+
+ def createHikariDS(String dbType, String jdbcUrl) {
+ HikariConfig config = new HikariConfig()
+ def jdbcUrlToSet = dbType == "derby" ? jdbcUrl + ";create=true" : jdbcUrl
+ config.setJdbcUrl(jdbcUrlToSet)
+ String username = jdbcUserNames.get(dbType)
+ if (username != null) {
+ config.setUsername(username)
+ }
+ config.setPassword("")
+ config.addDataSourceProperty("cachePrepStmts", "true")
+ config.addDataSourceProperty("prepStmtCacheSize", "250")
+ config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048")
+ config.setMaximumPoolSize(1)
+
+ return new HikariDataSource(config)
+ }
+
+ def createC3P0DS(String dbType, String jdbcUrl) {
+ DataSource ds = new ComboPooledDataSource()
+ ds.setDriverClass(jdbcDriverClassNames.get(dbType))
+ def jdbcUrlToSet = dbType == "derby" ? jdbcUrl + ";create=true" : jdbcUrl
+ ds.setJdbcUrl(jdbcUrlToSet)
+ String username = jdbcUserNames.get(dbType)
+ if (username != null) {
+ ds.setUser(username)
+ }
+ ds.setPassword("")
+ ds.setMaxPoolSize(1)
+ return ds
+ }
+
+ def createDS(String connectionPoolName, String dbType, String jdbcUrl) {
+ DataSource ds = null
+ if (connectionPoolName == "tomcat") {
+ ds = createTomcatDS(dbType, jdbcUrl)
+ }
+ if (connectionPoolName == "hikari") {
+ ds = createHikariDS(dbType, jdbcUrl)
+ }
+ if (connectionPoolName == "c3p0") {
+ ds = createC3P0DS(dbType, jdbcUrl)
+ }
+ return ds
+ }
+
def setupSpec() {
- Connection h2Connection = new Driver().connect("jdbc:h2:mem:" + dbName, null)
- Connection hsqlConnection = new JDBCDriver().connect("jdbc:hsqldb:mem:" + dbName, null)
- Connection derbyConnection = new EmbeddedDriver().connect("jdbc:derby:memory:" + dbName + ";create=true", null)
+ prepareConnectionPoolDatasources()
+
+ Connection h2Connection = new Driver().connect(jdbcUrls.get("h2"), null)
+ Connection hsqlConnection = new JDBCDriver().connect(jdbcUrls.get("hsqldb"), null)
+ Connection derbyConnection = new EmbeddedDriver().connect(jdbcUrls.get("derby"), null)
connections = [
h2 : h2Connection,
@@ -33,10 +144,18 @@ class JDBCInstrumentationTest extends AgentTestRunner {
connections.values().each {
it.close()
}
+ cpDatasources.values().each {
+ it.values().each {
+ datasource ->
+ if (datasource instanceof Closeable) {
+ datasource.close()
+ }
+ }
+ }
}
@Unroll
- def "basic statement on #driver generates spans"() {
+ def "basic statement with #connection.getClass().getCanonicalName() on #driver generates spans"() {
setup:
Statement statement = connection.createStatement()
ResultSet resultSet = statement.executeQuery(query)
@@ -44,45 +163,54 @@ class JDBCInstrumentationTest extends AgentTestRunner {
expect:
resultSet.next()
resultSet.getInt(1) == 3
- TEST_WRITER.size() == 1
-
- def trace = TEST_WRITER.firstTrace()
- trace.size() == 1
- def span = trace[0]
-
- span.context().operationName == "${driver}.query"
- span.serviceName == driver
- span.resourceName == query
- span.type == "sql"
- !span.context().getErrorFlag()
- span.context().parentId == 0
-
-
- def tags = span.context().tags
- tags["db.type"] == driver
- tags["db.user"] == username
- tags["span.kind"] == "client"
- tags["component"] == "java-jdbc-statement"
-
- tags["db.jdbc.url"].contains(driver)
- tags["span.origin.type"] != null
-
- tags["thread.name"] != null
- tags["thread.id"] != null
- tags.size() == username == null ? 7 : 8
+ assertTraces(TEST_WRITER, 1) {
+ trace(0, 1) {
+ span(0) {
+ operationName "${driver}.query"
+ serviceName driver
+ resourceName query
+ spanType DDSpanTypes.SQL
+ errored false
+ tags {
+ "db.type" driver
+ if (username != null) {
+ "db.user" username
+ }
+ "span.kind" Tags.SPAN_KIND_CLIENT
+ "span.type" DDSpanTypes.SQL
+ "component" "java-jdbc-statement"
+ "db.jdbc.url" jdbcUrls.get(driver)
+ "span.origin.type" String
+ defaultTags()
+ }
+ }
+ }
+ }
cleanup:
statement.close()
+ if (closeConection) {
+ connection.close()
+ }
where:
- driver | connection | username | query
- "h2" | connections.get("h2") | null | "SELECT 3"
- "derby" | connections.get("derby") | "APP" | "SELECT 3 FROM SYSIBM.SYSDUMMY1"
- "hsqldb" | connections.get("hsqldb") | "SA" | "SELECT 3 FROM INFORMATION_SCHEMA.SYSTEM_USERS"
+ driver | connection | username | query | closeConection
+ "h2" | connections.get("h2") | null | "SELECT 3" | false
+ "derby" | connections.get("derby") | "APP" | "SELECT 3 FROM SYSIBM.SYSDUMMY1" | false
+ "hsqldb" | connections.get("hsqldb") | "SA" | "SELECT 3 FROM INFORMATION_SCHEMA.SYSTEM_USERS" | false
+ "h2" | cpDatasources.get("tomcat").get("h2").getConnection() | null | "SELECT 3" | true
+ "derby" | cpDatasources.get("tomcat").get("derby").getConnection() | "APP" | "SELECT 3 FROM SYSIBM.SYSDUMMY1" | true
+ "hsqldb" | cpDatasources.get("tomcat").get("hsqldb").getConnection()| "SA" | "SELECT 3 FROM INFORMATION_SCHEMA.SYSTEM_USERS" | true
+ "h2" | cpDatasources.get("hikari").get("h2").getConnection() | null | "SELECT 3" | true
+ "derby" | cpDatasources.get("hikari").get("derby").getConnection() | "APP" | "SELECT 3 FROM SYSIBM.SYSDUMMY1" | true
+ "hsqldb" | cpDatasources.get("hikari").get("hsqldb").getConnection()| "SA" | "SELECT 3 FROM INFORMATION_SCHEMA.SYSTEM_USERS" | true
+ "h2" | cpDatasources.get("c3p0").get("h2").getConnection() | null | "SELECT 3" | true
+ "derby" | cpDatasources.get("c3p0").get("derby").getConnection() | "APP" | "SELECT 3 FROM SYSIBM.SYSDUMMY1" | true
+ "hsqldb" | cpDatasources.get("c3p0").get("hsqldb").getConnection() | "SA" | "SELECT 3 FROM INFORMATION_SCHEMA.SYSTEM_USERS" | true
}
@Unroll
- def "prepared statement execute on #driver generates a span"() {
+ def "prepared statement execute on #driver with #connection.getClass().getCanonicalName() generates a span"() {
setup:
PreparedStatement statement = connection.prepareStatement(query)
assert statement.execute()
@@ -91,45 +219,50 @@ class JDBCInstrumentationTest extends AgentTestRunner {
expect:
resultSet.next()
resultSet.getInt(1) == 3
- TEST_WRITER.size() == 1
-
- def trace = TEST_WRITER.firstTrace()
- trace.size() == 1
- def span = trace[0]
-
- span.context().operationName == "${driver}.query"
- span.serviceName == driver
- span.resourceName == query
- span.type == "sql"
- !span.context().getErrorFlag()
- span.context().parentId == 0
-
-
- def tags = span.context().tags
- tags["db.type"] == driver
- tags["db.user"] == username
- tags["span.kind"] == "client"
- tags["component"] == "java-jdbc-prepared_statement"
-
- tags["db.jdbc.url"].contains(driver)
- tags["span.origin.type"] != null
-
- tags["thread.name"] != null
- tags["thread.id"] != null
- tags.size() == username == null ? 7 : 8
+ assertTraces(TEST_WRITER, 1) {
+ trace(0, 1) {
+ span(0) {
+ operationName "${driver}.query"
+ serviceName driver
+ resourceName query
+ spanType DDSpanTypes.SQL
+ errored false
+ tags {
+ "db.type" driver
+ if (username != null) {
+ "db.user" username
+ }
+ "span.kind" Tags.SPAN_KIND_CLIENT
+ "span.type" DDSpanTypes.SQL
+ "component" "java-jdbc-prepared_statement"
+ "db.jdbc.url" jdbcUrls.get(driver)
+ "span.origin.type" String
+ defaultTags()
+ }
+ }
+ }
+ }
cleanup:
statement.close()
+ if (closeConection) {
+ connection.close()
+ }
where:
- driver | connection | username | query
- "h2" | connections.get("h2") | null | "SELECT 3"
- "derby" | connections.get("derby") | "APP" | "SELECT 3 FROM SYSIBM.SYSDUMMY1"
- "hsqldb" | connections.get("hsqldb") | "SA" | "SELECT 3 FROM INFORMATION_SCHEMA.SYSTEM_USERS"
+ driver | connection | username | query | closeConection
+ "h2" | connections.get("h2") | null | "SELECT 3" | false
+ "derby" | connections.get("derby") | "APP" | "SELECT 3 FROM SYSIBM.SYSDUMMY1" | false
+ "h2" | cpDatasources.get("tomcat").get("h2").getConnection() | null | "SELECT 3" | true
+ "derby" | cpDatasources.get("tomcat").get("derby").getConnection()| "APP" | "SELECT 3 FROM SYSIBM.SYSDUMMY1" | true
+ "h2" | cpDatasources.get("hikari").get("h2").getConnection() | null | "SELECT 3" | true
+ "derby" | cpDatasources.get("hikari").get("derby").getConnection()| "APP" | "SELECT 3 FROM SYSIBM.SYSDUMMY1" | true
+ "h2" | cpDatasources.get("c3p0").get("h2").getConnection() | null | "SELECT 3" | true
+ "derby" | cpDatasources.get("c3p0").get("derby").getConnection() | "APP" | "SELECT 3 FROM SYSIBM.SYSDUMMY1" | true
}
@Unroll
- def "prepared statement query on #driver generates a span"() {
+ def "prepared statement query on #driver with #connection.getClass().getCanonicalName() generates a span"() {
setup:
PreparedStatement statement = connection.prepareStatement(query)
ResultSet resultSet = statement.executeQuery()
@@ -137,45 +270,50 @@ class JDBCInstrumentationTest extends AgentTestRunner {
expect:
resultSet.next()
resultSet.getInt(1) == 3
- TEST_WRITER.size() == 1
-
- def trace = TEST_WRITER.firstTrace()
- trace.size() == 1
- def span = trace[0]
-
- span.context().operationName == "${driver}.query"
- span.serviceName == driver
- span.resourceName == query
- span.type == "sql"
- !span.context().getErrorFlag()
- span.context().parentId == 0
-
-
- def tags = span.context().tags
- tags["db.type"] == driver
- tags["db.user"] == username
- tags["span.kind"] == "client"
- tags["component"] == "java-jdbc-prepared_statement"
-
- tags["db.jdbc.url"].contains(driver)
- tags["span.origin.type"] != null
-
- tags["thread.name"] != null
- tags["thread.id"] != null
- tags.size() == username == null ? 7 : 8
+ assertTraces(TEST_WRITER, 1) {
+ trace(0, 1) {
+ span(0) {
+ operationName "${driver}.query"
+ serviceName driver
+ resourceName query
+ spanType DDSpanTypes.SQL
+ errored false
+ tags {
+ "db.type" driver
+ if (username != null) {
+ "db.user" username
+ }
+ "span.kind" Tags.SPAN_KIND_CLIENT
+ "span.type" DDSpanTypes.SQL
+ "component" "java-jdbc-prepared_statement"
+ "db.jdbc.url" jdbcUrls.get(driver)
+ "span.origin.type" String
+ defaultTags()
+ }
+ }
+ }
+ }
cleanup:
statement.close()
+ if (closeConection) {
+ connection.close()
+ }
where:
- driver | connection | username | query
- "h2" | connections.get("h2") | null | "SELECT 3"
- "derby" | connections.get("derby") | "APP" | "SELECT 3 FROM SYSIBM.SYSDUMMY1"
- "hsqldb" | connections.get("hsqldb") | "SA" | "SELECT 3 FROM INFORMATION_SCHEMA.SYSTEM_USERS"
+ driver | connection | username | query | closeConection
+ "h2" | connections.get("h2") | null | "SELECT 3" | false
+ "derby" | connections.get("derby") | "APP" | "SELECT 3 FROM SYSIBM.SYSDUMMY1" | false
+ "h2" | cpDatasources.get("tomcat").get("h2").getConnection() | null | "SELECT 3" | true
+ "derby" | cpDatasources.get("tomcat").get("derby").getConnection()| "APP" | "SELECT 3 FROM SYSIBM.SYSDUMMY1" | true
+ "h2" | cpDatasources.get("hikari").get("h2").getConnection() | null | "SELECT 3" | true
+ "derby" | cpDatasources.get("hikari").get("derby").getConnection()| "APP" | "SELECT 3 FROM SYSIBM.SYSDUMMY1" | true
+ "h2" | cpDatasources.get("c3p0").get("h2").getConnection() | null | "SELECT 3" | true
+ "derby" | cpDatasources.get("c3p0").get("derby").getConnection() | "APP" | "SELECT 3 FROM SYSIBM.SYSDUMMY1" | true
}
@Unroll
- def "statement update on #driver generates a span"() {
+ def "statement update on #driver with #connection.getClass().getCanonicalName() generates a span"() {
setup:
Statement statement = connection.createStatement()
def sql = connection.nativeSQL(query)
@@ -183,87 +321,101 @@ class JDBCInstrumentationTest extends AgentTestRunner {
expect:
!statement.execute(sql)
statement.updateCount == 0
-
- TEST_WRITER.size() == 1
-
- def trace = TEST_WRITER.firstTrace()
- trace.size() == 1
- def span = trace[0]
-
- span.context().operationName == "${driver}.query"
- span.serviceName == driver
- span.resourceName == query
- span.type == "sql"
- !span.context().getErrorFlag()
- span.context().parentId == 0
-
-
- def tags = span.context().tags
- tags["db.type"] == driver
- tags["db.user"] == username
- tags["span.kind"] == "client"
- tags["component"] == "java-jdbc-statement"
-
- tags["db.jdbc.url"].contains(driver)
- tags["span.origin.type"] != null
-
- tags["thread.name"] != null
- tags["thread.id"] != null
- tags.size() == username == null ? 7 : 8
+ assertTraces(TEST_WRITER, 1) {
+ trace(0, 1) {
+ span(0) {
+ operationName "${driver}.query"
+ serviceName driver
+ resourceName query
+ spanType DDSpanTypes.SQL
+ errored false
+ tags {
+ "db.type" driver
+ if (username != null) {
+ "db.user" username
+ }
+ "span.kind" Tags.SPAN_KIND_CLIENT
+ "span.type" DDSpanTypes.SQL
+ "component" "java-jdbc-statement"
+ "db.jdbc.url" jdbcUrls.get(driver)
+ "span.origin.type" String
+ defaultTags()
+ }
+ }
+ }
+ }
cleanup:
statement.close()
+ if (closeConection) {
+ connection.close()
+ }
where:
- driver | connection | username | query
- "h2" | connections.get("h2") | null | "CREATE TABLE S_H2 (id INTEGER not NULL, PRIMARY KEY ( id ))"
- "derby" | connections.get("derby") | "APP" | "CREATE TABLE S_DERBY (id INTEGER not NULL, PRIMARY KEY ( id ))"
- "hsqldb" | connections.get("hsqldb") | "SA" | "CREATE TABLE PUBLIC.S_HSQLDB (id INTEGER not NULL, PRIMARY KEY ( id ))"
+ driver | connection | username | query | closeConection
+ "h2" | connections.get("h2") | null | "CREATE TABLE S_H2 (id INTEGER not NULL, PRIMARY KEY ( id ))" | false
+ "derby" | connections.get("derby") | "APP" | "CREATE TABLE S_DERBY (id INTEGER not NULL, PRIMARY KEY ( id ))" | false
+ "hsqldb" | connections.get("hsqldb") | "SA" | "CREATE TABLE PUBLIC.S_HSQLDB (id INTEGER not NULL, PRIMARY KEY ( id ))" | false
+ "h2" | cpDatasources.get("tomcat").get("h2").getConnection() | null | "CREATE TABLE S_H2_TOMCAT (id INTEGER not NULL, PRIMARY KEY ( id ))" | true
+ "derby" | cpDatasources.get("tomcat").get("derby").getConnection() | "APP" | "CREATE TABLE S_DERBY_TOMCAT (id INTEGER not NULL, PRIMARY KEY ( id ))" | true
+ "hsqldb" | cpDatasources.get("tomcat").get("hsqldb").getConnection()| "SA" | "CREATE TABLE PUBLIC.S_HSQLDB_TOMCAT (id INTEGER not NULL, PRIMARY KEY ( id ))"| true
+ "h2" | cpDatasources.get("hikari").get("h2").getConnection() | null | "CREATE TABLE S_H2_HIKARI (id INTEGER not NULL, PRIMARY KEY ( id ))" | true
+ "derby" | cpDatasources.get("hikari").get("derby").getConnection() | "APP" | "CREATE TABLE S_DERBY_HIKARI (id INTEGER not NULL, PRIMARY KEY ( id ))" | true
+ "hsqldb" | cpDatasources.get("hikari").get("hsqldb").getConnection()| "SA" | "CREATE TABLE PUBLIC.S_HSQLDB_HIKARI (id INTEGER not NULL, PRIMARY KEY ( id ))"| true
+ "h2" | cpDatasources.get("c3p0").get("h2").getConnection() | null | "CREATE TABLE S_H2_C3P0 (id INTEGER not NULL, PRIMARY KEY ( id ))" | true
+ "derby" | cpDatasources.get("c3p0").get("derby").getConnection() | "APP" | "CREATE TABLE S_DERBY_C3P0 (id INTEGER not NULL, PRIMARY KEY ( id ))" | true
+ "hsqldb" | cpDatasources.get("c3p0").get("hsqldb").getConnection() | "SA" | "CREATE TABLE PUBLIC.S_HSQLDB_C3P0 (id INTEGER not NULL, PRIMARY KEY ( id ))" | true
}
@Unroll
- def "prepared statement update on #driver generates a span"() {
+ def "prepared statement update on #driver with #connection.getClass().getCanonicalName() generates a span"() {
setup:
def sql = connection.nativeSQL(query)
PreparedStatement statement = connection.prepareStatement(sql)
expect:
statement.executeUpdate() == 0
- TEST_WRITER.size() == 1
-
- def trace = TEST_WRITER.firstTrace()
- trace.size() == 1
- def span = trace[0]
-
- span.context().operationName == "${driver}.query"
- span.serviceName == driver
- span.resourceName == query
- span.type == "sql"
- !span.context().getErrorFlag()
- span.context().parentId == 0
-
-
- def tags = span.context().tags
- tags["db.type"] == driver
- tags["db.user"] == username
- tags["span.kind"] == "client"
- tags["component"] == "java-jdbc-prepared_statement"
-
- tags["db.jdbc.url"].contains(driver)
- tags["span.origin.type"] != null
-
- tags["thread.name"] != null
- tags["thread.id"] != null
- tags.size() == username == null ? 7 : 8
+ assertTraces(TEST_WRITER, 1) {
+ trace(0, 1) {
+ span(0) {
+ operationName "${driver}.query"
+ serviceName driver
+ resourceName query
+ spanType DDSpanTypes.SQL
+ errored false
+ tags {
+ "db.type" driver
+ if (username != null) {
+ "db.user" username
+ }
+ "span.kind" Tags.SPAN_KIND_CLIENT
+ "span.type" DDSpanTypes.SQL
+ "component" "java-jdbc-prepared_statement"
+ "db.jdbc.url" jdbcUrls.get(driver)
+ "span.origin.type" String
+ defaultTags()
+ }
+ }
+ }
+ }
cleanup:
statement.close()
+ if (closeConection) {
+ connection.close()
+ }
where:
- driver | connection | username | query
- "h2" | connections.get("h2") | null | "CREATE TABLE PS_H2 (id INTEGER not NULL, PRIMARY KEY ( id ))"
- "derby" | connections.get("derby") | "APP" | "CREATE TABLE PS_DERBY (id INTEGER not NULL, PRIMARY KEY ( id ))"
- "hsqldb" | connections.get("hsqldb") | "SA" | "CREATE TABLE PUBLIC.PS_HSQLDB (id INTEGER not NULL, PRIMARY KEY ( id ))"
+ driver | connection | username | query | closeConection
+ "h2" | connections.get("h2") | null | "CREATE TABLE PS_H2 (id INTEGER not NULL, PRIMARY KEY ( id ))" | false
+ "derby" | connections.get("derby") | "APP" | "CREATE TABLE PS_DERBY (id INTEGER not NULL, PRIMARY KEY ( id ))" | false
+ "h2" | cpDatasources.get("tomcat").get("h2").getConnection() | null | "CREATE TABLE PS_H2_TOMCAT (id INTEGER not NULL, PRIMARY KEY ( id ))" | true
+ "derby" | cpDatasources.get("tomcat").get("derby").getConnection() | "APP" | "CREATE TABLE PS_DERBY_TOMCAT (id INTEGER not NULL, PRIMARY KEY ( id ))" | true
+ "h2" | cpDatasources.get("hikari").get("h2").getConnection() | null | "CREATE TABLE PS_H2_HIKARI (id INTEGER not NULL, PRIMARY KEY ( id ))" | true
+ "derby" | cpDatasources.get("hikari").get("derby").getConnection() | "APP" | "CREATE TABLE PS_DERBY_HIKARI (id INTEGER not NULL, PRIMARY KEY ( id ))" | true
+ "h2" | cpDatasources.get("c3p0").get("h2").getConnection() | null | "CREATE TABLE PS_H2_C3P0 (id INTEGER not NULL, PRIMARY KEY ( id ))" | true
+ "derby" | cpDatasources.get("c3p0").get("derby").getConnection() | "APP" | "CREATE TABLE PS_DERBY_C3P0 (id INTEGER not NULL, PRIMARY KEY ( id ))" | true
+
}
@Unroll
@@ -291,36 +443,33 @@ class JDBCInstrumentationTest extends AgentTestRunner {
then:
rs.next()
rs.getInt(1) == 3
- TEST_WRITER.size() == 1
-
- def trace = TEST_WRITER.firstTrace()
- trace.size() == 1
-
- and:
- def span = trace[0]
- span.context().operationName == "${driver}.query"
- span.serviceName == driver
- span.resourceName == query
- span.type == "sql"
- !span.context().getErrorFlag()
- span.context().parentId == 0
-
- def tags = span.context().tags
- tags["db.type"] == driver
- tags["db.user"] == user
- tags["span.kind"] == "client"
+ assertTraces(TEST_WRITER, 1) {
+ trace(0, 1) {
+ span(0) {
+ operationName "${driver}.query"
+ serviceName driver
+ resourceName query
+ spanType DDSpanTypes.SQL
+ errored false
+ tags {
+ "db.type" driver
+ if (username != null) {
+ "db.user" username
+ }
if (prepareStatement) {
- tags["component"] == "java-jdbc-prepared_statement"
+ "component" "java-jdbc-prepared_statement"
} else {
- tags["component"] == "java-jdbc-statement"
+ "component" "java-jdbc-statement"
+ }
+ "span.kind" Tags.SPAN_KIND_CLIENT
+ "span.type" DDSpanTypes.SQL
+ "db.jdbc.url" jdbcUrls.get(driver)
+ "span.origin.type" String
+ defaultTags()
+ }
+ }
+ }
}
-
- tags["db.jdbc.url"].contains(driver)
- tags["span.origin.type"] != null
-
- tags["thread.name"] != null
- tags["thread.id"] != null
- tags.size() == user == null ? 7 : 8
cleanup:
if (statement != null) {
@@ -331,11 +480,97 @@ class JDBCInstrumentationTest extends AgentTestRunner {
}
where:
- prepareStatement | driver | driverClass | url | user | query
+ prepareStatement | driver | driverClass | url | username | query
true | "h2" | new Driver() | "jdbc:h2:mem:" + dbName | null | "SELECT 3;"
true | "derby" | new EmbeddedDriver() | "jdbc:derby:memory:" + dbName + ";create=true" | "APP" | "SELECT 3 FROM SYSIBM.SYSDUMMY1"
false | "h2" | new Driver() | "jdbc:h2:mem:" + dbName | null | "SELECT 3;"
false | "derby" | new EmbeddedDriver() | "jdbc:derby:memory:" + dbName + ";create=true" | "APP" | "SELECT 3 FROM SYSIBM.SYSDUMMY1"
}
+ @Unroll
+ def "#connectionPoolName connections should be cached in case of wrapped connections"() {
+ setup:
+ String dbType = "hsqldb"
+ DataSource ds = createDS(connectionPoolName, dbType, jdbcUrls.get(dbType))
+ String query = "SELECT 3 FROM INFORMATION_SCHEMA.SYSTEM_USERS"
+ int numQueries = 5
+ Connection connection = null
+ Statement statement = null
+ ResultSet rs = null
+ int[] res = new int[numQueries]
+
+ when:
+ for (int i = 0; i < numQueries; ++i) {
+ try {
+ connection = ds.getConnection()
+ statement = connection.prepareStatement(query)
+ rs = statement.executeQuery()
+ if (rs.next()) {
+ res[i] = rs.getInt(1)
+ } else {
+ res[i] = 0
+ }
+ } finally {
+ connection.close()
+ }
+ }
+
+ then:
+ for (int i = 0; i < numQueries; ++i) {
+ res[i] == 3
+ }
+ assertTraces(TEST_WRITER, 6) {
+ trace(0, 1) {
+ span(0) {
+ operationName "${dbType}.query"
+ serviceName dbType
+ resourceName "CALL USER()"
+ spanType DDSpanTypes.SQL
+ errored false
+ tags {
+ "db.type" "hsqldb"
+ "db.user" "SA"
+ "component" "java-jdbc-statement"
+ "span.kind" Tags.SPAN_KIND_CLIENT
+ "span.type" DDSpanTypes.SQL
+ "db.jdbc.url" jdbcUrls.get(dbType)
+ "span.origin.type" String
+ defaultTags()
+ }
+ }
+ }
+ for (int i = 1; i < numQueries + 1; ++i) {
+ trace(i, 1) {
+ span(0) {
+ operationName "${dbType}.query"
+ serviceName dbType
+ resourceName query
+ spanType DDSpanTypes.SQL
+ errored false
+ tags {
+ "db.type" dbType
+ "db.user" "SA"
+ "component" "java-jdbc-prepared_statement"
+ "span.kind" Tags.SPAN_KIND_CLIENT
+ "span.type" DDSpanTypes.SQL
+ "db.jdbc.url" jdbcUrls.get(dbType)
+ "span.origin.type" String
+ defaultTags()
+ }
+ }
+ }
+ }
+ }
+
+ cleanup:
+ if (ds instanceof Closeable) {
+ ds.close()
+ }
+
+ where:
+ connectionPoolName | _
+ "hikari" | _
+ "tomcat" | _
+ "c3p0" | _
+ }
}

Fixes #379.

Connection pools were creating proxy connection objects, so the proxy objects were cached. By unwrapping the proxies, the connection objects will be correctly cached and identified.

Refactored tests to use connection pools, and hsqldb connections only on statements (since they will not make a USER() query in a getMetaData call).