/** 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();
  }
  /** 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;
  }
  /** 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 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();
  }