/** Find and return a single bean using its unique id. */
  public <T> T find(OrmQueryRequest<T> request) {

    T bean = null;

    CQuery<T> cquery = queryBuilder.buildQuery(request);

    try {
      cquery.prepareBindExecuteQuery();

      if (request.isLogSql()) {
        logSql(cquery);
      }

      if (cquery.readBean()) {
        bean = cquery.getLoadedBean();
      }

      if (request.isLogSummary()) {
        logFindBeanSummary(cquery);
      }

      request.executeSecondaryQueries(defaultSecondaryQueryBatchSize);

      return bean;

    } catch (SQLException e) {
      throw cquery.createPersistenceException(e);

    } finally {
      cquery.close();
    }
  }
  /**
   * Read many beans using an iterator (except you need to close() the iterator when you have
   * finished).
   */
  public <T> QueryIterator<T> findIterate(OrmQueryRequest<T> request) {

    CQuery<T> cquery = queryBuilder.buildQuery(request);
    request.setCancelableQuery(cquery);

    try {

      if (!cquery.prepareBindExecuteQuery()) {
        // query has been cancelled already
        logger.trace("Future fetch already cancelled");
        return null;
      }

      if (request.isLogSql()) {
        logSql(cquery);
      }

      int iterateBufferSize =
          request.getSecondaryQueriesMinBatchSize(defaultSecondaryQueryBatchSize);

      QueryIterator<T> readIterate = cquery.readIterate(iterateBufferSize, request);

      if (request.isLogSummary()) {
        logFindManySummary(cquery);
      }

      return readIterate;

    } catch (SQLException e) {
      throw cquery.createPersistenceException(e);
    }
  }
  /** Create the Sql select based on the request. */
  public CQueryFetchIds(OrmQueryRequest<?> request, CQueryPredicates predicates, String sql) {

    this.request = request;
    this.query = request.getQuery();
    this.sql = sql;
    this.maxRows = query.getMaxRows();

    query.setGeneratedSql(sql);

    this.desc = request.getBeanDescriptor();
    this.predicates = predicates;
  }
 public CQueryPredicates(Binder binder, OrmQueryRequest<?> request) {
   this.binder = binder;
   this.request = request;
   this.query = request.getQuery();
   this.bindParams = query.getBindParams();
   this.idValue = query.getId();
 }
  /** Convert named parameters into an OrderedList. */
  private void buildBindWhereRawSql(boolean buildSql, boolean parseRaw, DeployParser parser) {
    if (buildSql || bindParams != null) {
      whereRawSql = buildWhereRawSql();
      boolean hasRaw = !"".equals(whereRawSql);
      if (hasRaw && parseRaw) {
        // parse with encrypted property awareness. This means that if we have
        // an encrypted property we will insert special named parameter place
        // holders for binding the encryption key values
        parser.setEncrypted(true);
        whereRawSql = parser.parse(whereRawSql);
        parser.setEncrypted(false);
      }

      if (bindParams != null) {
        if (hasRaw) {
          whereRawSql =
              BindParamsParser.parse(bindParams, whereRawSql, request.getBeanDescriptor());

        } else if (query.isRawSql() && !buildSql) {
          // RawSql query hit cached query plan. Need to convert
          // named parameters into positioned parameters so that
          // the named parameters are bound
          RawSql.Sql sql = query.getRawSql().getSql();
          String s = sql.isParsed() ? sql.getPreWhere() : sql.getUnparsedSql();
          if (bindParams.requiresNamedParamsPrepare()) {
            BindParamsParser.parse(bindParams, s);
          }
        }
      }
    }
  }
  public String bind(DataBind dataBind) throws SQLException {

    StringBuilder bindLog = new StringBuilder();

    if (idValue != null) {
      // this is a find by id type query...
      request.getBeanDescriptor().bindId(dataBind, idValue);
      bindLog.append(idValue);
    }

    if (bindParams != null) {
      // bind named and positioned parameters...
      binder.bind(bindParams, dataBind, bindLog);
    }

    if (whereExprBindValues != null) {

      for (int i = 0; i < whereExprBindValues.size(); i++) {
        Object bindValue = whereExprBindValues.get(i);
        binder.bindObject(dataBind, bindValue);
        if (i > 0 || idValue != null) {
          bindLog.append(",");
        }
        bindLog.append(bindValue);
      }
    }

    if (filterManyExprBindValues != null) {

      for (int i = 0; i < filterManyExprBindValues.size(); i++) {
        Object bindValue = filterManyExprBindValues.get(i);
        binder.bindObject(dataBind, bindValue);
        if (i > 0 || idValue != null) {
          bindLog.append(",");
        }
        bindLog.append(bindValue);
      }
    }

    if (havingNamedParams != null) {
      // bind named parameters in having...
      bindLog.append(" havingNamed ");
      binder.bind(havingNamedParams.list(), dataBind, bindLog);
    }

    if (havingExprBindValues != null) {
      // bind having expression...
      bindLog.append(" having ");
      for (int i = 0; i < havingExprBindValues.size(); i++) {
        Object bindValue = havingExprBindValues.get(i);
        binder.bindObject(dataBind, bindValue);
        if (i > 0) {
          bindLog.append(",");
        }
        bindLog.append(bindValue);
      }
    }

    return bindLog.toString();
  }
  /** Build and execute the row count query. */
  public <T> int findRowCount(OrmQueryRequest<T> request) {

    CQueryRowCount rcQuery = queryBuilder.buildRowCountQuery(request);
    try {

      int rowCount = rcQuery.findRowCount();

      if (request.isLogSql()) {
        String logSql = rcQuery.getGeneratedSql();
        if (TransactionManager.SQL_LOGGER.isTraceEnabled()) {
          logSql += "; --bind(" + rcQuery.getBindLog() + ")";
        }
        request.logSql(logSql);
      }

      if (request.isLogSummary()) {
        request.getTransaction().logSummary(rcQuery.getSummary());
      }

      if (request.getQuery().isFutureFetch()) {
        logger.debug("Future findRowCount completed!");
        request.getTransaction().end();
      }

      return rowCount;

    } catch (SQLException e) {
      throw CQuery.createPersistenceException(
          e, request.getTransaction(), rcQuery.getBindLog(), rcQuery.getGeneratedSql());
    }
  }
  /** Build and execute the find Id's query. */
  public <T> BeanIdList findIds(OrmQueryRequest<T> request) {

    CQueryFetchIds rcQuery = queryBuilder.buildFetchIdsQuery(request);
    try {

      BeanIdList list = rcQuery.findIds();

      if (request.isLogSql()) {
        String logSql = rcQuery.getGeneratedSql();
        if (TransactionManager.SQL_LOGGER.isTraceEnabled()) {
          logSql += "; --bind(" + rcQuery.getBindLog() + ")";
        }
        request.logSql(logSql);
      }

      if (request.isLogSummary()) {
        request.getTransaction().logSummary(rcQuery.getSummary());
      }

      if (!list.isFetchingInBackground() && request.getQuery().isFutureFetch()) {
        // end the transaction for futureFindIds (it had it's own one)
        logger.debug("Future findIds completed!");
        request.getTransaction().end();
      }

      return list;

    } catch (SQLException e) {
      throw CQuery.createPersistenceException(
          e, request.getTransaction(), rcQuery.getBindLog(), rcQuery.getGeneratedSql());
    }
  }
  /** Parse/Convert property names to database columns in the where and order by clauses etc. */
  private void parsePropertiesToDbColumns(DeployParser deployParser) {

    // order by is dependent on the manyProperty (if there is one)
    String logicalOrderBy = deriveOrderByWithMany(request.getManyProperty());
    if (logicalOrderBy != null) {
      dbOrderBy = deployParser.parse(logicalOrderBy);
    }

    // create a copy of the includes required to support the orderBy
    orderByIncludes = new HashSet<String>(deployParser.getIncludes());

    dbWhere = deriveWhere(deployParser);
    dbFilterMany = deriveFilterMany(deployParser);
    dbHaving = deriveHaving(deployParser);

    // all includes including ones for manyWhere clause
    predicateIncludes = deployParser.getIncludes();
  }
  /** This combines the sql from named/positioned parameters and expressions. */
  private void prepare(boolean buildSql, boolean parseRaw, DeployParser deployParser) {

    buildBindWhereRawSql(buildSql, parseRaw, deployParser);
    buildBindHavingRawSql(buildSql, parseRaw, deployParser);

    SpiExpressionList<?> whereExp = query.getWhereExpressions();
    if (whereExp != null) {
      DefaultExpressionRequest whereReq = new DefaultExpressionRequest(request, deployParser);
      whereExprBindValues = whereExp.buildBindValues(whereReq);
      if (buildSql) {
        whereExprSql = whereExp.buildSql(whereReq);
      }
    }

    BeanPropertyAssocMany<?> manyProperty = request.getManyProperty();
    if (manyProperty != null) {
      OrmQueryProperties chunk = query.getDetail().getChunk(manyProperty.getName(), false);
      SpiExpressionList<?> filterMany = chunk.getFilterMany();
      if (filterMany != null) {
        DefaultExpressionRequest filterReq = new DefaultExpressionRequest(request, deployParser);
        filterManyExprBindValues = filterMany.buildBindValues(filterReq);
        if (buildSql) {
          filterManyExprSql = filterMany.buildSql(filterReq);
        }
      }
    }

    // having expression
    SpiExpressionList<?> havingExpr = query.getHavingExpressions();
    if (havingExpr != null) {
      DefaultExpressionRequest havingReq = new DefaultExpressionRequest(request, deployParser);
      havingExprBindValues = havingExpr.buildBindValues(havingReq);
      if (buildSql) {
        havingExprSql = havingExpr.buildSql(havingReq);
      }
    }

    if (buildSql) {
      parsePropertiesToDbColumns(deployParser);
    }
  }
  /** There is a many property so we need to make sure the ordering is appropriate. */
  private String deriveOrderByWithMany(BeanPropertyAssocMany<?> manyProp) {

    if (manyProp == null) {
      return parseOrderBy();
    }

    String orderBy = parseOrderBy();

    BeanDescriptor<?> desc = request.getBeanDescriptor();
    String orderById = desc.getDefaultOrderBy();

    if (orderBy == null) {
      orderBy = orderById;
    }

    // check for default ordering on the many property...
    String manyOrderBy = manyProp.getFetchOrderBy();
    if (manyOrderBy != null) {
      orderBy = orderBy + ", " + CQueryBuilder.prefixOrderByFields(manyProp.getName(), manyOrderBy);
    }

    if (request.isFindById()) {
      // only one master bean so should be fine...
      return orderBy;
    }

    if (orderBy.startsWith(orderById)) {
      return orderBy;
    }

    // more than one top level row may be returned so
    // we need to make sure their is an order by on the
    // top level first (to ensure master/detail construction).

    int manyPos = orderBy.indexOf(manyProp.getName());
    int idPos = orderBy.indexOf(" " + orderById);

    if (manyPos == -1) {
      // no ordering of the many
      if (idPos == -1) {
        // append the orderById so that master level objects are ordered
        // even if the orderBy is not unique for the master object
        return orderBy + ", " + orderById;
      }
      // orderById is already in the order by clause
      return orderBy;
    }

    if (idPos <= -1 || idPos >= manyPos) {
      if (idPos > manyPos) {
        // there was an error with the order by...
        String msg =
            "A Query on ["
                + desc
                + "] includes a join to a 'many' association ["
                + manyProp.getName();
        msg += "] with an incorrect orderBy [" + orderBy + "]. The id property [" + orderById + "]";
        msg += " must come before the many property [" + manyProp.getName() + "] in the orderBy.";
        msg += " Ebean has automatically modified the orderBy clause to do this.";

        logger.warn(msg);
      }

      // the id needs to come before the manyPropName
      orderBy = orderBy.substring(0, manyPos) + orderById + ", " + orderBy.substring(manyPos);
    }

    return orderBy;
  }
  private String parseOrderBy() {

    return CQueryOrderBy.parse(request.getBeanDescriptor(), query);
  }
  public void prepare(boolean buildSql) {

    DeployParser deployParser = request.createDeployParser();
    prepare(buildSql, true, deployParser);
  }
  public String bind(DataBind dataBind) throws SQLException {

    StringBuilder bindLog = new StringBuilder();

    if (query.isVersionsBetween() && binder.isBindAsOfWithFromClause()) {
      // sql2011 based versions between timestamp syntax
      Timestamp start = query.getVersionStart();
      Timestamp end = query.getVersionEnd();
      bindLog.append("between ").append(start).append(" and ").append(end);
      binder.bindObject(dataBind, start);
      binder.bindObject(dataBind, end);
      bindLog.append(", ");
    }

    List<String> historyTableAlias = query.getAsOfTableAlias();
    if (historyTableAlias != null && binder.isBindAsOfWithFromClause()) {
      // bind the asOf value for each table alias as part of the from/join clauses
      // there is one effective date predicate per table alias
      Timestamp asOf = query.getAsOf();
      bindLog.append("asOf ").append(asOf);
      for (int i = 0; i < historyTableAlias.size() * binder.getAsOfBindCount(); i++) {
        binder.bindObject(dataBind, asOf);
      }
      bindLog.append(", ");
    }

    if (idValue != null) {
      // this is a find by id type query...
      request.getBeanDescriptor().bindId(dataBind, idValue);
      bindLog.append(idValue);
    }

    if (bindParams != null) {
      // bind named and positioned parameters...
      binder.bind(bindParams, dataBind, bindLog);
    }

    if (whereExprBindValues != null) {
      for (int i = 0; i < whereExprBindValues.size(); i++) {
        Object bindValue = whereExprBindValues.get(i);
        bindValue = binder.bindObject(dataBind, bindValue);
        if (i > 0 || idValue != null) {
          bindLog.append(",");
        }
        bindLog.append(bindValue);
      }
    }

    if (filterManyExprBindValues != null) {
      for (int i = 0; i < filterManyExprBindValues.size(); i++) {
        Object bindValue = filterManyExprBindValues.get(i);
        bindValue = binder.bindObject(dataBind, bindValue);
        if (i > 0 || idValue != null) {
          bindLog.append(",");
        }
        bindLog.append(bindValue);
      }
    }

    if (historyTableAlias != null && !binder.isBindAsOfWithFromClause()) {
      // bind the asOf value for each table alias after all the normal predicates
      // there is one effective date predicate per table alias
      Timestamp asOf = query.getAsOf();
      bindLog.append(" asOf ").append(asOf);
      for (int i = 0; i < historyTableAlias.size() * binder.getAsOfBindCount(); i++) {
        binder.bindObject(dataBind, asOf);
      }
    }

    if (havingNamedParams != null) {
      // bind named parameters in having...
      bindLog.append(" havingNamed ");
      binder.bind(havingNamedParams.list(), dataBind, bindLog);
    }

    if (havingExprBindValues != null) {
      // bind having expression...
      bindLog.append(" having ");
      for (int i = 0; i < havingExprBindValues.size(); i++) {
        Object bindValue = havingExprBindValues.get(i);
        bindValue = binder.bindObject(dataBind, bindValue);
        if (i > 0) {
          bindLog.append(",");
        }
        bindLog.append(bindValue);
      }
    }

    return bindLog.toString();
  }
  /** Execute the query returning the row count. */
  public BeanIdList findIds() throws SQLException {

    long startNano = System.nanoTime();

    try {
      // get the list that we are going to put the id's into.
      // This was already set so that it is available to be
      // read by other threads (it is a synchronised list)
      List<Object> idList = query.getIdList();
      if (idList == null) {
        // running in foreground thread (not FutureIds query)
        idList = Collections.synchronizedList(new ArrayList<Object>());
        query.setIdList(idList);
      }

      BeanIdList result = new BeanIdList(idList);

      SpiTransaction t = request.getTransaction();
      Connection conn = t.getInternalConnection();
      pstmt = conn.prepareStatement(sql);

      if (query.getBufferFetchSizeHint() > 0) {
        pstmt.setFetchSize(query.getBufferFetchSizeHint());
      }

      if (query.getTimeout() > 0) {
        pstmt.setQueryTimeout(query.getTimeout());
      }

      bindLog = predicates.bind(new DataBind(pstmt));

      ResultSet rset = pstmt.executeQuery();
      dataReader = new RsetDataReader(rset);

      boolean hitMaxRows = false;
      boolean hasMoreRows = false;
      rowCount = 0;

      DbReadContext ctx = new DbContext();

      while (rset.next()) {
        Object idValue = desc.getIdBinder().read(ctx);
        idList.add(idValue);
        // reset back to 0
        dataReader.resetColumnPosition();
        rowCount++;

        if (maxRows > 0 && rowCount == maxRows) {
          hitMaxRows = true;
          hasMoreRows = rset.next();
          break;
        }
      }

      if (hitMaxRows) {
        result.setHasMore(hasMoreRows);
      }

      long exeNano = System.nanoTime() - startNano;
      executionTimeMicros = (int) exeNano / 1000;

      return result;

    } finally {
      close();
    }
  }
  /** Find a list/map/set of beans. */
  public <T> BeanCollection<T> findMany(OrmQueryRequest<T> request) {

    // flag indicating whether we need to close the resources...
    boolean useBackgroundToContinueFetch = false;

    CQuery<T> cquery = queryBuilder.buildQuery(request);
    request.setCancelableQuery(cquery);

    try {
      if (!cquery.prepareBindExecuteQuery()) {
        // query has been cancelled already
        logger.trace("Future fetch already cancelled");
        return null;
      }

      if (request.isLogSql()) {
        logSql(cquery);
      }

      BeanCollection<T> beanCollection = cquery.readCollection();

      BeanCollectionTouched collectionTouched = request.getQuery().getBeanCollectionTouched();
      if (collectionTouched != null) {
        // register a listener that wants to be notified when the
        // bean collection is first used
        beanCollection.setBeanCollectionTouched(collectionTouched);
      }

      if (cquery.useBackgroundToContinueFetch()) {
        // stop the request from putting connection back into pool
        // before background fetching is finished.
        request.setBackgroundFetching();
        useBackgroundToContinueFetch = true;
        BackgroundFetch fetch = new BackgroundFetch(cquery);

        FutureTask<Integer> future = new FutureTask<Integer>(fetch);
        beanCollection.setBackgroundFetch(future);
        backgroundExecutor.execute(future);
      }

      if (request.isLogSummary()) {
        logFindManySummary(cquery);
      }

      request.executeSecondaryQueries(defaultSecondaryQueryBatchSize);

      return beanCollection;

    } catch (SQLException e) {
      throw cquery.createPersistenceException(e);

    } finally {
      if (useBackgroundToContinueFetch) {
        // left closing resources to BackgroundFetch...
      } else {
        if (cquery != null) {
          cquery.close();
        }
        if (request.getQuery().isFutureFetch()) {
          // end the transaction for futureFindIds
          // as it had it's own transaction
          logger.debug("Future fetch completed!");
          request.getTransaction().end();
        }
      }
    }
  }