javapostgresqlhibernatepostgishibernate-spatial

Spring(Boot) with JPA / Hibernate and PostgreSQL - use Overlaps for dates


So i have a project where we use springBoot and PostgreSQL 10 with PostGis and hibernate.spatial for spatial queries. Everything works fine so far.

A new requirement is to find entities, which start-end dates overlap the start-end dates of the query in any possible way (ranges might be enclosing, start-overlap, inbetween, end-overlap).

In PostgreSQL theres the Overlaps operator that seems a pretty good fit for the job.

When trying to use it within my JPA-Query for an Entity "Sth" like this..

select sth from Sth sth where 1=1 and (sth.start, sth.end) overlaps (:begin, :end)
// set begin and end params..

i get one of..

antlr.NoViableAltException: unexpected token: overlaps

antlr.NoViableAltException: unexpected AST node: (

org.postgresql.util.PSQLException: FEHLER: rt_raster_from_wkb: wkb size (5)  < min size (61)

Is it possible to use overlaps for dates with JPA without writing a native query?


Solution

  • So seems like there are three things you need to do to make it work.

    1. Its probably not possible to use overlaps as an operator, but luckily it seems it can also be used as a function: overlaps(start1, end1, start2, end2)

    2. Overlaps is not mapped by any of the hibernate-core PostgreSQL[NN]Dialects. But it is mapped by hibernate-spatial PostgisPG[NN]Dialects, where it maps to the st_overlaps-function for spatial overlapping. So you need to use your own custom Dialect that registers the overlaps function with an alias like so:


    public class PostgisDialect extends org.hibernate.spatial.dialect.postgis.PostgisPG95Dialect {
    
        public PostgisDialect() {
            super();
            registerFunction("dateoverlaps", new StandardSQLFunction("overlaps", StandardBasicTypes.BOOLEAN));
        }
    
    }
    

    and specify it as your spring.jpa.properties.hibernate.dialect (or spring.jpa.database-platform 🤷‍♂️).

    1. Your JPA query must include a = true like this:

    select sth from Sth sth where 1=1 and dateoverlaps(sth.start, sth.end, :begin, :end) = true
    

    You might addtionally enhance this by using the coalesce function to handle null-values, i.e.

    select sth from Sth sth where 1=1 and dateoverlaps(coalesce(sth.start, sth.plannedStart), coalesce(sth.end, '3999-01-01'), :begin, :end) = true
    

    which uses plannedStart when start is null and a long-in-the-future-date when end is null / open.