823 lines
37 KiB
Groovy
823 lines
37 KiB
Groovy
/*
|
|
* Copyright The OpenTelemetry Authors
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
import static io.opentelemetry.api.trace.SpanKind.CLIENT
|
|
import static io.opentelemetry.api.trace.SpanKind.INTERNAL
|
|
|
|
import com.mchange.v2.c3p0.ComboPooledDataSource
|
|
import com.zaxxer.hikari.HikariConfig
|
|
import com.zaxxer.hikari.HikariDataSource
|
|
import io.opentelemetry.api.trace.SpanKind
|
|
import io.opentelemetry.instrumentation.jdbc.TestConnection
|
|
import io.opentelemetry.instrumentation.jdbc.TestDriver
|
|
import io.opentelemetry.instrumentation.test.AgentInstrumentationSpecification
|
|
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes
|
|
import java.sql.CallableStatement
|
|
import java.sql.Connection
|
|
import java.sql.DatabaseMetaData
|
|
import java.sql.PreparedStatement
|
|
import java.sql.ResultSet
|
|
import java.sql.SQLException
|
|
import java.sql.Statement
|
|
import javax.sql.DataSource
|
|
import org.apache.derby.jdbc.EmbeddedDataSource
|
|
import org.apache.derby.jdbc.EmbeddedDriver
|
|
import org.h2.Driver
|
|
import org.h2.jdbcx.JdbcDataSource
|
|
import org.hsqldb.jdbc.JDBCDriver
|
|
import spock.lang.Shared
|
|
import spock.lang.Unroll
|
|
|
|
@Unroll
|
|
class JdbcInstrumentationTest extends AgentInstrumentationSpecification {
|
|
|
|
@Shared
|
|
def dbName = "jdbcUnitTest"
|
|
@Shared
|
|
def dbNameLower = dbName.toLowerCase()
|
|
|
|
@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, String> jdbcUserNames = [
|
|
"h2" : null,
|
|
"derby" : "APP",
|
|
"hsqldb": "SA",
|
|
]
|
|
|
|
@Shared
|
|
private Properties connectionProps = {
|
|
def props = new Properties()
|
|
// props.put("user", "someUser")
|
|
// props.put("password", "somePassword")
|
|
props.put("databaseName", "someDb")
|
|
props.put("OPEN_NEW", "true") // So H2 doesn't complain about username/password.
|
|
return props
|
|
}()
|
|
|
|
// 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() {
|
|
prepareConnectionPoolDatasources()
|
|
}
|
|
|
|
def cleanupSpec() {
|
|
cpDatasources.values().each {
|
|
it.values().each {
|
|
datasource ->
|
|
if (datasource instanceof Closeable) {
|
|
datasource.close()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
def "basic statement with #connection.getClass().getCanonicalName() on #system generates spans"() {
|
|
setup:
|
|
Statement statement = connection.createStatement()
|
|
ResultSet resultSet = runWithSpan("parent") {
|
|
return statement.executeQuery(query)
|
|
}
|
|
|
|
expect:
|
|
resultSet.next()
|
|
resultSet.getInt(1) == 3
|
|
assertTraces(1) {
|
|
trace(0, 2) {
|
|
span(0) {
|
|
name "parent"
|
|
kind SpanKind.INTERNAL
|
|
hasNoParent()
|
|
}
|
|
span(1) {
|
|
name spanName
|
|
kind CLIENT
|
|
childOf span(0)
|
|
attributes {
|
|
"$SemanticAttributes.DB_SYSTEM.key" system
|
|
"$SemanticAttributes.DB_NAME.key" dbNameLower
|
|
if (username != null) {
|
|
"$SemanticAttributes.DB_USER.key" username
|
|
}
|
|
"$SemanticAttributes.DB_CONNECTION_STRING.key" url
|
|
"$SemanticAttributes.DB_STATEMENT.key" sanitizedQuery
|
|
"$SemanticAttributes.DB_OPERATION.key" "SELECT"
|
|
"$SemanticAttributes.DB_SQL_TABLE.key" table
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
cleanup:
|
|
statement.close()
|
|
connection.close()
|
|
|
|
where:
|
|
system | connection | username | query | sanitizedQuery | spanName | url | table
|
|
"h2" | new Driver().connect(jdbcUrls.get("h2"), null) | null | "SELECT 3" | "SELECT ?" | "SELECT $dbNameLower" | "h2:mem:" | null
|
|
"derby" | new EmbeddedDriver().connect(jdbcUrls.get("derby"), null) | "APP" | "SELECT 3 FROM SYSIBM.SYSDUMMY1" | "SELECT ? FROM SYSIBM.SYSDUMMY1" | "SELECT SYSIBM.SYSDUMMY1" | "derby:memory:" | "SYSIBM.SYSDUMMY1"
|
|
"hsqldb" | new JDBCDriver().connect(jdbcUrls.get("hsqldb"), null) | "SA" | "SELECT 3 FROM INFORMATION_SCHEMA.SYSTEM_USERS" | "SELECT ? FROM INFORMATION_SCHEMA.SYSTEM_USERS" | "SELECT INFORMATION_SCHEMA.SYSTEM_USERS" | "hsqldb:mem:" | "INFORMATION_SCHEMA.SYSTEM_USERS"
|
|
"h2" | new Driver().connect(jdbcUrls.get("h2"), connectionProps) | null | "SELECT 3" | "SELECT ?" | "SELECT $dbNameLower" | "h2:mem:" | null
|
|
"derby" | new EmbeddedDriver().connect(jdbcUrls.get("derby"), connectionProps) | "APP" | "SELECT 3 FROM SYSIBM.SYSDUMMY1" | "SELECT ? FROM SYSIBM.SYSDUMMY1" | "SELECT SYSIBM.SYSDUMMY1" | "derby:memory:" | "SYSIBM.SYSDUMMY1"
|
|
"hsqldb" | new JDBCDriver().connect(jdbcUrls.get("hsqldb"), connectionProps) | "SA" | "SELECT 3 FROM INFORMATION_SCHEMA.SYSTEM_USERS" | "SELECT ? FROM INFORMATION_SCHEMA.SYSTEM_USERS" | "SELECT INFORMATION_SCHEMA.SYSTEM_USERS" | "hsqldb:mem:" | "INFORMATION_SCHEMA.SYSTEM_USERS"
|
|
"h2" | cpDatasources.get("tomcat").get("h2").getConnection() | null | "SELECT 3" | "SELECT ?" | "SELECT $dbNameLower" | "h2:mem:" | null
|
|
"derby" | cpDatasources.get("tomcat").get("derby").getConnection() | "APP" | "SELECT 3 FROM SYSIBM.SYSDUMMY1" | "SELECT ? FROM SYSIBM.SYSDUMMY1" | "SELECT SYSIBM.SYSDUMMY1" | "derby:memory:" | "SYSIBM.SYSDUMMY1"
|
|
"hsqldb" | cpDatasources.get("tomcat").get("hsqldb").getConnection() | "SA" | "SELECT 3 FROM INFORMATION_SCHEMA.SYSTEM_USERS" | "SELECT ? FROM INFORMATION_SCHEMA.SYSTEM_USERS" | "SELECT INFORMATION_SCHEMA.SYSTEM_USERS" | "hsqldb:mem:" | "INFORMATION_SCHEMA.SYSTEM_USERS"
|
|
"h2" | cpDatasources.get("hikari").get("h2").getConnection() | null | "SELECT 3" | "SELECT ?" | "SELECT $dbNameLower" | "h2:mem:" | null
|
|
"derby" | cpDatasources.get("hikari").get("derby").getConnection() | "APP" | "SELECT 3 FROM SYSIBM.SYSDUMMY1" | "SELECT ? FROM SYSIBM.SYSDUMMY1" | "SELECT SYSIBM.SYSDUMMY1" | "derby:memory:" | "SYSIBM.SYSDUMMY1"
|
|
"hsqldb" | cpDatasources.get("hikari").get("hsqldb").getConnection() | "SA" | "SELECT 3 FROM INFORMATION_SCHEMA.SYSTEM_USERS" | "SELECT ? FROM INFORMATION_SCHEMA.SYSTEM_USERS" | "SELECT INFORMATION_SCHEMA.SYSTEM_USERS" | "hsqldb:mem:" | "INFORMATION_SCHEMA.SYSTEM_USERS"
|
|
"h2" | cpDatasources.get("c3p0").get("h2").getConnection() | null | "SELECT 3" | "SELECT ?" | "SELECT $dbNameLower" | "h2:mem:" | null
|
|
"derby" | cpDatasources.get("c3p0").get("derby").getConnection() | "APP" | "SELECT 3 FROM SYSIBM.SYSDUMMY1" | "SELECT ? FROM SYSIBM.SYSDUMMY1" | "SELECT SYSIBM.SYSDUMMY1" | "derby:memory:" | "SYSIBM.SYSDUMMY1"
|
|
"hsqldb" | cpDatasources.get("c3p0").get("hsqldb").getConnection() | "SA" | "SELECT 3 FROM INFORMATION_SCHEMA.SYSTEM_USERS" | "SELECT ? FROM INFORMATION_SCHEMA.SYSTEM_USERS" | "SELECT INFORMATION_SCHEMA.SYSTEM_USERS" | "hsqldb:mem:" | "INFORMATION_SCHEMA.SYSTEM_USERS"
|
|
}
|
|
|
|
def "prepared statement execute on #system with #connection.getClass().getCanonicalName() generates a span"() {
|
|
setup:
|
|
PreparedStatement statement = connection.prepareStatement(query)
|
|
ResultSet resultSet = runWithSpan("parent") {
|
|
assert statement.execute()
|
|
return statement.resultSet
|
|
}
|
|
|
|
expect:
|
|
resultSet.next()
|
|
resultSet.getInt(1) == 3
|
|
assertTraces(1) {
|
|
trace(0, 2) {
|
|
span(0) {
|
|
name "parent"
|
|
kind SpanKind.INTERNAL
|
|
hasNoParent()
|
|
}
|
|
span(1) {
|
|
name spanName
|
|
kind CLIENT
|
|
childOf span(0)
|
|
attributes {
|
|
"$SemanticAttributes.DB_SYSTEM.key" system
|
|
"$SemanticAttributes.DB_NAME.key" dbNameLower
|
|
if (username != null) {
|
|
"$SemanticAttributes.DB_USER.key" username
|
|
}
|
|
"$SemanticAttributes.DB_CONNECTION_STRING.key" url
|
|
"$SemanticAttributes.DB_STATEMENT.key" sanitizedQuery
|
|
"$SemanticAttributes.DB_OPERATION.key" "SELECT"
|
|
"$SemanticAttributes.DB_SQL_TABLE.key" table
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
cleanup:
|
|
statement.close()
|
|
connection.close()
|
|
|
|
where:
|
|
system | connection | username | query | sanitizedQuery | spanName | url | table
|
|
"h2" | new Driver().connect(jdbcUrls.get("h2"), null) | null | "SELECT 3" | "SELECT ?" | "SELECT $dbNameLower" | "h2:mem:" | null
|
|
"derby" | new EmbeddedDriver().connect(jdbcUrls.get("derby"), null) | "APP" | "SELECT 3 FROM SYSIBM.SYSDUMMY1" | "SELECT ? FROM SYSIBM.SYSDUMMY1" | "SELECT SYSIBM.SYSDUMMY1" | "derby:memory:" | "SYSIBM.SYSDUMMY1"
|
|
"h2" | cpDatasources.get("tomcat").get("h2").getConnection() | null | "SELECT 3" | "SELECT ?" | "SELECT $dbNameLower" | "h2:mem:" | null
|
|
"derby" | cpDatasources.get("tomcat").get("derby").getConnection() | "APP" | "SELECT 3 FROM SYSIBM.SYSDUMMY1" | "SELECT ? FROM SYSIBM.SYSDUMMY1" | "SELECT SYSIBM.SYSDUMMY1" | "derby:memory:" | "SYSIBM.SYSDUMMY1"
|
|
"h2" | cpDatasources.get("hikari").get("h2").getConnection() | null | "SELECT 3" | "SELECT ?" | "SELECT $dbNameLower" | "h2:mem:" | null
|
|
"derby" | cpDatasources.get("hikari").get("derby").getConnection() | "APP" | "SELECT 3 FROM SYSIBM.SYSDUMMY1" | "SELECT ? FROM SYSIBM.SYSDUMMY1" | "SELECT SYSIBM.SYSDUMMY1" | "derby:memory:" | "SYSIBM.SYSDUMMY1"
|
|
"h2" | cpDatasources.get("c3p0").get("h2").getConnection() | null | "SELECT 3" | "SELECT ?" | "SELECT $dbNameLower" | "h2:mem:" | null
|
|
"derby" | cpDatasources.get("c3p0").get("derby").getConnection() | "APP" | "SELECT 3 FROM SYSIBM.SYSDUMMY1" | "SELECT ? FROM SYSIBM.SYSDUMMY1" | "SELECT SYSIBM.SYSDUMMY1" | "derby:memory:" | "SYSIBM.SYSDUMMY1"
|
|
}
|
|
|
|
def "prepared statement query on #system with #connection.getClass().getCanonicalName() generates a span"() {
|
|
setup:
|
|
PreparedStatement statement = connection.prepareStatement(query)
|
|
ResultSet resultSet = runWithSpan("parent") {
|
|
return statement.executeQuery()
|
|
}
|
|
|
|
expect:
|
|
resultSet.next()
|
|
resultSet.getInt(1) == 3
|
|
assertTraces(1) {
|
|
trace(0, 2) {
|
|
span(0) {
|
|
name "parent"
|
|
kind SpanKind.INTERNAL
|
|
hasNoParent()
|
|
}
|
|
span(1) {
|
|
name spanName
|
|
kind CLIENT
|
|
childOf span(0)
|
|
attributes {
|
|
"$SemanticAttributes.DB_SYSTEM.key" system
|
|
"$SemanticAttributes.DB_NAME.key" dbNameLower
|
|
if (username != null) {
|
|
"$SemanticAttributes.DB_USER.key" username
|
|
}
|
|
"$SemanticAttributes.DB_CONNECTION_STRING.key" url
|
|
"$SemanticAttributes.DB_STATEMENT.key" sanitizedQuery
|
|
"$SemanticAttributes.DB_OPERATION.key" "SELECT"
|
|
"$SemanticAttributes.DB_SQL_TABLE.key" table
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
cleanup:
|
|
statement.close()
|
|
connection.close()
|
|
|
|
where:
|
|
system | connection | username | query | sanitizedQuery | spanName | url | table
|
|
"h2" | new Driver().connect(jdbcUrls.get("h2"), null) | null | "SELECT 3" | "SELECT ?" | "SELECT $dbNameLower" | "h2:mem:" | null
|
|
"derby" | new EmbeddedDriver().connect(jdbcUrls.get("derby"), null) | "APP" | "SELECT 3 FROM SYSIBM.SYSDUMMY1" | "SELECT ? FROM SYSIBM.SYSDUMMY1" | "SELECT SYSIBM.SYSDUMMY1" | "derby:memory:" | "SYSIBM.SYSDUMMY1"
|
|
"h2" | cpDatasources.get("tomcat").get("h2").getConnection() | null | "SELECT 3" | "SELECT ?" | "SELECT $dbNameLower" | "h2:mem:" | null
|
|
"derby" | cpDatasources.get("tomcat").get("derby").getConnection() | "APP" | "SELECT 3 FROM SYSIBM.SYSDUMMY1" | "SELECT ? FROM SYSIBM.SYSDUMMY1" | "SELECT SYSIBM.SYSDUMMY1" | "derby:memory:" | "SYSIBM.SYSDUMMY1"
|
|
"h2" | cpDatasources.get("hikari").get("h2").getConnection() | null | "SELECT 3" | "SELECT ?" | "SELECT $dbNameLower" | "h2:mem:" | null
|
|
"derby" | cpDatasources.get("hikari").get("derby").getConnection() | "APP" | "SELECT 3 FROM SYSIBM.SYSDUMMY1" | "SELECT ? FROM SYSIBM.SYSDUMMY1" | "SELECT SYSIBM.SYSDUMMY1" | "derby:memory:" | "SYSIBM.SYSDUMMY1"
|
|
"h2" | cpDatasources.get("c3p0").get("h2").getConnection() | null | "SELECT 3" | "SELECT ?" | "SELECT $dbNameLower" | "h2:mem:" | null
|
|
"derby" | cpDatasources.get("c3p0").get("derby").getConnection() | "APP" | "SELECT 3 FROM SYSIBM.SYSDUMMY1" | "SELECT ? FROM SYSIBM.SYSDUMMY1" | "SELECT SYSIBM.SYSDUMMY1" | "derby:memory:" | "SYSIBM.SYSDUMMY1"
|
|
}
|
|
|
|
def "prepared call on #system with #connection.getClass().getCanonicalName() generates a span"() {
|
|
setup:
|
|
CallableStatement statement = connection.prepareCall(query)
|
|
ResultSet resultSet = runWithSpan("parent") {
|
|
return statement.executeQuery()
|
|
}
|
|
|
|
expect:
|
|
resultSet.next()
|
|
resultSet.getInt(1) == 3
|
|
assertTraces(1) {
|
|
trace(0, 2) {
|
|
span(0) {
|
|
name "parent"
|
|
kind SpanKind.INTERNAL
|
|
hasNoParent()
|
|
}
|
|
span(1) {
|
|
name spanName
|
|
kind CLIENT
|
|
childOf span(0)
|
|
attributes {
|
|
"$SemanticAttributes.DB_SYSTEM.key" system
|
|
"$SemanticAttributes.DB_NAME.key" dbName.toLowerCase()
|
|
if (username != null) {
|
|
"$SemanticAttributes.DB_USER.key" username
|
|
}
|
|
"$SemanticAttributes.DB_CONNECTION_STRING.key" url
|
|
"$SemanticAttributes.DB_STATEMENT.key" sanitizedQuery
|
|
"$SemanticAttributes.DB_OPERATION.key" "SELECT"
|
|
"$SemanticAttributes.DB_SQL_TABLE.key" table
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
cleanup:
|
|
statement.close()
|
|
connection.close()
|
|
|
|
where:
|
|
system | connection | username | query | sanitizedQuery | spanName | url | table
|
|
"h2" | new Driver().connect(jdbcUrls.get("h2"), null) | null | "SELECT 3" | "SELECT ?" | "SELECT $dbNameLower" | "h2:mem:" | null
|
|
"derby" | new EmbeddedDriver().connect(jdbcUrls.get("derby"), null) | "APP" | "SELECT 3 FROM SYSIBM.SYSDUMMY1" | "SELECT ? FROM SYSIBM.SYSDUMMY1" | "SELECT SYSIBM.SYSDUMMY1" | "derby:memory:" | "SYSIBM.SYSDUMMY1"
|
|
"h2" | cpDatasources.get("tomcat").get("h2").getConnection() | null | "SELECT 3" | "SELECT ?" | "SELECT $dbNameLower" | "h2:mem:" | null
|
|
"derby" | cpDatasources.get("tomcat").get("derby").getConnection() | "APP" | "SELECT 3 FROM SYSIBM.SYSDUMMY1" | "SELECT ? FROM SYSIBM.SYSDUMMY1" | "SELECT SYSIBM.SYSDUMMY1" | "derby:memory:" | "SYSIBM.SYSDUMMY1"
|
|
"h2" | cpDatasources.get("hikari").get("h2").getConnection() | null | "SELECT 3" | "SELECT ?" | "SELECT $dbNameLower" | "h2:mem:" | null
|
|
"derby" | cpDatasources.get("hikari").get("derby").getConnection() | "APP" | "SELECT 3 FROM SYSIBM.SYSDUMMY1" | "SELECT ? FROM SYSIBM.SYSDUMMY1" | "SELECT SYSIBM.SYSDUMMY1" | "derby:memory:" | "SYSIBM.SYSDUMMY1"
|
|
"h2" | cpDatasources.get("c3p0").get("h2").getConnection() | null | "SELECT 3" | "SELECT ?" | "SELECT $dbNameLower" | "h2:mem:" | null
|
|
"derby" | cpDatasources.get("c3p0").get("derby").getConnection() | "APP" | "SELECT 3 FROM SYSIBM.SYSDUMMY1" | "SELECT ? FROM SYSIBM.SYSDUMMY1" | "SELECT SYSIBM.SYSDUMMY1" | "derby:memory:" | "SYSIBM.SYSDUMMY1"
|
|
}
|
|
|
|
def "statement update on #system with #connection.getClass().getCanonicalName() generates a span"() {
|
|
setup:
|
|
Statement statement = connection.createStatement()
|
|
def sql = connection.nativeSQL(query)
|
|
|
|
expect:
|
|
runWithSpan("parent") {
|
|
return !statement.execute(sql)
|
|
}
|
|
statement.updateCount == 0
|
|
assertTraces(1) {
|
|
trace(0, 2) {
|
|
span(0) {
|
|
name "parent"
|
|
kind SpanKind.INTERNAL
|
|
hasNoParent()
|
|
}
|
|
span(1) {
|
|
name dbNameLower
|
|
kind CLIENT
|
|
childOf span(0)
|
|
attributes {
|
|
"$SemanticAttributes.DB_SYSTEM.key" system
|
|
"$SemanticAttributes.DB_NAME.key" dbNameLower
|
|
if (username != null) {
|
|
"$SemanticAttributes.DB_USER.key" username
|
|
}
|
|
"$SemanticAttributes.DB_STATEMENT.key" query
|
|
"$SemanticAttributes.DB_CONNECTION_STRING.key" url
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
cleanup:
|
|
statement.close()
|
|
connection.close()
|
|
|
|
where:
|
|
system | connection | username | query | url
|
|
"h2" | new Driver().connect(jdbcUrls.get("h2"), null) | null | "CREATE TABLE S_H2 (id INTEGER not NULL, PRIMARY KEY ( id ))" | "h2:mem:"
|
|
"derby" | new EmbeddedDriver().connect(jdbcUrls.get("derby"), null) | "APP" | "CREATE TABLE S_DERBY (id INTEGER not NULL, PRIMARY KEY ( id ))" | "derby:memory:"
|
|
"hsqldb" | new JDBCDriver().connect(jdbcUrls.get("hsqldb"), null) | "SA" | "CREATE TABLE PUBLIC.S_HSQLDB (id INTEGER not NULL, PRIMARY KEY ( id ))" | "hsqldb:mem:"
|
|
"h2" | cpDatasources.get("tomcat").get("h2").getConnection() | null | "CREATE TABLE S_H2_TOMCAT (id INTEGER not NULL, PRIMARY KEY ( id ))" | "h2:mem:"
|
|
"derby" | cpDatasources.get("tomcat").get("derby").getConnection() | "APP" | "CREATE TABLE S_DERBY_TOMCAT (id INTEGER not NULL, PRIMARY KEY ( id ))" | "derby:memory:"
|
|
"hsqldb" | cpDatasources.get("tomcat").get("hsqldb").getConnection() | "SA" | "CREATE TABLE PUBLIC.S_HSQLDB_TOMCAT (id INTEGER not NULL, PRIMARY KEY ( id ))" | "hsqldb:mem:"
|
|
"h2" | cpDatasources.get("hikari").get("h2").getConnection() | null | "CREATE TABLE S_H2_HIKARI (id INTEGER not NULL, PRIMARY KEY ( id ))" | "h2:mem:"
|
|
"derby" | cpDatasources.get("hikari").get("derby").getConnection() | "APP" | "CREATE TABLE S_DERBY_HIKARI (id INTEGER not NULL, PRIMARY KEY ( id ))" | "derby:memory:"
|
|
"hsqldb" | cpDatasources.get("hikari").get("hsqldb").getConnection() | "SA" | "CREATE TABLE PUBLIC.S_HSQLDB_HIKARI (id INTEGER not NULL, PRIMARY KEY ( id ))" | "hsqldb:mem:"
|
|
"h2" | cpDatasources.get("c3p0").get("h2").getConnection() | null | "CREATE TABLE S_H2_C3P0 (id INTEGER not NULL, PRIMARY KEY ( id ))" | "h2:mem:"
|
|
"derby" | cpDatasources.get("c3p0").get("derby").getConnection() | "APP" | "CREATE TABLE S_DERBY_C3P0 (id INTEGER not NULL, PRIMARY KEY ( id ))" | "derby:memory:"
|
|
"hsqldb" | cpDatasources.get("c3p0").get("hsqldb").getConnection() | "SA" | "CREATE TABLE PUBLIC.S_HSQLDB_C3P0 (id INTEGER not NULL, PRIMARY KEY ( id ))" | "hsqldb:mem:"
|
|
}
|
|
|
|
def "prepared statement update on #system with #connection.getClass().getCanonicalName() generates a span"() {
|
|
setup:
|
|
def sql = connection.nativeSQL(query)
|
|
PreparedStatement statement = connection.prepareStatement(sql)
|
|
|
|
expect:
|
|
runWithSpan("parent") {
|
|
return statement.executeUpdate() == 0
|
|
}
|
|
assertTraces(1) {
|
|
trace(0, 2) {
|
|
span(0) {
|
|
name "parent"
|
|
kind SpanKind.INTERNAL
|
|
hasNoParent()
|
|
}
|
|
span(1) {
|
|
name dbNameLower
|
|
kind CLIENT
|
|
childOf span(0)
|
|
attributes {
|
|
"$SemanticAttributes.DB_SYSTEM.key" system
|
|
"$SemanticAttributes.DB_NAME.key" dbName.toLowerCase()
|
|
if (username != null) {
|
|
"$SemanticAttributes.DB_USER.key" username
|
|
}
|
|
"$SemanticAttributes.DB_STATEMENT.key" query
|
|
"$SemanticAttributes.DB_CONNECTION_STRING.key" url
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
cleanup:
|
|
statement.close()
|
|
connection.close()
|
|
|
|
where:
|
|
system | connection | username | query | url
|
|
"h2" | new Driver().connect(jdbcUrls.get("h2"), null) | null | "CREATE TABLE PS_H2 (id INTEGER not NULL, PRIMARY KEY ( id ))" | "h2:mem:"
|
|
"derby" | new EmbeddedDriver().connect(jdbcUrls.get("derby"), null) | "APP" | "CREATE TABLE PS_DERBY (id INTEGER not NULL, PRIMARY KEY ( id ))" | "derby:memory:"
|
|
"h2" | cpDatasources.get("tomcat").get("h2").getConnection() | null | "CREATE TABLE PS_H2_TOMCAT (id INTEGER not NULL, PRIMARY KEY ( id ))" | "h2:mem:"
|
|
"derby" | cpDatasources.get("tomcat").get("derby").getConnection() | "APP" | "CREATE TABLE PS_DERBY_TOMCAT (id INTEGER not NULL, PRIMARY KEY ( id ))" | "derby:memory:"
|
|
"h2" | cpDatasources.get("hikari").get("h2").getConnection() | null | "CREATE TABLE PS_H2_HIKARI (id INTEGER not NULL, PRIMARY KEY ( id ))" | "h2:mem:"
|
|
"derby" | cpDatasources.get("hikari").get("derby").getConnection() | "APP" | "CREATE TABLE PS_DERBY_HIKARI (id INTEGER not NULL, PRIMARY KEY ( id ))" | "derby:memory:"
|
|
"h2" | cpDatasources.get("c3p0").get("h2").getConnection() | null | "CREATE TABLE PS_H2_C3P0 (id INTEGER not NULL, PRIMARY KEY ( id ))" | "h2:mem:"
|
|
"derby" | cpDatasources.get("c3p0").get("derby").getConnection() | "APP" | "CREATE TABLE PS_DERBY_C3P0 (id INTEGER not NULL, PRIMARY KEY ( id ))" | "derby:memory:"
|
|
}
|
|
|
|
def "connection constructor throwing then generating correct spans after recovery using #driver connection (prepare statement = #prepareStatement)"() {
|
|
setup:
|
|
Connection connection = null
|
|
|
|
when:
|
|
try {
|
|
connection = new TestConnection(true)
|
|
connection.url = "jdbc:testdb://localhost"
|
|
} catch (Exception ignored) {
|
|
connection = driver.connect(jdbcUrl, null)
|
|
}
|
|
|
|
def (Statement statement, ResultSet rs) = runWithSpan("parent") {
|
|
if (prepareStatement) {
|
|
def statement = connection.prepareStatement(query)
|
|
return new Tuple(statement, statement.executeQuery())
|
|
}
|
|
|
|
def statement = connection.createStatement()
|
|
return new Tuple(statement, statement.executeQuery(query))
|
|
}
|
|
|
|
then:
|
|
rs.next()
|
|
rs.getInt(1) == 3
|
|
assertTraces(1) {
|
|
trace(0, 2) {
|
|
span(0) {
|
|
name "parent"
|
|
kind SpanKind.INTERNAL
|
|
hasNoParent()
|
|
}
|
|
span(1) {
|
|
name spanName
|
|
kind CLIENT
|
|
childOf span(0)
|
|
attributes {
|
|
"$SemanticAttributes.DB_SYSTEM.key" system
|
|
"$SemanticAttributes.DB_NAME.key" dbNameLower
|
|
if (username != null) {
|
|
"$SemanticAttributes.DB_USER.key" username
|
|
}
|
|
"$SemanticAttributes.DB_CONNECTION_STRING.key" url
|
|
"$SemanticAttributes.DB_STATEMENT.key" sanitizedQuery
|
|
"$SemanticAttributes.DB_OPERATION.key" "SELECT"
|
|
"$SemanticAttributes.DB_SQL_TABLE.key" table
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
cleanup:
|
|
statement?.close()
|
|
connection?.close()
|
|
|
|
where:
|
|
prepareStatement | system | driver | jdbcUrl | username | query | sanitizedQuery | spanName | url | table
|
|
true | "h2" | new Driver() | "jdbc:h2:mem:" + dbName | null | "SELECT 3;" | "SELECT ?;" | "SELECT $dbNameLower" | "h2:mem:" | null
|
|
true | "derby" | new EmbeddedDriver() | "jdbc:derby:memory:" + dbName + ";create=true" | "APP" | "SELECT 3 FROM SYSIBM.SYSDUMMY1" | "SELECT ? FROM SYSIBM.SYSDUMMY1" | "SELECT SYSIBM.SYSDUMMY1" | "derby:memory:" | "SYSIBM.SYSDUMMY1"
|
|
false | "h2" | new Driver() | "jdbc:h2:mem:" + dbName | null | "SELECT 3;" | "SELECT ?;" | "SELECT $dbNameLower" | "h2:mem:" | null
|
|
false | "derby" | new EmbeddedDriver() | "jdbc:derby:memory:" + dbName + ";create=true" | "APP" | "SELECT 3 FROM SYSIBM.SYSDUMMY1" | "SELECT ? FROM SYSIBM.SYSDUMMY1" | "SELECT SYSIBM.SYSDUMMY1" | "derby:memory:" | "SYSIBM.SYSDUMMY1"
|
|
}
|
|
|
|
def "calling #datasource.class.simpleName getConnection generates a span when under existing trace"() {
|
|
setup:
|
|
assert datasource instanceof DataSource
|
|
init?.call(datasource)
|
|
|
|
when:
|
|
datasource.getConnection().close()
|
|
|
|
then:
|
|
!traces.any { it.any { it.name == "database.connection" } }
|
|
clearExportedData()
|
|
|
|
when:
|
|
runWithSpan("parent") {
|
|
datasource.getConnection().close()
|
|
}
|
|
|
|
then:
|
|
assertTraces(1) {
|
|
trace(0, recursive ? 3 : 2) {
|
|
span(0) {
|
|
name "parent"
|
|
kind SpanKind.INTERNAL
|
|
hasNoParent()
|
|
}
|
|
|
|
span(1) {
|
|
name "${datasource.class.simpleName}.getConnection"
|
|
kind INTERNAL
|
|
childOf span(0)
|
|
attributes {
|
|
"$SemanticAttributes.CODE_NAMESPACE.key" datasource.class.name
|
|
"$SemanticAttributes.CODE_FUNCTION.key" "getConnection"
|
|
}
|
|
}
|
|
if (recursive) {
|
|
span(2) {
|
|
name "${datasource.class.simpleName}.getConnection"
|
|
kind INTERNAL
|
|
childOf span(1)
|
|
attributes {
|
|
"$SemanticAttributes.CODE_NAMESPACE.key" datasource.class.name
|
|
"$SemanticAttributes.CODE_FUNCTION.key" "getConnection"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
where:
|
|
datasource | init
|
|
new JdbcDataSource() | { ds -> ds.setURL(jdbcUrls.get("h2")) }
|
|
new EmbeddedDataSource() | { ds -> ds.jdbcurl = jdbcUrls.get("derby") }
|
|
cpDatasources.get("hikari").get("h2") | null
|
|
cpDatasources.get("hikari").get("derby") | null
|
|
cpDatasources.get("c3p0").get("h2") | null
|
|
cpDatasources.get("c3p0").get("derby") | null
|
|
|
|
// Tomcat's pool doesn't work because the getConnection method is
|
|
// implemented in a parent class that doesn't implement DataSource
|
|
|
|
recursive = datasource instanceof EmbeddedDataSource
|
|
}
|
|
|
|
def "test getClientInfo exception"() {
|
|
setup:
|
|
Connection connection = new TestConnection(false)
|
|
connection.url = "jdbc:testdb://localhost"
|
|
|
|
when:
|
|
Statement statement = null
|
|
runWithSpan("parent") {
|
|
statement = connection.createStatement()
|
|
return statement.executeQuery(query)
|
|
}
|
|
|
|
then:
|
|
assertTraces(1) {
|
|
trace(0, 2) {
|
|
span(0) {
|
|
name "parent"
|
|
kind SpanKind.INTERNAL
|
|
hasNoParent()
|
|
}
|
|
span(1) {
|
|
name "DB Query"
|
|
kind CLIENT
|
|
childOf span(0)
|
|
attributes {
|
|
"$SemanticAttributes.DB_SYSTEM.key" "testdb"
|
|
"$SemanticAttributes.DB_STATEMENT.key" "testing ?"
|
|
"$SemanticAttributes.DB_CONNECTION_STRING.key" "testdb://localhost"
|
|
"$SemanticAttributes.NET_PEER_NAME.key" "localhost"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
cleanup:
|
|
statement?.close()
|
|
connection?.close()
|
|
|
|
where:
|
|
query = "testing 123"
|
|
}
|
|
|
|
def "should produce proper span name #spanName"() {
|
|
setup:
|
|
def driver = new TestDriver()
|
|
|
|
when:
|
|
def connection = driver.connect(url, null)
|
|
runWithSpan("parent") {
|
|
def statement = connection.createStatement()
|
|
return statement.executeQuery(query)
|
|
}
|
|
|
|
then:
|
|
assertTraces(1) {
|
|
trace(0, 2) {
|
|
span(0) {
|
|
name "parent"
|
|
kind SpanKind.INTERNAL
|
|
hasNoParent()
|
|
}
|
|
span(1) {
|
|
name spanName
|
|
kind CLIENT
|
|
childOf span(0)
|
|
attributes {
|
|
"$SemanticAttributes.DB_SYSTEM.key" "testdb"
|
|
"$SemanticAttributes.DB_NAME.key" databaseName
|
|
"$SemanticAttributes.DB_CONNECTION_STRING.key" "testdb://localhost"
|
|
"$SemanticAttributes.DB_STATEMENT.key" sanitizedQuery
|
|
"$SemanticAttributes.DB_OPERATION.key" operation
|
|
"$SemanticAttributes.DB_SQL_TABLE.key" table
|
|
"$SemanticAttributes.NET_PEER_NAME.key" "localhost"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
where:
|
|
url | query | sanitizedQuery | spanName | databaseName | operation | table
|
|
"jdbc:testdb://localhost?databaseName=test" | "SELECT * FROM table" | "SELECT * FROM table" | "SELECT test.table" | "test" | "SELECT" | "table"
|
|
"jdbc:testdb://localhost?databaseName=test" | "SELECT 42" | "SELECT ?" | "SELECT test" | "test" | "SELECT" | null
|
|
"jdbc:testdb://localhost" | "SELECT * FROM table" | "SELECT * FROM table" | "SELECT table" | null | "SELECT" | "table"
|
|
"jdbc:testdb://localhost?databaseName=test" | "CREATE TABLE table" | "CREATE TABLE table" | "test" | "test" | null | null
|
|
"jdbc:testdb://localhost" | "CREATE TABLE table" | "CREATE TABLE table" | "DB Query" | null | null | null
|
|
}
|
|
|
|
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
|
|
int[] res = new int[numQueries]
|
|
|
|
when:
|
|
for (int i = 0; i < numQueries; ++i) {
|
|
try {
|
|
connection = ds.getConnection()
|
|
def statement = connection.prepareStatement(query)
|
|
def 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(numQueries) {
|
|
for (int i = 0; i < numQueries; ++i) {
|
|
trace(i, 1) {
|
|
span(0) {
|
|
name "SELECT INFORMATION_SCHEMA.SYSTEM_USERS"
|
|
kind CLIENT
|
|
attributes {
|
|
"$SemanticAttributes.DB_SYSTEM.key" "hsqldb"
|
|
"$SemanticAttributes.DB_NAME.key" dbNameLower
|
|
"$SemanticAttributes.DB_USER.key" "SA"
|
|
"$SemanticAttributes.DB_CONNECTION_STRING.key" "hsqldb:mem:"
|
|
"$SemanticAttributes.DB_STATEMENT.key" "SELECT ? FROM INFORMATION_SCHEMA.SYSTEM_USERS"
|
|
"$SemanticAttributes.DB_OPERATION.key" "SELECT"
|
|
"$SemanticAttributes.DB_SQL_TABLE.key" "INFORMATION_SCHEMA.SYSTEM_USERS"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
cleanup:
|
|
if (ds instanceof Closeable) {
|
|
ds.close()
|
|
}
|
|
|
|
where:
|
|
connectionPoolName | _
|
|
"hikari" | _
|
|
"tomcat" | _
|
|
"c3p0" | _
|
|
}
|
|
|
|
// regression test for https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/2644
|
|
def "should handle recursive Statements inside Connection.getMetaData(): #desc"() {
|
|
given:
|
|
def connection = new DbCallingConnection(usePreparedStatementInConnection)
|
|
connection.url = "jdbc:testdb://localhost"
|
|
|
|
when:
|
|
runWithSpan("parent") {
|
|
executeQueryFunction(connection, "SELECT * FROM table")
|
|
}
|
|
|
|
then:
|
|
assertTraces(1) {
|
|
trace(0, 2) {
|
|
span(0) {
|
|
name "parent"
|
|
kind SpanKind.INTERNAL
|
|
hasNoParent()
|
|
}
|
|
span(1) {
|
|
name "SELECT table"
|
|
kind CLIENT
|
|
childOf span(0)
|
|
attributes {
|
|
"$SemanticAttributes.DB_SYSTEM.key" "testdb"
|
|
"$SemanticAttributes.DB_CONNECTION_STRING.key" "testdb://localhost"
|
|
"$SemanticAttributes.DB_STATEMENT.key" "SELECT * FROM table"
|
|
"$SemanticAttributes.DB_OPERATION.key" "SELECT"
|
|
"$SemanticAttributes.DB_SQL_TABLE.key" "table"
|
|
"$SemanticAttributes.NET_PEER_NAME.key" "localhost"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
where:
|
|
desc | usePreparedStatementInConnection | executeQueryFunction
|
|
"getMetaData() uses Statement, test Statement" | false | { con, query -> con.createStatement().executeQuery(query) }
|
|
"getMetaData() uses PreparedStatement, test Statement" | true | { con, query -> con.createStatement().executeQuery(query) }
|
|
"getMetaData() uses Statement, test PreparedStatement" | false | { con, query -> con.prepareStatement(query).executeQuery() }
|
|
"getMetaData() uses PreparedStatement, test PreparedStatement" | true | { con, query -> con.prepareStatement(query).executeQuery() }
|
|
}
|
|
|
|
class DbCallingConnection extends TestConnection {
|
|
final boolean usePreparedStatement
|
|
|
|
DbCallingConnection(boolean usePreparedStatement) {
|
|
super(false)
|
|
this.usePreparedStatement = usePreparedStatement
|
|
}
|
|
|
|
@Override
|
|
DatabaseMetaData getMetaData() throws SQLException {
|
|
// simulate retrieving DB metadata from the DB itself
|
|
if (usePreparedStatement) {
|
|
prepareStatement("SELECT * from DB_METADATA").executeQuery()
|
|
} else {
|
|
createStatement().executeQuery("SELECT * from DB_METADATA")
|
|
}
|
|
return super.getMetaData()
|
|
}
|
|
}
|
|
}
|