Skip to content

Commit

Permalink
Fix Hibernate Spatial test on SAP HANA
Browse files Browse the repository at this point in the history
  • Loading branch information
breglerj authored and sebersole committed Oct 13, 2017
1 parent 54b506e commit 8de0f9e
Show file tree
Hide file tree
Showing 14 changed files with 285 additions and 158 deletions.
Expand Up @@ -25,14 +25,18 @@ public class HANAGeometryTypeDescriptor implements SqlTypeDescriptor {

private static final long serialVersionUID = -6978798264716544804L;

/**
* An instance of the descrtiptor
*/
public static final HANAGeometryTypeDescriptor INSTANCE = new HANAGeometryTypeDescriptor();
public static final HANAGeometryTypeDescriptor CRS_LOADING_INSTANCE = new HANAGeometryTypeDescriptor( true );
public static final HANAGeometryTypeDescriptor INSTANCE = new HANAGeometryTypeDescriptor( false );

final boolean determineCrsIdFromDatabase;

public HANAGeometryTypeDescriptor(boolean determineCrsIdFromDatabase) {
this.determineCrsIdFromDatabase = determineCrsIdFromDatabase;
}

@Override
public int getSqlType() {
return Types.ARRAY;
return Types.OTHER;
}

@Override
Expand Down Expand Up @@ -67,7 +71,12 @@ public <X> ValueExtractor<X> getExtractor(final JavaTypeDescriptor<X> javaTypeDe

@Override
protected X doExtract(ResultSet rs, String name, WrapperOptions options) throws SQLException {
return getJavaDescriptor().wrap( HANASpatialUtils.toGeometry( rs.getObject( name ) ), options );
if ( HANAGeometryTypeDescriptor.this.determineCrsIdFromDatabase ) {
return getJavaDescriptor().wrap( HANASpatialUtils.toGeometry( rs, name ), options );
}
else {
return getJavaDescriptor().wrap( HANASpatialUtils.toGeometry( rs.getObject( name ) ), options );
}
}

@Override
Expand Down
Expand Up @@ -8,6 +8,8 @@

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.service.ServiceRegistry;
import org.hibernate.spatial.GeolatteGeometryType;
import org.hibernate.spatial.JTSGeometryType;
Expand All @@ -21,6 +23,8 @@ public class HANASpatialDialect extends HANAColumnStoreDialect implements Spatia

private static final long serialVersionUID = -432631517465714911L;

private static final String DETERMINE_CRS_ID_FROM_DATABASE_PARAMETER_NAME = "hibernate.spatial.dialect.hana.determine_crs_id_from_database";

public HANASpatialDialect() {
registerColumnType( HANAGeometryTypeDescriptor.INSTANCE.getSqlType(), "ST_GEOMETRY" );
registerColumnType( HANAPointTypeDescriptor.INSTANCE.getSqlType(), "ST_POINT" );
Expand Down Expand Up @@ -62,7 +66,7 @@ public HANASpatialDialect() {
registerFunction( SpatialFunction.overlaps.name(),
new HANASpatialFunction( "ST_Overlaps", StandardBasicTypes.NUMERIC_BOOLEAN, true ) );
registerFunction( SpatialFunction.relate.name(),
new HANASpatialFunction( "ST_Relate", StandardBasicTypes.INTEGER, true ) );
new HANASpatialFunction( "ST_Relate", StandardBasicTypes.NUMERIC_BOOLEAN, true ) );
registerFunction( SpatialFunction.srid.name(),
new HANASpatialFunction( "ST_SRID", StandardBasicTypes.INTEGER, false ) );
registerFunction( SpatialFunction.symdifference.name(), new HANASpatialFunction( "ST_SymDifference", true ) );
Expand Down Expand Up @@ -102,8 +106,29 @@ public String getSpatialRelateSQL(String columnName, int spatialRelation) {
@Override
public void contributeTypes(TypeContributions typeContributions, ServiceRegistry serviceRegistry) {
super.contributeTypes( typeContributions, serviceRegistry );
typeContributions.contributeType( new GeolatteGeometryType( HANAGeometryTypeDescriptor.INSTANCE ) );
typeContributions.contributeType( new JTSGeometryType( HANAGeometryTypeDescriptor.INSTANCE ) );

final ConfigurationService configurationService = serviceRegistry.getService( ConfigurationService.class );
boolean determineCrsIdFromDatabase = configurationService.getSetting(
DETERMINE_CRS_ID_FROM_DATABASE_PARAMETER_NAME,
new Converter<Boolean>() {

@Override
public Boolean convert(Object value) {
return Boolean.valueOf( value.toString() );
}

},
Boolean.FALSE ).booleanValue();

if ( determineCrsIdFromDatabase ) {
typeContributions.contributeType( new GeolatteGeometryType( HANAGeometryTypeDescriptor.CRS_LOADING_INSTANCE ) );
typeContributions.contributeType( new JTSGeometryType( HANAGeometryTypeDescriptor.CRS_LOADING_INSTANCE ) );
}
else {
typeContributions.contributeType( new GeolatteGeometryType( HANAGeometryTypeDescriptor.INSTANCE ) );
typeContributions.contributeType( new JTSGeometryType( HANAGeometryTypeDescriptor.INSTANCE ) );
}

}

@Override
Expand Down Expand Up @@ -203,5 +228,4 @@ public boolean supports(SpatialFunction function) {
}
return false;
}

}
Expand Up @@ -14,6 +14,8 @@

public class HANASpatialFunction extends StandardSQLFunction {

private static final String AS_EWKB_SUFFIX = ".ST_AsEWKB()";

private final boolean firstArgumentIsGeometryType;

public HANASpatialFunction(String name, boolean firstArgumentIsGeometryType) {
Expand All @@ -33,14 +35,22 @@ public String render(Type firstArgumentType, List arguments, SessionFactoryImple
}
else {
final StringBuilder buf = new StringBuilder();
buf.append( arguments.get( 0 ) ).append( "." ).append( getName() ).append( '(' );
// If the first argument is an expression, e.g. a nested function, strip the .ST_AsEWKB() suffix
buf.append( stripEWKBSuffix( arguments.get( 0 ) ) );

// Add function call
buf.append( "." ).append( getName() ).append( '(' );

// Add function arguments
for ( int i = 1; i < arguments.size(); i++ ) {
final Object argument = arguments.get( i );
final boolean parseFromWKB = this.firstArgumentIsGeometryType && i == 1 && "?".equals( argument );
// Check if first argument needs to be parsed from EWKB. This is the case if the first argument is a
// parameter that is set as EWKB or if it's a nested function call.
final boolean parseFromWKB = ( this.firstArgumentIsGeometryType && i == 1 && "?".equals( argument ) );
if ( parseFromWKB ) {
buf.append( "ST_GeomFromEWKB(" );
}
buf.append( argument );
buf.append( stripEWKBSuffix( argument ) );
if ( parseFromWKB ) {
buf.append( ")" );
}
Expand All @@ -49,7 +59,20 @@ public String render(Type firstArgumentType, List arguments, SessionFactoryImple
}
}
buf.append( ')' );
// If it doesn't specify an explicit type, assume it's a geometry
if ( this.getType() == null ) {
buf.append( AS_EWKB_SUFFIX );
}
return buf.toString();
}
}

private Object stripEWKBSuffix(Object argument) {
if ( ( argument instanceof String ) && ( (String) argument ).endsWith( AS_EWKB_SUFFIX ) ) {
String argumentString = (String) argument;
return argumentString.substring( 0, argumentString.length() - AS_EWKB_SUFFIX.length() );
}

return argument;
}
}
Expand Up @@ -7,6 +7,9 @@
package org.hibernate.spatial.dialect.hana;

import java.sql.Blob;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import org.geolatte.geom.ByteBuffer;
Expand All @@ -18,7 +21,95 @@

public class HANASpatialUtils {

private static final int POSTGIS_SRID_FLAG = 0x20000000;

@SuppressWarnings("resource")
public static Geometry<?> toGeometry(ResultSet rs, String name) throws SQLException {
ByteBuffer buffer = toByteBuffer( rs.getObject( name ) );

if ( buffer == null ) {
return null;
}

// Get table and column names from the result set metadata
String tableName = null;
String columnName = null;
for ( int i = 1; i <= rs.getMetaData().getColumnCount(); i++ ) {
if ( name.equals( rs.getMetaData().getColumnLabel( i ) ) || name.toUpperCase().equals( rs.getMetaData().getColumnLabel( i ) ) ) {
tableName = rs.getMetaData().getTableName( i );
columnName = rs.getMetaData().getColumnName( i );
}
}

assert tableName != null;
assert columnName != null;

// no table and/or column names found (
if ( tableName.isEmpty() || columnName.isEmpty() ) {
return toGeometry( buffer );
}

byte orderByte = buffer.get();
int typeCode = (int) buffer.getUInt();

Connection connection = rs.getStatement().getConnection();

// Check if SRID is set
if ( ( typeCode & POSTGIS_SRID_FLAG ) != POSTGIS_SRID_FLAG ) {
// No SRID set => try to get SRID from the database
try ( PreparedStatement psSrid = connection
.prepareStatement( "SELECT SRS_ID FROM SYS.ST_GEOMETRY_COLUMNS WHERE SCHEMA_NAME=CURRENT_SCHEMA AND TABLE_NAME=? AND COLUMN_NAME=?" ) ) {
psSrid.setString( 1, tableName );
psSrid.setString( 2, columnName );

try ( ResultSet rsSrid = psSrid.executeQuery() ) {
if ( rsSrid.next() ) {
int crsId = rsSrid.getInt( 1 );
buffer = addCrsId( buffer.toByteArray(), orderByte, typeCode, crsId );
}
else {
// ignore
}
}
}
}

return toGeometry( buffer );
}

private static ByteBuffer addCrsId(byte[] wkb, byte orderByte, int typeCode, int crsId) {
ByteBuffer buffer = ByteBuffer.allocate( wkb.length + 4 ); // original capacity + 4 bytes for the CRS ID
buffer.setByteOrder( ByteOrder.valueOf( orderByte ) );

buffer.put( orderByte ); // write byte order

buffer.putUInt( typeCode | POSTGIS_SRID_FLAG ); // set SRID flag

buffer.putInt( crsId ); // write CRS ID

// write remaining data

for ( int i = 5; i < wkb.length; i++ ) {
buffer.put( wkb[i] );
}

buffer.rewind();
return buffer;
}

public static Geometry<?> toGeometry(Object obj) {
return toGeometry( toByteBuffer( obj ) );
}

private static Geometry<?> toGeometry(ByteBuffer buffer) {
if ( buffer == null ) {
return null;
}
WkbDecoder decoder = Wkb.newDecoder( Wkb.Dialect.HANA_EWKB );
return decoder.decode( buffer );
}

private static ByteBuffer toByteBuffer(Object obj) {
byte[] raw = null;
if ( obj == null ) {
return null;
Expand All @@ -34,9 +125,8 @@ else if ( obj instanceof Blob ) {
}

ByteBuffer buffer = ByteBuffer.from( raw );

WkbDecoder decoder = Wkb.newDecoder( Wkb.Dialect.HANA_EWKB );
return decoder.decode( buffer );
buffer.setByteOrder( ByteOrder.valueOf( raw[0] ) );
return buffer;
}

private static byte[] toByteArray(Blob blob) {
Expand Down

0 comments on commit 8de0f9e

Please sign in to comment.