Skip to content

Commit e4fcfbf

Browse files
committed
Remove duplication and caching
Caching is not needed anymore as String concatenation is faster than query parsing.
1 parent a933b6d commit e4fcfbf

File tree

2 files changed

+64
-133
lines changed

2 files changed

+64
-133
lines changed

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

Lines changed: 61 additions & 131 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;
@@ -60,11 +58,6 @@ public PostgresTileStore(DataSource datasource, Tileset tileset, int postgresVer
6058
this.postgresVersion = postgresVersion;
6159
}
6260

63-
/**
64-
* A cache of queries.
65-
*/
66-
private final Map<Integer, Query> cache = new ConcurrentHashMap<>();
67-
6861
/**
6962
* A record that holds the sql of a prepared statement and the number of parameters.
7063
*
@@ -79,7 +72,7 @@ public ByteBuffer read(TileCoord tileCoord) throws TileStoreException {
7972
var start = System.currentTimeMillis();
8073

8174
// Prepare and cache the query
82-
var query = cache.computeIfAbsent(tileCoord.z(), this::prepareQuery);
75+
var query = prepareQuery(tileCoord);
8376

8477
// Fetch and compress the tile data
8578
try (var connection = datasource.getConnection();
@@ -124,25 +117,11 @@ public ByteBuffer read(TileCoord tileCoord) throws TileStoreException {
124117
/**
125118
* Prepare the sql query for a given zoom level.
126119
*
127-
* @param zoom the zoom level
128-
* @return the prepared query
129-
*/
130-
protected Query prepareQuery(int zoom) {
131-
if (postgresVersion >= 16) {
132-
return prepareNewQuery(zoom);
133-
} else {
134-
return prepareLegacyQuery(zoom);
135-
}
136-
}
137-
138-
/**
139-
* Prepare the sql query for a given zoom level that uses the new version of postgresql (>= 16).
140-
*
141-
* @param zoom the zoom level
120+
* @param tileCoord the tile coordinate
142121
* @return the prepared query
143122
*/
144123
@SuppressWarnings("squid:S3776")
145-
private Query prepareNewQuery(int zoom) {
124+
protected Query prepareQuery(TileCoord tileCoord) {
146125
// Initialize a builder for the tile sql
147126
var tileSql = new StringBuilder();
148127
tileSql.append("SELECT ");
@@ -166,7 +145,7 @@ private Query prepareNewQuery(int zoom) {
166145
for (var query : queries) {
167146

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

171150
// Add a union between queries
172151
if (queryCount > 0) {
@@ -178,18 +157,14 @@ private Query prepareNewQuery(int zoom) {
178157
.replaceAll("\\s+", " ")
179158
.replace(";", "")
180159
.replace("?", "??")
181-
.replace("$zoom", String.valueOf(zoom));
182-
var querySqlWithParams = String.format(
183-
"""
184-
SELECT
185-
mvtData.id AS id,
186-
mvtData.tags - 'id' AS tags,
187-
ST_AsMVTGeom(mvtData.geom, ST_TileEnvelope(?, ?, ?)) AS geom
188-
FROM (%s) AS mvtData
189-
WHERE mvtData.geom IS NOT NULL
190-
AND mvtData.geom && ST_TileEnvelope(?, ?, ?, margin => (64.0/4096))
191-
""",
192-
querySql);
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()));
164+
165+
var querySqlWithParams =
166+
postgresVersion >= 16 ? prepareNewQuery(querySql) : prepareLegacyQuery(querySql);
167+
193168
layerSql.append(querySqlWithParams);
194169

195170
// Increase the parameter count (e.g. ?) and sql count
@@ -223,113 +198,68 @@ AND mvtData.geom && ST_TileEnvelope(?, ?, ?, margin => (64.0/4096))
223198
tileSql.append(tileQueryTail);
224199

225200
// Format the sql query
226-
var sql = tileSql.toString().replace("\n", " ");
201+
var sql = tileSql.toString().replaceAll("\\s+", " ");
227202

228203
return new Query(sql, paramCount);
229204
}
230205

231206
/**
232-
* Prepare the sql query for a given zoom level that uses the legacy versions of postgresql (<
233-
* 16).
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.
234211
*
235-
* @param zoom the zoom level
212+
* @param sql the sql query
236213
* @return the prepared query
237214
*/
238215
@SuppressWarnings("squid:S3776")
239-
private Query prepareLegacyQuery(int zoom) {
240-
// Initialize a builder for the tile sql
241-
var tileSql = new StringBuilder();
242-
tileSql.append("SELECT ");
243-
244-
// Iterate over the layers and keep track of the number of layers and parameters included in the
245-
// final sql
246-
var layers = tileset.getVectorLayers();
247-
var layerCount = 0;
248-
var paramCount = 0;
249-
for (var layer : layers) {
250-
251-
// Initialize a builder for the layer sql
252-
var layerSql = new StringBuilder();
253-
var layerHead = String.format("(SELECT ST_AsMVT(mvtGeom.*, '%s') FROM (", layer.getId());
254-
layerSql.append(layerHead);
255-
256-
// Iterate over the queries and keep track of the number of queries included in the final
257-
// sql
258-
var queries = layer.getQueries();
259-
var queryCount = 0;
260-
for (var query : queries) {
261-
262-
// Only include the sql if the zoom level is in the range
263-
if (query.getMinzoom() <= zoom && zoom < query.getMaxzoom()) {
264-
265-
// Add a union between queries
266-
if (queryCount > 0) {
267-
layerSql.append("UNION ALL ");
268-
}
269-
270-
// Add the sql to the layer sql
271-
var querySql = query.getSql().trim()
272-
.replaceAll("\\s+", " ")
273-
.replace(";", "")
274-
.replace("?", "??")
275-
.replace("$zoom", String.valueOf(zoom));
276-
277-
// Append a new condition or a where clause
278-
if (querySql.toLowerCase().contains("where")) {
279-
querySql += " AND ";
280-
} else {
281-
querySql += " WHERE ";
282-
}
283-
284-
// Append the condition to the query sql
285-
querySql +=
286-
"geom IS NOT NULL AND geom && ST_TileEnvelope(?, ?, ?, margin => (64.0/4096))";
287-
288-
var querySqlWithParams = String.format(
289-
"""
290-
SELECT
291-
mvtData.id AS id,
292-
mvtData.tags - 'id' AS tags,
293-
ST_AsMVTGeom(mvtData.geom, ST_TileEnvelope(?, ?, ?)) AS geom
294-
FROM (%s) as mvtData
295-
""",
296-
querySql);
297-
layerSql.append(querySqlWithParams);
298-
299-
// Increase the parameter count (e.g. ?) and sql count
300-
paramCount += 6;
301-
queryCount++;
302-
}
303-
}
304-
305-
// Add the tail of the layer sql
306-
var layerQueryTail = ") AS mvtGeom)";
307-
layerSql.append(layerQueryTail);
308-
309-
// Only include the layer sql if queries were included for this layer
310-
if (queryCount > 0) {
311-
312-
// Add the concatenation between layer queries
313-
if (layerCount > 0) {
314-
tileSql.append(" || ");
315-
}
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+
}
316229

317-
// Add the layer sql to the mvt sql
318-
tileSql.append(layerSql);
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;
319242

320-
// Increase the layer count
321-
layerCount++;
322-
}
243+
// Append a new condition or a where clause
244+
if (sql.toLowerCase().contains("where")) {
245+
query += " AND ";
246+
} else {
247+
query += " WHERE ";
323248
}
324249

325-
// Add the tail of the tile sql
326-
var tileQueryTail = " AS mvtTile";
327-
tileSql.append(tileQueryTail);
328-
329-
// Format the sql query
330-
var sql = tileSql.toString().replaceAll("\\s+", " ");
331-
332-
return new Query(sql, paramCount);
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);
333263
}
334264

335265
/**

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
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;
2728
import org.junit.jupiter.api.BeforeEach;
2829
import org.junit.jupiter.api.Test;
2930

@@ -47,7 +48,7 @@ void prepare() {
4748
@Test
4849
void prepareNewQuery() {
4950
var postgresTileStore = new PostgresTileStore(null, tileset, 16);
50-
var query = postgresTileStore.prepareQuery(10);
51+
var query = postgresTileStore.prepareQuery(new TileCoord(1, 1, 10));
5152
assertEquals(
5253
"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",
5354
query.sql());
@@ -56,7 +57,7 @@ void prepareNewQuery() {
5657
@Test
5758
void prepareLegacyQuery() {
5859
var postgresTileStore = new PostgresTileStore(null, tileset, 15);
59-
var query = postgresTileStore.prepareQuery(10);
60+
var query = postgresTileStore.prepareQuery(new TileCoord(1, 1, 10));
6061
assertEquals(
6162
"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",
6263
query.sql());

0 commit comments

Comments
 (0)