Skip to content

Commit 8c65a30

Browse files
authored
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.
1 parent 9e1b882 commit 8c65a30

File tree

6 files changed

+126
-46
lines changed

6 files changed

+126
-46
lines changed

baremaps-cli/src/main/java/org/apache/baremaps/cli/map/Dev.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ public Integer call() throws Exception {
8787
var objectMapper = objectMapper();
8888
var tileset = objectMapper.readValue(configReader.read(this.tilesetPath), Tileset.class);
8989
var datasource = PostgresUtils.createDataSourceFromObject(tileset.getDatabase());
90+
var postgresVersion = PostgresUtils.getPostgresVersion(datasource);
9091

9192
var tilesetSupplier = (Supplier<Tileset>) () -> {
9293
try {
@@ -99,7 +100,7 @@ public Integer call() throws Exception {
99100

100101
var tileStoreSupplier = (Supplier<TileStore<ByteBuffer>>) () -> {
101102
var tileJSON = tilesetSupplier.get();
102-
return new PostgresTileStore(datasource, tileJSON);
103+
return new PostgresTileStore(datasource, tileJSON, postgresVersion);
103104
};
104105

105106
var styleSupplier = (Supplier<Style>) () -> {

baremaps-cli/src/main/java/org/apache/baremaps/cli/map/Serve.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,9 +93,10 @@ public Integer call() throws Exception {
9393
var caffeineSpec = CaffeineSpec.parse(cache);
9494
var tileset = objectMapper.readValue(configReader.read(tilesetPath), Tileset.class);
9595
var datasource = PostgresUtils.createDataSourceFromObject(tileset.getDatabase());
96+
var postgresVersion = PostgresUtils.getPostgresVersion(datasource);
9697

9798
try (
98-
var tileStore = new PostgresTileStore(datasource, tileset);
99+
var tileStore = new PostgresTileStore(datasource, tileset, postgresVersion);
99100
var tileCache = new VectorTileCache(tileStore, caffeineSpec)) {
100101

101102
var tileStoreSupplier = (Supplier<TileStore<ByteBuffer>>) () -> tileCache;

baremaps-core/src/main/java/org/apache/baremaps/tasks/ExportVectorTiles.java

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,11 @@
2222
import com.fasterxml.jackson.core.JsonProcessingException;
2323
import com.fasterxml.jackson.databind.ObjectMapper;
2424
import java.io.IOException;
25+
import java.nio.ByteBuffer;
2526
import java.nio.file.Files;
2627
import java.nio.file.Path;
2728
import java.nio.file.StandardOpenOption;
29+
import java.sql.SQLException;
2830
import java.util.*;
2931
import java.util.stream.Collectors;
3032
import javax.sql.DataSource;
@@ -34,6 +36,7 @@
3436
import org.apache.baremaps.maplibre.tileset.TilesetQuery;
3537
import org.apache.baremaps.openstreetmap.stream.ProgressLogger;
3638
import org.apache.baremaps.openstreetmap.stream.StreamUtils;
39+
import org.apache.baremaps.postgres.utils.PostgresUtils;
3740
import org.apache.baremaps.tilestore.*;
3841
import org.apache.baremaps.tilestore.file.FileTileStore;
3942
import org.apache.baremaps.tilestore.mbtiles.MBTilesStore;
@@ -146,7 +149,7 @@ public void execute(WorkflowContext context) throws Exception {
146149

147150
var bufferedTileEntryStream = StreamUtils.bufferInCompletionOrder(tileCoordStream, tile -> {
148151
try {
149-
return new TileEntry(tile, sourceTileStore.read(tile));
152+
return new TileEntry<>(tile, sourceTileStore.read(tile));
150153
} catch (TileStoreException e) {
151154
throw new WorkflowException(e);
152155
}
@@ -166,11 +169,14 @@ public void execute(WorkflowContext context) throws Exception {
166169
}
167170
}
168171

169-
private TileStore sourceTileStore(Tileset tileset, DataSource datasource) {
170-
return new PostgresTileStore(datasource, tileset);
172+
private TileStore<ByteBuffer> sourceTileStore(Tileset tileset, DataSource datasource)
173+
throws SQLException {
174+
var postgresVersion = PostgresUtils.getPostgresVersion(datasource);
175+
return new PostgresTileStore(datasource, tileset, postgresVersion);
171176
}
172177

173-
private TileStore targetTileStore(Tileset source) throws TileStoreException, IOException {
178+
private TileStore<ByteBuffer> targetTileStore(Tileset source)
179+
throws TileStoreException, IOException {
174180
switch (format) {
175181
case FILE:
176182
return new FileTileStore(repository.resolve("tiles"));

baremaps-core/src/main/java/org/apache/baremaps/tilestore/postgres/PostgresTileStore.java

Lines changed: 75 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,6 @@
2222
import java.io.OutputStream;
2323
import java.nio.ByteBuffer;
2424
import java.sql.ResultSet;
25-
import java.util.Map;
26-
import java.util.concurrent.ConcurrentHashMap;
2725
import java.util.zip.GZIPOutputStream;
2826
import javax.sql.DataSource;
2927
import org.apache.baremaps.maplibre.tileset.Tileset;
@@ -46,22 +44,20 @@ public class PostgresTileStore implements TileStore<ByteBuffer> {
4644

4745
private final Tileset tileset;
4846

47+
private final int postgresVersion;
48+
4949
/**
5050
* Constructs a {@code PostgresTileStore}.
5151
*
5252
* @param datasource the datasource
5353
* @param tileset the tileset
5454
*/
55-
public PostgresTileStore(DataSource datasource, Tileset tileset) {
55+
public PostgresTileStore(DataSource datasource, Tileset tileset, int postgresVersion) {
5656
this.datasource = datasource;
5757
this.tileset = tileset;
58+
this.postgresVersion = postgresVersion;
5859
}
5960

60-
/**
61-
* A cache of queries.
62-
*/
63-
private final Map<Integer, Query> cache = new ConcurrentHashMap<>();
64-
6561
/**
6662
* A record that holds the sql of a prepared statement and the number of parameters.
6763
*
@@ -76,7 +72,7 @@ public ByteBuffer read(TileCoord tileCoord) throws TileStoreException {
7672
var start = System.currentTimeMillis();
7773

7874
// Prepare and cache the query
79-
var query = cache.computeIfAbsent(tileCoord.z(), z -> prepareQuery(tileset, z));
75+
var query = prepareQuery(tileCoord);
8076

8177
// Fetch and compress the tile data
8278
try (var connection = datasource.getConnection();
@@ -119,14 +115,13 @@ public ByteBuffer read(TileCoord tileCoord) throws TileStoreException {
119115
}
120116

121117
/**
122-
* Prepare the sql query for a given tileset and zoom level.
118+
* Prepare the sql query for a given zoom level.
123119
*
124-
* @param tileset the tileset
125-
* @param zoom the zoom level
126-
* @return
120+
* @param tileCoord the tile coordinate
121+
* @return the prepared query
127122
*/
128123
@SuppressWarnings("squid:S3776")
129-
protected static Query prepareQuery(Tileset tileset, int zoom) {
124+
protected Query prepareQuery(TileCoord tileCoord) {
130125
// Initialize a builder for the tile sql
131126
var tileSql = new StringBuilder();
132127
tileSql.append("SELECT ");
@@ -150,7 +145,7 @@ protected static Query prepareQuery(Tileset tileset, int zoom) {
150145
for (var query : queries) {
151146

152147
// Only include the sql if the zoom level is in the range
153-
if (query.getMinzoom() <= zoom && zoom < query.getMaxzoom()) {
148+
if (query.getMinzoom() <= tileCoord.z() && tileCoord.z() < query.getMaxzoom()) {
154149

155150
// Add a union between queries
156151
if (queryCount > 0) {
@@ -162,28 +157,14 @@ protected static Query prepareQuery(Tileset tileset, int zoom) {
162157
.replaceAll("\\s+", " ")
163158
.replace(";", "")
164159
.replace("?", "??")
165-
.replace("$zoom", String.valueOf(zoom));
160+
.replace("$zoom", String.valueOf(tileCoord.z()))
161+
.replace("$z", String.valueOf(tileCoord.z()))
162+
.replace("$x", String.valueOf(tileCoord.x()))
163+
.replace("$y", String.valueOf(tileCoord.y()));
166164

167-
// Append a new condition or a where clause
168-
if (querySql.toLowerCase().contains("where")) {
169-
querySql += " AND ";
170-
} else {
171-
querySql += " WHERE ";
172-
}
165+
var querySqlWithParams =
166+
postgresVersion >= 16 ? prepareNewQuery(querySql) : prepareLegacyQuery(querySql);
173167

174-
// Append the condition to the query sql
175-
querySql +=
176-
"geom IS NOT NULL AND geom && ST_TileEnvelope(?, ?, ?, margin => (64.0/4096))";
177-
178-
var querySqlWithParams = String.format(
179-
"""
180-
SELECT
181-
tile.id AS id,
182-
tile.tags - 'id' AS tags,
183-
ST_AsMVTGeom(tile.geom, ST_TileEnvelope(?, ?, ?)) AS geom
184-
FROM (%s) as tile
185-
""",
186-
querySql);
187168
layerSql.append(querySqlWithParams);
188169

189170
// Increase the parameter count (e.g. ?) and sql count
@@ -222,6 +203,65 @@ protected static Query prepareQuery(Tileset tileset, int zoom) {
222203
return new Query(sql, paramCount);
223204
}
224205

206+
/**
207+
* Prepare the sql query for the new versions of postgresql (>= 16).
208+
* <p>
209+
* Recent versions of the postgresql database better optimize subqueries. Using subqueries is more
210+
* robust and allows for more complex queries.
211+
*
212+
* @param sql the sql query
213+
* @return the prepared query
214+
*/
215+
@SuppressWarnings("squid:S3776")
216+
private String prepareNewQuery(final String sql) {
217+
return String.format(
218+
"""
219+
SELECT
220+
mvtData.id AS id,
221+
mvtData.tags - 'id' AS tags,
222+
ST_AsMVTGeom(mvtData.geom, ST_TileEnvelope(?, ?, ?)) AS geom
223+
FROM (%s) AS mvtData
224+
WHERE mvtData.geom IS NOT NULL
225+
AND mvtData.geom && ST_TileEnvelope(?, ?, ?, margin => (64.0/4096))
226+
""",
227+
sql);
228+
}
229+
230+
/**
231+
* Prepare the sql query for the legacy versions of postgresql (< 16).
232+
* <p>
233+
* Older versions of the postgresql database do not optimize subqueries. Therefore, the conditions
234+
* are appended to the sql query, which is less robust and error-prone.
235+
*
236+
* @param sql the sql query
237+
* @return the prepared query
238+
*/
239+
@SuppressWarnings("squid:S3776")
240+
private String prepareLegacyQuery(final String sql) {
241+
String query = sql;
242+
243+
// Append a new condition or a where clause
244+
if (sql.toLowerCase().contains("where")) {
245+
query += " AND ";
246+
} else {
247+
query += " WHERE ";
248+
}
249+
250+
// Append the condition to the query sql
251+
query +=
252+
"geom IS NOT NULL AND geom && ST_TileEnvelope(?, ?, ?, margin => (64.0/4096))";
253+
254+
return String.format(
255+
"""
256+
SELECT
257+
mvtData.id AS id,
258+
mvtData.tags - 'id' AS tags,
259+
ST_AsMVTGeom(mvtData.geom, ST_TileEnvelope(?, ?, ?)) AS geom
260+
FROM (%s) as mvtData
261+
""",
262+
query);
263+
}
264+
225265
/**
226266
* This operation is not supported.
227267
*/

baremaps-core/src/test/java/org/apache/baremaps/tilestore/postgres/PostgresTileStoreTest.java

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,23 +24,42 @@
2424
import org.apache.baremaps.maplibre.tileset.Tileset;
2525
import org.apache.baremaps.maplibre.tileset.TilesetLayer;
2626
import org.apache.baremaps.maplibre.tileset.TilesetQuery;
27+
import org.apache.baremaps.tilestore.TileCoord;
28+
import org.junit.jupiter.api.BeforeEach;
2729
import org.junit.jupiter.api.Test;
2830

2931
class PostgresTileStoreTest {
3032

31-
@Test
32-
void prepareQuery() {
33-
var tileset = new Tileset();
33+
private Tileset tileset;
34+
35+
@BeforeEach
36+
void prepare() {
37+
tileset = new Tileset();
3438
tileset.setMinzoom(0);
3539
tileset.setMaxzoom(20);
3640
tileset.setVectorLayers(List.of(
3741
new TilesetLayer("a", Map.of(), "", 0, 20,
3842
List.of(new TilesetQuery(0, 20, "SELECT id, tags, geom FROM table"))),
3943
new TilesetLayer("b", Map.of(), "", 0, 20,
4044
List.of(new TilesetQuery(0, 20, "SELECT id, tags, geom FROM table")))));
41-
var query = PostgresTileStore.prepareQuery(tileset, 10);
45+
46+
}
47+
48+
@Test
49+
void prepareNewQuery() {
50+
var postgresTileStore = new PostgresTileStore(null, tileset, 16);
51+
var query = postgresTileStore.prepareQuery(new TileCoord(1, 1, 10));
52+
assertEquals(
53+
"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 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) AS mvtTile",
54+
query.sql());
55+
}
56+
57+
@Test
58+
void prepareLegacyQuery() {
59+
var postgresTileStore = new PostgresTileStore(null, tileset, 15);
60+
var query = postgresTileStore.prepareQuery(new TileCoord(1, 1, 10));
4261
assertEquals(
43-
"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 WHERE geom IS NOT NULL AND geom && ST_TileEnvelope(?, ?, ?, margin => (64.0/4096))) as tile ) AS mvtGeom) AS mvtTile",
62+
"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, tags, geom FROM table WHERE geom IS NOT NULL AND geom && ST_TileEnvelope(?, ?, ?, margin => (64.0/4096))) as mvtData ) AS mvtGeom) AS mvtTile",
4463
query.sql());
4564
}
4665
}

baremaps-postgres/src/main/java/org/apache/baremaps/postgres/utils/PostgresUtils.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,4 +178,17 @@ public static void executeResource(Connection connection, String resource)
178178
statement.execute(queries);
179179
}
180180
}
181+
182+
/**
183+
* Gets the version of the Postgres database.
184+
*
185+
* @param datasource the data source
186+
* @return the version of the Postgres database
187+
* @throws SQLException if a database access error occurs
188+
*/
189+
public static int getPostgresVersion(DataSource datasource) throws SQLException {
190+
try (Connection connection = datasource.getConnection()) {
191+
return connection.getMetaData().getDatabaseMajorVersion();
192+
}
193+
}
181194
}

0 commit comments

Comments
 (0)