/**
   * Method to create a PreparedStatement for use with the query.
   *
   * @param conn the Connection
   * @param queryStmt The statement text for the query
   * @param query The query
   * @return the PreparedStatement
   * @throws SQLException Thrown if an error occurs creating the statement
   */
  public static PreparedStatement getPreparedStatementForQuery(
      ManagedConnection conn, String queryStmt, Query query) throws SQLException {
    // Apply any non-standard result set definition if required (either from the PMF, or via query
    // extensions)
    String rsTypeString = RDBMSQueryUtils.getResultSetTypeForQuery(query);
    if (rsTypeString != null
        && (!rsTypeString.equals("scroll-sensitive")
            && !rsTypeString.equals("forward-only")
            && !rsTypeString.equals("scroll-insensitive"))) {
      throw new NucleusUserException(LOCALISER.msg("052510"));
    }
    if (rsTypeString != null) {
      DatastoreAdapter dba = ((RDBMSStoreManager) query.getStoreManager()).getDatastoreAdapter();

      // Add checks on what the DatastoreAdapter supports
      if (rsTypeString.equals("scroll-sensitive")
          && !dba.supportsOption(DatastoreAdapter.RESULTSET_TYPE_SCROLL_SENSITIVE)) {
        NucleusLogger.DATASTORE_RETRIEVE.info(
            "Query requested to run with result-set type of "
                + rsTypeString
                + " yet not supported by adapter. Using forward-only");
        rsTypeString = "forward-only";
      } else if (rsTypeString.equals("scroll-insensitive")
          && !dba.supportsOption(DatastoreAdapter.RESULTSET_TYPE_SCROLL_INSENSITIVE)) {
        NucleusLogger.DATASTORE_RETRIEVE.info(
            "Query requested to run with result-set type of "
                + rsTypeString
                + " yet not supported by adapter. Using forward-only");
        rsTypeString = "forward-only";
      } else if (rsTypeString.equals("forward-only")
          && !dba.supportsOption(DatastoreAdapter.RESULTSET_TYPE_FORWARD_ONLY)) {
        NucleusLogger.DATASTORE_RETRIEVE.info(
            "Query requested to run with result-set type of "
                + rsTypeString
                + " yet not supported by adapter. Using scroll-sensitive");
        rsTypeString = "scroll-sensitive";
      }
    }

    String rsConcurrencyString = RDBMSQueryUtils.getResultSetConcurrencyForQuery(query);
    if (rsConcurrencyString != null
        && (!rsConcurrencyString.equals("read-only")
            && !rsConcurrencyString.equals("updateable"))) {
      throw new NucleusUserException(LOCALISER.msg("052511"));
    }

    SQLController sqlControl = ((RDBMSStoreManager) query.getStoreManager()).getSQLController();
    PreparedStatement ps =
        sqlControl.getStatementForQuery(conn, queryStmt, rsTypeString, rsConcurrencyString);

    return ps;
  }
 /**
  * Accessor for the result set type for the specified query. Uses the persistence property
  * "datanucleus.rdbms.query.resultSetType" and allows it to be overridden by the query extension
  * of the same name. Checks both the PMF, and also the query extensions.
  *
  * @param query The query
  * @return The result set type string
  */
 public static String getResultSetTypeForQuery(Query query) {
   String rsTypeString =
       query
           .getExecutionContext()
           .getNucleusContext()
           .getPersistenceConfiguration()
           .getStringProperty(RDBMSPropertyNames.PROPERTY_RDBMS_QUERY_RESULT_SET_TYPE);
   Object rsTypeExt = query.getExtension(RDBMSPropertyNames.PROPERTY_RDBMS_QUERY_RESULT_SET_TYPE);
   if (rsTypeExt != null) {
     rsTypeString = (String) rsTypeExt;
   }
   return rsTypeString;
 }
  /**
   * Convenience method to return if the specified query should use an "UPDATE" lock on returned
   * objects. First checks whether serializeRead is set on the query and, if not, falls back to the
   * setting for the class.
   *
   * @param query The query
   * @return Whether to use an "UPDATE" lock
   */
  public static boolean useUpdateLockForQuery(Query query) {
    if (query.getSerializeRead() != null) {
      if (!query.getExecutionContext().getTransaction().isActive()) {
        // Only applies in a transaction
        return false;
      }

      // Query value takes top priority
      return query.getSerializeRead();
    } else {
      // Fallback to transaction or class itself
      return query.getExecutionContext().getSerializeReadForClass(query.getCandidateClassName());
    }
  }
  /**
   * Method to apply any restrictions to the created ResultSet.
   *
   * @param ps The PreparedStatement
   * @param query The query
   * @param applyTimeout Whether to apply the query timeout (if any) direct to the PreparedStatement
   * @throws SQLException Thrown when an error occurs applying the constraints
   */
  public static void prepareStatementForExecution(
      PreparedStatement ps, Query query, boolean applyTimeout) throws SQLException {
    NucleusContext nucleusCtx = query.getExecutionContext().getNucleusContext();
    RDBMSStoreManager storeMgr = (RDBMSStoreManager) query.getStoreManager();
    PersistenceConfiguration conf = nucleusCtx.getPersistenceConfiguration();

    if (applyTimeout) {
      Integer timeout = query.getDatastoreReadTimeoutMillis();
      if (timeout != null && timeout > 0) {
        ps.setQueryTimeout(timeout / 1000);
      }
    }

    // Apply any fetch size
    int fetchSize = 0;
    if (query.getFetchPlan().getFetchSize() > 0) {
      // FetchPlan has a size set so use that
      fetchSize = query.getFetchPlan().getFetchSize();
    }
    if (storeMgr.getDatastoreAdapter().supportsQueryFetchSize(fetchSize)) {
      ps.setFetchSize(fetchSize);
    }

    // Apply any fetch direction
    String fetchDir =
        conf.getStringProperty(RDBMSPropertyNames.PROPERTY_RDBMS_QUERY_FETCH_DIRECTION);
    Object fetchDirExt =
        query.getExtension(RDBMSPropertyNames.PROPERTY_RDBMS_QUERY_FETCH_DIRECTION);
    if (fetchDirExt != null) {
      fetchDir = (String) fetchDirExt;
      if (!fetchDir.equals("forward")
          && !fetchDir.equals("reverse")
          && !fetchDir.equals("unknown")) {
        throw new NucleusUserException(LOCALISER.msg("052512"));
      }
    }

    if (fetchDir.equals("reverse")) {
      ps.setFetchDirection(ResultSet.FETCH_REVERSE);
    } else if (fetchDir.equals("unknown")) {
      ps.setFetchDirection(ResultSet.FETCH_UNKNOWN);
    }

    // Add a limit on the number of rows to include the maximum we may need
    long toExclNo = query.getRangeToExcl();
    if (toExclNo != 0 && toExclNo != Long.MAX_VALUE) {
      if (toExclNo > Integer.MAX_VALUE) {
        // setMaxRows takes an int as input so limit to the correct range
        ps.setMaxRows(Integer.MAX_VALUE);
      } else {
        ps.setMaxRows((int) toExclNo);
      }
    }
  }