@Override
  public Query parse(QueryParseContext parseContext) throws IOException, QueryParsingException {
    ensureNotDeleteByQuery(NAME, parseContext);
    XContentParser parser = parseContext.parser();

    boolean queryFound = false;
    float boost = 1.0f;
    String childType = null;
    ScoreType scoreType = ScoreType.NONE;
    int minChildren = 0;
    int maxChildren = 0;
    int shortCircuitParentDocSet = 8192;
    String queryName = null;
    Tuple<String, SubSearchContext> innerHits = null;

    String currentFieldName = null;
    XContentParser.Token token;
    XContentStructure.InnerQuery iq = null;
    while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
      if (token == XContentParser.Token.FIELD_NAME) {
        currentFieldName = parser.currentName();
      } else if (token == XContentParser.Token.START_OBJECT) {
        // Usually, the query would be parsed here, but the child
        // type may not have been extracted yet, so use the
        // XContentStructure.<type> facade to parse if available,
        // or delay parsing if not.
        if ("query".equals(currentFieldName)) {
          iq =
              new XContentStructure.InnerQuery(
                  parseContext, childType == null ? null : new String[] {childType});
          queryFound = true;
        } else if ("inner_hits".equals(currentFieldName)) {
          innerHits = innerHitsQueryParserHelper.parse(parseContext);
        } else {
          throw new QueryParsingException(
              parseContext.index(),
              "[has_child] query does not support [" + currentFieldName + "]");
        }
      } else if (token.isValue()) {
        if ("type".equals(currentFieldName)
            || "child_type".equals(currentFieldName)
            || "childType".equals(currentFieldName)) {
          childType = parser.text();
        } else if ("score_type".equals(currentFieldName) || "scoreType".equals(currentFieldName)) {
          scoreType = ScoreType.fromString(parser.text());
        } else if ("score_mode".equals(currentFieldName) || "scoreMode".equals(currentFieldName)) {
          scoreType = ScoreType.fromString(parser.text());
        } else if ("boost".equals(currentFieldName)) {
          boost = parser.floatValue();
        } else if ("min_children".equals(currentFieldName)
            || "minChildren".equals(currentFieldName)) {
          minChildren = parser.intValue(true);
        } else if ("max_children".equals(currentFieldName)
            || "maxChildren".equals(currentFieldName)) {
          maxChildren = parser.intValue(true);
        } else if ("short_circuit_cutoff".equals(currentFieldName)) {
          shortCircuitParentDocSet = parser.intValue();
        } else if ("_name".equals(currentFieldName)) {
          queryName = parser.text();
        } else {
          throw new QueryParsingException(
              parseContext.index(),
              "[has_child] query does not support [" + currentFieldName + "]");
        }
      }
    }
    if (!queryFound) {
      throw new QueryParsingException(parseContext.index(), "[has_child] requires 'query' field");
    }
    if (childType == null) {
      throw new QueryParsingException(parseContext.index(), "[has_child] requires 'type' field");
    }

    Query innerQuery = iq.asQuery(childType);

    if (innerQuery == null) {
      return null;
    }
    innerQuery.setBoost(boost);

    DocumentMapper childDocMapper = parseContext.mapperService().documentMapper(childType);
    if (childDocMapper == null) {
      throw new QueryParsingException(
          parseContext.index(), "[has_child] No mapping for for type [" + childType + "]");
    }
    if (!childDocMapper.parentFieldMapper().active()) {
      throw new QueryParsingException(
          parseContext.index(),
          "[has_child]  Type [" + childType + "] does not have parent mapping");
    }

    if (innerHits != null) {
      InnerHitsContext.ParentChildInnerHits parentChildInnerHits =
          new InnerHitsContext.ParentChildInnerHits(
              innerHits.v2(), innerQuery, null, childDocMapper);
      String name = innerHits.v1() != null ? innerHits.v1() : childType;
      parseContext.addInnerHits(name, parentChildInnerHits);
    }

    ParentFieldMapper parentFieldMapper = childDocMapper.parentFieldMapper();
    if (!parentFieldMapper.active()) {
      throw new QueryParsingException(
          parseContext.index(), "[has_child] _parent field not configured");
    }

    String parentType = parentFieldMapper.type();
    DocumentMapper parentDocMapper = parseContext.mapperService().documentMapper(parentType);
    if (parentDocMapper == null) {
      throw new QueryParsingException(
          parseContext.index(),
          "[has_child]  Type ["
              + childType
              + "] points to a non existent parent type ["
              + parentType
              + "]");
    }

    if (maxChildren > 0 && maxChildren < minChildren) {
      throw new QueryParsingException(
          parseContext.index(), "[has_child] 'max_children' is less than 'min_children'");
    }

    BitDocIdSetFilter nonNestedDocsFilter = null;
    if (parentDocMapper.hasNestedObjects()) {
      nonNestedDocsFilter = parseContext.bitsetFilter(NonNestedDocsFilter.INSTANCE);
    }

    // wrap the query with type query
    innerQuery =
        new FilteredQuery(
            innerQuery,
            parseContext.cacheFilter(
                childDocMapper.typeFilter(), null, parseContext.autoFilterCachePolicy()));

    Query query;
    Filter parentFilter =
        parseContext.cacheFilter(
            parentDocMapper.typeFilter(), null, parseContext.autoFilterCachePolicy());
    ParentChildIndexFieldData parentChildIndexFieldData =
        parseContext.getForField(parentFieldMapper);
    if (minChildren > 1 || maxChildren > 0 || scoreType != ScoreType.NONE) {
      query =
          new ChildrenQuery(
              parentChildIndexFieldData,
              parentType,
              childType,
              parentFilter,
              innerQuery,
              scoreType,
              minChildren,
              maxChildren,
              shortCircuitParentDocSet,
              nonNestedDocsFilter);
    } else {
      query =
          new ChildrenConstantScoreQuery(
              parentChildIndexFieldData,
              innerQuery,
              parentType,
              childType,
              parentFilter,
              shortCircuitParentDocSet,
              nonNestedDocsFilter);
    }
    if (queryName != null) {
      parseContext.addNamedFilter(queryName, new CustomQueryWrappingFilter(query));
    }
    query.setBoost(boost);
    return query;
  }