hibernatelucenehibernate-search

Match null of non-string field in Hibernate Search query


I am trying to implement a query for the following logic:

Match results where endDate is above now OR endDate is not present (i.e. null)

So far, I am able to do the first part as following:

dateQuery = queryBuilder.range()
                        .onField("endDate")
                        .above(LocalDateTime.now())
                        .createQuery();

Unfortunately, I cannot figure out the second part. I have tried this (and failed):

queryBuilder.bool()
            .must(dateQuery)
            .should(queryBuilder.keyword().onField("endDate")
                                .matching("").createQuery())
            .createQuery();

Is there any elegant way of checking if a non-string field is Null in Hibernate Search?


Solution

  • By default empty/null values are not indexed, thus looking for an empty/null value will not return any result.

    With Hibernate Search 6 and above, consider the exists() predicate predicate, which you can negate to find documents where a given field does not exist (has no value).

    Hibernate Search 6.0 / 6.1:

    List<Book> hits = searchSession.search( Book.class )
            .where( f -> f.bool().mustNot( f.exists().field( "endDate" ) ) )
            .fetchHits( 20 );
    

    Or a simpler syntax with the upcoming release of Hibernate Search 6.2:

    List<Book> hits = searchSession.search( Book.class )
            .where( f -> f.not( f.exists().field( "endDate" ) ) )
            .fetchHits( 20 );
    

    Note however that, as a purely negative query, this won't perform well.

    For larger indexes, if you want decent performance, consider indexing null as a non-null value. See @GenericField(indexNullAs = ...) (also available on other @*Field annotations).

    Basically you'll annotate your property with something like @GenericField(indexNullAs = "9999-12-31T23:59:59.999"), then you'll create a query looking for the value LocalDateTime.parse("9999-12-31T23:59:59.999"), and Hibernate Search will return all documents that had a null value when indexed.


    Old answer for Hibernate Search 5:

    You should use @Field(indexNullAs = "...") in your mapping. In your case you should set indexNullAs to something like 9999-12-31T23:59:59.999 (last milisecond of the last day of year 9999).

    See the documentation about Field.indexNullAs for more information.