sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ama...@apache.org
Subject [sis] 14/45: WIP(SQLStore): working on query feature set
Date Tue, 12 Nov 2019 16:44:41 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 848c1060cea12a8b8030f26c58d7e9067939af30
Author: Alexis Manin <amanin@apache.org>
AuthorDate: Wed Sep 25 13:17:03 2019 +0200

    WIP(SQLStore): working on query feature set
---
 .../apache/sis/internal/sql/feature/Analyzer.java  | 42 ++++------
 .../sis/internal/sql/feature/FeatureAdapter.java   | 70 ++++++++++++++--
 .../apache/sis/internal/sql/feature/Features.java  | 46 +---------
 .../sis/internal/sql/feature/QueryFeatureSet.java  | 33 +++-----
 .../sis/internal/sql/feature/SQLBiFunction.java    | 38 +++++++++
 .../sis/internal/sql/feature/SQLQueryBuilder.java  | 28 +++++++
 .../sis/internal/sql/feature/SpatialFunctions.java | 97 ++++++++++++++++++----
 .../org/apache/sis/internal/sql/feature/Table.java |  8 +-
 .../java/org/apache/sis/storage/sql/SQLStore.java  | 22 ++---
 .../org/apache/sis/storage/sql/SQLStoreTest.java   | 17 ++++
 10 files changed, 275 insertions(+), 126 deletions(-)

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 0fce8d2..4183659 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
@@ -21,24 +21,12 @@ import java.sql.PreparedStatement;
 import java.sql.ResultSet;
 import java.sql.ResultSetMetaData;
 import java.sql.SQLException;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Optional;
-import java.util.Set;
+import java.util.*;
 import java.util.logging.Level;
 import java.util.logging.LogRecord;
 import javax.sql.DataSource;
 
 import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
 import org.opengis.feature.PropertyType;
 import org.opengis.util.GenericName;
 import org.opengis.util.NameFactory;
@@ -51,6 +39,7 @@ import org.apache.sis.feature.builder.FeatureTypeBuilder;
 import org.apache.sis.internal.metadata.sql.Dialect;
 import org.apache.sis.internal.metadata.sql.Reflection;
 import org.apache.sis.internal.metadata.sql.SQLUtilities;
+import org.apache.sis.internal.sql.feature.FeatureAdapter.PropertyMapper;
 import org.apache.sis.internal.system.DefaultFactories;
 import org.apache.sis.storage.DataStore;
 import org.apache.sis.storage.DataStoreContentException;
@@ -343,14 +332,19 @@ final class Analyzer {
         return new QuerySpecification(target, sourceQuery, optName);
     }
 
