sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ama...@apache.org
Subject [sis] 42/45: WIP(SQLSotre): first functional draft for PostGIS geometry decoding.
Date Tue, 12 Nov 2019 16:45:09 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 19dde60f603dab3e8e246978a4d3db778b6394f7
Author: Alexis Manin <amanin@apache.org>
AuthorDate: Fri Nov 8 17:17:42 2019 +0100

    WIP(SQLSotre): first functional draft for PostGIS geometry decoding.
---
 .../sis/internal/sql/feature/ANSIInterpreter.java  |   2 +-
 .../sis/internal/sql/feature/ANSIMapping.java      |  17 +-
 .../apache/sis/internal/sql/feature/Analyzer.java  |  61 +++---
 .../sis/internal/sql/feature/ColumnAdapter.java    |  22 ++-
 .../apache/sis/internal/sql/feature/Database.java  |  30 ++-
 .../sis/internal/sql/feature/DialectMapping.java   |  21 +-
 .../sis/internal/sql/feature/EWKBReader.java       |  68 +++++--
 .../sql/feature/GeometryIdentification.java        | 212 +++++++++++++++++++++
 .../internal/sql/feature/GeometryIdentifier.java   |  97 ----------
 .../sis/internal/sql/feature/PostGISMapping.java   | 147 +++++++++++++-
 .../sis/internal/sql/feature/QueryFeatureSet.java  |  30 +--
 .../sis/internal/sql/feature/SQLCloseable.java     |   8 +
 .../apache/sis/internal/sql/feature/SQLColumn.java |  82 ++++----
 .../sis/internal/sql/feature/SpatialFunctions.java |  18 +-
 .../apache/sis/internal/sql/feature/StreamSQL.java |   5 +-
 .../org/apache/sis/internal/sql/feature/Table.java |   2 +-
 16 files changed, 583 insertions(+), 239 deletions(-)

