public String toString(ValueContext vc) {
    valid = false;
    if (queryDefn == null) return "Query Definition is NULL";

    StringBuffer errorMsg = new StringBuffer();
    if (queryDefn.getErrors() != null) {
      List errors = queryDefn.getErrors();
      for (int i = 0; i < errors.size(); i++) errorMsg.append(errors.get(i) + ".\n");
    }
    if (select == null) {
      errorMsg.append("Query select is NULL.");
    } else {
      if (select.getErrors() != null) {
        List errors = select.getErrors();
        for (int i = 0; i < errors.size(); i++) errorMsg.append(errors.get(i) + ".\n");
      }
    }
    if (errorMsg.length() > 0) return errorMsg.toString();

    List showFields = select.getReportFields();
    int showFieldsCount = showFields.size();
    for (int sf = 0; sf < showFieldsCount; sf++) {
      QueryField field = (QueryField) showFields.get(sf);
      String selClauseAndLabel = field.getSelectClauseExprAndLabel();
      if (selClauseAndLabel != null) selectClause.add(field.getSelectClauseExprAndLabel());
      addJoin(field);
    }

    QueryConditions allSelectConditions = select.getConditions();
    QueryConditions usedSelectConditions = allSelectConditions.getUsedConditions(this, vc);

    // add join tables which have the auto-include flag set and their respective conditions to the
    // from and where clause lists. If the join is already in the 'joins' list, no need to add it
    // in.
    List autoIncJoinList = this.queryDefn.getAutoIncJoins();
    for (Iterator it = autoIncJoinList.iterator(); it.hasNext(); ) {
      this.addJoin((QueryJoin) it.next());
    }

    StringBuffer sql = new StringBuffer();

    int selectCount = selectClause.size();
    int selectLast = selectCount - 1;
    sql.append("select ");
    if (select.distinctRowsOnly()) sql.append("distinct \n");
    else sql.append("\n");
    for (int sc = 0; sc < selectCount; sc++) {
      sql.append("  " + selectClause.get(sc));
      if (sc != selectLast) sql.append(", ");
      sql.append("\n");
    }

    int fromCount = fromClause.size();
    int fromLast = fromCount - 1;
    sql.append("from \n");
    for (int fc = 0; fc < fromCount; fc++) {
      sql.append("  " + fromClause.get(fc));
      if (fc != fromLast) sql.append(", ");
      sql.append("\n");
    }

    boolean haveJoinWheres = false;
    int whereCount = whereJoinClause.size();
    int whereLast = whereCount - 1;
    if (whereCount > 0) {
      sql.append("where\n  (\n");
      for (int wc = 0; wc < whereCount; wc++) {
        sql.append("  " + whereJoinClause.get(wc));
        if (wc != whereLast) sql.append(" and ");
        sql.append("\n");
      }
      sql.append("  )");
      haveJoinWheres = true;
    }

    boolean haveCondWheres = false;
    int usedCondsCount = usedSelectConditions.size();
    if (usedCondsCount > 0) {
      if (haveJoinWheres) sql.append(" and (\n");
      else sql.append("where\n  (\n");
      usedSelectConditions.createSql(this, vc, usedSelectConditions, sql);
      sql.append("  )\n");
      haveCondWheres = true;
    }

    List whereExprs = select.getWhereExpressions();
    if (whereExprs != null && whereExprs.size() > 0) {
      int whereExprsLast = whereExprs.size() - 1;
      boolean first = false;
      if (!haveJoinWheres && !haveCondWheres) {
        sql.append("where\n  (\n");
        first = true;
      }

      int whereExprsCount = whereExprs.size();
      for (int we = 0; we < whereExprsCount; we++) {
        SqlWhereExpression expr = (SqlWhereExpression) whereExprs.get(we);
        if (first) first = false;
        else sql.append(expr.getConnectorSql());

        sql.append(" (");
        sql.append(expr.getWhereCondExpr(this));
        sql.append("  )\n");
      }
    }
    List groupBys = select.getGroupBy();
    int groupBysCount = groupBys.size();
    if (groupBysCount > 0) {
      int groupByLast = groupBysCount - 1;
      sql.append("group by\n");
      for (int gb = 0; gb < groupBysCount; gb++) {
        QueryField field = (QueryField) groupBys.get(gb);
        sql.append("  " + field.getQualifiedColName());
        if (gb != groupByLast) {
          sql.append(", ");
        }
        sql.append("\n");
      }
    }

    List orderBys = select.getOrderBy();
    int orderBysCount = orderBys.size();
    int orderBysLast = orderBysCount - 1;
    if (orderBysCount > 0) {
      sql.append("order by\n");
      for (int ob = 0; ob < orderBysCount; ob++) {
        QuerySortFieldRef sortRef = (QuerySortFieldRef) orderBys.get(ob);
        QueryField[] fields = sortRef.getFields(vc);
        if (fields == null) {
          return "Order by field '"
              + sortRef.getFieldName().getId()
              + "' did not evaluate to an appropriate QueryField.\n";
        } else {
          int lastField = fields.length - 1;
          for (int i = 0; i < fields.length; i++) {
            QueryField field = fields[i];
            if (field == null)
              return "Order by field ["
                  + i
                  + "] in '"
                  + sortRef.getFieldName().getId()
                  + "' did not evaluate to an appropriate QueryField.\n";

            sql.append("  " + field.getOrderByClauseExpr());
            if (sortRef.isDescending()) sql.append(" desc");

            if (i != lastField) {
              sql.append(", ");
              sql.append("\n");
            }
          }
        }

        if (ob != orderBysLast) sql.append(", ");
        sql.append("\n");
      }
    }

    valid = true;
    return sql.toString();
  }
  public void addJoin(QueryField field) {
    if (field == null) throw new RuntimeException("Null field");

    QueryJoin join = field.getJoin();
    this.addJoin(join);
  }