-    public FeatureType buildFeatureType(final SQLTypeSpecification spec) throws SQLException
{
+    public FeatureAdapter buildAdapter(final SQLTypeSpecification spec) throws SQLException
{
         final FeatureTypeBuilder builder = new FeatureTypeBuilder(nameFactory, functions.library,
locale);
-        builder.setName(spec.getName());
+        builder.setName(spec.getName() == null ? Names.createGenericName("sis", ":", UUID.randomUUID().toString())
: spec.getName()); 
         builder.setDefinition(spec.getDefinition());
         final String geomCol = spec.getPrimaryGeometryColumn().orElse("");
         final List pkCols = spec.getPK().map(PrimaryKey::getColumns).orElse(Collections.EMPTY_LIST);
+        List<PropertyMapper> attributes = new ArrayList<>();
+        // JDBC column indices are 1 based.
+        int i = 0;
         for (SQLColumn col : spec.getColumns()) {
-            Class<?> type = functions.toJavaType(col.getType(), col.getTypeName());
+            i++;
+            final SpatialFunctions.ColumnAdapter<?> colAdapter = functions.toJavaType(col.getType(),
col.getTypeName());
+            Class<?> type = colAdapter.javaType;
             final String colName = col.getName().getColumnName();
             final String attrName = col.getName().getAttributeName();
             if (type == null) {
@@ -372,13 +366,14 @@ final class Analyzer {
             if (geomCol.equals(attrName)) attribute.addRole(AttributeRole.DEFAULT_GEOMETRY);
 
             if (pkCols.contains(colName)) attribute.addRole(AttributeRole.IDENTIFIER_COMPONENT);
+            attributes.add(new PropertyMapper(attrName, i, colAdapter));
         }
 
         addImports(spec, builder);
 
         addExports(spec, builder);
 
-        return builder.build();
+        return new FeatureAdapter(builder.build(), attributes);
     }
 
     private void addExports(SQLTypeSpecification spec, FeatureTypeBuilder builder) throws
SQLException {
@@ -564,7 +559,6 @@ final class Analyzer {
 
     private class QuerySpecification implements SQLTypeSpecification {
 
-        int idx = 0;
         final int total;
         final PreparedStatement source;
         private final ResultSetMetaData meta;
@@ -581,13 +575,13 @@ final class Analyzer {
             name = optName;
 
             final ArrayList<SQLColumn> tmpCols = new ArrayList<>(total);
-            for (int i = 0 ; i < total ; i++) {
+            for (int i = 1 ; i <= total ; i++) {
                 tmpCols.add(new SQLColumn(
-                        meta.getColumnType(idx),
-                        meta.getColumnTypeName(idx),
-                        meta.isNullable(idx) == ResultSetMetaData.columnNullable,
-                        new ColumnRef(meta.getColumnName(idx)).as(meta.getColumnLabel(idx)),
-                        meta.getPrecision(idx)
+                        meta.getColumnType(i),
+                        meta.getColumnTypeName(i),
+                        meta.isNullable(i) == ResultSetMetaData.columnNullable,
+                        new ColumnRef(meta.getColumnName(i)).as(meta.getColumnLabel(i)),
+                        meta.getPrecision(i)
                 ));
             }
 
diff --git a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/FeatureAdapter.java
b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/FeatureAdapter.java
index 4fde073..0162e67 100644
--- a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/FeatureAdapter.java
+++ b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/FeatureAdapter.java
@@ -1,22 +1,78 @@
 package org.apache.sis.internal.sql.feature;
 
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 
+import org.opengis.feature.Feature;
 import org.opengis.feature.FeatureType;
 
-public class FeatureAdapter {
+import org.apache.sis.internal.sql.feature.SpatialFunctions.ColumnAdapter;
 
-    private final FeatureType type;
+import static org.apache.sis.util.ArgumentChecks.ensureNonNull;
 
-    public FeatureAdapter(FeatureType type) {
+class FeatureAdapter {
+
+    final FeatureType type;
+
+    private final List<PropertyMapper> attributeMappers;
+
+    FeatureAdapter(FeatureType type, List<PropertyMapper> attributeMappers) {
+        ensureNonNull("Target feature type", type);
+        ensureNonNull("Attribute mappers", attributeMappers);
         this.type = type;
+        this.attributeMappers = Collections.unmodifiableList(new ArrayList<>(attributeMappers));
+    }
+
+    Feature read(final ResultSet cursor) throws SQLException {
+        final Feature result = readAttributes(cursor);
+        addImports(result, cursor);
+        addExports(result);
+        return result;
+    }
+
+    private void addImports(final Feature target, final ResultSet cursor) {
+        // TODO: see Features class
     }
 
-    public FeatureType getType() {
-        return type;
+    private void addExports(final Feature target) {
+        // TODO: see Features class
     }
 
-    public List<Features.SQLFunction<ResultContext.Cell, ?>> getProperties()
{
-        throw new UnsupportedOperationException("");
+    private Feature readAttributes(final ResultSet cursor) throws SQLException {
+        final Feature result = type.newInstance();
+        for (PropertyMapper mapper : attributeMappers) mapper.read(cursor, result);
+        return result;
+    }
+
+    List<Feature> prefetch(final int size, final ResultSet cursor) throws SQLException
{
+        // TODO: optimize by resolving import associations by  batch import fetching.
+        final ArrayList<Feature> features = new ArrayList<>(size);
+        for (int i = 0 ; i < size && cursor.next() ; i++) {
+            features.add(read(cursor));
+        }
+
+        return features;
+    }
+
+    static class PropertyMapper {
+        // TODO: by using a indexed implementation of Feature, we could avoid the name mapping.
However, a JMH benchmark
+        // would be required in order to be sure it's impacting performance positively.
+        final String propertyName;
+        final int columnIndex;
+        final ColumnAdapter fetchValue;
+
+        PropertyMapper(String propertyName, int columnIndex, ColumnAdapter fetchValue) {
+            this.propertyName = propertyName;
+            this.columnIndex = columnIndex;
+            this.fetchValue = fetchValue;
+        }
+
+        private void read(ResultSet cursor, Feature target) throws SQLException {
+            final Object value = fetchValue.apply(cursor, columnIndex);
+            if (value != null) target.setPropertyValue(propertyName, value);
+        }
     }
 }
diff --git a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/Features.java
b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/Features.java
index d2d623b..c057ddf 100644
--- a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/Features.java
+++ b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/Features.java
@@ -533,38 +533,6 @@ final class Features implements Spliterator<Feature> {
         }
     }
 
-    /**
-     * Useful to customiez value retrieval on result sets. Example:
-     * {@code
-     * SQLBiFunction<ResultSet, Integer, Integer> get = ResultSet::getInt;
-     * }
-     * @param <T>
-     * @param <U>
-     * @param <R>
-     */
-    @FunctionalInterface
-    interface SQLBiFunction<T, U, R> {
-        R apply(T t, U u) throws SQLException;
-
-        /**
-         * Returns a composed function that first applies this function to
-         * its input, and then applies the {@code after} function to the result.
-         * If evaluation of either function throws an exception, it is relayed to
-         * the caller of the composed function.
-         *
-         * @param <V> the type of output of the {@code after} function, and of the
-         *           composed function
-         * @param after the function to apply after this function is applied
-         * @return a composed function that first applies this function and then
-         * applies the {@code after} function
-         * @throws NullPointerException if after is null
-         */
-        default <V> SQLBiFunction<T, U, V> andThen(Function<? super R, ? extends
V> after) {
-            ensureNonNull("After function", after);
-            return (T t, U u) -> after.apply(apply(t, u));
-        }
-    }
-
     @FunctionalInterface
     interface SQLFunction<T, R> {
         R apply(T t) throws SQLException;
@@ -600,14 +568,6 @@ final class Features implements Spliterator<Feature> {
             this.parent = parent;
         }
 
-        Connector distinct(ColumnRef... columns) {
-            return select(true, columns);
-        }
-
-        Connector select(boolean distinct, ColumnRef... columns) {
-            return new TableConnector(this, distinct, columns);
-        }
-
         Builder where(final Filter filter) {
             throw new UnsupportedOperationException("TODO");
         }
@@ -638,7 +598,7 @@ final class Features implements Spliterator<Feature> {
 
         @Override
         public Connector select(ColumnRef... columns) {
-            throw new UnsupportedOperationException("Not supported yet"); // "Alexis Manin
(Geomatys)" on 24/09/2019
+            return new TableConnector(this, columns);
         }
     }
 
@@ -650,9 +610,9 @@ final class Features implements Spliterator<Feature> {
 
         final SortBy[] sort;
 
-        TableConnector(Builder source, boolean distinct, ColumnRef[] columns) {
+        TableConnector(Builder source, ColumnRef[] columns) {
             this.source = source;
-            this.distinct = distinct;
+            this.distinct = source.distinct;
             this.columns = columns;
             this.sort = source.sort == null ? null : Arrays.copyOf(source.sort, source.sort.length);
         }
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 0607d3a..8441341 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
@@ -4,7 +4,6 @@ import java.sql.Connection;
 import java.sql.PreparedStatement;
 import java.sql.ResultSet;
 import java.sql.SQLException;
-import java.util.List;
 import java.util.Spliterator;
 import java.util.function.Consumer;
 import java.util.stream.Stream;
@@ -26,26 +25,23 @@ public class QueryFeatureSet extends AbstractFeatureSet {
      */
     private final SQLBuilder queryBuilder;
 
-    private final Analyzer analyzer;
-
     private final DataSource source;
-    private final FeatureType resultType;
 
-    private final FeatureAdapter adapter = null;
+    private final FeatureAdapter adapter;
+
+    public QueryFeatureSet(SQLBuilder queryBuilder, DataSource source, Connection conn) throws
SQLException {
+        this(queryBuilder, new Analyzer(source, conn.getMetaData(), null, null), source,
conn);
+    }
 
-    public QueryFeatureSet(SQLBuilder queryBuilder, Analyzer analyzer, DataSource source)
throws DataStoreException {
+    public QueryFeatureSet(SQLBuilder queryBuilder, Analyzer analyzer, DataSource source,
Connection conn) throws SQLException {
         super(analyzer.listeners);
         this.queryBuilder = queryBuilder;
-        this.analyzer = analyzer;
         this.source = source;
 
-        try (Connection conn = connectReadOnly(source)) {
-            final String sql = queryBuilder.toString();
-            final PreparedStatement statement = conn.prepareStatement(sql);
+        final String sql = queryBuilder.toString();
+        try (PreparedStatement statement = conn.prepareStatement(sql)) {
             final SQLTypeSpecification spec = analyzer.create(statement, sql, null);
-            resultType = analyzer.buildFeatureType(spec); // TODO: allow user to give a name
?
-        } catch (SQLException e) {
-            throw new DataStoreException("Cannot analyze query metadata (feature type determination)",
e);
+            adapter = analyzer.buildAdapter(spec);
         }
     }
 
@@ -79,7 +75,7 @@ public class QueryFeatureSet extends AbstractFeatureSet {
 
     @Override
     public FeatureType getType() {
-        return resultType;
+        return adapter.type;
     }
 
     @Override
@@ -153,7 +149,6 @@ public class QueryFeatureSet extends AbstractFeatureSet {
     private class ResultSpliterator implements Spliterator<Feature> {
 
         final ResultContext result;
-        FeatureAdapter adapter;
 
         private ResultSpliterator(ResultSet result) {
             this.result = new ResultContext(result);
@@ -163,12 +158,8 @@ public class QueryFeatureSet extends AbstractFeatureSet {
         public boolean tryAdvance(Consumer<? super Feature> action) {
             try {
                 if (result.source.next()) {
-                    final Feature f = adapter.getType().newInstance();
-                    final List<Features.SQLFunction<ResultContext.Cell, ?>> properties
= adapter.getProperties();
-                    for (int i = 0; i < properties.size() ; i++) {
-                        final Object value = properties.get(i).apply(result.cell(i, null));
-                        if (value != null) f.setPropertyValue(null, value);
-                    }
+                    final Feature f = adapter.read(result.source);
+                    action.accept(f);
                     return true;
                 } else return false;
             } catch (SQLException e) {
diff --git a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/SQLBiFunction.java
b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/SQLBiFunction.java
new file mode 100644
index 0000000..82414a7
--- /dev/null
+++ b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/SQLBiFunction.java
@@ -0,0 +1,38 @@
+package org.apache.sis.internal.sql.feature;
+
+import java.sql.SQLException;
+import java.util.function.Function;
+
+import static org.apache.sis.util.ArgumentChecks.ensureNonNull;
+
+/**
+ * Useful to customize value retrieval on result sets. Example:
+ * {@code
+ * SQLBiFunction<ResultSet, Integer, Integer> get = ResultSet::getInt;
+ * }
+ * @param <T> Type of the first arguement of the function.
+ * @param <U> Type of the second argument of the function.
+ * @param <R> Type of the function result.
+ */
+@FunctionalInterface
+interface SQLBiFunction<T, U, R> {
+    R apply(T t, U u) throws SQLException;
+
+    /**
+     * Returns a composed function that first applies this function to
+     * its input, and then applies the {@code after} function to the result.
+     * If evaluation of either function throws an exception, it is relayed to
+     * the caller of the composed function.
+     *
+     * @param <V> the type of output of the {@code after} function, and of the
+     *           composed function
+     * @param after the function to apply after this function is applied
+     * @return a composed function that first applies this function and then
+     * applies the {@code after} function
+     * @throws NullPointerException if after is null
+     */
+    default <V> SQLBiFunction<T, U, V> andThen(Function<? super R, ? extends
V> after) {
+        ensureNonNull("After function", after);
+        return (T t, U u) -> after.apply(apply(t, u));
+    }
+}
diff --git a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/SQLQueryBuilder.java
b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/SQLQueryBuilder.java
new file mode 100644
index 0000000..55337d7
--- /dev/null
+++ b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/SQLQueryBuilder.java
@@ -0,0 +1,28 @@
+package org.apache.sis.internal.sql.feature;
+
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.SQLException;
+import javax.sql.DataSource;
+
+import org.apache.sis.internal.metadata.sql.SQLBuilder;
+import org.apache.sis.storage.DataStoreException;
+import org.apache.sis.storage.FeatureSet;
+
+public class SQLQueryBuilder extends SQLBuilder {
+
+    final DataSource source;
+
+    public SQLQueryBuilder(DataSource source, final DatabaseMetaData metadata, final boolean
quoteSchema) throws SQLException {
+        super(metadata, quoteSchema);
+        this.source = source;
+    }
+
+    public FeatureSet build(final Connection connection) throws SQLException, DataStoreException
{
+        final Analyzer analyzer = new Analyzer(source, connection.getMetaData(), null, null);
+        // TODO: defensive copy of this builder.
+        return new QueryFeatureSet(this, analyzer, source, connection);
+    }
+
+
+}
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 d01dbdf..2975d95 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
@@ -24,14 +24,20 @@ import java.sql.SQLException;
 import java.sql.Time;
 import java.sql.Timestamp;
 import java.sql.Types;
+import java.time.Instant;
+import java.time.LocalTime;
 import java.time.OffsetDateTime;
 import java.time.OffsetTime;
+import java.time.ZoneOffset;
+import java.util.function.Function;
 
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 
 import org.apache.sis.internal.metadata.sql.Reflection;
 import org.apache.sis.setup.GeometryLibrary;
 
+import static org.apache.sis.util.ArgumentChecks.ensureNonNull;
+
 
 /**
  * Access to functions provided by geospatial databases.
@@ -98,37 +104,61 @@ class SpatialFunctions {
      * @return corresponding java type, or {@code null} if unknown.
      */
     @SuppressWarnings("fallthrough")
-    protected Class<?> toJavaType(final int sqlType, final String sqlTypeName) {
+    protected ColumnAdapter<?> toJavaType(final int sqlType, final String sqlTypeName)
{
         switch (sqlType) {
             case Types.BIT:
-            case Types.BOOLEAN:                 return Boolean.class;
-            case Types.TINYINT:                 if (!isByteUnsigned) return Byte.class; 
       // else fallthrough.
-            case Types.SMALLINT:                return Short.class;
-            case Types.INTEGER:                 return Integer.class;
-            case Types.BIGINT:                  return Long.class;
-            case Types.REAL:                    return Float.class;
+            case Types.BOOLEAN:                 return forceCast(Boolean.class);
+            case Types.TINYINT:                 if (!isByteUnsigned) return forceCast(Byte.class);
 // else fallthrough.
+            case Types.SMALLINT:                return forceCast(Short.class);
+            case Types.INTEGER:                 return forceCast(Integer.class);
+            case Types.BIGINT:                  return forceCast(Long.class);
+            case Types.REAL:                    return forceCast(Float.class);
             case Types.FLOAT:                   // Despite the name, this is implemented
as DOUBLE in major databases.
-            case Types.DOUBLE:                  return Double.class;
+            case Types.DOUBLE:                  return forceCast(Double.class);
             case Types.NUMERIC:                 // Similar to DECIMAL except that it uses
exactly the specified precision.
-            case Types.DECIMAL:                 return BigDecimal.class;
+            case Types.DECIMAL:                 return forceCast(BigDecimal.class);
             case Types.CHAR:
             case Types.VARCHAR:
-            case Types.LONGVARCHAR:             return String.class;
-            case Types.DATE:                    return Date.class;
-            case Types.TIME:                    return Time.class;
-            case Types.TIMESTAMP:               return Timestamp.class;
-            case Types.TIME_WITH_TIMEZONE:      return OffsetTime.class;
-            case Types.TIMESTAMP_WITH_TIMEZONE: return OffsetDateTime.class;
+            case Types.LONGVARCHAR:             return new ColumnAdapter<>(String.class,
ResultSet::getString);
+            case Types.DATE:                    return new ColumnAdapter<>(Date.class,
ResultSet::getDate);
+            case Types.TIME:                    return new ColumnAdapter<>(LocalTime.class,
SpatialFunctions::toLocalTime);
+            case Types.TIMESTAMP:               return new ColumnAdapter<>(Instant.class,
SpatialFunctions::toInstant);
+            case Types.TIME_WITH_TIMEZONE:      return new ColumnAdapter<>(OffsetTime.class,
SpatialFunctions::toOffsetTime);
+            case Types.TIMESTAMP_WITH_TIMEZONE: return new ColumnAdapter<>(OffsetDateTime.class,
SpatialFunctions::toODT);
             case Types.BINARY:
             case Types.VARBINARY:
-            case Types.LONGVARBINARY:           return byte[].class;
-            case Types.ARRAY:                   return Object[].class;
+            case Types.LONGVARBINARY:           return new ColumnAdapter<>(byte[].class,
ResultSet::getBytes);
+            case Types.ARRAY:                   return forceCast(Object[].class);
             case Types.OTHER:                   // Database-specific accessed via getObject
and setObject.
-            case Types.JAVA_OBJECT:             return Object.class;
+            case Types.JAVA_OBJECT:             return new ColumnAdapter<>(Object.class,
ResultSet::getObject);
             default:                            return null;
         }
     }
 
+    private static LocalTime toLocalTime(ResultSet source, int columnIndex) throws SQLException
{
+        final Time time = source.getTime(columnIndex);
+        return time == null ? null : time.toLocalTime();
+    }
+
+    private static Instant toInstant(ResultSet source, int columnIndex) throws SQLException
{
+        final Timestamp t = source.getTimestamp(columnIndex);
+        return t == null ? null : t.toInstant();
+    }
+
+    private static OffsetDateTime toODT(ResultSet source, int columnIndex) throws SQLException
{
+        final Timestamp t = source.getTimestamp(columnIndex);
+        final int offsetMinute = t.getTimezoneOffset();
+        return t == null ? null : t.toInstant()
+                .atOffset(ZoneOffset.ofHoursMinutes(offsetMinute / 60, offsetMinute % 60));
+    }
+
+    private static OffsetTime toOffsetTime(ResultSet source, int columnIndex) throws SQLException
{
+        final Time t = source.getTime(columnIndex);
+        final int offsetMinute = t.getTimezoneOffset();
+        return t == null ? null : t.toLocalTime()
+                .atOffset(ZoneOffset.ofHoursMinutes(offsetMinute / 60, offsetMinute % 60));
+    }
+
     /**
      * Creates the Coordinate Reference System associated to the the geometry SRID of a given
column.
      * The {@code reflect} argument is the result of a call to {@link DatabaseMetaData#getColumns
@@ -143,4 +173,35 @@ class SpatialFunctions {
     protected CoordinateReferenceSystem createGeometryCRS(ResultSet reflect) throws SQLException
{
         return null;
     }
+
+    private static <T> ColumnAdapter<T> forceCast(final Class<T> targetType)
{
+        return new ColumnAdapter<>(targetType, (r, i) -> forceCast(targetType, r,
i));
+    }
+
+    private static <T> T forceCast(final Class<T> targetType, ResultSet source,
final Integer columnIndex) throws SQLException {
+        final Object value = source.getObject(columnIndex);
+        return value == null ? null : targetType.cast(value);
+    }
+
+    protected static class ColumnAdapter<T> implements SQLBiFunction<ResultSet,
Integer, T> {
+        final Class<T> javaType;
+        private final SQLBiFunction<ResultSet, Integer, T> fetchValue;
+
+        protected ColumnAdapter(Class<T> javaType, SQLBiFunction<ResultSet, Integer,
T> fetchValue) {
+            ensureNonNull("Result java type", javaType);
+            ensureNonNull("Function for value retrieval", fetchValue);
+            this.javaType = javaType;
+            this.fetchValue = fetchValue;
+        }
+
+        @Override
+        public T apply(ResultSet resultSet, Integer integer) throws SQLException {
+            return fetchValue.apply(resultSet, integer);
+        }
+
+        @Override
+        public <V> SQLBiFunction<ResultSet, Integer, V> andThen(Function<?
super T, ? extends V> after) {
+            return fetchValue.andThen(after);
+        }
+    }
 }
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 aacf9c9..27c96bc 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
@@ -25,7 +25,6 @@ import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
-import java.util.concurrent.atomic.AtomicReference;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 import javax.sql.DataSource;
@@ -143,6 +142,8 @@ final class Table extends AbstractFeatureSet {
      */
     private final SQLBuilder sqlTemplate;
 
+    private final FeatureAdapter adapter;
+
     /**
      * Creates a description of the table of the given name.
      * The table is identified by {@code id}, which contains a (catalog, schema, name) tuple.
@@ -188,12 +189,13 @@ final class Table extends AbstractFeatureSet {
                 .map(PrimaryKey::getColumns)
                 .orElse(Collections.EMPTY_LIST)
                 .toArray(new String[0]);
-        this.featureType      = analyzer.buildFeatureType(specification);
+        this.adapter          = analyzer.buildAdapter(specification);
+        this.featureType      = adapter.type;
         this.importedKeys     = toArray(specification.getImports());
         this.exportedKeys     = toArray(specification.getExports());
         this.primaryKeyClass  = primaryKeys.length < 2? Object.class : Object[].class;
         this.hasGeometry      = specification.getPrimaryGeometryColumn().isPresent();
-        this.attributes = Collections.unmodifiableList(
+        this.attributes       = Collections.unmodifiableList(
                 specification.getColumns().stream()
                         .map(SQLColumn::getName)
                         .collect(Collectors.toList())
diff --git a/storage/sis-sqlstore/src/main/java/org/apache/sis/storage/sql/SQLStore.java b/storage/sis-sqlstore/src/main/java/org/apache/sis/storage/sql/SQLStore.java
index b7b1061..f4c20ca 100644
--- a/storage/sis-sqlstore/src/main/java/org/apache/sis/storage/sql/SQLStore.java
+++ b/storage/sis-sqlstore/src/main/java/org/apache/sis/storage/sql/SQLStore.java
@@ -16,27 +16,29 @@
  */
 package org.apache.sis.storage.sql;
 
-import java.util.Optional;
-import java.util.Collection;
-import javax.sql.DataSource;
+import java.lang.reflect.Method;
 import java.sql.Connection;
 import java.sql.SQLException;
-import java.lang.reflect.Method;
-import org.opengis.util.GenericName;
+import java.util.Collection;
+import java.util.Optional;
+import javax.sql.DataSource;
+
 import org.opengis.metadata.Metadata;
-import org.opengis.parameter.ParameterValueGroup;
 import org.opengis.metadata.spatial.SpatialRepresentationType;
-import org.apache.sis.storage.Resource;
+import org.opengis.parameter.ParameterValueGroup;
+import org.opengis.util.GenericName;
+
+import org.apache.sis.internal.sql.feature.Database;
+import org.apache.sis.internal.sql.feature.Resources;
+import org.apache.sis.internal.storage.MetadataBuilder;
 import org.apache.sis.storage.Aggregate;
 import org.apache.sis.storage.DataStore;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.IllegalNameException;
+import org.apache.sis.storage.Resource;
 import org.apache.sis.storage.StorageConnector;
 import org.apache.sis.storage.event.ChangeEvent;
 import org.apache.sis.storage.event.ChangeListener;
-import org.apache.sis.internal.sql.feature.Database;
-import org.apache.sis.internal.sql.feature.Resources;
-import org.apache.sis.internal.storage.MetadataBuilder;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.Exceptions;
 
diff --git a/storage/sis-sqlstore/src/test/java/org/apache/sis/storage/sql/SQLStoreTest.java
b/storage/sis-sqlstore/src/test/java/org/apache/sis/storage/sql/SQLStoreTest.java
index ff83c46..ef5d769 100644
--- a/storage/sis-sqlstore/src/test/java/org/apache/sis/storage/sql/SQLStoreTest.java
+++ b/storage/sis-sqlstore/src/test/java/org/apache/sis/storage/sql/SQLStoreTest.java
@@ -16,6 +16,7 @@
  */
 package org.apache.sis.storage.sql;
 
+import java.sql.Connection;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.HashMap;
@@ -30,6 +31,8 @@ import org.opengis.feature.FeatureAssociationRole;
 import org.opengis.feature.FeatureType;
 import org.opengis.feature.PropertyType;
 
+import org.apache.sis.internal.metadata.sql.SQLBuilder;
+import org.apache.sis.internal.sql.feature.QueryFeatureSet;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.FeatureSet;
 import org.apache.sis.storage.StorageConnector;
@@ -152,6 +155,7 @@ public final strictfp class SQLStoreTest extends TestCase {
                 // Now, we'll check that overloaded stream operations are functionally stable,
even stacked.
                 verifyStreamOperations(cities);
 
+                verifyQueries(tmp);
             }
         }
         assertEquals(Integer.valueOf(2), countryCount.remove("CAN"));
@@ -160,6 +164,19 @@ public final strictfp class SQLStoreTest extends TestCase {
         assertTrue  (countryCount.isEmpty());
     }
 
+    private void verifyQueries(TestDatabase source) throws Exception {
+        final QueryFeatureSet qfs;
+        try (Connection conn = source.source.getConnection()) {
+            final SQLBuilder builder = new SQLBuilder(conn.getMetaData(), false)
+                    .append("SELECT * FROM ").appendIdentifier("features", "Parks");
+            qfs = new QueryFeatureSet(builder, source.source, conn);
+        }
+
+        final FeatureType type = qfs.getType();
+        System.out.println(type);
+        qfs.features(false).forEach(System.out::println);
+    }
+
     /**
      * Checks that operations stacked on feature stream are well executed. This test focus
on mapping and peeking
      * actions overloaded by sql streams. We'd like to test skip and limit operations too,
but ignore it for now,


Mime
View raw message