mirror of
https://github.com/BlueMap-Minecraft/BlueMap.git
synced 2024-06-22 20:44:57 +02:00
384 lines
12 KiB
Java
384 lines
12 KiB
Java
|
package de.bluecolored.bluemap.core.storage.sql.commandset;
|
||
|
|
||
|
import com.github.benmanes.caffeine.cache.Caffeine;
|
||
|
import com.github.benmanes.caffeine.cache.LoadingCache;
|
||
|
import de.bluecolored.bluemap.core.storage.compression.Compression;
|
||
|
import de.bluecolored.bluemap.core.storage.sql.Database;
|
||
|
import lombok.RequiredArgsConstructor;
|
||
|
import org.intellij.lang.annotations.Language;
|
||
|
import org.jetbrains.annotations.Nullable;
|
||
|
|
||
|
import java.io.IOException;
|
||
|
import java.sql.*;
|
||
|
import java.util.ArrayList;
|
||
|
import java.util.List;
|
||
|
import java.util.Objects;
|
||
|
|
||
|
@SuppressWarnings("SqlSourceToSinkFlow")
|
||
|
@RequiredArgsConstructor
|
||
|
public abstract class AbstractCommandSet implements CommandSet {
|
||
|
|
||
|
private final Database db;
|
||
|
|
||
|
final LoadingCache<String, Integer> mapKeys = Caffeine.newBuilder()
|
||
|
.build(this::mapKey);
|
||
|
final LoadingCache<Compression, Integer> compressionKeys = Caffeine.newBuilder()
|
||
|
.build(this::compressionKey);
|
||
|
|
||
|
@Language("sql")
|
||
|
public abstract String createMapTableStatement();
|
||
|
|
||
|
@Language("sql")
|
||
|
public abstract String createCompressionTableStatement();
|
||
|
|
||
|
@Language("sql")
|
||
|
public abstract String createMapMetaTableStatement();
|
||
|
|
||
|
@Language("sql")
|
||
|
public abstract String createMapTileTableStatement();
|
||
|
|
||
|
@Language("sql")
|
||
|
public abstract String fixLegacyCompressionIdsStatement();
|
||
|
|
||
|
public void initializeTables() throws IOException {
|
||
|
db.run(connection -> {
|
||
|
executeUpdate(connection, createMapTableStatement());
|
||
|
executeUpdate(connection, createCompressionTableStatement());
|
||
|
executeUpdate(connection, createMapMetaTableStatement());
|
||
|
executeUpdate(connection, createMapTileTableStatement());
|
||
|
});
|
||
|
|
||
|
db.run(connection -> executeUpdate(connection, fixLegacyCompressionIdsStatement()));
|
||
|
}
|
||
|
|
||
|
@Language("sql")
|
||
|
public abstract String writeMapTileStatement();
|
||
|
|
||
|
@Override
|
||
|
public int writeMapTile(
|
||
|
String mapId, int lod, int x, int z, Compression compression,
|
||
|
byte[] bytes
|
||
|
) throws IOException {
|
||
|
int mapKey = Objects.requireNonNull(mapKeys.get(mapId));
|
||
|
int compressionKey = Objects.requireNonNull(compressionKeys.get(compression));
|
||
|
return db.run(connection -> {
|
||
|
return executeUpdate(connection,
|
||
|
writeMapTileStatement(),
|
||
|
mapKey, lod, x, z, compressionKey,
|
||
|
bytes
|
||
|
);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
@Language("sql")
|
||
|
public abstract String readMapTileStatement();
|
||
|
|
||
|
@Override
|
||
|
public byte @Nullable [] readMapTile(String mapId, int lod, int x, int z, Compression compression) throws IOException {
|
||
|
return db.run(connection -> {
|
||
|
ResultSet result = executeQuery(connection,
|
||
|
readMapTileStatement(),
|
||
|
mapId, lod, x, z, compression.getKey().getFormatted()
|
||
|
);
|
||
|
if (!result.next()) return null;
|
||
|
return result.getBytes(1);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
@Language("sql")
|
||
|
public abstract String deleteMapTileStatement();
|
||
|
|
||
|
@Override
|
||
|
public int deleteMapTile(String mapId, int lod, int x, int z, Compression compression) throws IOException {
|
||
|
return db.run(connection -> {
|
||
|
return executeUpdate(connection,
|
||
|
deleteMapTileStatement(),
|
||
|
mapId, lod, x, z, compression.getKey().getFormatted()
|
||
|
);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
@Language("sql")
|
||
|
public abstract String hasMapTileStatement();
|
||
|
|
||
|
@Override
|
||
|
public boolean hasMapTile(String mapId, int lod, int x, int z, Compression compression) throws IOException {
|
||
|
return db.run(connection -> {
|
||
|
ResultSet result = executeQuery(connection,
|
||
|
hasMapTileStatement(),
|
||
|
mapId, lod, x, z, compression.getKey().getFormatted()
|
||
|
);
|
||
|
if (!result.next()) throw new IllegalStateException("Counting query returned empty result!");
|
||
|
return result.getBoolean(1);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
@Language("sql")
|
||
|
public abstract String countAllMapTilesStatement();
|
||
|
|
||
|
@Override
|
||
|
public int countAllMapTiles(String mapId) throws IOException {
|
||
|
return db.run(connection -> {
|
||
|
ResultSet result = executeQuery(connection,
|
||
|
countAllMapTilesStatement(),
|
||
|
mapId
|
||
|
);
|
||
|
if (!result.next()) throw new IllegalStateException("Counting query returned empty result!");
|
||
|
return result.getInt(1);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
@Language("sql")
|
||
|
public abstract String purgeMapTilesStatement();
|
||
|
|
||
|
@Override
|
||
|
public int purgeMapTiles(String mapId, int limit) throws IOException {
|
||
|
return db.run(connection -> {
|
||
|
return executeUpdate(connection,
|
||
|
purgeMapTilesStatement(),
|
||
|
mapId, limit
|
||
|
);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
@Language("sql")
|
||
|
public abstract String listMapTilesStatement();
|
||
|
|
||
|
@Override
|
||
|
public TilePosition[] listMapTiles(String mapId, int lod, Compression compression, int start, int count) throws IOException {
|
||
|
return db.run(connection -> {
|
||
|
ResultSet result = executeQuery(connection,
|
||
|
listMapTilesStatement(),
|
||
|
mapId, lod, compression.getKey().getFormatted(),
|
||
|
count, start
|
||
|
);
|
||
|
|
||
|
TilePosition[] tiles = new TilePosition[count];
|
||
|
int i = 0;
|
||
|
while (result.next()) {
|
||
|
tiles[i++] = new TilePosition(
|
||
|
result.getInt(1),
|
||
|
result.getInt(2)
|
||
|
);
|
||
|
}
|
||
|
|
||
|
if (i < count) {
|
||
|
TilePosition[] trimmed = new TilePosition[i];
|
||
|
System.arraycopy(tiles, 0, trimmed, 0, i);
|
||
|
tiles = trimmed;
|
||
|
}
|
||
|
|
||
|
return tiles;
|
||
|
});
|
||
|
}
|
||
|
|
||
|
@Language("sql")
|
||
|
public abstract String writeMapMetaStatement();
|
||
|
|
||
|
@Override
|
||
|
public int writeMapMeta(String mapId, String itemName, byte[] bytes) throws IOException {
|
||
|
int mapKey = Objects.requireNonNull(mapKeys.get(mapId));
|
||
|
return db.run(connection -> {
|
||
|
return executeUpdate(connection,
|
||
|
writeMapMetaStatement(),
|
||
|
mapKey, itemName,
|
||
|
bytes
|
||
|
);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
@Language("sql")
|
||
|
public abstract String readMapMetaStatement();
|
||
|
|
||
|
@Override
|
||
|
public byte @Nullable [] readMapMeta(String mapId, String itemName) throws IOException {
|
||
|
return db.run(connection -> {
|
||
|
ResultSet result = executeQuery(connection,
|
||
|
readMapMetaStatement(),
|
||
|
mapId, itemName
|
||
|
);
|
||
|
if (!result.next()) return null;
|
||
|
return result.getBytes(1);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
@Language("sql")
|
||
|
public abstract String deleteMapMetaStatement();
|
||
|
|
||
|
@Override
|
||
|
public int deleteMapMeta(String mapId, String itemName) throws IOException {
|
||
|
return db.run(connection -> {
|
||
|
return executeUpdate(connection,
|
||
|
deleteMapMetaStatement(),
|
||
|
mapId, itemName
|
||
|
);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
@Language("sql")
|
||
|
public abstract String hasMapMetaStatement();
|
||
|
|
||
|
@Override
|
||
|
public boolean hasMapMeta(String mapId, String itemName) throws IOException {
|
||
|
return db.run(connection -> {
|
||
|
ResultSet result = executeQuery(connection,
|
||
|
hasMapMetaStatement(),
|
||
|
mapId, itemName
|
||
|
);
|
||
|
if (!result.next()) throw new IllegalStateException("Counting query returned empty result!");
|
||
|
return result.getBoolean(1);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
@Language("sql")
|
||
|
public abstract String purgeMapTileTableStatement();
|
||
|
|
||
|
@Language("sql")
|
||
|
public abstract String purgeMapMetaTableStatement();
|
||
|
|
||
|
@Language("sql")
|
||
|
public abstract String deleteMapStatement();
|
||
|
|
||
|
@Override
|
||
|
public void purgeMap(String mapId) throws IOException {
|
||
|
db.run(connection -> {
|
||
|
|
||
|
executeUpdate(connection,
|
||
|
purgeMapTileTableStatement(),
|
||
|
mapId
|
||
|
);
|
||
|
|
||
|
executeUpdate(connection,
|
||
|
purgeMapMetaTableStatement(),
|
||
|
mapId
|
||
|
);
|
||
|
|
||
|
executeUpdate(connection,
|
||
|
deleteMapStatement(),
|
||
|
mapId
|
||
|
);
|
||
|
|
||
|
});
|
||
|
}
|
||
|
|
||
|
@Language("sql")
|
||
|
public abstract String hasMapStatement();
|
||
|
|
||
|
@Override
|
||
|
public boolean hasMap(String mapId) throws IOException {
|
||
|
return db.run(connection -> {
|
||
|
ResultSet result = executeQuery(connection,
|
||
|
hasMapStatement(),
|
||
|
mapId
|
||
|
);
|
||
|
if (!result.next()) throw new IllegalStateException("Counting query returned empty result!");
|
||
|
return result.getBoolean(1);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
@Language("sql")
|
||
|
public abstract String listMapIdsStatement();
|
||
|
|
||
|
@Override
|
||
|
public String[] listMapIds(int start, int count) throws IOException {
|
||
|
return db.run(connection -> {
|
||
|
ResultSet result = executeQuery(connection,
|
||
|
listMapIdsStatement(),
|
||
|
count, start
|
||
|
);
|
||
|
List<String> mapIds = new ArrayList<>();
|
||
|
while (result.next()) {
|
||
|
mapIds.add(result.getString(1));
|
||
|
}
|
||
|
return mapIds.toArray(String[]::new);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
@Language("sql")
|
||
|
public abstract String findMapKeyStatement();
|
||
|
|
||
|
@Language("sql")
|
||
|
public abstract String createMapKeyStatement();
|
||
|
|
||
|
public int mapKey(String mapId) throws IOException {
|
||
|
return db.run(connection -> {
|
||
|
ResultSet result = executeQuery(connection,
|
||
|
findMapKeyStatement(),
|
||
|
mapId
|
||
|
);
|
||
|
|
||
|
if (result.next())
|
||
|
return result.getInt(1);
|
||
|
|
||
|
PreparedStatement statement = connection.prepareStatement(
|
||
|
createMapKeyStatement(),
|
||
|
Statement.RETURN_GENERATED_KEYS
|
||
|
);
|
||
|
statement.setString(1, mapId);
|
||
|
statement.executeUpdate();
|
||
|
|
||
|
ResultSet keys = statement.getGeneratedKeys();
|
||
|
if (!keys.next()) throw new IllegalStateException("No generated key returned!");
|
||
|
return keys.getInt(1);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
@Language("sql")
|
||
|
public abstract String findCompressionKeyStatement();
|
||
|
|
||
|
@Language("sql")
|
||
|
public abstract String createCompressionKeyStatement();
|
||
|
|
||
|
public int compressionKey(Compression compression) throws IOException {
|
||
|
return db.run(connection -> {
|
||
|
ResultSet result = executeQuery(connection,
|
||
|
findCompressionKeyStatement(),
|
||
|
compression.getKey().getFormatted()
|
||
|
);
|
||
|
|
||
|
if (result.next())
|
||
|
return result.getInt(1);
|
||
|
|
||
|
PreparedStatement statement = connection.prepareStatement(
|
||
|
createCompressionKeyStatement(),
|
||
|
Statement.RETURN_GENERATED_KEYS
|
||
|
);
|
||
|
statement.setString(1, compression.getKey().getFormatted());
|
||
|
statement.executeUpdate();
|
||
|
|
||
|
ResultSet keys = statement.getGeneratedKeys();
|
||
|
if (!keys.next()) throw new IllegalStateException("No generated key returned!");
|
||
|
return keys.getInt(1);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public boolean isClosed() {
|
||
|
return db.isClosed();
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void close() throws IOException {
|
||
|
db.close();
|
||
|
}
|
||
|
|
||
|
protected static ResultSet executeQuery(Connection connection, @Language("sql") String sql, Object... parameters) throws SQLException {
|
||
|
return prepareStatement(connection, sql, parameters).executeQuery();
|
||
|
}
|
||
|
|
||
|
@SuppressWarnings("UnusedReturnValue")
|
||
|
protected static int executeUpdate(Connection connection, @Language("sql") String sql, Object... parameters) throws SQLException {
|
||
|
return prepareStatement(connection, sql, parameters).executeUpdate();
|
||
|
}
|
||
|
|
||
|
private static PreparedStatement prepareStatement(Connection connection, @Language("sql") String sql, Object... parameters) throws SQLException {
|
||
|
// we only use this prepared statement once, but the DB-Driver caches those and reuses them
|
||
|
PreparedStatement statement = connection.prepareStatement(sql);
|
||
|
for (int i = 0; i < parameters.length; i++) {
|
||
|
statement.setObject(i + 1, parameters[i]);
|
||
|
}
|
||
|
return statement;
|
||
|
}
|
||
|
|
||
|
}
|