You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2022/03/15 19:36:51 UTC
[sis] 02/02: 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`.
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 <ma...@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.
*/