Fulltextfilter vs. Boolean QueryBuilder

Hi,
i really like Hibernate Search but can’t figure the design of Filters and when to use them.

We have a set of ca. 20 “Filterdefinitions” that will be reused at several occasions. Currently we use the enableFullTextFilter Feature. They work nicely but the thing that bugs me most is that the Lucene Queries are very difficult to write and they don’t throw exceptions on bad index or automatically use the same analyzers at query time.

  1. Is there a way to use the QueryBuilder in the Filters? (For exceptions on bad index configuration and automatic analyzer usage)
  2. Should i rather use the QueryBuilder with a large bool() Set and build my own Setup to “enable a Filter” in the bool() query? (Maintainability, Easier for developers)
  3. How will Hibernate Search 6 effect this? What is the recommended way that could work or would be easy to adapt for Hibernate Search 6?

With best wishes,
Peter

WIP Code of Filter usage:

if(kauffallSuche.verkaufsjahrVon != null || kauffallSuche.verkaufsjahrBis != null) {
            final LocalDate von = Optional.ofNullable(kauffallSuche.verkaufsjahrVon)
                    .map(j -> LocalDate.ofYearDay(j, 1).with(TemporalAdjusters.firstDayOfYear()))
                    .orElse(LocalDate.MIN);
            final LocalDate bis = Optional.ofNullable(kauffallSuche.verkaufsjahrBis)
                    .map(j -> LocalDate.ofYearDay(j, 1).with(TemporalAdjusters.lastDayOfYear()))
                    .orElse(LocalDate.MAX);
            fullTextQuery.enableFullTextFilter(FilterVerkaufsdatum.NAME)
                    .setParameter(FilterVerkaufsdatum.Param.verkaufsdatumVon.name(), von)
                    .setParameter(FilterVerkaufsdatum.Param.verkaufsdatumBis.name(), bis);

        }

WIP Code of Filter

public class FilterVerkaufsdatum {
    public static final String NAME = "verkaufsdatum";
    public enum Param {
        verkaufsdatumVon, verkaufsdatumBis
    }

    @Nullable
    private LocalDate verkaufsdatumVon;
    @Nullable
    private LocalDate verkaufsdatumBis;

    /**
     * injected parameter
     */
    public void setVerkaufsdatumVon(LocalDate von) {
        this.verkaufsdatumVon = von;
    }
    /**
     * injected parameter
     */
    public void setVerkaufsdatumBis(LocalDate bis) {
        this.verkaufsdatumBis = bis;
    }

    @Factory
    public Query getFilter() {
        final String path = String.join(".",
                Kauffall_.URKUNDE,
                Urkunde_.URKUNDEN_DATUM);

        return TermRangeQuery.newStringRange(
                path,
                // in case of search bugs, check if LocalDateBridge is really used on the field
                LocalDateBridge.INSTANCE.objectToString(Optional.ofNullable(verkaufsdatumVon).orElse(LocalDate.MIN)),
                LocalDateBridge.INSTANCE.objectToString(Optional.ofNullable(verkaufsdatumBis).orElse(LocalDate.MAX)),
                true, true
        );
    }
}

Hello,

Not easily:

  1. You would first need to get hold of the current Session/EntityManager from your filter, without any reference to your framework (Spring/CDI). That in itself is not always easy, but depending on your framework it may be doable.
  2. Then you can use Search.getFullTextSession/Search.getFullTextEntityManager and get the query builder as usual.

EDIT: Actually, you may be able to pass the QueryBuilder as a parameter? Quite a hack, but if it works…

You can definitely achieve results similar to the “full-text filters” without using them.

You can simply share code like usually do, by exposing methods in other components or in static helper classes. Something like this:

public class PurchaseDateFilter {

    static Query createQuery(QueryBuilder qb, LocalDate from, LocalDate to) {
        return qb.range(). ... .createQuery();
    }

}

public class ProductRepository {

    public List<T> search(...) {
        QueryBuilder qb = ...;
        BooleanJunction bool = qb.bool();
        bool.must( qb.all().createQuery() ); // Match all by default

        // Custom queries
        bool.must( qb.... );
       ...

        // Shared filters
        if ( from != null || to != null ) {
            bool.filteredBy( PurchaseDateFilter.createQuery( qb, from, to ) );
        }

        FullTextQuery ftQuery = ...;
        return ftQuery.list();
    }
}

Full-text filters are not implemented yet in Hibernate Search 6.

I personally have trouble figuring out the point of full-text filters, even in Hibernate Search 5, when you can do things like I showed above, which allow the use of a query builder and make the contract (parameters and their type) much clearer.

Full-text filters are an old feature, and the actual use cases are a bit lost in the mists of time. I’ve been told they are used by some for security purposes (using “advices” after the query is created), but to me that looks like quite an “afterthought” and dodgy approach to security.

So at the moment, re-implementing them is definitely not a priority, unless someone comes up with a compelling use case. Full-text filters may reappear at some point, but maybe not immediately in version 6.0, and maybe in a different form, to address a different problem: that of defining predicates on “object fields”, as explained in HSEARCH-3320. There is currently a pull request that seem to be going in this direction, but it has several problems and I haven’t found the time to solve them yet.

In short:

  • You can certainly use whatever you prefer.
  • Personally, I would pick the “util method” solution shown in the snippet above. But that’s admittedly a matter of taste.
  • In Hibernate Search 6, you can use the “util method” solution, but not the “named” full-text filters (yet).
  • Full-text filters may reappear in Hibernate Search 6 in a different form and to address a different use case.

Thanks! Thats a fast and great answer :smiley:. I will refactor towards the “util method” solution. Thanks!