content: /*
   * Copyright The OpenTelemetry Authors
   * SPDX-License-Identifier: Apache-2.0
   */
  
  package io.opentelemetry.javaagent.instrumentation.hibernate.v7_0;
  
  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.equalTo;
  import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies;
  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 org.junit.jupiter.api.Assertions.assertNotEquals;
  import static org.junit.jupiter.api.Named.named;
  
  import io.opentelemetry.api.common.AttributeKey;
  import io.opentelemetry.api.trace.SpanKind;
  import io.opentelemetry.sdk.testing.assertj.SpanDataAssert;
  import io.opentelemetry.sdk.trace.data.SpanData;
  import io.opentelemetry.sdk.trace.data.StatusData;
  import jakarta.persistence.Timeout;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.List;
  import java.util.Locale;
  import java.util.concurrent.atomic.AtomicReference;
  import java.util.function.BiConsumer;
  import java.util.function.Consumer;
  import java.util.function.Function;
  import java.util.stream.Stream;
  import org.hibernate.LockMode;
  import org.hibernate.ReplicationMode;
  import org.hibernate.Session;
  import org.hibernate.StatelessSession;
  import org.hibernate.UnknownEntityTypeException;
  import org.hibernate.query.SelectionQuery;
  import org.junit.jupiter.api.Test;
  import org.junit.jupiter.params.ParameterizedTest;
  import org.junit.jupiter.params.provider.Arguments;
  import org.junit.jupiter.params.provider.MethodSource;
  
  @SuppressWarnings("deprecation") // testing instrumentation of deprecated class
  class SessionTest extends AbstractHibernateTest {
    @ParameterizedTest(name = "{index}: {0}")
    @MethodSource("provideHibernateActionParameters")
    void testHibernateAction(Parameter parameter) {
      Session session = sessionFactory.openSession();
      session.beginTransaction();
  
      Value value = session.merge(prepopulated.get(0));
      testing.waitForTraces(1);
      testing.clearData();
  
      testing.runWithSpan(
          "parent",
          () -> {
            try {
              parameter.sessionMethodTest.accept(session, value);
            } catch (RuntimeException e) {
              // We expected this, we should see the error field set on the span.
            }
            session.getTransaction().commit();
            session.close();
          });
      assertTraces(parameter, "refresh".equals(parameter.methodName));
    }
  
    @ParameterizedTest(name = "{index}: {0}")
    @MethodSource("provideHibernateActionWithStatelessSessionParameters")
    void testHibernateActionStatelessSession(Parameter parameter) {
      testing.runWithSpan(
          "parent",
          () -> {
            StatelessSession session = sessionFactory.openStatelessSession();
            session.beginTransaction();
            try {
              parameter.statelessSessionMethodTest.accept(session, prepopulated.get(0));
            } catch (RuntimeException e) {
              // We expected this, we should see the error field set on the span.
            }
            session.getTransaction().commit();
            session.close();
          });
      assertTraces(parameter, true);
    }
  
    private static void assertTraces(Parameter parameter, boolean hasClientSpan) {
      testing.waitAndAssertTraces(
          trace -> {
            List<Consumer<SpanDataAssert>> assertions = new ArrayList<>();
            assertions.add(span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent());
            assertions.add(
                span ->
                    assertSessionSpan(
                        span,
                        trace.getSpan(0),
                        "Session." + parameter.methodName + " " + parameter.resource));
            if (hasClientSpan) {
              assertions.add(span -> assertClientSpan(span, trace.getSpan(1)));
            }
            assertions.add(
                span ->
                    assertSpanWithSessionId(
                        span,
                        trace.getSpan(0),
                        "Transaction.commit",
                        trace
                            .getSpan(1)
                            .getAttributes()
                            .get(AttributeKey.stringKey("hibernate.session_id"))));
  
            trace.hasSpansSatisfyingExactly(assertions);
          });
    }
  
    @ParameterizedTest(name = "{index}: {0}")
    @MethodSource("provideHibernateReplicateParameters")
    void testHibernateReplicate(Parameter parameter) {
      testing.runWithSpan(
          "parent",
          () -> {
            Session session = sessionFactory.openSession();
            session.beginTransaction();
            try {
              parameter.sessionMethodTest.accept(session, prepopulated.get(0));
            } catch (RuntimeException e) {
              // We expected this, we should see the error field set on the span.
            }
            session.getTransaction().commit();
            session.close();
          });
  
      testing.waitAndAssertTraces(
          trace ->
              trace.hasSpansSatisfyingExactly(
                  span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(),
                  span ->
                      assertSessionSpan(
                          span,
                          trace.getSpan(0),
                          "Session." + parameter.methodName + " " + parameter.resource),
                  span -> assertClientSpan(span, trace.getSpan(1), "SELECT"),
                  span ->
                      assertSpanWithSessionId(
                          span,
                          trace.getSpan(0),
                          "Transaction.commit",
                          trace
                              .getSpan(1)
                              .getAttributes()
                              .get(AttributeKey.stringKey("hibernate.session_id"))),
                  span -> assertClientSpan(span, trace.getSpan(3))));
    }
  
    @Test
    void testHibernateFailedReplicate() {
      testing.runWithSpan(
          "parent",
          () -> {
            Session session = sessionFactory.openSession();
            session.beginTransaction();
            try {
              session.replicate(
                  Long.valueOf(123) /* Not a valid entity */, ReplicationMode.OVERWRITE);
            } catch (RuntimeException e) {
              // We expected this, we should see the error field set on the span.
            }
            session.getTransaction().commit();
            session.close();
          });
  
      testing.waitAndAssertTraces(
          trace ->
              trace.hasSpansSatisfyingExactly(
                  span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(),
                  span ->
                      span.hasName("Session.replicate java.lang.Long")
                          .hasKind(SpanKind.INTERNAL)
                          .hasParent(trace.getSpan(0))
                          .hasStatus(StatusData.error())
                          .hasException(new UnknownEntityTypeException("java.lang.Long"))
                          .hasAttributesSatisfyingExactly(
                              satisfies(
                                  AttributeKey.stringKey("hibernate.session_id"),
                                  val -> val.isInstanceOf(String.class))),
                  span ->
                      assertSpanWithSessionId(
                          span,
                          trace.getSpan(0),
                          "Transaction.commit",
                          trace
                              .getSpan(1)
                              .getAttributes()
                              .get(AttributeKey.stringKey("hibernate.session_id")))));
    }
  
    @ParameterizedTest(name = "{index}: {0}")
    @MethodSource("provideHibernateCommitActionParameters")
    void testHibernateCommitAction(Parameter parameter) {
      testing.runWithSpan(
          "parent",
          () -> {
            Session session = sessionFactory.openSession();
            session.beginTransaction();
            try {
              parameter.sessionMethodTest.accept(session, prepopulated.get(0));
            } catch (RuntimeException e) {
              // We expected this, we should see the error field set on the span.
            }
            session.getTransaction().commit();
            session.close();
          });
  
      testing.waitAndAssertTraces(
          trace ->
              trace.hasSpansSatisfyingExactly(
                  span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(),
                  span ->
                      assertSessionSpan(
                          span,
                          trace.getSpan(0),
                          "Session." + parameter.methodName + " " + parameter.resource),
                  span ->
                      assertSpanWithSessionId(
                          span,
                          trace.getSpan(0),
                          "Transaction.commit",
                          trace
                              .getSpan(1)
                              .getAttributes()
                              .get(AttributeKey.stringKey("hibernate.session_id"))),
                  span -> assertClientSpan(span, trace.getSpan(2))));
    }
  
    @SuppressWarnings("deprecation") // TODO DB_CONNECTION_STRING deprecation
    @ParameterizedTest(name = "{index}: {0}")
    @MethodSource("provideAttachesStateToQueryParameters")
    void testAttachesStateToQuery(Parameter parameter) {
      testing.runWithSpan(
          "parent",
          () -> {
            Session session = sessionFactory.openSession();
            session.beginTransaction();
            SelectionQuery<?> query = parameter.queryBuildMethod.apply(session);
            query.list();
            session.getTransaction().commit();
            session.close();
          });
  
      testing.waitAndAssertTraces(
          trace ->
              trace.hasSpansSatisfyingExactly(
                  span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(),
                  span -> assertSessionSpan(span, trace.getSpan(0), parameter.resource),
                  span ->
                      span.hasKind(SpanKind.CLIENT)
                          .hasParent(trace.getSpan(1))
                          .hasAttributesSatisfyingExactly(
                              equalTo(maybeStable(DB_SYSTEM), maybeStableDbSystemName("h2")),
                              equalTo(maybeStable(DB_NAME), "db1"),
                              equalTo(DB_USER, emitStableDatabaseSemconv() ? null : "sa"),
                              equalTo(
                                  DB_CONNECTION_STRING,
                                  emitStableDatabaseSemconv() ? null : "h2:mem:"),
                              satisfies(
                                  maybeStable(DB_STATEMENT), val -> val.isInstanceOf(String.class)),
                              equalTo(maybeStable(DB_OPERATION), "SELECT"),
                              equalTo(maybeStable(DB_SQL_TABLE), "Value")),
                  span ->
                      assertSpanWithSessionId(
                          span,
                          trace.getSpan(0),
                          "Transaction.commit",
                          trace
                              .getSpan(1)
                              .getAttributes()
                              .get(AttributeKey.stringKey("hibernate.session_id")))));
    }
  
    @Test
    void testHibernateOverlappingSessions() {
      AtomicReference<String> sessionId1 = new AtomicReference<>();
      AtomicReference<String> sessionId2 = new AtomicReference<>();
      AtomicReference<String> sessionId3 = new AtomicReference<>();
  
      testing.runWithSpan(
          "overlapping Sessions",
          () -> {
            Session session1 = sessionFactory.openSession();
            session1.beginTransaction();
            StatelessSession session2 = sessionFactory.openStatelessSession();
            Session session3 = sessionFactory.openSession();
  
            Value value1 = new Value("Value 1");
            session1.merge(value1);
            session2.insert(new Value("Value 2"));
            session3.merge(new Value("Value 3"));
            session1.remove(value1);
  
            session2.close();
            session1.getTransaction().commit();
            session1.close();
            session3.close();
          });
  
      testing.waitAndAssertTraces(
          trace ->
              trace.hasSpansSatisfyingExactly(
                  span -> span.hasName("overlapping Sessions"),
                  span -> {
                    assertSessionSpan(
                        span,
                        trace.getSpan(0),
                        "Session.merge io.opentelemetry.javaagent.instrumentation.hibernate.v7_0.Value");
                    sessionId1.set(
                        trace
                            .getSpan(1)
                            .getAttributes()
                            .get(AttributeKey.stringKey("hibernate.session_id")));
                  },
                  span -> {
                    assertSessionSpan(
                        span,
                        trace.getSpan(0),
                        "Session.insert io.opentelemetry.javaagent.instrumentation.hibernate.v7_0.Value");
                    sessionId2.set(
                        trace
                            .getSpan(2)
                            .getAttributes()
                            .get(AttributeKey.stringKey("hibernate.session_id")));
                  },
                  span -> assertClientSpan(span, trace.getSpan(2), "INSERT"),
                  span -> {
                    assertSessionSpan(
                        span,
                        trace.getSpan(0),
                        "Session.merge io.opentelemetry.javaagent.instrumentation.hibernate.v7_0.Value");
                    sessionId3.set(
                        trace
                            .getSpan(4)
                            .getAttributes()
                            .get(AttributeKey.stringKey("hibernate.session_id")));
                  },
                  span ->
                      assertSpanWithSessionId(
                          span,
                          trace.getSpan(0),
                          "Session.remove io.opentelemetry.javaagent.instrumentation.hibernate.v7_0.Value",
                          sessionId1.get()),
                  span ->
                      assertSpanWithSessionId(
                          span, trace.getSpan(0), "Transaction.commit", sessionId1.get()),
                  span -> assertClientSpan(span, trace.getSpan(6), "INSERT")));
  
      assertNotEquals(sessionId1.get(), sessionId2.get());
      assertNotEquals(sessionId2.get(), sessionId3.get());
      assertNotEquals(sessionId1.get(), sessionId3.get());
    }
  
    private static Stream<Arguments> provideHibernateActionParameters() {
      List<BiConsumer<Session, Value>> sessionMethodTests =
          Arrays.asList(
              (session, val) -> session.lock(val, LockMode.READ),
              (session, val) -> session.lock(val, LockMode.READ, Timeout.seconds(1)),
              (session, val) -> session.refresh(val),
              (session, val) -> session.find(Value.class, val.getId()));
  
      return Stream.of(
          Arguments.of(
              named(
                  "lock",
                  new Parameter(
                      "lock",
                      "io.opentelemetry.javaagent.instrumentation.hibernate.v7_0.Value",
                      null,
                      sessionMethodTests.get(0),
                      null))),
          Arguments.of(
              named(
                  "lock with options",
                  new Parameter(
                      "lock",
                      "io.opentelemetry.javaagent.instrumentation.hibernate.v7_0.Value",
                      null,
                      sessionMethodTests.get(1),
                      null))),
          Arguments.of(
              named(
                  "refresh",
                  new Parameter(
                      "refresh",
                      "io.opentelemetry.javaagent.instrumentation.hibernate.v7_0.Value",
                      null,
                      sessionMethodTests.get(2),
                      null))),
          Arguments.of(
              named(
                  "find",
                  new Parameter(
                      "find",
                      "io.opentelemetry.javaagent.instrumentation.hibernate.v7_0.Value",
                      null,
                      sessionMethodTests.get(3),
                      null))));
    }
  
    private static Stream<Arguments> provideHibernateActionWithStatelessSessionParameters() {
      List<BiConsumer<StatelessSession, Value>> statelessSessionMethodTests =
          Arrays.asList(
              (statelessSession, val) -> statelessSession.refresh(val),
              (statelessSession, val) ->
                  statelessSession.refresh(
                      "io.opentelemetry.javaagent.instrumentation.hibernate.v7_0.Value", val),
              (statelessSession, val) ->
                  statelessSession.get(
                      "io.opentelemetry.javaagent.instrumentation.hibernate.v7_0.Value", val.getId()),
              (statelessSession, val) -> statelessSession.get(Value.class, val.getId()),
              (statelessSession, val) -> statelessSession.insert(new Value("insert me")),
              (statelessSession, val) ->
                  statelessSession.insert(
                      "io.opentelemetry.javaagent.instrumentation.hibernate.v7_0.Value",
                      new Value("insert me")),
              (statelessSession, val) -> statelessSession.insert(null, new Value("insert me")),
              (statelessSession, val) -> {
                val.setName("New name");
                statelessSession.update(val);
              },
              (statelessSession, val) -> {
                val.setName("New name");
                statelessSession.update(
                    "io.opentelemetry.javaagent.instrumentation.hibernate.v7_0.Value", val);
              },
              (statelessSession, val) -> {
                statelessSession.delete(val);
                prepopulated.remove(val);
              },
              (statelessSession, val) -> {
                statelessSession.delete(
                    "io.opentelemetry.javaagent.instrumentation.hibernate.v7_0.Value", val);
                prepopulated.remove(val);
              });
  
      return Stream.of(
          Arguments.of(
              named(
                  "refresh",
                  new Parameter(
                      "refresh",
                      "io.opentelemetry.javaagent.instrumentation.hibernate.v7_0.Value",
                      null,
                      null,
                      statelessSessionMethodTests.get(0)))),
          Arguments.of(
              named(
                  "refresh with entity name",
                  new Parameter(
                      "refresh",
                      "io.opentelemetry.javaagent.instrumentation.hibernate.v7_0.Value",
                      null,
                      null,
                      statelessSessionMethodTests.get(1)))),
          Arguments.of(
              named(
                  "get with entity name",
                  new Parameter(
                      "get",
                      "io.opentelemetry.javaagent.instrumentation.hibernate.v7_0.Value",
                      null,
                      null,
                      statelessSessionMethodTests.get(2)))),
          Arguments.of(
              named(
                  "get with entity class",
                  new Parameter(
                      "get",
                      "io.opentelemetry.javaagent.instrumentation.hibernate.v7_0.Value",
                      null,
                      null,
                      statelessSessionMethodTests.get(3)))),
          Arguments.of(
              named(
                  "insert",
                  new Parameter(
                      "insert",
                      "io.opentelemetry.javaagent.instrumentation.hibernate.v7_0.Value",
                      null,
                      null,
                      statelessSessionMethodTests.get(4)))),
          Arguments.of(
              named(
                  "insert with entity name",
                  new Parameter(
                      "insert",
                      "io.opentelemetry.javaagent.instrumentation.hibernate.v7_0.Value",
                      null,
                      null,
                      statelessSessionMethodTests.get(5)))),
          Arguments.of(
              named(
                  "insert with null entity name",
                  new Parameter(
                      "insert",
                      "io.opentelemetry.javaagent.instrumentation.hibernate.v7_0.Value",
                      null,
                      null,
                      statelessSessionMethodTests.get(6)))),
          Arguments.of(
              named(
                  "update (StatelessSession)",
                  new Parameter(
                      "update",
                      "io.opentelemetry.javaagent.instrumentation.hibernate.v7_0.Value",
                      null,
                      null,
                      statelessSessionMethodTests.get(7)))),
          Arguments.of(
              named(
                  "update with entity name (StatelessSession)",
                  new Parameter(
                      "update",
                      "io.opentelemetry.javaagent.instrumentation.hibernate.v7_0.Value",
                      null,
                      null,
                      statelessSessionMethodTests.get(8)))),
          Arguments.of(
              named(
                  "delete (Session)",
                  new Parameter(
                      "delete",
                      "io.opentelemetry.javaagent.instrumentation.hibernate.v7_0.Value",
                      null,
                      null,
                      statelessSessionMethodTests.get(9)))),
          Arguments.of(
              named(
                  "delete with entity name (Session)",
                  new Parameter(
                      "delete",
                      "io.opentelemetry.javaagent.instrumentation.hibernate.v7_0.Value",
                      null,
                      null,
                      statelessSessionMethodTests.get(10)))));
    }
  
    private static Stream<Arguments> provideHibernateReplicateParameters() {
      List<BiConsumer<Session, Value>> sessionMethodTests =
          Arrays.asList(
              (session, val) -> {
                Value replicated = new Value(val.getName() + " replicated");
                replicated.setId(val.getId());
                session.replicate(replicated, ReplicationMode.OVERWRITE);
              },
              (session, val) -> {
                Value replicated = new Value(val.getName() + " replicated");
                replicated.setId(val.getId());
                session.replicate("Value", replicated, ReplicationMode.OVERWRITE);
              });
  
      return Stream.of(
          Arguments.of(
              named(
                  "replicate",
                  new Parameter(
                      "replicate",
                      "io.opentelemetry.javaagent.instrumentation.hibernate.v7_0.Value",
                      null,
                      sessionMethodTests.get(0),
                      null))),
          Arguments.of(
              named(
                  "replicate by entityName",
                  new Parameter("replicate", "Value", null, sessionMethodTests.get(1), null))));
    }
  
    private static Stream<Arguments> provideHibernateCommitActionParameters() {
      List<BiConsumer<Session, Value>> sessionMethodTests =
          Arrays.asList(
              (session, val) -> session.merge(new Value("merge me in")),
              (session, val) -> session.merge("Value", new Value("merge me in")),
              (session, val) -> session.persist(new Value("merge me in")),
              (session, val) -> session.persist("Value", new Value("merge me in")),
              (session, val) -> session.persist(null, new Value("merge me in")),
              (session, val) -> {
                session.remove(val);
                prepopulated.remove(val);
              });
  
      return Stream.of(
          Arguments.of(
              named(
                  "merge",
                  new Parameter(
                      "merge",
                      "io.opentelemetry.javaagent.instrumentation.hibernate.v7_0.Value",
                      null,
                      sessionMethodTests.get(0),
                      null))),
          Arguments.of(
              named(
                  "merge with entity name",
                  new Parameter("merge", "Value", null, sessionMethodTests.get(1), null))),
          Arguments.of(
              named(
                  "persist",
                  new Parameter(
                      "persist",
                      "io.opentelemetry.javaagent.instrumentation.hibernate.v7_0.Value",
                      null,
                      sessionMethodTests.get(2),
                      null))),
          Arguments.of(
              named(
                  "persist with entity name",
                  new Parameter("persist", "Value", null, sessionMethodTests.get(3), null))),
          Arguments.of(
              named(
                  "persist with null entity name",
                  new Parameter(
                      "persist",
                      "io.opentelemetry.javaagent.instrumentation.hibernate.v7_0.Value",
                      null,
                      sessionMethodTests.get(4),
                      null))),
          Arguments.of(
              named(
                  "remove",
                  new Parameter(
                      "remove",
                      "io.opentelemetry.javaagent.instrumentation.hibernate.v7_0.Value",
                      null,
                      sessionMethodTests.get(5),
                      null))));
    }
  
    private static Stream<Arguments> provideAttachesStateToQueryParameters() {
      List<Function<Session, SelectionQuery<?>>> queryBuildMethods =
          Arrays.asList(
              session -> session.createQuery("from Value"),
              session -> session.getNamedQuery("TestNamedQuery"),
              session -> session.createNativeQuery("SELECT * FROM Value"),
              session -> session.createSelectionQuery("from Value"));
  
      return Stream.of(
          Arguments.of(
              named(
                  "createQuery",
                  new Parameter(
                      "createQuery",
                      "SELECT io.opentelemetry.javaagent.instrumentation.hibernate.v7_0.Value",
                      queryBuildMethods.get(0),
                      null,
                      null))),
          Arguments.of(
              named(
                  "getNamedQuery",
                  new Parameter(
                      "getNamedQuery",
                      "SELECT io.opentelemetry.javaagent.instrumentation.hibernate.v7_0.Value",
                      queryBuildMethods.get(1),
                      null,
                      null))),
          Arguments.of(
              named(
                  "createNativeQuery",
                  new Parameter(
                      "createNativeQuery", "SELECT Value", queryBuildMethods.get(2), null, null))),
          Arguments.of(
              named(
                  "createSelectionQuery",
                  new Parameter(
                      "createSelectionQuery",
                      "SELECT io.opentelemetry.javaagent.instrumentation.hibernate.v7_0.Value",
                      queryBuildMethods.get(3),
                      null,
                      null))));
    }
  
    private static class Parameter {
      Parameter(
          String methodName,
          String resource,
          Function<Session, SelectionQuery<?>> queryBuildMethod,
          BiConsumer<Session, Value> sessionMethodTest,
          BiConsumer<StatelessSession, Value> statelessSessionMethodTest) {
        this.methodName = methodName;
        this.resource = resource;
        this.sessionMethodTest = sessionMethodTest;
        this.queryBuildMethod = queryBuildMethod;
        this.statelessSessionMethodTest = statelessSessionMethodTest;
      }
  
      public final String methodName;
      public final String resource;
      public final Function<Session, SelectionQuery<?>> queryBuildMethod;
      public final BiConsumer<Session, Value> sessionMethodTest;
      public final BiConsumer<StatelessSession, Value> statelessSessionMethodTest;
    }
  
    private static SpanDataAssert assertSessionSpan(
        SpanDataAssert span, SpanData parent, String spanName) {
      return span.hasName(spanName)
          .hasKind(SpanKind.INTERNAL)
          .hasParent(parent)
          .hasAttributesSatisfyingExactly(
              satisfies(
                  AttributeKey.stringKey("hibernate.session_id"),
                  val -> val.isInstanceOf(String.class)));
    }
  
    private static SpanDataAssert assertSpanWithSessionId(
        SpanDataAssert span, SpanData parent, String spanName, String sessionId) {
      return span.hasName(spanName)
          .hasKind(SpanKind.INTERNAL)
          .hasParent(parent)
          .hasAttributesSatisfyingExactly(
              equalTo(AttributeKey.stringKey("hibernate.session_id"), sessionId));
    }
  
    @SuppressWarnings("deprecation") // TODO DB_CONNECTION_STRING deprecation
    private static SpanDataAssert assertClientSpan(SpanDataAssert span, SpanData parent) {
      return span.hasKind(SpanKind.CLIENT)
          .hasParent(parent)
          .hasAttributesSatisfyingExactly(
              equalTo(maybeStable(DB_SYSTEM), maybeStableDbSystemName("h2")),
              equalTo(maybeStable(DB_NAME), "db1"),
              equalTo(DB_USER, emitStableDatabaseSemconv() ? null : "sa"),
              equalTo(DB_CONNECTION_STRING, emitStableDatabaseSemconv() ? null : "h2:mem:"),
              satisfies(maybeStable(DB_STATEMENT), val -> val.isInstanceOf(String.class)),
              satisfies(maybeStable(DB_OPERATION), val -> val.isInstanceOf(String.class)),
              equalTo(maybeStable(DB_SQL_TABLE), "Value"));
    }
  
    @SuppressWarnings("deprecation") // TODO DB_CONNECTION_STRING deprecation
    private static SpanDataAssert assertClientSpan(
        SpanDataAssert span, SpanData parent, String verb) {
      return span.hasName(verb.concat(" db1.Value"))
          .hasKind(SpanKind.CLIENT)
          .hasParent(parent)
          .hasAttributesSatisfyingExactly(
              equalTo(maybeStable(DB_SYSTEM), maybeStableDbSystemName("h2")),
              equalTo(maybeStable(DB_NAME), "db1"),
              equalTo(DB_USER, emitStableDatabaseSemconv() ? null : "sa"),
              equalTo(DB_CONNECTION_STRING, emitStableDatabaseSemconv() ? null : "h2:mem:"),
              satisfies(
                  maybeStable(DB_STATEMENT),
                  stringAssert -> stringAssert.startsWith(verb.toLowerCase(Locale.ROOT))),
              equalTo(maybeStable(DB_OPERATION), verb),
              equalTo(maybeStable(DB_SQL_TABLE), "Value"));
    }
  }
  