class StatementInvocationHandler implements InvocationHandler {

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    checkNotNull(method);

    if (!method.getName().startsWith("execute")) {
      // Intentionally not tracing this call as no statement is executed.
      try {
        return method.invoke(statement, args);
      } catch (InvocationTargetException e) {
        // Rethrow the exception from the underlying method.
        throw e.getCause();
      }
    }

    Optional<String> sql;
    if (method.getParameterTypes().length > 0 && method.getParameterTypes()[0] == String.class) {
      sql = Optional.fromNullable((String) args[0]);
    } else if (this.sql.isPresent()) {
      sql = this.sql;
    } else {
      sql = Optional.absent();
    }

    try (TraceService.Span span = traceService.open("JDBC.Statement#" + method.getName())) {
      if (sql.isPresent()) {
        span.annotate(Label.SQL_TEXT, traceOptions.sqlScrubber().apply(sql.get()));
      }

      try {
        return method.invoke(statement, args);
      } catch (InvocationTargetException e) {
        // Rethrow the exception from the underlying method.
        throw e.getCause();
      }
    }
  }

    StatementInvocationHandler(
      Statement statement,
      TraceOptions traceOptions,
      TraceService traceService,
      Optional<String> sql);

}