sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ama...@apache.org
Subject [sis] 20/22: refactor(SQLStore): try to generify SQL geometry management.
Date Thu, 14 Nov 2019 11:46:54 GMT
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 <amanin@apache.org>
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();
     }
 }


Mime
View raw message