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 <[email protected]> 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,
