22
22
import java .io .OutputStream ;
23
23
import java .nio .ByteBuffer ;
24
24
import java .sql .ResultSet ;
25
- import java .util .Map ;
26
- import java .util .concurrent .ConcurrentHashMap ;
27
25
import java .util .zip .GZIPOutputStream ;
28
26
import javax .sql .DataSource ;
29
27
import org .apache .baremaps .maplibre .tileset .Tileset ;
@@ -60,11 +58,6 @@ public PostgresTileStore(DataSource datasource, Tileset tileset, int postgresVer
60
58
this .postgresVersion = postgresVersion ;
61
59
}
62
60
63
- /**
64
- * A cache of queries.
65
- */
66
- private final Map <Integer , Query > cache = new ConcurrentHashMap <>();
67
-
68
61
/**
69
62
* A record that holds the sql of a prepared statement and the number of parameters.
70
63
*
@@ -79,7 +72,7 @@ public ByteBuffer read(TileCoord tileCoord) throws TileStoreException {
79
72
var start = System .currentTimeMillis ();
80
73
81
74
// Prepare and cache the query
82
- var query = cache . computeIfAbsent (tileCoord . z (), this :: prepareQuery );
75
+ var query = prepareQuery (tileCoord );
83
76
84
77
// Fetch and compress the tile data
85
78
try (var connection = datasource .getConnection ();
@@ -124,25 +117,11 @@ public ByteBuffer read(TileCoord tileCoord) throws TileStoreException {
124
117
/**
125
118
* Prepare the sql query for a given zoom level.
126
119
*
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
142
121
* @return the prepared query
143
122
*/
144
123
@ SuppressWarnings ("squid:S3776" )
145
- private Query prepareNewQuery ( int zoom ) {
124
+ protected Query prepareQuery ( TileCoord tileCoord ) {
146
125
// Initialize a builder for the tile sql
147
126
var tileSql = new StringBuilder ();
148
127
tileSql .append ("SELECT " );
@@ -166,7 +145,7 @@ private Query prepareNewQuery(int zoom) {
166
145
for (var query : queries ) {
167
146
168
147
// 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 ()) {
170
149
171
150
// Add a union between queries
172
151
if (queryCount > 0 ) {
@@ -178,18 +157,14 @@ private Query prepareNewQuery(int zoom) {
178
157
.replaceAll ("\\ s+" , " " )
179
158
.replace (";" , "" )
180
159
.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
+
193
168
layerSql .append (querySqlWithParams );
194
169
195
170
// Increase the parameter count (e.g. ?) and sql count
@@ -223,113 +198,68 @@ AND mvtData.geom && ST_TileEnvelope(?, ?, ?, margin => (64.0/4096))
223
198
tileSql .append (tileQueryTail );
224
199
225
200
// Format the sql query
226
- var sql = tileSql .toString ().replace ("\n " , " " );
201
+ var sql = tileSql .toString ().replaceAll ("\\ s+ " , " " );
227
202
228
203
return new Query (sql , paramCount );
229
204
}
230
205
231
206
/**
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.
234
211
*
235
- * @param zoom the zoom level
212
+ * @param sql the sql query
236
213
* @return the prepared query
237
214
*/
238
215
@ 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
+ }
316
229
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 ;
319
242
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 " ;
323
248
}
324
249
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 );
333
263
}
334
264
335
265
/**
0 commit comments