This is an automated email from the ASF dual-hosted git repository.

bchapuis pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-baremaps.git


The following commit(s) were added to refs/heads/main by this push:
     new 8c65a3071 Generate different queries depending on the postgresql 
version (#914)
8c65a3071 is described below

commit 8c65a3071cd1b0a91fa7e365059c758941027f16
Author: Bertil Chapuis <[email protected]>
AuthorDate: Tue Jan 14 09:10:57 2025 +0100

    Generate different queries depending on the postgresql version (#914)
    
    - Generate different queries depending on the postgresql version
    - Caching is not needed anymore as String concatenation is faster than 
query parsing.
---
 .../main/java/org/apache/baremaps/cli/map/Dev.java |   3 +-
 .../java/org/apache/baremaps/cli/map/Serve.java    |   3 +-
 .../apache/baremaps/tasks/ExportVectorTiles.java   |  14 ++-
 .../tilestore/postgres/PostgresTileStore.java      | 110 ++++++++++++++-------
 .../tilestore/postgres/PostgresTileStoreTest.java  |  29 +++++-
 .../baremaps/postgres/utils/PostgresUtils.java     |  13 +++
 6 files changed, 126 insertions(+), 46 deletions(-)

diff --git a/baremaps-cli/src/main/java/org/apache/baremaps/cli/map/Dev.java 
b/baremaps-cli/src/main/java/org/apache/baremaps/cli/map/Dev.java
index e44fcd434..e899dabee 100644
--- a/baremaps-cli/src/main/java/org/apache/baremaps/cli/map/Dev.java
+++ b/baremaps-cli/src/main/java/org/apache/baremaps/cli/map/Dev.java
@@ -87,6 +87,7 @@ public class Dev implements Callable<Integer> {
     var objectMapper = objectMapper();
     var tileset = objectMapper.readValue(configReader.read(this.tilesetPath), 
Tileset.class);
     var datasource = 
PostgresUtils.createDataSourceFromObject(tileset.getDatabase());
+    var postgresVersion = PostgresUtils.getPostgresVersion(datasource);
 
     var tilesetSupplier = (Supplier<Tileset>) () -> {
       try {
@@ -99,7 +100,7 @@ public class Dev implements Callable<Integer> {
 
     var tileStoreSupplier = (Supplier<TileStore<ByteBuffer>>) () -> {
       var tileJSON = tilesetSupplier.get();
-      return new PostgresTileStore(datasource, tileJSON);
+      return new PostgresTileStore(datasource, tileJSON, postgresVersion);
     };
 
     var styleSupplier = (Supplier<Style>) () -> {
diff --git a/baremaps-cli/src/main/java/org/apache/baremaps/cli/map/Serve.java 
b/baremaps-cli/src/main/java/org/apache/baremaps/cli/map/Serve.java
index cdaa79192..59afb21fc 100644
--- a/baremaps-cli/src/main/java/org/apache/baremaps/cli/map/Serve.java
+++ b/baremaps-cli/src/main/java/org/apache/baremaps/cli/map/Serve.java
@@ -93,9 +93,10 @@ public class Serve implements Callable<Integer> {
     var caffeineSpec = CaffeineSpec.parse(cache);
     var tileset = objectMapper.readValue(configReader.read(tilesetPath), 
Tileset.class);
     var datasource = 
PostgresUtils.createDataSourceFromObject(tileset.getDatabase());
+    var postgresVersion = PostgresUtils.getPostgresVersion(datasource);
 
     try (
-        var tileStore = new PostgresTileStore(datasource, tileset);
+        var tileStore = new PostgresTileStore(datasource, tileset, 
postgresVersion);
         var tileCache = new VectorTileCache(tileStore, caffeineSpec)) {
 
       var tileStoreSupplier = (Supplier<TileStore<ByteBuffer>>) () -> 
tileCache;
diff --git 
a/baremaps-core/src/main/java/org/apache/baremaps/tasks/ExportVectorTiles.java 
b/baremaps-core/src/main/java/org/apache/baremaps/tasks/ExportVectorTiles.java
index 97cbbb72b..95e8e8001 100644
--- 
a/baremaps-core/src/main/java/org/apache/baremaps/tasks/ExportVectorTiles.java
+++ 
b/baremaps-core/src/main/java/org/apache/baremaps/tasks/ExportVectorTiles.java
@@ -22,9 +22,11 @@ import static 
org.apache.baremaps.utils.ObjectMapperUtils.objectMapper;
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import java.io.IOException;
+import java.nio.ByteBuffer;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.StandardOpenOption;
+import java.sql.SQLException;
 import java.util.*;
 import java.util.stream.Collectors;
 import javax.sql.DataSource;
@@ -34,6 +36,7 @@ import org.apache.baremaps.maplibre.tileset.Tileset;
 import org.apache.baremaps.maplibre.tileset.TilesetQuery;
 import org.apache.baremaps.openstreetmap.stream.ProgressLogger;
 import org.apache.baremaps.openstreetmap.stream.StreamUtils;
+import org.apache.baremaps.postgres.utils.PostgresUtils;
 import org.apache.baremaps.tilestore.*;
 import org.apache.baremaps.tilestore.file.FileTileStore;
 import org.apache.baremaps.tilestore.mbtiles.MBTilesStore;
@@ -146,7 +149,7 @@ public class ExportVectorTiles implements Task {
 
       var bufferedTileEntryStream = 
StreamUtils.bufferInCompletionOrder(tileCoordStream, tile -> {
         try {
-          return new TileEntry(tile, sourceTileStore.read(tile));
+          return new TileEntry<>(tile, sourceTileStore.read(tile));
         } catch (TileStoreException e) {
           throw new WorkflowException(e);
         }
@@ -166,11 +169,14 @@ public class ExportVectorTiles implements Task {
     }
   }
 
-  private TileStore sourceTileStore(Tileset tileset, DataSource datasource) {
-    return new PostgresTileStore(datasource, tileset);
+  private TileStore<ByteBuffer> sourceTileStore(Tileset tileset, DataSource 
datasource)
+      throws SQLException {
+    var postgresVersion = PostgresUtils.getPostgresVersion(datasource);
+    return new PostgresTileStore(datasource, tileset, postgresVersion);
   }
 
-  private TileStore targetTileStore(Tileset source) throws TileStoreException, 
IOException {
+  private TileStore<ByteBuffer> targetTileStore(Tileset source)
+      throws TileStoreException, IOException {
     switch (format) {
       case FILE:
         return new FileTileStore(repository.resolve("tiles"));
diff --git 
a/baremaps-core/src/main/java/org/apache/baremaps/tilestore/postgres/PostgresTileStore.java
 
b/baremaps-core/src/main/java/org/apache/baremaps/tilestore/postgres/PostgresTileStore.java
index b43514593..8c9caf173 100644
--- 
a/baremaps-core/src/main/java/org/apache/baremaps/tilestore/postgres/PostgresTileStore.java
+++ 
b/baremaps-core/src/main/java/org/apache/baremaps/tilestore/postgres/PostgresTileStore.java
@@ -22,8 +22,6 @@ import java.io.ByteArrayOutputStream;
 import java.io.OutputStream;
 import java.nio.ByteBuffer;
 import java.sql.ResultSet;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
 import java.util.zip.GZIPOutputStream;
 import javax.sql.DataSource;
 import org.apache.baremaps.maplibre.tileset.Tileset;
@@ -46,22 +44,20 @@ public class PostgresTileStore implements 
TileStore<ByteBuffer> {
 
   private final Tileset tileset;
 
+  private final int postgresVersion;
+
   /**
    * Constructs a {@code PostgresTileStore}.
    *
    * @param datasource the datasource
    * @param tileset the tileset
    */
-  public PostgresTileStore(DataSource datasource, Tileset tileset) {
+  public PostgresTileStore(DataSource datasource, Tileset tileset, int 
postgresVersion) {
     this.datasource = datasource;
     this.tileset = tileset;
+    this.postgresVersion = postgresVersion;
   }
 
-  /**
-   * A cache of queries.
-   */
-  private final Map<Integer, Query> cache = new ConcurrentHashMap<>();
-
   /**
    * A record that holds the sql of a prepared statement and the number of 
parameters.
    *
@@ -76,7 +72,7 @@ public class PostgresTileStore implements 
TileStore<ByteBuffer> {
     var start = System.currentTimeMillis();
 
     // Prepare and cache the query
-    var query = cache.computeIfAbsent(tileCoord.z(), z -> 
prepareQuery(tileset, z));
+    var query = prepareQuery(tileCoord);
 
     // Fetch and compress the tile data
     try (var connection = datasource.getConnection();
@@ -119,14 +115,13 @@ public class PostgresTileStore implements 
TileStore<ByteBuffer> {
   }
 
   /**
-   * Prepare the sql query for a given tileset and zoom level.
+   * Prepare the sql query for a given zoom level.
    *
-   * @param tileset the tileset
-   * @param zoom the zoom level
-   * @return
+   * @param tileCoord the tile coordinate
+   * @return the prepared query
    */
   @SuppressWarnings("squid:S3776")
-  protected static Query prepareQuery(Tileset tileset, int zoom) {
+  protected Query prepareQuery(TileCoord tileCoord) {
     // Initialize a builder for the tile sql
     var tileSql = new StringBuilder();
     tileSql.append("SELECT ");
@@ -150,7 +145,7 @@ public class PostgresTileStore implements 
TileStore<ByteBuffer> {
       for (var query : queries) {
 
         // Only include the sql if the zoom level is in the range
-        if (query.getMinzoom() <= zoom && zoom < query.getMaxzoom()) {
+        if (query.getMinzoom() <= tileCoord.z() && tileCoord.z() < 
query.getMaxzoom()) {
 
           // Add a union between queries
           if (queryCount > 0) {
@@ -162,28 +157,14 @@ public class PostgresTileStore implements 
TileStore<ByteBuffer> {
               .replaceAll("\\s+", " ")
               .replace(";", "")
               .replace("?", "??")
-              .replace("$zoom", String.valueOf(zoom));
+              .replace("$zoom", String.valueOf(tileCoord.z()))
+              .replace("$z", String.valueOf(tileCoord.z()))
+              .replace("$x", String.valueOf(tileCoord.x()))
+              .replace("$y", String.valueOf(tileCoord.y()));
 
-          // Append a new condition or a where clause
-          if (querySql.toLowerCase().contains("where")) {
-            querySql += " AND ";
-          } else {
-            querySql += " WHERE ";
-          }
+          var querySqlWithParams =
+              postgresVersion >= 16 ? prepareNewQuery(querySql) : 
prepareLegacyQuery(querySql);
 
-          // Append the condition to the query sql
-          querySql +=
-              "geom IS NOT NULL AND geom && ST_TileEnvelope(?, ?, ?, margin => 
(64.0/4096))";
-
-          var querySqlWithParams = String.format(
-              """
-                  SELECT
-                    tile.id AS id,
-                    tile.tags - 'id' AS tags,
-                    ST_AsMVTGeom(tile.geom, ST_TileEnvelope(?, ?, ?)) AS geom
-                  FROM (%s) as tile
-                  """,
-              querySql);
           layerSql.append(querySqlWithParams);
 
           // Increase the parameter count (e.g. ?) and sql count
@@ -222,6 +203,65 @@ public class PostgresTileStore implements 
TileStore<ByteBuffer> {
     return new Query(sql, paramCount);
   }
 
+  /**
+   * Prepare the sql query for the new versions of postgresql (>= 16).
+   * <p>
+   * Recent versions of the postgresql database better optimize subqueries. 
Using subqueries is more
+   * robust and allows for more complex queries.
+   *
+   * @param sql the sql query
+   * @return the prepared query
+   */
+  @SuppressWarnings("squid:S3776")
+  private String prepareNewQuery(final String sql) {
+    return String.format(
+        """
+            SELECT
+            mvtData.id AS id,
+            mvtData.tags - 'id' AS tags,
+            ST_AsMVTGeom(mvtData.geom, ST_TileEnvelope(?, ?, ?)) AS geom
+            FROM (%s) AS mvtData
+            WHERE mvtData.geom IS NOT NULL
+            AND mvtData.geom && ST_TileEnvelope(?, ?, ?, margin => (64.0/4096))
+            """,
+        sql);
+  }
+
+  /**
+   * Prepare the sql query for the legacy versions of postgresql (< 16).
+   * <p>
+   * Older versions of the postgresql database do not optimize subqueries. 
Therefore, the conditions
+   * are appended to the sql query, which is less robust and error-prone.
+   *
+   * @param sql the sql query
+   * @return the prepared query
+   */
+  @SuppressWarnings("squid:S3776")
+  private String prepareLegacyQuery(final String sql) {
+    String query = sql;
+
+    // Append a new condition or a where clause
+    if (sql.toLowerCase().contains("where")) {
+      query += " AND ";
+    } else {
+      query += " WHERE ";
+    }
+
+    // Append the condition to the query sql
+    query +=
+        "geom IS NOT NULL AND geom && ST_TileEnvelope(?, ?, ?, margin => 
(64.0/4096))";
+
+    return String.format(
+        """
+            SELECT
+              mvtData.id AS id,
+              mvtData.tags - 'id' AS tags,
+              ST_AsMVTGeom(mvtData.geom, ST_TileEnvelope(?, ?, ?)) AS geom
+            FROM (%s) as mvtData
+            """,
+        query);
+  }
+
   /**
    * This operation is not supported.
    */
diff --git 
a/baremaps-core/src/test/java/org/apache/baremaps/tilestore/postgres/PostgresTileStoreTest.java
 
b/baremaps-core/src/test/java/org/apache/baremaps/tilestore/postgres/PostgresTileStoreTest.java
index bc593c4c8..becaece27 100644
--- 
a/baremaps-core/src/test/java/org/apache/baremaps/tilestore/postgres/PostgresTileStoreTest.java
+++ 
b/baremaps-core/src/test/java/org/apache/baremaps/tilestore/postgres/PostgresTileStoreTest.java
@@ -24,13 +24,17 @@ import java.util.Map;
 import org.apache.baremaps.maplibre.tileset.Tileset;
 import org.apache.baremaps.maplibre.tileset.TilesetLayer;
 import org.apache.baremaps.maplibre.tileset.TilesetQuery;
+import org.apache.baremaps.tilestore.TileCoord;
+import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
 class PostgresTileStoreTest {
 
-  @Test
-  void prepareQuery() {
-    var tileset = new Tileset();
+  private Tileset tileset;
+
+  @BeforeEach
+  void prepare() {
+    tileset = new Tileset();
     tileset.setMinzoom(0);
     tileset.setMaxzoom(20);
     tileset.setVectorLayers(List.of(
@@ -38,9 +42,24 @@ class PostgresTileStoreTest {
             List.of(new TilesetQuery(0, 20, "SELECT id, tags, geom FROM 
table"))),
         new TilesetLayer("b", Map.of(), "", 0, 20,
             List.of(new TilesetQuery(0, 20, "SELECT id, tags, geom FROM 
table")))));
-    var query = PostgresTileStore.prepareQuery(tileset, 10);
+
+  }
+
+  @Test
+  void prepareNewQuery() {
+    var postgresTileStore = new PostgresTileStore(null, tileset, 16);
+    var query = postgresTileStore.prepareQuery(new TileCoord(1, 1, 10));
+    assertEquals(
+        "SELECT (SELECT ST_AsMVT(mvtGeom.*, 'a') FROM (SELECT mvtData.id AS 
id, mvtData.tags - 'id' AS tags, ST_AsMVTGeom(mvtData.geom, ST_TileEnvelope(?, 
?, ?)) AS geom FROM (SELECT id, tags, geom FROM table) AS mvtData WHERE 
mvtData.geom IS NOT NULL AND mvtData.geom && ST_TileEnvelope(?, ?, ?, margin => 
(64.0/4096)) ) AS mvtGeom) || (SELECT ST_AsMVT(mvtGeom.*, 'b') FROM (SELECT 
mvtData.id AS id, mvtData.tags - 'id' AS tags, ST_AsMVTGeom(mvtData.geom, 
ST_TileEnvelope(?, ?, ?)) AS geom F [...]
+        query.sql());
+  }
+
+  @Test
+  void prepareLegacyQuery() {
+    var postgresTileStore = new PostgresTileStore(null, tileset, 15);
+    var query = postgresTileStore.prepareQuery(new TileCoord(1, 1, 10));
     assertEquals(
-        "SELECT (SELECT ST_AsMVT(mvtGeom.*, 'a') FROM (SELECT tile.id AS id, 
tile.tags - 'id' AS tags, ST_AsMVTGeom(tile.geom, ST_TileEnvelope(?, ?, ?)) AS 
geom FROM (SELECT id, tags, geom FROM table WHERE geom IS NOT NULL AND geom && 
ST_TileEnvelope(?, ?, ?, margin => (64.0/4096))) as tile ) AS mvtGeom) || 
(SELECT ST_AsMVT(mvtGeom.*, 'b') FROM (SELECT tile.id AS id, tile.tags - 'id' 
AS tags, ST_AsMVTGeom(tile.geom, ST_TileEnvelope(?, ?, ?)) AS geom FROM (SELECT 
id, tags, geom FROM table [...]
+        "SELECT (SELECT ST_AsMVT(mvtGeom.*, 'a') FROM (SELECT mvtData.id AS 
id, mvtData.tags - 'id' AS tags, ST_AsMVTGeom(mvtData.geom, ST_TileEnvelope(?, 
?, ?)) AS geom FROM (SELECT id, tags, geom FROM table WHERE geom IS NOT NULL 
AND geom && ST_TileEnvelope(?, ?, ?, margin => (64.0/4096))) as mvtData ) AS 
mvtGeom) || (SELECT ST_AsMVT(mvtGeom.*, 'b') FROM (SELECT mvtData.id AS id, 
mvtData.tags - 'id' AS tags, ST_AsMVTGeom(mvtData.geom, ST_TileEnvelope(?, ?, 
?)) AS geom FROM (SELECT id,  [...]
         query.sql());
   }
 }
diff --git 
a/baremaps-postgres/src/main/java/org/apache/baremaps/postgres/utils/PostgresUtils.java
 
b/baremaps-postgres/src/main/java/org/apache/baremaps/postgres/utils/PostgresUtils.java
index c70db2408..a65cdb13f 100644
--- 
a/baremaps-postgres/src/main/java/org/apache/baremaps/postgres/utils/PostgresUtils.java
+++ 
b/baremaps-postgres/src/main/java/org/apache/baremaps/postgres/utils/PostgresUtils.java
@@ -178,4 +178,17 @@ public final class PostgresUtils {
       statement.execute(queries);
     }
   }
+
+  /**
+   * Gets the version of the Postgres database.
+   *
+   * @param datasource the data source
+   * @return the version of the Postgres database
+   * @throws SQLException if a database access error occurs
+   */
+  public static int getPostgresVersion(DataSource datasource) throws 
SQLException {
+    try (Connection connection = datasource.getConnection()) {
+      return connection.getMetaData().getDatabaseMajorVersion();
+    }
+  }
 }

Reply via email to