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 8820c6fa484369affaa029cf0c56adcdabc4ed58
Author: Martin Desruisseaux <martin.desruisse...@geomatys.com>
AuthorDate: Mon Dec 27 16:16:04 2021 +0100

    Execute PostgreSQL `ANALYZE` at most once per table.
    Add documentation about the rational for not using `ST_Extent`.
---
 .../apache/sis/internal/sql/feature/Database.java    |  3 ++-
 .../org/apache/sis/internal/sql/feature/Table.java   | 20 +++++++++++++++++++-
 .../sis/internal/sql/postgis/ExtentEstimator.java    | 16 ++++++++++++----
 .../apache/sis/internal/sql/postgis/Postgres.java    |  7 +++++--
 4 files changed, 38 insertions(+), 8 deletions(-)

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 b898efe..79e9a9e 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
@@ -604,10 +604,11 @@ public class Database<G> extends Syntax  {
      * @param  table    the table for which to compute an estimation of the 
envelope.
      * @param  columns  all columns in the table. Implementation should ignore 
non-geometry columns.
      *                  This is a reference to an internal array; <strong>do 
not modify</strong>.
+     * @param  recall   if it is at least the second time that this method is 
invoked for the specified table.
      * @return an estimation of the spatiotemporal resource extent, or {@code 
null} if none.
      * @throws SQLException if an error occurred while fetching the envelope.
      */
-    protected Envelope getEstimatedExtent(TableReference table, Column[] 
columns) throws SQLException {
+    protected Envelope getEstimatedExtent(TableReference table, Column[] 
columns, boolean recall) throws SQLException {
         return null;
     }
 
diff --git 
a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/Table.java
 
b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/Table.java
index 9c18aed..295cb01 100644
--- 
a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/Table.java
+++ 
b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/Table.java
@@ -157,6 +157,14 @@ final class Table extends AbstractFeatureSet {
     private WeakValueHashMap<?,Object> instanceForPrimaryKeys;
 
     /**
+     * {@code true} if {@link #getEnvelope()} has been invoked at least once 
on this table.
+     * This is used for performing only once operations such as PosthreSQL 
{@code ANALYZE}.
+     *
+     * @see #getEnvelope()
+     */
+    private boolean isEnvelopeAnalyzed;
+
+    /**
      * Creates a description of the table analyzed by the given object.
      *
      * @param  database  information about the database (syntax for building 
SQL statements, …).
@@ -323,13 +331,23 @@ final class Table extends AbstractFeatureSet {
      * discouraged. Despite that, this method may return smaller envelopes 
because the computation is done using
      * a subset of all data.
      *
+     * <h2>Limitations</h2>
+     * The exact behavior is database-dependent.
+     * For example PostGIS implementation assumes that all geometries in the 
same column are in the same CRS.
+     * If geometries in different <em>rows</em> use different CRS, coordinate 
transformations are <em>not</em>
+     * applied and the result is likely to be invalid. However if different 
<em>column</em> use different CRS,
+     * coordinate transformations between columns is applied and the result is 
in the CRS of the first column
+     * having at least one geometry.
+     *
      * @return an estimation of the spatiotemporal resource extent.
      * @throws DataStoreException if an error occurred while reading or 
computing the envelope.
      */
     @Override
     public Optional<Envelope> getEnvelope() throws DataStoreException {
         if (hasGeometry) try {
-            return Optional.ofNullable(database.getEstimatedExtent(name, 
attributes));
+            final boolean recall = isEnvelopeAnalyzed;
+            isEnvelopeAnalyzed = true;
+            return Optional.ofNullable(database.getEstimatedExtent(name, 
attributes, recall));
         } catch (SQLException e) {
             throw new DataStoreException(e);
         } else {
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 2fa86da..0cbbbd2 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
@@ -31,6 +31,13 @@ import org.apache.sis.internal.metadata.sql.SQLBuilder;
 /**
  * Estimation of the extent of geometries in a given table or column using 
statistics if available.
  * Uses the PostGIS {@code ST_EstimatedExtent(…)} function to get a rough 
estimation of column extent.
+ * If {@code ST_EstimatedExtent(…)} gave no result and it was the first 
attempt on the specified table,
+ * then this class executes {@code ANALYZE} and tries again to get the extent. 
This strategy works well
+ * when requesting envelope on newly created tables.
+ *
+ * <h2>Design notes</h2>
+ * We do not use the most accurate {@code ST_Extent} function because it is 
costly on large tables.
+ * At the time of writing this class (December 2021), {@code ST_Extent} does 
not use column index.
  *
  * @author  Alexis Manin (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
@@ -88,12 +95,13 @@ final class ExtentEstimator {
      * If there is no statistics available, then this method executes {@code 
ANALYZE}
      * and tries again.
      *
-     * @param statement  statement to use for executing queries. Shall be 
closed by caller.
+     * @param  statement  statement to use for executing queries. Shall be 
closed by caller.
+     * @param  recall     if it is at least the second time that this method 
is invoked for the table.
      * @return an estimation of the union of extents in given columns, or 
{@code null} if unknown.
      */
-    GeneralEnvelope estimate(final Statement statement) throws SQLException {
+    GeneralEnvelope estimate(final Statement statement, final boolean recall) 
throws SQLException {
         query(statement);
-        if (envelope == null) {
+        if (envelope == null && !recall) {
             builder.append("ANALYZE ").appendIdentifier(table.catalog, 
table.schema, table.table);
             final String sql = builder.toString();
             builder.clear();
@@ -110,7 +118,7 @@ final class ExtentEstimator {
      * Estimates the extent in the specified columns using current statistics.
      * If there is no statistics available, then this method returns {@code 
null}.
      *
-     * @param statement  statement to use for executing queries. Shall be 
closed by caller.
+     * @param  statement  statement to use for executing queries. Shall be 
closed by caller.
      * @return an estimation of the union of extents in given columns, or 
{@code null} if unknown.
      */
     private void query(final Statement statement) throws SQLException {
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 06925c8..66a60b1 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
@@ -177,16 +177,19 @@ public final class Postgres<G> extends Database<G> {
      * @param  table    the table for which to compute an estimation of the 
envelope.
      * @param  columns  all columns in the table (including non-geometry 
columns).
      *                  This is a reference to an internal array; <strong>do 
not modify</strong>.
+     * @param  recall   if it is at least the second time that this method is 
invoked for the specified table.
      * @return an estimation of the spatiotemporal resource extent, or {@code 
null} if none.
      * @throws SQLException if an error occurred while fetching the envelope.
      *
      * @see <a 
href="https://postgis.net/docs/ST_EstimatedExtent.html";>ST_EstimatedExtent</a>
      */
     @Override
-    protected Envelope getEstimatedExtent(final TableReference table, final 
Column[] columns) throws SQLException {
+    protected Envelope getEstimatedExtent(final TableReference table, final 
Column[] columns, final boolean recall)
+            throws SQLException
+    {
         final ExtentEstimator ex = new ExtentEstimator(this, table, columns);
         try (Connection c = source.getConnection(); Statement statement = 
c.createStatement()) {
-            return ex.estimate(statement);
+            return ex.estimate(statement, recall);
         }
     }
 }

Reply via email to