@Test
  public void testAnyOnArrayLiteral() throws Exception {
    Reference ref = createReference("d", DataTypes.STRING);
    Literal stringArrayLiteral =
        Literal.newLiteral(
            new Object[] {new BytesRef("a"), new BytesRef("b"), new BytesRef("c")},
            new ArrayType(DataTypes.STRING));

    // col != ANY (1,2,3)
    Query neqQuery = convert(whereClause(AnyNeqOperator.NAME, ref, stringArrayLiteral));
    assertThat(neqQuery, instanceOf(FilteredQuery.class));
    assertThat(((FilteredQuery) neqQuery).getFilter(), instanceOf(BooleanFilter.class));
    BooleanFilter filter = (BooleanFilter) ((FilteredQuery) neqQuery).getFilter();
    assertThat(filter.toString(), is("BooleanFilter(-BooleanFilter(+d:a +d:b +d:c))"));

    // col like any (1,2,3)
    Query likeQuery = convert(whereClause(AnyLikeOperator.NAME, ref, stringArrayLiteral));
    assertThat(likeQuery, instanceOf(BooleanQuery.class));
    BooleanQuery likeBQuery = (BooleanQuery) likeQuery;
    assertThat(likeBQuery.clauses().size(), is(3));
    for (int i = 0; i < 2; i++) {
      // like --> XConstantScoreQuery with regexp-filter
      Query filteredQuery = likeBQuery.clauses().get(i).getQuery();
      assertThat(filteredQuery, instanceOf(XConstantScoreQuery.class));
      assertThat(((XConstantScoreQuery) filteredQuery).getFilter(), instanceOf(RegexpFilter.class));
    }

    // col not like any (1,2,3)
    Query notLikeQuery = convert(whereClause(AnyNotLikeOperator.NAME, ref, stringArrayLiteral));
    assertThat(notLikeQuery, instanceOf(BooleanQuery.class));
    BooleanQuery notLikeBQuery = (BooleanQuery) notLikeQuery;
    assertThat(notLikeBQuery.clauses(), hasSize(1));
    BooleanClause clause = notLikeBQuery.clauses().get(0);
    assertThat(clause.getOccur(), is(BooleanClause.Occur.MUST_NOT));
    assertThat(((BooleanQuery) clause.getQuery()).clauses(), hasSize(3));
    for (BooleanClause innerClause : ((BooleanQuery) clause.getQuery()).clauses()) {
      assertThat(innerClause.getOccur(), is(BooleanClause.Occur.MUST));
      assertThat(innerClause.getQuery(), instanceOf(XConstantScoreQuery.class));
      assertThat(
          ((XConstantScoreQuery) innerClause.getQuery()).getFilter(),
          instanceOf(RegexpFilter.class));
    }

    // col < any (1,2,3)
    Query ltQuery2 = convert(whereClause(AnyLtOperator.NAME, ref, stringArrayLiteral));
    assertThat(ltQuery2, instanceOf(BooleanQuery.class));
    BooleanQuery ltBQuery = (BooleanQuery) ltQuery2;
    assertThat(ltBQuery.toString(), is("(d:{* TO a} d:{* TO b} d:{* TO c})~1"));
  }
      @Override
      protected Query applyArrayLiteral(Reference reference, Literal arrayLiteral, Context context)
          throws IOException {
        //  col != ANY ([1,2,3]) --> not(col=1 and col=2 and col=3)
        String columnName = reference.info().ident().columnIdent().fqn();
        QueryBuilderHelper builder = QueryBuilderHelper.forType(reference.valueType());
        BooleanFilter filter = new BooleanFilter();

        BooleanFilter notFilter = new BooleanFilter();
        for (Object value : toIterable(arrayLiteral.value())) {
          notFilter.add(builder.eqFilter(columnName, value), BooleanClause.Occur.MUST);
        }
        filter.add(notFilter, BooleanClause.Occur.MUST_NOT);

        return new FilteredQuery(Queries.newMatchAllQuery(), filter);
      }
      @Override
      public Query apply(Function input, Context context) {
        assert input != null;
        assert input.arguments().size() == 1;
        Symbol arg = input.arguments().get(0);
        if (arg.symbolType() != SymbolType.REFERENCE) {
          return null;
        }
        Reference reference = (Reference) arg;

        String columnName = reference.info().ident().columnIdent().fqn();
        QueryBuilderHelper builderHelper = QueryBuilderHelper.forType(reference.valueType());
        BooleanFilter boolFilter = new BooleanFilter();
        boolFilter.add(
            builderHelper.rangeFilter(columnName, null, null, true, true),
            BooleanClause.Occur.MUST_NOT);
        return new FilteredQuery(Queries.newMatchAllQuery(), boolFilter);
      }
      @Override
      public Query apply(Function input, Context context) {
        Tuple<Reference, Literal> tuple = super.prepare(input);
        if (tuple == null) {
          return null;
        }
        Reference reference = tuple.v1();
        Literal literal = tuple.v2();
        String columnName = reference.info().ident().columnIdent().fqn();
        if (DataTypes.isCollectionType(reference.valueType())
            && DataTypes.isCollectionType(literal.valueType())) {

          // create boolean filter with term filters to pre-filter the result before applying the
          // functionQuery.
          BooleanFilter boolTermsFilter = new BooleanFilter();
          DataType type = literal.valueType();
          while (DataTypes.isCollectionType(type)) {
            type = ((CollectionType) type).innerType();
          }
          QueryBuilderHelper builder = QueryBuilderHelper.forType(type);
          Object value = literal.value();
          buildTermsQuery(boolTermsFilter, value, columnName, builder);

          if (boolTermsFilter.clauses().isEmpty()) {
            // all values are null...
            return genericFunctionQuery(input, context);
          }

          // wrap boolTermsFilter and genericFunction filter in an additional BooleanFilter to
          // control the ordering of the filters
          // termsFilter is applied first
          // afterwards the more expensive genericFunctionFilter
          BooleanFilter filterClauses = new BooleanFilter();
          filterClauses.add(boolTermsFilter, BooleanClause.Occur.MUST);
          filterClauses.add(genericFunctionFilter(input, context), BooleanClause.Occur.MUST);
          return new FilteredQuery(Queries.newMatchAllQuery(), filterClauses);
        }
        QueryBuilderHelper builder = QueryBuilderHelper.forType(tuple.v1().valueType());
        return builder.eq(columnName, tuple.v2().value());
      }
  @Test
  public void testEqOnTwoArraysBecomesGenericFunctionQuery() throws Exception {
    DataType longArray = new ArrayType(DataTypes.LONG);
    Query query =
        convert(
            new WhereClause(
                createFunction(
                    EqOperator.NAME,
                    DataTypes.BOOLEAN,
                    createReference("x", longArray),
                    Literal.newLiteral(longArray, new Object[] {10L, null, 20L}))));
    assertThat(query, instanceOf(FilteredQuery.class));
    FilteredQuery filteredQuery = (FilteredQuery) query;

    assertThat(filteredQuery.getFilter(), instanceOf(BooleanFilter.class));
    assertThat(filteredQuery.getQuery(), instanceOf(XConstantScoreQuery.class));

    BooleanFilter filter = (BooleanFilter) filteredQuery.getFilter();
    assertThat(
        filter.clauses().get(0).getFilter(),
        instanceOf(BooleanFilter.class)); // booleanFilter with terms filter
    assertThat(
        filter.clauses().get(1).getFilter(), instanceOf(Filter.class)); // generic function filter
  }
 private void buildTermsQuery(
     BooleanFilter booleanFilter,
     Object value,
     String columnName,
     QueryBuilderHelper builder) {
   if (value == null) {
     return;
   }
   if (value.getClass().isArray()) {
     Object[] array = (Object[]) value;
     for (Object o : array) {
       buildTermsQuery(booleanFilter, o, columnName, builder);
     }
   } else {
     booleanFilter.add(builder.eqFilter(columnName, value), BooleanClause.Occur.MUST);
   }
 }