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 <martin.desruisse...@geomatys.com>
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);


Reply via email to