From 9252946e34ba81f64b524a2ac31162d768c2e7d8 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Wed, 22 Oct 2025 16:43:16 -0700 Subject: [PATCH 1/4] Unify jdbc tests --- .../jdbc/test/JdbcInstrumentationTest.java | 1841 +--------------- .../test/PreparedStatementParametersTest.java | 598 +---- .../jdbc/test/SqlCommenterTest.java | 167 +- .../OpenTelemetryPreparedStatement.java | 16 +- .../jdbc/internal/OpenTelemetryStatement.java | 18 +- .../jdbc/JdbcInstrumentationTest.java | 152 ++ .../jdbc/LibraryJdbcTestTelemetry.java | 136 ++ .../jdbc/PreparedStatementParametersTest.java | 32 + .../jdbc/SqlCommenterTest.java | 90 + instrumentation/jdbc/testing/build.gradle.kts | 14 + .../instrumentation/jdbc/TestConnection.java | 13 +- .../instrumentation/jdbc/TestDriver.java | 2 +- .../AbstractJdbcInstrumentationTest.java | 1947 +++++++++++++++++ ...stractPreparedStatementParametersTest.java | 617 ++++++ .../testing/AbstractSqlCommenterTest.java | 190 ++ .../jdbc/testing}/DbCallingConnection.java | 8 +- .../jdbc/testing}/ProxyStatementFactory.java | 8 +- .../jdbc/testing}/TestClassLoader.java | 5 +- .../jdbc/testing}/TestInterface.java | 2 +- 19 files changed, 3248 insertions(+), 2608 deletions(-) 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/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 (80%) 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/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/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..2156c9bd6dba --- /dev/null +++ b/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/JdbcInstrumentationTest.java @@ -0,0 +1,152 @@ +/* + * 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 instrumentConnection(Connection connection) throws SQLException { + return telemetryHelper.instrumentConnection(connection); + } + + @Override + protected DataSource instrumentDataSource(DataSource dataSource) { + return telemetryHelper.instrumentDataSource(dataSource); + } + + @Override + protected String expectedGetConnectionSpanName( + DataSource originalDatasource, DataSource instrumentedDatasource) { + return originalDatasource.getClass().getSimpleName(); + } + + @Override + protected Class expectedGetConnectionCodeClass( + DataSource originalDatasource, DataSource instrumentedDatasource) { + return originalDatasource.getClass(); + } + + 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.instrumentConnection(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..ff15f494f213 --- /dev/null +++ b/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/LibraryJdbcTestTelemetry.java @@ -0,0 +1,136 @@ +/* + * 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.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 LibraryJdbcTestTelemetry { + + private final InstrumentationExtension testing; + private JdbcTelemetry telemetry; + private JdbcTelemetry telemetryWithQueryParameters; + + LibraryJdbcTestTelemetry(InstrumentationExtension testing) { + this.testing = testing; + } + + Connection instrumentConnection(Connection connection) throws SQLException { + return wrapConnection(connection, 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(); + } + + DataSource instrumentDataSource(DataSource dataSource) { + return wrapDataSource(dataSource, telemetry()); + } + + DataSource instrumentDataSourceWithQueryParameters(DataSource dataSource) { + return wrapDataSource(dataSource, telemetryWithQueryParameters()); + } + + 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; + } + + private static final class SingleConnectionDataSource implements DataSource { + private final Connection delegate; + + private 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/PreparedStatementParametersTest.java b/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/PreparedStatementParametersTest.java new file mode 100644 index 000000000000..d94236b52f47 --- /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 instrumentConnection(Connection connection) throws SQLException { + return telemetryHelper.instrumentConnectionWithQueryParameters(connection); + } +} 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..ebeefa7b16f2 --- /dev/null +++ b/instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/SqlCommenterTest.java @@ -0,0 +1,90 @@ +/* + * 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.io.PrintWriter; +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; +import java.util.List; +import java.util.logging.Logger; +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 createConnection(List executedSql) throws SQLException { + JdbcTelemetryBuilder builder = JdbcTelemetry.builder(testing.getOpenTelemetry()); + Experimental.setEnableSqlCommenter(builder, true); + JdbcTelemetry telemetry = builder.build(); + DataSource dataSource = telemetry.wrap(new RecordingDataSource(executedSql)); + return dataSource.getConnection(); + } + + private static final class RecordingDataSource implements DataSource { + private final List executedSql; + + private RecordingDataSource(List executedSql) { + this.executedSql = executedSql; + } + + @Override + public Connection getConnection() { + return new TestConnection(executedSql::add); + } + + @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 { + throw new SQLFeatureNotSupportedException(); + } + + @Override + public boolean isWrapperFor(Class iface) { + return false; + } + } +} diff --git a/instrumentation/jdbc/testing/build.gradle.kts b/instrumentation/jdbc/testing/build.gradle.kts index 3a99cee0a166..68f74a0bf298 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") + + api("com.h2database:h2:1.3.169") + api("org.apache.derby:derby:10.6.1.0") + api("org.hsqldb:hsqldb:2.0.0") + + api("org.apache.tomcat:tomcat-jdbc:7.0.19") + api("org.apache.tomcat:tomcat-juli:7.0.19") + api("com.zaxxer:HikariCP:2.4.0") + api("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..9663bc36a44d 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 @@ -33,6 +33,10 @@ public class TestConnection implements Connection { public TestConnection() {} + public TestConnection(String url) { + this.url = url; + } + public TestConnection(Consumer sqlConsumer) { this.sqlConsumer = sqlConsumer; } @@ -43,6 +47,11 @@ public TestConnection(boolean throwException) { } } + public TestConnection(String url, boolean throwException) { + this(throwException); + this.url = url; + } + @Override public void abort(Executor executor) throws SQLException {} @@ -294,10 +303,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..6d990d7d2ccf --- /dev/null +++ b/instrumentation/jdbc/testing/src/main/java/io/opentelemetry/instrumentation/jdbc/testing/AbstractJdbcInstrumentationTest.java @@ -0,0 +1,1947 @@ +/* + * 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 instrumentConnection(Connection connection) throws SQLException { + return connection; + } + + protected DataSource instrumentDataSource(DataSource dataSource) { + return dataSource; + } + + protected String expectedGetConnectionSpanName( + DataSource originalDatasource, DataSource instrumentedDatasource) { + return instrumentedDatasource.getClass().getSimpleName(); + } + + protected Class expectedGetConnectionCodeClass( + DataSource originalDatasource, DataSource instrumentedDatasource) { + return instrumentedDatasource.getClass(); + } + + 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 connection, + String username, + String query, + String sanitizedQuery, + String spanName, + String url, + String table) + throws SQLException { + connection = instrumentConnection(connection); + 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 { + connection = instrumentConnection(connection); + 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 { + connection = instrumentConnection(connection); + 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 { + connection = instrumentConnection(connection); + 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 { + connection = instrumentConnection(connection); + 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 { + connection = instrumentConnection(connection); + 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 instrumentedDatasource = instrumentDataSource(datasource); + instrumentedDatasource.getConnection().close(); + assertThat(testing().spans()).noneMatch(span -> span.getName().equals("database.connection")); + + testing().clearData(); + + List attributesAssertions = + codeFunctionAssertions( + expectedGetConnectionCodeClass(datasource, instrumentedDatasource), "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)); + + DataSource finalDatasource = instrumentedDatasource; + testing().runWithSpan("parent", () -> finalDatasource.getConnection().close()); + testing() + .waitAndAssertTraces( + trace -> { + List> assertions = + new ArrayList<>( + asList( + span1 -> span1.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), + span1 -> + span1 + .hasName( + expectedGetConnectionSpanName( + datasource, instrumentedDatasource) + + ".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 rawConnection = new TestConnection("jdbc:testdb://localhost", false); + Connection connection = instrumentConnection(rawConnection); + 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 = instrumentConnection(driver.connect(url, null)); + cleanup.deferCleanup(connection); + + Connection finalConnection = connection; + + testing() + .runWithSpan( + "parent", + () -> { + Statement statement = finalConnection.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 rawDataSource = createDs(connectionPoolName, dbType, jdbcUrls.get(dbType)); + DataSource dataSource = instrumentDataSource(rawDataSource); + DataSource finalDataSource = dataSource; + cleanup.deferCleanup( + () -> { + if (finalDataSource instanceof Closeable) { + ((Closeable) finalDataSource).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 = dataSource.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 rawConnection = + new DbCallingConnection(usePreparedStatementInConnection, "jdbc:testdb://localhost"); + Connection connection = instrumentConnection(rawConnection); + + 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 = + instrumentConnection(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 = + instrumentConnection(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 void testBatchImpl( + String system, + Connection connection, + String username, + String url, + String tableName, + ThrowingConsumer action) + throws SQLException { + connection = instrumentConnection(connection); + 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 { + connection = instrumentConnection(connection); + 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 { + connection = instrumentConnection(connection); + 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 { + connection = instrumentConnection(connection); + 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 { + Connection instrumentedConnection = instrumentConnection(connection); + Connection finalConnection = instrumentedConnection; + + String tableName = "TXN_COMMIT_TEST_" + system.toUpperCase(Locale.ROOT); + Statement createTable = finalConnection.createStatement(); + createTable.execute("CREATE TABLE " + tableName + " (id INTEGER not NULL, PRIMARY KEY ( id ))"); + cleanup.deferCleanup(createTable); + + finalConnection.setAutoCommit(false); + + testing().waitForTraces(1); + testing().clearData(); + + Statement insertStatement = finalConnection.createStatement(); + cleanup.deferCleanup(insertStatement); + + testing() + .runWithSpan( + "parent", + () -> { + insertStatement.executeUpdate("INSERT INTO " + tableName + " VALUES(1)"); + finalConnection.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 { + Connection instrumentedConnection = instrumentConnection(connection); + Connection finalConnection = instrumentedConnection; + + String tableName = "TXN_ROLLBACK_TEST_" + system.toUpperCase(Locale.ROOT); + Statement createTable = finalConnection.createStatement(); + createTable.execute("CREATE TABLE " + tableName + " (id INTEGER not NULL, PRIMARY KEY ( id ))"); + cleanup.deferCleanup(createTable); + + finalConnection.setAutoCommit(false); + + testing().waitForTraces(1); + testing().clearData(); + + Statement insertStatement = finalConnection.createStatement(); + cleanup.deferCleanup(insertStatement); + + testing() + .runWithSpan( + "parent", + () -> { + insertStatement.executeUpdate("INSERT INTO " + tableName + " VALUES(1)"); + finalConnection.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 = + instrumentConnection(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 = + instrumentConnection(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..24487b9679f9 --- /dev/null +++ b/instrumentation/jdbc/testing/src/main/java/io/opentelemetry/instrumentation/jdbc/testing/AbstractPreparedStatementParametersTest.java @@ -0,0 +1,617 @@ +/* + * 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 instrumentConnection(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 { + Connection instrumentedConnection = instrumentConnection(connection); + PreparedStatement statement = instrumentedConnection.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..4f0d24bc323f --- /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 createConnection(List executedSql) throws SQLException { + return new TestConnection(executedSql::add); + } + + @Test + void testSqlCommenterStatement() throws SQLException { + List executedSql = new ArrayList<>(); + Connection connection = createConnection(executedSql); + 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 = createConnection(executedSql); + 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 = createConnection(executedSql); + 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 = createConnection(executedSql); + + 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 = createConnection(executedSql); + + 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 80% 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..88d6ac84d6df 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,7 +3,7 @@ * 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; @@ -13,7 +13,11 @@ class DbCallingConnection extends TestConnection { final boolean usePreparedStatement; DbCallingConnection(boolean usePreparedStatement) { - super(false); + this(usePreparedStatement, null); + } + + DbCallingConnection(boolean usePreparedStatement, String url) { + super(url, false); 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 eaaa8cedcd017b14f682e92a15fcb5f58ea34eba Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Wed, 22 Oct 2025 20:49:15 -0700 Subject: [PATCH 2/4] more --- .../jdbc/LibraryJdbcTestTelemetry.java | 59 ---- .../jdbc/SingleConnectionDataSource.java | 65 ++++ .../jdbc/SqlCommenterTest.java | 57 +-- .../internal/OpenTelemetryConnectionTest.java | 328 +----------------- .../testing/AbstractSqlCommenterTest.java | 14 +- 5 files changed, 75 insertions(+), 448 deletions(-) create mode 100644 instrumentation/jdbc/library/src/test/java/io/opentelemetry/instrumentation/jdbc/SingleConnectionDataSource.java 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 index ff15f494f213..9c431ed93ac7 100644 --- 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 @@ -9,11 +9,8 @@ import io.opentelemetry.instrumentation.jdbc.datasource.OpenTelemetryDataSource; import io.opentelemetry.instrumentation.jdbc.internal.OpenTelemetryConnection; import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; -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 LibraryJdbcTestTelemetry { @@ -47,10 +44,6 @@ DataSource instrumentDataSource(DataSource dataSource) { return wrapDataSource(dataSource, telemetry()); } - DataSource instrumentDataSourceWithQueryParameters(DataSource dataSource) { - return wrapDataSource(dataSource, telemetryWithQueryParameters()); - } - private static DataSource wrapDataSource(DataSource dataSource, JdbcTelemetry telemetry) { if (dataSource instanceof OpenTelemetryDataSource) { return dataSource; @@ -81,56 +74,4 @@ private JdbcTelemetry telemetryWithQueryParameters() { } return telemetryWithQueryParameters; } - - private static final class SingleConnectionDataSource implements DataSource { - private final Connection delegate; - - private 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/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 index ebeefa7b16f2..fa3a3f29ac56 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 @@ -11,12 +11,8 @@ import io.opentelemetry.instrumentation.jdbc.testing.AbstractSqlCommenterTest; import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; -import java.io.PrintWriter; import java.sql.Connection; import java.sql.SQLException; -import java.sql.SQLFeatureNotSupportedException; -import java.util.List; -import java.util.logging.Logger; import javax.sql.DataSource; import org.junit.jupiter.api.extension.RegisterExtension; @@ -31,60 +27,11 @@ protected InstrumentationExtension testing() { } @Override - protected Connection createConnection(List executedSql) throws SQLException { + 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 RecordingDataSource(executedSql)); + DataSource dataSource = telemetry.wrap(new SingleConnectionDataSource(connection)); return dataSource.getConnection(); } - - private static final class RecordingDataSource implements DataSource { - private final List executedSql; - - private RecordingDataSource(List executedSql) { - this.executedSql = executedSql; - } - - @Override - public Connection getConnection() { - return new TestConnection(executedSql::add); - } - - @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 { - throw new SQLFeatureNotSupportedException(); - } - - @Override - public boolean isWrapperFor(Class iface) { - return false; - } - } } 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/src/main/java/io/opentelemetry/instrumentation/jdbc/testing/AbstractSqlCommenterTest.java b/instrumentation/jdbc/testing/src/main/java/io/opentelemetry/instrumentation/jdbc/testing/AbstractSqlCommenterTest.java index 4f0d24bc323f..fcb0bcf5d535 100644 --- 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 @@ -27,14 +27,14 @@ public abstract class AbstractSqlCommenterTest { protected abstract InstrumentationExtension testing(); - protected Connection createConnection(List executedSql) throws SQLException { - return new TestConnection(executedSql::add); + protected Connection wrap(Connection connection) throws SQLException { + return connection; } @Test void testSqlCommenterStatement() throws SQLException { List executedSql = new ArrayList<>(); - Connection connection = createConnection(executedSql); + Connection connection = wrap(new TestConnection(executedSql::add)); Statement statement = connection.createStatement(); cleanup.deferCleanup(statement); @@ -58,7 +58,7 @@ void testSqlCommenterStatement() throws SQLException { @ValueSource(booleans = {true, false}) void testSqlCommenterStatementUpdate(boolean largeUpdate) throws SQLException { List executedSql = new ArrayList<>(); - Connection connection = createConnection(executedSql); + Connection connection = wrap(new TestConnection(executedSql::add)); Statement statement = connection.createStatement(); cleanup.deferCleanup(statement); @@ -91,7 +91,7 @@ void testSqlCommenterStatementUpdate(boolean largeUpdate) throws SQLException { @ValueSource(booleans = {true, false}) void testSqlCommenterStatementBatch(boolean largeUpdate) throws SQLException { List executedSql = new ArrayList<>(); - Connection connection = createConnection(executedSql); + Connection connection = wrap(new TestConnection(executedSql::add)); Statement statement = connection.createStatement(); cleanup.deferCleanup(statement); @@ -130,7 +130,7 @@ void testSqlCommenterStatementBatch(boolean largeUpdate) throws SQLException { @Test void testSqlCommenterPreparedStatement() throws SQLException { List executedSql = new ArrayList<>(); - Connection connection = createConnection(executedSql); + Connection connection = wrap(new TestConnection(executedSql::add)); String query = "SELECT 1"; testing() @@ -159,7 +159,7 @@ void testSqlCommenterPreparedStatement() throws SQLException { @ValueSource(booleans = {true, false}) void testSqlCommenterPreparedStatementUpdate(boolean largeUpdate) throws SQLException { List executedSql = new ArrayList<>(); - Connection connection = createConnection(executedSql); + Connection connection = wrap(new TestConnection(executedSql::add)); String query = "INSERT INTO test VALUES(1)"; testing() From 237983db35793d75e64393e722f936e509d81be1 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Fri, 24 Oct 2025 10:12:09 -0700 Subject: [PATCH 3/4] wrap --- .../jdbc/JdbcInstrumentationTest.java | 10 ++-- .../jdbc/LibraryJdbcTestTelemetry.java | 4 +- .../jdbc/PreparedStatementParametersTest.java | 2 +- instrumentation/jdbc/testing/build.gradle.kts | 1 - .../instrumentation/jdbc/TestConnection.java | 3 +- .../AbstractJdbcInstrumentationTest.java | 46 +++++++++---------- ...stractPreparedStatementParametersTest.java | 4 +- 7 files changed, 35 insertions(+), 35 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 2156c9bd6dba..d2dacd347f20 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 @@ -38,13 +38,13 @@ protected InstrumentationExtension testing() { } @Override - protected Connection instrumentConnection(Connection connection) throws SQLException { - return telemetryHelper.instrumentConnection(connection); + protected Connection wrap(Connection connection) throws SQLException { + return telemetryHelper.wrap(connection); } @Override - protected DataSource instrumentDataSource(DataSource dataSource) { - return telemetryHelper.instrumentDataSource(dataSource); + protected DataSource wrap(DataSource dataSource) { + return telemetryHelper.wrap(dataSource); } @Override @@ -115,7 +115,7 @@ public Connection connect(String url, Properties info) throws SQLException { if (connection == null) { return null; } - return telemetryHelper.instrumentConnection(connection); + return telemetryHelper.wrap(connection); } @Override 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 index 9c431ed93ac7..bcb4e716370e 100644 --- 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 @@ -23,7 +23,7 @@ final class LibraryJdbcTestTelemetry { this.testing = testing; } - Connection instrumentConnection(Connection connection) throws SQLException { + Connection wrap(Connection connection) throws SQLException { return wrapConnection(connection, telemetry()); } @@ -40,7 +40,7 @@ private static Connection wrapConnection(Connection connection, JdbcTelemetry te return dataSource.getConnection(); } - DataSource instrumentDataSource(DataSource dataSource) { + DataSource wrap(DataSource dataSource) { return wrapDataSource(dataSource, telemetry()); } 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 d94236b52f47..7e63eb2f1a1a 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 @@ -26,7 +26,7 @@ protected InstrumentationExtension testing() { } @Override - protected Connection instrumentConnection(Connection connection) throws SQLException { + protected Connection wrap(Connection connection) throws SQLException { return telemetryHelper.instrumentConnectionWithQueryParameters(connection); } } diff --git a/instrumentation/jdbc/testing/build.gradle.kts b/instrumentation/jdbc/testing/build.gradle.kts index 68f74a0bf298..357cb1839a48 100644 --- a/instrumentation/jdbc/testing/build.gradle.kts +++ b/instrumentation/jdbc/testing/build.gradle.kts @@ -16,7 +16,6 @@ dependencies { api("org.hsqldb:hsqldb:2.0.0") api("org.apache.tomcat:tomcat-jdbc:7.0.19") - api("org.apache.tomcat:tomcat-juli:7.0.19") api("com.zaxxer:HikariCP:2.4.0") api("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 9663bc36a44d..52348e8fd009 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,7 +28,8 @@ /** A JDBC connection class that optionally throws an exception in the constructor, used to test */ public class TestConnection implements Connection { - private String url; + + private final String url; Consumer sqlConsumer = unused -> {}; public TestConnection() {} 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 6d990d7d2ccf..c238b2e00fc2 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 @@ -82,11 +82,11 @@ public abstract class AbstractJdbcInstrumentationTest { protected abstract InstrumentationExtension testing(); - protected Connection instrumentConnection(Connection connection) throws SQLException { + protected Connection wrap(Connection connection) throws SQLException { return connection; } - protected DataSource instrumentDataSource(DataSource dataSource) { + protected DataSource wrap(DataSource dataSource) { return dataSource; } @@ -376,7 +376,7 @@ public void testBasicStatement( String url, String table) throws SQLException { - connection = instrumentConnection(connection); + connection = wrap(connection); Statement statement = connection.createStatement(); cleanup.deferCleanup(statement); ResultSet resultSet = testing().runWithSpan("parent", () -> statement.executeQuery(query)); @@ -505,7 +505,7 @@ void testPreparedStatementExecute( String url, String table) throws SQLException { - connection = instrumentConnection(connection); + connection = wrap(connection); PreparedStatement statement = connection.prepareStatement(query); cleanup.deferCleanup(statement); ResultSet resultSet = @@ -552,7 +552,7 @@ void testPreparedStatementQuery( String url, String table) throws SQLException { - connection = instrumentConnection(connection); + connection = wrap(connection); PreparedStatement statement = connection.prepareStatement(query); cleanup.deferCleanup(statement); ResultSet resultSet = testing().runWithSpan("parent", () -> statement.executeQuery()); @@ -592,7 +592,7 @@ void testPreparedCall( String url, String table) throws SQLException { - connection = instrumentConnection(connection); + connection = wrap(connection); CallableStatement statement = connection.prepareCall(query); cleanup.deferCleanup(statement); ResultSet resultSet = testing().runWithSpan("parent", () -> statement.executeQuery()); @@ -731,7 +731,7 @@ void testStatementUpdate( String url, String table) throws SQLException { - connection = instrumentConnection(connection); + connection = wrap(connection); Statement statement = connection.createStatement(); cleanup.deferCleanup(statement); String sql = connection.nativeSQL(query); @@ -909,7 +909,7 @@ void testPreparedStatementUpdateImpl( String table, ThrowingConsumer action) throws SQLException { - connection = instrumentConnection(connection); + connection = wrap(connection); String sql = connection.nativeSQL(query); PreparedStatement statement = connection.prepareStatement(sql); cleanup.deferCleanup(statement); @@ -1083,7 +1083,7 @@ void testGetConnection( if (init != null) { init.accept(datasource); } - DataSource instrumentedDatasource = instrumentDataSource(datasource); + DataSource instrumentedDatasource = wrap(datasource); instrumentedDatasource.getConnection().close(); assertThat(testing().spans()).noneMatch(span -> span.getName().equals("database.connection")); @@ -1126,7 +1126,7 @@ void testGetConnection( @ValueSource(strings = "testing 123") void testGetClientInfoException(String query) throws SQLException { TestConnection rawConnection = new TestConnection("jdbc:testdb://localhost", false); - Connection connection = instrumentConnection(rawConnection); + Connection connection = wrap(rawConnection); cleanup.deferCleanup(connection); Statement statement = @@ -1214,7 +1214,7 @@ void testProduceProperSpanName( String table) throws SQLException { Driver driver = new TestDriver(); - Connection connection = instrumentConnection(driver.connect(url, null)); + Connection connection = wrap(driver.connect(url, null)); cleanup.deferCleanup(connection); Connection finalConnection = connection; @@ -1253,7 +1253,7 @@ void testProduceProperSpanName( void testConnectionCached(String connectionPoolName) throws SQLException { String dbType = "hsqldb"; DataSource rawDataSource = createDs(connectionPoolName, dbType, jdbcUrls.get(dbType)); - DataSource dataSource = instrumentDataSource(rawDataSource); + DataSource dataSource = wrap(rawDataSource); DataSource finalDataSource = dataSource; cleanup.deferCleanup( () -> { @@ -1347,7 +1347,7 @@ void testHandleRecursiveStatements( throws Exception { DbCallingConnection rawConnection = new DbCallingConnection(usePreparedStatementInConnection, "jdbc:testdb://localhost"); - Connection connection = instrumentConnection(rawConnection); + Connection connection = wrap(rawConnection); testing() .runWithSpan( @@ -1382,7 +1382,7 @@ void testHandleRecursiveStatements( @Test void testProxyStatement() throws Exception { Connection connection = - instrumentConnection(new org.h2.Driver().connect(jdbcUrls.get("h2"), null)); + wrap(new org.h2.Driver().connect(jdbcUrls.get("h2"), null)); Statement statement = connection.createStatement(); cleanup.deferCleanup(statement); cleanup.deferCleanup(connection); @@ -1410,7 +1410,7 @@ void testProxyStatement() throws Exception { @Test void testProxyPreparedStatement() throws SQLException { Connection connection = - instrumentConnection(new org.h2.Driver().connect(jdbcUrls.get("h2"), null)); + wrap(new org.h2.Driver().connect(jdbcUrls.get("h2"), null)); PreparedStatement statement = connection.prepareStatement("SELECT 3"); cleanup.deferCleanup(statement); cleanup.deferCleanup(connection); @@ -1480,7 +1480,7 @@ private void testBatchImpl( String tableName, ThrowingConsumer action) throws SQLException { - connection = instrumentConnection(connection); + connection = wrap(connection); Statement createTable = connection.createStatement(); createTable.execute("CREATE TABLE " + tableName + " (id INTEGER not NULL, PRIMARY KEY ( id ))"); cleanup.deferCleanup(createTable); @@ -1534,7 +1534,7 @@ DB_CONNECTION_STRING, emitStableDatabaseSemconv() ? null : url), @MethodSource("batchStream") void testMultiBatch(String system, Connection connection, String username, String url) throws SQLException { - connection = instrumentConnection(connection); + connection = wrap(connection); String tableName1 = "multi_batch_test_1"; String tableName2 = "multi_batch_test_2"; Statement createTable1 = connection.createStatement(); @@ -1594,7 +1594,7 @@ DB_CONNECTION_STRING, emitStableDatabaseSemconv() ? null : url), @MethodSource("batchStream") void testSingleItemBatch(String system, Connection connection, String username, String url) throws SQLException { - connection = instrumentConnection(connection); + connection = wrap(connection); String tableName = "single_item_batch_test"; Statement createTable = connection.createStatement(); createTable.execute("CREATE TABLE " + tableName + " (id INTEGER not NULL, PRIMARY KEY ( id ))"); @@ -1635,7 +1635,7 @@ DB_CONNECTION_STRING, emitStableDatabaseSemconv() ? null : url), @MethodSource("batchStream") void testPreparedBatch(String system, Connection connection, String username, String url) throws SQLException { - connection = instrumentConnection(connection); + connection = wrap(connection); String tableName = "prepared_batch_test"; Statement createTable = connection.createStatement(); createTable.execute("CREATE TABLE " + tableName + " (id INTEGER not NULL, PRIMARY KEY ( id ))"); @@ -1709,7 +1709,7 @@ void testSqlCommenterNotEnabled() throws SQLException { @MethodSource("transactionOperationsStream") void testCommitTransaction(String system, Connection connection, String username, String url) throws SQLException { - Connection instrumentedConnection = instrumentConnection(connection); + Connection instrumentedConnection = wrap(connection); Connection finalConnection = instrumentedConnection; String tableName = "TXN_COMMIT_TEST_" + system.toUpperCase(Locale.ROOT); @@ -1771,7 +1771,7 @@ DB_CONNECTION_STRING, emitStableDatabaseSemconv() ? null : url), @MethodSource("transactionOperationsStream") void testRollbackTransaction(String system, Connection connection, String username, String url) throws SQLException { - Connection instrumentedConnection = instrumentConnection(connection); + Connection instrumentedConnection = wrap(connection); Connection finalConnection = instrumentedConnection; String tableName = "TXN_ROLLBACK_TEST_" + system.toUpperCase(Locale.ROOT); @@ -1864,7 +1864,7 @@ private PreparedStatement wrapPreparedStatement(PreparedStatement statement) { @Test void testPreparedStatementWrapper() throws SQLException { Connection connection = - instrumentConnection(new org.h2.Driver().connect(jdbcUrls.get("h2"), null)); + wrap(new org.h2.Driver().connect(jdbcUrls.get("h2"), null)); Connection proxyConnection = ProxyStatementFactory.proxy( Connection.class, @@ -1905,7 +1905,7 @@ void testPreparedStatementWrapper() throws SQLException { @Test void testStatementWrapper() throws SQLException { Connection connection = - instrumentConnection(new org.h2.Driver().connect(jdbcUrls.get("h2"), null)); + wrap(new org.h2.Driver().connect(jdbcUrls.get("h2"), null)); Statement statement = connection.createStatement(); cleanup.deferCleanup(statement); cleanup.deferCleanup(connection); 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 index 24487b9679f9..6f084a54d689 100644 --- 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 @@ -53,7 +53,7 @@ public abstract class AbstractPreparedStatementParametersTest { protected abstract InstrumentationExtension testing(); - protected Connection instrumentConnection(Connection connection) throws SQLException { + protected Connection wrap(Connection connection) throws SQLException { return connection; } @@ -571,7 +571,7 @@ private void test( ThrowingConsumer setParameter, String expectedParameterValue) throws SQLException { - Connection instrumentedConnection = instrumentConnection(connection); + Connection instrumentedConnection = wrap(connection); PreparedStatement statement = instrumentedConnection.prepareStatement(query); cleanup.deferCleanup(statement); From 504cd8f347b3e621a649afce678b8ace6fb3c40d Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Fri, 24 Oct 2025 10:34:11 -0700 Subject: [PATCH 4/4] update --- .../instrumentation/jdbc/TestConnection.java | 15 +++++++++------ .../testing/AbstractJdbcInstrumentationTest.java | 2 +- .../jdbc/testing/DbCallingConnection.java | 8 ++------ 3 files changed, 12 insertions(+), 13 deletions(-) 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 52348e8fd009..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 @@ -30,27 +30,30 @@ public class TestConnection implements Connection { private final String url; - Consumer sqlConsumer = unused -> {}; + private final Consumer sqlConsumer; - public TestConnection() {} + public TestConnection() { + this(null, unused -> {}); + } public TestConnection(String url) { - this.url = 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"); } } - public TestConnection(String url, boolean throwException) { - this(throwException); + private TestConnection(String url, Consumer sqlConsumer) { this.url = url; + this.sqlConsumer = sqlConsumer; } @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 index c238b2e00fc2..0ba478faca92 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 @@ -1125,7 +1125,7 @@ void testGetConnection( @DisplayName("test getClientInfo exception") @ValueSource(strings = "testing 123") void testGetClientInfoException(String query) throws SQLException { - TestConnection rawConnection = new TestConnection("jdbc:testdb://localhost", false); + TestConnection rawConnection = new TestConnection("jdbc:testdb://localhost"); Connection connection = wrap(rawConnection); cleanup.deferCleanup(connection); diff --git a/instrumentation/jdbc/testing/src/main/java/io/opentelemetry/instrumentation/jdbc/testing/DbCallingConnection.java b/instrumentation/jdbc/testing/src/main/java/io/opentelemetry/instrumentation/jdbc/testing/DbCallingConnection.java index 88d6ac84d6df..41a2f90be7e1 100644 --- a/instrumentation/jdbc/testing/src/main/java/io/opentelemetry/instrumentation/jdbc/testing/DbCallingConnection.java +++ b/instrumentation/jdbc/testing/src/main/java/io/opentelemetry/instrumentation/jdbc/testing/DbCallingConnection.java @@ -10,14 +10,10 @@ import java.sql.SQLException; class DbCallingConnection extends TestConnection { - final boolean usePreparedStatement; - - DbCallingConnection(boolean usePreparedStatement) { - this(usePreparedStatement, null); - } + private final boolean usePreparedStatement; DbCallingConnection(boolean usePreparedStatement, String url) { - super(url, false); + super(url); this.usePreparedStatement = usePreparedStatement; }