This is an automated email from the ASF dual-hosted git repository. desruisseaux pushed a commit to branch geoapi-4.0 in repository https://gitbox.apache.org/repos/asf/sis.git
commit c5263872c941d2735894064cf73ef9031d223f73 Author: Martin Desruisseaux <martin.desruisse...@geomatys.com> AuthorDate: Tue Mar 15 20:03:01 2022 +0100 Make SQLStore a little bit more robust to NullPointerException when the geometry type can not be determined. Add support for arrays, which are implemented as multi-occurrence attribute values in features. Unwrap the value of `org.postgresql.util.PGobject`. --- .../apache/sis/internal/feature/GeometryType.java | 2 +- ide-project/NetBeans/nbproject/project.properties | 2 +- storage/sis-sqlstore/pom.xml | 2 +- .../apache/sis/internal/sql/feature/Analyzer.java | 2 +- .../apache/sis/internal/sql/feature/Column.java | 73 ++++++++++++++----- .../apache/sis/internal/sql/feature/Database.java | 53 ++++++++++++-- .../sis/internal/sql/feature/InfoStatements.java | 13 ++++ .../sis/internal/sql/feature/ValueGetter.java | 81 +++++++++++++++++++++- .../sis/internal/sql/postgis/ExtentEstimator.java | 4 +- .../sis/internal/sql/postgis/ObjectGetter.java | 76 ++++++++++++++++++++ .../apache/sis/internal/sql/postgis/Postgres.java | 24 ++++++- 11 files changed, 301 insertions(+), 31 deletions(-) diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/GeometryType.java b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/GeometryType.java index a326c43..dbc7746 100644 --- a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/GeometryType.java +++ b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/GeometryType.java @@ -145,7 +145,7 @@ public enum GeometryType { * Types for geometries having <var>Z</var> and <var>M</var> are replaced by 2D types. * * @param type WKB geometry type. - * @return enumeration value for the given type, or {@code null}. + * @return enumeration value for the given type, or {@code null} if the given type is not recognized. * * @see #binaryType() */ diff --git a/ide-project/NetBeans/nbproject/project.properties b/ide-project/NetBeans/nbproject/project.properties index bc3ac69..7a573de 100644 --- a/ide-project/NetBeans/nbproject/project.properties +++ b/ide-project/NetBeans/nbproject/project.properties @@ -140,6 +140,7 @@ javac.classpath=\ ${maven.repository}/com/esri/geometry/esri-geometry-api/${esri.api.version}/esri-geometry-api-${esri.api.version}.jar:\ ${maven.repository}/org/locationtech/jts/jts-core/${jts.version}/jts-core-${jts.version}.jar:\ ${maven.repository}/javax/javaee-api/${jee.version}/javaee-api-${jee.version}.jar:\ + ${maven.repository}/org/postgresql/postgresql/${postgresql.version}/postgresql-${postgresql.version}.jar:\ ${maven.repository}/edu/ucar/cdm-core/${netcdf.version}/cdm-core-${netcdf.version}.jar:\ ${maven.repository}/edu/ucar/udunits/${netcdf.version}/udunits-${netcdf.version}.jar:\ ${maven.repository}/com/google/guava/guava/${guava.version}/guava-${guava.version}.jar:\ @@ -149,7 +150,6 @@ javac.processorpath=\ javac.test.classpath=\ ${javac.classpath}:\ ${maven.repository}/org/apache/derby/derby/${derby.version}/derby-${derby.version}.jar:\ - ${maven.repository}/org/postgresql/postgresql/${postgresql.version}/postgresql-${postgresql.version}.jar:\ ${maven.repository}/org/hsqldb/hsqldb/${hsqldb.version}/hsqldb-${hsqldb.version}.jar:\ ${maven.repository}/com/h2database/h2/${h2.version}/h2-${h2.version}.jar:\ ${maven.repository}/gov/nist/math/jama/${jama.version}/jama-${jama.version}.jar:\ diff --git a/storage/sis-sqlstore/pom.xml b/storage/sis-sqlstore/pom.xml index 3053c18..cf1f6f3 100644 --- a/storage/sis-sqlstore/pom.xml +++ b/storage/sis-sqlstore/pom.xml @@ -142,7 +142,7 @@ <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> - <scope>test</scope> + <scope>provided</scope> </dependency> <dependency> <groupId>org.locationtech.jts</groupId> 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 2a288ea..9e84bed 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 @@ -272,7 +272,7 @@ final class Analyzer { final ValueGetter<?> setValueGetter(final Column column) { ValueGetter<?> getter = database.getMapping(column); if (getter == null) { - getter = ValueGetter.AsObject.INSTANCE; + getter = database.getDefaultMapping(); warning(Resources.Keys.UnknownType_1, column.typeName); } column.valueGetter = getter; diff --git a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/Column.java b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/Column.java index 1ec8fbe..b2dbd83 100644 --- a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/Column.java +++ b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/Column.java @@ -21,6 +21,8 @@ import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.DatabaseMetaData; import java.sql.SQLException; +import java.sql.SQLDataException; +import java.util.Optional; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.apache.sis.internal.metadata.sql.Reflection; import org.apache.sis.internal.metadata.sql.SQLUtilities; @@ -45,7 +47,7 @@ import org.apache.sis.util.Localized; * * @author Alexis Manin (Geomatys) * @author Martin Desruisseaux (Geomatys) - * @version 1.1 + * @version 1.2 * * @see ResultSet#getMetaData() * @see DatabaseMetaData#getColumns(String, String, String, String) @@ -81,8 +83,9 @@ public final class Column { public final int type; /** - * A name for the value type, free-text from the database engine. For more information about this, please see + * A name for the value type, free-text from the database engine. For more information about this, see * {@link DatabaseMetaData#getColumns(String, String, String, String)} and {@link Reflection#TYPE_NAME}. + * This value shall not be null. * * @see Reflection#TYPE_NAME */ @@ -127,6 +130,21 @@ public final class Column { ValueGetter<?> valueGetter; /** + * Creates a synthetic column (a column not inferred from database analysis) + * for describing the type of elements in an array. + * + * @param type SQL type of the column. + * @param typeName SQL name of the type. + */ + Column(final int type, final String typeName) { + this.name = label = propertyName = "element"; + this.type = type; + this.typeName = typeName; + this.precision = 0; + this.isNullable = false; + } + + /** * Creates a new column from database metadata. * Information are fetched from current {@code ResultSet} row. * This method does not change cursor position. @@ -171,9 +189,16 @@ public final class Column { * PostgreSQL JDBC drivers sometime gives the fully qualified type name. * For example we sometime get {@code "public"."geometry"} (including the quotes) * instead of a plain {@code geometry}. If this is the case, keep only the local part. + * + * @param type value found in the {@value Reflection.TYPE_NAME} column. + * @param quote value of {@code DatabaseMetaData.getIdentifierQuoteString()}. + * @return local part of the type name. */ - private static String localPart(String type, final String quote) { - if (type != null && quote != null) { + private static String localPart(String type, final String quote) throws SQLDataException { + if (type == null) { + throw new SQLDataException(Errors.format(Errors.Keys.MissingValueInColumn_1, Reflection.TYPE_NAME)); + } + if (quote != null) { int end = type.lastIndexOf(quote); if (end >= 0) { int start = type.lastIndexOf(quote, end - 1); @@ -214,24 +239,24 @@ public final class Column { /** * If this column is a geometry column, returns the type of the geometry objects. - * Otherwise returns {@code null} (including the case where this is a raster column). + * Otherwise returns empty (including the case where this is a raster column). * Note that if this column is a geometry column but the geometry type was not defined, * then {@link GeometryType#GEOMETRY} is returned as a fallback. * - * @return type of geometry objects, or {@code null} if this column is not a geometry column. + * @return type of geometry objects, or empty if this column is not a geometry column. */ - public final GeometryType getGeometryType() { - return geometryType; + public final Optional<GeometryType> getGeometryType() { + return Optional.ofNullable(geometryType); } /** * If this column is a geometry or raster column, returns the default coordinate reference system. - * Otherwise returns {@code null}. The CRS may also be null even for a geometry column if it is unspecified. + * Otherwise returns empty. The CRS may also be empty even for a geometry column if it is unspecified. * - * @return CRS of geometries or rasters in this column, or {@code null} if unknown or not applicable. + * @return CRS of geometries or rasters in this column, or empty if unknown or not applicable. */ - public final CoordinateReferenceSystem getDefaultCRS() { - return defaultCRS; + public final Optional<CoordinateReferenceSystem> getDefaultCRS() { + return Optional.ofNullable(defaultCRS); } /** @@ -242,15 +267,29 @@ public final class Column { * @return builder for the added feature attribute. */ final AttributeTypeBuilder<?> createAttribute(final FeatureTypeBuilder feature) { - final Class<?> type = valueGetter.valueType; - final AttributeTypeBuilder<?> attribute = feature.addAttribute(type).setName(propertyName); - if (precision > 0 && CharSequence.class.isAssignableFrom(type)) { + Class<?> valueType = valueGetter.valueType; + final boolean isArray = (valueGetter instanceof ValueGetter.AsArray); + if (isArray) { + valueType = ((ValueGetter.AsArray) valueGetter).cmget.valueType; + } + final AttributeTypeBuilder<?> attribute = feature.addAttribute(valueType).setName(propertyName); + if (precision > 0 && precision != Integer.MAX_VALUE && CharSequence.class.isAssignableFrom(valueType)) { attribute.setMaximalLength(precision); } - if (isNullable) { + if (isArray) { + /* + * We have no standard API yet for determining the minimal and maximal array length. + * The PostgreSQL driver seems to use the `precision` field, but it may be specific + * to that driver and seems to be always `MAX_VALUE` anyway. + */ attribute.setMinimumOccurs(0); + attribute.setMaximumOccurs(Integer.MAX_VALUE); + } else if (isNullable) { + attribute.setMinimumOccurs(0); + } + if (geometryType != null || defaultCRS != null) { + attribute.setCRS(defaultCRS); } - attribute.setCRS(defaultCRS); return attribute; } 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 d00ff70..3abf514 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 @@ -27,6 +27,7 @@ import java.util.ArrayList; import java.util.Locale; import java.util.logging.LogRecord; import java.util.AbstractMap.SimpleImmutableEntry; +import java.sql.Array; import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.ResultSet; @@ -571,9 +572,8 @@ public class Database<G> extends Syntax { case Types.TIME_WITH_TIMEZONE: return ValueGetter.AsOffsetTime.INSTANCE; case Types.TIMESTAMP_WITH_TIMEZONE: return ValueGetter.AsOffsetDateTime.INSTANCE; case Types.BLOB: return ValueGetter.AsBytes.INSTANCE; - case Types.ARRAY: // TODO case Types.OTHER: - case Types.JAVA_OBJECT: return ValueGetter.AsObject.INSTANCE; + case Types.JAVA_OBJECT: return getDefaultMapping(); case Types.BINARY: case Types.VARBINARY: case Types.LONGVARBINARY: { @@ -584,11 +584,48 @@ public class Database<G> extends Syntax { default: throw new AssertionError(encoding); } } + case Types.ARRAY: { + final int componentType = getArrayComponentType(columnDefinition); + final ValueGetter<?> component = getMapping(new Column(componentType, columnDefinition.typeName)); + if (component == ValueGetter.AsObject.INSTANCE) { + return ValueGetter.AsArray.INSTANCE; + } + return new ValueGetter.AsArray(component); + } default: return null; } } /** + * Returns the type of components in SQL arrays stored in a column. + * This method is invoked when {@link #type} = {@link Types#ARRAY}. + * The default implementation returns {@link Types#OTHER} because JDBC + * column metadata does not provide information about component types. + * Database-specific subclasses should override this method if they can + * provide that information from the {@link Column#typeName} value. + * + * @param columnDefinition information about the column to extract array component type. + * @return one of {@link Types} constants. + * + * @see Array#getBaseType() + */ + protected int getArrayComponentType(final Column columnDefinition) { + return Types.OTHER; + } + + /** + * Returns a mapping for {@link Types#JAVA_OBJECT} or unrecognized types. Some JDBC drivers wrap + * objects in implementation-specific classes, for example {@link org.postgresql.util.PGobject}. + * This method should be overwritten in database-specific subclasses for returning a value getter + * capable to unwrap the value. + * + * @return the default mapping for unknown or unrecognized types. + */ + protected ValueGetter<Object> getDefaultMapping() { + return ValueGetter.AsObject.INSTANCE; + } + + /** * Returns an identifier of the way binary data are encoded by the JDBC driver. * * @param columnDefinition information about the column to extract binary values from. @@ -616,16 +653,22 @@ public class Database<G> extends Syntax { } /** - * Returns a function for getting values from a geometry column. + * Returns a function for getting values from a geometry or geography column. * This is a helper method for {@link #getMapping(Column)} implementations. * * @param columnDefinition information about the column to extract values from and expose through Java API. * @return converter to the corresponding java type, or {@code null} if this class can not find a mapping, */ protected final ValueGetter<?> forGeometry(final Column columnDefinition) { - final GeometryType type = columnDefinition.getGeometryType(); + /* + * The geometry type should not be empty. But it may still happen if the "GEOMETRY_COLUMNS" + * table does not contain a line for the specified column. It is a server issue, but seems + * to happen sometime. + */ + final GeometryType type = columnDefinition.getGeometryType().orElse(GeometryType.GEOMETRY); final Class<? extends G> geometryClass = geomLibrary.getGeometryClass(type).asSubclass(geomLibrary.rootClass); - return new GeometryGetter<>(geomLibrary, geometryClass, columnDefinition.getDefaultCRS(), getBinaryEncoding(columnDefinition)); + return new GeometryGetter<>(geomLibrary, geometryClass, columnDefinition.getDefaultCRS().orElse(null), + getBinaryEncoding(columnDefinition)); } /** diff --git a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/InfoStatements.java b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/InfoStatements.java index 46a1e59..f04eca5 100644 --- a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/InfoStatements.java +++ b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/InfoStatements.java @@ -25,6 +25,7 @@ import java.util.Locale; import java.util.logging.Level; import java.util.logging.LogRecord; import java.text.ParseException; +import java.sql.Array; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; @@ -178,6 +179,18 @@ public class InfoStatements implements Localized, AutoCloseable { } /** + * Returns a function for getting values of components in the given array. + * If no match is found, then this method returns {@code null}. + * + * @param array the array from which to get the mapping of component values. + * @return converter to the corresponding java type, or {@code null} if this class can not find a mapping. + * @throws SQLException if the mapping can not be obtained. + */ + public final ValueGetter<?> getComponentMapping(final Array array) throws SQLException { + return database.getMapping(new Column(array.getBaseType(), array.getBaseTypeName())); + } + + /** * Appends a {@code " FROM <table> WHERE "} text to the given builder. * The table name will be prefixed by catalog and schema name if applicable. */ diff --git a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/ValueGetter.java b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/ValueGetter.java index 99ae321..fd30c9a 100644 --- a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/ValueGetter.java +++ b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/ValueGetter.java @@ -17,6 +17,8 @@ package org.apache.sis.internal.sql.feature; import java.util.Calendar; +import java.util.Collection; +import java.sql.Array; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Date; @@ -28,7 +30,10 @@ import java.time.OffsetDateTime; import java.time.OffsetTime; import java.time.ZoneOffset; import java.math.BigDecimal; +import org.apache.sis.math.Vector; +import org.apache.sis.util.Numbers; import org.apache.sis.util.ArgumentChecks; +import org.apache.sis.internal.util.UnmodifiableArrayList; /** @@ -97,8 +102,12 @@ public abstract class ValueGetter<T> { private AsObject() {super(Object.class);} /** Fetches the value from the specified column in the given result set. */ - @Override public Object getValue(InfoStatements stmts, ResultSet source, int columnIndex) throws SQLException { - return source.getObject(columnIndex); + @Override public Object getValue(InfoStatements stmts, ResultSet source, int columnIndex) throws Exception { + Object value = source.getObject(columnIndex); + if (value instanceof Array) { + value = toCollection(stmts, null, (Array) value); + } + return value; } } @@ -369,4 +378,72 @@ public abstract class ValueGetter<T> { return time.toLocalTime().atOffset(ZoneOffset.ofHoursMinutes(offsetMinute / 60, offsetMinute % 60)); } } + + /** + * A getter of values specified as Java array. + * This is okay for array of reasonable size. + * Should not be used for very large arrays. + */ + static final class AsArray extends ValueGetter<Collection<?>> { + /** The getter for components in the array, or {@code null} for automatic. */ + public final ValueGetter<?> cmget; + + /** Accessor for components of automatic type. */ + public static final AsArray INSTANCE = new AsArray(null); + + /** Creates a new getter of arrays. */ + @SuppressWarnings({"unchecked","rawtypes"}) + AsArray(final ValueGetter<?> cmget) { + super((Class) Collection.class); + this.cmget = cmget; + } + + /** Fetches the value from the specified column in the given result set. */ + @Override public Collection<?> getValue(InfoStatements stmts, ResultSet source, int columnIndex) throws Exception { + return toCollection(stmts, cmget, source.getArray(columnIndex)); + } + } + + /** + * Converts the given SQL array to a Java array and free the SQL array. + * The returned array may be a primitive array or an array of objects. + * + * @param stmts information about the statement being executed, or {@code null} if none. + * @param cmget the getter for components in the array, or {@code null} for automatic. + * @param array the SQL array, or {@code null} if none. + * @return the Java array, or {@code null} if the given SQL array is null. + * @throws Exception if an error occurred. May be an SQL error, a WKB parsing error, <i>etc.</i> + */ + protected static Collection<?> toCollection(final InfoStatements stmts, ValueGetter<?> cmget, final Array array) throws Exception { + if (array == null) { + return null; + } + Object result = array.getArray(); + if (cmget == null && stmts != null) { + cmget = stmts.getComponentMapping(array); + } + Class<?> componentType = Numbers.primitiveToWrapper(result.getClass().getComponentType()); + if (cmget != null && !cmget.valueType.isAssignableFrom(componentType)) { + /* + * If the elements in the `result` array are not of the expected type, fetch them again + * but this time using the converter. This fallback is inefficient because we fetch the + * same data that we already have, but the array should be short and this fallback will + * hopefully not be needed most of the time. It is also the only way to have the number + * of elements in advance. + */ + componentType = Numbers.wrapperToPrimitive(cmget.valueType); + final int length = java.lang.reflect.Array.getLength(result); + result = java.lang.reflect.Array.newInstance(componentType, length); + try (ResultSet r = array.getResultSet()) { + while (r.next()) { + java.lang.reflect.Array.set(result, r.getInt(1) - 1, cmget.getValue(stmts, r, 2)); + } + } + } + array.free(); + if (Numbers.isNumber(componentType)) { + return Vector.create(result, true); + } + return UnmodifiableArrayList.wrap((Object[]) result); + } } diff --git a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/postgis/ExtentEstimator.java b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/postgis/ExtentEstimator.java index 0cbbbd2..bf20180 100644 --- a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/postgis/ExtentEstimator.java +++ b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/postgis/ExtentEstimator.java @@ -123,7 +123,7 @@ final class ExtentEstimator { */ private void query(final Statement statement) throws SQLException { for (final Column column : columns) { - if (column.getGeometryType() != null) { + if (column.getGeometryType().isPresent()) { database.appendFunctionCall(builder.append("SELECT "), "ST_EstimatedExtent"); builder.append('('); if (table.schema != null) { @@ -136,7 +136,7 @@ final class ExtentEstimator { final String wkt = result.getString(1); if (wkt != null) { final GeneralEnvelope env = new GeneralEnvelope(wkt); - env.setCoordinateReferenceSystem(column.getDefaultCRS()); + column.getDefaultCRS().ifPresent(env::setCoordinateReferenceSystem); if (envelope == null) { envelope = env; } else try { diff --git a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/postgis/ObjectGetter.java b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/postgis/ObjectGetter.java new file mode 100644 index 0000000..b9f454a --- /dev/null +++ b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/postgis/ObjectGetter.java @@ -0,0 +1,76 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.sis.internal.sql.postgis; + +import java.sql.Array; +import java.sql.ResultSet; +import org.apache.sis.internal.sql.feature.InfoStatements; +import org.apache.sis.internal.sql.feature.ValueGetter; +import org.postgresql.util.PGobject; + + +/** + * Decoder of object of arbitrary kinds. + * + * @author Martin Desruisseaux (Geomatys) + * @version 1.2 + * @since 1.2 + * @module + */ +final class ObjectGetter extends ValueGetter<Object> { + /** + * The singleton instance. + */ + static final ObjectGetter INSTANCE = new ObjectGetter(); + + /** + * Creates the singleton instance. + */ + private ObjectGetter() { + super(Object.class); + } + + /** + * Gets the value in the column at specified index. + * The given result set must have its cursor position on the line to read. + * This method does not modify the cursor position. + * + * @param stmts prepared statements for fetching CRS from SRID, or {@code null} if none. + * @param source the result set from which to get the value. + * @param columnIndex index of the column in which to get the value. + * @return Object value in the given column. May be {@code null}. + * @throws Exception if an error occurred. May be an SQL error, a WKB parsing error, <i>etc.</i> + */ + @Override + public Object getValue(InfoStatements stmts, ResultSet source, int columnIndex) throws Exception { + Object value = source.getObject(columnIndex); + if (value instanceof PGobject) { + final PGobject po = (PGobject) value; + /* + * TODO: we should invoke `getType()` and select a decoding algorithm depending on the type. + * The driver also has a `PGBinaryObject` that we can check for more efficient data transfer + * of points and bounding boxes. For now we just get the the wrapped value, which is always + * a `String`. + */ + value = po.getValue(); + } + if (value instanceof Array) { + value = toCollection(stmts, null, (Array) value); + } + return value; + } +} diff --git a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/postgis/Postgres.java b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/postgis/Postgres.java index 66a60b1..3c4ec10 100644 --- a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/postgis/Postgres.java +++ b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/postgis/Postgres.java @@ -119,12 +119,34 @@ public final class Postgres<G> extends Database<G> { return forGeometry(columnDefinition); } if ("raster".equalsIgnoreCase(columnDefinition.typeName)) { - return new RasterGetter(columnDefinition.getDefaultCRS(), getBinaryEncoding(columnDefinition)); + return new RasterGetter(columnDefinition.getDefaultCRS().orElse(null), + getBinaryEncoding(columnDefinition)); } return super.getMapping(columnDefinition); } /** + * Returns the type of components in SQL arrays stored in a column. + * This method is invoked when {@link #type} = {@link Types#ARRAY}. + */ + @Override + protected int getArrayComponentType(final Column columnDefinition) { + switch (columnDefinition.typeName) { + // More types to be added later. + case "_text": return Types.VARCHAR; + } + return super.getArrayComponentType(columnDefinition); + } + + /** + * Returns the mapping for {@link Object} or unrecognized types. + */ + @Override + protected ValueGetter<Object> getDefaultMapping() { + return ObjectGetter.INSTANCE; + } + + /** * Returns an identifier of the way binary data are encoded by the JDBC driver. * Data stored as PostgreSQL {@code BYTEA} type are encoded in hexadecimal. */