From cab651e19451230ce61c25bb5f68b15a654d0a8f Mon Sep 17 00:00:00 2001 From: Daniel Shuy Date: Mon, 24 Feb 2020 21:42:48 +0800 Subject: [PATCH] HHH-13799 : Criteria API support for Hibernate Spatial (#3159) * HHH-13799 : Criteria API support for Hibernate Spatial Co-authored-by: Karel Maesen --- .../db2/resources/hibernate.properties | 2 +- .../resources/hibernate.properties | 2 +- hibernate-spatial/matrix-test.sh | 3 + .../hibernate/spatial/SpatialFunction.java | 10 +- .../spatial/dialect/WithCustomJPAFilter.java | 18 + .../spatial/dialect/h2geodb/GeoDBDialect.java | 31 + .../dialect/hana/HANASpatialDialect.java | 19 + .../dialect/mysql/MySQL5SpatialFunctions.java | 8 + .../dialect/mysql/MySQL8SpatialFunctions.java | 8 + .../dialect/oracle/OracleSDOSupport.java | 12 +- .../oracle/OracleSpatial10gDialect.java | 8 +- .../oracle/OracleSpatialFunctions.java | 7 + .../oracle/OracleSpatialSDO10gDialect.java | 9 +- .../dialect/postgis/PostgisFunctions.java | 31 + .../dialect/sqlserver/SqlServerFunctions.java | 3 + .../spatial/predicate/FilterPredicate.java | 80 +++ .../spatial/predicate/SpatialPredicates.java | 575 ++++++++++++++++++ .../integration/TestSpatialPredicates.java | 264 ++++++++ .../spatial/testing/TestSupportFactories.java | 1 - .../dialects/postgis/PostgisTestSupport.java | 9 +- 20 files changed, 1087 insertions(+), 13 deletions(-) create mode 100644 hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/WithCustomJPAFilter.java create mode 100644 hibernate-spatial/src/main/java/org/hibernate/spatial/predicate/FilterPredicate.java create mode 100644 hibernate-spatial/src/main/java/org/hibernate/spatial/predicate/SpatialPredicates.java create mode 100644 hibernate-spatial/src/test/java/org/hibernate/spatial/integration/TestSpatialPredicates.java diff --git a/hibernate-spatial/databases/db2/resources/hibernate.properties b/hibernate-spatial/databases/db2/resources/hibernate.properties index dd6ec8b7a7..8e18cb62c5 100644 --- a/hibernate-spatial/databases/db2/resources/hibernate.properties +++ b/hibernate-spatial/databases/db2/resources/hibernate.properties @@ -10,7 +10,7 @@ hibernate.dialect org.hibernate.spatial.dialect.db2.DB2SpatialDialect hibernate.connection.driver_class com.ibm.db2.jcc.DB2Driver hibernate.connection.url jdbc:db2://localhost:50000/hibern8 hibernate.connection.username db2inst1 -hibernate.connection.password password +hibernate.connection.password oPucroAsMAgL hibernate.connection.pool_size 5 diff --git a/hibernate-spatial/databases/sqlserver2012_spatial/resources/hibernate.properties b/hibernate-spatial/databases/sqlserver2012_spatial/resources/hibernate.properties index 741dbcb220..85e9226d79 100644 --- a/hibernate-spatial/databases/sqlserver2012_spatial/resources/hibernate.properties +++ b/hibernate-spatial/databases/sqlserver2012_spatial/resources/hibernate.properties @@ -9,7 +9,7 @@ hibernate.dialect org.hibernate.spatial.dialect.sqlserver.SqlServer2012SpatialDi hibernate.connection.driver_class com.microsoft.sqlserver.jdbc.SQLServerDriver hibernate.connection.url jdbc:sqlserver://localhost:1433;databaseName=TestDb hibernate.connection.username hibern8 -hibernate.connection.password hibern8Pass +hibernate.connection.password langpaswoord123A%1 hibernate.connection.pool_size 5 diff --git a/hibernate-spatial/matrix-test.sh b/hibernate-spatial/matrix-test.sh index 911098fb54..472055c484 100755 --- a/hibernate-spatial/matrix-test.sh +++ b/hibernate-spatial/matrix-test.sh @@ -1,5 +1,8 @@ #! /bin/bash +## The same effect can be achieved by setting the system properties +# in ~/.gradle/gradle.properties + TASK=matrix if [[ -n $@ ]]; then TASK="$@" diff --git a/hibernate-spatial/src/main/java/org/hibernate/spatial/SpatialFunction.java b/hibernate-spatial/src/main/java/org/hibernate/spatial/SpatialFunction.java index 4d57e565be..4227b04413 100644 --- a/hibernate-spatial/src/main/java/org/hibernate/spatial/SpatialFunction.java +++ b/hibernate-spatial/src/main/java/org/hibernate/spatial/SpatialFunction.java @@ -164,8 +164,16 @@ public enum SpatialFunction { /** * the extents function */ - extent( "common" ); + extent( "common" ), + /** + * The filter function + *

+ *

Corresponds to the Oracle Spatial's "SDO_FILTER" function, or the "&&" operator of PostGIS. + */ + filter( "filter" ), + + ; private final String description; diff --git a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/WithCustomJPAFilter.java b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/WithCustomJPAFilter.java new file mode 100644 index 0000000000..2d81d15d23 --- /dev/null +++ b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/WithCustomJPAFilter.java @@ -0,0 +1,18 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.spatial.dialect; + +/** + * An Interface for {@code SpatialDialect}s that require a custom + * rendering to JPAQL for the filter predicate + *

+ * Created by Karel Maesen, Geovise BVBA on 09/02/2020. + */ +public interface WithCustomJPAFilter { + + String filterExpression(String geometryParam, String filterParam); +} diff --git a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/h2geodb/GeoDBDialect.java b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/h2geodb/GeoDBDialect.java index 38ef54b974..4c4df78cad 100644 --- a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/h2geodb/GeoDBDialect.java +++ b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/h2geodb/GeoDBDialect.java @@ -7,9 +7,15 @@ package org.hibernate.spatial.dialect.h2geodb; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.hibernate.QueryException; import org.hibernate.boot.model.TypeContributions; import org.hibernate.dialect.H2Dialect; import org.hibernate.dialect.function.StandardSQLFunction; +import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.service.ServiceRegistry; import org.hibernate.spatial.GeolatteGeometryJavaTypeDescriptor; import org.hibernate.spatial.GeolatteGeometryType; @@ -19,6 +25,7 @@ import org.hibernate.spatial.SpatialDialect; import org.hibernate.spatial.SpatialFunction; import org.hibernate.spatial.SpatialRelation; import org.hibernate.type.StandardBasicTypes; +import org.hibernate.type.Type; /** * Extends the H2Dialect by also including information on spatial functions. @@ -74,6 +81,8 @@ public class GeoDBDialect extends H2Dialect implements SpatialDialect { registerFunction( "dwithin", new StandardSQLFunction( "ST_DWithin", StandardBasicTypes.BOOLEAN ) ); + // Register Spatial Filter function + registerFunction( SpatialFunction.filter.name(), new FilterFunction() ); } @Override @@ -157,4 +166,26 @@ public class GeoDBDialect extends H2Dialect implements SpatialDialect { return function != SpatialFunction.difference && ( getFunctions().get( function.toString() ) != null ); } + private static class FilterFunction extends StandardSQLFunction { + + public FilterFunction() { + super( "&&" ); + } + + @Override + public String render( + Type firstArgumentType, List arguments, SessionFactoryImplementor sessionFactory) { + int argumentCount = arguments.size(); + if ( argumentCount != 2 ) { + throw new QueryException( String.format( "2 arguments expected, received %d", argumentCount ) ); + } + + return Stream.of( + String.valueOf( arguments.get( 0 ) ), + getRenderedName( arguments ), + String.valueOf( arguments.get( 1 ) ) + ).collect( Collectors.joining( " ", "(", ")" ) ); + } + } + } diff --git a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/hana/HANASpatialDialect.java b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/hana/HANASpatialDialect.java index 9a41fe09ca..43b3fa0ac7 100644 --- a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/hana/HANASpatialDialect.java +++ b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/hana/HANASpatialDialect.java @@ -7,11 +7,13 @@ package org.hibernate.spatial.dialect.hana; import java.sql.Types; +import java.util.List; import org.hibernate.boot.model.TypeContributions; import org.hibernate.dialect.HANAColumnStoreDialect; import org.hibernate.engine.config.spi.ConfigurationService; import org.hibernate.engine.config.spi.ConfigurationService.Converter; +import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.service.ServiceRegistry; import org.hibernate.spatial.GeolatteGeometryJavaTypeDescriptor; import org.hibernate.spatial.GeolatteGeometryType; @@ -22,6 +24,7 @@ import org.hibernate.spatial.SpatialDialect; import org.hibernate.spatial.SpatialFunction; import org.hibernate.spatial.SpatialRelation; import org.hibernate.type.StandardBasicTypes; +import org.hibernate.type.Type; public class HANASpatialDialect extends HANAColumnStoreDialect implements SpatialDialect { @@ -102,6 +105,9 @@ public class HANASpatialDialect extends HANAColumnStoreDialect implements Spatia registerFunction( SpatialFunction.within.name(), new HANASpatialFunction( "ST_Within", StandardBasicTypes.NUMERIC_BOOLEAN, true ) ); + registerFunction( + SpatialFunction.filter.name(), + new FilterFunction() ); /* * Additional HANA functions @@ -408,4 +414,17 @@ public class HANASpatialDialect extends HANAColumnStoreDialect implements Spatia } return false; } + + private static class FilterFunction extends HANASpatialFunction { + + public FilterFunction() { + super( "ST_IntersectsFilter", StandardBasicTypes.NUMERIC_BOOLEAN, true ); + } + + @Override + public String render( + Type firstArgumentType, List arguments, SessionFactoryImplementor sessionFactory) { + return super.render( firstArgumentType, arguments, sessionFactory ) + " = 1"; + } + } } diff --git a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/mysql/MySQL5SpatialFunctions.java b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/mysql/MySQL5SpatialFunctions.java index 40bd5fbf98..85f972129b 100644 --- a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/mysql/MySQL5SpatialFunctions.java +++ b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/mysql/MySQL5SpatialFunctions.java @@ -7,6 +7,7 @@ package org.hibernate.spatial.dialect.mysql; import org.hibernate.dialect.function.StandardSQLFunction; +import org.hibernate.spatial.SpatialFunction; import org.hibernate.spatial.dialect.SpatialFunctionsRegistry; import org.hibernate.type.StandardBasicTypes; @@ -163,6 +164,13 @@ class MySQL5SpatialFunctions extends SpatialFunctionsRegistry { // "union" // ) // ); + + functionMap.put( + SpatialFunction.filter.name(), new StandardSQLFunction( + "MBRIntersects", + StandardBasicTypes.BOOLEAN + ) + ); } } diff --git a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/mysql/MySQL8SpatialFunctions.java b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/mysql/MySQL8SpatialFunctions.java index 4084e99b17..a0ec9c314f 100644 --- a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/mysql/MySQL8SpatialFunctions.java +++ b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/mysql/MySQL8SpatialFunctions.java @@ -7,6 +7,7 @@ package org.hibernate.spatial.dialect.mysql; import org.hibernate.dialect.function.StandardSQLFunction; +import org.hibernate.spatial.SpatialFunction; import org.hibernate.spatial.dialect.SpatialFunctionsRegistry; import org.hibernate.type.StandardBasicTypes; @@ -169,6 +170,13 @@ class MySQL8SpatialFunctions extends SpatialFunctionsRegistry { "ST_Union" ) ); + + functionMap.put( + SpatialFunction.filter.name(), new StandardSQLFunction( + "MBRIntersects", + StandardBasicTypes.BOOLEAN + ) + ); } } diff --git a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/oracle/OracleSDOSupport.java b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/oracle/OracleSDOSupport.java index e7af3ce58d..2b066d561b 100644 --- a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/oracle/OracleSDOSupport.java +++ b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/oracle/OracleSDOSupport.java @@ -7,6 +7,7 @@ package org.hibernate.spatial.dialect.oracle; import java.io.Serializable; +import java.util.Locale; import org.hibernate.boot.model.TypeContributions; import org.hibernate.boot.registry.selector.spi.StrategySelector; @@ -22,6 +23,7 @@ import org.hibernate.spatial.SpatialDialect; import org.hibernate.spatial.SpatialFunction; import org.hibernate.spatial.SpatialRelation; import org.hibernate.spatial.dialect.SpatialFunctionsRegistry; +import org.hibernate.spatial.dialect.WithCustomJPAFilter; import org.jboss.logging.Logger; @@ -33,7 +35,7 @@ import org.geolatte.geom.codec.db.oracle.OracleJDBCTypeFactory; *

* Created by Karel Maesen, Geovise BVBA on 01/11/16. */ -class OracleSDOSupport implements SpatialDialect, Serializable { +class OracleSDOSupport implements SpatialDialect, Serializable, WithCustomJPAFilter { private static final HSMessageLogger log = Logger.getMessageLogger( HSMessageLogger.class, @@ -290,7 +292,7 @@ class OracleSDOSupport implements SpatialDialect, Serializable { */ @Override public String getHavingSridSQL(String columnName) { - return String.format( " (MDSYS.ST_GEOMETRY(%s).ST_SRID() = ?)", columnName ); + return String.format( " (MDSYS.ST_GEOMETRY(%s).ST_SRID() = ?)", columnName , Locale.US); } /** @@ -304,7 +306,7 @@ class OracleSDOSupport implements SpatialDialect, Serializable { */ @Override public String getIsEmptySQL(String columnName, boolean isEmpty) { - return String.format( "( MDSYS.ST_GEOMETRY(%s).ST_ISEMPTY() = %d )", columnName, isEmpty ? 1 : 0 ); + return String.format( "( MDSYS.ST_GEOMETRY(%s).ST_ISEMPTY() = %d )", columnName, isEmpty ? 1 : 0 , Locale.US); } /** @@ -331,4 +333,8 @@ class OracleSDOSupport implements SpatialDialect, Serializable { } + @Override + public String filterExpression(String geometryParam, String filterParam) { + return SpatialFunction.filter.name() + "(" + geometryParam + ", " + filterParam + ") = 'TRUE' "; + } } diff --git a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/oracle/OracleSpatial10gDialect.java b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/oracle/OracleSpatial10gDialect.java index ec6de6ddc2..84ce98d45d 100644 --- a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/oracle/OracleSpatial10gDialect.java +++ b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/oracle/OracleSpatial10gDialect.java @@ -18,6 +18,7 @@ import org.hibernate.service.ServiceRegistry; import org.hibernate.spatial.HSMessageLogger; import org.hibernate.spatial.SpatialDialect; import org.hibernate.spatial.SpatialFunction; +import org.hibernate.spatial.dialect.WithCustomJPAFilter; import org.jboss.logging.Logger; @@ -26,7 +27,7 @@ import org.jboss.logging.Logger; * * @author Karel Maesen */ -public class OracleSpatial10gDialect extends Oracle10gDialect implements SpatialDialect, Serializable { +public class OracleSpatial10gDialect extends Oracle10gDialect implements SpatialDialect, WithCustomJPAFilter, Serializable { private static final HSMessageLogger log = Logger.getMessageLogger( HSMessageLogger.class, @@ -101,5 +102,8 @@ public class OracleSpatial10gDialect extends Oracle10gDialect implements Spatial return ( getFunctions().get( function.toString() ) != null ); } - + @Override + public String filterExpression(String geometryParam, String filterParam) { + return sdoSupport.filterExpression( geometryParam, filterParam ); + } } diff --git a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/oracle/OracleSpatialFunctions.java b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/oracle/OracleSpatialFunctions.java index a93429c66e..e25e5e5e87 100644 --- a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/oracle/OracleSpatialFunctions.java +++ b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/oracle/OracleSpatialFunctions.java @@ -12,6 +12,7 @@ import org.hibernate.QueryException; import org.hibernate.dialect.function.StandardSQLFunction; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.spatial.SpatialAnalysis; +import org.hibernate.spatial.SpatialFunction; import org.hibernate.spatial.SpatialRelation; import org.hibernate.spatial.dialect.SpatialFunctionsRegistry; import org.hibernate.spatial.dialect.oracle.criterion.OracleSpatialAggregate; @@ -138,6 +139,12 @@ class OracleSpatialFunctions extends SpatialFunctionsRegistry { new SpatialAggregationFunction( "extent", OracleSpatialAggregate.EXTENT, sdoSupport ) ); + // spatial filter function + put( + SpatialFunction.filter.name(), + new StandardSQLFunction( "SDO_FILTER" ) + ); + //other common functions put( "transform", new StandardSQLFunction( "SDO_CS.TRANSFORM" ) ); diff --git a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/oracle/OracleSpatialSDO10gDialect.java b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/oracle/OracleSpatialSDO10gDialect.java index fc97d85b33..297ae591a2 100644 --- a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/oracle/OracleSpatialSDO10gDialect.java +++ b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/oracle/OracleSpatialSDO10gDialect.java @@ -17,6 +17,7 @@ import org.hibernate.service.ServiceRegistry; import org.hibernate.spatial.HSMessageLogger; import org.hibernate.spatial.SpatialDialect; import org.hibernate.spatial.SpatialFunction; +import org.hibernate.spatial.dialect.WithCustomJPAFilter; import org.jboss.logging.Logger; @@ -25,7 +26,8 @@ import org.jboss.logging.Logger; *

* Created by Karel Maesen, Geovise BVBA on 11/02/17. */ -public class OracleSpatialSDO10gDialect extends Oracle10gDialect implements SpatialDialect, Serializable { +public class OracleSpatialSDO10gDialect extends Oracle10gDialect + implements SpatialDialect, WithCustomJPAFilter, Serializable { private static final HSMessageLogger log = Logger.getMessageLogger( HSMessageLogger.class, @@ -100,5 +102,8 @@ public class OracleSpatialSDO10gDialect extends Oracle10gDialect implements Spat return !function.equals( SpatialFunction.crosses ) && ( getFunctions().get( function.toString() ) != null ); } - + @Override + public String filterExpression(String geometryParam, String filterParam) { + return sdoSupport.filterExpression( geometryParam, filterParam ); + } } diff --git a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PostgisFunctions.java b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PostgisFunctions.java index 2a97c77cf8..0c3c08eb19 100644 --- a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PostgisFunctions.java +++ b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PostgisFunctions.java @@ -7,9 +7,13 @@ package org.hibernate.spatial.dialect.postgis; import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.hibernate.QueryException; import org.hibernate.dialect.function.StandardSQLFunction; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.spatial.SpatialFunction; import org.hibernate.spatial.dialect.SpatialFunctionsRegistry; import org.hibernate.type.StandardBasicTypes; import org.hibernate.type.Type; @@ -177,6 +181,11 @@ class PostgisFunctions extends SpatialFunctionsRegistry { "extent", new ExtentFunction() ); + //register Spatial Filter function + put( + SpatialFunction.filter.name(), new FilterFunction() + ); + //other common functions put( "dwithin", new StandardSQLFunction( @@ -206,4 +215,26 @@ class PostgisFunctions extends SpatialFunctionsRegistry { } } + private static class FilterFunction extends StandardSQLFunction { + + public FilterFunction() { + super( "&&" ); + } + + @Override + public String render( + Type firstArgumentType, List arguments, SessionFactoryImplementor sessionFactory) { + int argumentCount = arguments.size(); + if ( argumentCount != 2 ) { + throw new QueryException( String.format( "2 arguments expected, received %d", argumentCount ) ); + } + + return Stream.of( + String.valueOf( arguments.get( 0 ) ), + getRenderedName( arguments ), + String.valueOf( arguments.get( 1 ) ) + ).collect( Collectors.joining( " ", "(", ")" ) ); + } + } + } diff --git a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/sqlserver/SqlServerFunctions.java b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/sqlserver/SqlServerFunctions.java index d002221f17..5dd20c9b28 100644 --- a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/sqlserver/SqlServerFunctions.java +++ b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/sqlserver/SqlServerFunctions.java @@ -7,6 +7,7 @@ package org.hibernate.spatial.dialect.sqlserver; import org.hibernate.dialect.function.SQLFunctionTemplate; +import org.hibernate.spatial.SpatialFunction; import org.hibernate.spatial.dialect.SpatialFunctionsRegistry; import org.hibernate.type.StandardBasicTypes; @@ -62,5 +63,7 @@ class SqlServerFunctions extends SpatialFunctionsRegistry { "pointonsurface", new SqlServerMethod( "STPointOnSurface" ) ); + // Register spatial filter function. + put( SpatialFunction.filter.name(), new SQLFunctionTemplate( StandardBasicTypes.BOOLEAN, "?1.Filter(?2)" ) ); } } diff --git a/hibernate-spatial/src/main/java/org/hibernate/spatial/predicate/FilterPredicate.java b/hibernate-spatial/src/main/java/org/hibernate/spatial/predicate/FilterPredicate.java new file mode 100644 index 0000000000..88883793b3 --- /dev/null +++ b/hibernate-spatial/src/main/java/org/hibernate/spatial/predicate/FilterPredicate.java @@ -0,0 +1,80 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.spatial.predicate; + +import java.io.Serializable; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.Expression; +import javax.persistence.criteria.Predicate; + +import org.hibernate.dialect.Dialect; +import org.hibernate.query.criteria.internal.CriteriaBuilderImpl; +import org.hibernate.query.criteria.internal.ParameterRegistry; +import org.hibernate.query.criteria.internal.Renderable; +import org.hibernate.query.criteria.internal.compile.RenderingContext; +import org.hibernate.query.criteria.internal.predicate.AbstractSimplePredicate; +import org.hibernate.spatial.SpatialDialect; +import org.hibernate.spatial.SpatialFunction; +import org.hibernate.spatial.criterion.SpatialFilter; +import org.hibernate.spatial.dialect.WithCustomJPAFilter; +import org.hibernate.spatial.jts.EnvelopeAdapter; + +import org.locationtech.jts.geom.Envelope; +import org.locationtech.jts.geom.Geometry; + +/** + * JPA Criteria API {@link Predicate} equivalent of {@link SpatialFilter}. + */ +public class FilterPredicate extends AbstractSimplePredicate implements Serializable { + + private final Expression geometry; + private final Expression filter; + + public FilterPredicate( + CriteriaBuilder criteriaBuilder, Expression geometry, + Expression filter) { + super( (CriteriaBuilderImpl) criteriaBuilder ); + this.geometry = geometry; + this.filter = filter; + } + + public FilterPredicate( + CriteriaBuilder criteriaBuilder, Expression geometry, + Geometry filter) { + this( criteriaBuilder, geometry, criteriaBuilder.literal( filter ) + ); + } + + public FilterPredicate( + CriteriaBuilder criteriaBuilder, Expression geometry, + Envelope envelope, int srid) { + this( criteriaBuilder, geometry, EnvelopeAdapter.toPolygon( envelope, srid ) + ); + } + + @Override + public void registerParameters(ParameterRegistry registry) { + Helper.possibleParameter( geometry, registry ); + Helper.possibleParameter( filter, registry ); + } + + @Override + public String render(boolean isNegated, RenderingContext renderingContext) { + String geometryParameter = ( (Renderable) geometry ).render( renderingContext ); + String filterParameter = ( (Renderable) filter ).render( renderingContext ); + Dialect dialect = renderingContext.getDialect(); + if ( !( dialect instanceof SpatialDialect ) ) { + throw new IllegalStateException( "Dialect must be spatially enabled dialect" ); + } + if ( dialect instanceof WithCustomJPAFilter ) { + return ( (WithCustomJPAFilter) dialect ).filterExpression( geometryParameter, filterParameter ); + } + else { + return SpatialFunction.filter.name() + "(" + geometryParameter + ", " + filterParameter + ") = true"; + } + } +} diff --git a/hibernate-spatial/src/main/java/org/hibernate/spatial/predicate/SpatialPredicates.java b/hibernate-spatial/src/main/java/org/hibernate/spatial/predicate/SpatialPredicates.java new file mode 100644 index 0000000000..df803ae6e7 --- /dev/null +++ b/hibernate-spatial/src/main/java/org/hibernate/spatial/predicate/SpatialPredicates.java @@ -0,0 +1,575 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.spatial.predicate; + +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.Expression; +import javax.persistence.criteria.Predicate; + +import org.hibernate.spatial.SpatialFunction; +import org.hibernate.spatial.criterion.SpatialRestrictions; + +import org.locationtech.jts.geom.Envelope; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.Polygon; + +/** + * A factory for spatial JPA Criteria API {@link Predicate}s. + * + * @author Daniel Shuy + * @see SpatialRestrictions + */ +public final class SpatialPredicates { + + private SpatialPredicates() { + } + + /** + * Create a predicate for testing the arguments for "spatially equal" constraint. + * + * @param criteriaBuilder CriteriaBuilder + * @param geometry1 geometry expression + * @param geometry2 geometry expression + * + * @return "spatially equal" predicate + * + * @see SpatialRestrictions#eq(String, Geometry) + */ + public static Predicate eq( + CriteriaBuilder criteriaBuilder, Expression geometry1, + Expression geometry2) { + return booleanExpressionToPredicate( + criteriaBuilder, + criteriaBuilder.function( SpatialFunction.equals.toString(), boolean.class, + geometry1, geometry2 + ) + ); + } + + /** + * Create a predicate for testing the arguments for "spatially equal" constraint. + * + * @param criteriaBuilder CriteriaBuilder + * @param geometry1 geometry expression + * @param geometry2 geometry value + * + * @return "spatially equal" predicate + * + * @see #eq(CriteriaBuilder, Expression, Expression) + */ + public static Predicate eq( + CriteriaBuilder criteriaBuilder, Expression geometry1, + Geometry geometry2) { + return eq( criteriaBuilder, geometry1, + criteriaBuilder.literal( geometry2 ) + ); + } + + /** + * Create a predicate for testing the arguments for "spatially within" constraint. + * + * @param criteriaBuilder CriteriaBuilder + * @param geometry1 geometry expression + * @param geometry2 geometry expression + * + * @return "spatially within" predicate + * + * @see SpatialRestrictions#within(String, Geometry) + */ + public static Predicate within( + CriteriaBuilder criteriaBuilder, Expression geometry1, + Expression geometry2) { + return booleanExpressionToPredicate( + criteriaBuilder, + criteriaBuilder.function( SpatialFunction.within.toString(), boolean.class, + geometry1, geometry2 + ) + ); + } + + /** + * Create a predicate for testing the arguments for "spatially within" constraint. + * + * @param criteriaBuilder CriteriaBuilder + * @param geometry1 geometry expression + * @param geometry2 geometry value + * + * @return "spatially within" predicate + * + * @see #within(CriteriaBuilder, Expression, Expression) + */ + public static Predicate within( + CriteriaBuilder criteriaBuilder, Expression geometry1, + Geometry geometry2) { + return within( criteriaBuilder, geometry1, + criteriaBuilder.literal( geometry2 ) + ); + } + + /** + * Create a predicate for testing the arguments for "spatially contains" constraint. + * + * @param criteriaBuilder CriteriaBuilder + * @param geometry1 geometry expression + * @param geometry2 geometry expression + * + * @return "spatially contains" predicate + * + * @see SpatialRestrictions#contains(String, Geometry) + */ + public static Predicate contains( + CriteriaBuilder criteriaBuilder, Expression geometry1, + Expression geometry2) { + return booleanExpressionToPredicate( + criteriaBuilder, + criteriaBuilder.function( SpatialFunction.contains.toString(), boolean.class, + geometry1, geometry2 + ) + ); + } + + /** + * Create a predicate for testing the arguments for "spatially contains" constraint. + * + * @param criteriaBuilder CriteriaBuilder + * @param geometry1 geometry expression + * @param geometry2 geometry value + * + * @return "spatially contains" predicate + * + * @see #contains(CriteriaBuilder, Expression, Expression) + */ + public static Predicate contains( + CriteriaBuilder criteriaBuilder, Expression geometry1, + Geometry geometry2) { + return contains( criteriaBuilder, geometry1, + criteriaBuilder.literal( geometry2 ) + ); + } + + /** + * Create a predicate for testing the arguments for "spatially crosses" constraint. + * + * @param criteriaBuilder CriteriaBuilder + * @param geometry1 geometry expression + * @param geometry2 geometry expression + * + * @return "spatially crosses" predicate + * + * @see SpatialRestrictions#crosses(String, Geometry) + */ + public static Predicate crosses( + CriteriaBuilder criteriaBuilder, Expression geometry1, + Expression geometry2) { + return booleanExpressionToPredicate( + criteriaBuilder, + criteriaBuilder.function( SpatialFunction.crosses.toString(), boolean.class, + geometry1, geometry2 + ) + ); + } + + /** + * Create a predicate for testing the arguments for "spatially crosses" constraint. + * + * @param criteriaBuilder CriteriaBuilder + * @param geometry1 geometry expression + * @param geometry2 geometry value + * + * @return "spatially crosses" predicate + * + * @see #crosses(CriteriaBuilder, Expression, Expression) + */ + public static Predicate crosses( + CriteriaBuilder criteriaBuilder, Expression geometry1, + Geometry geometry2) { + return crosses( criteriaBuilder, geometry1, + criteriaBuilder.literal( geometry2 ) + ); + } + + /** + * Create a predicate for testing the arguments for "spatially disjoint" constraint. + * + * @param criteriaBuilder CriteriaBuilder + * @param geometry1 geometry expression + * @param geometry2 geometry expression + * + * @return "spatially disjoint" predicate + * + * @see SpatialRestrictions#disjoint(String, Geometry) + */ + public static Predicate disjoint( + CriteriaBuilder criteriaBuilder, Expression geometry1, + Expression geometry2) { + return booleanExpressionToPredicate( + criteriaBuilder, + criteriaBuilder.function( SpatialFunction.disjoint.toString(), boolean.class, + geometry1, geometry2 + ) + ); + } + + /** + * Create a predicate for testing the arguments for "spatially disjoint" constraint. + * + * @param criteriaBuilder CriteriaBuilder + * @param geometry1 geometry expression + * @param geometry2 geometry value + * + * @return "spatially disjoint" predicate + * + * @see #disjoint(CriteriaBuilder, Expression, Expression) + */ + public static Predicate disjoint( + CriteriaBuilder criteriaBuilder, Expression geometry1, + Geometry geometry2) { + return disjoint( criteriaBuilder, geometry1, + criteriaBuilder.literal( geometry2 ) + ); + } + + /** + * Create a predicate for testing the arguments for "spatially intersects" constraint. + * + * @param criteriaBuilder CriteriaBuilder + * @param geometry1 geometry expression + * @param geometry2 geometry expression + * + * @return "spatially intersects" predicate + * + * @see SpatialRestrictions#intersects(String, Geometry) + */ + public static Predicate intersects( + CriteriaBuilder criteriaBuilder, Expression geometry1, + Expression geometry2) { + return booleanExpressionToPredicate( + criteriaBuilder, + criteriaBuilder.function( SpatialFunction.intersects.toString(), boolean.class, + geometry1, geometry2 + ) + ); + } + + /** + * Create a predicate for testing the arguments for "spatially intersects" constraint. + * + * @param criteriaBuilder CriteriaBuilder + * @param geometry1 geometry expression + * @param geometry2 geometry value + * + * @return "spatially intersects" predicate + * + * @see #intersects(CriteriaBuilder, Expression, Expression) + */ + public static Predicate intersects( + CriteriaBuilder criteriaBuilder, Expression geometry1, + Geometry geometry2) { + return intersects( criteriaBuilder, geometry1, + criteriaBuilder.literal( geometry2 ) + ); + } + + /** + * Create a predicate for testing the arguments for "spatially overlaps" constraint. + * + * @param criteriaBuilder CriteriaBuilder + * @param geometry1 geometry expression + * @param geometry2 geometry expression + * + * @return "spatially overlaps" predicate + * + * @see SpatialRestrictions#overlaps(String, Geometry) + */ + public static Predicate overlaps( + CriteriaBuilder criteriaBuilder, Expression geometry1, + Expression geometry2) { + return booleanExpressionToPredicate( + criteriaBuilder, + criteriaBuilder.function( SpatialFunction.overlaps.toString(), boolean.class, + geometry1, geometry2 + ) + ); + } + + /** + * Create a predicate for testing the arguments for "spatially overlaps" constraint. + * + * @param criteriaBuilder CriteriaBuilder + * @param geometry1 geometry expression + * @param geometry2 geometry value + * + * @return "spatially overlaps" predicate + * + * @see #overlaps(CriteriaBuilder, Expression, Expression) + */ + public static Predicate overlaps( + CriteriaBuilder criteriaBuilder, Expression geometry1, + Geometry geometry2) { + return overlaps( criteriaBuilder, geometry1, + criteriaBuilder.literal( geometry2 ) + ); + } + + /** + * Create a predicate for testing the arguments for "spatially touches" constraint. + * + * @param criteriaBuilder CriteriaBuilder + * @param geometry1 geometry expression + * @param geometry2 geometry expression + * + * @return "spatially touches" predicate + * + * @see SpatialRestrictions#touches(String, Geometry) + */ + public static Predicate touches( + CriteriaBuilder criteriaBuilder, Expression geometry1, + Expression geometry2) { + return booleanExpressionToPredicate( + criteriaBuilder, + criteriaBuilder.function( SpatialFunction.touches.toString(), boolean.class, + geometry1, geometry2 + ) + ); + } + + /** + * Create a predicate for testing the arguments for "spatially touches" constraint. + * + * @param criteriaBuilder CriteriaBuilder + * @param geometry1 geometry expression + * @param geometry2 geometry value + * + * @return "spatially touches" predicate + * + * @see #touches(CriteriaBuilder, Expression, Expression) + */ + public static Predicate touches( + CriteriaBuilder criteriaBuilder, Expression geometry1, + Geometry geometry2) { + return touches( criteriaBuilder, geometry1, + criteriaBuilder.literal( geometry2 ) + ); + } + + /** + * Create a predicate for testing the arguments for bounding box overlap constraint. + * + * @param criteriaBuilder CriteriaBuilder + * @param geometry1 geometry expression + * @param geometry2 geometry expression whose bounding box to use in the comparison + * + * @return bounding box overlap predicate + * + * @see FilterPredicate + * @see SpatialRestrictions#filter(String, Geometry) + */ + public static Predicate filter( + CriteriaBuilder criteriaBuilder, Expression geometry1, + Expression geometry2) { + return new FilterPredicate( criteriaBuilder, geometry1, geometry2 ); + } + + /** + * Create a predicate for testing the arguments for bounding box overlap constraint. + * + * @param criteriaBuilder CriteriaBuilder + * @param geometry1 geometry expression + * @param geometry2 geometry value whose bounding box to use in the comparison + * + * @return bounding box overlap predicate + * + * @see FilterPredicate + * @see SpatialRestrictions#filter(String, Geometry) + */ + public static Predicate filter( + CriteriaBuilder criteriaBuilder, Expression geometry1, + Geometry geometry2) { + return new FilterPredicate( criteriaBuilder, geometry1, geometry2 ); + } + + /** + * Create a predicate for testing the arguments for bounding box overlap constraint. + * + * @param criteriaBuilder CriteriaBuilder + * @param geometry geometry expression + * @param envelope envelope or bounding box to use in the comparison + * @param srid the SRID of the bounding box + * + * @return bounding box overlap predicate + * + * @see FilterPredicate + * @see SpatialRestrictions#filter(String, Envelope, int) + */ + public static Predicate filterByPolygon( + CriteriaBuilder criteriaBuilder, Expression geometry, + Envelope envelope, int srid) { + return new FilterPredicate( criteriaBuilder, geometry, envelope, srid ); + } + + /** + * Create a predicate for testing the arguments for "distance within" constraint. + * + * @param criteriaBuilder CriteriaBuilder + * @param geometry1 geometry expression + * @param geometry2 geometry expression + * @param distance distance expression + * + * @return "distance within" predicate + * + * @see SpatialRestrictions#distanceWithin(String, Geometry, double) + */ + public static Predicate distanceWithin( + CriteriaBuilder criteriaBuilder, Expression geometry1, + Expression geometry2, Expression distance) { + return booleanExpressionToPredicate( + criteriaBuilder, + criteriaBuilder.function( SpatialFunction.dwithin.toString(), boolean.class, + geometry1, geometry2, distance + ) + ); + } + + /** + * Create a predicate for testing the arguments for "distance within" constraint. + * + * @param criteriaBuilder CriteriaBuilder + * @param geometry1 geometry expression + * @param geometry2 geometry value + * @param distance distance expression + * + * @return "distance within" predicate + * + * @see #distanceWithin(CriteriaBuilder, Expression, Expression, Expression) + */ + public static Predicate distanceWithin( + CriteriaBuilder criteriaBuilder, Expression geometry1, + Geometry geometry2, Expression distance) { + return distanceWithin( criteriaBuilder, geometry1, + criteriaBuilder.literal( geometry2 ), distance + ); + } + + /** + * Create a predicate for testing the arguments for "distance within" constraint. + * + * @param criteriaBuilder CriteriaBuilder + * @param geometry1 geometry expression + * @param geometry2 geometry value + * @param distance distance value + * + * @return "distance within" predicate + * + * @see #distanceWithin(CriteriaBuilder, Expression, Expression, Expression) + */ + public static Predicate distanceWithin( + CriteriaBuilder criteriaBuilder, Expression geometry1, + Geometry geometry2, double distance) { + return distanceWithin( criteriaBuilder, geometry1, + criteriaBuilder.literal( geometry2 ), criteriaBuilder.literal( distance ) + ); + } + + /** + * Create a predicate for testing the arguments for "distance within" constraint. + * + * @param criteriaBuilder CriteriaBuilder + * @param geometry1 geometry expression + * @param geometry2 geometry expression + * @param distance distance value + * + * @return "distance within" predicate + * + * @see #distanceWithin(CriteriaBuilder, Expression, Expression, Expression) + */ + public static Predicate distanceWithin( + CriteriaBuilder criteriaBuilder, Expression geometry1, + Expression geometry2, double distance) { + return distanceWithin( criteriaBuilder, geometry1, geometry2, + criteriaBuilder.literal( distance ) + ); + } + + /** + * Create a predicate for testing the arguments for "having srid" constraint. + * + * @param criteriaBuilder CriteriaBuilder + * @param geometry geometry expression + * @param srid SRID expression + * + * @return "having srid" predicate + * + * @see SpatialRestrictions#havingSRID(String, int) + */ + public static Predicate havingSRID( + CriteriaBuilder criteriaBuilder, Expression geometry, + Expression srid) { + return criteriaBuilder.equal( + criteriaBuilder.function( SpatialFunction.srid.toString(), int.class, geometry ), + srid + ); + } + + /** + * Create a predicate for testing the arguments for "having srid" constraint. + * + * @param criteriaBuilder CriteriaBuilder + * @param geometry geometry expression + * @param srid SRID expression + * + * @return "having srid" predicate + * + * @see #havingSRID(CriteriaBuilder, Expression, Expression) + */ + public static Predicate havingSRID( + CriteriaBuilder criteriaBuilder, Expression geometry, + int srid) { + return havingSRID( criteriaBuilder, geometry, + criteriaBuilder.literal( srid ) + ); + } + + /** + * Create a predicate for testing the arguments for "is empty" constraint. + * + * @param criteriaBuilder CriteriaBuilder + * @param geometry geometry expression + * + * @return "is empty" predicate + * + * @see SpatialRestrictions#isEmpty(String) + */ + public static Predicate isEmpty(CriteriaBuilder criteriaBuilder, Expression geometry) { + return booleanExpressionToPredicate( + criteriaBuilder, + criteriaBuilder.function( SpatialFunction.isempty.toString(), boolean.class, + geometry + ) + ); + } + + /** + * Create a predicate for testing the arguments for "is not empty" constraint. + * + * @param criteriaBuilder CriteriaBuilder + * @param geometry geometry expression + * + * @return "is not empty" predicate + * + * @see SpatialRestrictions#isNotEmpty(String) + */ + public static Predicate isNotEmpty(CriteriaBuilder criteriaBuilder, Expression geometry) { + return isEmpty( criteriaBuilder, geometry ) + .not(); + } + + private static Predicate booleanExpressionToPredicate( + CriteriaBuilder criteriaBuilder, + Expression expression) { + return criteriaBuilder.equal( expression, true ); + } +} diff --git a/hibernate-spatial/src/test/java/org/hibernate/spatial/integration/TestSpatialPredicates.java b/hibernate-spatial/src/test/java/org/hibernate/spatial/integration/TestSpatialPredicates.java new file mode 100644 index 0000000000..45351f69e0 --- /dev/null +++ b/hibernate-spatial/src/test/java/org/hibernate/spatial/integration/TestSpatialPredicates.java @@ -0,0 +1,264 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.spatial.integration; + +import java.sql.SQLException; +import java.util.List; +import java.util.Map; +import java.util.function.BiFunction; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Predicate; +import javax.persistence.criteria.Root; + +import org.hibernate.Session; +import org.hibernate.Transaction; +import org.hibernate.spatial.HSMessageLogger; +import org.hibernate.spatial.SpatialFunction; +import org.hibernate.spatial.dialect.hana.HANASpatialDialect; +import org.hibernate.spatial.integration.jts.JtsGeomEntity; +import org.hibernate.spatial.predicate.SpatialPredicates; +import org.hibernate.spatial.testing.SpatialDialectMatcher; +import org.hibernate.spatial.testing.SpatialFunctionalTestCase; + +import org.hibernate.testing.Skip; +import org.hibernate.testing.SkipForDialect; +import org.junit.Test; + +import org.jboss.logging.Logger; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +/** + * @see TestSpatialRestrictions + */ +@Skip(condition = SpatialDialectMatcher.class, message = "No Spatial Dialect") +@SkipForDialect(value = HANASpatialDialect.class, comment = "The HANA dialect is tested via org.hibernate.spatial.dialect.hana.TestHANASpatialFunctions", jiraKey = "HHH-12426") +public class TestSpatialPredicates extends SpatialFunctionalTestCase { + + private static HSMessageLogger LOG = Logger.getMessageLogger( + HSMessageLogger.class, + TestSpatialPredicates.class.getName() + ); + + protected HSMessageLogger getLogger() { + return LOG; + } + + @Test + public void within() throws SQLException { + if ( !isSupportedByDialect( SpatialFunction.within ) ) { + return; + } + Map dbexpected = expectationsFactory.getWithin( expectationsFactory.getTestPolygon() ); + BiFunction, Predicate> predicateFactory = (criteriaBuilder, root) -> + SpatialPredicates.within( criteriaBuilder, root.get( "geom" ), expectationsFactory.getTestPolygon() ); + retrieveAndCompare( dbexpected, predicateFactory ); + } + + @Test + public void filter() throws SQLException { + if ( !dialectSupportsFiltering() ) { + LOG.info( "Filtering is not supported by Dialect" ); + return; + } + Map dbexpected = expectationsFactory.getFilter( expectationsFactory.getTestPolygon() ); + BiFunction, Predicate> predicateFactory = (criteriaBuilder, root) -> + SpatialPredicates.filter( criteriaBuilder, root.get( "geom" ), expectationsFactory.getTestPolygon() ); + retrieveAndCompare( dbexpected, predicateFactory ); + } + + @Test + public void contains() throws SQLException { + if ( !isSupportedByDialect( SpatialFunction.contains ) ) { + return; + } + Map dbexpected = expectationsFactory.getContains( expectationsFactory.getTestPolygon() ); + BiFunction, Predicate> predicateFactory = (criteriaBuilder, root) -> + SpatialPredicates.contains( + criteriaBuilder, + root.get( "geom" ), + expectationsFactory.getTestPolygon() + ); + retrieveAndCompare( dbexpected, predicateFactory ); + } + + @Test + public void crosses() throws SQLException { + if ( !isSupportedByDialect( SpatialFunction.crosses ) ) { + return; + } + Map dbexpected = expectationsFactory.getCrosses( expectationsFactory.getTestPolygon() ); + BiFunction, Predicate> predicateFactory = (criteriaBuilder, root) -> + SpatialPredicates.crosses( criteriaBuilder, root.get( "geom" ), expectationsFactory.getTestPolygon() ); + retrieveAndCompare( dbexpected, predicateFactory ); + } + + @Test + public void touches() throws SQLException { + if ( !isSupportedByDialect( SpatialFunction.touches ) ) { + return; + } + Map dbexpected = expectationsFactory.getTouches( expectationsFactory.getTestPolygon() ); + BiFunction, Predicate> predicateFactory = (criteriaBuilder, root) -> + SpatialPredicates.touches( criteriaBuilder, root.get( "geom" ), expectationsFactory.getTestPolygon() ); + retrieveAndCompare( dbexpected, predicateFactory ); + } + + @Test + public void disjoint() throws SQLException { + if ( !isSupportedByDialect( SpatialFunction.disjoint ) ) { + return; + } + Map dbexpected = expectationsFactory.getDisjoint( expectationsFactory.getTestPolygon() ); + BiFunction, Predicate> predicateFactory = (criteriaBuilder, root) -> + SpatialPredicates.disjoint( + criteriaBuilder, + root.get( "geom" ), + expectationsFactory.getTestPolygon() + ); + retrieveAndCompare( dbexpected, predicateFactory ); + } + + @Test + public void eq() throws SQLException { + if ( !isSupportedByDialect( SpatialFunction.equals ) ) { + return; + } + Map dbexpected = expectationsFactory.getEquals( expectationsFactory.getTestPolygon() ); + BiFunction, Predicate> predicateFactory = (criteriaBuilder, root) -> + SpatialPredicates.eq( criteriaBuilder, root.get( "geom" ), expectationsFactory.getTestPolygon() ); + retrieveAndCompare( dbexpected, predicateFactory ); + } + + @Test + public void intersects() throws SQLException { + if ( !isSupportedByDialect( SpatialFunction.intersects ) ) { + return; + } + Map dbexpected = expectationsFactory.getIntersects( expectationsFactory.getTestPolygon() ); + BiFunction, Predicate> predicateFactory = (criteriaBuilder, root) -> + SpatialPredicates.intersects( + criteriaBuilder, + root.get( "geom" ), + expectationsFactory.getTestPolygon() + ); + retrieveAndCompare( dbexpected, predicateFactory ); + } + + @Test + public void overlaps() throws SQLException { + if ( !isSupportedByDialect( SpatialFunction.overlaps ) ) { + return; + } + Map dbexpected = expectationsFactory.getOverlaps( expectationsFactory.getTestPolygon() ); + BiFunction, Predicate> predicateFactory = (criteriaBuilder, root) -> + SpatialPredicates.overlaps( + criteriaBuilder, + root.get( "geom" ), + expectationsFactory.getTestPolygon() + ); + retrieveAndCompare( dbexpected, predicateFactory ); + } + + @Test + public void dwithin() throws SQLException { + if ( !isSupportedByDialect( SpatialFunction.dwithin ) ) { + return; + } + Map dbexpected = expectationsFactory.getDwithin( expectationsFactory.getTestPoint(), 30.0 ); + BiFunction, Predicate> predicateFactory = (criteriaBuilder, root) -> + SpatialPredicates.distanceWithin( + criteriaBuilder, + root.get( "geom" ), + expectationsFactory.getTestPoint(), + 30.0 + ); + retrieveAndCompare( dbexpected, predicateFactory ); + } + + @Test + public void isEmpty() throws SQLException { + if ( !isSupportedByDialect( SpatialFunction.isempty ) ) { + return; + } + Map dbexpected = expectationsFactory.getIsEmpty(); + BiFunction, Predicate> predicateFactory = (criteriaBuilder, root) -> + SpatialPredicates.isEmpty( criteriaBuilder, root.get( "geom" ) ); + retrieveAndCompare( dbexpected, predicateFactory ); + } + + @Test + public void isNotEmpty() throws SQLException { + if ( !isSupportedByDialect( SpatialFunction.isempty ) ) { + return; + } + Map dbexpected = expectationsFactory.getIsNotEmpty(); + BiFunction, Predicate> predicateFactory = (criteriaBuilder, root) -> + SpatialPredicates.isNotEmpty( criteriaBuilder, root.get( "geom" ) ); + retrieveAndCompare( dbexpected, predicateFactory ); + } + + @Test + public void havingSRID() throws SQLException { + if ( !isSupportedByDialect( SpatialFunction.srid ) ) { + return; + } + Map dbexpected = expectationsFactory.havingSRID( 4326 ); + BiFunction, Predicate> predicateFactory = (criteriaBuilder, root) -> + SpatialPredicates.havingSRID( criteriaBuilder, root.get( "geom" ), 4326 ); + retrieveAndCompare( dbexpected, predicateFactory ); + dbexpected = expectationsFactory.havingSRID( 31370 ); + predicateFactory = (criteriaBuilder, root) -> + SpatialPredicates.havingSRID( criteriaBuilder, root.get( "geom" ), 31370 ); + retrieveAndCompare( dbexpected, predicateFactory ); + } + + private void retrieveAndCompare( + Map dbexpected, + BiFunction, Predicate> predicateFactory) { + try (Session session = openSession()) { + Transaction tx = null; + try { + tx = session.beginTransaction(); + CriteriaBuilder criteriaBuilder = session.getCriteriaBuilder(); + CriteriaQuery criteriaQuery = criteriaBuilder.createQuery( JtsGeomEntity.class ); + Root root = criteriaQuery.from( JtsGeomEntity.class ); + criteriaQuery.select( root ) + .where( predicateFactory.apply( criteriaBuilder, root ) ); + List list = session.createQuery( criteriaQuery ) + .getResultList(); + compare( dbexpected, list ); + } + finally { + if ( tx != null ) { + tx.rollback(); + } + } + } + } + + private void compare(Map dbexpected, List list) { + int cnt = dbexpected.entrySet() + .stream() + .filter( Map.Entry::getValue ) + .reduce( 0, (accumulator, entry) -> { + if ( !findInList( entry.getKey(), list ) ) { + fail( String.format( "Expected object with id= %d, but not found in result", entry.getKey() ) ); + } + return accumulator + 1; + }, Integer::sum ); + assertEquals( cnt, list.size() ); + LOG.infof( "Found %d objects within testsuite-suite polygon.", cnt ); + } + + private boolean findInList(Integer id, List list) { + return list.stream() + .anyMatch( entity -> entity.getId().equals( id ) ); + } +} diff --git a/hibernate-spatial/src/test/java/org/hibernate/spatial/testing/TestSupportFactories.java b/hibernate-spatial/src/test/java/org/hibernate/spatial/testing/TestSupportFactories.java index 6dd4dda86e..d8fab275b2 100644 --- a/hibernate-spatial/src/test/java/org/hibernate/spatial/testing/TestSupportFactories.java +++ b/hibernate-spatial/src/test/java/org/hibernate/spatial/testing/TestSupportFactories.java @@ -38,7 +38,6 @@ public class TestSupportFactories { private static Class getSupportFactoryClass(Dialect dialect) { String canonicalName = dialect.getClass().getCanonicalName(); - if ( ( dialect instanceof SpatialDialect ) && PostgreSQL82Dialect.class.isAssignableFrom( dialect.getClass() ) ) { //this test works because all postgis dialects ultimately derive of the Postgresql82Dialect return PostgisTestSupport.class; diff --git a/hibernate-spatial/src/test/java/org/hibernate/spatial/testing/dialects/postgis/PostgisTestSupport.java b/hibernate-spatial/src/test/java/org/hibernate/spatial/testing/dialects/postgis/PostgisTestSupport.java index 22ccf23b94..4a07688c05 100644 --- a/hibernate-spatial/src/test/java/org/hibernate/spatial/testing/dialects/postgis/PostgisTestSupport.java +++ b/hibernate-spatial/src/test/java/org/hibernate/spatial/testing/dialects/postgis/PostgisTestSupport.java @@ -8,6 +8,9 @@ package org.hibernate.spatial.testing.dialects.postgis; +import org.hibernate.spatial.integration.TestSpatialFunctions; +import org.hibernate.spatial.integration.TestSpatialPredicates; +import org.hibernate.spatial.integration.TestSpatialRestrictions; import org.hibernate.spatial.testing.AbstractExpectationsFactory; import org.hibernate.spatial.testing.DataSourceUtils; import org.hibernate.spatial.testing.SQLExpressionTemplate; @@ -24,8 +27,10 @@ public class PostgisTestSupport extends TestSupport { public TestData createTestData(BaseCoreFunctionalTestCase testcase) { - if ( testcase.getClass().getCanonicalName().contains( "TestSpatialFunctions" ) || - testcase.getClass().getCanonicalName().contains( "TestSpatialRestrictions" ) ) { + Class testcaseClass = testcase.getClass(); + if ( testcaseClass == TestSpatialFunctions.class || + testcaseClass == TestSpatialRestrictions.class || + testcaseClass == TestSpatialPredicates.class ) { return TestData.fromFile( "postgis-functions-test.xml" ); } return TestData.fromFile( "test-data-set.xml" );