@Override
  public Query build() {
    if (filters == null || filters.isEmpty()) return null;

    List<Query> queries = new LinkedList<Query>();

    // Build query definition for filter operations.
    for (DataSetFilter filter : filters) {
      Query subQuery = build(filter.getColumnFilterList(), Operator.AND);
      if (subQuery == null) continue;
      queries.add(subQuery);
    }

    // Build query definition for interval group selections.
    for (DataSetGroup group : groups) {
      if (group.isSelect()) {
        List<Query> subQueries = buildGroupIntervalQuery(group);
        if (subQueries != null && !subQueries.isEmpty()) {
          for (Query subQuery : subQueries) {
            queries.add(subQuery);
          }
        }
      }
    }

    Query result = joinQueriesAndFilters(queries, Operator.AND);

    // If result is a filter, wrap into a MATCH_ALL filtered query, as EL aggregations requires
    // working with queries.
    if (result != null && isFilter(result)) {
      Query filtered = new Query(Query.Type.FILTERED);
      Query matchAll = new Query(Query.Type.MATCH_ALL);
      filtered.setParam(Query.Parameter.QUERY.name(), matchAll);
      filtered.setParam(Query.Parameter.FILTER.name(), result);
      return filtered;
    }
    return result;
  }
  protected Query buildColumnCoreFunctionFilter(
      CoreFunctionFilter filter, DataSetMetadata metadata) {
    String columnId = filter.getColumnId();
    ColumnType columnType = metadata.getColumnType(columnId);
    ElasticSearchDataSetDef def = (ElasticSearchDataSetDef) metadata.getDefinition();

    Query result = null;

    CoreFunctionType type = filter.getType();
    List params = filter.getParameters();

    if (CoreFunctionType.IS_NULL.equals(type)) {

      result = new Query(Query.Type.NOT);
      Query existResult = new Query(columnId, Query.Type.EXISTS);
      result.setParam(Query.Parameter.FILTER.name(), existResult);

    } else if (CoreFunctionType.NOT_NULL.equals(type)) {

      result = new Query(columnId, Query.Type.EXISTS);

    } else if (CoreFunctionType.EQUALS_TO.equals(type)) {

      Object value = formatValue(def, columnId, params.get(0));

      if (ColumnType.LABEL.equals(columnType)) {
        result = new Query(columnId, Query.Type.TERM);
      } else {
        result = new Query(columnId, Query.Type.MATCH);
      }
      result.setParam(Query.Parameter.VALUE.name(), value);

    } else if (CoreFunctionType.LIKE_TO.equals(type)) {

      Object value = formatValue(def, columnId, params.get(0));

      if (value != null) {

        if (ColumnType.NUMBER.equals(columnType) || ColumnType.DATE.equals(columnType)) {
          throw new RuntimeException(
              "The operator LIKE can be applied only for LABEL or TEXT column types. The column ["
                  + columnId
                  + "] is type ["
                  + columnType.name()
                  + "}.");
        }

        // TEXT or LABEL columns.
        String indexType = def.getPattern(columnId);
        if (indexType == null || indexType.trim().length() == 0) {
          // Default ELS index type for String fields is ANALYZED.
          indexType = FieldMappingResponse.IndexType.ANALYZED.name();
        }

        // Replace Dashbuilder wildcard characters by the ones used in ELS wildcard query.
        boolean caseSensitive = params.size() < 2 || Boolean.parseBoolean(params.get(1).toString());
        boolean isFieldAnalyzed =
            FieldMappingResponse.IndexType.ANALYZED.name().equalsIgnoreCase(indexType);

        // Case un-sensitive is not supported for not_analyzed string fields.
        if (!isFieldAnalyzed && !caseSensitive) {
          throw new RuntimeException(
              "Case unsensitive is not supported for not_analyzed string fields. Field: ["
                  + columnId
                  + "].");
        }

        String pattern = utils.transformPattern(value.toString());
        boolean isLowerCaseExpandedTerms = isFieldAnalyzed && (!caseSensitive);
        result = new Query(columnId, Query.Type.QUERY_STRING);
        result.setParam(Query.Parameter.DEFAULT_FIELD.name(), columnId);
        result.setParam(Query.Parameter.DEFAULT_OPERATOR.name(), "AND");
        result.setParam(Query.Parameter.QUERY.name(), pattern);
        result.setParam(Query.Parameter.LOWERCASE_EXPANDED_TERMS.name(), isLowerCaseExpandedTerms);
      }

    } else if (CoreFunctionType.NOT_EQUALS_TO.equals(type)) {

      Object value = formatValue(def, columnId, params.get(0));

      if (ColumnType.LABEL.equals(columnType)) {
        Query resultMatch = new Query(columnId, Query.Type.TERM);
        resultMatch.setParam(Query.Parameter.VALUE.name(), value);
        result = new Query(columnId, Query.Type.NOT);
        result.setParam(Query.Parameter.FILTER.name(), resultMatch);
      } else {
        Query resultMatch = new Query(columnId, Query.Type.MATCH);
        resultMatch.setParam(Query.Parameter.VALUE.name(), value);
        result = new Query(columnId, Query.Type.BOOL);
        result.setParam(Query.Parameter.MUST_NOT.name(), asList(resultMatch));
      }

    } else if (CoreFunctionType.LOWER_THAN.equals(type)) {

      Object value = formatValue(def, columnId, params.get(0));
      result = new Query(columnId, Query.Type.RANGE);
      result.setParam(Query.Parameter.LT.name(), value);

    } else if (CoreFunctionType.LOWER_OR_EQUALS_TO.equals(type)) {

      Object value = formatValue(def, columnId, params.get(0));
      result = new Query(columnId, Query.Type.RANGE);
      result.setParam(Query.Parameter.LTE.name(), value);

    } else if (CoreFunctionType.GREATER_THAN.equals(type)) {

      Object value = formatValue(def, columnId, params.get(0));
      result = new Query(columnId, Query.Type.RANGE);
      result.setParam(Query.Parameter.GT.name(), value);

    } else if (CoreFunctionType.GREATER_OR_EQUALS_TO.equals(type)) {

      Object value = formatValue(def, columnId, params.get(0));
      result = new Query(columnId, Query.Type.RANGE);
      result.setParam(Query.Parameter.GTE.name(), value);

    } else if (CoreFunctionType.BETWEEN.equals(type)) {

      Object value0 = formatValue(def, columnId, params.get(0));
      Object value1 = formatValue(def, columnId, params.get(1));
      result = new Query(columnId, Query.Type.RANGE);
      result.setParam(Query.Parameter.GT.name(), value0);
      result.setParam(Query.Parameter.LT.name(), value1);

    } else if (CoreFunctionType.TIME_FRAME.equals(type)) {

      TimeFrame timeFrame = TimeFrame.parse(params.get(0).toString());
      if (timeFrame != null) {
        java.sql.Date past = new java.sql.Date(timeFrame.getFrom().getTimeInstant().getTime());
        java.sql.Date future = new java.sql.Date(timeFrame.getTo().getTimeInstant().getTime());
        result = new Query(columnId, Query.Type.RANGE);
        result.setParam(Query.Parameter.GTE.name(), past);
        result.setParam(Query.Parameter.LTE.name(), future);
      }

    } else {
      throw new IllegalArgumentException("Core function type not supported: " + type);
    }

    return result;
  }
  private Query joinQueriesAndFilters(List<Query> queries, Operator operator) {
    if (queries == null || queries.isEmpty()) return null;

    Query result = null;

    List<Query> subQueries = getQueries(queries);
    List<Query> subFilters = getFilters(queries);
    boolean existFilters = !subFilters.isEmpty();
    boolean existQueries = !subQueries.isEmpty();
    boolean onlyOneQuery = queries.size() == 1;

    String boolType = getBooleanQueryType(operator);
    Query.Type filterOperator = getType(operator);
    if (!existFilters) {
      if (onlyOneQuery && !operator.equals(Operator.NOT)) {
        // Single query.
        return queries.get(0);
      } else {
        // Multiple queries.
        result = new Query(Query.Type.BOOL);
        result.setParam(boolType, queries);
      }
    } else if (!existQueries) {

      if (onlyOneQuery && !operator.equals(Operator.NOT)) {
        // Single filter.
        return queries.get(0);
      } else if (onlyOneQuery) {
        // Single NOT filter.
        result = new Query(Query.Type.NOT);
        result.setParam(Query.Parameter.FILTER.name(), queries.get(0));

      } else {
        // Multiple filters.
        result = new Query(filterOperator);
        result.setParam(Query.Parameter.FILTERS.name(), queries);
      }
    } else {

      Query booleanQuery = null;
      if (subQueries.size() == 1) {
        booleanQuery = subQueries.get(0);
      } else {
        booleanQuery = new Query(Query.Type.BOOL);
        booleanQuery.setParam(boolType, subQueries);
      }

      Query filter = null;
      if (subFilters.size() == 1) {
        filter = subFilters.get(0);
      } else {
        filter = new Query(filterOperator);
        filter.setParam(Query.Parameter.FILTERS.name(), subFilters);
      }

      // Join queries and filters using a FILTERED query.
      result = new Query(Query.Type.FILTERED);
      result.setParam(Query.Parameter.QUERY.name(), booleanQuery);
      result.setParam(Query.Parameter.FILTER.name(), filter);
    }

    return result;
  }