This is an automated email from the ASF dual-hosted git repository. amanin pushed a commit to branch refactor/sql-store in repository https://gitbox.apache.org/repos/asf/sis.git
commit 7d8921b21818286f193cafba23a28ba11a560467 Author: Alexis Manin <[email protected]> AuthorDate: Wed Nov 13 16:30:17 2019 +0100 refactor(SQLStore): try to generify SQL geometry management. --- storage/sis-sqlstore/pom.xml | 2 +- .../sis/internal/sql/feature/ANSIMapping.java | 4 +- .../sis/internal/sql/feature/OGC06104r4.java | 159 +++++++++++++++++++++ .../sis/internal/sql/feature/PostGISMapping.java | 85 +---------- .../sis/internal/sql/feature/SpatialFunctions.java | 21 ++- 5 files changed, 189 insertions(+), 82 deletions(-) diff --git a/storage/sis-sqlstore/pom.xml b/storage/sis-sqlstore/pom.xml index 22bb00f..a167041 100644 --- a/storage/sis-sqlstore/pom.xml +++ b/storage/sis-sqlstore/pom.xml @@ -155,7 +155,7 @@ <dependency> <groupId>org.locationtech.jts</groupId> <artifactId>jts-core</artifactId> - <scope>compile</scope> + <scope>test</scope> </dependency> <dependency> diff --git a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/ANSIMapping.java b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/ANSIMapping.java index b98f53f..b600f0c 100644 --- a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/ANSIMapping.java +++ b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/ANSIMapping.java @@ -68,7 +68,9 @@ class ANSIMapping implements DialectMapping { case Types.VARBINARY: case Types.LONGVARBINARY: return new ColumnAdapter.Simple<>(byte[].class, ResultSet::getBytes); case Types.ARRAY: return forceCast(Object[].class); - case Types.OTHER: // Database-specific accessed via getObject and setObject. + case Types.OTHER: + + // Database-specific accessed via getObject and setObject. case Types.JAVA_OBJECT: return new ColumnAdapter.Simple<>(Object.class, ResultSet::getObject); default: return null; } diff --git a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/OGC06104r4.java b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/OGC06104r4.java new file mode 100644 index 0000000..480de0c --- /dev/null +++ b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/OGC06104r4.java @@ -0,0 +1,159 @@ +package org.apache.sis.internal.sql.feature; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.text.ParseException; +import java.util.Optional; + +import org.opengis.referencing.crs.CoordinateReferenceSystem; + +import org.apache.sis.internal.feature.Geometries; +import org.apache.sis.internal.metadata.sql.Dialect; +import org.apache.sis.util.collection.BackingStoreException; +import org.apache.sis.util.collection.Cache; + +/** + * Geometric SQL mapping based on <a href="https://www.opengeospatial.org/standards/sfs">OpenGIS® Implementation Standard for Geographic information -Simple feature access -Part 2: SQL option</a> + * + * @implNote WARNING: This class will (almost certainly) not work as is. It provides a base implementation for geometry + * access on any SQL simple feature compliant database, but the standard does not specify precisely what mode of + * representation is the default (WKB or WKT). The aim is to base specific drivers on this class (see {@link PostGISMapping} + * for an example). + */ +final class OGC06104r4 implements DialectMapping { + + final OGC06104r4.Spi spi; + final GeometryIdentification identifyGeometries; + + final Geometries library; + + /** + * A cache valid ONLY FOR A DATASOURCE. IT'S IMPORTANT ! Why ? Because : + * <ul> + * <li>CRS definition could differ between databases (PostGIS version, user alterations, etc.)</li> + * <li>Avoid inter-database locking</li> + * </ul> + */ + final Cache<Integer, CoordinateReferenceSystem> sessionCache; + + private OGC06104r4(final OGC06104r4.Spi spi, Connection c) throws SQLException { + this.spi = spi; + sessionCache = new Cache<>(7, 0, true); + this.identifyGeometries = new GeometryIdentification(c, "geometry_columns", "f_geometry_column", "type", sessionCache); + + this.library = Geometries.implementation(null); + } + + @Override + public OGC06104r4.Spi getSpi() { + return spi; + } + + @Override + public Optional<ColumnAdapter<?>> getMapping(SQLColumn columnDefinition) { + if (columnDefinition.typeName != null && "geometry".equalsIgnoreCase(columnDefinition.typeName)) { + return Optional.of(forGeometry(columnDefinition)); + } + + return Optional.empty(); + } + + private ColumnAdapter<?> forGeometry(SQLColumn definition) { + // In case of a computed column, geometric definition could be null. + final GeometryIdentification.GeometryColumn geomDef; + try { + geomDef = identifyGeometries.fetch(definition).orElse(null); + } catch (SQLException | ParseException e) { + throw new BackingStoreException(e); + } + String geometryType = geomDef == null ? null : geomDef.type; + final Class geomClass = getGeometricClass(geometryType, library); + + return new WKBReader(geomClass, geomDef == null ? null : geomDef.crs); + } + + @Override + public void close() throws SQLException { + identifyGeometries.close(); + } + + static Class getGeometricClass(String geometryType, final Geometries library) { + if (geometryType == null) return library.rootClass; + + // remove Z, M or ZM suffix + if (geometryType.endsWith("M")) geometryType = geometryType.substring(0, geometryType.length()-1); + if (geometryType.endsWith("Z")) geometryType = geometryType.substring(0, geometryType.length()-1); + + final Class geomClass; + switch (geometryType) { + case "POINT": + geomClass = library.pointClass; + break; + case "LINESTRING": + geomClass = library.polylineClass; + break; + case "POLYGON": + geomClass = library.polygonClass; + break; + default: geomClass = library.rootClass; + } + return geomClass; + } + + abstract static class Reader implements ColumnAdapter { + + final Class geomClass; + + public Reader(Class geomClass) { + this.geomClass = geomClass; + } + + @Override + public Class getJavaType() { + return geomClass; + } + } + + private final class WKBReader extends Reader implements SQLBiFunction<ResultSet, Integer, Object> { + + final CoordinateReferenceSystem crsToApply; + + private WKBReader(Class geomClass, CoordinateReferenceSystem crsToApply) { + super(geomClass); + this.crsToApply = crsToApply; + } + + @Override + public Object apply(ResultSet resultSet, Integer integer) throws SQLException { + final byte[] bytes = resultSet.getBytes(integer); + if (bytes == null) return null; + // EWKB reader should be compliant with standard WKB format. However, it is not sure that database driver + // will return WKB, so we should find a way to ensure SQL queries would use ST_AsBinary function. + return new EWKBReader(library).forCrs(crsToApply).read(bytes); + } + + @Override + public SQLBiFunction prepare(Connection target) { + return this; + } + + @Override + public Optional<CoordinateReferenceSystem> getCrs() { + return Optional.ofNullable(crsToApply); + } + } + + public static final class Spi implements DialectMapping.Spi { + + @Override + public Optional<DialectMapping> create(Connection c) throws SQLException { + return Optional.of(new OGC06104r4(this, c)); + } + + @Override + public Dialect getDialect() { + return Dialect.ANSI; + } + } +} diff --git a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/PostGISMapping.java b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/PostGISMapping.java index ac9fc0f..fddf714 100644 --- a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/PostGISMapping.java +++ b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/PostGISMapping.java @@ -4,7 +4,6 @@ import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; -import java.sql.Types; import java.text.ParseException; import java.util.Optional; import java.util.logging.Level; @@ -18,6 +17,8 @@ import org.apache.sis.util.collection.BackingStoreException; import org.apache.sis.util.collection.Cache; import org.apache.sis.util.logging.Logging; +import static org.apache.sis.internal.sql.feature.OGC06104r4.*; + /** * Maps geometric values between PostGIS natural representation (Hexadecimal EWKT) and SIS. * For more information about EWKB format, see: @@ -32,8 +33,6 @@ public final class PostGISMapping implements DialectMapping { final GeometryIdentification identifyGeometries; final GeometryIdentification identifyGeographies; - final Connection connection; - final Geometries library; /** @@ -46,7 +45,6 @@ public final class PostGISMapping implements DialectMapping { final Cache<Integer, CoordinateReferenceSystem> sessionCache; private PostGISMapping(final PostGISMapping.Spi spi, Connection c) throws SQLException { - connection = c; this.spi = spi; sessionCache = new Cache<>(7, 0, true); this.identifyGeometries = new GeometryIdentification(c, "geometry_columns", "f_geometry_column", "type", sessionCache); @@ -62,13 +60,11 @@ public final class PostGISMapping implements DialectMapping { @Override public Optional<ColumnAdapter<?>> getMapping(SQLColumn definition) { - switch (definition.type) { - case (Types.OTHER): return Optional.ofNullable(forOther(definition)); - } - return Optional.empty(); + return Optional.ofNullable(forGeometry(definition)); } - private ColumnAdapter<?> forOther(SQLColumn definition) { + private ColumnAdapter<?> forGeometry(SQLColumn definition) { + if (definition.typeName == null) return null; switch (definition.typeName.trim().toLowerCase()) { case "geometry": return forGeometry(definition, identifyGeometries); @@ -87,7 +83,7 @@ public final class PostGISMapping implements DialectMapping { throw new BackingStoreException(e); } String geometryType = geomDef == null ? null : geomDef.type; - final Class geomClass = getGeometricClass(geometryType); + final Class geomClass = getGeometricClass(geometryType, library); if (geomDef == null || geomDef.crs == null) { return new HexEWKBDynamicCrs(geomClass); @@ -98,29 +94,6 @@ public final class PostGISMapping implements DialectMapping { } } - private Class getGeometricClass(String geometryType) { - if (geometryType == null) return library.rootClass; - - // remove Z, M or ZM suffix - if (geometryType.endsWith("M")) geometryType = geometryType.substring(0, geometryType.length()-1); - if (geometryType.endsWith("Z")) geometryType = geometryType.substring(0, geometryType.length()-1); - - final Class geomClass; - switch (geometryType) { - case "POINT": - geomClass = library.pointClass; - break; - case "LINESTRING": - geomClass = library.polylineClass; - break; - case "POLYGON": - geomClass = library.polygonClass; - break; - default: geomClass = library.rootClass; - } - return geomClass; - } - @Override public void close() throws SQLException { identifyGeometries.close(); @@ -158,52 +131,6 @@ public final class PostGISMapping implements DialectMapping { } } - private abstract class Reader implements ColumnAdapter { - - final Class geomClass; - - public Reader(Class geomClass) { - this.geomClass = geomClass; - } - - @Override - public Class getJavaType() { - return geomClass; - } - } - - private final class WKBReader extends Reader implements SQLBiFunction<ResultSet, Integer, Object> { - - final CoordinateReferenceSystem crsToApply; - - private WKBReader(Class geomClass, CoordinateReferenceSystem crsToApply) { - super(geomClass); - this.crsToApply = crsToApply; - } - - @Override - public Object apply(ResultSet resultSet, Integer integer) throws SQLException { - final byte[] bytes = resultSet.getBytes(integer); - if (bytes == null) return null; - final Object value = library.parseWKB(bytes); - if (value != null && crsToApply != null) { - library.setCRS(value, crsToApply); - } - - return value; - } - - @Override - public SQLBiFunction prepare(Connection target) { - return this; - } - - @Override - public Optional<CoordinateReferenceSystem> getCrs() { - return Optional.ofNullable(crsToApply); - } - } - private final class HexEWKBFixedCrs extends Reader { final CoordinateReferenceSystem crsToApply; diff --git a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/SpatialFunctions.java b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/SpatialFunctions.java index 01b59b8..bf95272 100644 --- a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/SpatialFunctions.java +++ b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/SpatialFunctions.java @@ -22,12 +22,15 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Types; import java.util.Optional; +import java.util.logging.Level; +import java.util.logging.Logger; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.apache.sis.internal.metadata.sql.Dialect; import org.apache.sis.internal.metadata.sql.Reflection; import org.apache.sis.setup.GeometryLibrary; +import org.apache.sis.util.logging.Logging; /** @@ -121,10 +124,26 @@ class SpatialFunctions { return null; } + /** + * TODO: improve by using service loader discovery, to allow user custom mappings. + * @param dialect Dialect of the target database. + * @param c The connection to use if mapping initialization requires fetching information from database. + * @return + * @throws SQLException + */ static Optional<DialectMapping> forDialect(final Dialect dialect, Connection c) throws SQLException { switch (dialect) { case POSTGRESQL: return new PostGISMapping.Spi().create(c); - default: return Optional.empty(); + default: { + try { + return new OGC06104r4.Spi().create(c); + } catch (SQLException e) { + final Logger logger = Logging.getLogger("org.apache.sis.internal.sql"); + logger.warning("No supported geometric binding. For more information, activate debug logs."); + logger.log(Level.FINE, "Error while creating default Geometric binding over SQL", e); + } + } } + return Optional.empty(); } }
