From fefd71f8929fba08ed2899deef9b43243ab8e254 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Wed, 22 Oct 2025 16:43:16 -0700 Subject: [PATCH 1/5] Unify jdbc tests --- .../jdbc/javaagent/build.gradle.kts | 7 +- ...urceTest.java => DruidDataSourceTest.java} | 2 +- .../jdbc/test/JdbcInstrumentationTest.java | 1841 +--------------- .../test/PreparedStatementParametersTest.java | 598 +---- .../jdbc/test/SqlCommenterTest.java | 167 +- .../scalaexecutors/SlickTest.scala | 2 +- instrumentation/jdbc/library/build.gradle.kts | 9 + .../OpenTelemetryPreparedStatement.java | 16 +- .../jdbc/internal/OpenTelemetryStatement.java | 18 +- .../jdbc/JdbcInstrumentationTest.java | 140 ++ .../jdbc/LibraryJdbcTestTelemetry.java | 77 + .../jdbc/PreparedStatementParametersTest.java | 32 + .../jdbc/SingleConnectionDataSource.java | 65 + .../jdbc/SqlCommenterTest.java | 37 + .../internal/OpenTelemetryConnectionTest.java | 328 +-- instrumentation/jdbc/testing/build.gradle.kts | 14 + .../instrumentation/jdbc/TestConnection.java | 25 +- .../instrumentation/jdbc/TestDriver.java | 2 +- .../AbstractJdbcInstrumentationTest.java | 1918 +++++++++++++++++ ...stractPreparedStatementParametersTest.java | 616 ++++++ .../testing/AbstractSqlCommenterTest.java | 190 ++ .../jdbc/testing}/DbCallingConnection.java | 8 +- .../jdbc/testing}/ProxyStatementFactory.java | 8 +- .../jdbc/testing}/TestClassLoader.java | 5 +- .../jdbc/testing}/TestInterface.java | 2 +- 25 files changed, 3179 insertions(+), 2948 deletions(-) rename instrumentation/jdbc/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test/{DruicDataSourceTest.java => DruidDataSourceTest.java} (99%) create mode 100644 instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/JdbcInstrumentationTest.java create mode 100644 instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/LibraryJdbcTestTelemetry.java create mode 100644 instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/PreparedStatementParametersTest.java create mode 100644 instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/SingleConnectionDataSource.java create mode 100644 instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/SqlCommenterTest.java create mode 100644 instrumentation/jdbc/testing/src/main/java/io/opentelemetry/instrumentation/jdbc/testing/AbstractJdbcInstrumentationTest.java create mode 100644 instrumentation/jdbc/testing/src/main/java/io/opentelemetry/instrumentation/jdbc/testing/AbstractPreparedStatementParametersTest.java create mode 100644 instrumentation/jdbc/testing/src/main/java/io/opentelemetry/instrumentation/jdbc/testing/AbstractSqlCommenterTest.java rename instrumentation/jdbc/{javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test => testing/src/main/java/io/opentelemetry/instrumentation/jdbc/testing}/DbCallingConnection.java (78%) rename instrumentation/jdbc/{javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test => testing/src/main/java/io/opentelemetry/instrumentation/jdbc/testing}/ProxyStatementFactory.java (92%) rename instrumentation/jdbc/{javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test => testing/src/main/java/io/opentelemetry/instrumentation/jdbc/testing}/TestClassLoader.java (80%) rename instrumentation/jdbc/{javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test => testing/src/main/java/io/opentelemetry/instrumentation/jdbc/testing}/TestInterface.java (87%) diff --git a/instrumentation/jdbc/javaagent/build.gradle.kts b/instrumentation/jdbc/javaagent/build.gradle.kts index 81b457ad2271..ea7e6e5878c3 100644 --- a/instrumentation/jdbc/javaagent/build.gradle.kts +++ b/instrumentation/jdbc/javaagent/build.gradle.kts @@ -20,15 +20,12 @@ dependencies { compileOnly("com.google.auto.value:auto-value-annotations") annotationProcessor("com.google.auto.value:auto-value") - // jdbc unit testing testLibrary("com.h2database:h2:1.3.169") - // first version jdk 1.6 compatible - testLibrary("org.apache.derby:derby:10.6.1.0") + testLibrary("org.apache.derby:derby:10.6.1.0") // first version jdk 1.6 compatible testLibrary("org.hsqldb:hsqldb:2.0.0") testLibrary("org.apache.tomcat:tomcat-jdbc:7.0.19") - // tomcat needs this to run - testLibrary("org.apache.tomcat:tomcat-juli:7.0.19") + testLibrary("org.apache.tomcat:tomcat-juli:7.0.19") // tomcat jdbc needs this testLibrary("com.zaxxer:HikariCP:2.4.0") testLibrary("com.mchange:c3p0:0.9.5") testLibrary("com.alibaba:druid:1.2.20") diff --git a/instrumentation/jdbc/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test/DruicDataSourceTest.java b/instrumentation/jdbc/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test/DruidDataSourceTest.java similarity index 99% rename from instrumentation/jdbc/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test/DruicDataSourceTest.java rename to instrumentation/jdbc/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test/DruidDataSourceTest.java index de506db95fb2..8d4fbd3e662e 100644 --- a/instrumentation/jdbc/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test/DruicDataSourceTest.java +++ b/instrumentation/jdbc/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test/DruidDataSourceTest.java @@ -29,7 +29,7 @@ import org.junit.jupiter.api.extension.RegisterExtension; @SuppressWarnings("deprecation") // using deprecated semconv -class DruicDataSourceTest { +class DruidDataSourceTest { @RegisterExtension static final InstrumentationExtension testing = AgentInstrumentationExtension.create(); diff --git a/instrumentation/jdbc/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test/JdbcInstrumentationTest.java b/instrumentation/jdbc/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test/JdbcInstrumentationTest.java index e369d0854bca..076622216dbf 100644 --- a/instrumentation/jdbc/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test/JdbcInstrumentationTest.java +++ b/instrumentation/jdbc/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test/JdbcInstrumentationTest.java @@ -5,1849 +5,18 @@ package io.opentelemetry.javaagent.instrumentation.jdbc.test; -import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableDatabaseSemconv; -import static io.opentelemetry.instrumentation.testing.junit.code.SemconvCodeStabilityUtil.codeFunctionAssertions; -import static io.opentelemetry.instrumentation.testing.junit.db.DbClientMetricsTestUtil.assertDurationMetric; -import static io.opentelemetry.instrumentation.testing.junit.db.SemconvStabilityUtil.maybeStable; -import static io.opentelemetry.instrumentation.testing.junit.db.SemconvStabilityUtil.maybeStableDbSystemName; -import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; -import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; -import static io.opentelemetry.semconv.DbAttributes.DB_COLLECTION_NAME; -import static io.opentelemetry.semconv.DbAttributes.DB_NAMESPACE; -import static io.opentelemetry.semconv.DbAttributes.DB_OPERATION_BATCH_SIZE; -import static io.opentelemetry.semconv.DbAttributes.DB_OPERATION_NAME; -import static io.opentelemetry.semconv.DbAttributes.DB_SYSTEM_NAME; -import static io.opentelemetry.semconv.ServerAttributes.SERVER_ADDRESS; -import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_CONNECTION_STRING; -import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_NAME; -import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_OPERATION; -import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_SQL_TABLE; -import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_STATEMENT; -import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_SYSTEM; -import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_USER; -import static java.util.Arrays.asList; -import static org.junit.jupiter.api.Assumptions.assumeTrue; - -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Maps; -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.testing.internal.AutoCleanupExtension; +import io.opentelemetry.instrumentation.jdbc.testing.AbstractJdbcInstrumentationTest; import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; -import io.opentelemetry.sdk.testing.assertj.AttributeAssertion; -import io.opentelemetry.sdk.testing.assertj.SpanDataAssert; -import io.opentelemetry.sdk.testing.assertj.TraceAssert; -import java.beans.PropertyVetoException; -import java.io.Closeable; -import java.io.IOException; -import java.sql.CallableStatement; -import java.sql.Connection; -import java.sql.Driver; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Statement; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Properties; -import java.util.function.Consumer; -import java.util.stream.Stream; -import javax.sql.DataSource; -import org.apache.derby.jdbc.EmbeddedDataSource; -import org.apache.derby.jdbc.EmbeddedDriver; -import org.assertj.core.api.ThrowingConsumer; -import org.h2.jdbcx.JdbcDataSource; -import org.hsqldb.jdbc.JDBCDriver; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; -import org.junit.jupiter.params.provider.ValueSource; - -@SuppressWarnings("deprecation") // using deprecated semconv -class JdbcInstrumentationTest { - @RegisterExtension static final AutoCleanupExtension cleanup = AutoCleanupExtension.create(); +class JdbcInstrumentationTest extends AbstractJdbcInstrumentationTest { @RegisterExtension static final InstrumentationExtension testing = AgentInstrumentationExtension.create(); - private static final String dbName = "jdbcUnitTest"; - private static final String dbNameLower = dbName.toLowerCase(Locale.ROOT); - private static final Map jdbcUrls = - ImmutableMap.of( - "h2", "jdbc:h2:mem:" + dbName, - "derby", "jdbc:derby:memory:" + dbName, - "hsqldb", "jdbc:hsqldb:mem:" + dbName); - private static final Map jdbcDriverClassNames = - ImmutableMap.of( - "h2", "org.h2.Driver", - "derby", "org.apache.derby.jdbc.EmbeddedDriver", - "hsqldb", "org.hsqldb.jdbc.JDBCDriver"); - private static final Map jdbcUserNames = Maps.newHashMap(); - private static final Properties connectionProps = new Properties(); - // JDBC Connection pool name (i.e. HikariCP) -> Map - private static final Map> cpDatasources = Maps.newHashMap(); - - static { - jdbcUserNames.put("derby", "APP"); - jdbcUserNames.put("h2", null); - jdbcUserNames.put("hsqldb", "SA"); - - connectionProps.put("databaseName", "someDb"); - connectionProps.put("OPEN_NEW", "true"); // So H2 doesn't complain about username/password. - } - - @BeforeAll - static void setUp() { - prepareConnectionPoolDatasources(); - } - - @AfterAll - static void tearDown() { - cpDatasources - .values() - .forEach( - k -> - k.values() - .forEach( - dataSource -> { - if (dataSource instanceof Closeable) { - try { - ((Closeable) dataSource).close(); - } catch (IOException ignore) { - // ignore - } - } - })); - } - - static void prepareConnectionPoolDatasources() { - List connectionPoolNames = asList("tomcat", "hikari", "c3p0"); - connectionPoolNames.forEach( - cpName -> { - Map dbDsMapping = new HashMap<>(); - jdbcUrls.forEach( - (dbType, jdbcUrl) -> dbDsMapping.put(dbType, createDs(cpName, dbType, jdbcUrl))); - cpDatasources.put(cpName, dbDsMapping); - }); - } - - static DataSource createTomcatDs(String dbType, String jdbcUrl) { - org.apache.tomcat.jdbc.pool.DataSource ds = new org.apache.tomcat.jdbc.pool.DataSource(); - String jdbcUrlToSet = dbType.equals("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; - } - - static DataSource createHikariDs(String dbType, String jdbcUrl) { - HikariConfig config = new HikariConfig(); - String jdbcUrlToSet = dbType.equals("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); - } - - static DataSource createC3P0Ds(String dbType, String jdbcUrl) { - ComboPooledDataSource ds = new ComboPooledDataSource(); - try { - ds.setDriverClass(jdbcDriverClassNames.get(dbType)); - } catch (PropertyVetoException e) { - throw new RuntimeException(e); - } - String jdbcUrlToSet = dbType.equals("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; - } - - static DataSource createDs(String connectionPoolName, String dbType, String jdbcUrl) { - DataSource ds = null; - if (connectionPoolName.equals("tomcat")) { - ds = createTomcatDs(dbType, jdbcUrl); - } - if (connectionPoolName.equals("hikari")) { - ds = createHikariDs(dbType, jdbcUrl); - } - if (connectionPoolName.equals("c3p0")) { - ds = createC3P0Ds(dbType, jdbcUrl); - } - return ds; - } - - static Stream basicStatementStream() throws SQLException { - return Stream.of( - Arguments.of( - "h2", - new org.h2.Driver().connect(jdbcUrls.get("h2"), null), - null, - "SELECT 3", - "SELECT ?", - "SELECT " + dbNameLower, - "h2:mem:", - null), - Arguments.of( - "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"), - Arguments.of( - "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"), - Arguments.of( - "h2", - new org.h2.Driver().connect(jdbcUrls.get("h2"), connectionProps), - null, - "SELECT 3", - "SELECT ?", - "SELECT " + dbNameLower, - "h2:mem:", - null), - Arguments.of( - "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"), - Arguments.of( - "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"), - Arguments.of( - "h2", - cpDatasources.get("tomcat").get("h2").getConnection(), - null, - "SELECT 3", - "SELECT ?", - "SELECT " + dbNameLower, - "h2:mem:", - null), - Arguments.of( - "derby", - cpDatasources.get("tomcat").get("derby").getConnection(), - "APP", - "SELECT 3 FROM SYSIBM.SYSDUMMY1", - "SELECT ? FROM SYSIBM.SYSDUMMY1", - "SELECT SYSIBM.SYSDUMMY1", - "derby:memory:", - "SYSIBM.SYSDUMMY1"), - Arguments.of( - "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"), - Arguments.of( - "h2", - cpDatasources.get("hikari").get("h2").getConnection(), - null, - "SELECT 3", - "SELECT ?", - "SELECT " + dbNameLower, - "h2:mem:", - null), - Arguments.of( - "derby", - cpDatasources.get("hikari").get("derby").getConnection(), - "APP", - "SELECT 3 FROM SYSIBM.SYSDUMMY1", - "SELECT ? FROM SYSIBM.SYSDUMMY1", - "SELECT SYSIBM.SYSDUMMY1", - "derby:memory:", - "SYSIBM.SYSDUMMY1"), - Arguments.of( - "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"), - Arguments.of( - "h2", - cpDatasources.get("c3p0").get("h2").getConnection(), - null, - "SELECT 3", - "SELECT ?", - "SELECT " + dbNameLower, - "h2:mem:", - null), - Arguments.of( - "derby", - cpDatasources.get("c3p0").get("derby").getConnection(), - "APP", - "SELECT 3 FROM SYSIBM.SYSDUMMY1", - "SELECT ? FROM SYSIBM.SYSDUMMY1", - "SELECT SYSIBM.SYSDUMMY1", - "derby:memory:", - "SYSIBM.SYSDUMMY1"), - Arguments.of( - "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")); - } - - @ParameterizedTest - @MethodSource("basicStatementStream") - public void testBasicStatement( - String system, - Connection connection, - String username, - String query, - String sanitizedQuery, - String spanName, - String url, - String table) - throws SQLException { - Statement statement = connection.createStatement(); - cleanup.deferCleanup(statement); - ResultSet resultSet = testing.runWithSpan("parent", () -> statement.executeQuery(query)); - - resultSet.next(); - assertThat(resultSet.getInt(1)).isEqualTo(3); - - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), - span -> - span.hasName(spanName) - .hasKind(SpanKind.CLIENT) - .hasParent(trace.getSpan(0)) - .hasAttributesSatisfyingExactly( - equalTo(maybeStable(DB_SYSTEM), maybeStableDbSystemName(system)), - equalTo(maybeStable(DB_NAME), dbNameLower), - equalTo(DB_USER, emitStableDatabaseSemconv() ? null : username), - equalTo(DB_CONNECTION_STRING, emitStableDatabaseSemconv() ? null : url), - equalTo(maybeStable(DB_STATEMENT), sanitizedQuery), - equalTo(maybeStable(DB_OPERATION), "SELECT"), - equalTo(maybeStable(DB_SQL_TABLE), table)))); - - if (table != null) { - assertDurationMetric( - testing, - "io.opentelemetry.jdbc", - DB_SYSTEM_NAME, - DB_COLLECTION_NAME, - DB_NAMESPACE, - DB_OPERATION_NAME); - } else { - assertDurationMetric( - testing, "io.opentelemetry.jdbc", DB_SYSTEM_NAME, DB_OPERATION_NAME, DB_NAMESPACE); - } - } - - static Stream preparedStatementStream() throws SQLException { - return Stream.of( - Arguments.of( - "h2", - new org.h2.Driver().connect(jdbcUrls.get("h2"), null), - null, - "SELECT 3", - "SELECT ?", - "SELECT " + dbNameLower, - "h2:mem:", - null), - Arguments.of( - "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"), - Arguments.of( - "h2", - cpDatasources.get("tomcat").get("h2").getConnection(), - null, - "SELECT 3", - "SELECT ?", - "SELECT " + dbNameLower, - "h2:mem:", - null), - Arguments.of( - "derby", - cpDatasources.get("tomcat").get("derby").getConnection(), - "APP", - "SELECT 3 FROM SYSIBM.SYSDUMMY1", - "SELECT ? FROM SYSIBM.SYSDUMMY1", - "SELECT SYSIBM.SYSDUMMY1", - "derby:memory:", - "SYSIBM.SYSDUMMY1"), - Arguments.of( - "h2", - cpDatasources.get("hikari").get("h2").getConnection(), - null, - "SELECT 3", - "SELECT ?", - "SELECT " + dbNameLower, - "h2:mem:", - null), - Arguments.of( - "derby", - cpDatasources.get("hikari").get("derby").getConnection(), - "APP", - "SELECT 3 FROM SYSIBM.SYSDUMMY1", - "SELECT ? FROM SYSIBM.SYSDUMMY1", - "SELECT SYSIBM.SYSDUMMY1", - "derby:memory:", - "SYSIBM.SYSDUMMY1"), - Arguments.of( - "h2", - cpDatasources.get("c3p0").get("h2").getConnection(), - null, - "SELECT 3", - "SELECT ?", - "SELECT " + dbNameLower, - "h2:mem:", - null), - Arguments.of( - "derby", - cpDatasources.get("c3p0").get("derby").getConnection(), - "APP", - "SELECT 3 FROM SYSIBM.SYSDUMMY1", - "SELECT ? FROM SYSIBM.SYSDUMMY1", - "SELECT SYSIBM.SYSDUMMY1", - "derby:memory:", - "SYSIBM.SYSDUMMY1")); - } - - @ParameterizedTest - @MethodSource("preparedStatementStream") - void testPreparedStatementExecute( - String system, - Connection connection, - String username, - String query, - String sanitizedQuery, - String spanName, - String url, - String table) - throws SQLException { - PreparedStatement statement = connection.prepareStatement(query); - cleanup.deferCleanup(statement); - ResultSet resultSet = - testing.runWithSpan( - "parent", - () -> { - statement.execute(); - return statement.getResultSet(); - }); - - resultSet.next(); - assertThat(resultSet.getInt(1)).isEqualTo(3); - - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), - span -> - span.hasName(spanName) - .hasKind(SpanKind.CLIENT) - .hasParent(trace.getSpan(0)) - .hasAttributesSatisfyingExactly( - equalTo(maybeStable(DB_SYSTEM), maybeStableDbSystemName(system)), - equalTo(maybeStable(DB_NAME), dbNameLower), - equalTo(DB_USER, emitStableDatabaseSemconv() ? null : username), - equalTo(DB_CONNECTION_STRING, emitStableDatabaseSemconv() ? null : url), - equalTo(maybeStable(DB_STATEMENT), sanitizedQuery), - equalTo(maybeStable(DB_OPERATION), "SELECT"), - equalTo(maybeStable(DB_SQL_TABLE), table)))); - } - - @ParameterizedTest - @MethodSource("preparedStatementStream") - void testPreparedStatementQuery( - String system, - Connection connection, - String username, - String query, - String sanitizedQuery, - String spanName, - String url, - String table) - throws SQLException { - PreparedStatement statement = connection.prepareStatement(query); - cleanup.deferCleanup(statement); - ResultSet resultSet = testing.runWithSpan("parent", () -> statement.executeQuery()); - - resultSet.next(); - assertThat(resultSet.getInt(1)).isEqualTo(3); - - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), - span -> - span.hasName(spanName) - .hasKind(SpanKind.CLIENT) - .hasParent(trace.getSpan(0)) - .hasAttributesSatisfyingExactly( - equalTo(maybeStable(DB_SYSTEM), maybeStableDbSystemName(system)), - equalTo(maybeStable(DB_NAME), dbNameLower), - equalTo(DB_USER, emitStableDatabaseSemconv() ? null : username), - equalTo(DB_CONNECTION_STRING, emitStableDatabaseSemconv() ? null : url), - equalTo(maybeStable(DB_STATEMENT), sanitizedQuery), - equalTo(maybeStable(DB_OPERATION), "SELECT"), - equalTo(maybeStable(DB_SQL_TABLE), table)))); - } - - @ParameterizedTest - @MethodSource("preparedStatementStream") - void testPreparedCall( - String system, - Connection connection, - String username, - String query, - String sanitizedQuery, - String spanName, - String url, - String table) - throws SQLException { - CallableStatement statement = connection.prepareCall(query); - cleanup.deferCleanup(statement); - ResultSet resultSet = testing.runWithSpan("parent", () -> statement.executeQuery()); - - resultSet.next(); - assertThat(resultSet.getInt(1)).isEqualTo(3); - - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), - span -> - span.hasName(spanName) - .hasKind(SpanKind.CLIENT) - .hasParent(trace.getSpan(0)) - .hasAttributesSatisfyingExactly( - equalTo(maybeStable(DB_SYSTEM), maybeStableDbSystemName(system)), - equalTo(maybeStable(DB_NAME), dbNameLower), - equalTo(DB_USER, emitStableDatabaseSemconv() ? null : username), - equalTo(DB_CONNECTION_STRING, emitStableDatabaseSemconv() ? null : url), - equalTo(maybeStable(DB_STATEMENT), sanitizedQuery), - equalTo(maybeStable(DB_OPERATION), "SELECT"), - equalTo(maybeStable(DB_SQL_TABLE), table)))); - } - - static Stream statementUpdateStream() throws SQLException { - return Stream.of( - Arguments.of( - "h2", - new org.h2.Driver().connect(jdbcUrls.get("h2"), null), - null, - "CREATE TABLE S_H2 (id INTEGER not NULL, PRIMARY KEY ( id ))", - "CREATE TABLE jdbcunittest.S_H2", - "h2:mem:", - "S_H2"), - Arguments.of( - "derby", - new EmbeddedDriver().connect(jdbcUrls.get("derby"), null), - "APP", - "CREATE TABLE S_DERBY (id INTEGER not NULL, PRIMARY KEY ( id ))", - "CREATE TABLE jdbcunittest.S_DERBY", - "derby:memory:", - "S_DERBY"), - Arguments.of( - "hsqldb", - new JDBCDriver().connect(jdbcUrls.get("hsqldb"), null), - "SA", - "CREATE TABLE PUBLIC.S_HSQLDB (id INTEGER not NULL, PRIMARY KEY ( id ))", - "CREATE TABLE PUBLIC.S_HSQLDB", - "hsqldb:mem:", - "PUBLIC.S_HSQLDB"), - Arguments.of( - "h2", - cpDatasources.get("tomcat").get("h2").getConnection(), - null, - "CREATE TABLE S_H2_TOMCAT (id INTEGER not NULL, PRIMARY KEY ( id ))", - "CREATE TABLE jdbcunittest.S_H2_TOMCAT", - "h2:mem:", - "S_H2_TOMCAT"), - Arguments.of( - "derby", - cpDatasources.get("tomcat").get("derby").getConnection(), - "APP", - "CREATE TABLE S_DERBY_TOMCAT (id INTEGER not NULL, PRIMARY KEY ( id ))", - "CREATE TABLE jdbcunittest.S_DERBY_TOMCAT", - "derby:memory:", - "S_DERBY_TOMCAT"), - Arguments.of( - "hsqldb", - cpDatasources.get("tomcat").get("hsqldb").getConnection(), - "SA", - "CREATE TABLE PUBLIC.S_HSQLDB_TOMCAT (id INTEGER not NULL, PRIMARY KEY ( id ))", - "CREATE TABLE PUBLIC.S_HSQLDB_TOMCAT", - "hsqldb:mem:", - "PUBLIC.S_HSQLDB_TOMCAT"), - Arguments.of( - "h2", - cpDatasources.get("hikari").get("h2").getConnection(), - null, - "CREATE TABLE S_H2_HIKARI (id INTEGER not NULL, PRIMARY KEY ( id ))", - "CREATE TABLE jdbcunittest.S_H2_HIKARI", - "h2:mem:", - "S_H2_HIKARI"), - Arguments.of( - "derby", - cpDatasources.get("hikari").get("derby").getConnection(), - "APP", - "CREATE TABLE S_DERBY_HIKARI (id INTEGER not NULL, PRIMARY KEY ( id ))", - "CREATE TABLE jdbcunittest.S_DERBY_HIKARI", - "derby:memory:", - "S_DERBY_HIKARI"), - Arguments.of( - "hsqldb", - cpDatasources.get("hikari").get("hsqldb").getConnection(), - "SA", - "CREATE TABLE PUBLIC.S_HSQLDB_HIKARI (id INTEGER not NULL, PRIMARY KEY ( id ))", - "CREATE TABLE PUBLIC.S_HSQLDB_HIKARI", - "hsqldb:mem:", - "PUBLIC.S_HSQLDB_HIKARI"), - Arguments.of( - "h2", - cpDatasources.get("c3p0").get("h2").getConnection(), - null, - "CREATE TABLE S_H2_C3P0 (id INTEGER not NULL, PRIMARY KEY ( id ))", - "CREATE TABLE jdbcunittest.S_H2_C3P0", - "h2:mem:", - "S_H2_C3P0"), - Arguments.of( - "derby", - cpDatasources.get("c3p0").get("derby").getConnection(), - "APP", - "CREATE TABLE S_DERBY_C3P0 (id INTEGER not NULL, PRIMARY KEY ( id ))", - "CREATE TABLE jdbcunittest.S_DERBY_C3P0", - "derby:memory:", - "S_DERBY_C3P0"), - Arguments.of( - "hsqldb", - cpDatasources.get("c3p0").get("hsqldb").getConnection(), - "SA", - "CREATE TABLE PUBLIC.S_HSQLDB_C3P0 (id INTEGER not NULL, PRIMARY KEY ( id ))", - "CREATE TABLE PUBLIC.S_HSQLDB_C3P0", - "hsqldb:mem:", - "PUBLIC.S_HSQLDB_C3P0")); - } - - @ParameterizedTest - @MethodSource("statementUpdateStream") - void testStatementUpdate( - String system, - Connection connection, - String username, - String query, - String spanName, - String url, - String table) - throws SQLException { - Statement statement = connection.createStatement(); - cleanup.deferCleanup(statement); - String sql = connection.nativeSQL(query); - testing.runWithSpan("parent", () -> assertThat(statement.execute(sql)).isFalse()); - - assertThat(statement.getUpdateCount()).isEqualTo(0); - - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), - span -> - span.hasName(spanName) - .hasKind(SpanKind.CLIENT) - .hasParent(trace.getSpan(0)) - .hasAttributesSatisfyingExactly( - equalTo(maybeStable(DB_SYSTEM), maybeStableDbSystemName(system)), - equalTo(maybeStable(DB_NAME), dbNameLower), - equalTo(DB_USER, emitStableDatabaseSemconv() ? null : username), - equalTo(DB_CONNECTION_STRING, emitStableDatabaseSemconv() ? null : url), - equalTo(maybeStable(DB_STATEMENT), query), - equalTo(maybeStable(DB_OPERATION), "CREATE TABLE"), - equalTo(maybeStable(DB_SQL_TABLE), table)))); - } - - static Stream preparedStatementUpdateStream() throws SQLException { - return Stream.of( - Arguments.of( - "h2", - new org.h2.Driver().connect(jdbcUrls.get("h2"), null), - null, - "CREATE TABLE PS_H2 (id INTEGER not NULL, PRIMARY KEY ( id ))", - "CREATE TABLE jdbcunittest.PS_H2", - "h2:mem:", - "PS_H2"), - Arguments.of( - "derby", - new EmbeddedDriver().connect(jdbcUrls.get("derby"), null), - "APP", - "CREATE TABLE PS_DERBY (id INTEGER not NULL, PRIMARY KEY ( id ))", - "CREATE TABLE jdbcunittest.PS_DERBY", - "derby:memory:", - "PS_DERBY"), - Arguments.of( - "h2", - cpDatasources.get("tomcat").get("h2").getConnection(), - null, - "CREATE TABLE PS_H2_TOMCAT (id INTEGER not NULL, PRIMARY KEY ( id ))", - "CREATE TABLE jdbcunittest.PS_H2_TOMCAT", - "h2:mem:", - "PS_H2_TOMCAT"), - Arguments.of( - "derby", - cpDatasources.get("tomcat").get("derby").getConnection(), - "APP", - "CREATE TABLE PS_DERBY_TOMCAT (id INTEGER not NULL, PRIMARY KEY ( id ))", - "CREATE TABLE jdbcunittest.PS_DERBY_TOMCAT", - "derby:memory:", - "PS_DERBY_TOMCAT"), - Arguments.of( - "h2", - cpDatasources.get("hikari").get("h2").getConnection(), - null, - "CREATE TABLE PS_H2_HIKARI (id INTEGER not NULL, PRIMARY KEY ( id ))", - "CREATE TABLE jdbcunittest.PS_H2_HIKARI", - "h2:mem:", - "PS_H2_HIKARI"), - Arguments.of( - "derby", - cpDatasources.get("hikari").get("derby").getConnection(), - "APP", - "CREATE TABLE PS_DERBY_HIKARI (id INTEGER not NULL, PRIMARY KEY ( id ))", - "CREATE TABLE jdbcunittest.PS_DERBY_HIKARI", - "derby:memory:", - "PS_DERBY_HIKARI"), - Arguments.of( - "h2", - cpDatasources.get("c3p0").get("h2").getConnection(), - null, - "CREATE TABLE PS_H2_C3P0 (id INTEGER not NULL, PRIMARY KEY ( id ))", - "CREATE TABLE jdbcunittest.PS_H2_C3P0", - "h2:mem:", - "PS_H2_C3P0"), - Arguments.of( - "derby", - cpDatasources.get("c3p0").get("derby").getConnection(), - "APP", - "CREATE TABLE PS_DERBY_C3P0 (id INTEGER not NULL, PRIMARY KEY ( id ))", - "CREATE TABLE jdbcunittest.PS_DERBY_C3P0", - "derby:memory:", - "PS_DERBY_C3P0")); - } - - @ParameterizedTest - @MethodSource("preparedStatementUpdateStream") - void testPreparedStatementUpdate( - String system, - Connection connection, - String username, - String query, - String spanName, - String url, - String table) - throws SQLException { - testPreparedStatementUpdateImpl( - system, - connection, - username, - query, - spanName, - url, - table, - statement -> assertThat(statement.executeUpdate()).isEqualTo(0)); - } - - static Stream preparedStatementLargeUpdateStream() throws SQLException { - return Stream.of( - Arguments.of( - "h2", - new org.h2.Driver().connect(jdbcUrls.get("h2"), null), - null, - "CREATE TABLE PS_LARGE_H2 (id INTEGER not NULL, PRIMARY KEY ( id ))", - "CREATE TABLE jdbcunittest.PS_LARGE_H2", - "h2:mem:", - "PS_LARGE_H2"), - Arguments.of( - "h2", - cpDatasources.get("tomcat").get("h2").getConnection(), - null, - "CREATE TABLE PS_LARGE_H2_TOMCAT (id INTEGER not NULL, PRIMARY KEY ( id ))", - "CREATE TABLE jdbcunittest.PS_LARGE_H2_TOMCAT", - "h2:mem:", - "PS_LARGE_H2_TOMCAT"), - Arguments.of( - "h2", - cpDatasources.get("hikari").get("h2").getConnection(), - null, - "CREATE TABLE PS_LARGE_H2_HIKARI (id INTEGER not NULL, PRIMARY KEY ( id ))", - "CREATE TABLE jdbcunittest.PS_LARGE_H2_HIKARI", - "h2:mem:", - "PS_LARGE_H2_HIKARI")); - } - - @ParameterizedTest - @MethodSource("preparedStatementLargeUpdateStream") - void testPreparedStatementLargeUpdate( - String system, - Connection connection, - String username, - String query, - String spanName, - String url, - String table) - throws SQLException { - testPreparedStatementUpdateImpl( - system, - connection, - username, - query, - spanName, - url, - table, - statement -> assertThat(statement.executeLargeUpdate()).isEqualTo(0)); - } - - void testPreparedStatementUpdateImpl( - String system, - Connection connection, - String username, - String query, - String spanName, - String url, - String table, - ThrowingConsumer action) - throws SQLException { - String sql = connection.nativeSQL(query); - PreparedStatement statement = connection.prepareStatement(sql); - cleanup.deferCleanup(statement); - testing.runWithSpan("parent", () -> action.accept(statement)); - - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), - span -> - span.hasName(spanName) - .hasKind(SpanKind.CLIENT) - .hasParent(trace.getSpan(0)) - .hasAttributesSatisfyingExactly( - equalTo(maybeStable(DB_SYSTEM), maybeStableDbSystemName(system)), - equalTo(maybeStable(DB_NAME), dbNameLower), - equalTo(DB_USER, emitStableDatabaseSemconv() ? null : username), - equalTo(DB_CONNECTION_STRING, emitStableDatabaseSemconv() ? null : url), - equalTo(maybeStable(DB_STATEMENT), query), - equalTo(maybeStable(DB_OPERATION), "CREATE TABLE"), - equalTo(maybeStable(DB_SQL_TABLE), table)))); - } - - static Stream connectionConstructorStream() { - return Stream.of( - Arguments.of( - true, - "h2", - new org.h2.Driver(), - "jdbc:h2:mem:" + dbName, - null, - "SELECT 3;", - "SELECT ?;", - "SELECT " + dbNameLower, - "h2:mem:", - null), - Arguments.of( - 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"), - Arguments.of( - false, - "h2", - new org.h2.Driver(), - "jdbc:h2:mem:" + dbName, - null, - "SELECT 3;", - "SELECT ?;", - "SELECT " + dbNameLower, - "h2:mem:", - null), - Arguments.of( - 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")); - } - - @SuppressWarnings("CatchingUnchecked") - @ParameterizedTest - @MethodSource("connectionConstructorStream") - void testConnectionConstructorThrowing( - boolean prepareStatement, - String system, - Driver driver, - String jdbcUrl, - String username, - String query, - String sanitizedQuery, - String spanName, - String url, - String table) - throws SQLException { - Connection connection = null; - - try { - connection = new TestConnection(true); - } catch (Exception ignored) { - connection = driver.connect(jdbcUrl, null); - } - cleanup.deferCleanup(connection); - Connection finalConnection = connection; - ResultSet rs = - testing.runWithSpan( - "parent", - () -> { - if (prepareStatement) { - PreparedStatement stmt = finalConnection.prepareStatement(query); - cleanup.deferCleanup(stmt); - return stmt.executeQuery(); - } else { - Statement stmt = finalConnection.createStatement(); - cleanup.deferCleanup(stmt); - return stmt.executeQuery(query); - } - }); - - rs.next(); - assertThat(rs.getInt(1)).isEqualTo(3); - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), - span -> - span.hasName(spanName) - .hasKind(SpanKind.CLIENT) - .hasParent(trace.getSpan(0)) - .hasAttributesSatisfyingExactly( - equalTo(maybeStable(DB_SYSTEM), maybeStableDbSystemName(system)), - equalTo(maybeStable(DB_NAME), dbNameLower), - equalTo(DB_USER, emitStableDatabaseSemconv() ? null : username), - equalTo(DB_CONNECTION_STRING, emitStableDatabaseSemconv() ? null : url), - equalTo(maybeStable(DB_STATEMENT), sanitizedQuery), - equalTo(maybeStable(DB_OPERATION), "SELECT"), - equalTo(maybeStable(DB_SQL_TABLE), table)))); - } - - static Stream getConnectionStream() { - return Stream.of( - Arguments.of( - new JdbcDataSource(), - (Consumer) ds -> ((JdbcDataSource) ds).setUrl(jdbcUrls.get("h2")), - "h2", - null, - "h2:mem:"), - Arguments.of( - new EmbeddedDataSource(), - (Consumer) - ds -> ((EmbeddedDataSource) ds).setDatabaseName("memory:" + dbName), - "derby", - "APP", - "derby:memory:"), - Arguments.of(cpDatasources.get("hikari").get("h2"), null, "h2", null, "h2:mem:"), - Arguments.of( - cpDatasources.get("hikari").get("derby"), null, "derby", "APP", "derby:memory:"), - Arguments.of(cpDatasources.get("c3p0").get("h2"), null, "h2", null, "h2:mem:"), - Arguments.of( - cpDatasources.get("c3p0").get("derby"), null, "derby", "APP", "derby:memory:")); - } - - @ParameterizedTest(autoCloseArguments = false) - @MethodSource("getConnectionStream") - void testGetConnection( - DataSource datasource, - Consumer init, - String system, - String user, - String connectionString) - throws SQLException { - // Tomcat's pool doesn't work because the getConnection method is - // implemented in a parent class that doesn't implement DataSource - - if (init != null) { - init.accept(datasource); - } - datasource.getConnection().close(); - assertThat(testing.spans()).noneMatch(span -> span.getName().equals("database.connection")); - - testing.clearData(); - - List attributesAssertions = - codeFunctionAssertions(datasource.getClass(), "getConnection"); - attributesAssertions.add(equalTo(maybeStable(DB_SYSTEM), maybeStableDbSystemName(system))); - attributesAssertions.add(equalTo(maybeStable(DB_SYSTEM), maybeStableDbSystemName(system))); - attributesAssertions.add(equalTo(DB_USER, emitStableDatabaseSemconv() ? null : user)); - attributesAssertions.add(equalTo(maybeStable(DB_NAME), "jdbcunittest")); - attributesAssertions.add( - equalTo(DB_CONNECTION_STRING, emitStableDatabaseSemconv() ? null : connectionString)); - - testing.runWithSpan("parent", () -> datasource.getConnection().close()); - testing.waitAndAssertTraces( - trace -> { - List> assertions = - new ArrayList<>( - asList( - span1 -> span1.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), - span1 -> - span1 - .hasName(datasource.getClass().getSimpleName() + ".getConnection") - .hasKind(SpanKind.INTERNAL) - .hasParent(trace.getSpan(0)) - .hasAttributesSatisfyingExactly(attributesAssertions))); - trace.hasSpansSatisfyingExactly(assertions); - }); - } - - @ParameterizedTest - @DisplayName("test getClientInfo exception") - @ValueSource(strings = "testing 123") - void testGetClientInfoException(String query) throws SQLException { - TestConnection connection = new TestConnection(false); - cleanup.deferCleanup(connection); - connection.setUrl("jdbc:testdb://localhost"); - - Statement statement = - testing.runWithSpan( - "parent", - () -> { - Statement stmt = connection.createStatement(); - stmt.executeQuery(query); - return stmt; - }); - cleanup.deferCleanup(statement); - - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), - span -> - span.hasName("DB Query") - .hasKind(SpanKind.CLIENT) - .hasParent(trace.getSpan(0)) - .hasAttributesSatisfyingExactly( - equalTo(maybeStable(DB_SYSTEM), "other_sql"), - equalTo(maybeStable(DB_STATEMENT), "testing ?"), - equalTo( - DB_CONNECTION_STRING, - emitStableDatabaseSemconv() ? null : "testdb://localhost"), - equalTo(SERVER_ADDRESS, "localhost")))); - } - - static Stream spanNameStream() { - return Stream.of( - Arguments.of( - "jdbc:testdb://localhost?databaseName=test", - "SELECT * FROM table", - "SELECT * FROM table", - "SELECT test.table", - "test", - "SELECT", - "table"), - Arguments.of( - "jdbc:testdb://localhost?databaseName=test", - "SELECT 42", - "SELECT ?", - "SELECT test", - "test", - "SELECT", - null), - Arguments.of( - "jdbc:testdb://localhost", - "SELECT * FROM table", - "SELECT * FROM table", - "SELECT table", - null, - "SELECT", - "table"), - Arguments.of( - "jdbc:testdb://localhost?databaseName=test", - "CREATE TABLE table", - "CREATE TABLE table", - "CREATE TABLE test.table", - "test", - "CREATE TABLE", - "table"), - Arguments.of( - "jdbc:testdb://localhost", - "CREATE TABLE table", - "CREATE TABLE table", - "CREATE TABLE table", - null, - "CREATE TABLE", - "table")); - } - - @ParameterizedTest - @MethodSource("spanNameStream") - void testProduceProperSpanName( - String url, - String query, - String sanitizedQuery, - String spanName, - String databaseName, - String operation, - String table) - throws SQLException { - Driver driver = new TestDriver(); - Connection connection = driver.connect(url, null); - cleanup.deferCleanup(connection); - - testing.runWithSpan( - "parent", - () -> { - Statement statement = connection.createStatement(); - statement.executeQuery(query); - }); - - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), - span -> - span.hasName(spanName) - .hasKind(SpanKind.CLIENT) - .hasParent(trace.getSpan(0)) - .hasAttributesSatisfyingExactly( - equalTo(maybeStable(DB_SYSTEM), "other_sql"), - equalTo(maybeStable(DB_NAME), databaseName), - equalTo( - DB_CONNECTION_STRING, - emitStableDatabaseSemconv() ? null : "testdb://localhost"), - equalTo(maybeStable(DB_STATEMENT), sanitizedQuery), - equalTo(maybeStable(DB_OPERATION), operation), - equalTo(maybeStable(DB_SQL_TABLE), table), - equalTo(SERVER_ADDRESS, "localhost")))); - } - - @ParameterizedTest - @ValueSource(strings = {"hikari", "tomcat", "c3p0"}) - void testConnectionCached(String connectionPoolName) throws SQLException { - String dbType = "hsqldb"; - DataSource ds = createDs(connectionPoolName, dbType, jdbcUrls.get(dbType)); - cleanup.deferCleanup( - () -> { - if (ds instanceof Closeable) { - ((Closeable) ds).close(); - } - }); - String query = "SELECT 3 FROM INFORMATION_SCHEMA.SYSTEM_USERS"; - int numQueries = 5; - int[] res = new int[numQueries]; - - for (int i = 0; i < numQueries; ++i) { - try (Connection connection = ds.getConnection(); - PreparedStatement statement = connection.prepareStatement(query)) { - ResultSet rs = statement.executeQuery(); - if (rs.next()) { - res[i] = rs.getInt(1); - } else { - res[i] = 0; - } - } - } - - for (int i = 0; i < numQueries; ++i) { - assertThat(res[i]).isEqualTo(3); - } - - List> assertions = new ArrayList<>(); - Consumer traceAssertConsumer = - trace -> - trace.hasSpansSatisfyingExactly( - span -> - span.hasName("SELECT INFORMATION_SCHEMA.SYSTEM_USERS") - .hasKind(SpanKind.CLIENT) - .hasAttributesSatisfyingExactly( - equalTo(maybeStable(DB_SYSTEM), "hsqldb"), - equalTo(maybeStable(DB_NAME), dbNameLower), - equalTo(DB_USER, emitStableDatabaseSemconv() ? null : "SA"), - equalTo( - DB_CONNECTION_STRING, - emitStableDatabaseSemconv() ? null : "hsqldb:mem:"), - equalTo( - maybeStable(DB_STATEMENT), - "SELECT ? FROM INFORMATION_SCHEMA.SYSTEM_USERS"), - equalTo(maybeStable(DB_OPERATION), "SELECT"), - equalTo(maybeStable(DB_SQL_TABLE), "INFORMATION_SCHEMA.SYSTEM_USERS"))); - for (int i = 0; i < numQueries; i++) { - assertions.add(traceAssertConsumer); - } - - testing.waitAndAssertTraces(assertions); - } - - @FunctionalInterface - public interface ThrowingBiConsumer { - void accept(T t, U u) throws Exception; - } - - static Stream recursiveStatementsStream() { - return Stream.of( - Arguments.of( - "getMetaData() uses Statement, test Statement", - false, - (ThrowingBiConsumer) - (con, query) -> con.createStatement().executeQuery(query)), - Arguments.of( - "getMetaData() uses PreparedStatement, test Statement", - true, - (ThrowingBiConsumer) - (con, query) -> con.createStatement().executeQuery(query)), - Arguments.of( - "getMetaData() uses Statement, test PreparedStatement", - false, - (ThrowingBiConsumer) - (con, query) -> con.prepareStatement(query).executeQuery()), - Arguments.of( - "getMetaData() uses PreparedStatement, test PreparedStatement", - true, - (ThrowingBiConsumer) - (con, query) -> con.prepareStatement(query).executeQuery())); - } - - // regression test for - // https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/2644 - @ParameterizedTest - @MethodSource("recursiveStatementsStream") - void testHandleRecursiveStatements( - String desc, - boolean usePreparedStatementInConnection, - ThrowingBiConsumer executeQueryFunction) - throws Exception { - DbCallingConnection connection = new DbCallingConnection(usePreparedStatementInConnection); - connection.setUrl("jdbc:testdb://localhost"); - - testing.runWithSpan( - "parent", - () -> { - executeQueryFunction.accept(connection, "SELECT * FROM table"); - }); - - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), - span -> - span.hasName("SELECT table") - .hasKind(SpanKind.CLIENT) - .hasParent(trace.getSpan(0)) - .hasAttributesSatisfyingExactly( - equalTo(maybeStable(DB_SYSTEM), "other_sql"), - equalTo( - DB_CONNECTION_STRING, - emitStableDatabaseSemconv() ? null : "testdb://localhost"), - equalTo(maybeStable(DB_STATEMENT), "SELECT * FROM table"), - equalTo(maybeStable(DB_OPERATION), "SELECT"), - equalTo(maybeStable(DB_SQL_TABLE), "table"), - equalTo(SERVER_ADDRESS, "localhost")))); - } - - // regression test for - // https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/6015 - @DisplayName("test proxy statement") - @Test - void testProxyStatement() throws Exception { - Connection connection = new org.h2.Driver().connect(jdbcUrls.get("h2"), null); - Statement statement = connection.createStatement(); - cleanup.deferCleanup(statement); - cleanup.deferCleanup(connection); - - Statement proxyStatement = ProxyStatementFactory.proxyStatementWithCustomClassLoader(statement); - ResultSet resultSet = - testing.runWithSpan("parent", () -> proxyStatement.executeQuery("SELECT 3")); - - resultSet.next(); - assertThat(resultSet.getInt(1)).isEqualTo(3); - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), - span -> - span.hasName("SELECT " + dbNameLower) - .hasKind(SpanKind.CLIENT) - .hasParent(trace.getSpan(0)))); - } - - // regression test for - // https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/9359 - @DisplayName("test proxy prepared statement") - @Test - void testProxyPreparedStatement() throws SQLException { - Connection connection = new org.h2.Driver().connect(jdbcUrls.get("h2"), null); - PreparedStatement statement = connection.prepareStatement("SELECT 3"); - cleanup.deferCleanup(statement); - cleanup.deferCleanup(connection); - - PreparedStatement proxyStatement = ProxyStatementFactory.proxyPreparedStatement(statement); - ResultSet resultSet = testing.runWithSpan("parent", () -> proxyStatement.executeQuery()); - - resultSet.next(); - assertThat(resultSet.getInt(1)).isEqualTo(3); - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), - span -> - span.hasName("SELECT " + dbNameLower) - .hasKind(SpanKind.CLIENT) - .hasParent(trace.getSpan(0)))); - } - - static Stream batchStream() throws SQLException { - return Stream.of( - Arguments.of("h2", new org.h2.Driver().connect(jdbcUrls.get("h2"), null), null, "h2:mem:"), - Arguments.of( - "derby", - new EmbeddedDriver().connect(jdbcUrls.get("derby"), null), - "APP", - "derby:memory:"), - Arguments.of( - "hsqldb", new JDBCDriver().connect(jdbcUrls.get("hsqldb"), null), "SA", "hsqldb:mem:")); - } - - @ParameterizedTest - @MethodSource("batchStream") - void testBatch(String system, Connection connection, String username, String url) - throws SQLException { - testBatchImpl( - system, - connection, - username, - url, - "simple_batch_test", - statement -> assertThat(statement.executeBatch()).isEqualTo(new int[] {1, 1})); - } - - @ParameterizedTest - @MethodSource("batchStream") - void testLargeBatch(String system, Connection connection, String username, String url) - throws SQLException { - // derby and hsqldb used in this test don't support executeLargeBatch - assumeTrue("h2".equals(system)); - - testBatchImpl( - system, - connection, - username, - url, - "simple_batch_test_large", - statement -> assertThat(statement.executeLargeBatch()).isEqualTo(new long[] {1, 1})); - } - - private static void testBatchImpl( - String system, - Connection connection, - String username, - String url, - String tableName, - ThrowingConsumer action) - throws SQLException { - Statement createTable = connection.createStatement(); - createTable.execute("CREATE TABLE " + tableName + " (id INTEGER not NULL, PRIMARY KEY ( id ))"); - cleanup.deferCleanup(createTable); - - testing.waitForTraces(1); - testing.clearData(); - - Statement statement = connection.createStatement(); - cleanup.deferCleanup(statement); - statement.addBatch("INSERT INTO non_existent_table VALUES(1)"); - statement.clearBatch(); - statement.addBatch("INSERT INTO " + tableName + " VALUES(1)"); - statement.addBatch("INSERT INTO " + tableName + " VALUES(2)"); - testing.runWithSpan("parent", () -> action.accept(statement)); - - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), - span -> - span.hasName( - emitStableDatabaseSemconv() - ? "BATCH INSERT jdbcunittest." + tableName - : "jdbcunittest") - .hasKind(SpanKind.CLIENT) - .hasParent(trace.getSpan(0)) - .hasAttributesSatisfyingExactly( - equalTo(maybeStable(DB_SYSTEM), maybeStableDbSystemName(system)), - equalTo(maybeStable(DB_NAME), dbNameLower), - equalTo(DB_USER, emitStableDatabaseSemconv() ? null : username), - equalTo(DB_CONNECTION_STRING, emitStableDatabaseSemconv() ? null : url), - equalTo( - maybeStable(DB_STATEMENT), - emitStableDatabaseSemconv() - ? "INSERT INTO " + tableName + " VALUES(?)" - : null), - equalTo( - maybeStable(DB_OPERATION), - emitStableDatabaseSemconv() ? "BATCH INSERT" : null), - equalTo( - maybeStable(DB_SQL_TABLE), - emitStableDatabaseSemconv() ? tableName : null), - equalTo( - DB_OPERATION_BATCH_SIZE, - emitStableDatabaseSemconv() ? 2L : null)))); - } - - @ParameterizedTest - @MethodSource("batchStream") - void testMultiBatch(String system, Connection connection, String username, String url) - throws SQLException { - String tableName1 = "multi_batch_test_1"; - String tableName2 = "multi_batch_test_2"; - Statement createTable1 = connection.createStatement(); - createTable1.execute( - "CREATE TABLE " + tableName1 + " (id INTEGER not NULL, PRIMARY KEY ( id ))"); - cleanup.deferCleanup(createTable1); - Statement createTable2 = connection.createStatement(); - createTable2.execute( - "CREATE TABLE " + tableName2 + " (id INTEGER not NULL, PRIMARY KEY ( id ))"); - cleanup.deferCleanup(createTable1); - - testing.waitForTraces(2); - testing.clearData(); - - Statement statement = connection.createStatement(); - cleanup.deferCleanup(statement); - statement.addBatch("INSERT INTO " + tableName1 + " VALUES(1)"); - statement.addBatch("INSERT INTO " + tableName2 + " VALUES(2)"); - testing.runWithSpan( - "parent", () -> assertThat(statement.executeBatch()).isEqualTo(new int[] {1, 1})); - - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), - span -> - span.hasName( - emitStableDatabaseSemconv() - ? "BATCH INSERT jdbcunittest" - : "jdbcunittest") - .hasKind(SpanKind.CLIENT) - .hasParent(trace.getSpan(0)) - .hasAttributesSatisfyingExactly( - equalTo(maybeStable(DB_SYSTEM), maybeStableDbSystemName(system)), - equalTo(maybeStable(DB_NAME), dbNameLower), - equalTo(DB_USER, emitStableDatabaseSemconv() ? null : username), - equalTo(DB_CONNECTION_STRING, emitStableDatabaseSemconv() ? null : url), - equalTo( - maybeStable(DB_STATEMENT), - emitStableDatabaseSemconv() - ? "INSERT INTO " - + tableName1 - + " VALUES(?); INSERT INTO multi_batch_test_2 VALUES(?)" - : null), - equalTo( - maybeStable(DB_OPERATION), - emitStableDatabaseSemconv() ? "BATCH INSERT" : null), - equalTo( - DB_OPERATION_BATCH_SIZE, - emitStableDatabaseSemconv() ? 2L : null)))); - } - - @ParameterizedTest - @MethodSource("batchStream") - void testSingleItemBatch(String system, Connection connection, String username, String url) - throws SQLException { - String tableName = "single_item_batch_test"; - Statement createTable = connection.createStatement(); - createTable.execute("CREATE TABLE " + tableName + " (id INTEGER not NULL, PRIMARY KEY ( id ))"); - cleanup.deferCleanup(createTable); - - testing.waitForTraces(1); - testing.clearData(); - - Statement statement = connection.createStatement(); - cleanup.deferCleanup(statement); - statement.addBatch("INSERT INTO " + tableName + " VALUES(1)"); - testing.runWithSpan( - "parent", () -> assertThat(statement.executeBatch()).isEqualTo(new int[] {1})); - - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), - span -> - span.hasName("INSERT jdbcunittest." + tableName) - .hasKind(SpanKind.CLIENT) - .hasParent(trace.getSpan(0)) - .hasAttributesSatisfyingExactly( - equalTo(maybeStable(DB_SYSTEM), maybeStableDbSystemName(system)), - equalTo(maybeStable(DB_NAME), dbNameLower), - equalTo(DB_USER, emitStableDatabaseSemconv() ? null : username), - equalTo(DB_CONNECTION_STRING, emitStableDatabaseSemconv() ? null : url), - equalTo( - maybeStable(DB_STATEMENT), - "INSERT INTO " + tableName + " VALUES(?)"), - equalTo(maybeStable(DB_OPERATION), "INSERT"), - equalTo(maybeStable(DB_SQL_TABLE), tableName)))); - } - - @ParameterizedTest - @MethodSource("batchStream") - void testPreparedBatch(String system, Connection connection, String username, String url) - throws SQLException { - String tableName = "prepared_batch_test"; - Statement createTable = connection.createStatement(); - createTable.execute("CREATE TABLE " + tableName + " (id INTEGER not NULL, PRIMARY KEY ( id ))"); - cleanup.deferCleanup(createTable); - - testing.waitForTraces(1); - testing.clearData(); - - PreparedStatement statement = - connection.prepareStatement("INSERT INTO " + tableName + " VALUES(?)"); - cleanup.deferCleanup(statement); - statement.setInt(1, 1); - statement.addBatch(); - statement.clearBatch(); - statement.setInt(1, 1); - statement.addBatch(); - statement.setInt(1, 2); - statement.addBatch(); - testing.runWithSpan( - "parent", () -> assertThat(statement.executeBatch()).isEqualTo(new int[] {1, 1})); - - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), - span -> - span.hasName( - emitStableDatabaseSemconv() - ? "BATCH INSERT jdbcunittest." + tableName - : "INSERT jdbcunittest." + tableName) - .hasKind(SpanKind.CLIENT) - .hasParent(trace.getSpan(0)) - .hasAttributesSatisfyingExactly( - equalTo(maybeStable(DB_SYSTEM), maybeStableDbSystemName(system)), - equalTo(maybeStable(DB_NAME), dbNameLower), - equalTo(DB_USER, emitStableDatabaseSemconv() ? null : username), - equalTo(DB_CONNECTION_STRING, emitStableDatabaseSemconv() ? null : url), - equalTo( - maybeStable(DB_STATEMENT), - "INSERT INTO " + tableName + " VALUES(?)"), - equalTo( - maybeStable(DB_OPERATION), - emitStableDatabaseSemconv() ? "BATCH INSERT" : "INSERT"), - equalTo(maybeStable(DB_SQL_TABLE), tableName), - equalTo( - DB_OPERATION_BATCH_SIZE, - emitStableDatabaseSemconv() ? 2L : null)))); - } - - // test that sqlcommenter is not enabled by default - @Test - void testSqlCommenterNotEnabled() throws SQLException { - List executedSql = new ArrayList<>(); - Connection connection = new TestConnection(executedSql::add); - Statement statement = connection.createStatement(); - - cleanup.deferCleanup(statement); - cleanup.deferCleanup(connection); - - String query = "SELECT 1"; - testing.runWithSpan("parent", () -> statement.execute(query)); - - assertThat(executedSql).hasSize(1); - assertThat(executedSql.get(0)).isEqualTo(query); - } - - @ParameterizedTest - @MethodSource("transactionOperationsStream") - void testCommitTransaction(String system, Connection connection, String username, String url) - throws SQLException { - - String tableName = "TXN_COMMIT_TEST_" + system.toUpperCase(Locale.ROOT); - Statement createTable = connection.createStatement(); - createTable.execute("CREATE TABLE " + tableName + " (id INTEGER not NULL, PRIMARY KEY ( id ))"); - cleanup.deferCleanup(createTable); - - connection.setAutoCommit(false); - - testing.waitForTraces(1); - testing.clearData(); - - Statement insertStatement = connection.createStatement(); - cleanup.deferCleanup(insertStatement); - - testing.runWithSpan( - "parent", - () -> { - insertStatement.executeUpdate("INSERT INTO " + tableName + " VALUES(1)"); - connection.commit(); - }); - - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), - span -> - span.hasName("INSERT jdbcunittest." + tableName) - .hasKind(SpanKind.CLIENT) - .hasParent(trace.getSpan(0)) - .hasAttributesSatisfyingExactly( - equalTo(maybeStable(DB_SYSTEM), maybeStableDbSystemName(system)), - equalTo(maybeStable(DB_NAME), dbNameLower), - equalTo(DB_USER, emitStableDatabaseSemconv() ? null : username), - equalTo(DB_CONNECTION_STRING, emitStableDatabaseSemconv() ? null : url), - equalTo( - maybeStable(DB_STATEMENT), - "INSERT INTO " + tableName + " VALUES(?)"), - equalTo(maybeStable(DB_OPERATION), "INSERT"), - equalTo(maybeStable(DB_SQL_TABLE), tableName)), - span -> - span.hasName("COMMIT") - .hasKind(SpanKind.CLIENT) - .hasParent(trace.getSpan(0)) - .hasAttributesSatisfyingExactly( - equalTo(maybeStable(DB_SYSTEM), maybeStableDbSystemName(system)), - equalTo(maybeStable(DB_NAME), dbNameLower), - equalTo(maybeStable(DB_OPERATION), "COMMIT"), - equalTo(DB_USER, emitStableDatabaseSemconv() ? null : username), - equalTo( - DB_CONNECTION_STRING, emitStableDatabaseSemconv() ? null : url)))); - } - - @ParameterizedTest - @MethodSource("transactionOperationsStream") - void testRollbackTransaction(String system, Connection connection, String username, String url) - throws SQLException { - - String tableName = "TXN_ROLLBACK_TEST_" + system.toUpperCase(Locale.ROOT); - Statement createTable = connection.createStatement(); - createTable.execute("CREATE TABLE " + tableName + " (id INTEGER not NULL, PRIMARY KEY ( id ))"); - cleanup.deferCleanup(createTable); - - connection.setAutoCommit(false); - - testing.waitForTraces(1); - testing.clearData(); - - Statement insertStatement = connection.createStatement(); - cleanup.deferCleanup(insertStatement); - - testing.runWithSpan( - "parent", - () -> { - insertStatement.executeUpdate("INSERT INTO " + tableName + " VALUES(1)"); - connection.rollback(); - }); - - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), - span -> - span.hasName("INSERT jdbcunittest." + tableName) - .hasKind(SpanKind.CLIENT) - .hasParent(trace.getSpan(0)) - .hasAttributesSatisfyingExactly( - equalTo(maybeStable(DB_SYSTEM), maybeStableDbSystemName(system)), - equalTo(maybeStable(DB_NAME), dbNameLower), - equalTo(DB_USER, emitStableDatabaseSemconv() ? null : username), - equalTo(DB_CONNECTION_STRING, emitStableDatabaseSemconv() ? null : url), - equalTo( - maybeStable(DB_STATEMENT), - "INSERT INTO " + tableName + " VALUES(?)"), - equalTo(maybeStable(DB_OPERATION), "INSERT"), - equalTo(maybeStable(DB_SQL_TABLE), tableName)), - span -> - span.hasName("ROLLBACK") - .hasKind(SpanKind.CLIENT) - .hasParent(trace.getSpan(0)) - .hasAttributesSatisfyingExactly( - equalTo(maybeStable(DB_SYSTEM), maybeStableDbSystemName(system)), - equalTo(maybeStable(DB_NAME), dbNameLower), - equalTo(maybeStable(DB_OPERATION), "ROLLBACK"), - equalTo(DB_USER, emitStableDatabaseSemconv() ? null : username), - equalTo( - DB_CONNECTION_STRING, emitStableDatabaseSemconv() ? null : url)))); - } - - static Stream transactionOperationsStream() throws SQLException { - return Stream.of( - Arguments.of("h2", new org.h2.Driver().connect(jdbcUrls.get("h2"), null), null, "h2:mem:"), - Arguments.of( - "derby", - new EmbeddedDriver().connect(jdbcUrls.get("derby"), null), - "APP", - "derby:memory:"), - Arguments.of( - "hsqldb", new JDBCDriver().connect(jdbcUrls.get("hsqldb"), null), "SA", "hsqldb:mem:")); - } - - private static PreparedStatement wrapPreparedStatement(PreparedStatement statement) { - return ProxyStatementFactory.proxyPreparedStatement( - (proxy, method, args) -> { - if ("isWrapperFor".equals(method.getName()) - && args.length == 1 - && args[0] == PreparedStatement.class) { - return true; - } - if ("unwrap".equals(method.getName()) - && args.length == 1 - && args[0] == PreparedStatement.class) { - return statement; - } - return testing.runWithSpan("wrapper", () -> method.invoke(statement, args)); - }); - } - - // test that tracing does not start from a wrapper - // regression test for - // https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/14733 - @Test - void testPreparedStatementWrapper() throws SQLException { - Connection connection = new org.h2.Driver().connect(jdbcUrls.get("h2"), null); - Connection proxyConnection = - ProxyStatementFactory.proxy( - Connection.class, - (proxy, method, args) -> { - // we don't implement unwrapping here as that would also cause the executeQuery - // instrumentation to get skipped for the prepared statement wrapper - if ("prepareStatement".equals(method.getName())) { - return wrapPreparedStatement((PreparedStatement) method.invoke(connection, args)); - } - return method.invoke(connection, args); - }); - PreparedStatement statement = proxyConnection.prepareStatement("SELECT 3"); - cleanup.deferCleanup(statement); - cleanup.deferCleanup(connection); - - ResultSet resultSet = testing.runWithSpan("parent", () -> statement.executeQuery()); - - resultSet.next(); - assertThat(resultSet.getInt(1)).isEqualTo(3); - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), - span -> - span.hasName("wrapper").hasKind(SpanKind.INTERNAL).hasParent(trace.getSpan(0)), - span -> - span.hasName("SELECT " + dbNameLower) - .hasKind(SpanKind.CLIENT) - .hasParent(trace.getSpan(1)))); - } - - // test that tracing does not start from a wrapper - // regression test for - // https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/14733 - @Test - void testStatementWrapper() throws SQLException { - Connection connection = new org.h2.Driver().connect(jdbcUrls.get("h2"), null); - Statement statement = connection.createStatement(); - cleanup.deferCleanup(statement); - cleanup.deferCleanup(connection); - - Statement proxyStatement = - ProxyStatementFactory.proxyStatement( - (proxy, method, args) -> { - if ("isWrapperFor".equals(method.getName()) - && args.length == 1 - && args[0] == Statement.class) { - return true; - } - if ("unwrap".equals(method.getName()) - && args.length == 1 - && args[0] == Statement.class) { - return statement; - } - return testing.runWithSpan("wrapper", () -> method.invoke(statement, args)); - }); - ResultSet resultSet = - testing.runWithSpan("parent", () -> proxyStatement.executeQuery("SELECT 3")); - - resultSet.next(); - assertThat(resultSet.getInt(1)).isEqualTo(3); - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), - span -> - span.hasName("wrapper").hasKind(SpanKind.INTERNAL).hasParent(trace.getSpan(0)), - span -> - span.hasName("SELECT " + dbNameLower) - .hasKind(SpanKind.CLIENT) - .hasParent(trace.getSpan(1)))); + @Override + protected InstrumentationExtension testing() { + return testing; } } diff --git a/instrumentation/jdbc/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test/PreparedStatementParametersTest.java b/instrumentation/jdbc/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test/PreparedStatementParametersTest.java index 6af380167147..04825f27c0a2 100644 --- a/instrumentation/jdbc/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test/PreparedStatementParametersTest.java +++ b/instrumentation/jdbc/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test/PreparedStatementParametersTest.java @@ -5,606 +5,18 @@ package io.opentelemetry.javaagent.instrumentation.jdbc.test; -import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableDatabaseSemconv; -import static io.opentelemetry.instrumentation.testing.junit.db.SemconvStabilityUtil.maybeStable; -import static io.opentelemetry.instrumentation.testing.junit.db.SemconvStabilityUtil.maybeStableDbSystemName; -import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; -import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; -import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_CONNECTION_STRING; -import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_NAME; -import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_OPERATION; -import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_QUERY_PARAMETER; -import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_SQL_TABLE; -import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_STATEMENT; -import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_SYSTEM; -import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_USER; - -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Maps; -import io.opentelemetry.api.trace.SpanKind; -import io.opentelemetry.instrumentation.testing.internal.AutoCleanupExtension; +import io.opentelemetry.instrumentation.jdbc.testing.AbstractPreparedStatementParametersTest; import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; -import java.math.BigDecimal; -import java.sql.Connection; -import java.sql.Date; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Time; -import java.sql.Timestamp; -import java.sql.Types; -import java.util.Calendar; -import java.util.Locale; -import java.util.Map; -import java.util.Properties; -import java.util.stream.Stream; -import org.apache.derby.jdbc.EmbeddedDriver; -import org.hsqldb.jdbc.JDBCDriver; -import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.extension.RegisterExtension; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; - -@SuppressWarnings("deprecation") // using deprecated semconv -class PreparedStatementParametersTest { - @RegisterExtension static final AutoCleanupExtension cleanup = AutoCleanupExtension.create(); +class PreparedStatementParametersTest extends AbstractPreparedStatementParametersTest { @RegisterExtension static final InstrumentationExtension testing = AgentInstrumentationExtension.create(); - private static final String dbName = "jdbcUnitTest"; - private static final String dbNameLower = dbName.toLowerCase(Locale.ROOT); - - private static final Map jdbcUrls = - ImmutableMap.of( - "h2", "jdbc:h2:mem:" + dbName, - "derby", "jdbc:derby:memory:" + dbName, - "hsqldb", "jdbc:hsqldb:mem:" + dbName); - private static final Map jdbcUserNames = Maps.newHashMap(); - private static final Properties connectionProps = new Properties(); - - static { - jdbcUserNames.put("derby", "APP"); - jdbcUserNames.put("h2", null); - jdbcUserNames.put("hsqldb", "SA"); - - connectionProps.put("databaseName", "someDb"); - connectionProps.put("OPEN_NEW", "true"); // So H2 doesn't complain about username/password. - connectionProps.put("create", "true"); - } - - static Stream preparedStatementStream() throws SQLException { - return Stream.of( - Arguments.of( - "h2", - new org.h2.Driver().connect(jdbcUrls.get("h2"), null), - null, - "SELECT 3, ?", - "SELECT 3, ?", - "SELECT " + dbNameLower, - "h2:mem:", - null), - Arguments.of( - "derby", - new EmbeddedDriver().connect(jdbcUrls.get("derby"), connectionProps), - "APP", - "SELECT 3 FROM SYSIBM.SYSDUMMY1 WHERE IBMREQD=? OR 1=1", - "SELECT 3 FROM SYSIBM.SYSDUMMY1 WHERE IBMREQD=? OR 1=1", - "SELECT SYSIBM.SYSDUMMY1", - "derby:memory:", - "SYSIBM.SYSDUMMY1"), - Arguments.of( - "hsqldb", - new JDBCDriver().connect(jdbcUrls.get("hsqldb"), null), - "SA", - "SELECT 3 FROM INFORMATION_SCHEMA.SYSTEM_USERS WHERE USER_NAME=? OR 1=1", - "SELECT 3 FROM INFORMATION_SCHEMA.SYSTEM_USERS WHERE USER_NAME=? OR 1=1", - "SELECT INFORMATION_SCHEMA.SYSTEM_USERS", - "hsqldb:mem:", - "INFORMATION_SCHEMA.SYSTEM_USERS")); - } - - @ParameterizedTest - @MethodSource("preparedStatementStream") - void testBooleanPreparedStatementParameter( - String system, - Connection connection, - String username, - String query, - String sanitizedQuery, - String spanName, - String url, - String table) - throws SQLException { - test( - system, - connection, - username, - query, - sanitizedQuery, - spanName, - url, - table, - statement -> statement.setBoolean(1, true), - "true"); - } - - @ParameterizedTest - @MethodSource("preparedStatementStream") - void testShortPreparedStatementParameter( - String system, - Connection connection, - String username, - String query, - String sanitizedQuery, - String spanName, - String url, - String table) - throws SQLException { - test( - system, - connection, - username, - query, - sanitizedQuery, - spanName, - url, - table, - statement -> statement.setShort(1, (short) 0), - "0"); - } - - @ParameterizedTest - @MethodSource("preparedStatementStream") - void testIntPreparedStatementParameter( - String system, - Connection connection, - String username, - String query, - String sanitizedQuery, - String spanName, - String url, - String table) - throws SQLException { - test( - system, - connection, - username, - query, - sanitizedQuery, - spanName, - url, - table, - statement -> statement.setInt(1, 0), - "0"); - } - - @ParameterizedTest - @MethodSource("preparedStatementStream") - void testLongPreparedStatementParameter( - String system, - Connection connection, - String username, - String query, - String sanitizedQuery, - String spanName, - String url, - String table) - throws SQLException { - test( - system, - connection, - username, - query, - sanitizedQuery, - spanName, - url, - table, - statement -> statement.setLong(1, 0), - "0"); - } - - @ParameterizedTest - @MethodSource("preparedStatementStream") - void testFloatPreparedStatementParameter( - String system, - Connection connection, - String username, - String query, - String sanitizedQuery, - String spanName, - String url, - String table) - throws SQLException { - test( - system, - connection, - username, - query, - sanitizedQuery, - spanName, - url, - table, - statement -> statement.setFloat(1, 0.1f), - "0.1"); - } - - @ParameterizedTest - @MethodSource("preparedStatementStream") - void testDoublePreparedStatementParameter( - String system, - Connection connection, - String username, - String query, - String sanitizedQuery, - String spanName, - String url, - String table) - throws SQLException { - test( - system, - connection, - username, - query, - sanitizedQuery, - spanName, - url, - table, - statement -> statement.setDouble(1, 0.1), - "0.1"); - } - - @ParameterizedTest - @MethodSource("preparedStatementStream") - void testBigDecimalPreparedStatementParameter( - String system, - Connection connection, - String username, - String query, - String sanitizedQuery, - String spanName, - String url, - String table) - throws SQLException { - test( - system, - connection, - username, - query, - sanitizedQuery, - spanName, - url, - table, - statement -> statement.setBigDecimal(1, BigDecimal.ZERO), - "0"); - } - - @ParameterizedTest - @MethodSource("preparedStatementStream") - void testStringPreparedStatementParameter( - String system, - Connection connection, - String username, - String query, - String sanitizedQuery, - String spanName, - String url, - String table) - throws SQLException { - test( - system, - connection, - username, - query, - sanitizedQuery, - spanName, - url, - table, - statement -> statement.setString(1, "S"), - "S"); - } - - @ParameterizedTest - @MethodSource("preparedStatementStream") - void testObjectPreparedStatementParameter( - String system, - Connection connection, - String username, - String query, - String sanitizedQuery, - String spanName, - String url, - String table) - throws SQLException { - test( - system, - connection, - username, - query, - sanitizedQuery, - spanName, - url, - table, - statement -> statement.setObject(1, "S"), - "S"); - } - - @ParameterizedTest - @MethodSource("preparedStatementStream") - void testObjectWithTypePreparedStatementParameter( - String system, - Connection connection, - String username, - String query, - String sanitizedQuery, - String spanName, - String url, - String table) - throws SQLException { - // we are using old database drivers that don't support the tested setObject method - Assumptions.assumeTrue(Boolean.getBoolean("testLatestDeps")); - - test( - system, - connection, - username, - query, - sanitizedQuery, - spanName, - url, - table, - statement -> statement.setObject(1, "S", Types.CHAR), - "S"); - } - - @ParameterizedTest - @MethodSource("preparedStatementStream") - void testDate2PreparedStatementParameter( - String system, - Connection connection, - String username, - String query, - String sanitizedQuery, - String spanName, - String url, - String table) - throws SQLException { - String updatedQuery = query.replace("USER_NAME=?", "CURDATE()=?"); - String updatedQuerySanitized = sanitizedQuery.replace("USER_NAME=?", "CURDATE()=?"); - - test( - system, - connection, - username, - updatedQuery, - updatedQuerySanitized, - spanName, - url, - table, - statement -> statement.setDate(1, Date.valueOf("2000-01-01")), - "2000-01-01"); - } - - @ParameterizedTest - @MethodSource("preparedStatementStream") - void testDate3PreparedStatementParameter( - String system, - Connection connection, - String username, - String query, - String sanitizedQuery, - String spanName, - String url, - String table) - throws SQLException { - String updatedQuery = query.replace("USER_NAME=?", "CURDATE()=?"); - String updatedQuerySanitized = sanitizedQuery.replace("USER_NAME=?", "CURDATE()=?"); - - test( - system, - connection, - username, - updatedQuery, - updatedQuerySanitized, - spanName, - url, - table, - statement -> statement.setDate(1, Date.valueOf("2000-01-01"), Calendar.getInstance()), - "2000-01-01"); - } - - @ParameterizedTest - @MethodSource("preparedStatementStream") - void testTime2PreparedStatementParameter( - String system, - Connection connection, - String username, - String query, - String sanitizedQuery, - String spanName, - String url, - String table) - throws SQLException { - String updatedQuery = query.replace("USER_NAME=?", "CURTIME()=?"); - String updatedQuerySanitized = sanitizedQuery.replace("USER_NAME=?", "CURTIME()=?"); - - test( - system, - connection, - username, - updatedQuery, - updatedQuerySanitized, - spanName, - url, - table, - statement -> statement.setTime(1, Time.valueOf("00:00:00")), - "00:00:00"); - } - - @ParameterizedTest - @MethodSource("preparedStatementStream") - void testTime3PreparedStatementParameter( - String system, - Connection connection, - String username, - String query, - String sanitizedQuery, - String spanName, - String url, - String table) - throws SQLException { - String updatedQuery = query.replace("USER_NAME=?", "CURTIME()=?"); - String updatedQuerySanitized = sanitizedQuery.replace("USER_NAME=?", "CURTIME()=?"); - - test( - system, - connection, - username, - updatedQuery, - updatedQuerySanitized, - spanName, - url, - table, - statement -> statement.setTime(1, Time.valueOf("00:00:00"), Calendar.getInstance()), - "00:00:00"); - } - - @ParameterizedTest - @MethodSource("preparedStatementStream") - void testTimestamp2PreparedStatementParameter( - String system, - Connection connection, - String username, - String query, - String sanitizedQuery, - String spanName, - String url, - String table) - throws SQLException { - String updatedQuery = query.replace("USER_NAME=?", "NOW()=?"); - String updatedQuerySanitized = sanitizedQuery.replace("USER_NAME=?", "NOW()=?"); - - test( - system, - connection, - username, - updatedQuery, - updatedQuerySanitized, - spanName, - url, - table, - statement -> statement.setTimestamp(1, Timestamp.valueOf("2000-01-01 00:00:00")), - "2000-01-01 00:00:00.0"); - } - - @ParameterizedTest - @MethodSource("preparedStatementStream") - void testTimestamp3PreparedStatementParameter( - String system, - Connection connection, - String username, - String query, - String sanitizedQuery, - String spanName, - String url, - String table) - throws SQLException { - String updatedQuery = query.replace("USER_NAME=?", "NOW()=?"); - String updatedQuerySanitized = sanitizedQuery.replace("USER_NAME=?", "NOW()=?"); - - test( - system, - connection, - username, - updatedQuery, - updatedQuerySanitized, - spanName, - url, - table, - statement -> - statement.setTimestamp( - 1, Timestamp.valueOf("2000-01-01 00:00:00"), Calendar.getInstance()), - "2000-01-01 00:00:00.0"); - } - - @ParameterizedTest - @MethodSource("preparedStatementStream") - void testNstringPreparedStatementParameter( - String system, - Connection connection, - String username, - String query, - String sanitizedQuery, - String spanName, - String url, - String table) - throws SQLException { - Assumptions.assumeFalse(system.equalsIgnoreCase("derby")); - - test( - system, - connection, - username, - query, - sanitizedQuery, - spanName, - url, - table, - statement -> statement.setNString(1, "S"), - "S"); - } - - private static void test( - String system, - Connection connection, - String username, - String query, - String sanitizedQuery, - String spanName, - String url, - String table, - ThrowingConsumer setParameter, - String expectedParameterValue) - throws SQLException { - PreparedStatement statement = connection.prepareStatement(query); - cleanup.deferCleanup(statement); - - ResultSet resultSet = - testing.runWithSpan( - "parent", - () -> { - setParameter.accept(statement); - statement.execute(); - return statement.getResultSet(); - }); - - resultSet.next(); - assertThat(resultSet.getInt(1)).isEqualTo(3); - - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), - span -> - span.hasName(spanName) - .hasKind(SpanKind.CLIENT) - .hasParent(trace.getSpan(0)) - .hasAttributesSatisfyingExactly( - equalTo(maybeStable(DB_SYSTEM), maybeStableDbSystemName(system)), - equalTo(maybeStable(DB_NAME), dbNameLower), - equalTo(DB_USER, emitStableDatabaseSemconv() ? null : username), - equalTo(DB_CONNECTION_STRING, emitStableDatabaseSemconv() ? null : url), - equalTo(maybeStable(DB_STATEMENT), sanitizedQuery), - equalTo(maybeStable(DB_OPERATION), "SELECT"), - equalTo(maybeStable(DB_SQL_TABLE), table), - equalTo( - DB_QUERY_PARAMETER.getAttributeKey("0"), expectedParameterValue)))); - } - - public interface ThrowingConsumer { - void accept(T t) throws E; + @Override + protected InstrumentationExtension testing() { + return testing; } } diff --git a/instrumentation/jdbc/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test/SqlCommenterTest.java b/instrumentation/jdbc/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test/SqlCommenterTest.java index 71a6843c0b23..bdf59fd58e20 100644 --- a/instrumentation/jdbc/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test/SqlCommenterTest.java +++ b/instrumentation/jdbc/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test/SqlCommenterTest.java @@ -5,175 +5,18 @@ package io.opentelemetry.javaagent.instrumentation.jdbc.test; -import static org.assertj.core.api.Assertions.assertThat; - -import io.opentelemetry.instrumentation.api.internal.SemconvStability; -import io.opentelemetry.instrumentation.jdbc.TestConnection; -import io.opentelemetry.instrumentation.testing.internal.AutoCleanupExtension; +import io.opentelemetry.instrumentation.jdbc.testing.AbstractSqlCommenterTest; import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.SQLException; -import java.sql.Statement; -import java.util.ArrayList; -import java.util.List; -import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; -class SqlCommenterTest { - @RegisterExtension static final AutoCleanupExtension cleanup = AutoCleanupExtension.create(); +class SqlCommenterTest extends AbstractSqlCommenterTest { @RegisterExtension static final InstrumentationExtension testing = AgentInstrumentationExtension.create(); - @Test - void testSqlCommenterStatement() throws SQLException { - List executedSql = new ArrayList<>(); - Connection connection = new TestConnection(executedSql::add); - Statement statement = connection.createStatement(); - - cleanup.deferCleanup(statement); - cleanup.deferCleanup(connection); - - String query = "SELECT 1"; - testing.runWithSpan("parent", () -> statement.execute(query)); - - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> span.hasName("parent").hasNoParent(), - span -> span.hasName("SELECT dbname").hasParent(trace.getSpan(0)))); - - assertThat(executedSql).hasSize(1); - assertThat(executedSql.get(0)).contains(query).contains("traceparent"); - } - - @ParameterizedTest - @ValueSource(booleans = {true, false}) - void testSqlCommenterStatementUpdate(boolean largeUpdate) throws SQLException { - List executedSql = new ArrayList<>(); - Connection connection = new TestConnection(executedSql::add); - Statement statement = connection.createStatement(); - - cleanup.deferCleanup(statement); - cleanup.deferCleanup(connection); - - String query = "INSERT INTO test VALUES(1)"; - testing.runWithSpan( - "parent", - () -> { - if (largeUpdate) { - statement.executeLargeUpdate(query); - } else { - statement.execute(query); - } - }); - - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> span.hasName("parent").hasNoParent(), - span -> span.hasName("INSERT dbname.test").hasParent(trace.getSpan(0)))); - - assertThat(executedSql).hasSize(1); - assertThat(executedSql.get(0)).contains(query).contains("traceparent"); - } - - @ParameterizedTest - @ValueSource(booleans = {true, false}) - void testSqlCommenterStatementBatch(boolean largeUpdate) throws SQLException { - List executedSql = new ArrayList<>(); - Connection connection = new TestConnection(executedSql::add); - Statement statement = connection.createStatement(); - - cleanup.deferCleanup(statement); - cleanup.deferCleanup(connection); - - testing.runWithSpan( - "parent", - () -> { - statement.addBatch("INSERT INTO test VALUES(1)"); - statement.addBatch("INSERT INTO test VALUES(2)"); - if (largeUpdate) { - statement.executeLargeBatch(); - } else { - statement.executeBatch(); - } - }); - - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> span.hasName("parent").hasNoParent(), - span -> - span.hasName( - SemconvStability.emitStableDatabaseSemconv() - ? "BATCH INSERT dbname.test" - : "dbname") - .hasParent(trace.getSpan(0)))); - - assertThat(executedSql).hasSize(2); - assertThat(executedSql.get(0)).contains("INSERT INTO test VALUES(1)").contains("traceparent"); - assertThat(executedSql.get(1)).contains("INSERT INTO test VALUES(2)").contains("traceparent"); - } - - @Test - void testSqlCommenterPreparedStatement() throws SQLException { - List executedSql = new ArrayList<>(); - Connection connection = new TestConnection(executedSql::add); - - String query = "SELECT 1"; - testing.runWithSpan( - "parent", - () -> { - PreparedStatement statement = connection.prepareStatement(query); - cleanup.deferCleanup(statement); - cleanup.deferCleanup(connection); - - statement.execute(); - }); - - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> span.hasName("parent").hasNoParent(), - span -> span.hasName("SELECT dbname").hasParent(trace.getSpan(0)))); - - assertThat(executedSql).hasSize(1); - assertThat(executedSql.get(0)).contains(query).contains("traceparent"); - } - - @ParameterizedTest - @ValueSource(booleans = {true, false}) - void testSqlCommenterPreparedStatementUpdate(boolean largeUpdate) throws SQLException { - List executedSql = new ArrayList<>(); - Connection connection = new TestConnection(executedSql::add); - - String query = "INSERT INTO test VALUES(1)"; - testing.runWithSpan( - "parent", - () -> { - PreparedStatement statement = connection.prepareStatement(query); - cleanup.deferCleanup(statement); - cleanup.deferCleanup(connection); - - if (largeUpdate) { - statement.executeLargeUpdate(); - } else { - statement.executeUpdate(); - } - }); - - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> span.hasName("parent").hasNoParent(), - span -> span.hasName("INSERT dbname.test").hasParent(trace.getSpan(0)))); - - assertThat(executedSql).hasSize(1); - assertThat(executedSql.get(0)).contains(query).contains("traceparent"); + @Override + protected InstrumentationExtension testing() { + return testing; } } diff --git a/instrumentation/jdbc/javaagent/src/test/scala/io/opentelemetry/javaagent/instrumentation/scalaexecutors/SlickTest.scala b/instrumentation/jdbc/javaagent/src/test/scala/io/opentelemetry/javaagent/instrumentation/scalaexecutors/SlickTest.scala index 79c74f466a16..7542c9a15b5b 100644 --- a/instrumentation/jdbc/javaagent/src/test/scala/io/opentelemetry/javaagent/instrumentation/scalaexecutors/SlickTest.scala +++ b/instrumentation/jdbc/javaagent/src/test/scala/io/opentelemetry/javaagent/instrumentation/scalaexecutors/SlickTest.scala @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.scalaexecutors +package io.opentelemetry.javaagent.instrumentation.jdbc.slick import io.opentelemetry.api.trace.{SpanKind, Tracer} import io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableDatabaseSemconv diff --git a/instrumentation/jdbc/library/build.gradle.kts b/instrumentation/jdbc/library/build.gradle.kts index 3c3246102fbb..5e116b24b436 100644 --- a/instrumentation/jdbc/library/build.gradle.kts +++ b/instrumentation/jdbc/library/build.gradle.kts @@ -13,6 +13,15 @@ dependencies { annotationProcessor("com.google.auto.value:auto-value") testImplementation(project(":instrumentation:jdbc:testing")) + + testLibrary("com.h2database:h2:1.3.169") + testLibrary("org.apache.derby:derby:10.6.1.0") // first version jdk 1.6 compatible + testLibrary("org.hsqldb:hsqldb:2.0.0") + + testLibrary("org.apache.tomcat:tomcat-jdbc:7.0.19") + testLibrary("org.apache.tomcat:tomcat-juli:7.0.19") // tomcat jdbc needs this + testLibrary("com.zaxxer:HikariCP:2.4.0") + testLibrary("com.mchange:c3p0:0.9.5") } tasks { diff --git a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/OpenTelemetryPreparedStatement.java b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/OpenTelemetryPreparedStatement.java index 05cfcfa15a9b..c821c3e0b2a3 100644 --- a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/OpenTelemetryPreparedStatement.java +++ b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/OpenTelemetryPreparedStatement.java @@ -244,18 +244,21 @@ public void setBinaryStream(int parameterIndex, InputStream x) throws SQLExcepti @Override public void setObject(int parameterIndex, Object x, int targetSqlType) throws SQLException { delegate.setObject(parameterIndex, x, targetSqlType); + putParameter(parameterIndex, x); } @SuppressWarnings("UngroupedOverloads") @Override public void setObject(int parameterIndex, Object x) throws SQLException { delegate.setObject(parameterIndex, x); + putParameter(parameterIndex, x); } @Override public void setObject(int parameterIndex, Object x, int targetSqlType, int scaleOrLength) throws SQLException { delegate.setObject(parameterIndex, x, targetSqlType, scaleOrLength); + putParameter(parameterIndex, x); } @Override @@ -419,15 +422,26 @@ private T wrapBatchCall(ThrowingSupplier callable public void setObject(int parameterIndex, Object x, SQLType targetSqlType, int scaleOrLength) throws SQLException { delegate.setObject(parameterIndex, x, targetSqlType, scaleOrLength); + putParameter(parameterIndex, x); } @Override public void setObject(int parameterIndex, Object x, SQLType targetSqlType) throws SQLException { delegate.setObject(parameterIndex, x, targetSqlType); + putParameter(parameterIndex, x); } @Override public long executeLargeUpdate() throws SQLException { - return wrapCall(query, delegate::executeLargeUpdate); + return wrapCall( + query, + () -> { + try { + return delegate.executeLargeUpdate(); + } catch (UnsupportedOperationException ignored) { + // Fallback for drivers that only implement executeUpdate + return (long) delegate.executeUpdate(); + } + }); } } diff --git a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/OpenTelemetryStatement.java b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/OpenTelemetryStatement.java index 56551fd90b81..472f0e700f5e 100644 --- a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/OpenTelemetryStatement.java +++ b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/internal/OpenTelemetryStatement.java @@ -28,6 +28,7 @@ import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; import java.sql.SQLWarning; import java.sql.Statement; import java.util.ArrayList; @@ -329,7 +330,22 @@ public long getLargeMaxRows() throws SQLException { @Override public long[] executeLargeBatch() throws SQLException { - return wrapBatchCall(delegate::executeLargeBatch); + return wrapBatchCall( + () -> { + try { + return delegate.executeLargeBatch(); + } catch (UnsupportedOperationException + | SQLFeatureNotSupportedException + | AbstractMethodError ignored) { + // Older drivers rely on the default executeLargeBatch implementation, which throws. + int[] batchResult = delegate.executeBatch(); + long[] converted = new long[batchResult.length]; + for (int index = 0; index < batchResult.length; index++) { + converted[index] = batchResult[index]; + } + return converted; + } + }); } @Override diff --git a/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/JdbcInstrumentationTest.java b/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/JdbcInstrumentationTest.java new file mode 100644 index 000000000000..35973f363b9b --- /dev/null +++ b/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/JdbcInstrumentationTest.java @@ -0,0 +1,140 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.jdbc; + +import io.opentelemetry.instrumentation.jdbc.testing.AbstractJdbcInstrumentationTest; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; +import java.sql.Connection; +import java.sql.Driver; +import java.sql.DriverPropertyInfo; +import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; +import java.util.Locale; +import java.util.Properties; +import java.util.logging.Logger; +import java.util.stream.Stream; +import javax.sql.DataSource; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.params.provider.Arguments; + +class JdbcInstrumentationTest extends AbstractJdbcInstrumentationTest { + + @RegisterExtension + static final InstrumentationExtension testing = LibraryInstrumentationExtension.create(); + + private static final LibraryJdbcTestTelemetry telemetryHelper = + new LibraryJdbcTestTelemetry(testing); + + private static final String DB_NAME = "jdbcUnitTest"; + private static final String DB_NAME_LOWER = DB_NAME.toLowerCase(Locale.ROOT); + + @Override + protected InstrumentationExtension testing() { + return testing; + } + + @Override + protected Connection wrap(Connection connection) throws SQLException { + return telemetryHelper.wrap(connection); + } + + @Override + protected DataSource wrap(DataSource dataSource) { + return telemetryHelper.wrap(dataSource); + } + + static Stream connectionConstructorStream() { + return Stream.of( + Arguments.of( + true, + "h2", + wrapDriver(new org.h2.Driver()), + "jdbc:h2:mem:" + DB_NAME, + null, + "SELECT 3;", + "SELECT ?;", + "SELECT " + DB_NAME_LOWER, + "h2:mem:", + null), + Arguments.of( + true, + "derby", + wrapDriver(new org.apache.derby.jdbc.EmbeddedDriver()), + "jdbc:derby:memory:" + DB_NAME + ";create=true", + "APP", + "SELECT 3 FROM SYSIBM.SYSDUMMY1", + "SELECT ? FROM SYSIBM.SYSDUMMY1", + "SELECT SYSIBM.SYSDUMMY1", + "derby:memory:", + "SYSIBM.SYSDUMMY1"), + Arguments.of( + false, + "h2", + wrapDriver(new org.h2.Driver()), + "jdbc:h2:mem:" + DB_NAME, + null, + "SELECT 3;", + "SELECT ?;", + "SELECT " + DB_NAME_LOWER, + "h2:mem:", + null), + Arguments.of( + false, + "derby", + wrapDriver(new org.apache.derby.jdbc.EmbeddedDriver()), + "jdbc:derby:memory:" + DB_NAME + ";create=true", + "APP", + "SELECT 3 FROM SYSIBM.SYSDUMMY1", + "SELECT ? FROM SYSIBM.SYSDUMMY1", + "SELECT SYSIBM.SYSDUMMY1", + "derby:memory:", + "SYSIBM.SYSDUMMY1")); + } + + private static Driver wrapDriver(Driver delegate) { + return new Driver() { + @Override + public Connection connect(String url, Properties info) throws SQLException { + Connection connection = delegate.connect(url, info); + if (connection == null) { + return null; + } + return telemetryHelper.wrap(connection); + } + + @Override + public boolean acceptsURL(String url) throws SQLException { + return delegate.acceptsURL(url); + } + + @Override + public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) throws SQLException { + return delegate.getPropertyInfo(url, info); + } + + @Override + public int getMajorVersion() { + return delegate.getMajorVersion(); + } + + @Override + public int getMinorVersion() { + return delegate.getMinorVersion(); + } + + @Override + public boolean jdbcCompliant() { + return delegate.jdbcCompliant(); + } + + @Override + public Logger getParentLogger() throws SQLFeatureNotSupportedException { + return delegate.getParentLogger(); + } + }; + } +} diff --git a/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/LibraryJdbcTestTelemetry.java b/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/LibraryJdbcTestTelemetry.java new file mode 100644 index 000000000000..2bd819429e7f --- /dev/null +++ b/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/LibraryJdbcTestTelemetry.java @@ -0,0 +1,77 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.jdbc; + +import io.opentelemetry.instrumentation.jdbc.datasource.JdbcTelemetry; +import io.opentelemetry.instrumentation.jdbc.datasource.OpenTelemetryDataSource; +import io.opentelemetry.instrumentation.jdbc.internal.OpenTelemetryConnection; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import java.sql.Connection; +import java.sql.SQLException; +import javax.sql.DataSource; + +final class LibraryJdbcTestTelemetry { + + private final InstrumentationExtension testing; + private JdbcTelemetry telemetry; + private JdbcTelemetry telemetryWithQueryParameters; + + LibraryJdbcTestTelemetry(InstrumentationExtension testing) { + this.testing = testing; + } + + Connection wrap(Connection connection) throws SQLException { + return wrapConnection(connection, telemetry()); + } + + DataSource wrap(DataSource dataSource) { + return wrapDataSource(dataSource, telemetry()); + } + + Connection instrumentConnectionWithQueryParameters(Connection connection) throws SQLException { + return wrapConnection(connection, telemetryWithQueryParameters()); + } + + private static Connection wrapConnection(Connection connection, JdbcTelemetry telemetry) + throws SQLException { + if (connection instanceof OpenTelemetryConnection) { + return connection; + } + DataSource dataSource = telemetry.wrap(new SingleConnectionDataSource(connection)); + return dataSource.getConnection(); + } + + private static DataSource wrapDataSource(DataSource dataSource, JdbcTelemetry telemetry) { + if (dataSource instanceof OpenTelemetryDataSource) { + return dataSource; + } + return telemetry.wrap(dataSource); + } + + private JdbcTelemetry telemetry() { + if (telemetry == null) { + telemetry = + JdbcTelemetry.builder(testing.getOpenTelemetry()) + .setDataSourceInstrumenterEnabled(true) + .setTransactionInstrumenterEnabled(true) + .build(); + } + return telemetry; + } + + private JdbcTelemetry telemetryWithQueryParameters() { + if (telemetryWithQueryParameters == null) { + telemetryWithQueryParameters = + JdbcTelemetry.builder(testing.getOpenTelemetry()) + .setDataSourceInstrumenterEnabled(true) + .setTransactionInstrumenterEnabled(true) + .setCaptureQueryParameters(true) + .setStatementSanitizationEnabled(false) + .build(); + } + return telemetryWithQueryParameters; + } +} diff --git a/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/PreparedStatementParametersTest.java b/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/PreparedStatementParametersTest.java new file mode 100644 index 000000000000..7e63eb2f1a1a --- /dev/null +++ b/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/PreparedStatementParametersTest.java @@ -0,0 +1,32 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.jdbc; + +import io.opentelemetry.instrumentation.jdbc.testing.AbstractPreparedStatementParametersTest; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; +import java.sql.Connection; +import java.sql.SQLException; +import org.junit.jupiter.api.extension.RegisterExtension; + +class PreparedStatementParametersTest extends AbstractPreparedStatementParametersTest { + + @RegisterExtension + static final InstrumentationExtension testing = LibraryInstrumentationExtension.create(); + + private static final LibraryJdbcTestTelemetry telemetryHelper = + new LibraryJdbcTestTelemetry(testing); + + @Override + protected InstrumentationExtension testing() { + return testing; + } + + @Override + protected Connection wrap(Connection connection) throws SQLException { + return telemetryHelper.instrumentConnectionWithQueryParameters(connection); + } +} diff --git a/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/SingleConnectionDataSource.java b/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/SingleConnectionDataSource.java new file mode 100644 index 000000000000..7bb00c345da7 --- /dev/null +++ b/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/SingleConnectionDataSource.java @@ -0,0 +1,65 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.jdbc; + +import java.io.PrintWriter; +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; +import java.util.logging.Logger; +import javax.sql.DataSource; + +final class SingleConnectionDataSource implements DataSource { + private final Connection delegate; + + SingleConnectionDataSource(Connection delegate) { + this.delegate = delegate; + } + + @Override + public Connection getConnection() { + return delegate; + } + + @Override + public Connection getConnection(String username, String password) { + return getConnection(); + } + + @Override + public PrintWriter getLogWriter() { + return null; + } + + @Override + public void setLogWriter(PrintWriter out) {} + + @Override + public void setLoginTimeout(int seconds) {} + + @Override + public int getLoginTimeout() { + return 0; + } + + @Override + public Logger getParentLogger() throws SQLFeatureNotSupportedException { + throw new SQLFeatureNotSupportedException(); + } + + @Override + public T unwrap(Class iface) throws SQLException { + if (iface.isInstance(delegate)) { + return iface.cast(delegate); + } + throw new SQLFeatureNotSupportedException(); + } + + @Override + public boolean isWrapperFor(Class iface) { + return iface.isInstance(delegate); + } +} diff --git a/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/SqlCommenterTest.java b/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/SqlCommenterTest.java new file mode 100644 index 000000000000..fa3a3f29ac56 --- /dev/null +++ b/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/SqlCommenterTest.java @@ -0,0 +1,37 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.jdbc; + +import io.opentelemetry.instrumentation.jdbc.datasource.JdbcTelemetry; +import io.opentelemetry.instrumentation.jdbc.datasource.JdbcTelemetryBuilder; +import io.opentelemetry.instrumentation.jdbc.datasource.internal.Experimental; +import io.opentelemetry.instrumentation.jdbc.testing.AbstractSqlCommenterTest; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; +import java.sql.Connection; +import java.sql.SQLException; +import javax.sql.DataSource; +import org.junit.jupiter.api.extension.RegisterExtension; + +class SqlCommenterTest extends AbstractSqlCommenterTest { + + @RegisterExtension + static final InstrumentationExtension testing = LibraryInstrumentationExtension.create(); + + @Override + protected InstrumentationExtension testing() { + return testing; + } + + @Override + protected Connection wrap(Connection connection) throws SQLException { + JdbcTelemetryBuilder builder = JdbcTelemetry.builder(testing.getOpenTelemetry()); + Experimental.setEnableSqlCommenter(builder, true); + JdbcTelemetry telemetry = builder.build(); + DataSource dataSource = telemetry.wrap(new SingleConnectionDataSource(connection)); + return dataSource.getConnection(); + } +} diff --git a/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/internal/OpenTelemetryConnectionTest.java b/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/internal/OpenTelemetryConnectionTest.java index 6fab871f1413..260f23077016 100644 --- a/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/internal/OpenTelemetryConnectionTest.java +++ b/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/internal/OpenTelemetryConnectionTest.java @@ -5,56 +5,23 @@ package io.opentelemetry.instrumentation.jdbc.internal; -import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableDatabaseSemconv; import static io.opentelemetry.instrumentation.jdbc.internal.JdbcInstrumenterFactory.createStatementInstrumenter; import static io.opentelemetry.instrumentation.jdbc.internal.JdbcInstrumenterFactory.createTransactionInstrumenter; -import static io.opentelemetry.instrumentation.testing.junit.db.SemconvStabilityUtil.maybeStable; -import static io.opentelemetry.instrumentation.testing.junit.db.SemconvStabilityUtil.maybeStableDbSystemName; -import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; -import static io.opentelemetry.semconv.ServerAttributes.SERVER_ADDRESS; -import static io.opentelemetry.semconv.ServerAttributes.SERVER_PORT; -import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_CONNECTION_STRING; -import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_NAME; -import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_OPERATION; -import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_QUERY_PARAMETER; -import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_SQL_TABLE; -import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_STATEMENT; -import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_SYSTEM; -import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_USER; import static org.assertj.core.api.Assertions.assertThat; import io.opentelemetry.api.OpenTelemetry; -import io.opentelemetry.api.trace.Span; -import io.opentelemetry.api.trace.SpanContext; -import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.context.propagation.ContextPropagators; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import io.opentelemetry.instrumentation.jdbc.TestConnection; import io.opentelemetry.instrumentation.jdbc.internal.dbinfo.DbInfo; import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; -import io.opentelemetry.sdk.testing.assertj.AttributeAssertion; -import java.math.BigDecimal; -import java.net.MalformedURLException; -import java.net.URI; -import java.sql.Date; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; import java.sql.Statement; -import java.sql.Time; -import java.sql.Timestamp; import java.util.ArrayList; -import java.util.Arrays; -import java.util.Calendar; import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.Stream; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; class OpenTelemetryConnectionTest { @@ -68,28 +35,6 @@ void resetTest() { executedSql.clear(); } - @ParameterizedTest - @ValueSource(booleans = {true, false}) - void testVerifyCreateStatement(boolean sqlCommenterEnabled) throws SQLException { - OpenTelemetryConnection connection = getConnection(sqlCommenterEnabled); - String query = "SELECT * FROM users"; - Statement statement = connection.createStatement(); - - SpanContext spanContext = - testing.runWithSpan( - "parent", - () -> { - assertThat(statement.execute(query)).isTrue(); - return Span.current().getSpanContext(); - }); - - assertExecutedSql(executedSql, query, sqlCommenterEnabled, spanContext); - jdbcTraceAssertion(connection.getDbInfo(), query); - - statement.close(); - connection.close(); - } - @SuppressWarnings("unchecked") @Test void testVerifyCreateStatementReturnsOtelWrapper() throws Exception { @@ -105,78 +50,6 @@ void testVerifyCreateStatementReturnsOtelWrapper() throws Exception { connection.close(); } - @ParameterizedTest - @ValueSource(booleans = {true, false}) - void testVerifyPrepareStatement(boolean sqlCommenterEnabled) throws SQLException { - OpenTelemetryConnection connection = getConnection(sqlCommenterEnabled); - String query = "SELECT * FROM users"; - - SpanContext spanContext = - testing.runWithSpan( - "parent", - () -> { - PreparedStatement statement = connection.prepareStatement(query); - assertThat(statement.execute()).isTrue(); - ResultSet resultSet = statement.getResultSet(); - assertThat(resultSet).isInstanceOf(OpenTelemetryResultSet.class); - assertThat(resultSet.getStatement()).isEqualTo(statement); - statement.close(); - return Span.current().getSpanContext(); - }); - - assertExecutedSql(executedSql, query, sqlCommenterEnabled, spanContext); - jdbcTraceAssertion(connection.getDbInfo(), query); - - connection.close(); - } - - @ParameterizedTest - @ValueSource(booleans = {true, false}) - void testVerifyPrepareStatementUpdate(boolean sqlCommenterEnabled) throws SQLException { - OpenTelemetryConnection connection = getConnection(sqlCommenterEnabled); - String query = "UPDATE users SET name = name"; - - SpanContext spanContext = - testing.runWithSpan( - "parent", - () -> { - PreparedStatement statement = connection.prepareStatement(query); - statement.executeUpdate(); - assertThat(statement.getResultSet()).isNull(); - statement.close(); - return Span.current().getSpanContext(); - }); - - assertExecutedSql(executedSql, query, sqlCommenterEnabled, spanContext); - jdbcTraceAssertion(connection.getDbInfo(), query, "UPDATE"); - - connection.close(); - } - - @ParameterizedTest - @ValueSource(booleans = {true, false}) - void testVerifyPrepareStatementQuery(boolean sqlCommenterEnabled) throws SQLException { - OpenTelemetryConnection connection = getConnection(sqlCommenterEnabled); - String query = "SELECT * FROM users"; - - SpanContext spanContext = - testing.runWithSpan( - "parent", - () -> { - PreparedStatement statement = connection.prepareStatement(query); - ResultSet resultSet = statement.executeQuery(); - assertThat(resultSet).isInstanceOf(OpenTelemetryResultSet.class); - assertThat(resultSet.getStatement()).isEqualTo(statement); - statement.close(); - return Span.current().getSpanContext(); - }); - - assertExecutedSql(executedSql, query, sqlCommenterEnabled, spanContext); - jdbcTraceAssertion(connection.getDbInfo(), query); - - connection.close(); - } - @SuppressWarnings("unchecked") @Test void testVerifyPrepareStatementReturnsOtelWrapper() throws Exception { @@ -204,28 +77,6 @@ void testVerifyPrepareStatementReturnsOtelWrapper() throws Exception { connection.close(); } - @ParameterizedTest - @ValueSource(booleans = {true, false}) - void testVerifyPrepareCall(boolean sqlCommenterEnabled) throws SQLException { - OpenTelemetryConnection connection = getConnection(sqlCommenterEnabled); - String query = "SELECT * FROM users"; - - SpanContext spanContext = - testing.runWithSpan( - "parent", - () -> { - PreparedStatement statement = connection.prepareCall(query); - assertThat(statement.execute()).isTrue(); - statement.close(); - return Span.current().getSpanContext(); - }); - - assertExecutedSql(executedSql, query, sqlCommenterEnabled, spanContext); - jdbcTraceAssertion(connection.getDbInfo(), query); - - connection.close(); - } - @SuppressWarnings("unchecked") @Test void testVerifyPrepareCallReturnsOtelWrapper() throws Exception { @@ -247,93 +98,6 @@ void testVerifyPrepareCallReturnsOtelWrapper() throws Exception { connection.close(); } - @Test - void testVerifyCommit() throws Exception { - OpenTelemetryConnection connection = getConnection(); - - testing.runWithSpan("parent", connection::commit); - transactionTraceAssertion(connection.getDbInfo(), "COMMIT"); - - connection.close(); - } - - @Test - void testVerifyRollback() throws Exception { - OpenTelemetryConnection connection = getConnection(); - - testing.runWithSpan("parent", () -> connection.rollback()); - transactionTraceAssertion(connection.getDbInfo(), "ROLLBACK"); - - connection.close(); - } - - // https://github.com/open-telemetry/semantic-conventions/pull/2093 - @SuppressWarnings("deprecation") - @Test - void testVerifyPrepareStatementParameters() throws SQLException, MalformedURLException { - OpenTelemetry openTelemetry = testing.getOpenTelemetry(); - Instrumenter instrumenter = - createStatementInstrumenter(testing.getOpenTelemetry(), true); - Instrumenter transactionInstrumenter = - createTransactionInstrumenter(openTelemetry, false); - DbInfo dbInfo = getDbInfo(); - OpenTelemetryConnection connection = - new OpenTelemetryConnection( - new TestConnection(), dbInfo, instrumenter, transactionInstrumenter, true, false); - String query = "SELECT * FROM users WHERE id=? AND age=3"; - String sanitized = "SELECT * FROM users WHERE id=? AND age=3"; - PreparedStatement statement = connection.prepareStatement(query); - // doesn't need to match the number of placeholders in this context - statement.setBoolean(1, true); - statement.setShort(2, (short) 1); - statement.setInt(3, 2); - statement.setLong(4, 3); - statement.setFloat(5, 4); - statement.setDouble(6, 5.5); - statement.setBigDecimal(7, BigDecimal.valueOf(6)); - statement.setString(8, "S"); - statement.setDate(9, Date.valueOf("2000-01-01")); - statement.setDate(10, Date.valueOf("2000-01-02"), Calendar.getInstance()); - statement.setTime(11, Time.valueOf("00:00:00")); - statement.setTime(12, Time.valueOf("00:00:01"), Calendar.getInstance()); - statement.setTimestamp(13, Timestamp.valueOf("2000-01-01 00:00:00")); - statement.setTimestamp(14, Timestamp.valueOf("2000-01-01 00:00:01"), Calendar.getInstance()); - statement.setURL(15, URI.create("http://localhost:8080").toURL()); - statement.setNString(16, "S"); - - testing.runWithSpan( - "parent", - () -> { - ResultSet resultSet = statement.executeQuery(); - assertThat(resultSet).isInstanceOf(OpenTelemetryResultSet.class); - assertThat(resultSet.getStatement()).isEqualTo(statement); - }); - - jdbcTraceAssertion( - dbInfo, - sanitized, - "SELECT", - equalTo(DB_QUERY_PARAMETER.getAttributeKey("0"), "true"), - equalTo(DB_QUERY_PARAMETER.getAttributeKey("1"), "1"), - equalTo(DB_QUERY_PARAMETER.getAttributeKey("2"), "2"), - equalTo(DB_QUERY_PARAMETER.getAttributeKey("3"), "3"), - equalTo(DB_QUERY_PARAMETER.getAttributeKey("4"), "4.0"), - equalTo(DB_QUERY_PARAMETER.getAttributeKey("5"), "5.5"), - equalTo(DB_QUERY_PARAMETER.getAttributeKey("6"), "6"), - equalTo(DB_QUERY_PARAMETER.getAttributeKey("7"), "S"), - equalTo(DB_QUERY_PARAMETER.getAttributeKey("8"), "2000-01-01"), - equalTo(DB_QUERY_PARAMETER.getAttributeKey("9"), "2000-01-02"), - equalTo(DB_QUERY_PARAMETER.getAttributeKey("10"), "00:00:00"), - equalTo(DB_QUERY_PARAMETER.getAttributeKey("11"), "00:00:01"), - equalTo(DB_QUERY_PARAMETER.getAttributeKey("12"), "2000-01-01 00:00:00.0"), - equalTo(DB_QUERY_PARAMETER.getAttributeKey("13"), "2000-01-01 00:00:01.0"), - equalTo(DB_QUERY_PARAMETER.getAttributeKey("14"), "http://localhost:8080"), - equalTo(DB_QUERY_PARAMETER.getAttributeKey("15"), "S")); - - statement.close(); - connection.close(); - } - private static DbInfo getDbInfo() { return DbInfo.builder() .system("my_system") @@ -347,97 +111,7 @@ private static DbInfo getDbInfo() { .build(); } - private static void jdbcTraceAssertion(DbInfo dbInfo, String query) { - jdbcTraceAssertion(dbInfo, query, "SELECT"); - } - - @SuppressWarnings("deprecation") // old semconv - private static void jdbcTraceAssertion( - DbInfo dbInfo, String query, String operation, AttributeAssertion... assertions) { - List baseAttributeAssertions = - Arrays.asList( - equalTo(maybeStable(DB_SYSTEM), maybeStableDbSystemName(dbInfo.getSystem())), - equalTo(maybeStable(DB_NAME), dbInfo.getName()), - equalTo(DB_USER, emitStableDatabaseSemconv() ? null : dbInfo.getUser()), - equalTo( - DB_CONNECTION_STRING, emitStableDatabaseSemconv() ? null : dbInfo.getShortUrl()), - equalTo(maybeStable(DB_STATEMENT), query), - equalTo(maybeStable(DB_OPERATION), operation), - equalTo(maybeStable(DB_SQL_TABLE), "users"), - equalTo(SERVER_ADDRESS, dbInfo.getHost()), - equalTo(SERVER_PORT, dbInfo.getPort())); - - List additionAttributeAssertions = Arrays.asList(assertions); - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), - span -> - span.hasName(operation + " my_name.users") - .hasKind(SpanKind.CLIENT) - .hasParent(trace.getSpan(0)) - .hasAttributesSatisfyingExactly( - Stream.concat( - baseAttributeAssertions.stream(), - additionAttributeAssertions.stream()) - .collect(Collectors.toList())))); - } - - @SuppressWarnings("deprecation") // old semconv - private static void transactionTraceAssertion(DbInfo dbInfo, String operation) { - testing.waitAndAssertTraces( - trace -> - trace.hasSpansSatisfyingExactly( - span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), - span -> - span.hasName(operation) - .hasKind(SpanKind.CLIENT) - .hasParent(trace.getSpan(0)) - .hasAttributesSatisfyingExactly( - equalTo( - maybeStable(DB_SYSTEM), - maybeStableDbSystemName(dbInfo.getSystem())), - equalTo(maybeStable(DB_NAME), dbInfo.getName()), - equalTo(maybeStable(DB_OPERATION), operation), - equalTo(DB_USER, emitStableDatabaseSemconv() ? null : dbInfo.getUser()), - equalTo( - DB_CONNECTION_STRING, - emitStableDatabaseSemconv() ? null : dbInfo.getShortUrl()), - equalTo(SERVER_ADDRESS, dbInfo.getHost()), - equalTo(SERVER_PORT, dbInfo.getPort())))); - } - - private static void assertExecutedSql( - List executedSql, - String query, - boolean sqlCommenterEnabled, - SpanContext spanContext) { - assertThat(executedSql).hasSize(1); - if (sqlCommenterEnabled) { - assertThat(executedSql.get(0)) - .contains(query) - .contains("traceparent") - .contains(spanContext.getTraceId()) - .contains(spanContext.getSpanId()); - } else { - assertThat(executedSql.get(0)).isEqualTo(query); - } - } - - private static OpenTelemetryConnection getConnection() { - return getConnection(false); - } - - private static OpenTelemetryConnection getConnection(boolean sqlCommenterEnabled) { - return getConnection(testing.getOpenTelemetry(), sqlCommenterEnabled); - } - private static OpenTelemetryConnection getConnection(OpenTelemetry openTelemetry) { - return getConnection(openTelemetry, false); - } - - private static OpenTelemetryConnection getConnection( - OpenTelemetry openTelemetry, boolean sqlCommenterEnabled) { Instrumenter statementInstrumenter = createStatementInstrumenter(openTelemetry); Instrumenter transactionInstrumenter = @@ -449,6 +123,6 @@ private static OpenTelemetryConnection getConnection( statementInstrumenter, transactionInstrumenter, false, - sqlCommenterEnabled); + false); } } diff --git a/instrumentation/jdbc/testing/build.gradle.kts b/instrumentation/jdbc/testing/build.gradle.kts index 3a99cee0a166..1ce8f2c86de3 100644 --- a/instrumentation/jdbc/testing/build.gradle.kts +++ b/instrumentation/jdbc/testing/build.gradle.kts @@ -6,3 +6,17 @@ plugins { id("otel.java-conventions") } + +dependencies { + api(project(":testing-common")) + api("com.google.guava:guava") + + compileOnly("com.h2database:h2:1.3.169") + compileOnly("org.apache.derby:derby:10.6.1.0") + compileOnly("org.hsqldb:hsqldb:2.0.0") + + compileOnly("org.apache.tomcat:tomcat-jdbc:7.0.19") + compileOnly("org.apache.tomcat:tomcat-juli:7.0.19") + compileOnly("com.zaxxer:HikariCP:2.4.0") + compileOnly("com.mchange:c3p0:0.9.5") +} diff --git a/instrumentation/jdbc/testing/src/main/java/io/opentelemetry/instrumentation/jdbc/TestConnection.java b/instrumentation/jdbc/testing/src/main/java/io/opentelemetry/instrumentation/jdbc/TestConnection.java index d32c8d29134a..9c1c4d15edd6 100644 --- a/instrumentation/jdbc/testing/src/main/java/io/opentelemetry/instrumentation/jdbc/TestConnection.java +++ b/instrumentation/jdbc/testing/src/main/java/io/opentelemetry/instrumentation/jdbc/TestConnection.java @@ -28,21 +28,34 @@ /** A JDBC connection class that optionally throws an exception in the constructor, used to test */ public class TestConnection implements Connection { - private String url; - Consumer sqlConsumer = unused -> {}; - public TestConnection() {} + private final String url; + private final Consumer sqlConsumer; + + public TestConnection() { + this(null, unused -> {}); + } + + public TestConnection(String url) { + this(url, unused -> {}); + } public TestConnection(Consumer sqlConsumer) { - this.sqlConsumer = sqlConsumer; + this(null, sqlConsumer); } public TestConnection(boolean throwException) { + this(null, unused -> {}); if (throwException) { throw new IllegalStateException("connection exception"); } } + private TestConnection(String url, Consumer sqlConsumer) { + this.url = url; + this.sqlConsumer = sqlConsumer; + } + @Override public void abort(Executor executor) throws SQLException {} @@ -294,10 +307,6 @@ public void setTransactionIsolation(int level) throws SQLException {} @Override public void setTypeMap(Map> map) throws SQLException {} - public void setUrl(String url) { - this.url = url; - } - @Override public T unwrap(Class iface) throws SQLException { return null; diff --git a/instrumentation/jdbc/testing/src/main/java/io/opentelemetry/instrumentation/jdbc/TestDriver.java b/instrumentation/jdbc/testing/src/main/java/io/opentelemetry/instrumentation/jdbc/TestDriver.java index dd0e185dff3f..317cc5456404 100644 --- a/instrumentation/jdbc/testing/src/main/java/io/opentelemetry/instrumentation/jdbc/TestDriver.java +++ b/instrumentation/jdbc/testing/src/main/java/io/opentelemetry/instrumentation/jdbc/TestDriver.java @@ -16,7 +16,7 @@ public class TestDriver implements Driver { @Override public Connection connect(String url, Properties info) throws SQLException { - return new TestConnection(); + return new TestConnection(url); } @Override diff --git a/instrumentation/jdbc/testing/src/main/java/io/opentelemetry/instrumentation/jdbc/testing/AbstractJdbcInstrumentationTest.java b/instrumentation/jdbc/testing/src/main/java/io/opentelemetry/instrumentation/jdbc/testing/AbstractJdbcInstrumentationTest.java new file mode 100644 index 000000000000..3392a9f0424c --- /dev/null +++ b/instrumentation/jdbc/testing/src/main/java/io/opentelemetry/instrumentation/jdbc/testing/AbstractJdbcInstrumentationTest.java @@ -0,0 +1,1918 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.jdbc.testing; + +import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableDatabaseSemconv; +import static io.opentelemetry.instrumentation.testing.junit.code.SemconvCodeStabilityUtil.codeFunctionAssertions; +import static io.opentelemetry.instrumentation.testing.junit.db.DbClientMetricsTestUtil.assertDurationMetric; +import static io.opentelemetry.instrumentation.testing.junit.db.SemconvStabilityUtil.maybeStable; +import static io.opentelemetry.instrumentation.testing.junit.db.SemconvStabilityUtil.maybeStableDbSystemName; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; +import static io.opentelemetry.semconv.DbAttributes.DB_COLLECTION_NAME; +import static io.opentelemetry.semconv.DbAttributes.DB_NAMESPACE; +import static io.opentelemetry.semconv.DbAttributes.DB_OPERATION_BATCH_SIZE; +import static io.opentelemetry.semconv.DbAttributes.DB_OPERATION_NAME; +import static io.opentelemetry.semconv.DbAttributes.DB_SYSTEM_NAME; +import static io.opentelemetry.semconv.ServerAttributes.SERVER_ADDRESS; +import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_CONNECTION_STRING; +import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_NAME; +import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_OPERATION; +import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_SQL_TABLE; +import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_STATEMENT; +import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_SYSTEM; +import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_USER; +import static java.util.Arrays.asList; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; +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.testing.internal.AutoCleanupExtension; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.sdk.testing.assertj.AttributeAssertion; +import io.opentelemetry.sdk.testing.assertj.SpanDataAssert; +import io.opentelemetry.sdk.testing.assertj.TraceAssert; +import java.beans.PropertyVetoException; +import java.io.Closeable; +import java.io.IOException; +import java.sql.CallableStatement; +import java.sql.Connection; +import java.sql.Driver; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Properties; +import java.util.function.Consumer; +import java.util.stream.Stream; +import javax.sql.DataSource; +import org.apache.derby.jdbc.EmbeddedDataSource; +import org.apache.derby.jdbc.EmbeddedDriver; +import org.assertj.core.api.ThrowingConsumer; +import org.h2.jdbcx.JdbcDataSource; +import org.hsqldb.jdbc.JDBCDriver; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; + +@SuppressWarnings("deprecation") // using deprecated semconv +public abstract class AbstractJdbcInstrumentationTest { + + @RegisterExtension static final AutoCleanupExtension cleanup = AutoCleanupExtension.create(); + + protected abstract InstrumentationExtension testing(); + + protected Connection wrap(Connection connection) throws SQLException { + return connection; + } + + protected DataSource wrap(DataSource dataSource) { + return dataSource; + } + + private static final String dbName = "jdbcUnitTest"; + private static final String dbNameLower = dbName.toLowerCase(Locale.ROOT); + private static final Map jdbcUrls = + ImmutableMap.of( + "h2", "jdbc:h2:mem:" + dbName, + "derby", "jdbc:derby:memory:" + dbName, + "hsqldb", "jdbc:hsqldb:mem:" + dbName); + private static final Map jdbcDriverClassNames = + ImmutableMap.of( + "h2", "org.h2.Driver", + "derby", "org.apache.derby.jdbc.EmbeddedDriver", + "hsqldb", "org.hsqldb.jdbc.JDBCDriver"); + private static final Map jdbcUserNames = Maps.newHashMap(); + private static final Properties connectionProps = new Properties(); + // JDBC Connection pool name (i.e. HikariCP) -> Map + private static final Map> cpDatasources = Maps.newHashMap(); + + static { + jdbcUserNames.put("derby", "APP"); + jdbcUserNames.put("h2", null); + jdbcUserNames.put("hsqldb", "SA"); + + connectionProps.put("databaseName", "someDb"); + connectionProps.put("OPEN_NEW", "true"); // So H2 doesn't complain about username/password. + } + + @BeforeAll + static void setUp() { + prepareConnectionPoolDatasources(); + } + + @AfterAll + static void tearDown() { + cpDatasources + .values() + .forEach( + k -> + k.values() + .forEach( + dataSource -> { + if (dataSource instanceof Closeable) { + try { + ((Closeable) dataSource).close(); + } catch (IOException ignore) { + // ignore + } + } + })); + } + + static void prepareConnectionPoolDatasources() { + List connectionPoolNames = asList("tomcat", "hikari", "c3p0"); + connectionPoolNames.forEach( + cpName -> { + Map dbDsMapping = new HashMap<>(); + jdbcUrls.forEach( + (dbType, jdbcUrl) -> dbDsMapping.put(dbType, createDs(cpName, dbType, jdbcUrl))); + cpDatasources.put(cpName, dbDsMapping); + }); + } + + static DataSource createTomcatDs(String dbType, String jdbcUrl) { + org.apache.tomcat.jdbc.pool.DataSource ds = new org.apache.tomcat.jdbc.pool.DataSource(); + String jdbcUrlToSet = dbType.equals("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; + } + + static DataSource createHikariDs(String dbType, String jdbcUrl) { + HikariConfig config = new HikariConfig(); + String jdbcUrlToSet = dbType.equals("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); + } + + static DataSource createC3P0Ds(String dbType, String jdbcUrl) { + ComboPooledDataSource ds = new ComboPooledDataSource(); + try { + ds.setDriverClass(jdbcDriverClassNames.get(dbType)); + } catch (PropertyVetoException e) { + throw new IllegalStateException(e); + } + String jdbcUrlToSet = dbType.equals("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; + } + + static DataSource createDs(String connectionPoolName, String dbType, String jdbcUrl) { + DataSource ds = null; + if (connectionPoolName.equals("tomcat")) { + ds = createTomcatDs(dbType, jdbcUrl); + } + if (connectionPoolName.equals("hikari")) { + ds = createHikariDs(dbType, jdbcUrl); + } + if (connectionPoolName.equals("c3p0")) { + ds = createC3P0Ds(dbType, jdbcUrl); + } + return ds; + } + + static Stream basicStatementStream() throws SQLException { + return Stream.of( + Arguments.of( + "h2", + new org.h2.Driver().connect(jdbcUrls.get("h2"), null), + null, + "SELECT 3", + "SELECT ?", + "SELECT " + dbNameLower, + "h2:mem:", + null), + Arguments.of( + "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"), + Arguments.of( + "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"), + Arguments.of( + "h2", + new org.h2.Driver().connect(jdbcUrls.get("h2"), connectionProps), + null, + "SELECT 3", + "SELECT ?", + "SELECT " + dbNameLower, + "h2:mem:", + null), + Arguments.of( + "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"), + Arguments.of( + "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"), + Arguments.of( + "h2", + cpDatasources.get("tomcat").get("h2").getConnection(), + null, + "SELECT 3", + "SELECT ?", + "SELECT " + dbNameLower, + "h2:mem:", + null), + Arguments.of( + "derby", + cpDatasources.get("tomcat").get("derby").getConnection(), + "APP", + "SELECT 3 FROM SYSIBM.SYSDUMMY1", + "SELECT ? FROM SYSIBM.SYSDUMMY1", + "SELECT SYSIBM.SYSDUMMY1", + "derby:memory:", + "SYSIBM.SYSDUMMY1"), + Arguments.of( + "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"), + Arguments.of( + "h2", + cpDatasources.get("hikari").get("h2").getConnection(), + null, + "SELECT 3", + "SELECT ?", + "SELECT " + dbNameLower, + "h2:mem:", + null), + Arguments.of( + "derby", + cpDatasources.get("hikari").get("derby").getConnection(), + "APP", + "SELECT 3 FROM SYSIBM.SYSDUMMY1", + "SELECT ? FROM SYSIBM.SYSDUMMY1", + "SELECT SYSIBM.SYSDUMMY1", + "derby:memory:", + "SYSIBM.SYSDUMMY1"), + Arguments.of( + "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"), + Arguments.of( + "h2", + cpDatasources.get("c3p0").get("h2").getConnection(), + null, + "SELECT 3", + "SELECT ?", + "SELECT " + dbNameLower, + "h2:mem:", + null), + Arguments.of( + "derby", + cpDatasources.get("c3p0").get("derby").getConnection(), + "APP", + "SELECT 3 FROM SYSIBM.SYSDUMMY1", + "SELECT ? FROM SYSIBM.SYSDUMMY1", + "SELECT SYSIBM.SYSDUMMY1", + "derby:memory:", + "SYSIBM.SYSDUMMY1"), + Arguments.of( + "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")); + } + + @ParameterizedTest + @MethodSource("basicStatementStream") + public void testBasicStatement( + String system, + Connection conn, + String username, + String query, + String sanitizedQuery, + String spanName, + String url, + String table) + throws SQLException { + Connection connection = wrap(conn); + Statement statement = connection.createStatement(); + cleanup.deferCleanup(statement); + ResultSet resultSet = testing().runWithSpan("parent", () -> statement.executeQuery(query)); + + resultSet.next(); + assertThat(resultSet.getInt(1)).isEqualTo(3); + + testing() + .waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), + span -> + span.hasName(spanName) + .hasKind(SpanKind.CLIENT) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo(maybeStable(DB_SYSTEM), maybeStableDbSystemName(system)), + equalTo(maybeStable(DB_NAME), dbNameLower), + equalTo(DB_USER, emitStableDatabaseSemconv() ? null : username), + equalTo( + DB_CONNECTION_STRING, emitStableDatabaseSemconv() ? null : url), + equalTo(maybeStable(DB_STATEMENT), sanitizedQuery), + equalTo(maybeStable(DB_OPERATION), "SELECT"), + equalTo(maybeStable(DB_SQL_TABLE), table)))); + + if (table != null) { + assertDurationMetric( + testing(), + "io.opentelemetry.jdbc", + DB_SYSTEM_NAME, + DB_COLLECTION_NAME, + DB_NAMESPACE, + DB_OPERATION_NAME); + } else { + assertDurationMetric( + testing(), "io.opentelemetry.jdbc", DB_SYSTEM_NAME, DB_OPERATION_NAME, DB_NAMESPACE); + } + } + + static Stream preparedStatementStream() throws SQLException { + return Stream.of( + Arguments.of( + "h2", + new org.h2.Driver().connect(jdbcUrls.get("h2"), null), + null, + "SELECT 3", + "SELECT ?", + "SELECT " + dbNameLower, + "h2:mem:", + null), + Arguments.of( + "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"), + Arguments.of( + "h2", + cpDatasources.get("tomcat").get("h2").getConnection(), + null, + "SELECT 3", + "SELECT ?", + "SELECT " + dbNameLower, + "h2:mem:", + null), + Arguments.of( + "derby", + cpDatasources.get("tomcat").get("derby").getConnection(), + "APP", + "SELECT 3 FROM SYSIBM.SYSDUMMY1", + "SELECT ? FROM SYSIBM.SYSDUMMY1", + "SELECT SYSIBM.SYSDUMMY1", + "derby:memory:", + "SYSIBM.SYSDUMMY1"), + Arguments.of( + "h2", + cpDatasources.get("hikari").get("h2").getConnection(), + null, + "SELECT 3", + "SELECT ?", + "SELECT " + dbNameLower, + "h2:mem:", + null), + Arguments.of( + "derby", + cpDatasources.get("hikari").get("derby").getConnection(), + "APP", + "SELECT 3 FROM SYSIBM.SYSDUMMY1", + "SELECT ? FROM SYSIBM.SYSDUMMY1", + "SELECT SYSIBM.SYSDUMMY1", + "derby:memory:", + "SYSIBM.SYSDUMMY1"), + Arguments.of( + "h2", + cpDatasources.get("c3p0").get("h2").getConnection(), + null, + "SELECT 3", + "SELECT ?", + "SELECT " + dbNameLower, + "h2:mem:", + null), + Arguments.of( + "derby", + cpDatasources.get("c3p0").get("derby").getConnection(), + "APP", + "SELECT 3 FROM SYSIBM.SYSDUMMY1", + "SELECT ? FROM SYSIBM.SYSDUMMY1", + "SELECT SYSIBM.SYSDUMMY1", + "derby:memory:", + "SYSIBM.SYSDUMMY1")); + } + + @ParameterizedTest + @MethodSource("preparedStatementStream") + void testPreparedStatementExecute( + String system, + Connection conn, + String username, + String query, + String sanitizedQuery, + String spanName, + String url, + String table) + throws SQLException { + Connection connection = wrap(conn); + PreparedStatement statement = connection.prepareStatement(query); + cleanup.deferCleanup(statement); + ResultSet resultSet = + testing() + .runWithSpan( + "parent", + () -> { + statement.execute(); + return statement.getResultSet(); + }); + + resultSet.next(); + assertThat(resultSet.getInt(1)).isEqualTo(3); + + testing() + .waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), + span -> + span.hasName(spanName) + .hasKind(SpanKind.CLIENT) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo(maybeStable(DB_SYSTEM), maybeStableDbSystemName(system)), + equalTo(maybeStable(DB_NAME), dbNameLower), + equalTo(DB_USER, emitStableDatabaseSemconv() ? null : username), + equalTo( + DB_CONNECTION_STRING, emitStableDatabaseSemconv() ? null : url), + equalTo(maybeStable(DB_STATEMENT), sanitizedQuery), + equalTo(maybeStable(DB_OPERATION), "SELECT"), + equalTo(maybeStable(DB_SQL_TABLE), table)))); + } + + @ParameterizedTest + @MethodSource("preparedStatementStream") + void testPreparedStatementQuery( + String system, + Connection conn, + String username, + String query, + String sanitizedQuery, + String spanName, + String url, + String table) + throws SQLException { + Connection connection = wrap(conn); + PreparedStatement statement = connection.prepareStatement(query); + cleanup.deferCleanup(statement); + ResultSet resultSet = testing().runWithSpan("parent", () -> statement.executeQuery()); + + resultSet.next(); + assertThat(resultSet.getInt(1)).isEqualTo(3); + + testing() + .waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), + span -> + span.hasName(spanName) + .hasKind(SpanKind.CLIENT) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo(maybeStable(DB_SYSTEM), maybeStableDbSystemName(system)), + equalTo(maybeStable(DB_NAME), dbNameLower), + equalTo(DB_USER, emitStableDatabaseSemconv() ? null : username), + equalTo( + DB_CONNECTION_STRING, emitStableDatabaseSemconv() ? null : url), + equalTo(maybeStable(DB_STATEMENT), sanitizedQuery), + equalTo(maybeStable(DB_OPERATION), "SELECT"), + equalTo(maybeStable(DB_SQL_TABLE), table)))); + } + + @ParameterizedTest + @MethodSource("preparedStatementStream") + void testPreparedCall( + String system, + Connection conn, + String username, + String query, + String sanitizedQuery, + String spanName, + String url, + String table) + throws SQLException { + Connection connection = wrap(conn); + CallableStatement statement = connection.prepareCall(query); + cleanup.deferCleanup(statement); + ResultSet resultSet = testing().runWithSpan("parent", () -> statement.executeQuery()); + + resultSet.next(); + assertThat(resultSet.getInt(1)).isEqualTo(3); + + testing() + .waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), + span -> + span.hasName(spanName) + .hasKind(SpanKind.CLIENT) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo(maybeStable(DB_SYSTEM), maybeStableDbSystemName(system)), + equalTo(maybeStable(DB_NAME), dbNameLower), + equalTo(DB_USER, emitStableDatabaseSemconv() ? null : username), + equalTo( + DB_CONNECTION_STRING, emitStableDatabaseSemconv() ? null : url), + equalTo(maybeStable(DB_STATEMENT), sanitizedQuery), + equalTo(maybeStable(DB_OPERATION), "SELECT"), + equalTo(maybeStable(DB_SQL_TABLE), table)))); + } + + static Stream statementUpdateStream() throws SQLException { + return Stream.of( + Arguments.of( + "h2", + new org.h2.Driver().connect(jdbcUrls.get("h2"), null), + null, + "CREATE TABLE S_H2 (id INTEGER not NULL, PRIMARY KEY ( id ))", + "CREATE TABLE jdbcunittest.S_H2", + "h2:mem:", + "S_H2"), + Arguments.of( + "derby", + new EmbeddedDriver().connect(jdbcUrls.get("derby"), null), + "APP", + "CREATE TABLE S_DERBY (id INTEGER not NULL, PRIMARY KEY ( id ))", + "CREATE TABLE jdbcunittest.S_DERBY", + "derby:memory:", + "S_DERBY"), + Arguments.of( + "hsqldb", + new JDBCDriver().connect(jdbcUrls.get("hsqldb"), null), + "SA", + "CREATE TABLE PUBLIC.S_HSQLDB (id INTEGER not NULL, PRIMARY KEY ( id ))", + "CREATE TABLE PUBLIC.S_HSQLDB", + "hsqldb:mem:", + "PUBLIC.S_HSQLDB"), + Arguments.of( + "h2", + cpDatasources.get("tomcat").get("h2").getConnection(), + null, + "CREATE TABLE S_H2_TOMCAT (id INTEGER not NULL, PRIMARY KEY ( id ))", + "CREATE TABLE jdbcunittest.S_H2_TOMCAT", + "h2:mem:", + "S_H2_TOMCAT"), + Arguments.of( + "derby", + cpDatasources.get("tomcat").get("derby").getConnection(), + "APP", + "CREATE TABLE S_DERBY_TOMCAT (id INTEGER not NULL, PRIMARY KEY ( id ))", + "CREATE TABLE jdbcunittest.S_DERBY_TOMCAT", + "derby:memory:", + "S_DERBY_TOMCAT"), + Arguments.of( + "hsqldb", + cpDatasources.get("tomcat").get("hsqldb").getConnection(), + "SA", + "CREATE TABLE PUBLIC.S_HSQLDB_TOMCAT (id INTEGER not NULL, PRIMARY KEY ( id ))", + "CREATE TABLE PUBLIC.S_HSQLDB_TOMCAT", + "hsqldb:mem:", + "PUBLIC.S_HSQLDB_TOMCAT"), + Arguments.of( + "h2", + cpDatasources.get("hikari").get("h2").getConnection(), + null, + "CREATE TABLE S_H2_HIKARI (id INTEGER not NULL, PRIMARY KEY ( id ))", + "CREATE TABLE jdbcunittest.S_H2_HIKARI", + "h2:mem:", + "S_H2_HIKARI"), + Arguments.of( + "derby", + cpDatasources.get("hikari").get("derby").getConnection(), + "APP", + "CREATE TABLE S_DERBY_HIKARI (id INTEGER not NULL, PRIMARY KEY ( id ))", + "CREATE TABLE jdbcunittest.S_DERBY_HIKARI", + "derby:memory:", + "S_DERBY_HIKARI"), + Arguments.of( + "hsqldb", + cpDatasources.get("hikari").get("hsqldb").getConnection(), + "SA", + "CREATE TABLE PUBLIC.S_HSQLDB_HIKARI (id INTEGER not NULL, PRIMARY KEY ( id ))", + "CREATE TABLE PUBLIC.S_HSQLDB_HIKARI", + "hsqldb:mem:", + "PUBLIC.S_HSQLDB_HIKARI"), + Arguments.of( + "h2", + cpDatasources.get("c3p0").get("h2").getConnection(), + null, + "CREATE TABLE S_H2_C3P0 (id INTEGER not NULL, PRIMARY KEY ( id ))", + "CREATE TABLE jdbcunittest.S_H2_C3P0", + "h2:mem:", + "S_H2_C3P0"), + Arguments.of( + "derby", + cpDatasources.get("c3p0").get("derby").getConnection(), + "APP", + "CREATE TABLE S_DERBY_C3P0 (id INTEGER not NULL, PRIMARY KEY ( id ))", + "CREATE TABLE jdbcunittest.S_DERBY_C3P0", + "derby:memory:", + "S_DERBY_C3P0"), + Arguments.of( + "hsqldb", + cpDatasources.get("c3p0").get("hsqldb").getConnection(), + "SA", + "CREATE TABLE PUBLIC.S_HSQLDB_C3P0 (id INTEGER not NULL, PRIMARY KEY ( id ))", + "CREATE TABLE PUBLIC.S_HSQLDB_C3P0", + "hsqldb:mem:", + "PUBLIC.S_HSQLDB_C3P0")); + } + + @ParameterizedTest + @MethodSource("statementUpdateStream") + void testStatementUpdate( + String system, + Connection conn, + String username, + String query, + String spanName, + String url, + String table) + throws SQLException { + Connection connection = wrap(conn); + Statement statement = connection.createStatement(); + cleanup.deferCleanup(statement); + String sql = connection.nativeSQL(query); + testing().runWithSpan("parent", () -> assertThat(statement.execute(sql)).isFalse()); + + assertThat(statement.getUpdateCount()).isEqualTo(0); + + testing() + .waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), + span -> + span.hasName(spanName) + .hasKind(SpanKind.CLIENT) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo(maybeStable(DB_SYSTEM), maybeStableDbSystemName(system)), + equalTo(maybeStable(DB_NAME), dbNameLower), + equalTo(DB_USER, emitStableDatabaseSemconv() ? null : username), + equalTo( + DB_CONNECTION_STRING, emitStableDatabaseSemconv() ? null : url), + equalTo(maybeStable(DB_STATEMENT), query), + equalTo(maybeStable(DB_OPERATION), "CREATE TABLE"), + equalTo(maybeStable(DB_SQL_TABLE), table)))); + } + + static Stream preparedStatementUpdateStream() throws SQLException { + return Stream.of( + Arguments.of( + "h2", + new org.h2.Driver().connect(jdbcUrls.get("h2"), null), + null, + "CREATE TABLE PS_H2 (id INTEGER not NULL, PRIMARY KEY ( id ))", + "CREATE TABLE jdbcunittest.PS_H2", + "h2:mem:", + "PS_H2"), + Arguments.of( + "derby", + new EmbeddedDriver().connect(jdbcUrls.get("derby"), null), + "APP", + "CREATE TABLE PS_DERBY (id INTEGER not NULL, PRIMARY KEY ( id ))", + "CREATE TABLE jdbcunittest.PS_DERBY", + "derby:memory:", + "PS_DERBY"), + Arguments.of( + "h2", + cpDatasources.get("tomcat").get("h2").getConnection(), + null, + "CREATE TABLE PS_H2_TOMCAT (id INTEGER not NULL, PRIMARY KEY ( id ))", + "CREATE TABLE jdbcunittest.PS_H2_TOMCAT", + "h2:mem:", + "PS_H2_TOMCAT"), + Arguments.of( + "derby", + cpDatasources.get("tomcat").get("derby").getConnection(), + "APP", + "CREATE TABLE PS_DERBY_TOMCAT (id INTEGER not NULL, PRIMARY KEY ( id ))", + "CREATE TABLE jdbcunittest.PS_DERBY_TOMCAT", + "derby:memory:", + "PS_DERBY_TOMCAT"), + Arguments.of( + "h2", + cpDatasources.get("hikari").get("h2").getConnection(), + null, + "CREATE TABLE PS_H2_HIKARI (id INTEGER not NULL, PRIMARY KEY ( id ))", + "CREATE TABLE jdbcunittest.PS_H2_HIKARI", + "h2:mem:", + "PS_H2_HIKARI"), + Arguments.of( + "derby", + cpDatasources.get("hikari").get("derby").getConnection(), + "APP", + "CREATE TABLE PS_DERBY_HIKARI (id INTEGER not NULL, PRIMARY KEY ( id ))", + "CREATE TABLE jdbcunittest.PS_DERBY_HIKARI", + "derby:memory:", + "PS_DERBY_HIKARI"), + Arguments.of( + "h2", + cpDatasources.get("c3p0").get("h2").getConnection(), + null, + "CREATE TABLE PS_H2_C3P0 (id INTEGER not NULL, PRIMARY KEY ( id ))", + "CREATE TABLE jdbcunittest.PS_H2_C3P0", + "h2:mem:", + "PS_H2_C3P0"), + Arguments.of( + "derby", + cpDatasources.get("c3p0").get("derby").getConnection(), + "APP", + "CREATE TABLE PS_DERBY_C3P0 (id INTEGER not NULL, PRIMARY KEY ( id ))", + "CREATE TABLE jdbcunittest.PS_DERBY_C3P0", + "derby:memory:", + "PS_DERBY_C3P0")); + } + + @ParameterizedTest + @MethodSource("preparedStatementUpdateStream") + void testPreparedStatementUpdate( + String system, + Connection conn, + String username, + String query, + String spanName, + String url, + String table) + throws SQLException { + Connection connection = wrap(conn); + testPreparedStatementUpdateImpl( + system, + connection, + username, + query, + spanName, + url, + table, + statement -> assertThat(statement.executeUpdate()).isEqualTo(0)); + } + + static Stream preparedStatementLargeUpdateStream() throws SQLException { + return Stream.of( + Arguments.of( + "h2", + new org.h2.Driver().connect(jdbcUrls.get("h2"), null), + null, + "CREATE TABLE PS_LARGE_H2 (id INTEGER not NULL, PRIMARY KEY ( id ))", + "CREATE TABLE jdbcunittest.PS_LARGE_H2", + "h2:mem:", + "PS_LARGE_H2"), + Arguments.of( + "h2", + cpDatasources.get("tomcat").get("h2").getConnection(), + null, + "CREATE TABLE PS_LARGE_H2_TOMCAT (id INTEGER not NULL, PRIMARY KEY ( id ))", + "CREATE TABLE jdbcunittest.PS_LARGE_H2_TOMCAT", + "h2:mem:", + "PS_LARGE_H2_TOMCAT"), + Arguments.of( + "h2", + cpDatasources.get("hikari").get("h2").getConnection(), + null, + "CREATE TABLE PS_LARGE_H2_HIKARI (id INTEGER not NULL, PRIMARY KEY ( id ))", + "CREATE TABLE jdbcunittest.PS_LARGE_H2_HIKARI", + "h2:mem:", + "PS_LARGE_H2_HIKARI")); + } + + @ParameterizedTest + @MethodSource("preparedStatementLargeUpdateStream") + void testPreparedStatementLargeUpdate( + String system, + Connection conn, + String username, + String query, + String spanName, + String url, + String table) + throws SQLException { + Connection connection = wrap(conn); + testPreparedStatementUpdateImpl( + system, + connection, + username, + query, + spanName, + url, + table, + statement -> assertThat(statement.executeLargeUpdate()).isEqualTo(0)); + } + + void testPreparedStatementUpdateImpl( + String system, + Connection connection, + String username, + String query, + String spanName, + String url, + String table, + ThrowingConsumer action) + throws SQLException { + String sql = connection.nativeSQL(query); + PreparedStatement statement = connection.prepareStatement(sql); + cleanup.deferCleanup(statement); + testing().runWithSpan("parent", () -> action.accept(statement)); + + testing() + .waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), + span -> + span.hasName(spanName) + .hasKind(SpanKind.CLIENT) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo(maybeStable(DB_SYSTEM), maybeStableDbSystemName(system)), + equalTo(maybeStable(DB_NAME), dbNameLower), + equalTo(DB_USER, emitStableDatabaseSemconv() ? null : username), + equalTo( + DB_CONNECTION_STRING, emitStableDatabaseSemconv() ? null : url), + equalTo(maybeStable(DB_STATEMENT), query), + equalTo(maybeStable(DB_OPERATION), "CREATE TABLE"), + equalTo(maybeStable(DB_SQL_TABLE), table)))); + } + + static Stream connectionConstructorStream() { + return Stream.of( + Arguments.of( + true, + "h2", + new org.h2.Driver(), + "jdbc:h2:mem:" + dbName, + null, + "SELECT 3;", + "SELECT ?;", + "SELECT " + dbNameLower, + "h2:mem:", + null), + Arguments.of( + 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"), + Arguments.of( + false, + "h2", + new org.h2.Driver(), + "jdbc:h2:mem:" + dbName, + null, + "SELECT 3;", + "SELECT ?;", + "SELECT " + dbNameLower, + "h2:mem:", + null), + Arguments.of( + 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")); + } + + @SuppressWarnings("CatchingUnchecked") + @ParameterizedTest + @MethodSource("connectionConstructorStream") + void testConnectionConstructorThrowing( + boolean prepareStatement, + String system, + Driver driver, + String jdbcUrl, + String username, + String query, + String sanitizedQuery, + String spanName, + String url, + String table) + throws SQLException { + Connection connection; + + try { + connection = new TestConnection(true); + } catch (Exception ignored) { + connection = driver.connect(jdbcUrl, null); + } + cleanup.deferCleanup(connection); + Connection finalConnection = connection; + ResultSet rs = + testing() + .runWithSpan( + "parent", + () -> { + if (prepareStatement) { + PreparedStatement stmt = finalConnection.prepareStatement(query); + cleanup.deferCleanup(stmt); + return stmt.executeQuery(); + } else { + Statement stmt = finalConnection.createStatement(); + cleanup.deferCleanup(stmt); + return stmt.executeQuery(query); + } + }); + + rs.next(); + assertThat(rs.getInt(1)).isEqualTo(3); + testing() + .waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), + span -> + span.hasName(spanName) + .hasKind(SpanKind.CLIENT) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo(maybeStable(DB_SYSTEM), maybeStableDbSystemName(system)), + equalTo(maybeStable(DB_NAME), dbNameLower), + equalTo(DB_USER, emitStableDatabaseSemconv() ? null : username), + equalTo( + DB_CONNECTION_STRING, emitStableDatabaseSemconv() ? null : url), + equalTo(maybeStable(DB_STATEMENT), sanitizedQuery), + equalTo(maybeStable(DB_OPERATION), "SELECT"), + equalTo(maybeStable(DB_SQL_TABLE), table)))); + } + + static Stream getConnectionStream() { + return Stream.of( + Arguments.of( + new JdbcDataSource(), + (Consumer) ds -> ((JdbcDataSource) ds).setURL(jdbcUrls.get("h2")), + "h2", + null, + "h2:mem:"), + Arguments.of( + new EmbeddedDataSource(), + (Consumer) + ds -> ((EmbeddedDataSource) ds).setDatabaseName("memory:" + dbName), + "derby", + "APP", + "derby:memory:"), + Arguments.of(cpDatasources.get("hikari").get("h2"), null, "h2", null, "h2:mem:"), + Arguments.of( + cpDatasources.get("hikari").get("derby"), null, "derby", "APP", "derby:memory:"), + Arguments.of(cpDatasources.get("c3p0").get("h2"), null, "h2", null, "h2:mem:"), + Arguments.of( + cpDatasources.get("c3p0").get("derby"), null, "derby", "APP", "derby:memory:")); + } + + @ParameterizedTest(autoCloseArguments = false) + @MethodSource("getConnectionStream") + void testGetConnection( + DataSource ds, Consumer init, String system, String user, String connectionString) + throws SQLException { + // Tomcat's pool doesn't work because the getConnection method is + // implemented in a parent class that doesn't implement DataSource + + if (init != null) { + init.accept(ds); + } + Class originalDatasourceClass = ds.getClass(); + DataSource datasource = wrap(ds); + datasource.getConnection().close(); + assertThat(testing().spans()).noneMatch(span -> span.getName().equals("database.connection")); + + testing().clearData(); + + List attributesAssertions = + codeFunctionAssertions(originalDatasourceClass, "getConnection"); + attributesAssertions.add(equalTo(maybeStable(DB_SYSTEM), maybeStableDbSystemName(system))); + attributesAssertions.add(equalTo(maybeStable(DB_SYSTEM), maybeStableDbSystemName(system))); + attributesAssertions.add(equalTo(DB_USER, emitStableDatabaseSemconv() ? null : user)); + attributesAssertions.add(equalTo(maybeStable(DB_NAME), "jdbcunittest")); + attributesAssertions.add( + equalTo(DB_CONNECTION_STRING, emitStableDatabaseSemconv() ? null : connectionString)); + + testing().runWithSpan("parent", () -> datasource.getConnection().close()); + testing() + .waitAndAssertTraces( + trace -> { + List> assertions = + new ArrayList<>( + asList( + span1 -> span1.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), + span1 -> + span1 + .hasName( + originalDatasourceClass.getSimpleName() + ".getConnection") + .hasKind(SpanKind.INTERNAL) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly(attributesAssertions))); + trace.hasSpansSatisfyingExactly(assertions); + }); + } + + @ParameterizedTest + @DisplayName("test getClientInfo exception") + @ValueSource(strings = "testing 123") + void testGetClientInfoException(String query) throws SQLException { + Connection connection = wrap(new TestConnection("jdbc:testdb://localhost")); + cleanup.deferCleanup(connection); + + Statement statement = + testing() + .runWithSpan( + "parent", + () -> { + Statement stmt = connection.createStatement(); + stmt.executeQuery(query); + return stmt; + }); + cleanup.deferCleanup(statement); + + testing() + .waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), + span -> + span.hasName("DB Query") + .hasKind(SpanKind.CLIENT) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo(maybeStable(DB_SYSTEM), "other_sql"), + equalTo(maybeStable(DB_STATEMENT), "testing ?"), + equalTo( + DB_CONNECTION_STRING, + emitStableDatabaseSemconv() ? null : "testdb://localhost"), + equalTo(SERVER_ADDRESS, "localhost")))); + } + + static Stream spanNameStream() { + return Stream.of( + Arguments.of( + "jdbc:testdb://localhost?databaseName=test", + "SELECT * FROM table", + "SELECT * FROM table", + "SELECT test.table", + "test", + "SELECT", + "table"), + Arguments.of( + "jdbc:testdb://localhost?databaseName=test", + "SELECT 42", + "SELECT ?", + "SELECT test", + "test", + "SELECT", + null), + Arguments.of( + "jdbc:testdb://localhost", + "SELECT * FROM table", + "SELECT * FROM table", + "SELECT table", + null, + "SELECT", + "table"), + Arguments.of( + "jdbc:testdb://localhost?databaseName=test", + "CREATE TABLE table", + "CREATE TABLE table", + "CREATE TABLE test.table", + "test", + "CREATE TABLE", + "table"), + Arguments.of( + "jdbc:testdb://localhost", + "CREATE TABLE table", + "CREATE TABLE table", + "CREATE TABLE table", + null, + "CREATE TABLE", + "table")); + } + + @ParameterizedTest + @MethodSource("spanNameStream") + void testProduceProperSpanName( + String url, + String query, + String sanitizedQuery, + String spanName, + String databaseName, + String operation, + String table) + throws SQLException { + Driver driver = new TestDriver(); + Connection connection = wrap(driver.connect(url, null)); + cleanup.deferCleanup(connection); + + testing() + .runWithSpan( + "parent", + () -> { + Statement statement = connection.createStatement(); + statement.executeQuery(query); + }); + + testing() + .waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), + span -> + span.hasName(spanName) + .hasKind(SpanKind.CLIENT) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo(maybeStable(DB_SYSTEM), "other_sql"), + equalTo(maybeStable(DB_NAME), databaseName), + equalTo( + DB_CONNECTION_STRING, + emitStableDatabaseSemconv() ? null : "testdb://localhost"), + equalTo(maybeStable(DB_STATEMENT), sanitizedQuery), + equalTo(maybeStable(DB_OPERATION), operation), + equalTo(maybeStable(DB_SQL_TABLE), table), + equalTo(SERVER_ADDRESS, "localhost")))); + } + + @ParameterizedTest + @ValueSource(strings = {"hikari", "tomcat", "c3p0"}) + void testConnectionCached(String connectionPoolName) throws SQLException { + String dbType = "hsqldb"; + DataSource ds = wrap(createDs(connectionPoolName, dbType, jdbcUrls.get(dbType))); + cleanup.deferCleanup( + () -> { + if (ds instanceof Closeable) { + ((Closeable) ds).close(); + } + }); + String query = "SELECT 3 FROM INFORMATION_SCHEMA.SYSTEM_USERS"; + int numQueries = 5; + int[] res = new int[numQueries]; + + for (int i = 0; i < numQueries; ++i) { + try (Connection connection = ds.getConnection(); + PreparedStatement statement = connection.prepareStatement(query)) { + ResultSet rs = statement.executeQuery(); + if (rs.next()) { + res[i] = rs.getInt(1); + } else { + res[i] = 0; + } + } + } + + for (int i = 0; i < numQueries; ++i) { + assertThat(res[i]).isEqualTo(3); + } + + List> assertions = new ArrayList<>(); + Consumer traceAssertConsumer = + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("SELECT INFORMATION_SCHEMA.SYSTEM_USERS") + .hasKind(SpanKind.CLIENT) + .hasAttributesSatisfyingExactly( + equalTo(maybeStable(DB_SYSTEM), "hsqldb"), + equalTo(maybeStable(DB_NAME), dbNameLower), + equalTo(DB_USER, emitStableDatabaseSemconv() ? null : "SA"), + equalTo( + DB_CONNECTION_STRING, + emitStableDatabaseSemconv() ? null : "hsqldb:mem:"), + equalTo( + maybeStable(DB_STATEMENT), + "SELECT ? FROM INFORMATION_SCHEMA.SYSTEM_USERS"), + equalTo(maybeStable(DB_OPERATION), "SELECT"), + equalTo(maybeStable(DB_SQL_TABLE), "INFORMATION_SCHEMA.SYSTEM_USERS"))); + for (int i = 0; i < numQueries; i++) { + assertions.add(traceAssertConsumer); + } + + testing().waitAndAssertTraces(assertions); + } + + @FunctionalInterface + public interface ThrowingBiConsumer { + void accept(T t, U u) throws Exception; + } + + static Stream recursiveStatementsStream() { + return Stream.of( + Arguments.of( + "getMetaData() uses Statement, test Statement", + false, + (ThrowingBiConsumer) + (con, query) -> con.createStatement().executeQuery(query)), + Arguments.of( + "getMetaData() uses PreparedStatement, test Statement", + true, + (ThrowingBiConsumer) + (con, query) -> con.createStatement().executeQuery(query)), + Arguments.of( + "getMetaData() uses Statement, test PreparedStatement", + false, + (ThrowingBiConsumer) + (con, query) -> con.prepareStatement(query).executeQuery()), + Arguments.of( + "getMetaData() uses PreparedStatement, test PreparedStatement", + true, + (ThrowingBiConsumer) + (con, query) -> con.prepareStatement(query).executeQuery())); + } + + // regression test for + // https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/2644 + @ParameterizedTest + @MethodSource("recursiveStatementsStream") + void testHandleRecursiveStatements( + String desc, + boolean usePreparedStatementInConnection, + ThrowingBiConsumer executeQueryFunction) + throws Exception { + Connection connection = + wrap(new DbCallingConnection(usePreparedStatementInConnection, "jdbc:testdb://localhost")); + + testing() + .runWithSpan( + "parent", + () -> { + executeQueryFunction.accept(connection, "SELECT * FROM table"); + }); + + testing() + .waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), + span -> + span.hasName("SELECT table") + .hasKind(SpanKind.CLIENT) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo(maybeStable(DB_SYSTEM), "other_sql"), + equalTo( + DB_CONNECTION_STRING, + emitStableDatabaseSemconv() ? null : "testdb://localhost"), + equalTo(maybeStable(DB_STATEMENT), "SELECT * FROM table"), + equalTo(maybeStable(DB_OPERATION), "SELECT"), + equalTo(maybeStable(DB_SQL_TABLE), "table"), + equalTo(SERVER_ADDRESS, "localhost")))); + } + + // regression test for + // https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/6015 + @DisplayName("test proxy statement") + @Test + void testProxyStatement() throws Exception { + Connection connection = wrap(new org.h2.Driver().connect(jdbcUrls.get("h2"), null)); + Statement statement = connection.createStatement(); + cleanup.deferCleanup(statement); + cleanup.deferCleanup(connection); + + Statement proxyStatement = ProxyStatementFactory.proxyStatementWithCustomClassLoader(statement); + ResultSet resultSet = + testing().runWithSpan("parent", () -> proxyStatement.executeQuery("SELECT 3")); + + resultSet.next(); + assertThat(resultSet.getInt(1)).isEqualTo(3); + testing() + .waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), + span -> + span.hasName("SELECT " + dbNameLower) + .hasKind(SpanKind.CLIENT) + .hasParent(trace.getSpan(0)))); + } + + // regression test for + // https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/9359 + @DisplayName("test proxy prepared statement") + @Test + void testProxyPreparedStatement() throws SQLException { + Connection connection = wrap(new org.h2.Driver().connect(jdbcUrls.get("h2"), null)); + PreparedStatement statement = connection.prepareStatement("SELECT 3"); + cleanup.deferCleanup(statement); + cleanup.deferCleanup(connection); + + PreparedStatement proxyStatement = ProxyStatementFactory.proxyPreparedStatement(statement); + ResultSet resultSet = testing().runWithSpan("parent", () -> proxyStatement.executeQuery()); + + resultSet.next(); + assertThat(resultSet.getInt(1)).isEqualTo(3); + testing() + .waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), + span -> + span.hasName("SELECT " + dbNameLower) + .hasKind(SpanKind.CLIENT) + .hasParent(trace.getSpan(0)))); + } + + static Stream batchStream() throws SQLException { + return Stream.of( + Arguments.of("h2", new org.h2.Driver().connect(jdbcUrls.get("h2"), null), null, "h2:mem:"), + Arguments.of( + "derby", + new EmbeddedDriver().connect(jdbcUrls.get("derby"), null), + "APP", + "derby:memory:"), + Arguments.of( + "hsqldb", new JDBCDriver().connect(jdbcUrls.get("hsqldb"), null), "SA", "hsqldb:mem:")); + } + + @ParameterizedTest + @MethodSource("batchStream") + void testBatch(String system, Connection connection, String username, String url) + throws SQLException { + testBatchImpl( + system, + wrap(connection), + username, + url, + "simple_batch_test", + statement -> assertThat(statement.executeBatch()).isEqualTo(new int[] {1, 1})); + } + + @ParameterizedTest + @MethodSource("batchStream") + void testLargeBatch(String system, Connection connection, String username, String url) + throws SQLException { + // derby and hsqldb used in this test don't support executeLargeBatch + assumeTrue("h2".equals(system)); + + testBatchImpl( + system, + wrap(connection), + username, + url, + "simple_batch_test_large", + statement -> assertThat(statement.executeLargeBatch()).isEqualTo(new long[] {1, 1})); + } + + private void testBatchImpl( + String system, + Connection connection, + String username, + String url, + String tableName, + ThrowingConsumer action) + throws SQLException { + Statement createTable = connection.createStatement(); + createTable.execute("CREATE TABLE " + tableName + " (id INTEGER not NULL, PRIMARY KEY ( id ))"); + cleanup.deferCleanup(createTable); + + testing().waitForTraces(1); + testing().clearData(); + + Statement statement = connection.createStatement(); + cleanup.deferCleanup(statement); + statement.addBatch("INSERT INTO non_existent_table VALUES(1)"); + statement.clearBatch(); + statement.addBatch("INSERT INTO " + tableName + " VALUES(1)"); + statement.addBatch("INSERT INTO " + tableName + " VALUES(2)"); + testing().runWithSpan("parent", () -> action.accept(statement)); + + testing() + .waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), + span -> + span.hasName( + emitStableDatabaseSemconv() + ? "BATCH INSERT jdbcunittest." + tableName + : "jdbcunittest") + .hasKind(SpanKind.CLIENT) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo(maybeStable(DB_SYSTEM), maybeStableDbSystemName(system)), + equalTo(maybeStable(DB_NAME), dbNameLower), + equalTo(DB_USER, emitStableDatabaseSemconv() ? null : username), + equalTo( + DB_CONNECTION_STRING, emitStableDatabaseSemconv() ? null : url), + equalTo( + maybeStable(DB_STATEMENT), + emitStableDatabaseSemconv() + ? "INSERT INTO " + tableName + " VALUES(?)" + : null), + equalTo( + maybeStable(DB_OPERATION), + emitStableDatabaseSemconv() ? "BATCH INSERT" : null), + equalTo( + maybeStable(DB_SQL_TABLE), + emitStableDatabaseSemconv() ? tableName : null), + equalTo( + DB_OPERATION_BATCH_SIZE, + emitStableDatabaseSemconv() ? 2L : null)))); + } + + @ParameterizedTest + @MethodSource("batchStream") + void testMultiBatch(String system, Connection conn, String username, String url) + throws SQLException { + Connection connection = wrap(conn); + String tableName1 = "multi_batch_test_1"; + String tableName2 = "multi_batch_test_2"; + Statement createTable1 = connection.createStatement(); + createTable1.execute( + "CREATE TABLE " + tableName1 + " (id INTEGER not NULL, PRIMARY KEY ( id ))"); + cleanup.deferCleanup(createTable1); + Statement createTable2 = connection.createStatement(); + createTable2.execute( + "CREATE TABLE " + tableName2 + " (id INTEGER not NULL, PRIMARY KEY ( id ))"); + cleanup.deferCleanup(createTable1); + + testing().waitForTraces(2); + testing().clearData(); + + Statement statement = connection.createStatement(); + cleanup.deferCleanup(statement); + statement.addBatch("INSERT INTO " + tableName1 + " VALUES(1)"); + statement.addBatch("INSERT INTO " + tableName2 + " VALUES(2)"); + testing() + .runWithSpan( + "parent", () -> assertThat(statement.executeBatch()).isEqualTo(new int[] {1, 1})); + + testing() + .waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), + span -> + span.hasName( + emitStableDatabaseSemconv() + ? "BATCH INSERT jdbcunittest" + : "jdbcunittest") + .hasKind(SpanKind.CLIENT) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo(maybeStable(DB_SYSTEM), maybeStableDbSystemName(system)), + equalTo(maybeStable(DB_NAME), dbNameLower), + equalTo(DB_USER, emitStableDatabaseSemconv() ? null : username), + equalTo( + DB_CONNECTION_STRING, emitStableDatabaseSemconv() ? null : url), + equalTo( + maybeStable(DB_STATEMENT), + emitStableDatabaseSemconv() + ? "INSERT INTO " + + tableName1 + + " VALUES(?); INSERT INTO multi_batch_test_2 VALUES(?)" + : null), + equalTo( + maybeStable(DB_OPERATION), + emitStableDatabaseSemconv() ? "BATCH INSERT" : null), + equalTo( + DB_OPERATION_BATCH_SIZE, + emitStableDatabaseSemconv() ? 2L : null)))); + } + + @ParameterizedTest + @MethodSource("batchStream") + void testSingleItemBatch(String system, Connection conn, String username, String url) + throws SQLException { + Connection connection = wrap(conn); + String tableName = "single_item_batch_test"; + Statement createTable = connection.createStatement(); + createTable.execute("CREATE TABLE " + tableName + " (id INTEGER not NULL, PRIMARY KEY ( id ))"); + cleanup.deferCleanup(createTable); + + testing().waitForTraces(1); + testing().clearData(); + + Statement statement = connection.createStatement(); + cleanup.deferCleanup(statement); + statement.addBatch("INSERT INTO " + tableName + " VALUES(1)"); + testing() + .runWithSpan("parent", () -> assertThat(statement.executeBatch()).isEqualTo(new int[] {1})); + + testing() + .waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), + span -> + span.hasName("INSERT jdbcunittest." + tableName) + .hasKind(SpanKind.CLIENT) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo(maybeStable(DB_SYSTEM), maybeStableDbSystemName(system)), + equalTo(maybeStable(DB_NAME), dbNameLower), + equalTo(DB_USER, emitStableDatabaseSemconv() ? null : username), + equalTo( + DB_CONNECTION_STRING, emitStableDatabaseSemconv() ? null : url), + equalTo( + maybeStable(DB_STATEMENT), + "INSERT INTO " + tableName + " VALUES(?)"), + equalTo(maybeStable(DB_OPERATION), "INSERT"), + equalTo(maybeStable(DB_SQL_TABLE), tableName)))); + } + + @ParameterizedTest + @MethodSource("batchStream") + void testPreparedBatch(String system, Connection conn, String username, String url) + throws SQLException { + Connection connection = wrap(conn); + String tableName = "prepared_batch_test"; + Statement createTable = connection.createStatement(); + createTable.execute("CREATE TABLE " + tableName + " (id INTEGER not NULL, PRIMARY KEY ( id ))"); + cleanup.deferCleanup(createTable); + + testing().waitForTraces(1); + testing().clearData(); + + PreparedStatement statement = + connection.prepareStatement("INSERT INTO " + tableName + " VALUES(?)"); + cleanup.deferCleanup(statement); + statement.setInt(1, 1); + statement.addBatch(); + statement.clearBatch(); + statement.setInt(1, 1); + statement.addBatch(); + statement.setInt(1, 2); + statement.addBatch(); + testing() + .runWithSpan( + "parent", () -> assertThat(statement.executeBatch()).isEqualTo(new int[] {1, 1})); + + testing() + .waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), + span -> + span.hasName( + emitStableDatabaseSemconv() + ? "BATCH INSERT jdbcunittest." + tableName + : "INSERT jdbcunittest." + tableName) + .hasKind(SpanKind.CLIENT) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo(maybeStable(DB_SYSTEM), maybeStableDbSystemName(system)), + equalTo(maybeStable(DB_NAME), dbNameLower), + equalTo(DB_USER, emitStableDatabaseSemconv() ? null : username), + equalTo( + DB_CONNECTION_STRING, emitStableDatabaseSemconv() ? null : url), + equalTo( + maybeStable(DB_STATEMENT), + "INSERT INTO " + tableName + " VALUES(?)"), + equalTo( + maybeStable(DB_OPERATION), + emitStableDatabaseSemconv() ? "BATCH INSERT" : "INSERT"), + equalTo(maybeStable(DB_SQL_TABLE), tableName), + equalTo( + DB_OPERATION_BATCH_SIZE, + emitStableDatabaseSemconv() ? 2L : null)))); + } + + // test that sqlcommenter is not enabled by default + @Test + void testSqlCommenterNotEnabled() throws SQLException { + List executedSql = new ArrayList<>(); + Connection connection = new TestConnection(executedSql::add); + Statement statement = connection.createStatement(); + + cleanup.deferCleanup(statement); + cleanup.deferCleanup(connection); + + String query = "SELECT 1"; + testing().runWithSpan("parent", () -> statement.execute(query)); + + assertThat(executedSql).hasSize(1); + assertThat(executedSql.get(0)).isEqualTo(query); + } + + @ParameterizedTest + @MethodSource("transactionOperationsStream") + void testCommitTransaction(String system, Connection conn, String username, String url) + throws SQLException { + Connection connection = wrap(conn); + + String tableName = "TXN_COMMIT_TEST_" + system.toUpperCase(Locale.ROOT); + Statement createTable = connection.createStatement(); + createTable.execute("CREATE TABLE " + tableName + " (id INTEGER not NULL, PRIMARY KEY ( id ))"); + cleanup.deferCleanup(createTable); + + connection.setAutoCommit(false); + + testing().waitForTraces(1); + testing().clearData(); + + Statement insertStatement = connection.createStatement(); + cleanup.deferCleanup(insertStatement); + + testing() + .runWithSpan( + "parent", + () -> { + insertStatement.executeUpdate("INSERT INTO " + tableName + " VALUES(1)"); + connection.commit(); + }); + + testing() + .waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), + span -> + span.hasName("INSERT jdbcunittest." + tableName) + .hasKind(SpanKind.CLIENT) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo(maybeStable(DB_SYSTEM), maybeStableDbSystemName(system)), + equalTo(maybeStable(DB_NAME), dbNameLower), + equalTo(DB_USER, emitStableDatabaseSemconv() ? null : username), + equalTo( + DB_CONNECTION_STRING, emitStableDatabaseSemconv() ? null : url), + equalTo( + maybeStable(DB_STATEMENT), + "INSERT INTO " + tableName + " VALUES(?)"), + equalTo(maybeStable(DB_OPERATION), "INSERT"), + equalTo(maybeStable(DB_SQL_TABLE), tableName)), + span -> + span.hasName("COMMIT") + .hasKind(SpanKind.CLIENT) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo(maybeStable(DB_SYSTEM), maybeStableDbSystemName(system)), + equalTo(maybeStable(DB_NAME), dbNameLower), + equalTo(maybeStable(DB_OPERATION), "COMMIT"), + equalTo(DB_USER, emitStableDatabaseSemconv() ? null : username), + equalTo( + DB_CONNECTION_STRING, + emitStableDatabaseSemconv() ? null : url)))); + } + + @ParameterizedTest + @MethodSource("transactionOperationsStream") + void testRollbackTransaction(String system, Connection conn, String username, String url) + throws SQLException { + Connection connection = wrap(conn); + + String tableName = "TXN_ROLLBACK_TEST_" + system.toUpperCase(Locale.ROOT); + Statement createTable = connection.createStatement(); + createTable.execute("CREATE TABLE " + tableName + " (id INTEGER not NULL, PRIMARY KEY ( id ))"); + cleanup.deferCleanup(createTable); + + connection.setAutoCommit(false); + + testing().waitForTraces(1); + testing().clearData(); + + Statement insertStatement = connection.createStatement(); + cleanup.deferCleanup(insertStatement); + + testing() + .runWithSpan( + "parent", + () -> { + insertStatement.executeUpdate("INSERT INTO " + tableName + " VALUES(1)"); + connection.rollback(); + }); + + testing() + .waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), + span -> + span.hasName("INSERT jdbcunittest." + tableName) + .hasKind(SpanKind.CLIENT) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo(maybeStable(DB_SYSTEM), maybeStableDbSystemName(system)), + equalTo(maybeStable(DB_NAME), dbNameLower), + equalTo(DB_USER, emitStableDatabaseSemconv() ? null : username), + equalTo( + DB_CONNECTION_STRING, emitStableDatabaseSemconv() ? null : url), + equalTo( + maybeStable(DB_STATEMENT), + "INSERT INTO " + tableName + " VALUES(?)"), + equalTo(maybeStable(DB_OPERATION), "INSERT"), + equalTo(maybeStable(DB_SQL_TABLE), tableName)), + span -> + span.hasName("ROLLBACK") + .hasKind(SpanKind.CLIENT) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo(maybeStable(DB_SYSTEM), maybeStableDbSystemName(system)), + equalTo(maybeStable(DB_NAME), dbNameLower), + equalTo(maybeStable(DB_OPERATION), "ROLLBACK"), + equalTo(DB_USER, emitStableDatabaseSemconv() ? null : username), + equalTo( + DB_CONNECTION_STRING, + emitStableDatabaseSemconv() ? null : url)))); + } + + static Stream transactionOperationsStream() throws SQLException { + return Stream.of( + Arguments.of("h2", new org.h2.Driver().connect(jdbcUrls.get("h2"), null), null, "h2:mem:"), + Arguments.of( + "derby", + new EmbeddedDriver().connect(jdbcUrls.get("derby"), null), + "APP", + "derby:memory:"), + Arguments.of( + "hsqldb", new JDBCDriver().connect(jdbcUrls.get("hsqldb"), null), "SA", "hsqldb:mem:")); + } + + private PreparedStatement wrapPreparedStatement(PreparedStatement statement) { + return ProxyStatementFactory.proxyPreparedStatement( + (proxy, method, args) -> { + if ("isWrapperFor".equals(method.getName()) + && args.length == 1 + && args[0] == PreparedStatement.class) { + return true; + } + if ("unwrap".equals(method.getName()) + && args.length == 1 + && args[0] == PreparedStatement.class) { + return statement; + } + return testing().runWithSpan("wrapper", () -> method.invoke(statement, args)); + }); + } + + // test that tracing does not start from a wrapper + // regression test for + // https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/14733 + @Test + void testPreparedStatementWrapper() throws SQLException { + Connection connection = wrap(new org.h2.Driver().connect(jdbcUrls.get("h2"), null)); + Connection proxyConnection = + ProxyStatementFactory.proxy( + Connection.class, + (proxy, method, args) -> { + // we don't implement unwrapping here as that would also cause the executeQuery + // instrumentation to get skipped for the prepared statement wrapper + if ("prepareStatement".equals(method.getName())) { + return wrapPreparedStatement((PreparedStatement) method.invoke(connection, args)); + } + return method.invoke(connection, args); + }); + PreparedStatement statement = proxyConnection.prepareStatement("SELECT 3"); + cleanup.deferCleanup(statement); + cleanup.deferCleanup(connection); + + ResultSet resultSet = testing().runWithSpan("parent", () -> statement.executeQuery()); + + resultSet.next(); + assertThat(resultSet.getInt(1)).isEqualTo(3); + testing() + .waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), + span -> + span.hasName("wrapper") + .hasKind(SpanKind.INTERNAL) + .hasParent(trace.getSpan(0)), + span -> + span.hasName("SELECT " + dbNameLower) + .hasKind(SpanKind.CLIENT) + .hasParent(trace.getSpan(1)))); + } + + // test that tracing does not start from a wrapper + // regression test for + // https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/14733 + @Test + void testStatementWrapper() throws SQLException { + Connection connection = wrap(new org.h2.Driver().connect(jdbcUrls.get("h2"), null)); + Statement statement = connection.createStatement(); + cleanup.deferCleanup(statement); + cleanup.deferCleanup(connection); + + Statement proxyStatement = + ProxyStatementFactory.proxyStatement( + (proxy, method, args) -> { + if ("isWrapperFor".equals(method.getName()) + && args.length == 1 + && args[0] == Statement.class) { + return true; + } + if ("unwrap".equals(method.getName()) + && args.length == 1 + && args[0] == Statement.class) { + return statement; + } + return testing().runWithSpan("wrapper", () -> method.invoke(statement, args)); + }); + ResultSet resultSet = + testing().runWithSpan("parent", () -> proxyStatement.executeQuery("SELECT 3")); + + resultSet.next(); + assertThat(resultSet.getInt(1)).isEqualTo(3); + testing() + .waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), + span -> + span.hasName("wrapper") + .hasKind(SpanKind.INTERNAL) + .hasParent(trace.getSpan(0)), + span -> + span.hasName("SELECT " + dbNameLower) + .hasKind(SpanKind.CLIENT) + .hasParent(trace.getSpan(1)))); + } +} diff --git a/instrumentation/jdbc/testing/src/main/java/io/opentelemetry/instrumentation/jdbc/testing/AbstractPreparedStatementParametersTest.java b/instrumentation/jdbc/testing/src/main/java/io/opentelemetry/instrumentation/jdbc/testing/AbstractPreparedStatementParametersTest.java new file mode 100644 index 000000000000..e01baa928e00 --- /dev/null +++ b/instrumentation/jdbc/testing/src/main/java/io/opentelemetry/instrumentation/jdbc/testing/AbstractPreparedStatementParametersTest.java @@ -0,0 +1,616 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.jdbc.testing; + +import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableDatabaseSemconv; +import static io.opentelemetry.instrumentation.testing.junit.db.SemconvStabilityUtil.maybeStable; +import static io.opentelemetry.instrumentation.testing.junit.db.SemconvStabilityUtil.maybeStableDbSystemName; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; +import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_CONNECTION_STRING; +import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_NAME; +import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_OPERATION; +import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_QUERY_PARAMETER; +import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_SQL_TABLE; +import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_STATEMENT; +import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_SYSTEM; +import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_USER; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.instrumentation.testing.internal.AutoCleanupExtension; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import java.math.BigDecimal; +import java.sql.Connection; +import java.sql.Date; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Time; +import java.sql.Timestamp; +import java.sql.Types; +import java.util.Calendar; +import java.util.Locale; +import java.util.Map; +import java.util.Properties; +import java.util.stream.Stream; +import org.apache.derby.jdbc.EmbeddedDriver; +import org.hsqldb.jdbc.JDBCDriver; +import org.junit.jupiter.api.Assumptions; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +@SuppressWarnings("deprecation") // using deprecated semconv +public abstract class AbstractPreparedStatementParametersTest { + + @RegisterExtension static final AutoCleanupExtension cleanup = AutoCleanupExtension.create(); + + protected abstract InstrumentationExtension testing(); + + protected Connection wrap(Connection connection) throws SQLException { + return connection; + } + + private static final String dbName = "jdbcUnitTest"; + private static final String dbNameLower = dbName.toLowerCase(Locale.ROOT); + + private static final Map jdbcUrls = + ImmutableMap.of( + "h2", "jdbc:h2:mem:" + dbName, + "derby", "jdbc:derby:memory:" + dbName, + "hsqldb", "jdbc:hsqldb:mem:" + dbName); + private static final Map jdbcUserNames = Maps.newHashMap(); + private static final Properties connectionProps = new Properties(); + + static { + jdbcUserNames.put("derby", "APP"); + jdbcUserNames.put("h2", null); + jdbcUserNames.put("hsqldb", "SA"); + + connectionProps.put("databaseName", "someDb"); + connectionProps.put("OPEN_NEW", "true"); // So H2 doesn't complain about username/password. + connectionProps.put("create", "true"); + } + + static Stream preparedStatementStream() throws SQLException { + return Stream.of( + Arguments.of( + "h2", + new org.h2.Driver().connect(jdbcUrls.get("h2"), null), + null, + "SELECT 3, ?", + "SELECT 3, ?", + "SELECT " + dbNameLower, + "h2:mem:", + null), + Arguments.of( + "derby", + new EmbeddedDriver().connect(jdbcUrls.get("derby"), connectionProps), + "APP", + "SELECT 3 FROM SYSIBM.SYSDUMMY1 WHERE IBMREQD=? OR 1=1", + "SELECT 3 FROM SYSIBM.SYSDUMMY1 WHERE IBMREQD=? OR 1=1", + "SELECT SYSIBM.SYSDUMMY1", + "derby:memory:", + "SYSIBM.SYSDUMMY1"), + Arguments.of( + "hsqldb", + new JDBCDriver().connect(jdbcUrls.get("hsqldb"), null), + "SA", + "SELECT 3 FROM INFORMATION_SCHEMA.SYSTEM_USERS WHERE USER_NAME=? OR 1=1", + "SELECT 3 FROM INFORMATION_SCHEMA.SYSTEM_USERS WHERE USER_NAME=? OR 1=1", + "SELECT INFORMATION_SCHEMA.SYSTEM_USERS", + "hsqldb:mem:", + "INFORMATION_SCHEMA.SYSTEM_USERS")); + } + + @ParameterizedTest + @MethodSource("preparedStatementStream") + void testBooleanPreparedStatementParameter( + String system, + Connection connection, + String username, + String query, + String sanitizedQuery, + String spanName, + String url, + String table) + throws SQLException { + test( + system, + connection, + username, + query, + sanitizedQuery, + spanName, + url, + table, + statement -> statement.setBoolean(1, true), + "true"); + } + + @ParameterizedTest + @MethodSource("preparedStatementStream") + void testShortPreparedStatementParameter( + String system, + Connection connection, + String username, + String query, + String sanitizedQuery, + String spanName, + String url, + String table) + throws SQLException { + test( + system, + connection, + username, + query, + sanitizedQuery, + spanName, + url, + table, + statement -> statement.setShort(1, (short) 0), + "0"); + } + + @ParameterizedTest + @MethodSource("preparedStatementStream") + void testIntPreparedStatementParameter( + String system, + Connection connection, + String username, + String query, + String sanitizedQuery, + String spanName, + String url, + String table) + throws SQLException { + test( + system, + connection, + username, + query, + sanitizedQuery, + spanName, + url, + table, + statement -> statement.setInt(1, 0), + "0"); + } + + @ParameterizedTest + @MethodSource("preparedStatementStream") + void testLongPreparedStatementParameter( + String system, + Connection connection, + String username, + String query, + String sanitizedQuery, + String spanName, + String url, + String table) + throws SQLException { + test( + system, + connection, + username, + query, + sanitizedQuery, + spanName, + url, + table, + statement -> statement.setLong(1, 0), + "0"); + } + + @ParameterizedTest + @MethodSource("preparedStatementStream") + void testFloatPreparedStatementParameter( + String system, + Connection connection, + String username, + String query, + String sanitizedQuery, + String spanName, + String url, + String table) + throws SQLException { + test( + system, + connection, + username, + query, + sanitizedQuery, + spanName, + url, + table, + statement -> statement.setFloat(1, 0.1f), + "0.1"); + } + + @ParameterizedTest + @MethodSource("preparedStatementStream") + void testDoublePreparedStatementParameter( + String system, + Connection connection, + String username, + String query, + String sanitizedQuery, + String spanName, + String url, + String table) + throws SQLException { + test( + system, + connection, + username, + query, + sanitizedQuery, + spanName, + url, + table, + statement -> statement.setDouble(1, 0.1), + "0.1"); + } + + @ParameterizedTest + @MethodSource("preparedStatementStream") + void testBigDecimalPreparedStatementParameter( + String system, + Connection connection, + String username, + String query, + String sanitizedQuery, + String spanName, + String url, + String table) + throws SQLException { + test( + system, + connection, + username, + query, + sanitizedQuery, + spanName, + url, + table, + statement -> statement.setBigDecimal(1, BigDecimal.ZERO), + "0"); + } + + @ParameterizedTest + @MethodSource("preparedStatementStream") + void testStringPreparedStatementParameter( + String system, + Connection connection, + String username, + String query, + String sanitizedQuery, + String spanName, + String url, + String table) + throws SQLException { + test( + system, + connection, + username, + query, + sanitizedQuery, + spanName, + url, + table, + statement -> statement.setString(1, "S"), + "S"); + } + + @ParameterizedTest + @MethodSource("preparedStatementStream") + void testObjectPreparedStatementParameter( + String system, + Connection connection, + String username, + String query, + String sanitizedQuery, + String spanName, + String url, + String table) + throws SQLException { + test( + system, + connection, + username, + query, + sanitizedQuery, + spanName, + url, + table, + statement -> statement.setObject(1, "S"), + "S"); + } + + @ParameterizedTest + @MethodSource("preparedStatementStream") + void testObjectWithTypePreparedStatementParameter( + String system, + Connection connection, + String username, + String query, + String sanitizedQuery, + String spanName, + String url, + String table) + throws SQLException { + // we are using old database drivers that don't support the tested setObject method + Assumptions.assumeTrue(Boolean.getBoolean("testLatestDeps")); + + test( + system, + connection, + username, + query, + sanitizedQuery, + spanName, + url, + table, + statement -> statement.setObject(1, "S", Types.CHAR), + "S"); + } + + @ParameterizedTest + @MethodSource("preparedStatementStream") + void testDate2PreparedStatementParameter( + String system, + Connection connection, + String username, + String query, + String sanitizedQuery, + String spanName, + String url, + String table) + throws SQLException { + String updatedQuery = query.replace("USER_NAME=?", "CURDATE()=?"); + String updatedQuerySanitized = sanitizedQuery.replace("USER_NAME=?", "CURDATE()=?"); + + test( + system, + connection, + username, + updatedQuery, + updatedQuerySanitized, + spanName, + url, + table, + statement -> statement.setDate(1, Date.valueOf("2000-01-01")), + "2000-01-01"); + } + + @ParameterizedTest + @MethodSource("preparedStatementStream") + void testDate3PreparedStatementParameter( + String system, + Connection connection, + String username, + String query, + String sanitizedQuery, + String spanName, + String url, + String table) + throws SQLException { + String updatedQuery = query.replace("USER_NAME=?", "CURDATE()=?"); + String updatedQuerySanitized = sanitizedQuery.replace("USER_NAME=?", "CURDATE()=?"); + + test( + system, + connection, + username, + updatedQuery, + updatedQuerySanitized, + spanName, + url, + table, + statement -> statement.setDate(1, Date.valueOf("2000-01-01"), Calendar.getInstance()), + "2000-01-01"); + } + + @ParameterizedTest + @MethodSource("preparedStatementStream") + void testTime2PreparedStatementParameter( + String system, + Connection connection, + String username, + String query, + String sanitizedQuery, + String spanName, + String url, + String table) + throws SQLException { + String updatedQuery = query.replace("USER_NAME=?", "CURTIME()=?"); + String updatedQuerySanitized = sanitizedQuery.replace("USER_NAME=?", "CURTIME()=?"); + + test( + system, + connection, + username, + updatedQuery, + updatedQuerySanitized, + spanName, + url, + table, + statement -> statement.setTime(1, Time.valueOf("00:00:00")), + "00:00:00"); + } + + @ParameterizedTest + @MethodSource("preparedStatementStream") + void testTime3PreparedStatementParameter( + String system, + Connection connection, + String username, + String query, + String sanitizedQuery, + String spanName, + String url, + String table) + throws SQLException { + String updatedQuery = query.replace("USER_NAME=?", "CURTIME()=?"); + String updatedQuerySanitized = sanitizedQuery.replace("USER_NAME=?", "CURTIME()=?"); + + test( + system, + connection, + username, + updatedQuery, + updatedQuerySanitized, + spanName, + url, + table, + statement -> statement.setTime(1, Time.valueOf("00:00:00"), Calendar.getInstance()), + "00:00:00"); + } + + @ParameterizedTest + @MethodSource("preparedStatementStream") + void testTimestamp2PreparedStatementParameter( + String system, + Connection connection, + String username, + String query, + String sanitizedQuery, + String spanName, + String url, + String table) + throws SQLException { + String updatedQuery = query.replace("USER_NAME=?", "NOW()=?"); + String updatedQuerySanitized = sanitizedQuery.replace("USER_NAME=?", "NOW()=?"); + + test( + system, + connection, + username, + updatedQuery, + updatedQuerySanitized, + spanName, + url, + table, + statement -> statement.setTimestamp(1, Timestamp.valueOf("2000-01-01 00:00:00")), + "2000-01-01 00:00:00.0"); + } + + @ParameterizedTest + @MethodSource("preparedStatementStream") + void testTimestamp3PreparedStatementParameter( + String system, + Connection connection, + String username, + String query, + String sanitizedQuery, + String spanName, + String url, + String table) + throws SQLException { + String updatedQuery = query.replace("USER_NAME=?", "NOW()=?"); + String updatedQuerySanitized = sanitizedQuery.replace("USER_NAME=?", "NOW()=?"); + + test( + system, + connection, + username, + updatedQuery, + updatedQuerySanitized, + spanName, + url, + table, + statement -> + statement.setTimestamp( + 1, Timestamp.valueOf("2000-01-01 00:00:00"), Calendar.getInstance()), + "2000-01-01 00:00:00.0"); + } + + @ParameterizedTest + @MethodSource("preparedStatementStream") + void testNstringPreparedStatementParameter( + String system, + Connection connection, + String username, + String query, + String sanitizedQuery, + String spanName, + String url, + String table) + throws SQLException { + Assumptions.assumeFalse(system.equalsIgnoreCase("derby")); + + test( + system, + connection, + username, + query, + sanitizedQuery, + spanName, + url, + table, + statement -> statement.setNString(1, "S"), + "S"); + } + + private void test( + String system, + Connection connection, + String username, + String query, + String sanitizedQuery, + String spanName, + String url, + String table, + ThrowingConsumer setParameter, + String expectedParameterValue) + throws SQLException { + PreparedStatement statement = wrap(connection).prepareStatement(query); + cleanup.deferCleanup(statement); + + ResultSet resultSet = + testing() + .runWithSpan( + "parent", + () -> { + setParameter.accept(statement); + statement.execute(); + return statement.getResultSet(); + }); + + resultSet.next(); + assertThat(resultSet.getInt(1)).isEqualTo(3); + + testing() + .waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), + span -> + span.hasName(spanName) + .hasKind(SpanKind.CLIENT) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo(maybeStable(DB_SYSTEM), maybeStableDbSystemName(system)), + equalTo(maybeStable(DB_NAME), dbNameLower), + equalTo(DB_USER, emitStableDatabaseSemconv() ? null : username), + equalTo( + DB_CONNECTION_STRING, emitStableDatabaseSemconv() ? null : url), + equalTo(maybeStable(DB_STATEMENT), sanitizedQuery), + equalTo(maybeStable(DB_OPERATION), "SELECT"), + equalTo(maybeStable(DB_SQL_TABLE), table), + equalTo( + DB_QUERY_PARAMETER.getAttributeKey("0"), + expectedParameterValue)))); + } + + public interface ThrowingConsumer { + void accept(T t) throws E; + } +} diff --git a/instrumentation/jdbc/testing/src/main/java/io/opentelemetry/instrumentation/jdbc/testing/AbstractSqlCommenterTest.java b/instrumentation/jdbc/testing/src/main/java/io/opentelemetry/instrumentation/jdbc/testing/AbstractSqlCommenterTest.java new file mode 100644 index 000000000000..fcb0bcf5d535 --- /dev/null +++ b/instrumentation/jdbc/testing/src/main/java/io/opentelemetry/instrumentation/jdbc/testing/AbstractSqlCommenterTest.java @@ -0,0 +1,190 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.jdbc.testing; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.opentelemetry.instrumentation.api.internal.SemconvStability; +import io.opentelemetry.instrumentation.jdbc.TestConnection; +import io.opentelemetry.instrumentation.testing.internal.AutoCleanupExtension; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +public abstract class AbstractSqlCommenterTest { + @RegisterExtension static final AutoCleanupExtension cleanup = AutoCleanupExtension.create(); + + protected abstract InstrumentationExtension testing(); + + protected Connection wrap(Connection connection) throws SQLException { + return connection; + } + + @Test + void testSqlCommenterStatement() throws SQLException { + List executedSql = new ArrayList<>(); + Connection connection = wrap(new TestConnection(executedSql::add)); + Statement statement = connection.createStatement(); + + cleanup.deferCleanup(statement); + cleanup.deferCleanup(connection); + + String query = "SELECT 1"; + testing().runWithSpan("parent", () -> statement.execute(query)); + + testing() + .waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasNoParent(), + span -> span.hasName("SELECT dbname").hasParent(trace.getSpan(0)))); + + assertThat(executedSql).hasSize(1); + assertThat(executedSql.get(0)).contains(query).contains("traceparent"); + } + + @ParameterizedTest + @ValueSource(booleans = {true, false}) + void testSqlCommenterStatementUpdate(boolean largeUpdate) throws SQLException { + List executedSql = new ArrayList<>(); + Connection connection = wrap(new TestConnection(executedSql::add)); + Statement statement = connection.createStatement(); + + cleanup.deferCleanup(statement); + cleanup.deferCleanup(connection); + + String query = "INSERT INTO test VALUES(1)"; + testing() + .runWithSpan( + "parent", + () -> { + if (largeUpdate) { + statement.executeLargeUpdate(query); + } else { + statement.execute(query); + } + }); + + testing() + .waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasNoParent(), + span -> span.hasName("INSERT dbname.test").hasParent(trace.getSpan(0)))); + + assertThat(executedSql).hasSize(1); + assertThat(executedSql.get(0)).contains(query).contains("traceparent"); + } + + @ParameterizedTest + @ValueSource(booleans = {true, false}) + void testSqlCommenterStatementBatch(boolean largeUpdate) throws SQLException { + List executedSql = new ArrayList<>(); + Connection connection = wrap(new TestConnection(executedSql::add)); + Statement statement = connection.createStatement(); + + cleanup.deferCleanup(statement); + cleanup.deferCleanup(connection); + + testing() + .runWithSpan( + "parent", + () -> { + statement.addBatch("INSERT INTO test VALUES(1)"); + statement.addBatch("INSERT INTO test VALUES(2)"); + if (largeUpdate) { + statement.executeLargeBatch(); + } else { + statement.executeBatch(); + } + }); + + testing() + .waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasNoParent(), + span -> + span.hasName( + SemconvStability.emitStableDatabaseSemconv() + ? "BATCH INSERT dbname.test" + : "dbname") + .hasParent(trace.getSpan(0)))); + + assertThat(executedSql).hasSize(2); + assertThat(executedSql.get(0)).contains("INSERT INTO test VALUES(1)").contains("traceparent"); + assertThat(executedSql.get(1)).contains("INSERT INTO test VALUES(2)").contains("traceparent"); + } + + @Test + void testSqlCommenterPreparedStatement() throws SQLException { + List executedSql = new ArrayList<>(); + Connection connection = wrap(new TestConnection(executedSql::add)); + + String query = "SELECT 1"; + testing() + .runWithSpan( + "parent", + () -> { + PreparedStatement statement = connection.prepareStatement(query); + cleanup.deferCleanup(statement); + cleanup.deferCleanup(connection); + + statement.execute(); + }); + + testing() + .waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasNoParent(), + span -> span.hasName("SELECT dbname").hasParent(trace.getSpan(0)))); + + assertThat(executedSql).hasSize(1); + assertThat(executedSql.get(0)).contains(query).contains("traceparent"); + } + + @ParameterizedTest + @ValueSource(booleans = {true, false}) + void testSqlCommenterPreparedStatementUpdate(boolean largeUpdate) throws SQLException { + List executedSql = new ArrayList<>(); + Connection connection = wrap(new TestConnection(executedSql::add)); + + String query = "INSERT INTO test VALUES(1)"; + testing() + .runWithSpan( + "parent", + () -> { + PreparedStatement statement = connection.prepareStatement(query); + cleanup.deferCleanup(statement); + cleanup.deferCleanup(connection); + + if (largeUpdate) { + statement.executeLargeUpdate(); + } else { + statement.executeUpdate(); + } + }); + + testing() + .waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasNoParent(), + span -> span.hasName("INSERT dbname.test").hasParent(trace.getSpan(0)))); + + assertThat(executedSql).hasSize(1); + assertThat(executedSql.get(0)).contains(query).contains("traceparent"); + } +} diff --git a/instrumentation/jdbc/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test/DbCallingConnection.java b/instrumentation/jdbc/testing/src/main/java/io/opentelemetry/instrumentation/jdbc/testing/DbCallingConnection.java similarity index 78% rename from instrumentation/jdbc/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test/DbCallingConnection.java rename to instrumentation/jdbc/testing/src/main/java/io/opentelemetry/instrumentation/jdbc/testing/DbCallingConnection.java index 356199e5c76b..41a2f90be7e1 100644 --- a/instrumentation/jdbc/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test/DbCallingConnection.java +++ b/instrumentation/jdbc/testing/src/main/java/io/opentelemetry/instrumentation/jdbc/testing/DbCallingConnection.java @@ -3,17 +3,17 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.jdbc.test; +package io.opentelemetry.instrumentation.jdbc.testing; import io.opentelemetry.instrumentation.jdbc.TestConnection; import java.sql.DatabaseMetaData; import java.sql.SQLException; class DbCallingConnection extends TestConnection { - final boolean usePreparedStatement; + private final boolean usePreparedStatement; - DbCallingConnection(boolean usePreparedStatement) { - super(false); + DbCallingConnection(boolean usePreparedStatement, String url) { + super(url); this.usePreparedStatement = usePreparedStatement; } diff --git a/instrumentation/jdbc/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test/ProxyStatementFactory.java b/instrumentation/jdbc/testing/src/main/java/io/opentelemetry/instrumentation/jdbc/testing/ProxyStatementFactory.java similarity index 92% rename from instrumentation/jdbc/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test/ProxyStatementFactory.java rename to instrumentation/jdbc/testing/src/main/java/io/opentelemetry/instrumentation/jdbc/testing/ProxyStatementFactory.java index 3b5cf1dc7b1c..4682a93da00e 100644 --- a/instrumentation/jdbc/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test/ProxyStatementFactory.java +++ b/instrumentation/jdbc/testing/src/main/java/io/opentelemetry/instrumentation/jdbc/testing/ProxyStatementFactory.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.jdbc.test; +package io.opentelemetry.instrumentation.jdbc.testing; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; @@ -59,10 +59,8 @@ public static T proxy( // in the same package as the package private interface // by default we ignore jdk proxies, having the proxy in a different package ensures it gets // instrumented - if (!proxy - .getClass() - .getName() - .startsWith("io.opentelemetry.javaagent.instrumentation.jdbc.test")) { + String expectedPackage = TestInterface.class.getPackage().getName(); + if (!proxy.getClass().getName().startsWith(expectedPackage)) { throw new IllegalStateException("proxy is in wrong package"); } diff --git a/instrumentation/jdbc/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test/TestClassLoader.java b/instrumentation/jdbc/testing/src/main/java/io/opentelemetry/instrumentation/jdbc/testing/TestClassLoader.java similarity index 80% rename from instrumentation/jdbc/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test/TestClassLoader.java rename to instrumentation/jdbc/testing/src/main/java/io/opentelemetry/instrumentation/jdbc/testing/TestClassLoader.java index a6a1704b53ad..ef3c8aaf102d 100644 --- a/instrumentation/jdbc/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test/TestClassLoader.java +++ b/instrumentation/jdbc/testing/src/main/java/io/opentelemetry/instrumentation/jdbc/testing/TestClassLoader.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.jdbc.test; +package io.opentelemetry.instrumentation.jdbc.testing; import java.net.URL; import java.net.URLClassLoader; @@ -23,7 +23,8 @@ protected synchronized Class loadClass(String name, boolean resolve) if (clazz != null) { return clazz; } - if (name.startsWith("io.opentelemetry.javaagent.instrumentation.jdbc.test")) { + String instrumentationPackage = TestInterface.class.getPackage().getName(); + if (name.startsWith(instrumentationPackage)) { try { return findClass(name); } catch (ClassNotFoundException exception) { diff --git a/instrumentation/jdbc/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test/TestInterface.java b/instrumentation/jdbc/testing/src/main/java/io/opentelemetry/instrumentation/jdbc/testing/TestInterface.java similarity index 87% rename from instrumentation/jdbc/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test/TestInterface.java rename to instrumentation/jdbc/testing/src/main/java/io/opentelemetry/instrumentation/jdbc/testing/TestInterface.java index ee246133e013..48b60d57d566 100644 --- a/instrumentation/jdbc/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jdbc/test/TestInterface.java +++ b/instrumentation/jdbc/testing/src/main/java/io/opentelemetry/instrumentation/jdbc/testing/TestInterface.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.jdbc.test; +package io.opentelemetry.instrumentation.jdbc.testing; // Adding a package private interface to jdk proxy forces defining the proxy class in the package // of the package private class. Usually proxy classes are defined in a package that we exclude from From 5f09ad57d45408809e1677eaa35efdef0ed396a9 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Sat, 25 Oct 2025 11:48:55 -0700 Subject: [PATCH 2/5] fix --- .../jdbc/JdbcInstrumentationTest.java | 102 ------------------ .../AbstractJdbcInstrumentationTest.java | 1 + 2 files changed, 1 insertion(+), 102 deletions(-) diff --git a/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/JdbcInstrumentationTest.java b/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/JdbcInstrumentationTest.java index 35973f363b9b..a3fe3f8462ef 100644 --- a/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/JdbcInstrumentationTest.java +++ b/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/JdbcInstrumentationTest.java @@ -9,17 +9,9 @@ import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; import java.sql.Connection; -import java.sql.Driver; -import java.sql.DriverPropertyInfo; import java.sql.SQLException; -import java.sql.SQLFeatureNotSupportedException; -import java.util.Locale; -import java.util.Properties; -import java.util.logging.Logger; -import java.util.stream.Stream; import javax.sql.DataSource; import org.junit.jupiter.api.extension.RegisterExtension; -import org.junit.jupiter.params.provider.Arguments; class JdbcInstrumentationTest extends AbstractJdbcInstrumentationTest { @@ -29,9 +21,6 @@ class JdbcInstrumentationTest extends AbstractJdbcInstrumentationTest { private static final LibraryJdbcTestTelemetry telemetryHelper = new LibraryJdbcTestTelemetry(testing); - private static final String DB_NAME = "jdbcUnitTest"; - private static final String DB_NAME_LOWER = DB_NAME.toLowerCase(Locale.ROOT); - @Override protected InstrumentationExtension testing() { return testing; @@ -46,95 +35,4 @@ protected Connection wrap(Connection connection) throws SQLException { protected DataSource wrap(DataSource dataSource) { return telemetryHelper.wrap(dataSource); } - - static Stream connectionConstructorStream() { - return Stream.of( - Arguments.of( - true, - "h2", - wrapDriver(new org.h2.Driver()), - "jdbc:h2:mem:" + DB_NAME, - null, - "SELECT 3;", - "SELECT ?;", - "SELECT " + DB_NAME_LOWER, - "h2:mem:", - null), - Arguments.of( - true, - "derby", - wrapDriver(new org.apache.derby.jdbc.EmbeddedDriver()), - "jdbc:derby:memory:" + DB_NAME + ";create=true", - "APP", - "SELECT 3 FROM SYSIBM.SYSDUMMY1", - "SELECT ? FROM SYSIBM.SYSDUMMY1", - "SELECT SYSIBM.SYSDUMMY1", - "derby:memory:", - "SYSIBM.SYSDUMMY1"), - Arguments.of( - false, - "h2", - wrapDriver(new org.h2.Driver()), - "jdbc:h2:mem:" + DB_NAME, - null, - "SELECT 3;", - "SELECT ?;", - "SELECT " + DB_NAME_LOWER, - "h2:mem:", - null), - Arguments.of( - false, - "derby", - wrapDriver(new org.apache.derby.jdbc.EmbeddedDriver()), - "jdbc:derby:memory:" + DB_NAME + ";create=true", - "APP", - "SELECT 3 FROM SYSIBM.SYSDUMMY1", - "SELECT ? FROM SYSIBM.SYSDUMMY1", - "SELECT SYSIBM.SYSDUMMY1", - "derby:memory:", - "SYSIBM.SYSDUMMY1")); - } - - private static Driver wrapDriver(Driver delegate) { - return new Driver() { - @Override - public Connection connect(String url, Properties info) throws SQLException { - Connection connection = delegate.connect(url, info); - if (connection == null) { - return null; - } - return telemetryHelper.wrap(connection); - } - - @Override - public boolean acceptsURL(String url) throws SQLException { - return delegate.acceptsURL(url); - } - - @Override - public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) throws SQLException { - return delegate.getPropertyInfo(url, info); - } - - @Override - public int getMajorVersion() { - return delegate.getMajorVersion(); - } - - @Override - public int getMinorVersion() { - return delegate.getMinorVersion(); - } - - @Override - public boolean jdbcCompliant() { - return delegate.jdbcCompliant(); - } - - @Override - public Logger getParentLogger() throws SQLFeatureNotSupportedException { - return delegate.getParentLogger(); - } - }; - } } diff --git a/instrumentation/jdbc/testing/src/main/java/io/opentelemetry/instrumentation/jdbc/testing/AbstractJdbcInstrumentationTest.java b/instrumentation/jdbc/testing/src/main/java/io/opentelemetry/instrumentation/jdbc/testing/AbstractJdbcInstrumentationTest.java index 3392a9f0424c..f870ebaf8c9d 100644 --- a/instrumentation/jdbc/testing/src/main/java/io/opentelemetry/instrumentation/jdbc/testing/AbstractJdbcInstrumentationTest.java +++ b/instrumentation/jdbc/testing/src/main/java/io/opentelemetry/instrumentation/jdbc/testing/AbstractJdbcInstrumentationTest.java @@ -996,6 +996,7 @@ void testConnectionConstructorThrowing( } catch (Exception ignored) { connection = driver.connect(jdbcUrl, null); } + connection = wrap(connection); cleanup.deferCleanup(connection); Connection finalConnection = connection; ResultSet rs = From aef9a61f9d76ae17d3705c3922391a105d26e0fe Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Sat, 25 Oct 2025 13:13:46 -0700 Subject: [PATCH 3/5] simplify --- .../jdbc/JdbcInstrumentationTest.java | 19 ++++- .../jdbc/LibraryJdbcTestTelemetry.java | 77 ------------------- .../jdbc/PreparedStatementParametersTest.java | 17 +++- 3 files changed, 29 insertions(+), 84 deletions(-) delete mode 100644 instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/LibraryJdbcTestTelemetry.java diff --git a/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/JdbcInstrumentationTest.java b/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/JdbcInstrumentationTest.java index a3fe3f8462ef..b0c0e8790d75 100644 --- a/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/JdbcInstrumentationTest.java +++ b/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/JdbcInstrumentationTest.java @@ -5,6 +5,7 @@ package io.opentelemetry.instrumentation.jdbc; +import io.opentelemetry.instrumentation.jdbc.datasource.JdbcTelemetry; import io.opentelemetry.instrumentation.jdbc.testing.AbstractJdbcInstrumentationTest; import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; @@ -18,8 +19,11 @@ class JdbcInstrumentationTest extends AbstractJdbcInstrumentationTest { @RegisterExtension static final InstrumentationExtension testing = LibraryInstrumentationExtension.create(); - private static final LibraryJdbcTestTelemetry telemetryHelper = - new LibraryJdbcTestTelemetry(testing); + private static final JdbcTelemetry telemetry = + JdbcTelemetry.builder(testing.getOpenTelemetry()) + .setDataSourceInstrumenterEnabled(true) + .setTransactionInstrumenterEnabled(true) + .build(); @Override protected InstrumentationExtension testing() { @@ -28,11 +32,18 @@ protected InstrumentationExtension testing() { @Override protected Connection wrap(Connection connection) throws SQLException { - return telemetryHelper.wrap(connection); + // if (connection instanceof OpenTelemetryConnection) { + // return connection; + // } + DataSource dataSource = telemetry.wrap(new SingleConnectionDataSource(connection)); + return dataSource.getConnection(); } @Override protected DataSource wrap(DataSource dataSource) { - return telemetryHelper.wrap(dataSource); + // if (dataSource instanceof OpenTelemetryDataSource) { + // return dataSource; + // } + return telemetry.wrap(dataSource); } } diff --git a/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/LibraryJdbcTestTelemetry.java b/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/LibraryJdbcTestTelemetry.java deleted file mode 100644 index 2bd819429e7f..000000000000 --- a/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/LibraryJdbcTestTelemetry.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.jdbc; - -import io.opentelemetry.instrumentation.jdbc.datasource.JdbcTelemetry; -import io.opentelemetry.instrumentation.jdbc.datasource.OpenTelemetryDataSource; -import io.opentelemetry.instrumentation.jdbc.internal.OpenTelemetryConnection; -import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; -import java.sql.Connection; -import java.sql.SQLException; -import javax.sql.DataSource; - -final class LibraryJdbcTestTelemetry { - - private final InstrumentationExtension testing; - private JdbcTelemetry telemetry; - private JdbcTelemetry telemetryWithQueryParameters; - - LibraryJdbcTestTelemetry(InstrumentationExtension testing) { - this.testing = testing; - } - - Connection wrap(Connection connection) throws SQLException { - return wrapConnection(connection, telemetry()); - } - - DataSource wrap(DataSource dataSource) { - return wrapDataSource(dataSource, telemetry()); - } - - Connection instrumentConnectionWithQueryParameters(Connection connection) throws SQLException { - return wrapConnection(connection, telemetryWithQueryParameters()); - } - - private static Connection wrapConnection(Connection connection, JdbcTelemetry telemetry) - throws SQLException { - if (connection instanceof OpenTelemetryConnection) { - return connection; - } - DataSource dataSource = telemetry.wrap(new SingleConnectionDataSource(connection)); - return dataSource.getConnection(); - } - - private static DataSource wrapDataSource(DataSource dataSource, JdbcTelemetry telemetry) { - if (dataSource instanceof OpenTelemetryDataSource) { - return dataSource; - } - return telemetry.wrap(dataSource); - } - - private JdbcTelemetry telemetry() { - if (telemetry == null) { - telemetry = - JdbcTelemetry.builder(testing.getOpenTelemetry()) - .setDataSourceInstrumenterEnabled(true) - .setTransactionInstrumenterEnabled(true) - .build(); - } - return telemetry; - } - - private JdbcTelemetry telemetryWithQueryParameters() { - if (telemetryWithQueryParameters == null) { - telemetryWithQueryParameters = - JdbcTelemetry.builder(testing.getOpenTelemetry()) - .setDataSourceInstrumenterEnabled(true) - .setTransactionInstrumenterEnabled(true) - .setCaptureQueryParameters(true) - .setStatementSanitizationEnabled(false) - .build(); - } - return telemetryWithQueryParameters; - } -} diff --git a/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/PreparedStatementParametersTest.java b/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/PreparedStatementParametersTest.java index 7e63eb2f1a1a..0b02ce316810 100644 --- a/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/PreparedStatementParametersTest.java +++ b/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/PreparedStatementParametersTest.java @@ -5,11 +5,13 @@ package io.opentelemetry.instrumentation.jdbc; +import io.opentelemetry.instrumentation.jdbc.datasource.JdbcTelemetry; import io.opentelemetry.instrumentation.jdbc.testing.AbstractPreparedStatementParametersTest; import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; import java.sql.Connection; import java.sql.SQLException; +import javax.sql.DataSource; import org.junit.jupiter.api.extension.RegisterExtension; class PreparedStatementParametersTest extends AbstractPreparedStatementParametersTest { @@ -17,8 +19,13 @@ class PreparedStatementParametersTest extends AbstractPreparedStatementParameter @RegisterExtension static final InstrumentationExtension testing = LibraryInstrumentationExtension.create(); - private static final LibraryJdbcTestTelemetry telemetryHelper = - new LibraryJdbcTestTelemetry(testing); + private static final JdbcTelemetry telemetry = + JdbcTelemetry.builder(testing.getOpenTelemetry()) + .setDataSourceInstrumenterEnabled(true) + .setTransactionInstrumenterEnabled(true) + .setCaptureQueryParameters(true) + .setStatementSanitizationEnabled(false) + .build(); @Override protected InstrumentationExtension testing() { @@ -27,6 +34,10 @@ protected InstrumentationExtension testing() { @Override protected Connection wrap(Connection connection) throws SQLException { - return telemetryHelper.instrumentConnectionWithQueryParameters(connection); + // if (connection instanceof OpenTelemetryConnection) { + // return connection; + // } + DataSource dataSource = telemetry.wrap(new SingleConnectionDataSource(connection)); + return dataSource.getConnection(); } } From 9d2feb2635f9427d712df79863d7e253729985f7 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Sat, 25 Oct 2025 13:35:23 -0700 Subject: [PATCH 4/5] reduce diff --- .../scalaexecutors/SlickTest.scala | 2 +- .../jdbc/ConnectionWrapper.java | 77 +++++++++++++++++++ .../jdbc/JdbcInstrumentationTest.java | 9 +-- .../jdbc/PreparedStatementParametersTest.java | 8 +- .../jdbc/SingleConnectionDataSource.java | 65 ---------------- .../jdbc/SqlCommenterTest.java | 15 ++-- .../jdbc/testing/ProxyStatementFactory.java | 3 +- .../jdbc/testing/TestClassLoader.java | 3 +- 8 files changed, 91 insertions(+), 91 deletions(-) create mode 100644 instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/ConnectionWrapper.java delete mode 100644 instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/SingleConnectionDataSource.java diff --git a/instrumentation/jdbc/javaagent/src/test/scala/io/opentelemetry/javaagent/instrumentation/scalaexecutors/SlickTest.scala b/instrumentation/jdbc/javaagent/src/test/scala/io/opentelemetry/javaagent/instrumentation/scalaexecutors/SlickTest.scala index 7542c9a15b5b..79c74f466a16 100644 --- a/instrumentation/jdbc/javaagent/src/test/scala/io/opentelemetry/javaagent/instrumentation/scalaexecutors/SlickTest.scala +++ b/instrumentation/jdbc/javaagent/src/test/scala/io/opentelemetry/javaagent/instrumentation/scalaexecutors/SlickTest.scala @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.javaagent.instrumentation.jdbc.slick +package io.opentelemetry.javaagent.instrumentation.scalaexecutors import io.opentelemetry.api.trace.{SpanKind, Tracer} import io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableDatabaseSemconv diff --git a/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/ConnectionWrapper.java b/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/ConnectionWrapper.java new file mode 100644 index 000000000000..c84aa91ff8b6 --- /dev/null +++ b/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/ConnectionWrapper.java @@ -0,0 +1,77 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.jdbc; + +import io.opentelemetry.instrumentation.jdbc.datasource.JdbcTelemetry; +import java.io.PrintWriter; +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; +import java.util.logging.Logger; +import javax.sql.DataSource; + +class ConnectionWrapper { + + // hack since JdbcTelemetry only supports wrapping data sources + static Connection wrap(Connection connection, JdbcTelemetry telemetry) throws SQLException { + DataSource dataSource = telemetry.wrap(new SingleConnectionDataSource(connection)); + return dataSource.getConnection(); + } + + private ConnectionWrapper() {} + + private static class SingleConnectionDataSource implements DataSource { + private final Connection delegate; + + SingleConnectionDataSource(Connection delegate) { + this.delegate = delegate; + } + + @Override + public Connection getConnection() { + return delegate; + } + + @Override + public Connection getConnection(String username, String password) { + return getConnection(); + } + + @Override + public PrintWriter getLogWriter() { + return null; + } + + @Override + public void setLogWriter(PrintWriter out) {} + + @Override + public void setLoginTimeout(int seconds) {} + + @Override + public int getLoginTimeout() { + return 0; + } + + @Override + public Logger getParentLogger() throws SQLFeatureNotSupportedException { + throw new SQLFeatureNotSupportedException(); + } + + @Override + public T unwrap(Class iface) throws SQLException { + if (iface.isInstance(delegate)) { + return iface.cast(delegate); + } + throw new SQLFeatureNotSupportedException(); + } + + @Override + public boolean isWrapperFor(Class iface) { + return iface.isInstance(delegate); + } + } +} diff --git a/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/JdbcInstrumentationTest.java b/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/JdbcInstrumentationTest.java index b0c0e8790d75..bc0ecf91d961 100644 --- a/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/JdbcInstrumentationTest.java +++ b/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/JdbcInstrumentationTest.java @@ -32,18 +32,11 @@ protected InstrumentationExtension testing() { @Override protected Connection wrap(Connection connection) throws SQLException { - // if (connection instanceof OpenTelemetryConnection) { - // return connection; - // } - DataSource dataSource = telemetry.wrap(new SingleConnectionDataSource(connection)); - return dataSource.getConnection(); + return ConnectionWrapper.wrap(connection, telemetry); } @Override protected DataSource wrap(DataSource dataSource) { - // if (dataSource instanceof OpenTelemetryDataSource) { - // return dataSource; - // } return telemetry.wrap(dataSource); } } diff --git a/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/PreparedStatementParametersTest.java b/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/PreparedStatementParametersTest.java index 0b02ce316810..facd9838afa5 100644 --- a/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/PreparedStatementParametersTest.java +++ b/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/PreparedStatementParametersTest.java @@ -11,7 +11,6 @@ import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; import java.sql.Connection; import java.sql.SQLException; -import javax.sql.DataSource; import org.junit.jupiter.api.extension.RegisterExtension; class PreparedStatementParametersTest extends AbstractPreparedStatementParametersTest { @@ -24,7 +23,6 @@ class PreparedStatementParametersTest extends AbstractPreparedStatementParameter .setDataSourceInstrumenterEnabled(true) .setTransactionInstrumenterEnabled(true) .setCaptureQueryParameters(true) - .setStatementSanitizationEnabled(false) .build(); @Override @@ -34,10 +32,6 @@ protected InstrumentationExtension testing() { @Override protected Connection wrap(Connection connection) throws SQLException { - // if (connection instanceof OpenTelemetryConnection) { - // return connection; - // } - DataSource dataSource = telemetry.wrap(new SingleConnectionDataSource(connection)); - return dataSource.getConnection(); + return ConnectionWrapper.wrap(connection, telemetry); } } diff --git a/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/SingleConnectionDataSource.java b/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/SingleConnectionDataSource.java deleted file mode 100644 index 7bb00c345da7..000000000000 --- a/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/SingleConnectionDataSource.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.jdbc; - -import java.io.PrintWriter; -import java.sql.Connection; -import java.sql.SQLException; -import java.sql.SQLFeatureNotSupportedException; -import java.util.logging.Logger; -import javax.sql.DataSource; - -final class SingleConnectionDataSource implements DataSource { - private final Connection delegate; - - SingleConnectionDataSource(Connection delegate) { - this.delegate = delegate; - } - - @Override - public Connection getConnection() { - return delegate; - } - - @Override - public Connection getConnection(String username, String password) { - return getConnection(); - } - - @Override - public PrintWriter getLogWriter() { - return null; - } - - @Override - public void setLogWriter(PrintWriter out) {} - - @Override - public void setLoginTimeout(int seconds) {} - - @Override - public int getLoginTimeout() { - return 0; - } - - @Override - public Logger getParentLogger() throws SQLFeatureNotSupportedException { - throw new SQLFeatureNotSupportedException(); - } - - @Override - public T unwrap(Class iface) throws SQLException { - if (iface.isInstance(delegate)) { - return iface.cast(delegate); - } - throw new SQLFeatureNotSupportedException(); - } - - @Override - public boolean isWrapperFor(Class iface) { - return iface.isInstance(delegate); - } -} diff --git a/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/SqlCommenterTest.java b/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/SqlCommenterTest.java index fa3a3f29ac56..89a9c3c40e8e 100644 --- a/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/SqlCommenterTest.java +++ b/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/SqlCommenterTest.java @@ -13,7 +13,6 @@ import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; import java.sql.Connection; import java.sql.SQLException; -import javax.sql.DataSource; import org.junit.jupiter.api.extension.RegisterExtension; class SqlCommenterTest extends AbstractSqlCommenterTest { @@ -21,6 +20,14 @@ class SqlCommenterTest extends AbstractSqlCommenterTest { @RegisterExtension static final InstrumentationExtension testing = LibraryInstrumentationExtension.create(); + private static final JdbcTelemetry telemetry; + + static { + JdbcTelemetryBuilder builder = JdbcTelemetry.builder(testing.getOpenTelemetry()); + Experimental.setEnableSqlCommenter(builder, true); + telemetry = builder.build(); + } + @Override protected InstrumentationExtension testing() { return testing; @@ -28,10 +35,6 @@ protected InstrumentationExtension testing() { @Override protected Connection wrap(Connection connection) throws SQLException { - JdbcTelemetryBuilder builder = JdbcTelemetry.builder(testing.getOpenTelemetry()); - Experimental.setEnableSqlCommenter(builder, true); - JdbcTelemetry telemetry = builder.build(); - DataSource dataSource = telemetry.wrap(new SingleConnectionDataSource(connection)); - return dataSource.getConnection(); + return ConnectionWrapper.wrap(connection, telemetry); } } diff --git a/instrumentation/jdbc/testing/src/main/java/io/opentelemetry/instrumentation/jdbc/testing/ProxyStatementFactory.java b/instrumentation/jdbc/testing/src/main/java/io/opentelemetry/instrumentation/jdbc/testing/ProxyStatementFactory.java index 4682a93da00e..cb78b196924e 100644 --- a/instrumentation/jdbc/testing/src/main/java/io/opentelemetry/instrumentation/jdbc/testing/ProxyStatementFactory.java +++ b/instrumentation/jdbc/testing/src/main/java/io/opentelemetry/instrumentation/jdbc/testing/ProxyStatementFactory.java @@ -59,8 +59,7 @@ public static T proxy( // in the same package as the package private interface // by default we ignore jdk proxies, having the proxy in a different package ensures it gets // instrumented - String expectedPackage = TestInterface.class.getPackage().getName(); - if (!proxy.getClass().getName().startsWith(expectedPackage)) { + if (!proxy.getClass().getName().startsWith("io.opentelemetry.instrumentation.jdbc.testing")) { throw new IllegalStateException("proxy is in wrong package"); } diff --git a/instrumentation/jdbc/testing/src/main/java/io/opentelemetry/instrumentation/jdbc/testing/TestClassLoader.java b/instrumentation/jdbc/testing/src/main/java/io/opentelemetry/instrumentation/jdbc/testing/TestClassLoader.java index ef3c8aaf102d..7fcda12a0da2 100644 --- a/instrumentation/jdbc/testing/src/main/java/io/opentelemetry/instrumentation/jdbc/testing/TestClassLoader.java +++ b/instrumentation/jdbc/testing/src/main/java/io/opentelemetry/instrumentation/jdbc/testing/TestClassLoader.java @@ -23,8 +23,7 @@ protected synchronized Class loadClass(String name, boolean resolve) if (clazz != null) { return clazz; } - String instrumentationPackage = TestInterface.class.getPackage().getName(); - if (name.startsWith(instrumentationPackage)) { + if (name.startsWith("io.opentelemetry.instrumentation.jdbc.testing")) { try { return findClass(name); } catch (ClassNotFoundException exception) { From 6e4b8c0455b71e6a9853221a771db88200cb3872 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Sat, 25 Oct 2025 17:35:39 -0700 Subject: [PATCH 5/5] fix --- instrumentation/jdbc/library/build.gradle.kts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/instrumentation/jdbc/library/build.gradle.kts b/instrumentation/jdbc/library/build.gradle.kts index 5e116b24b436..bc51a76fad09 100644 --- a/instrumentation/jdbc/library/build.gradle.kts +++ b/instrumentation/jdbc/library/build.gradle.kts @@ -22,6 +22,9 @@ dependencies { testLibrary("org.apache.tomcat:tomcat-juli:7.0.19") // tomcat jdbc needs this testLibrary("com.zaxxer:HikariCP:2.4.0") testLibrary("com.mchange:c3p0:0.9.5") + + // some classes in earlier versions of derby were split out into derbytools in later versions + latestDepTestLibrary("org.apache.derby:derbytools:latest.release") } tasks {