Skip to content

Commit

Permalink
Generate different queries depending on the postgresql version
Browse files Browse the repository at this point in the history
  • Loading branch information
bchapuis committed Jan 11, 2025
1 parent 8d858f1 commit a933b6d
Show file tree
Hide file tree
Showing 6 changed files with 170 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ public Integer call() throws Exception {
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 {
Expand All @@ -99,7 +100,7 @@ public Integer call() throws Exception {

var tileStoreSupplier = (Supplier<TileStore<ByteBuffer>>) () -> {
var tileJSON = tilesetSupplier.get();
return new PostgresTileStore(datasource, tileJSON);
return new PostgresTileStore(datasource, tileJSON, postgresVersion);
};

var styleSupplier = (Supplier<Style>) () -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,10 @@ public Integer call() throws Exception {
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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,11 @@
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;
Expand All @@ -34,6 +36,7 @@
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;
Expand Down Expand Up @@ -146,7 +149,7 @@ public void execute(WorkflowContext context) throws Exception {

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);
}
Expand All @@ -166,11 +169,14 @@ public void execute(WorkflowContext context) throws Exception {
}
}

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"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,18 @@ 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;
}

/**
Expand All @@ -76,7 +79,7 @@ public ByteBuffer read(TileCoord tileCoord) throws TileStoreException {
var start = System.currentTimeMillis();

// Prepare and cache the query
var query = cache.computeIfAbsent(tileCoord.z(), z -> prepareQuery(tileset, z));
var query = cache.computeIfAbsent(tileCoord.z(), this::prepareQuery);

// Fetch and compress the tile data
try (var connection = datasource.getConnection();
Expand Down Expand Up @@ -119,14 +122,121 @@ public ByteBuffer read(TileCoord tileCoord) throws TileStoreException {
}

/**
* Prepare the sql query for a given tileset and zoom level.
* Prepare the sql query for a given zoom level.
*
* @param zoom the zoom level
* @return the prepared query
*/
protected Query prepareQuery(int zoom) {
if (postgresVersion >= 16) {
return prepareNewQuery(zoom);
} else {
return prepareLegacyQuery(zoom);
}
}

/**
* Prepare the sql query for a given zoom level that uses the new version of postgresql (>= 16).
*
* @param zoom the zoom level
* @return the prepared query
*/
@SuppressWarnings("squid:S3776")
private Query prepareNewQuery(int zoom) {
// Initialize a builder for the tile sql
var tileSql = new StringBuilder();
tileSql.append("SELECT ");

// Iterate over the layers and keep track of the number of layers and parameters included in the
// final sql
var layers = tileset.getVectorLayers();
var layerCount = 0;
var paramCount = 0;
for (var layer : layers) {

// Initialize a builder for the layer sql
var layerSql = new StringBuilder();
var layerHead = String.format("(SELECT ST_AsMVT(mvtGeom.*, '%s') FROM (", layer.getId());
layerSql.append(layerHead);

// Iterate over the queries and keep track of the number of queries included in the final
// sql
var queries = layer.getQueries();
var queryCount = 0;
for (var query : queries) {

// Only include the sql if the zoom level is in the range
if (query.getMinzoom() <= zoom && zoom < query.getMaxzoom()) {

// Add a union between queries
if (queryCount > 0) {
layerSql.append("UNION ALL ");
}

// Add the sql to the layer sql
var querySql = query.getSql().trim()
.replaceAll("\\s+", " ")
.replace(";", "")
.replace("?", "??")
.replace("$zoom", String.valueOf(zoom));
var querySqlWithParams = 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))
""",
querySql);
layerSql.append(querySqlWithParams);

// Increase the parameter count (e.g. ?) and sql count
paramCount += 6;
queryCount++;
}
}

// Add the tail of the layer sql
var layerQueryTail = ") AS mvtGeom)";
layerSql.append(layerQueryTail);

// Only include the layer sql if queries were included for this layer
if (queryCount > 0) {

// Add the concatenation between layer queries
if (layerCount > 0) {
tileSql.append(" || ");
}

// Add the layer sql to the mvt sql
tileSql.append(layerSql);

// Increase the layer count
layerCount++;
}
}

// Add the tail of the tile sql
var tileQueryTail = " AS mvtTile";
tileSql.append(tileQueryTail);

// Format the sql query
var sql = tileSql.toString().replace("\n", " ");

return new Query(sql, paramCount);
}

/**
* Prepare the sql query for a given zoom level that uses the legacy versions of postgresql (<
* 16).
*
* @param tileset the tileset
* @param zoom the zoom level
* @return
* @return the prepared query
*/
@SuppressWarnings("squid:S3776")
protected static Query prepareQuery(Tileset tileset, int zoom) {
private Query prepareLegacyQuery(int zoom) {
// Initialize a builder for the tile sql
var tileSql = new StringBuilder();
tileSql.append("SELECT ");
Expand Down Expand Up @@ -178,10 +288,10 @@ protected static Query prepareQuery(Tileset tileset, int zoom) {
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
mvtData.id AS id,
mvtData.tags - 'id' AS tags,
ST_AsMVTGeom(mvtData.geom, ST_TileEnvelope(?, ?, ?)) AS geom
FROM (%s) as mvtData
""",
querySql);
layerSql.append(querySqlWithParams);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,23 +24,41 @@
import org.apache.baremaps.maplibre.tileset.Tileset;
import org.apache.baremaps.maplibre.tileset.TilesetLayer;
import org.apache.baremaps.maplibre.tileset.TilesetQuery;
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(
new TilesetLayer("a", Map.of(), "", 0, 20,
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(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 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",
query.sql());
}

@Test
void prepareLegacyQuery() {
var postgresTileStore = new PostgresTileStore(null, tileset, 15);
var query = postgresTileStore.prepareQuery(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 WHERE geom IS NOT NULL AND geom && ST_TileEnvelope(?, ?, ?, margin => (64.0/4096))) as tile ) AS mvtGeom) AS mvtTile",
"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",
query.sql());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -178,4 +178,17 @@ public static void executeResource(Connection connection, String resource)
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();
}
}
}

0 comments on commit a933b6d

Please sign in to comment.