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
The following commit(s) were added to refs/heads/geoapi-4.0 by this push:
new 38bf69764d Add a `Dialect.DUCKDB` case together with specific code in
SQL store. Contains modifications to SQL store internal for accommodating
DuckDB.
38bf69764d is described below
commit 38bf69764dc1f8d52ad996344ea280b40c9551aa
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Tue Mar 18 19:46:34 2025 +0100
Add a `Dialect.DUCKDB` case together with specific code in SQL store.
Contains modifications to SQL store internal for accommodating DuckDB.
---
.../org/apache/sis/metadata/sql/privy/Dialect.java | 52 ++++++++++-
.../main/module-info.java | 1 +
.../org/apache/sis/storage/sql/duckdb/DuckDB.java | 88 ++++++++++++++++++
.../storage/sql/duckdb/ExtendedClauseWriter.java | 61 ++++++++++++
.../sis/storage/sql/duckdb/package-info.java | 60 ++++++++++++
.../apache/sis/storage/sql/feature/Analyzer.java | 2 +
.../apache/sis/storage/sql/feature/Database.java | 23 ++++-
.../sis/storage/sql/feature/FeatureIterator.java | 11 ++-
.../sis/storage/sql/feature/GeometryEncoding.java | 39 ++++++++
.../sis/storage/sql/feature/GeometryGetter.java | 103 +++++++++++++--------
.../storage/sql/feature/SelectionClauseWriter.java | 3 +-
.../storage/sql/feature/GeometryGetterTest.java | 2 +-
.../sis/storage/sql/postgis/RasterReaderTest.java | 5 +-
.../sis/storage/sql/postgis/RasterWriterTest.java | 4 +-
14 files changed, 405 insertions(+), 49 deletions(-)
diff --git
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/privy/Dialect.java
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/privy/Dialect.java
index c1f2b16716..0cccbb1cd2 100644
---
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/privy/Dialect.java
+++
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/privy/Dialect.java
@@ -19,6 +19,7 @@ package org.apache.sis.metadata.sql.privy;
import java.sql.SQLException;
import java.sql.DatabaseMetaData;
import org.apache.sis.util.CharSequences;
+import org.apache.sis.util.Workaround;
import org.apache.sis.util.privy.Constants;
@@ -83,7 +84,40 @@ public enum Dialect {
*
* @see <a href="https://www.sqlite.org/omitted.html">SQL Features That
SQLite Does Not Implement</a>
*/
- SQLITE("sqlite", 0);
+ SQLITE("sqlite", 0),
+
+ /**
+ * The database uses DuckDB syntax. This is subset of SQL. DuckDB is not
designed for transactional
+ * applications, but rather for analytical processing. It runs on the
local machine without server.
+ *
+ * <h4>Spatial extension</h4>
+ * The following <abbr>SQL</abbr> statement needs to be executed at least
once when DuckDB
+ * is used for the first time. It can be executed with a {@link
java.sql.Statement}.
+ *
+ * {@snippet lang="sql" :
+ * INSTALL spatial
+ * }
+ *
+ * Then, the following <abbr>SQL</abbr> statement should be executed on
every new connection.
+ * Actually, in our tests, it appears sometime necessary, sometime not.
+ *
+ * {@snippet lang="sql" :
+ * LOAD spatial
+ * }
+ *
+ * @see <a
href="https://github.com/duckdb/duckdb-java/issues/165">DuckDB-Java issue
#165</a>
+ */
+ DUCKDB("duckdb", 0) {
+ @Override
+ @Workaround(library = "DuckDB", version = "1.2.1")
+ public String toCompatibleMetadataPattern(String pattern, final int
argument) {
+ switch (argument) {
+ case 1: if (pattern == null) pattern = "%"; break;
+ case 2: pattern = pattern.replace("\\", ""); break;
+ }
+ return pattern;
+ }
+ };
/**
* The protocol in JDBC URL, or {@code null} if unknown.
@@ -159,6 +193,22 @@ public enum Dialect {
return (flags & Supports.CONCURRENCY) != 0;
}
+ /**
+ * Converts the pattern to something that can be used for requesting
metadata.
+ * This is a workaround for a DuckDB bug and may be removed in a future
version.
+ *
+ * @param pattern the schema pattern to apply.
+ * @param argument 1 for the {@code schemaPattern}, 2 for {@code
functionNamePattern}.
+ * @return the schema pattern to use.
+ *
+ * @see DatabaseMetaData#getFunctions(String, String, String)
+ * @see <a
href="https://github.com/duckdb/duckdb-java/issues/165">DuckDB-Java issue
#165</a>
+ */
+ @Workaround(library = "DuckDB", version = "1.2.1")
+ public String toCompatibleMetadataPattern(String pattern, int argument) {
+ return pattern;
+ }
+
/**
* Returns the presumed SQL dialect.
* If this method cannot guess the dialect, than {@link #ANSI} is presumed.
diff --git a/endorsed/src/org.apache.sis.storage.sql/main/module-info.java
b/endorsed/src/org.apache.sis.storage.sql/main/module-info.java
index aa3eab4fe6..c34df3d4f4 100644
--- a/endorsed/src/org.apache.sis.storage.sql/main/module-info.java
+++ b/endorsed/src/org.apache.sis.storage.sql/main/module-info.java
@@ -41,6 +41,7 @@
* @author Johann Sorel (Geomatys)
* @author Martin Desruisseaux (Geomatys)
* @author Alexis Manin (Geomatys)
+ * @author Guilhem Legal (Geomatys)
* @version 1.5
* @since 1.0
*/
diff --git
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/duckdb/DuckDB.java
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/duckdb/DuckDB.java
new file mode 100644
index 0000000000..58f6ada7da
--- /dev/null
+++
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/duckdb/DuckDB.java
@@ -0,0 +1,88 @@
+/*
+ * 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.storage.sql.duckdb;
+
+import java.util.Locale;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.sql.DatabaseMetaData;
+import java.sql.SQLException;
+import javax.sql.DataSource;
+import org.apache.sis.geometry.wrapper.Geometries;
+import org.apache.sis.metadata.sql.privy.Dialect;
+import org.apache.sis.storage.event.StoreListeners;
+import org.apache.sis.storage.sql.feature.Column;
+import org.apache.sis.storage.sql.feature.Database;
+import org.apache.sis.storage.sql.feature.GeometryEncoding;
+import org.apache.sis.storage.sql.feature.SelectionClauseWriter;
+
+
+/**
+ * Information about a connection to a DuckDB database.
+ * This class specializes some of the functions for converting DuckDB spatial
extension objects to Java objects.
+ * See the package Javadoc for recommendation about how to connect to a DuckDB
database.
+ *
+ * @param <G> the type of geometry objects. Depends on the backing
implementation (ESRI, JTS, Java2D…).
+ *
+ * @author Guilhem Legal (Geomatys)
+ * @author Martin Desruisseaux (Geomatys)
+ */
+public final class DuckDB<G> extends Database<G> {
+ /**
+ * Creates a new session for a DuckDB database.
+ *
+ * @param source provider of (pooled) connections to the database.
+ * @param metadata metadata about the database for which a session
is created.
+ * @param dialect additional information not provided by {@code
metadata}.
+ * @param geomLibrary the factory to use for creating geometric
objects.
+ * @param contentLocale the locale to use for international texts to
write in the database, or {@code null} for default.
+ * @param listeners where to send warnings.
+ * @param locks the read/write locks, or {@code null} if none.
+ * @throws SQLException if an error occurred while reading database
metadata.
+ */
+ public DuckDB(final DataSource source, final DatabaseMetaData metadata,
final Dialect dialect,
+ final Geometries<G> geomLibrary, final Locale contentLocale,
final StoreListeners listeners,
+ final ReadWriteLock locks)
+ throws SQLException
+ {
+ super(source, metadata, dialect, geomLibrary, contentLocale,
listeners, locks);
+ }
+
+ /**
+ * Whether to decode the geometry from <abbr>WKB</abbr> instead of
<abbr>WKT</abbr>.
+ * In theory, the use of binary format should be more efficient. But the
DuckDB driver
+ * has some issues with extracting bytes from geometry columns at the time
or writing.
+ * The current version extracts the geometries through <abbr>WKT</abbr>
representation.
+ * The reasons for not using <abbr>WKB</abbr> at this stage are:
+ *
+ * <ul>
+ * <li>It requires to build the query like this: {@code
CAST(ST_AsWKB(geom_column) AS BLOB)}.</li>
+ * <li>It seems that for large dataset, reading from WKB is a lot slower
than reading from WKT.</li>
+ * </ul>
+ */
+ @Override
+ protected GeometryEncoding getGeometryEncoding(final Column
columnDefinition) {
+ return GeometryEncoding.WKT;
+ }
+
+ /**
+ * Returns the converter from filters/expressions to the {@code WHERE}
part of SQL statement.
+ */
+ @Override
+ protected SelectionClauseWriter getFilterToSQL() {
+ return ExtendedClauseWriter.INSTANCE;
+ }
+}
diff --git
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/duckdb/ExtendedClauseWriter.java
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/duckdb/ExtendedClauseWriter.java
new file mode 100644
index 0000000000..a6232bda80
--- /dev/null
+++
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/duckdb/ExtendedClauseWriter.java
@@ -0,0 +1,61 @@
+/*
+ * 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.storage.sql.duckdb;
+
+import org.apache.sis.storage.sql.feature.SelectionClauseWriter;
+import org.opengis.filter.SpatialOperatorName;
+
+
+/**
+ * Converter from filters/expressions to the {@code WHERE} part of SQL
statement.
+ * This class adds support for {@code BBOX} as a synonymous of {@code
ST_Intersects}.
+ *
+ * @author Guilhem Legal (Geomatys)
+ */
+final class ExtendedClauseWriter extends SelectionClauseWriter {
+ /**
+ * The unique instance.
+ */
+ static final ExtendedClauseWriter INSTANCE = new ExtendedClauseWriter();
+
+ /**
+ * Creates a new converter from filters/expressions to SQL.
+ */
+ private ExtendedClauseWriter() {
+ super(DEFAULT);
+ setFilterHandler(SpatialOperatorName.BBOX,
getFilterHandler(SpatialOperatorName.INTERSECTS));
+ }
+
+ /**
+ * Creates a new converter initialized to the same handlers as the
specified converter.
+ *
+ * @param source the converter from which to copy the handlers.
+ */
+ private ExtendedClauseWriter(ExtendedClauseWriter source) {
+ super(source);
+ }
+
+ /**
+ * Creates a new converter of the same class as {@code this} and
initialized with the same data.
+ *
+ * @return a converter initialized to a copy of {@code this}.
+ */
+ @Override
+ protected SelectionClauseWriter duplicate() {
+ return new ExtendedClauseWriter(this);
+ }
+}
diff --git
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/duckdb/package-info.java
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/duckdb/package-info.java
new file mode 100644
index 0000000000..5c100e6246
--- /dev/null
+++
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/duckdb/package-info.java
@@ -0,0 +1,60 @@
+/*
+ * 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.
+ */
+
+/**
+ * Specialization of {@code org.apache.sis.storage.sql.feature} for the DuckDB
database.
+ * Since DuckDB 1.2.1 does not provide a {@link javax.sql.DataSource}
implementation,
+ * users need to provide their own. The user's {@code DataSource} should load
the spatial
+ * extension if desired and enable the streaming. The following snippet is a
suggestion:
+ *
+ * {@snippet lang="java" :
+ * import java.util.Properties;
+ * import java.sql.Connection;
+ * import java.sql.DriverManager;
+ * import java.sql.SQLException;
+ * import java.sql.Statement;
+ * import javax.sql.DataSource;
+ * import org.duckdb.DuckDBDriver;
+ *
+ * class DuckDataSource implements DataSource {
+ * private final String url;
+ * private boolean initialized;
+ *
+ * DuckDataSource(final String url) {
+ * this.url = url;
+ * }
+ *
+ * @Override
+ * public Connection getConnection() throws SQLException {
+ * var info = new Properties();
+ * info.setProperty(DuckDBDriver.JDBC_STREAM_RESULTS, "true");
+ * Connection c = DriverManager.getConnection(url, info);
+ * try (Statement s = c.createStatement()) {
+ * if (!initialized) {
+ * initialized = true;
+ * s.execute("INSTALL spatial");
+ * }
+ * s.execute("LOAD spatial");
+ * }
+ * return c;
+ * }
+ * }
+ *
+ * @author Guilhem Legal (Geomatys)
+ * @author Martin Desruisseaux (Geomatys)
+ */
+package org.apache.sis.storage.sql.duckdb;
diff --git
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/Analyzer.java
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/Analyzer.java
index 2f42b0d4bf..a2333db673 100644
---
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/Analyzer.java
+++
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/Analyzer.java
@@ -45,6 +45,7 @@ import org.apache.sis.storage.InternalDataStoreException;
import org.apache.sis.storage.event.StoreListeners;
import org.apache.sis.storage.sql.ResourceDefinition;
import org.apache.sis.storage.sql.postgis.Postgres;
+import org.apache.sis.storage.sql.duckdb.DuckDB;
import org.apache.sis.metadata.sql.privy.Dialect;
import org.apache.sis.metadata.sql.privy.Reflection;
import org.apache.sis.util.ArraysExt;
@@ -191,6 +192,7 @@ public final class Analyzer {
final Dialect dialect = Dialect.guess(metadata);
switch (dialect) {
case POSTGRESQL: database = new Postgres<>(source, metadata,
dialect, g, contentLocale, listeners, locks); break;
+ case DUCKDB: database = new DuckDB<> (source, metadata,
dialect, g, contentLocale, listeners, locks); break;
default: database = new Database<>(source, metadata,
dialect, g, contentLocale, listeners, locks); break;
}
ignoredTables = database.detectSpatialSchema(metadata, tableTypes);
diff --git
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/Database.java
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/Database.java
index b00d58183b..fbb62c850b 100644
---
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/Database.java
+++
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/Database.java
@@ -630,6 +630,9 @@ public class Database<G> extends Syntax {
*
* @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 cannot find a mapping,
+ *
+ * @see #getBinaryEncoding(Column)
+ * @see #getGeometryEncoding(Column)
*/
protected final ValueGetter<?> forGeometry(final Column columnDefinition) {
/*
@@ -640,7 +643,7 @@ public class Database<G> extends Syntax {
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().orElse(null),
- getBinaryEncoding(columnDefinition));
+ getBinaryEncoding(columnDefinition),
getGeometryEncoding(columnDefinition));
}
/**
@@ -736,15 +739,31 @@ public class Database<G> extends Syntax {
}
/**
- * Returns an identifier of the way binary data are encoded by the JDBC
driver.
+ * Returns an identifier of the way binary data are encoded by the
<abbr>JDBC</abbr> driver.
+ * The default implementation returns {@link BinaryEncoding#RAW}.
*
* @param columnDefinition information about the column to extract
binary values from.
* @return how the binary data are returned by the JDBC driver.
+ *
+ * @see #forGeometry(Column)
*/
protected BinaryEncoding getBinaryEncoding(final Column columnDefinition) {
return BinaryEncoding.RAW;
}
+ /**
+ * Returns an identifier of the way geometries should be read and written.
+ * The default implementation returns {@link GeometryEncoding#WKB}.
+ *
+ * @param columnDefinition information about the column to extract
geometry values from.
+ * @return how the geometry should be read or written (as text or as
binary).
+ *
+ * @see #forGeometry(Column)
+ */
+ protected GeometryEncoding getGeometryEncoding(final Column
columnDefinition) {
+ return GeometryEncoding.WKB;
+ }
+
/**
* Computes an estimation of the envelope of all geometry columns in the
given table.
* The returned envelope shall contain at least the two-dimensional
spatial components.
diff --git
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/FeatureIterator.java
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/FeatureIterator.java
index 13dea0b1fd..13be593b51 100644
---
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/FeatureIterator.java
+++
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/FeatureIterator.java
@@ -144,14 +144,19 @@ final class FeatureIterator implements
Spliterator<Feature>, AutoCloseable {
}
sql = builder.appendFetchPage(offset, count).toString();
}
- result = connection.createStatement().executeQuery(sql);
- dependencies = new FeatureIterator[adapter.dependencies.length];
- statement = null;
+ /*
+ * Create the statement for the SQL query. The call to
`createStatement()` should be at the end,
+ * after the call to `countRows(…)`, because some JDBC drivers close
the statement when we ask
+ * for metadata (probably a bug, but not all JDBC drivers are mature).
+ */
if (filter == null) {
estimatedSize = Math.min(table.countRows(connection.getMetaData(),
distinct, true), offset + count) - offset;
} else {
estimatedSize = 0; // Cannot estimate the size if
there is filtering conditions.
}
+ result = connection.createStatement().executeQuery(sql);
+ dependencies = new FeatureIterator[adapter.dependencies.length];
+ statement = null;
}
/**
diff --git
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/GeometryEncoding.java
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/GeometryEncoding.java
new file mode 100644
index 0000000000..544d0596ed
--- /dev/null
+++
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/GeometryEncoding.java
@@ -0,0 +1,39 @@
+/*
+ * 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.storage.sql.feature;
+
+
+/**
+ * The encoding to use for reading or writing geometries from a {@code
ResultSet}, in preference order.
+ * In theory, the use of a binary format should be more efficient. But some
<abbr>JDBC</abbr> drivers
+ * have issues with extracting bytes from geometry columns. It also happens
sometime that, surprisingly
+ * the use of <abbr>WKT</abbr> appear to be faster than <abbr>WKB</abbr> with
some databases.
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ */
+public enum GeometryEncoding {
+ /**
+ * Use Well-Known Binary (<abbr>WKB</abbr>) format.
+ * Includes the Geopackage geometry encoding extension, which is
identified by the "GP" prefix.
+ */
+ WKB,
+
+ /**
+ * Use Well-Known Text (<abbr>WKT</abbr>) format.
+ */
+ WKT
+}
diff --git
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/GeometryGetter.java
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/GeometryGetter.java
index fefebec715..38aadc6e33 100644
---
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/GeometryGetter.java
+++
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/GeometryGetter.java
@@ -71,6 +71,13 @@ final class GeometryGetter<G, V extends G> extends
ValueGetter<V> {
*/
private final BinaryEncoding encoding;
+ /**
+ * Whether to use binary (<abbr>WKB</abbr>) or textual (<abbr>WKT</abbr>).
+ * In theory, the binary format should be more efficient.
+ * But this is not always well supported.
+ */
+ private final GeometryEncoding format;
+
/**
* Creates a new reader. The same instance can be reused for parsing an
arbitrary
* number of geometries sharing the same default CRS.
@@ -79,14 +86,17 @@ final class GeometryGetter<G, V extends G> extends
ValueGetter<V> {
* @param geometryClass the type of geometry to be returned by this
{@code ValueGetter}.
* @param defaultCRS the CRS to use if none can be mapped from the
SRID, or {@code null} if none.
* @param encoding the way binary data are encoded in the
geometry column.
+ * @param format whether to use <abbr>WKB</abbr> or
<abbr>WKT</abbr>.
*/
GeometryGetter(final Geometries<G> geometryFactory, final Class<V>
geometryClass,
- final CoordinateReferenceSystem defaultCRS, final BinaryEncoding
encoding)
+ final CoordinateReferenceSystem defaultCRS, final BinaryEncoding
encoding,
+ final GeometryEncoding format)
{
super(geometryClass);
this.geometryFactory = geometryFactory;
this.defaultCRS = defaultCRS;
this.encoding = encoding;
+ this.format = format;
}
/**
@@ -105,45 +115,64 @@ final class GeometryGetter<G, V extends G> extends
ValueGetter<V> {
*/
@Override
public V getValue(final InfoStatements stmts, final ResultSet source,
final int columnIndex) throws Exception {
- final byte[] wkb = encoding.getBytes(source, columnIndex);
- if (wkb == null) return null;
- final ByteBuffer buffer = ByteBuffer.wrap(wkb);
- /*
- * The bytes should describe a geometry encoded in Well Known Binary
(WKB) format,
- * but this implementation accepts also the Geopackage geometry
encoding:
- *
- * https://www.geopackage.org/spec140/index.html#gpb_spec
- *
- * This is still a geometry in WKB format, but preceded by a header of
at least two 32-bits integers.
- */
- int gpkgSrid = 0; // ≤0 means "no CRS" as of
`stmts.fetchCRS(int)` contract.
- if (wkb.length > 2*Integer.BYTES && wkb[0] == 'G' && wkb[1] == 'P') {
- final int version = Byte.toUnsignedInt(wkb[2]); // 8-bit
unsigned integer, 0 = version 1
- if (version != 0) {
- throw new
DataStoreContentException(Errors.forLocale(stmts.getLocale())
- .getString(Errors.Keys.UnsupportedFormatVersion_2,
"Geopackage", version));
+ final GeometryWrapper geom;
+ int gpkgSrid = 0; // A value ≤ 0 means "no CRS" as of
`stmts.fetchCRS(int)` contract.
+ switch (format) {
+ default: {
+ return null;
+ }
+ case WKT: {
+ final String wkt = source.getString(columnIndex);
+ if (wkt == null) return null;
+ geom = geometryFactory.parseWKT(wkt);
+ break;
}
- final int flags = wkb[3];
- final boolean bigEndian = (flags & 0b000001) == 0;
- final int envelopeType = (flags & 0b001110) >> 1;
- // final boolean isEmpty = (flags & 0b010000) != 0;
- // final boolean extendedType = (flags & 0b100000) != 0;
- buffer.order(bigEndian ? ByteOrder.BIG_ENDIAN :
ByteOrder.LITTLE_ENDIAN);
- gpkgSrid = buffer.getInt(Integer.BYTES);
- // Skip header and envelope.
- final int offset;
- switch (envelopeType) {
- case 0: offset = 2*Integer.BYTES; break; //
No envelope.
- case 1: offset = 2*Integer.BYTES + 4*Double.BYTES; break; //
2D envelope.
- case 2: //
3D envelope with Z.
- case 3: offset = 2*Integer.BYTES + 6*Double.BYTES; break; //
3D envelope with M.
- case 4: offset = 2*Integer.BYTES + 8*Double.BYTES; break; //
4D envelope.
- default: throw new
DataStoreContentException(Errors.forLocale(stmts.getLocale())
- .getString(Errors.Keys.UnexpectedValueInElement_2,
"envelope contents indicator"));
+ case WKB: {
+ final byte[] wkb = encoding.getBytes(source, columnIndex);
+ if (wkb == null) return null;
+ final ByteBuffer buffer = ByteBuffer.wrap(wkb);
+ /*
+ * The bytes should describe a geometry encoded in Well Known
Binary (WKB) format,
+ * but this implementation accepts also the Geopackage
geometry encoding:
+ *
+ * https://www.geopackage.org/spec140/index.html#gpb_spec
+ *
+ * This is still a geometry in WKB format, but preceded by a
header of at least two 32-bits integers.
+ */
+ if (wkb.length > 2*Integer.BYTES && wkb[0] == 'G' && wkb[1] ==
'P') {
+ final int version = Byte.toUnsignedInt(wkb[2]); //
8-bit unsigned integer, 0 = version 1
+ if (version != 0) {
+ throw new
DataStoreContentException(Errors.forLocale(stmts.getLocale())
+
.getString(Errors.Keys.UnsupportedFormatVersion_2, "Geopackage", version));
+ }
+ final int flags = wkb[3];
+ final boolean bigEndian = (flags & 0b000001) == 0;
+ final int envelopeType = (flags & 0b001110) >> 1;
+ // final boolean isEmpty = (flags & 0b010000) != 0;
+ // final boolean extendedType = (flags & 0b100000) != 0;
+ buffer.order(bigEndian ? ByteOrder.BIG_ENDIAN :
ByteOrder.LITTLE_ENDIAN);
+ gpkgSrid = buffer.getInt(Integer.BYTES);
+ // Skip header and envelope.
+ final int offset;
+ switch (envelopeType) {
+ case 0: offset = 2*Integer.BYTES;
break; // No envelope.
+ case 1: offset = 2*Integer.BYTES + 4*Double.BYTES;
break; // 2D envelope.
+ case 2:
// 3D envelope with Z.
+ case 3: offset = 2*Integer.BYTES + 6*Double.BYTES;
break; // 3D envelope with M.
+ case 4: offset = 2*Integer.BYTES + 8*Double.BYTES;
break; // 4D envelope.
+ default: throw new
DataStoreContentException(Errors.forLocale(stmts.getLocale())
+
.getString(Errors.Keys.UnexpectedValueInElement_2, "envelope contents
indicator"));
+ }
+ buffer.position(offset).order(ByteOrder.BIG_ENDIAN);
+ }
+ geom = geometryFactory.parseWKB(buffer);
+ break;
}
- buffer.position(offset).order(ByteOrder.BIG_ENDIAN);
}
- final GeometryWrapper geom = geometryFactory.parseWKB(buffer);
+ /*
+ * Set the CRS. This is often a constant value defined for the whole
column.
+ * But some formats allow to specify a SRID individually on the
geometry.
+ */
CoordinateReferenceSystem crs = defaultCRS;
if (stmts != null) {
crs = stmts.fetchCRS(geom.getSRID().orElse(gpkgSrid));
diff --git
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/SelectionClauseWriter.java
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/SelectionClauseWriter.java
index a47317c3ca..208fb33c89 100644
---
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/SelectionClauseWriter.java
+++
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/SelectionClauseWriter.java
@@ -174,7 +174,8 @@ public class SelectionClauseWriter extends Visitor<Feature,
SelectionClause> {
*/
final String prefix = database.escapeWildcards(lowerCase ? "st_" :
"ST_");
try (ResultSet r =
metadata.getFunctions(database.catalogOfSpatialTables,
-
database.schemaOfSpatialTables, prefix + '%'))
+
database.dialect.toCompatibleMetadataPattern(database.schemaOfSpatialTables, 1),
+ database.dialect.toCompatibleMetadataPattern(prefix + '%',
2)))
{
while (r.next()) {
unsupported.remove(r.getString("FUNCTION_NAME"));
diff --git
a/endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/feature/GeometryGetterTest.java
b/endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/feature/GeometryGetterTest.java
index 7fe09610ef..3158ada9a8 100644
---
a/endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/feature/GeometryGetterTest.java
+++
b/endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/feature/GeometryGetterTest.java
@@ -65,7 +65,7 @@ public final class GeometryGetterTest extends TestCase {
@SuppressWarnings("unchecked")
private GeometryGetter<?,?> createReader(final GeometryLibrary library,
final BinaryEncoding encoding) {
GF = Geometries.factory(library);
- return new GeometryGetter<>(GF, (Class) GF.rootClass,
HardCodedCRS.WGS84, encoding);
+ return new GeometryGetter<>(GF, (Class) GF.rootClass,
HardCodedCRS.WGS84, encoding, GeometryEncoding.WKB);
}
/**
diff --git
a/endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/postgis/RasterReaderTest.java
b/endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/postgis/RasterReaderTest.java
index 763efc905c..4c4490eed6 100644
---
a/endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/postgis/RasterReaderTest.java
+++
b/endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/postgis/RasterReaderTest.java
@@ -64,6 +64,7 @@ public final class RasterReaderTest extends TestCase {
* Reads the file for the given test enumeration and compares with the
expected raster.
* The given reader and input are used for reading the raster. The input
will be closed.
*/
+ @SuppressWarnings("ConvertToTryWithResources") // Because testing on a
byte array, closing is not very important.
static void compareReadResult(final TestRaster test, final RasterReader
reader, final ChannelDataInput input) throws Exception {
final GridCoverage coverage = reader.readAsCoverage(input);
input.channel.close();
@@ -77,8 +78,8 @@ public final class RasterReaderTest extends TestCase {
*/
static void compareReadResult(final TestRaster test, final GridCoverage
coverage) {
final RenderedImage image = coverage.render(null);
- final DataBufferUShort expected = (DataBufferUShort)
test.createRaster().getDataBuffer();
- final DataBufferUShort actual = (DataBufferUShort) image.getTile(0,
0).getDataBuffer();
+ final var expected = (DataBufferUShort)
test.createRaster().getDataBuffer();
+ final var actual = (DataBufferUShort) image.getTile(0,
0).getDataBuffer();
assertTrue(Arrays.deepEquals(expected.getBankData(),
actual.getBankData()));
}
}
diff --git
a/endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/postgis/RasterWriterTest.java
b/endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/postgis/RasterWriterTest.java
index 838f11891b..12b92c6c8a 100644
---
a/endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/postgis/RasterWriterTest.java
+++
b/endorsed/src/org.apache.sis.storage.sql/test/org/apache/sis/storage/sql/postgis/RasterWriterTest.java
@@ -59,8 +59,8 @@ public final class RasterWriterTest extends TestCase {
*/
private static void compareWriteResult(final TestRaster test) throws
Exception {
final Raster raster = test.createRaster();
- final RasterWriter writer = new RasterWriter(null);
- final ByteArrayOutputStream buffer = new
ByteArrayOutputStream(test.length);
+ final var writer = new RasterWriter(null);
+ final var buffer = new ByteArrayOutputStream(test.length);
final ChannelDataOutput output = test.output(buffer);
writer.setGridToCRS(TestRaster.getGridGeometry());
writer.write(raster, output);