HHH-14801 Register remaining spatial functions for Postgis

Add more spatial functions
This commit is contained in:
Karel Maesen 2021-08-07 15:36:23 +02:00
parent 24c5099eb7
commit ba47586634
11 changed files with 280 additions and 56 deletions

View File

@ -23,9 +23,60 @@ public enum CommonSpatialFunction {
ST_DIMENSION( FunctionKey.apply( "st_dimension", "dimension" ), 1, StandardBasicTypes.INTEGER ),
ST_SRID( FunctionKey.apply( "st_srid", "srid" ), 1, StandardBasicTypes.INTEGER ),
ST_ENVELOPE( FunctionKey.apply( "st_envelope", "envelope" ), 1 ),
;
ST_ASBINARY( FunctionKey.apply( "st_asbinary", "asbinary" ), 1, StandardBasicTypes.BINARY ),
ST_ISEMPTY( FunctionKey.apply( "st_isempty", "isempty" ), 1, StandardBasicTypes.BOOLEAN ),
ST_ISSIMPLE( FunctionKey.apply( "st_issimple", "issimple" ), 1, StandardBasicTypes.BOOLEAN ),
ST_BOUNDARY( FunctionKey.apply( "st_boundary", "boundary" ), 1 ),
ST_OVERLAPS( FunctionKey.apply( "st_overlaps", "overlaps" ), 2, StandardBasicTypes.BOOLEAN ),
ST_INTERSECTS( FunctionKey.apply( "st_intersects", "intersects" ), 2, StandardBasicTypes.BOOLEAN ),
ST_EQUALS( FunctionKey.apply( "st_equals", "equals" ), 2, StandardBasicTypes.BOOLEAN ),
ST_CONTAINS( FunctionKey.apply( "st_contains", "contains" ), 2, StandardBasicTypes.BOOLEAN ),
ST_CROSSES( FunctionKey.apply( "st_crosses", "crosses" ), 2, StandardBasicTypes.BOOLEAN ),
ST_DISJOINT( FunctionKey.apply( "st_disjoint", "disjoint" ), 2, StandardBasicTypes.BOOLEAN ),
ST_TOUCHES( FunctionKey.apply( "st_touches", "touches" ), 2, StandardBasicTypes.BOOLEAN ),
ST_WITHIN( FunctionKey.apply( "st_within", "within" ), 2, StandardBasicTypes.BOOLEAN ),
ST_RELATE( FunctionKey.apply( "st_relate", "relate" ), 2, StandardBasicTypes.STRING ),
ST_DISTANCE( FunctionKey.apply( "st_distance", "distance" ), 2, StandardBasicTypes.DOUBLE ),
ST_BUFFER( FunctionKey.apply( "st_buffer", "buffer" ), 2 ),
ST_CONVEXHULL( FunctionKey.apply( "st_convexhull", "convexhull" ), 1 ),
ST_DIFFERENCE( FunctionKey.apply( "st_difference", "difference" ), 2 ),
ST_INTERSECTION( FunctionKey.apply( "st_intersection", "intersection" ), 2 ),
ST_SYMDIFFERENCE( FunctionKey.apply( "st_symdifference", "symdifference" ), 2 ),
ST_UNION( FunctionKey.apply( "st_union", "geomunion" ), 2 );
public static enum Type {
/**
* Geometry -> String or Byte[]
*/
GEOMETRY_OUTPUT,
/**
* String or Byte Array -> Geometry
*/
GEOMETRY_INPUT,
/**
* Geometry [, OBJECT]* -> Geometry
*/
CONSTRUCTION,
/**
* Geometry, Geometry, [Geometry]* -> Geometry
*/
OVERLAY,
/**
* Geometry, Geometry -> Boolean (or String for st_relate)
*/
ANALYSIS,
/**
* Geometry -> Boolean
*/
VALIDATION,
/**
* Geometry[, Object]* -> Scalar type
*/
INFORMATION,
}
private final FunctionKey key;
private final BasicType<?> ReturnType;
private final boolean spatialReturnType;
@ -61,4 +112,42 @@ public enum CommonSpatialFunction {
public int getNumArgs() {
return numArgs;
}
public Type getType() {
switch ( this ) {
case ST_SRID:
case ST_DIMENSION:
case ST_GEOMETRYTYPE:
case ST_DISTANCE:
return Type.INFORMATION;
case ST_ASBINARY:
case ST_ASTEXT:
return Type.GEOMETRY_OUTPUT;
case ST_ENVELOPE:
case ST_BOUNDARY:
case ST_BUFFER:
case ST_CONVEXHULL:
return Type.CONSTRUCTION;
case ST_DIFFERENCE:
case ST_INTERSECTION:
case ST_SYMDIFFERENCE:
case ST_UNION:
return Type.OVERLAY;
case ST_CONTAINS:
case ST_CROSSES:
case ST_DISJOINT:
case ST_INTERSECTS:
case ST_EQUALS:
case ST_TOUCHES:
case ST_WITHIN:
case ST_OVERLAPS:
case ST_RELATE:
return Type.ANALYSIS;
case ST_ISEMPTY:
case ST_ISSIMPLE:
return Type.VALIDATION;
default:
throw new IllegalStateException( "The function " + this.getKey().getName() + " is not categorized" );
}
}
}

View File

@ -9,7 +9,9 @@ package org.hibernate.spatial.integration;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.hibernate.spatial.CommonSpatialFunction;
import org.hibernate.spatial.GeomCodec;
import org.hibernate.spatial.testing.JTSGeometryEquality;
import org.hibernate.spatial.testing.dialects.NativeSQLTemplates;
@ -21,10 +23,15 @@ import org.hibernate.spatial.testing.domain.GeomEntityLike;
import org.hibernate.testing.orm.junit.DialectContext;
import static org.hibernate.spatial.testing.datareader.TestSupport.TestDataPurpose.SpatialFunctionsData;
import static org.hibernate.spatial.testing.datareader.TestSupport.TestDataPurpose.StoreRetrieveData;
@Deprecated
public class SpatialTestDataProvider {
protected final static String JTS = "jts";
protected final NativeSQLTemplates templates;
protected final NativeSQLTemplates templates;
protected final Map<CommonSpatialFunction, String> hqlOverrides;
private final TestData funcTestData;
protected TestData testData;
protected GeomCodec codec;
protected JTSGeometryEquality geometryEquality;
@ -33,8 +40,10 @@ public class SpatialTestDataProvider {
try {
TestSupport support = TestSupportFactories.instance().getTestSupportFactory( DialectContext.getDialect() );
templates = support.templates();
hqlOverrides = support.hqlOverrides();
codec = support.codec();
testData = support.createTestData( TestSupport.TestDataPurpose.StoreRetrieveData );
testData = support.createTestData( StoreRetrieveData );
funcTestData = support.createTestData( SpatialFunctionsData );
geometryEquality = support.createGeometryEquality();
}
catch (InstantiationException | IllegalAccessException e) {
@ -43,9 +52,13 @@ public class SpatialTestDataProvider {
}
protected <T extends GeomEntityLike<?>> List<T> entities(Class<T> clazz) {
return entities( clazz, StoreRetrieveData );
}
protected <T extends GeomEntityLike<?>> List<T> entities(Class<T> clazz, TestSupport.TestDataPurpose purpose) {
try {
List<T> entities = new ArrayList<>();
for ( TestDataElement testDataElement : testData ) {
for ( TestDataElement testDataElement : purpose == StoreRetrieveData ? testData : funcTestData ) {
T entity = clazz.getDeclaredConstructor().newInstance();
entity.setGeomFromWkt( testDataElement.wkt );
entity.setId( testDataElement.id );

View File

@ -19,6 +19,7 @@ import java.util.Locale;
import java.util.stream.Stream;
import org.hibernate.spatial.integration.SpatialTestDataProvider;
import org.hibernate.spatial.testing.datareader.TestSupport;
import org.hibernate.spatial.testing.domain.GeomEntity;
import org.hibernate.spatial.testing.domain.JtsGeomEntity;
import org.hibernate.spatial.testing.domain.SpatialDomainModel;
@ -52,8 +53,15 @@ public class CommonFunctionTests extends SpatialTestDataProvider
@BeforeEach
public void beforeEach() {
scope.inTransaction( session -> super.entities( JtsGeomEntity.class ).forEach( session::save ) );
scope.inTransaction( session -> super.entities( GeomEntity.class ).forEach( session::save ) );
scope.inTransaction( session -> super.entities(
JtsGeomEntity.class,
TestSupport.TestDataPurpose.SpatialFunctionsData
)
.forEach( session::save ) );
scope.inTransaction( session -> super.entities(
GeomEntity.class,
TestSupport.TestDataPurpose.SpatialFunctionsData
).forEach( session::save ) );
}
@AfterEach
@ -65,7 +73,7 @@ public class CommonFunctionTests extends SpatialTestDataProvider
@TestFactory
public Stream<DynamicTest> testFunction() {
return
TestTemplates.all( templates)
TestTemplates.all( templates, hqlOverrides )
// TODO -- filter for supported functions
.flatMap( t -> Stream.of(
t.build( Model.JTSMODEL, codec ),
@ -98,9 +106,9 @@ public class CommonFunctionTests extends SpatialTestDataProvider
protected <T> Executable executableTest(FunctionTestTemplate template, String fnName) {
Executable testF = () -> {
expected = template.executeNativeQuery( scope );
received = template.executeHQL( scope, fnName);
received = template.executeHQL( scope, fnName );
assertEquals( expected, received );
};
};
return testF;
}
}

View File

@ -14,7 +14,6 @@
package org.hibernate.spatial.integration.functions;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
@ -23,14 +22,18 @@ import java.util.stream.Stream;
import org.hibernate.Session;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.query.NativeQuery;
import org.hibernate.query.Query;
import org.hibernate.spatial.CommonSpatialFunction;
import org.hibernate.spatial.GeomCodec;
import org.hibernate.spatial.testing.HQLTemplate;
import org.hibernate.spatial.testing.NativeSQLTemplate;
import org.hibernate.type.Type;
import org.hibernate.type.StandardBasicTypes;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.geolatte.geom.Geometry;
import org.geolatte.geom.codec.Wkt;
@SuppressWarnings({ "rawtypes", "unchecked" })
public class FunctionTestTemplate {
@ -39,23 +42,24 @@ public class FunctionTestTemplate {
final private NativeSQLTemplate sqlTemplate;
final private RowObjectMapper rowObjectMapper;
final private Model model;
final private List<Param> parameters;
final private Geometry<?> testGeometry;
final private GeomCodec codec;
FunctionTestTemplate(
CommonSpatialFunction function,
HQLTemplate hqlTemplate,
NativeSQLTemplate sqlTemplate,
RowObjectMapper rowObjectMapper,
Model model,
List<Param> params,
Geometry<?> testGeometry,
GeomCodec codec) {
this.spatialFunction = function;
this.hqlTemplate = hqlTemplate;
this.sqlTemplate = sqlTemplate;
this.rowObjectMapper = rowObjectMapper;
this.model = model;
this.parameters = params;
this.testGeometry = testGeometry;
this.codec = codec;
}
@ -81,8 +85,17 @@ public class FunctionTestTemplate {
return map( results.get() );
}
private NativeQuery<Object> createNativeQuery(Session session, String table) {
return session.createNativeQuery( sqlTemplate.mkNativeSQLString( table ) );
private NativeQuery createNativeQuery(Session session, String table) {
NativeQuery query = session.createNativeQuery( sqlTemplate.mkNativeSQLString( table ) );
if ( spatialFunction.getReturnType() != null ) {
query.addScalar( "id", StandardBasicTypes.INTEGER );
query.addScalar( "result", spatialFunction.getReturnType() );
}
if ( testGeometry != null ) {
query.setParameter( "filter", Wkt.toWkt( testGeometry ) );
}
return query;
}
private List<Object> map(List<Object> list) {
@ -105,43 +118,53 @@ public class FunctionTestTemplate {
final AtomicReference<List> results = new AtomicReference<>();
final String entity = model.entityClass.getCanonicalName();
scope.inSession(
session -> results.set( session.createQuery(
hqlTemplate.mkHQLString( functionName, entity ) ).getResultList() ) );
session -> {
Query query = session.createQuery( hqlTemplate.mkHQLString( functionName, entity ) );
if ( testGeometry != null ) {
query.setParameter(
"filter",
getModel().from.apply( testGeometry )
);
}
results.set( query.getResultList() );
} );
return (List) results.get().stream().map( rowObjectMapper::apply ).collect( Collectors.toList() );
}
static class Param {
final Object value;
final Type type;
public Param(Object value, Type type) {
this.value = value;
this.type = type;
}
}
static class Builder {
CommonSpatialFunction key;
HQLTemplate hql = new HQLTemplate( "select id, %s(geom) from %s" );
CommonSpatialFunction function;
HQLTemplate hql;
NativeSQLTemplate sql;
RowObjectMapper mapper;
List<Param> params = new ArrayList<>();
Geometry<?> testGeometry;
public Builder(CommonSpatialFunction function) {
this.function = function;
}
FunctionTestTemplate build(Model model, GeomCodec codec) {
if ( hql == null ) {
if ( testGeometry != null ) {
hql = new HQLTemplate( "select id, %s(geom, :filter) from %s" );
}
else if ( function == CommonSpatialFunction.ST_BUFFER ) {
hql = new HQLTemplate( "select id, %s(geom, 2) from %s" );
}
else {
hql = new HQLTemplate( "select id, %s(geom) from %s" );
}
}
if ( this.mapper == null ) {
this.mapper = new RowObjectMapper() {
};
}
return new FunctionTestTemplate( key, hql, sql, mapper, model, params, codec );
}
Builder key(CommonSpatialFunction key) {
this.key = key;
return this;
return new FunctionTestTemplate( function, hql, sql, mapper, model, testGeometry, codec );
}
Builder hql(String hqlString) {
this.hql = new HQLTemplate( hqlString );
if ( hqlString != null ) {
this.hql = new HQLTemplate( hqlString );
}
return this;
}
@ -150,8 +173,10 @@ public class FunctionTestTemplate {
return this;
}
Builder parameter(Object value, Type type) {
this.params.add( new Param( value, type ) );
Builder geometry(Geometry<?> value) {
if ( value != null ) {
this.testGeometry = value;
}
return this;
}
}

View File

@ -9,8 +9,12 @@ package org.hibernate.spatial.integration.functions;
import java.util.function.Function;
import org.hibernate.spatial.GeolatteGeometryType;
import org.hibernate.spatial.JTSGeometryType;
import org.hibernate.spatial.dialect.postgis.PGGeometryTypeDescriptor;
import org.hibernate.spatial.testing.domain.GeomEntity;
import org.hibernate.spatial.testing.domain.JtsGeomEntity;
import org.hibernate.type.Type;
import org.geolatte.geom.Geometry;
import org.geolatte.geom.jts.JTS;
@ -34,6 +38,7 @@ enum Model {
final Function<Object, Geometry> to;
final Function<Geometry, Object> from;
Model(
Class<?> entityClass,
Function<Object, Geometry> to,

View File

@ -14,8 +14,8 @@
package org.hibernate.spatial.integration.functions;
import java.util.Arrays;
import java.util.Objects;
import java.util.function.Function;
public interface RowObjectMapper<T> {
default Data apply(Object obj) {
@ -42,7 +42,19 @@ class Data {
return false;
}
Data data = (Data) o;
return Objects.equals( id, data.id ) && Objects.equals( datum, data.datum );
return Objects.equals( id, data.id ) && isEquals( datum, data.datum );
}
private boolean isEquals(Object thisDatum, Object thatDatum) {
if ( thisDatum instanceof byte[] ) {
if ( !( thatDatum instanceof byte[] ) ) {
return false;
}
return Arrays.equals( (byte[]) thisDatum, (byte[]) thatDatum );
}
return Objects.equals( thisDatum, thatDatum );
}
@Override

View File

@ -20,21 +20,45 @@ import java.util.stream.Stream;
import org.hibernate.spatial.CommonSpatialFunction;
import org.hibernate.spatial.testing.dialects.NativeSQLTemplates;
import org.geolatte.geom.G2D;
import org.geolatte.geom.Polygon;
import static org.geolatte.geom.builder.DSL.g;
import static org.geolatte.geom.builder.DSL.polygon;
import static org.geolatte.geom.builder.DSL.ring;
import static org.geolatte.geom.crs.CoordinateReferenceSystems.WGS84;
public abstract class TestTemplates {
static FunctionTestTemplate.Builder builder() {
return new FunctionTestTemplate.Builder();
static FunctionTestTemplate.Builder builder(CommonSpatialFunction function) {
return new FunctionTestTemplate.Builder( function );
}
public static Stream<FunctionTestTemplate.Builder> all(NativeSQLTemplates sqlTemplates) {
static final Polygon<G2D> filter = polygon(
WGS84,
ring( g( 0, 0 ), g( 0, 10 ), g( 10, 10 ), g( 10, 0 ), g( 0, 0 ) )
);
public static Stream<FunctionTestTemplate.Builder> all(
NativeSQLTemplates sqlTemplates,
Map<CommonSpatialFunction, String> hqlOverrides) {
Map<CommonSpatialFunction, String> templates = sqlTemplates.all();
return templates
.keySet()
.stream()
.map( key -> builder()
.key( key )
.sql( templates.get( key ) ) );
.map( function -> builder( function )
.hql( hqlOverrides.get( function ) )
.sql( templates.get( function ) )
.geometry( setFilter( function ) ? filter : null ) );
}
private static boolean setFilter(CommonSpatialFunction function) {
return function.getType() == CommonSpatialFunction.Type.ANALYSIS ||
function.getType() == CommonSpatialFunction.Type.OVERLAY ||
function == CommonSpatialFunction.ST_DISTANCE;
}
}

View File

@ -14,9 +14,13 @@
package org.hibernate.spatial.testing.datareader;
import java.util.HashMap;
import java.util.Map;
import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.engine.config.spi.ConfigurationService;
import org.hibernate.spatial.CommonSpatialFunction;
import org.hibernate.spatial.GeomCodec;
import org.hibernate.spatial.testing.AbstractExpectationsFactory;
import org.hibernate.spatial.testing.DataSourceUtils;
@ -38,6 +42,10 @@ public abstract class TestSupport {
return null;
};
public Map<CommonSpatialFunction, String> hqlOverrides() {
return new HashMap<>();
}
public enum TestDataPurpose {
SpatialFunctionsData,
StoreRetrieveData

View File

@ -24,16 +24,37 @@ import static org.hibernate.spatial.CommonSpatialFunction.*;
public class NativeSQLTemplates {
private final Map<CommonSpatialFunction, String> sqls = new HashMap<>();
protected final Map<CommonSpatialFunction, String> sqls = new HashMap<>();
// Note that we alias the function invocation so that
// we can map the return value to the required type
public NativeSQLTemplates() {
sqls.put( ST_ASTEXT, "select id, st_astext(geom) from %s" );
sqls.put( ST_GEOMETRYTYPE, "select id, st_geometrytype(geom) from %s" );
sqls.put( ST_DIMENSION, "select id, st_dimension(geom) from %s" );
sqls.put( ST_ENVELOPE, "select id, st_envelope(geom) from %s" );
sqls.put( ST_SRID, "select id, st_srid(geom) from %s" );
sqls.put( ST_ASTEXT, "select id, st_astext(geom) as result from %s" );
sqls.put( ST_GEOMETRYTYPE, "select id, st_geometrytype(geom) as result from %s" );
sqls.put( ST_DIMENSION, "select id, st_dimension(geom) as result from %s" );
sqls.put( ST_ENVELOPE, "select id, st_envelope(geom) as result from %s" );
sqls.put( ST_SRID, "select id, st_srid(geom) as result from %s" );
sqls.put( ST_ASBINARY, "select id, st_asbinary(geom) as result from %s" );
sqls.put( ST_ISEMPTY, "select id, st_isempty(geom) as result from %s" );
sqls.put( ST_ISSIMPLE, "select id, st_issimple(geom) as result from %s" );
sqls.put( ST_BOUNDARY, "select id, st_boundary(geom) as result from %s" );
sqls.put( ST_OVERLAPS, "select id, st_overlaps(geom, st_geomfromtext(:filter, 4326)) as result from %s" );
sqls.put( ST_INTERSECTS, "select id, st_intersects(geom, st_geomfromtext(:filter, 4326)) as result from %s" );
sqls.put( ST_CROSSES, "select id, st_crosses(geom, st_geomfromtext(:filter, 4326)) as result from %s" );
sqls.put( ST_CONTAINS, "select id, st_contains(geom, st_geomfromtext(:filter, 4326)) as result from %s" );
sqls.put( ST_DISJOINT, "select id, st_disjoint(geom, st_geomfromtext(:filter, 4326)) as result from %s" );
sqls.put( ST_RELATE, "select id, st_relate(geom, st_geomfromtext(:filter, 4326)) as result from %s" );
sqls.put( ST_TOUCHES, "select id, st_touches(geom, st_geomfromtext(:filter, 4326)) as result from %s" );
sqls.put( ST_WITHIN, "select id, st_within(geom, st_geomfromtext(:filter, 4326)) as result from %s" );
sqls.put( ST_EQUALS, "select id, st_equals(geom, st_geomfromtext(:filter, 4326)) as result from %s" );
sqls.put( ST_DISTANCE, "select id, st_distance(geom, st_geomfromtext(:filter, 4326)) as result from %s" );
sqls.put( ST_BUFFER, "select id, st_buffer(geom, 2) as result from %s" );
sqls.put( ST_CONVEXHULL, "select id, st_convexhull(geom) as result from %s" );
sqls.put( ST_DIFFERENCE, "select id, st_difference(geom, st_geomfromtext(:filter, 4326)) as result from %s" );
sqls.put( ST_INTERSECTION, "select id, st_intersection(geom, st_geomfromtext(:filter, 4326)) as result from %s" );
sqls.put( ST_SYMDIFFERENCE, "select id, st_symdifference(geom, st_geomfromtext(:filter, 4326)) as result from %s" );
sqls.put( ST_UNION, "select id, st_union(geom, st_geomfromtext(:filter, 4326)) as result from %s" );
}
public Map<CommonSpatialFunction, String> all() {

View File

@ -7,6 +7,13 @@
package org.hibernate.spatial.testing.dialects.postgis;
import org.hibernate.spatial.CommonSpatialFunction;
import org.hibernate.spatial.testing.dialects.NativeSQLTemplates;
public class PostgisNativeSQLTemplates extends NativeSQLTemplates{}
import static org.hibernate.spatial.CommonSpatialFunction.ST_ISSIMPLE;
public class PostgisNativeSQLTemplates extends NativeSQLTemplates {
public PostgisNativeSQLTemplates() {
super();
}
}

View File

@ -8,14 +8,18 @@
package org.hibernate.spatial.testing.dialects.postgis;
import java.util.HashMap;
import java.util.Map;
import org.hibernate.spatial.CommonSpatialFunction;
import org.hibernate.spatial.GeomCodec;
import org.hibernate.spatial.dialect.postgis.PGGeometryTypeDescriptor;
import org.hibernate.spatial.testing.AbstractExpectationsFactory;
import org.hibernate.spatial.testing.DataSourceUtils;
import org.hibernate.spatial.testing.dialects.NativeSQLTemplates;
import org.hibernate.spatial.testing.SQLExpressionTemplate;
import org.hibernate.spatial.testing.datareader.TestData;
import org.hibernate.spatial.testing.datareader.TestSupport;
import org.hibernate.spatial.testing.dialects.NativeSQLTemplates;
import org.geolatte.geom.Geometry;
import org.geolatte.geom.codec.Wkt;
@ -24,6 +28,7 @@ import org.geolatte.geom.codec.Wkt;
* @author Karel Maesen, Geovise BVBA
* creation-date: Sep 30, 2010
*/
@Deprecated
public class PostgisTestSupport extends TestSupport {
@ -32,6 +37,13 @@ public class PostgisTestSupport extends TestSupport {
return new PostgisNativeSQLTemplates();
}
//TODO put this in its own class (analogous to NativeSQLTemplates)
@Override
public Map<CommonSpatialFunction, String> hqlOverrides() {
Map<CommonSpatialFunction, String> overrides = new HashMap<>();
return overrides;
}
@Override
public TestData createTestData(TestDataPurpose purpose) {
switch ( purpose ) {