diff --git a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/ANSIInterpreter.java b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/ANSIInterpreter.java
index 943df9e..76bfec0 100644
--- a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/ANSIInterpreter.java
+++ b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/ANSIInterpreter.java
@@ -184,7 +184,7 @@ public class ANSIInterpreter implements FilterVisitor, ExpressionVisitor {
     public Object visit(BBOX filter, Object extraData) {
         // TODO: This is a wrong interpretation, but sqlmm has no equivalent of filter encoding bbox, so we'll
         // fallback on a standard intersection. However, PostGIS, H2, etc. have their own versions of such filters.
-        return function("ST_Intersects(", filter, extraData);
+        return function("ST_Intersects", filter, extraData);
     }
 
     @Override
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 f430e40..ef6403b 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
@@ -14,8 +14,6 @@ import java.time.OffsetTime;
 import java.time.ZoneOffset;
 import java.util.Optional;
 
-import org.apache.sis.internal.metadata.sql.Dialect;
-
 public class ANSIMapping implements DialectMapping {
 
     /**
@@ -30,17 +28,20 @@ public class ANSIMapping implements DialectMapping {
     }
 
     @Override
-    public Dialect getDialect() {
-        return Dialect.ANSI;
+    public Spi getSpi() {
+        return null;
     }
 
     @Override
-    public Optional<ColumnAdapter<?>> getMapping(int sqlType, String sqlTypeName) {
-        return Optional.ofNullable(getMappingImpl(sqlType, sqlTypeName));
+    public void close() throws SQLException {}
+
+    @Override
+    public Optional<ColumnAdapter<?>> getMapping(SQLColumn columnDefinition) {
+        return Optional.ofNullable(getMappingImpl(columnDefinition));
     }
 
-    public ColumnAdapter<?> getMappingImpl(int sqlType, String sqlTypeName) {
-        switch (sqlType) {
+    public ColumnAdapter<?> getMappingImpl(SQLColumn columnDefinition) {
+        switch (columnDefinition.type) {
             case Types.BIT:
             case Types.BOOLEAN:                 return forceCast(Boolean.class);
             case Types.TINYINT:                 if (!isByteUnsigned) return forceCast(Byte.class);  // else fallthrough.
diff --git a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/Analyzer.java b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/Analyzer.java
index 4a9cdd1..9878a5e 100644
--- a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/Analyzer.java
+++ b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/Analyzer.java
@@ -16,6 +16,7 @@
  */
 package org.apache.sis.internal.sql.feature;
 
+import java.sql.Connection;
 import java.sql.DatabaseMetaData;
 import java.sql.PreparedStatement;
 import java.sql.ResultSet;
@@ -27,8 +28,6 @@ import java.util.logging.Level;
 import java.util.logging.LogRecord;
 import javax.sql.DataSource;
 
-import org.opengis.feature.Feature;
-import org.opengis.feature.PropertyType;
 import org.opengis.util.GenericName;
 import org.opengis.util.NameFactory;
 import org.opengis.util.NameSpace;
@@ -52,6 +51,8 @@ import org.apache.sis.util.iso.Names;
 import org.apache.sis.util.logging.WarningListeners;
 import org.apache.sis.util.resources.ResourceInternationalString;
 
+import static org.apache.sis.util.ArgumentChecks.ensureNonNull;
+
 
 /**
  * Helper methods for creating {@code FeatureType}s from database structure.
@@ -60,6 +61,7 @@ import org.apache.sis.util.resources.ResourceInternationalString;
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
+ * @author  Alexis Manin (Geomatys)
  * @version 1.0
  * @since   1.0
  * @module
@@ -73,6 +75,11 @@ final class Analyzer {
     final DataSource source;
 
     /**
+     * A connection used all along this component life to query database.
+     */
+    final Connection connection;
+
+    /**
      * Information about the database as a whole.
      * Used for fetching tables, columns, primary keys <i>etc.</i>
      */
@@ -147,20 +154,24 @@ final class Analyzer {
      * Creates a new analyzer for the database described by given metadata.
      *
      * @param  source     the data source, usually given by user at {@code SQLStore} creation time.
-     * @param  metadata   Value of {@code source.getConnection().getMetaData()}.
+     * @param  databaseConnection   Database entrypoint. It's the caller responsability to handle connection lifecycle,
+     *                              and ensure this object life span is shorter than the connection one.
      * @param  listeners  Value of {@code SQLStore.listeners}.
      * @param  locale     Value of {@code SQLStore.getLocale()}.
      */
-    Analyzer(final DataSource source, final DatabaseMetaData metadata, final WarningListeners<DataStore> listeners,
+    Analyzer(final DataSource source, final Connection databaseConnection, final WarningListeners<DataStore> listeners,
              final Locale locale) throws SQLException
     {
+        ensureNonNull("Database connection provider", source);
+        ensureNonNull("Database connection", databaseConnection);
         this.source      = source;
-        this.metadata    = metadata;
+        this.connection  = databaseConnection;
+        this.metadata    = databaseConnection.getMetaData();
         this.listeners   = listeners;
         this.locale      = locale;
         this.strings     = new HashMap<>();
         this.escape      = metadata.getSearchStringEscape();
-        this.functions   = new SpatialFunctions(metadata);
+        this.functions   = new SpatialFunctions(databaseConnection, metadata);
         this.nameFactory = DefaultFactories.forBuildin(NameFactory.class);
         /*
          * The following tables are defined by ISO 19125 / OGC Simple feature access part 2.
@@ -305,10 +316,6 @@ final class Analyzer {
         warnings.add(Resources.formatInternational(key, argument));
     }
 
-    private PropertyAdapter analyze(SQLColumn target) {
-        throw new UnsupportedOperationException();
-    }
-
     /**
      * Invoked after we finished to create all tables. This method flush the warnings
      * (omitting duplicated warnings), then returns all tables including dependencies.
@@ -345,10 +352,10 @@ final class Analyzer {
         int i = 0;
         for (SQLColumn col : spec.getColumns()) {
             i++;
-            final ColumnAdapter<?> colAdapter = functions.toJavaType(col.getType(), col.getTypeName());
+            final ColumnAdapter<?> colAdapter = functions.toJavaType(col);
             Class<?> type = colAdapter.javaType;
-            final String colName = col.getName().getColumnName();
-            final String attrName = col.getName().getAttributeName();
+            final String colName = col.naming.getColumnName();
+            final String attrName = col.naming.getAttributeName();
             if (type == null) {
                 warning(Resources.Keys.UnknownType_1, colName);
                 type = Object.class;
@@ -357,14 +364,13 @@ final class Analyzer {
             final AttributeTypeBuilder<?> attribute = builder
                     .addAttribute(type)
                     .setName(attrName);
-            if (col.isNullable()) attribute.setMinimumOccurs(0);
-            final int precision = col.getPrecision();
-            /* TODO: we should check column type. Precision for numbers or blobs is meaningfull, but the convention
+            if (col.isNullable) attribute.setMinimumOccurs(0);
+            /* TODO: we should check column type. Precision for numbers or blobs is meaningful, but the convention
              * exposed by SIS does not allow to distinguish such cases.
              */
-            if (precision > 0) attribute.setMaximalLength(precision);
+            if (col.precision > 0) attribute.setMaximalLength(col.precision);
 
-            col.getCrs().ifPresent(attribute::setCRS);
+            colAdapter.getCrs().ifPresent(attribute::setCRS);
             if (geomCol.equals(attrName)) attribute.addRole(AttributeRole.DEFAULT_GEOMETRY);
 
             if (pkCols.contains(colName)) attribute.addRole(AttributeRole.IDENTIFIER_COMPONENT);
@@ -422,11 +428,10 @@ final class Analyzer {
         }
     }
 
-    private interface PropertyAdapter {
-        PropertyType getType();
-        void fill(ResultSet source, final Feature target);
-    }
-
+    /**
+     * TODO: this object needs a live connection. Check if we should parse all information at built, to avoid requiring
+     * keeping a connection all along.
+     */
     private final class TableMetadata implements SQLTypeSpecification {
         final TableReference id;
         private final String tableEsc;
@@ -448,7 +453,6 @@ final class Analyzer {
                 final List<String> cols = new ArrayList<>();
                 while (reflect.next()) {
                     cols.add(getUniqueString(reflect, Reflection.COLUMN_NAME));
-                    // The actual Boolean value will be fetched in the loop on columns later.
                 }
                 pk = PrimaryKey.create(cols);
             }
@@ -577,12 +581,18 @@ final class Analyzer {
 
             final ArrayList<SQLColumn> tmpCols = new ArrayList<>(total);
             for (int i = 1 ; i <= total ; i++) {
+                final TableReference optTable;
+                final String table = meta.getTableName(i);
+                if (table != null) {
+                    optTable = new TableReference(meta.getCatalogName(i), meta.getSchemaName(i), table, null);
+                } else optTable = null;
                 tmpCols.add(new SQLColumn(
                         meta.getColumnType(i),
                         meta.getColumnTypeName(i),
                         meta.isNullable(i) == ResultSetMetaData.columnNullable,
                         new ColumnRef(meta.getColumnName(i)).as(meta.getColumnLabel(i)),
-                        meta.getPrecision(i)
+                        meta.getPrecision(i),
+                        optTable
                 ));
             }
 
@@ -619,5 +629,4 @@ final class Analyzer {
             return Collections.EMPTY_LIST;
         }
     }
-
 }
diff --git a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/ColumnAdapter.java b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/ColumnAdapter.java
index 8fdd3a5..da13ae4 100644
--- a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/ColumnAdapter.java
+++ b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/ColumnAdapter.java
@@ -2,8 +2,11 @@ package org.apache.sis.internal.sql.feature;
 
 import java.sql.ResultSet;
 import java.sql.SQLException;
+import java.util.Optional;
 import java.util.function.Function;
 
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+
 import static org.apache.sis.util.ArgumentChecks.ensureNonNull;
 
 /**
@@ -12,15 +15,21 @@ import static org.apache.sis.util.ArgumentChecks.ensureNonNull;
  *
  * @param <T> Type of object decoded from cell.
  */
-class ColumnAdapter<T> implements SQLBiFunction<ResultSet, Integer, T> {
+public class ColumnAdapter<T> implements SQLBiFunction<ResultSet, Integer, T> {
     final Class<T> javaType;
     private final SQLBiFunction<ResultSet, Integer, T> fetchValue;
+    private final CoordinateReferenceSystem crs;
+
+    public ColumnAdapter(Class<T> javaType, SQLBiFunction<ResultSet, Integer, T> fetchValue) {
+        this(javaType, fetchValue, null);
+    }
 
-    protected ColumnAdapter(Class<T> javaType, SQLBiFunction<ResultSet, Integer, T> fetchValue) {
+    public ColumnAdapter(Class<T> javaType, SQLBiFunction<ResultSet, Integer, T> fetchValue, final CoordinateReferenceSystem crs) {
         ensureNonNull("Result java type", javaType);
         ensureNonNull("Function for value retrieval", fetchValue);
         this.javaType = javaType;
         this.fetchValue = fetchValue;
+        this.crs = crs;
     }
 
     @Override
@@ -32,4 +41,13 @@ class ColumnAdapter<T> implements SQLBiFunction<ResultSet, Integer, T> {
     public <V> SQLBiFunction<ResultSet, Integer, V> andThen(Function<? super T, ? extends V> after) {
         return fetchValue.andThen(after);
     }
+
+    /**
+     * Note : This method could be used not only for geometric fields, but also on numeric ones representing 1D systems.
+     *
+     * @return Potentially an empty shell, or the default coordinate reference system for this column values.
+     */
+    public Optional<CoordinateReferenceSystem> getCrs() {
+        return Optional.ofNullable(crs);
+    }
 }
diff --git a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/Database.java b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/Database.java
index 3f04da4..0985cfb 100644
--- a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/Database.java
+++ b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/Database.java
@@ -112,7 +112,7 @@ public final class Database {
             final GenericName[] tableNames, final WarningListeners<DataStore> listeners)
             throws SQLException, DataStoreException
     {
-        final Analyzer analyzer = new Analyzer(source, connection.getMetaData(), listeners, store.getLocale());
+        final Analyzer analyzer = new Analyzer(source, connection, listeners, store.getLocale());
         final String[] tableTypes = getTableTypes(analyzer.metadata);
         final Set<TableReference> declared = new LinkedHashSet<>();
         for (final GenericName tableName : tableNames) {
@@ -233,4 +233,32 @@ public final class Database {
     public String toString() {
         return TableReference.toString(this, (n) -> appendTo(n));
     }
+
+    /**
+     * Acquire a connection over parent database, forcing a few parameters to ensure optimal read performance and
+     * limiting user rights :
+     * <ul>
+     *     <li>{@link Connection#setAutoCommit(boolean) auto-commit} to false</li>
+     *     <li>{@link Connection#setReadOnly(boolean) querying read-only}</li>
+     * </ul>
+     *
+     * @param source Database pointer to create connection from.
+     * @return A new connection to database, with deactivated auto-commit.
+     * @throws SQLException If we cannot create a new connection. See {@link DataSource#getConnection()} for details.
+     */
+    public static Connection connectReadOnly(final DataSource source) throws SQLException {
+        final Connection c = source.getConnection();
+        try {
+            c.setAutoCommit(false);
+            c.setReadOnly(true);
+        } catch (SQLException e) {
+            try {
+                c.close();
+            } catch (RuntimeException | SQLException bis) {
+                e.addSuppressed(bis);
+            }
+            throw e;
+        }
+        return c;
+    }
 }
diff --git a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/DialectMapping.java b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/DialectMapping.java
index ce6e403..33d0266 100644
--- a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/DialectMapping.java
+++ b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/DialectMapping.java
@@ -1,12 +1,27 @@
 package org.apache.sis.internal.sql.feature;
 
+import java.sql.Connection;
+import java.sql.SQLException;
 import java.util.Optional;
 
 import org.apache.sis.internal.metadata.sql.Dialect;
 
-public interface DialectMapping {
+public interface DialectMapping extends SQLCloseable {
 
-    Dialect getDialect();
+    Spi getSpi();
 
-    Optional<ColumnAdapter<?>> getMapping(final int sqlType, final String sqlTypeName);
+    Optional<ColumnAdapter<?>> getMapping(final SQLColumn columnDefinition);
+
+    interface Spi {
+        /**
+         *
+         * @param c The connection to use to connect to the database. It will be read-only.
+         * @return A component compatible with database of given connection, or nothing if the database is not supported
+         * by this component.
+         * @throws SQLException If an error occurs while fetching information from database.
+         */
+        Optional<DialectMapping> create(final Connection c) throws SQLException;
+
+        Dialect getDialect();
+    }
 }
diff --git a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/EWKBReader.java b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/EWKBReader.java
index 1681034..926dbc5 100644
--- a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/EWKBReader.java
+++ b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/EWKBReader.java
@@ -4,18 +4,24 @@ import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
 import java.util.Arrays;
 
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+
 import org.apache.sis.internal.feature.Geometries;
 import org.apache.sis.math.Vector;
 import org.apache.sis.setup.GeometryLibrary;
 
+import static java.lang.Character.digit;
+import static org.apache.sis.util.ArgumentChecks.ensureNonEmpty;
+
 /**
- * PostGIS Hexa-EWKB Geometry reader/write classes.
+ * PostGIS EWKB Geometry reader/write classes.
  * http://postgis.net/docs/using_postgis_dbmanagement.html#EWKB_EWKT
  *
  * This format is the natural form returned by a query selection a geometry field
  * whithout using any ST_X method.
  *
  * @author Johann Sorel (Geomatys)
+ * @author Alexis Manin (Geomatys)
  */
 class EWKBReader {
 
@@ -35,19 +41,34 @@ class EWKBReader {
 
     private final Geometries factory;
 
+    private CoordinateReferenceSystem crs;
+
     EWKBReader() {
-        this(null);
+        this((GeometryLibrary) null);
     }
 
     EWKBReader(GeometryLibrary library) {
-        this.factory = Geometries.implementation(library);
+        this(Geometries.implementation(library));
+    }
+
+    EWKBReader(Geometries geometryFactory) {
+        this.factory = geometryFactory;
+    }
+
+    public EWKBReader setCrs(CoordinateReferenceSystem defaultCrs) {
+        this.crs = defaultCrs;
+        return this;
+    }
+
+    Object readHexa(final String hexaEWkb) {
+        return read(decodeHex(hexaEWkb));
     }
 
     Object read(final byte[] eWkb) {
         return new Reader(ByteBuffer.wrap(eWkb)).read();
     }
 
-    private class Reader {
+    private final class Reader {
         final ByteBuffer buffer;
         final int geomType;
         final int dimension;
@@ -55,23 +76,26 @@ class EWKBReader {
 
         private Reader(ByteBuffer buffer) {
             final byte endianess = buffer.get();
-            if (isBigEndian(endianess)) {
-                this.buffer = buffer.order(ByteOrder.BIG_ENDIAN);
+            if (isLittleEndian(endianess)) {
+                this.buffer = buffer.order(ByteOrder.LITTLE_ENDIAN);
             } else this.buffer = buffer;
             final int     flags     = buffer.getInt();
             final boolean flagZ     = (flags & MASK_Z)    != 0;
             final boolean flagM     = (flags & MASK_M)    != 0;
             final boolean flagSRID  = (flags & MASK_SRID) != 0;
                           geomType  = (flags & MASK_GEOMTYPE);
-                          dimension = 2 + ((flagZ)?1:0) + ((flagM)?1:0);
+                          dimension = 2 + ((flagZ) ? 1 : 0) + ((flagM) ? 1 : 0);
                           srid      = flagSRID ? buffer.getInt() : 0;
         }
 
         Object read() {
             final Object geom = decodeGeometry();
-            if (srid > 0) {
-                // TODO: set CRS
+
+            // TODO: set CRS
+            if (crs != null) {
+            } else if (srid > 0) {
             }
+
             return geom;
         }
 
@@ -145,7 +169,29 @@ class EWKBReader {
 
     }
 
-    private static boolean isBigEndian(byte endianess) {
-        return endianess == 0; // org.postgis.binary.ValueGetter.XDR.NUMBER
+    private static boolean isLittleEndian(byte endianess) {
+        return endianess == 1; // org.postgis.binary.ValueGetter.NDR.NUMBER
+    }
+
+    /**
+     * Convert a text representing an hexadecimal set of values (no separator, each 2 characters form a value).
+     *
+     * @param hexa The hexadecimal values to decode. Should neither be null nor empty.
+     * @return Real values encoded by input hexadecimal text. Never null, never empty.
+     */
+    static byte[] decodeHex(String hexa) {
+        ensureNonEmpty("Hexadecimal text", hexa);
+        int len = hexa.length();
+        // Handle odd length by considering last character as a lone value
+        byte[] data = new byte[(len+1) / 2];
+        int limit = (len % 2 == 0) ? len : len - 1;
+        for (int i = 0, j=0 ; i < limit ; ) {
+            data[j++] = (byte) ((digit(hexa.charAt(i++), 16) << 4)
+                    + digit(hexa.charAt(i++), 16));
+        }
+
+        if (limit < len) data[data.length - 1] = (byte) digit(hexa.charAt(limit), 16);
+
+        return data;
     }
 }
diff --git a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/GeometryIdentification.java b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/GeometryIdentification.java
new file mode 100644
index 0000000..a10932c
--- /dev/null
+++ b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/GeometryIdentification.java
@@ -0,0 +1,212 @@
+package org.apache.sis.internal.sql.feature;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.text.ParseException;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.opengis.util.FactoryException;
+
+import org.apache.sis.io.wkt.Convention;
+import org.apache.sis.io.wkt.WKTFormat;
+import org.apache.sis.referencing.CRS;
+
+import static org.apache.sis.util.ArgumentChecks.ensureNonEmpty;
+
+/**
+ * Not THREAD-SAFE !
+ *
+ * @implNote <a href="https://www.jooq.org/doc/3.12/manual/sql-execution/fetching/pojos/#N5EFC1">I miss JOOQ...</a>
+ */
+class GeometryIdentification implements SQLCloseable {
+
+    private final Connection c;
+    final PreparedStatement identifySchemaQuery;
+    /**
+     * A statement serving two purposes:
+     * <ol>
+     *     <li>Searching all available geometric columns of a specified table</li>
+     *     <li>Fetching geometric information for a specific column</li>
+     * </ol>
+     *
+     * @implNote The statement definition is able to serve both purposes by changing geometry column filter to no-op.
+     */
+    final PreparedStatement columnQuery;
+    final PreparedStatement wktFromSrid;
+
+    private final WKTFormat wktReader;
+
+    /**
+     * Describes if geometry column registry include a column for geometry types, according that one can apparently omit
+     * it (see Simple_feature_access_-_Part_2_SQL_option_v1.2.1, section 6.2: Architecture - SQL implementation using
+     * Geometry Types).
+     */
+    final boolean typeIncluded;
+
+    public GeometryIdentification(Connection c) throws SQLException {
+        this(c, "geometry_columns", "f_geometry_column", "geometry_type");
+    }
+    public GeometryIdentification(Connection c, String identificationTable, String geometryColumnName, String typeColumnName) throws SQLException {
+        this.c = c;
+        typeIncluded = typeColumnName != null && !(typeColumnName=typeColumnName.trim()).isEmpty();
+        identifySchemaQuery = c.prepareStatement("SELECT DISTINCT(f_table_schema) FROM "+identificationTable+" WHERE f_table_name = ?");
+        columnQuery = c.prepareStatement(
+                "SELECT "+geometryColumnName+", coord_dimension, srid" + (typeIncluded ? ", "+typeColumnName : "") + ' ' +
+                "FROM "+identificationTable+" " +
+                "WHERE f_table_schema LIKE ? " +
+                "AND f_table_name = ? " +
+                "AND "+geometryColumnName+" LIKE ?"
+        );
+        wktFromSrid = c.prepareStatement("SELECT auth_name, auth_srid, srtext FROM spatial_ref_sys WHERE srid=?");
+        wktReader = new WKTFormat(null, null);
+        wktReader.setConvention(Convention.WKT1_COMMON_UNITS);
+    }
+
+    Set<GeometryColumn> fetchGeometricColumns(String schema, final String table) throws SQLException, ParseException {
+        ensureNonEmpty("Table name", table);
+        if (schema == null || (schema = schema.trim()).isEmpty()) {
+            // To avoid ambiguity, we have to restrict search to a single schema
+            identifySchemaQuery.setString(1, table);
+            try (ResultSet result = identifySchemaQuery.executeQuery()) {
+                if (!result.next()) return Collections.EMPTY_SET;
+                schema = result.getString(1);
+                if (result.next()) throw new IllegalArgumentException("Multiple tables match given name. Please specify schema to remove all ambiguities");
+            }
+        }
+
+        columnQuery.setString(1, schema);
+        columnQuery.setString(2, table);
+        columnQuery.setString(3, "%");
+        try (ResultSet result = columnQuery.executeQuery()) {
+            final HashSet<GeometryColumn> cols = new HashSet<>();
+            while (result.next()) {
+                cols.add(create(result));
+            }
+            return cols;
+        } finally {
+            columnQuery.clearParameters();
+        }
+    }
+
+    Optional<GeometryColumn> fetch(SQLColumn target) throws SQLException, ParseException {
+        if (target == null || target.origin == null) return Optional.empty();
+
+        String schema = target.origin.schema;
+        if (schema == null || (schema = schema.trim()).isEmpty()) schema = "%";
+        columnQuery.setString(1, schema);
+        columnQuery.setString(2, target.origin.table);
+        columnQuery.setString(3, target.naming.getColumnName());
+
+        try (ResultSet result = columnQuery.executeQuery()) {
+            if (result.next()) return Optional.of(create(result));
+        } finally {
+            columnQuery.clearParameters();
+        }
+        return Optional.empty();
+    }
+
+    private GeometryColumn create(final ResultSet cursor) throws SQLException, ParseException {
+        final String name = cursor.getString(1);
+        final int dimension = cursor.getInt(2);
+        final int pgSrid = cursor.getInt(3);
+        final String type = cursor.getString(4);
+        // Note: we make a query per entry, which could impact performance. However, 99% of defined tables
+        // will have only one geometry column. Moreover, even with more than one, with prepared statement, the
+        // performance impact should stay low.
+        final CoordinateReferenceSystem crs = fetchCrs(pgSrid);
+        return new GeometryColumn(name, dimension, pgSrid, type, crs);
+    }
+
+    /**
+     * Try to fetch spatial system relative to given SRID.
+     * @param pgSrid The SRID as defined by the database (see
+     *               <a href="http://postgis.refractions.net/documentation/manual-1.3/ch04.html#id2571265">Official PostGIS documentation</a> for details).
+     * @return If input was 0 or less, a null value is returned. Otherwise, return the CRS decoded from database WKT.
+     * @throws IllegalArgumentException If given SRID is above 0, but no coordinate system definition can be found for
+     * it in the database, or found object is not a database, or no WKT is available, but authority code is not
+     * supported by SIS.
+     * @throws IllegalStateException If more than one match is found for given SRID.
+     */
+    private CoordinateReferenceSystem fetchCrs(int pgSrid) throws SQLException, IllegalArgumentException, ParseException {
+        if (pgSrid <= 0) return null;
+
+        wktFromSrid.setInt(1, pgSrid);
+        try (ResultSet result = wktFromSrid.executeQuery()) {
+            if (!result.next()) throw new IllegalArgumentException("No entry found for SRID "+pgSrid);
+            final String authority = result.getString(1);
+            final int authorityCode = result.getInt(2);
+            final String pgWkt = result.getString(3);
+
+            // That should never happen, but if it does, there's a serious problem !
+            if (result.next()) throw new IllegalStateException("More than one definition available for SRID "+pgSrid);
+
+            if (pgWkt == null || pgWkt.trim().isEmpty()) {
+                try {
+                    return CRS.getAuthorityFactory(authority).createCoordinateReferenceSystem(Integer.toString(authorityCode));
+                } catch (FactoryException e) {
+                    throw new IllegalArgumentException(String.format(
+                            "Input SRID (%d) does not provide any WKT, but its authority code (%s:%d) is not supported by SIS",
+                            pgSrid, authority, authorityCode
+                    ), e);
+                }
+            }
+            final Object parsedWkt = wktReader.parseObject(pgWkt);
+            if (parsedWkt instanceof CoordinateReferenceSystem) {
+                return (CoordinateReferenceSystem) parsedWkt;
+            } else throw new ParseException(String.format(
+                    "WKT of given SRID cannot be interprated as a CRS.%nInput SRID: %d%nOutput type: %s",
+                    pgSrid, parsedWkt.getClass().getCanonicalName()
+            ), 0);
+        } finally {
+            wktFromSrid.clearParameters();
+        }
+    }
+
+    @Override
+    public void close() throws SQLException {
+        try (
+                SQLCloseable c1 = columnQuery::close;
+                SQLCloseable c2 = identifySchemaQuery::close;
+                SQLCloseable c3 = wktFromSrid::close;
+         ) {}
+    }
+
+    static final class GeometryColumn {
+        final String name;
+        final int dimension;
+        final int pgSrid;
+        final String type;
+
+        final CoordinateReferenceSystem crs;
+
+        private GeometryColumn(String name, int dimension, int srid, final String type, CoordinateReferenceSystem crs) {
+            this.name = name;
+            this.dimension = dimension;
+            this.pgSrid = srid;
+            this.crs = crs;
+            this.type = type;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+            GeometryColumn that = (GeometryColumn) o;
+            return dimension == that.dimension &&
+                    pgSrid == that.pgSrid &&
+                    name.equals(that.name);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(name);
+        }
+    }
+}
diff --git a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/GeometryIdentifier.java b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/GeometryIdentifier.java
deleted file mode 100644
index cd0cdc0..0000000
--- a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/GeometryIdentifier.java
+++ /dev/null
@@ -1,97 +0,0 @@
-package org.apache.sis.internal.sql.feature;
-
-import java.sql.Connection;
-import java.sql.PreparedStatement;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Objects;
-import java.util.Set;
-
-import org.apache.sis.internal.metadata.sql.SQLBuilder;
-
-import static org.apache.sis.util.ArgumentChecks.ensureNonEmpty;
-
-/**
- * Not THREAD-SAFE !
- */
-class GeometryIdentifier implements AutoCloseable {
-
-    private final Connection c;
-    final PreparedStatement identifySchemaQuery;
-    final PreparedStatement columnQuery;
-
-    /**
-     * Prefetch an SQL builder, so subsequent calls will use a copy constructor to avoid re-analyzing database metadata.
-     */
-    private final SQLBuilder template;
-
-    public GeometryIdentifier(Connection c, boolean quoteSchema) throws SQLException {
-        this.c = c;
-        template = new SQLBuilder(c.getMetaData(), quoteSchema);
-        identifySchemaQuery = c.prepareStatement("SELECT DISTINCT(f_table_schema) FROM GEOMETRY_COLUMNS WHERE f_table_name = ?");
-        columnQuery = c.prepareStatement("SELECT f_geometry_column, coord_dimension, srid FROM GEOMETRY_COLUMNS WHERE f_schema_name = ? AND table = ?");
-    }
-
-    Set<GeometryColumn> fetchGeometricColumns(String schema, final String table) throws SQLException {
-        final SQLBuilder builder = new SQLBuilder(template);
-        ensureNonEmpty("Table name", table);
-        if (schema == null || (schema = schema.trim()).isEmpty()) {
-            // To avoid ambiguity, we have to restrict search to a single schema
-            identifySchemaQuery.setString(1, table);
-            try (ResultSet result = identifySchemaQuery.executeQuery()) {
-                if (!result.next()) return Collections.EMPTY_SET;
-                schema = result.getString(1);
-                if (result.next()) throw new IllegalArgumentException("Multiple tables match given name. Please specify schema to remove all ambiguities");
-            }
-        }
-
-        columnQuery.setString(1, schema);
-        columnQuery.setString(2, table);
-        try (ResultSet result = columnQuery.executeQuery()) {
-            final HashSet<GeometryColumn> cols = new HashSet<>();
-            while (result.next()) {
-                cols.add(new GeometryColumn(result.getString(1), result.getInt(2), result.getInt(3)));
-            }
-            return cols;
-        }
-    }
-
-    @Override
-    public void close() throws SQLException {
-        try (SQLCloseable c1 = columnQuery::close; SQLCloseable c2 = identifySchemaQuery::close;) {}
-    }
-
-    private interface SQLCloseable extends AutoCloseable {
-        @Override
-        void close() throws SQLException;
-    }
-
-    static final class GeometryColumn {
-        final String name;
-        final int dimension;
-        final int srid;
-
-        GeometryColumn(String name, int dimension, int srid) {
-            this.name = name;
-            this.dimension = dimension;
-            this.srid = srid;
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (this == o) return true;
-            if (o == null || getClass() != o.getClass()) return false;
-            GeometryColumn that = (GeometryColumn) o;
-            return dimension == that.dimension &&
-                    srid == that.srid &&
-                    name.equals(that.name);
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(name);
-        }
-    }
-}
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 9c9303d..84144c8 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
@@ -1,29 +1,160 @@
 package org.apache.sis.internal.sql.feature;
 
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
 import java.sql.Types;
+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;
+
+public final class PostGISMapping implements DialectMapping {
+
+    final PostGISMapping.Spi spi;
+    final GeometryIdentification identifyGeometries;
+    final GeometryIdentification identifyGeographies;
+
+    final Connection connection;
+
+    final Geometries<?> library;
+
+    private PostGISMapping(final PostGISMapping.Spi spi, Connection c) throws SQLException {
+        connection = c;
+        this.spi = spi;
+        this.identifyGeometries = new GeometryIdentification(c, "geometry_columns", "f_geometry_column", "type");
+        this.identifyGeographies = new GeometryIdentification(c, "geography_columns", "f_geography_column", "type");
+
+        this.library = Geometries.implementation(null);
+    }
 
-public class PostGISMapping implements DialectMapping {
     @Override
-    public Dialect getDialect() {
-        return Dialect.POSTGRESQL;
+    public Spi getSpi() {
+        return spi;
     }
 
     @Override
-    public Optional<ColumnAdapter<?>> getMapping(int sqlType, String sqlTypeName) {
-        switch (sqlType) {
-            case (Types.OTHER):
+    public Optional<ColumnAdapter<?>> getMapping(SQLColumn definition) {
+        switch (definition.type) {
+            case (Types.OTHER): return Optional.ofNullable(forOther(definition));
         }
         return Optional.empty();
     }
 
-    private ColumnAdapter<?> forOther(String sqlTypeName) {
-        switch (sqlTypeName.toLowerCase()) {
+    private ColumnAdapter<?> forOther(SQLColumn definition) {
+        switch (definition.typeName.trim().toLowerCase()) {
             case "geometry":
+                return forGeometry(definition, identifyGeometries);
             case "geography":
+                return forGeometry(definition, identifyGeographies);
             default: return null;
         }
     }
+
+    private ColumnAdapter<?> forGeometry(SQLColumn definition, GeometryIdentification ident) {
+        // In case of a computed column, geometric definition could be null.
+        final GeometryIdentification.GeometryColumn geomDef;
+        try {
+            geomDef = ident.fetch(definition).orElse(null);
+        } catch (SQLException | ParseException e) {
+            throw new BackingStoreException(e);
+        }
+        String geometryType = geomDef == null ? null : geomDef.type;
+        final Class geomClass = getGeometricClass(geometryType);
+        SQLBiFunction<ResultSet, Integer, Object> geometryDecoder;
+        // TODO: activate optimisation : WKB is lighter, but we need to modify user query, and to know CRS in advance.
+//        if (geomDef.crs == null) {
+            geometryDecoder = new HexEWKBReader();
+//        } else {
+//            // For optimisation purpose, we'll return directly a WKB reader if CRS is known in advance.
+//            geometryDecoder = new WKBReader(geomDef.crs);
+//        }
+
+        return new ColumnAdapter<>(geomClass, geometryDecoder, geomDef == null ? null : geomDef.crs);
+    }
+
+    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();
+    }
+
+    public static final class Spi implements DialectMapping.Spi {
+
+        @Override
+        public Optional<DialectMapping> create(Connection c) throws SQLException {
+            return Optional.of(new PostGISMapping(this, c));
+        }
+
+        @Override
+        public Dialect getDialect() {
+            return Dialect.POSTGRESQL;
+        }
+    }
+
+    private final class WKBReader implements SQLBiFunction<ResultSet, Integer, Object> {
+
+        final CoordinateReferenceSystem crsToApply;
+
+        private WKBReader(CoordinateReferenceSystem crsToApply) {
+            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) {
+                // TODO: set CRS
+            }
+
+            return value;
+        }
+    }
+
+    private final class HexEWKBReader implements SQLBiFunction<ResultSet, Integer, Object> {
+
+        final EWKBReader reader;
+
+        private HexEWKBReader() {
+            this(null);
+        }
+
+        private HexEWKBReader(CoordinateReferenceSystem crsToApply) {
+            reader = new EWKBReader(library).setCrs(crsToApply);
+        }
+
+        @Override
+        public Object apply(ResultSet resultSet, Integer integer) throws SQLException {
+            final String hexa = resultSet.getString(integer);
+            return hexa == null ? null : reader.readHexa(hexa);
+        }
+    }
 }
diff --git a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/QueryFeatureSet.java b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/QueryFeatureSet.java
index a117b13..7b94f2f 100644
--- a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/QueryFeatureSet.java
+++ b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/QueryFeatureSet.java
@@ -123,7 +123,7 @@ public class QueryFeatureSet extends AbstractFeatureSet {
      * @throws SQLException If input query compiling or analysis of its metadata fails.
      */
     public QueryFeatureSet(SQLBuilder queryBuilder, DataSource source, Connection conn) throws SQLException {
-        this(queryBuilder, new Analyzer(source, conn.getMetaData(), null, null), source, conn);
+        this(queryBuilder, new Analyzer(source, conn, null, null), source, conn);
     }
 
 
@@ -195,34 +195,6 @@ public class QueryFeatureSet extends AbstractFeatureSet {
         return super.subset(query);
     }
 
-    /**
-     * Acquire a connection over parent database, forcing a few parameters to ensure optimal read performance and
-     * limiting user rights :
-     * <ul>
-     *     <li>{@link Connection#setAutoCommit(boolean) auto-commit} to false</li>
-     *     <li>{@link Connection#setReadOnly(boolean) querying read-only}</li>
-     * </ul>
-     *
-     * @param source Database pointer to create connection from.
-     * @return A new connection to database, with deactivated auto-commit.
-     * @throws SQLException If we cannot create a new connection. See {@link DataSource#getConnection()} for details.
-     */
-    public static Connection connectReadOnly(final DataSource source) throws SQLException {
-        final Connection c = source.getConnection();
-        try {
-            c.setAutoCommit(false);
-            c.setReadOnly(true);
-        } catch (SQLException e) {
-            try {
-                c.close();
-            } catch (RuntimeException | SQLException bis) {
-                e.addSuppressed(bis);
-            }
-            throw e;
-        }
-        return c;
-    }
-
     class SubsetAdapter extends SQLQueryAdapter {
 
         SubsetAdapter() {
diff --git a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/SQLCloseable.java b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/SQLCloseable.java
new file mode 100644
index 0000000..439b4f1
--- /dev/null
+++ b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/SQLCloseable.java
@@ -0,0 +1,8 @@
+package org.apache.sis.internal.sql.feature;
+
+import java.sql.SQLException;
+
+public interface SQLCloseable extends AutoCloseable {
+    @Override
+    void close() throws SQLException;
+}
diff --git a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/SQLColumn.java b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/SQLColumn.java
index d92fe8c..68e6382 100644
--- a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/SQLColumn.java
+++ b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/SQLColumn.java
@@ -1,58 +1,58 @@
 package org.apache.sis.internal.sql.feature;
 
+import java.sql.DatabaseMetaData;
 import java.sql.ResultSetMetaData;
-import java.util.Optional;
+import java.sql.Types;
 
-import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.apache.sis.internal.metadata.sql.Reflection;
 
+/**
+ * A simple POJO to hold information about an SQL column. This mainly represents information extracted from
+ * {@link DatabaseMetaData#getColumns(String, String, String, String) database metadata}.
+ * Note that for now, only a few selected information are represented. If needed, new fields could be added if needed.
+ * The aim is to describe as well as possible all SQL related information about a column, to allow mapping to feature
+ * model as accurate as possible.
+ */
 class SQLColumn {
+
+    /**
+     * Value type as specified in {@link Types}
+     */
     final int type;
+    /**
+     * A name for the value type, free-text from the database engine. For more information about this, please see
+     * {@link DatabaseMetaData#getColumns(String, String, String, String)} and {@link Reflection#TYPE_NAME}.
+     */
     final String typeName;
-    private final boolean isNullable;
-    private final ColumnRef naming;
-    private final int precision;
-
-    SQLColumn(int type, String typeName, boolean isNullable, ColumnRef naming, int precision) {
-        this.type = type;
-        this.typeName = typeName;
-        this.isNullable = isNullable;
-        this.naming = naming;
-        this.precision = precision;
-    }
-
-    public ColumnRef getName() {
-        return naming;
-    }
-
-    public int getType() {
-        return type;
-    }
-
-    public String getTypeName() {
-        return typeName;
-    }
+    final boolean isNullable;
 
-    public boolean isNullable() {
-        return isNullable;
-    }
+    /**
+     * Name of the column, optionally with an alias, in case of a query analysis.
+     */
+    final ColumnRef naming;
 
     /**
-     * Same as {@link ResultSetMetaData#getPrecision(int)}.
-     * @return 0 if unknown. For texts, maximum number of characters allowed. For numerics, max precision. For blobs,
-     * number of bytes allowed.
+     * Same as {@link ResultSetMetaData#getPrecision(int)}. It will be 0 if unknown. For texts, it represents maximum
+     * number of characters allowed. For numbers, its maximum precision. For blobs, a limit in allowed number of bytes.
      */
-    public int getPrecision() {
-        return precision;
-    }
+    final int precision;
 
     /**
-     * TODO: implement.
-     * Note : This method could be used not only for geometric fields, but also on numeric ones representing 1D
-     * systems.
-     *
-     * @return null for now, implementation needed.
+     * Optional. The table that contains this column. It could be null in case this column specification is done from
+     * query analysis.
      */
-    public Optional<CoordinateReferenceSystem> getCrs() {
-        return Optional.empty();
+    final TableReference origin;
+
+    SQLColumn(int type, String typeName, boolean isNullable, ColumnRef naming, int precision) {
+        this(type, typeName, isNullable, naming, precision, null);
+    }
+
+    SQLColumn(int type, String typeName, boolean isNullable, ColumnRef naming, int precision, TableReference origin) {
+        this.type = type;
+        this.typeName = typeName;
+        this.isNullable = isNullable;
+        this.naming = naming;
+        this.precision = precision;
+        this.origin = origin;
     }
 }
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 ca5ff05..01b59b8 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
@@ -16,6 +16,7 @@
  */
 package org.apache.sis.internal.sql.feature;
 
+import java.sql.Connection;
 import java.sql.DatabaseMetaData;
 import java.sql.ResultSet;
 import java.sql.SQLException;
@@ -60,7 +61,7 @@ class SpatialFunctions {
     /**
      * Creates a new accessor to geospatial functions for the database described by given metadata.
      */
-    SpatialFunctions(final DatabaseMetaData metadata) throws SQLException {
+    SpatialFunctions(final Connection c, final DatabaseMetaData metadata) throws SQLException {
         /*
          * Get information about whether byte are unsigned.
          * According JDBC specification, the rows shall be ordered by DATA_TYPE.
@@ -83,7 +84,7 @@ class SpatialFunctions {
         library = null;
 
         final Dialect dialect = Dialect.guess(metadata);
-        specificMapping = forDialect(dialect);
+        specificMapping = forDialect(dialect, c);
         defaultMapping = new ANSIMapping(isByteUnsigned);
     }
 
@@ -96,14 +97,13 @@ class SpatialFunctions {
      * <p>The default implementation handles the types declared in {@link Types} class.
      * Subclasses should handle the geometry types declared by spatial extensions.</p>
      *
-     * @param  sqlType      SQL type code as one of {@link java.sql.Types} constants.
-     * @param  sqlTypeName  data source dependent type name. For User Defined Type (UDT) the name is fully qualified.
+     * @param  columnDefinition Definition of source database column, including its SQL type and type name.
      * @return corresponding java type, or {@code null} if unknown.
      */
     @SuppressWarnings("fallthrough")
-    protected ColumnAdapter<?> toJavaType(final int sqlType, final String sqlTypeName) {
-        return specificMapping.flatMap(dialect -> dialect.getMapping(sqlType, sqlTypeName))
-                .orElseGet(() -> defaultMapping.getMappingImpl(sqlType, sqlTypeName));
+    protected ColumnAdapter<?> toJavaType(final SQLColumn columnDefinition) {
+        return specificMapping.flatMap(dialect -> dialect.getMapping(columnDefinition))
+                .orElseGet(() -> defaultMapping.getMappingImpl(columnDefinition));
     }
 
     /**
@@ -121,9 +121,9 @@ class SpatialFunctions {
         return null;
     }
 
-    static Optional<DialectMapping> forDialect(final Dialect dialect) {
+    static Optional<DialectMapping> forDialect(final Dialect dialect, Connection c) throws SQLException {
         switch (dialect) {
-            case POSTGRESQL: return Optional.of(new PostGISMapping());
+            case POSTGRESQL: return new PostGISMapping.Spi().create(c);
             default: return Optional.empty();
         }
     }
diff --git a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/StreamSQL.java b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/StreamSQL.java
index 7291a04..ff72c02 100644
--- a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/StreamSQL.java
+++ b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/StreamSQL.java
@@ -48,6 +48,7 @@ import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.util.collection.BackingStoreException;
 import org.apache.sis.util.logging.Logging;
 
+import static org.apache.sis.internal.sql.feature.Database.connectReadOnly;
 import static org.apache.sis.util.ArgumentChecks.ensureNonNull;
 import static org.apache.sis.util.ArgumentChecks.ensurePositive;
 
@@ -179,7 +180,7 @@ class StreamSQL extends StreamDecoration<Feature> {
             // If underlying connector does not support query estimation, we will fallback on brut-force counting.
             return super.count();
         }
-        try (Connection conn = QueryFeatureSet.connectReadOnly(source)) {
+        try (Connection conn = connectReadOnly(source)) {
             try (Statement st = conn.createStatement();
                  ResultSet rs = st.executeQuery(sql)) {
                 if (rs.next()) {
@@ -194,7 +195,7 @@ class StreamSQL extends StreamDecoration<Feature> {
     @Override
     protected synchronized Stream<Feature> createDecoratedStream() {
         final AtomicReference<Connection> connectionRef = new AtomicReference<>();
-        Stream<Feature> featureStream = Stream.of(uncheck(() -> QueryFeatureSet.connectReadOnly(source)))
+        Stream<Feature> featureStream = Stream.of(uncheck(() -> connectReadOnly(source)))
                 .map(Supplier::get)
                 .peek(connectionRef::set)
                 .flatMap(conn -> {
diff --git a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/Table.java b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/Table.java
index 7205f70..22c9aba 100644
--- a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/Table.java
+++ b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/Table.java
@@ -200,7 +200,7 @@ final class Table extends AbstractFeatureSet {
         this.hasGeometry      = specification.getPrimaryGeometryColumn().isPresent();
         this.attributes       = Collections.unmodifiableList(
                 specification.getColumns().stream()
-                        .map(SQLColumn::getName)
+                        .map(column -> column.naming)
                         .collect(Collectors.toList())
         );
     }


Mime
View raw message