protected Query eq(Operation<?> operation, QueryMetadata metadata, boolean ignoreCase) {
    verifyArguments(operation);
    Path<?> path = getPath(operation.getArg(0));
    String field = toField(path);

    if (Number.class.isAssignableFrom(operation.getArg(1).getType())) {
      @SuppressWarnings("unchecked") // guarded by previous check
      Constant<? extends Number> rightArg = (Constant<? extends Number>) operation.getArg(1);
      return new TermQuery(new Term(field, convertNumber(rightArg.getConstant())));
    }

    return eq(field, convert(path, operation.getArg(1), metadata), ignoreCase);
  }
  protected Query range(
      Path<?> leftHandSide,
      String field,
      @Nullable Expression<?> min,
      @Nullable Expression<?> max,
      boolean minInc,
      boolean maxInc,
      QueryMetadata metadata) {
    if (min != null && Number.class.isAssignableFrom(min.getType())
        || max != null && Number.class.isAssignableFrom(max.getType())) {
      @SuppressWarnings("unchecked") // guarded by previous check
      Constant<? extends Number> minConstant = (Constant<? extends Number>) min;
      @SuppressWarnings("unchecked") // guarded by previous check
      Constant<? extends Number> maxConstant = (Constant<? extends Number>) max;

      Class<? extends Number> numType =
          minConstant != null ? minConstant.getType() : maxConstant.getType();
      // this is not necessarily safe, but compile time checking
      // on the user side mandates these types to be interchangeable
      @SuppressWarnings("unchecked")
      Class<Number> unboundedNumType = (Class<Number>) numType;
      return numericRange(
          unboundedNumType,
          field,
          minConstant == null ? null : minConstant.getConstant(),
          maxConstant == null ? null : maxConstant.getConstant(),
          minInc,
          maxInc);
    }
    return stringRange(leftHandSide, field, min, max, minInc, maxInc, metadata);
  }