/*
  Copyright (c) 2003-2025 YourKit GmbH
  All rights reserved.

  Redistribution and use in source and binary forms, with or without
  modification, are permitted provided that the following conditions are met:

  * Redistributions of source code must retain the above copyright
    notice, this list of conditions and the following disclaimer.

  * Redistributions in binary form must reproduce the above copyright
    notice, this list of conditions and the following disclaimer in the
    documentation and/or other materials provided with the distribution.

  * Neither the name of YourKit nor the
    names of its contributors may be used to endorse or promote products
    derived from this software without specific prior written permission.

  THIS SOFTWARE IS PROVIDED BY YOURKIT "AS IS" AND ANY
  EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  DISCLAIMED. IN NO EVENT SHALL YOURKIT BE LIABLE FOR ANY
  DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

package com.yourkit.probes.builtin;

import com.yourkit.asserts.NotNull;
import com.yourkit.asserts.Nullable;
import com.yourkit.probes.*;

import static com.yourkit.probes.ReflectionUtil.callMethod0;
import static com.yourkit.probes.ReflectionUtil.getFieldValue;

public final class Databases {
  private static final Object ourLock = new Object();

  private static final WeakKeyMap<Object, String> ourDataSource2Context = new WeakKeyMap<>();

  /**
   * Non-prepared statement's batch
   */
  private static final WeakKeyMap<Object, StringBuilder> ourStatement2Batch = new WeakKeyMap<>();

  private static final MasterResourceRegistry<Object> ourConnections = new MasterResourceRegistry<Object>(
    Databases.class,
    DatabasesConstants.TABLE_NAME,
    "Database" // column name
  ) {
    @Nullable
    @Override
    protected String retrieveResourceName(@NotNull final Object connection) {
      return getConnectionUrl(connection);
    }
  };

  /**
   * Map Statement to Connection
   */
  private static final DependentResourceRegistry<Object, Object> ourStatements = new DependentResourceRegistry<Object, Object>(
    ourConnections,
    "Statement",
    null // we don't need a string column to identify non-prepared statements
  ) {
    @Nullable
    @Override
    protected Object retrieveParent(@NotNull final Object statement) {
      return getConnectionByStatement(statement);
    }
  };

  private static final class QueryTable extends Table {
    private final StringColumn mySQL = new StringColumn("SQL");

    public QueryTable() {
      super(ourStatements.getResourceTable(), "Query", Table.MASK_FOR_LASTING_EVENTS);
    }
  }
  private static final QueryTable T_QUERY = new QueryTable();

  /**
   * Map PreparedStatement to Connection
   */
  private static final DependentResourceRegistry<Object, Object> ourPreparedStatements = new DependentResourceRegistry<Object, Object>(
    ourConnections,
    "Prepared Statement",
    "SQL" // column name
  ) {
    @Nullable
    @Override
    protected Object retrieveParent(@NotNull final Object preparedStatement) {
      return getConnectionByStatement(preparedStatement);
    }
  };

  private static final class PreparedStatementQueryTable extends Table {
    public PreparedStatementQueryTable() {
      super(ourPreparedStatements.getResourceTable(), "Query", Table.MASK_FOR_LASTING_EVENTS);
    }
  }
  private static final PreparedStatementQueryTable T_PREPARED_STATEMENT_QUERY = new PreparedStatementQueryTable();


  private static final String BATCH_OPERATION_ADD = "Add";
  private static final String BATCH_OPERATION_CLEAR = "Clear";

  private static final class PreparedStatementBatchTable extends Table {
    private final StringColumn myOperation = new StringColumn("Operation");

    public PreparedStatementBatchTable() {
      super(ourPreparedStatements.getResourceTable(), "Batch", Table.MASK_FOR_LASTING_EVENTS);
    }
  }
  private static final PreparedStatementBatchTable T_PREPARED_STATEMENT_BATCH = new PreparedStatementBatchTable();

  private static final class BatchTable extends Table {
    private final StringColumn myOperation = new StringColumn("Operation");
    private final StringColumn mySQL = new StringColumn("SQL");

    public BatchTable() {
      super(ourStatements.getResourceTable(), "Batch", Table.MASK_FOR_LASTING_EVENTS);
    }
  }
  private static final BatchTable T_BATCH = new BatchTable();


  @MethodPattern(
    "java.sql.DriverManager:getConnection(String, java.util.Properties, java.lang.ClassLoader)"
  )
  public static final class DriverManager_getConnection_Probe {
    public static long onEnter() {
      return ourConnections.openOnEnter();
    }

    public static void onExit(
      @OnEnterResult final long resourceID,
      @ReturnValue @Nullable final Object connection,
      @ThrownException @Nullable final Throwable e,
      @Param(1) final String database
    ) {
      ourConnections.openOnExit(
        resourceID,
        database,
        connection, // sic! we are sure this is a Connection because of the method pattern
        e,
        FailedEventPolicy.DISCARD
      );
    }
  }

  @RetransformIfInstanceOf("java.sql.Driver")
  @MethodPattern(
    {
      "*:connect(String, java.util.Properties)",
      "-org.springframework.jdbc.*:*(*)"
    }
  )
  public static final class Driver_connect_Probe {
    public static long onEnter(@This final Object driver) {
      if (!isDriver(getMask(driver))) {
        return Table.NO_ROW;
      }

      return ourConnections.openOnEnter();
    }

    public static void onExit(
      @OnEnterResult final long resourceID,
      @Param(1) final String url,
      @ThrownException @Nullable final Throwable e,
      @ReturnValue @Nullable final Object connection
    ) {
      if (resourceID == Table.NO_ROW) {
        // micro optimization
        return;
      }

      ourConnections.openOnExit(
        resourceID,
        url,
        connection, // sic! we are sure this is a Connection because of it's a Driver interface method
        e,
        FailedEventPolicy.DISCARD
      );
    }
  }

  @MethodPattern("javax.naming.InitialContext:lookup(String)")
  public static final class InitialContext_lookup_Probe {
    public static void onReturn(
      @ReturnValue @Nullable final Object returnValue,
      @Param(1) final String context
    ) {
      if (context == null || !isDataSource(getMask(returnValue))) {
        return;
      }

      synchronized (ourLock) {
        ourDataSource2Context.put(returnValue, context);
      }
    }
  }

  @MethodPattern(
    {
      "org.apache.commons.dbcp.BasicDataSource:setUrl(String)",
      "org.apache.derby.jdbc.ClientBaseDataSource:setDatabaseName(String)"
    }
  )
  public static final class DataSource_setName_Probe {
    public static void onReturn(
      @This final Object dataSource,
      @Param(1) final String url
    ) {
      if (url == null || !isDataSource(getMask(dataSource))) {
        return;
      }

      synchronized (ourLock) {
        ourDataSource2Context.put(dataSource, url);
      }
    }
  }

  @RetransformIfInstanceOf(
    {
      "javax.sql.DataSource",
      "javax.sql.PooledConnection"
    }
  )
  @MethodPattern(
    {
      "*:getConnection()",
      "-org.springframework.jdbc.*:*(*)"
    }
  )
  public static final class DataSource_or_PooledConnection_getConnection_Probe {
    public static long onEnter(@This final Object o) {
      final int mask = getMask(o);
      if (!isDataSource(mask) && !isPooledConnection(mask)) {
        return Table.NO_ROW;
      }

      return ourConnections.openOnEnter();
    }

    public static void onExit(
      @OnEnterResult final long resourceID,
      @This final Object o,
      @ReturnValue @Nullable final Object connection,
      @ThrownException @Nullable final Throwable e
    ) {
      if (resourceID == Table.NO_ROW) {
        // micro optimization
        return;
      }

      final String nameFromDataSource;
      if (isDataSource(getMask(o))) {
        synchronized (ourLock) {
          nameFromDataSource = ourDataSource2Context.get(o);
        }
      }
      else {
        // the name will be obtained with retrieveResourceName()
        nameFromDataSource = null;
      }

      ourConnections.openOnExit(
        resourceID,
        nameFromDataSource,
        connection, // sic! we are sure this is a Connection because it's a return value from interface method
        e,
        FailedEventPolicy.DISCARD
      );
    }
  }

  @RetransformIfInstanceOf("java.sql.Statement")
  @MethodPattern(
    {
      "*:execute(String, int) boolean",
      "*:execute(String) boolean",
      "*:execute(String, int[]) boolean",
      "*:execute(String, String[]) boolean",
      "*:executeQuery(String) java.sql.ResultSet",
      "*:executeUpdate(String, int[]) int",
      "*:executeUpdate(String) int",
      "*:executeUpdate(String, int) int",
      "*:executeUpdate(String, String[]) int",
      "-*StatementWrapper*:*(*)"
    }
  )
  public static final class Statement_execute_Probe {
    public static int onEnter(
      @This final Object statement,
      @Param(1) final String query
    ) {
      final int mask = getMask(statement);
      if (!isStatement(mask) || isPreparedStatement(mask)) {
        return Table.NO_ROW;
      }

      final int statementRowIndex = ourStatements.getOrCreate(statement);
      if (Table.shouldIgnoreRow(statementRowIndex)) {
        return Table.NO_ROW;
      }

      final int rowIndex = T_QUERY.createRow(statementRowIndex);
      if (Table.shouldIgnoreRow(rowIndex)) {
        return Table.NO_ROW;
      }

      T_QUERY.mySQL.setValue(rowIndex, query == null ? "<unknown>" : query);
      return rowIndex;
    }

    public static void onExit(
      @OnEnterResult final int rowIndex,
      @ThrownException @Nullable final Throwable e
    ) {
      T_QUERY.closeRow(rowIndex, e);
    }
  }

  @RetransformIfInstanceOf("java.sql.PreparedStatement")
  @MethodPattern(
    {
      "*:execute() boolean",
      "*:executeQuery() java.sql.ResultSet",
      "*:executeUpdate() int",
      "-org.datanucleus.store.rdbms.ParamLoggingPreparedStatement:*(*)",
      "-*StatementWrapper*:*(*)"
    }
  )
  public static final class PreparedStatement_execute_Probe {
    public static int onEnter(@This final Object preparedStatement) {
      if (!isPreparedStatement(getMask(preparedStatement))) {
        return Table.NO_ROW;
      }

      final int statementRowIndex = ourPreparedStatements.getOrCreate(preparedStatement);
      return T_PREPARED_STATEMENT_QUERY.createRow(statementRowIndex);
    }

    public static void onExit(
      @OnEnterResult final int rowIndex,
      @ThrownException @Nullable final Throwable e
    ) {
      T_PREPARED_STATEMENT_QUERY.closeRow(rowIndex, e);
    }
  }

  @RetransformIfInstanceOf("java.sql.Statement")
  @MethodPattern(
    {
      "*:addBatch(String) void",
      "-*StatementWrapper*:*(*)"
    }
  )
  public static final class Statement_addBatch_Probe {
    public static int onEnter(
      @This final Object statement,
      @Param(1) final String query
    ) {
      final int mask = getMask(statement);
      if (!isStatement(mask) || isPreparedStatement(mask)) {
        return Table.NO_ROW;
      }

      // add the query to the batch
      synchronized (ourStatement2Batch) {
        StringBuilder batch = ourStatement2Batch.get(statement);
        if (batch == null) {
          batch = new StringBuilder();
          ourStatement2Batch.put(statement, batch);
        }
        if (batch.length() != 0) {
          batch.append("\n");
        }
        batch.append(query);
      }

      final int statementRowIndex = ourStatements.getOrCreate(statement);
      if (Table.shouldIgnoreRow(statementRowIndex)) {
        return Table.NO_ROW;
      }

      final int rowIndex = T_BATCH.createRow(statementRowIndex);
      if (Table.shouldIgnoreRow(rowIndex)) {
        return Table.NO_ROW;
      }

      T_BATCH.mySQL.setValue(rowIndex, query);
      T_BATCH.myOperation.setValue(rowIndex, BATCH_OPERATION_ADD);
      return rowIndex;
    }

    public static void onExit(
      @OnEnterResult final int rowIndex,
      @ThrownException @Nullable final Throwable e
    ) {
      T_BATCH.closeRow(rowIndex, e);
    }
  }

  @RetransformIfInstanceOf("java.sql.PreparedStatement")
  @MethodPattern(
    {
      "*:addBatch() void",
      "-*StatementWrapper*:*(*)"
    }
  )
  public static final class PreparedStatement_addBatch_Probe {
    public static int onEnter(@This final Object preparedStatement) {
      if (!isPreparedStatement(getMask(preparedStatement))) {
        return Table.NO_ROW;
      }

      final int statementRowIndex = ourPreparedStatements.getOrCreate(preparedStatement);
      if (Table.shouldIgnoreRow(statementRowIndex)) {
        return Table.NO_ROW;
      }

      final int rowIndex = T_PREPARED_STATEMENT_BATCH.createRow(statementRowIndex);
      if (Table.shouldIgnoreRow(rowIndex)) {
        return Table.NO_ROW;
      }

      T_PREPARED_STATEMENT_BATCH.myOperation.setValue(rowIndex, BATCH_OPERATION_ADD);
      return rowIndex;
    }

    public static void onExit(
      @OnEnterResult final int rowIndex,
      @ThrownException @Nullable final Throwable e
    ) {
      T_PREPARED_STATEMENT_BATCH.closeRow(rowIndex, e);
    }
  }

  @RetransformIfInstanceOf("java.sql.Statement")
  @MethodPattern(
    {
      "*:executeBatch() int[]",
      "-*StatementWrapper*:*(*)"
    }
  )
  public static final class Statement_executeBatch_Probe {
    public static int onEnter(@This final Object statement) {
      final int mask = getMask(statement);

      if (!isStatement(mask)) {
        return Table.NO_ROW;
      }

      final boolean isPreparedStatement = isPreparedStatement(mask);

      final int statementRowIndex;
      if (isPreparedStatement) {
        statementRowIndex = ourPreparedStatements.getOrCreate(statement);
      }
      else {
        statementRowIndex = ourStatements.getOrCreate(statement);
      }
      if (Table.shouldIgnoreRow(statementRowIndex)) {
        return Table.NO_ROW;
      }

      if (isPreparedStatement) {
        return T_PREPARED_STATEMENT_QUERY.createRow(statementRowIndex);
      }
      else {
        final int rowIndex = T_QUERY.createRow(statementRowIndex);

        final StringBuilder builder;
        synchronized (ourStatement2Batch) {
          // JDBC specification:
          // "The statement's batch is reset to empty once executeBatch returns."

          builder = ourStatement2Batch.remove(statement);
        }

        if (builder != null) {
          T_QUERY.mySQL.setValue(rowIndex, builder.toString());
        }
        return rowIndex;
      }
    }

    public static void onExit(
      @This final Object statement,
      @OnEnterResult final int rowIndex,
      @ThrownException @Nullable final Throwable e
    ) {
      if (Table.shouldIgnoreRow(rowIndex)) {
        // micro optimization
        return;
      }

      if (isPreparedStatement(getMask(statement))) {
        T_PREPARED_STATEMENT_QUERY.closeRow(rowIndex, e);
      }
      else {
        // non-prepared statement
        T_QUERY.closeRow(rowIndex, e);
      }
    }
  }

  @RetransformIfInstanceOf("java.sql.Statement")
  @MethodPattern(
    {
      "*:clearBatch() void",
      "-*StatementWrapper*:*(*)"
    }
  )
  public static final class Statement_clearBatch_Probe {
    public static int onEnter(@This final Object o) {
      final int mask = getMask(o);

      if (!isStatement(mask)) {
        return Table.NO_ROW;
      }

      if (isPreparedStatement(mask)) {
        final int statementRowIndex = ourPreparedStatements.getOrCreate(o);
        if (Table.shouldIgnoreRow(statementRowIndex)) {
          return Table.NO_ROW;
        }

        final int rowIndex = T_PREPARED_STATEMENT_BATCH.createRow(statementRowIndex);
        if (Table.shouldIgnoreRow(rowIndex)) {
          return Table.NO_ROW;
        }

        T_PREPARED_STATEMENT_BATCH.myOperation.setValue(rowIndex, BATCH_OPERATION_CLEAR);
        return rowIndex;
      }

      // non-prepared statement

      synchronized (ourStatement2Batch) {
        ourStatement2Batch.remove(o);
      }

      final int statementRowIndex = ourStatements.getOrCreate(o);
      if (Table.shouldIgnoreRow(statementRowIndex)) {
        return Table.NO_ROW;
      }

      final int rowIndex = T_BATCH.createRow(statementRowIndex);
      if (Table.shouldIgnoreRow(rowIndex)) {
        return Table.NO_ROW;
      }

      T_BATCH.mySQL.setValue(rowIndex, "");
      T_BATCH.myOperation.setValue(rowIndex, BATCH_OPERATION_CLEAR);
      return rowIndex;
    }

    public static void onExit(
      @OnEnterResult final int rowIndex,
      @This final Object o,
      @ThrownException @Nullable final Throwable e
    ) {
      if (Table.shouldIgnoreRow(rowIndex)) {
        // micro optimization
        return;
      }

      if (isPreparedStatement(getMask(o))) {
        T_PREPARED_STATEMENT_BATCH.closeRow(rowIndex, e);
      }
      else {
        // non-prepared statement
        T_BATCH.closeRow(rowIndex, e);
      }
    }
  }

  @RetransformIfInstanceOf(
    {
      "java.sql.Connection",
      "java.sql.Statement"
    }
  )
  @MethodPattern(
    {
      "*:close() void",
      "-java.io.*:*()",
      "-java.nio.*:*()",
      "-sun.nio.*:*()",
      "-sun.net.*:*()",
      "-sun.rmi.*:*()",
      "-sun.security.*:*()",
      "-java.net.*:*()",
      "-*javax.management.*:*()",
      "-org.aspectj.*:*()",
      "-com.sun.xml.*:*()",
      "-*xerces*:*()",
      "-*Stream*:*()",
      "-*Socket*:*()",
      "-*Reader*:*()",
      "-*Writer*:*()",
      "-*File*:*()",
      "-*StatementWrapper*:*(*)"
    }
  )
  public static final class Connection_or_Statement_close_Probe {
    public static int onEnter(@This final Object o) {
      final int mask = getMask(o);

      if (isConnection(mask)) {
        return ourConnections.closeOnEnter(o);
      }

      if (!isStatement(mask)) {
        return Table.NO_ROW;
      }

      if (isPreparedStatement(mask)) {
        return ourPreparedStatements.closeOnEnter(o);
      }

      return ourStatements.closeOnEnter(o);
    }

    public static void onExit(
      @This final Object o,
      @OnEnterResult final int rowIndex,
      @ThrownException @Nullable final Throwable e
    ) {
      if (Table.shouldIgnoreRow(rowIndex)) {
        // micro optimization
        return;
      }

      final int mask = getMask(o);

      if (isConnection(mask)) {
        ourConnections.closeOnExit(rowIndex, e);
        return;
      }

      if (isPreparedStatement(mask)) {
        ourPreparedStatements.closeOnExit(rowIndex, e);
      }
      else {
        ourStatements.closeOnExit(rowIndex, e);
      }
    }
  }

  @RetransformIfInstanceOf("java.sql.Connection")
  @MethodPattern(
    {
      "*:createStatement(int, int, int)",
      "*:createStatement(int, int)",
      "*:createStatement()"
    }
  )
  public static final class Connection_createStatement_Probe {
    public static long onEnter(@This final Object connection) {
      if (!isConnection(getMask(connection))) {
        return Table.NO_ROW;
      }

      return ourStatements.openOnEnter();
    }

    public static void onExit(
      @OnEnterResult final long resourceID,
      @This final Object connection,
      @ReturnValue @Nullable final Object statement,
      @ThrownException @Nullable final Throwable e
    ) {
      if (resourceID == Table.NO_ROW) {
        // micro optimization
        return;
      }

      ourStatements.openOnExit(
        resourceID,
        "", // no name
        unwrapStatement(statement), // sic! we are sure this is a Statement because it's a return value of the Connection interface method
        connection,
        e,
        FailedEventPolicy.DISCARD
      );
    }
  }

  @RetransformIfInstanceOf("java.sql.Connection")
  @MethodPattern(
    {
      "*:prepareCall(String)",
      "*:prepareCall(String, int, int)",
      "*:prepareCall(String, int, int, int)",
      "*:prepareStatement(String)",
      "*:prepareStatement(String, int)",
      "*:prepareStatement(String, int, int)",
      "*:prepareStatement(String, int, int, int)",
      "*:prepareStatement(String, int[])",
      "*:prepareStatement(String, String[])"
    }
  )
  public static final class Connection_prepareStatement_Probe {
    public static long onEnter(@This final Object connection) {
      if (!isConnection(getMask(connection))) {
        return Table.NO_ROW;
      }

      return ourPreparedStatements.openOnEnter();
    }

    public static void onExit(
      @OnEnterResult final long resourceID,
      @This final Object connection,
      @Param(1) final String query,
      @ReturnValue @Nullable final Object preparedStatement,
      @ThrownException @Nullable final Throwable e
    ) {
      if (resourceID == Table.NO_ROW) {
        // micro optimization
        return;
      }

      ourPreparedStatements.openOnExit(
        resourceID,
        query,
        unwrapStatement(preparedStatement), // sic! we are sure this is a PreparedStatement because it's a return value of the Connection interface method
        connection,
        e,
        FailedEventPolicy.DISCARD
      );
    }
  }

  @Nullable
  static Object unwrapStatement(@Nullable final Object wrapper) {
    if (wrapper == null) {
      return null;
    }

    final Class<?> aClass = wrapper.getClass();
    final String className = aClass.getName();
    final Object result;
    if (className.equals("oracle.jdbc.driver.OracleStatementWrapper")) {
      result = getFieldValue(aClass, wrapper, "statement");
    }
    else if (className.equals("oracle.jdbc.driver.OraclePreparedStatementWrapper")) {
      result = getFieldValue(aClass.getSuperclass(), wrapper, "statement");
    }
    else {
      result = wrapper;
    }

    return result != null ? result : wrapper;
  }

  @Nullable
  static String getConnectionUrl(@NotNull final Object connection) {
    if (shouldSkipMetadata(connection)) {
      return null;
    }

    // java.sql.DatabaseMetaData
    final Object metaData = callMethod0(connection, "getMetaData");
    if (metaData == null) {
      return null;
    }

    return (String)callMethod0(metaData, "getURL");
  }

  private static final ClassChecker ourDelegatingConnectionClassChecker = new ClassChecker(
    "org.apache.tomcat.dbcp.dbcp2.DelegatingConnection",
    "org.apache.commons.dbcp2.DelegatingConnection"
  );

  private static boolean shouldSkipMetadata(@NotNull final Object connection) {
    Object c = connection;

    // unwrap delegation
    if (ourDelegatingConnectionClassChecker.isAssignableFrom(c.getClass())) {
      c = callMethod0(c, "getInnermostDelegateInternal");
      if (c == null) {
        return false;
      }
    }

    // calling getMetaData() leaks a socket inside neo4j code
    return c.getClass().getName().equals("org.neo4j.jdbc.bolt.BoltConnection");
  }

  private static Object getConnectionByStatement(@NotNull final Object statement) {
    return callMethod0(statement, "getConnection");
  }

  private static final ClassChecker ourStatementClassChecker = new ClassChecker(
    "java.sql.PreparedStatement", // index 0
    "java.sql.Statement", // index 1
    "javax.sql.DataSource", // index 2
    "java.sql.Connection", // index 3
    "javax.sql.PooledConnection", // index 4
    "java.sql.Driver" // index 5
  );

  static int getMask(@Nullable final Object o) {
    return ourStatementClassChecker.getMask(o);
  }

  static boolean isPreparedStatement(final int mask) {
    return (mask & 1) != 0; // 1 << 0
  }

  static boolean isStatement(final int mask) {
    return (mask & (1 << 1)) != 0;
  }

  static boolean isDataSource(final int mask) {
    return (mask & (1 << 2)) != 0;
  }

  static boolean isConnection(final int mask) {
    return (mask & (1 << 3)) != 0;
  }

  static boolean isPooledConnection(final int mask) {
    return (mask & (1 << 4)) != 0;
  }

  static boolean isDriver(final int mask) {
    return (mask & (1 << 5)) != 0;
  }
}
