From 33b7c78bdd573ed6d47fd4d74589e6d32231f52c Mon Sep 17 00:00:00 2001 From: zbx1425 Date: Fri, 21 Jul 2023 23:48:14 +0800 Subject: [PATCH] Remove SQLite JDBC --- build.gradle | 5 - .../java/cn/zbx1425/worldcomment/Main.java | 20 ++-- .../worldcomment/data/CommentEntry.java | 99 +++++++++------- .../worldcomment/data/CommentTable.java | 111 ++++++++++-------- .../zbx1425/worldcomment/data/Database.java | 67 ++--------- .../worldcomment/data/DimensionTable.java | 70 ----------- 6 files changed, 132 insertions(+), 240 deletions(-) delete mode 100644 common/src/main/java/cn/zbx1425/worldcomment/data/DimensionTable.java diff --git a/build.gradle b/build.gradle index a25c5d6..157df88 100644 --- a/build.gradle +++ b/build.gradle @@ -78,11 +78,6 @@ subprojects { officialMojangMappings() parchment("org.parchmentmc.data:parchment-${minecraft_version}:${parchment_version}@zip") } - - implementation 'org.xerial:sqlite-jdbc:3.42.0.0' - if (!rootProject.properties.containsKey("noShadowSqlite")) { - shadowCommon 'org.xerial:sqlite-jdbc:3.42.0.0' - } } } diff --git a/common/src/main/java/cn/zbx1425/worldcomment/Main.java b/common/src/main/java/cn/zbx1425/worldcomment/Main.java index 8123862..77d2a31 100644 --- a/common/src/main/java/cn/zbx1425/worldcomment/Main.java +++ b/common/src/main/java/cn/zbx1425/worldcomment/Main.java @@ -9,14 +9,15 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.sql.SQLException; +import java.io.IOException; public class Main { public static final String MOD_ID = "worldcomment"; - public static final Logger LOGGER = LoggerFactory.getLogger("Subnoteica"); + public static Database DATABASE; + public static final RegistryObject ITEM_COMMENT_TOOL = new RegistryObject<>(CommentToolItem::new); public static void init(RegistriesWrapper registries) { @@ -24,17 +25,10 @@ public static void init(RegistriesWrapper registries) { ServerPlatform.registerServerStartingEvent(server -> { try { - Database.loadDatabase(server); - } catch (SQLException e) { - LOGGER.error("Failed to open database", e); - throw new RuntimeException(e); - } - }); - ServerPlatform.registerServerStoppingEvent(server -> { - try { - Database.INSTANCE.close(); - } catch (SQLException e) { - LOGGER.error("Failed to close database", e); + DATABASE = new Database(server); + DATABASE.load(); + } catch (IOException e) { + LOGGER.error("Failed to open data storage", e); throw new RuntimeException(e); } }); diff --git a/common/src/main/java/cn/zbx1425/worldcomment/data/CommentEntry.java b/common/src/main/java/cn/zbx1425/worldcomment/data/CommentEntry.java index fb854ed..8779aee 100644 --- a/common/src/main/java/cn/zbx1425/worldcomment/data/CommentEntry.java +++ b/common/src/main/java/cn/zbx1425/worldcomment/data/CommentEntry.java @@ -1,20 +1,25 @@ package cn.zbx1425.worldcomment.data; +import io.netty.buffer.Unpooled; import net.minecraft.core.BlockPos; -import net.minecraft.core.UUIDUtil; +import net.minecraft.network.FriendlyByteBuf; import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.player.Player; import net.minecraft.world.level.ChunkPos; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; import java.nio.ByteBuffer; -import java.sql.ResultSet; -import java.sql.SQLException; import java.util.UUID; public class CommentEntry { + public static int REGION_SHIFT = 2; + public long id; public long timestamp; - public boolean deleted; public ResourceLocation level; public ChunkPos region; public BlockPos location; @@ -22,21 +27,39 @@ public class CommentEntry { public String initiatorName; public int messageType; public String message; + public boolean deleted; public String imageUrl; - public CommentEntry(CommentTable table, ResultSet result) throws SQLException { - int iota = 0; - id = result.getLong(++iota); - timestamp = result.getLong(++iota); - deleted = result.getBoolean(++iota); - level = table.db.dimensions.getDimensionById(result.getInt(++iota)); - region = new ChunkPos(result.getLong(++iota)); - location = new BlockPos(result.getInt(++iota), result.getInt(++iota), result.getInt(++iota)); - initiator = uuidFromByteArray(result.getBytes(++iota)); - initiatorName = result.getString(++iota); - messageType = result.getInt(++iota); - message = result.getString(++iota); - imageUrl = result.getString(++iota); + public long fileOffset; + + public CommentEntry(Player initiator, BlockPos placedAt, int messageType, String message, String imageUrl) { + id = Database.SNOWFLAKE.nextId(); + timestamp = System.currentTimeMillis(); + level = initiator.level().dimension().location(); + location = placedAt; + region = new ChunkPos(location.getX() >> REGION_SHIFT, location.getZ() >> REGION_SHIFT); + this.initiator = initiator.getGameProfile().getId(); + initiatorName = initiator.getGameProfile().getName(); + this.messageType = messageType; + this.message = message; + deleted = false; + this.imageUrl = imageUrl; + } + + public CommentEntry(ResourceLocation level, FriendlyByteBuf src) { + fileOffset = src.readerIndex(); + id = src.readLong(); + timestamp = src.readLong(); + this.level = level; + location = src.readBlockPos(); + region = new ChunkPos(location.getX() >> REGION_SHIFT, location.getZ() >> REGION_SHIFT); + initiator = src.readUUID(); + initiatorName = src.readUtf(); + messageType = src.readInt(); + message = src.readUtf(); + deleted = src.readBoolean(); + imageUrl = src.readUtf(); + src.skipBytes(16 - (src.readerIndex() % 16)); } private static UUID uuidFromByteArray(byte[] bytes) { @@ -44,30 +67,24 @@ private static UUID uuidFromByteArray(byte[] bytes) { return new UUID(bb.getLong(), bb.getLong()); } - public void insertTo(CommentTable table) throws SQLException { - table.db.execute(""" - INSERT OR REPLACE INTO comments ( - id, timestamp, deleted, level, region, locationX, locationY, locationZ, initiator, initiatorName, messageType, message, imageUrl - ) VALUES ( - ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? - ); - """, params -> { - int iota = 0; - if (id == 0) id = Database.SNOWFLAKE.nextId(); - params.setLong(++iota, id); - params.setLong(++iota, timestamp); - params.setBoolean(++iota, deleted); - params.setInt(++iota, table.db.dimensions.getDimensionId(level)); - params.setLong(++iota, region.toLong()); - params.setInt(++iota, location.getX()); - params.setInt(++iota, location.getY()); - params.setInt(++iota, location.getZ()); - params.setBytes(++iota, UUIDUtil.uuidToByteArray(initiator)); - params.setString(++iota, initiatorName); - params.setInt(++iota, messageType); - params.setString(++iota, message); - params.setString(++iota, imageUrl); - }); + public void writeBuffer(FriendlyByteBuf dst) { + dst.writeLong(id); + dst.writeLong(timestamp); + dst.writeBlockPos(location); + dst.writeUUID(initiator); + dst.writeUtf(initiatorName); + dst.writeInt(messageType); + dst.writeUtf(message); + dst.writeBoolean(deleted); + dst.writeUtf(imageUrl); + dst.writeZero(16 - (dst.writerIndex() % 16)); + } + + public void writeFileStream(FileOutputStream oStream) throws IOException { + FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer(256)); + writeBuffer(buf); + fileOffset = oStream.getChannel().position(); + oStream.write(buf.array(), 0, buf.writerIndex()); } } diff --git a/common/src/main/java/cn/zbx1425/worldcomment/data/CommentTable.java b/common/src/main/java/cn/zbx1425/worldcomment/data/CommentTable.java index c21d8df..6094169 100644 --- a/common/src/main/java/cn/zbx1425/worldcomment/data/CommentTable.java +++ b/common/src/main/java/cn/zbx1425/worldcomment/data/CommentTable.java @@ -1,75 +1,82 @@ package cn.zbx1425.worldcomment.data; +import io.netty.buffer.Unpooled; +import it.unimi.dsi.fastutil.longs.Long2ObjectMap; +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import net.minecraft.network.FriendlyByteBuf; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.Level; -import java.sql.ResultSet; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; import java.sql.SQLException; -import java.util.ArrayList; -import java.util.List; +import java.util.*; +import java.util.stream.Stream; public class CommentTable { public final Database db; - public int maxId; + + Map>> regionIndex = new HashMap<>(); + Map> playerIndex = new HashMap<>(); public CommentTable(Database db) { this.db = db; } - public void init() throws SQLException { - db.execute(""" - CREATE TABLE IF NOT EXISTS comments ( - id INTEGER PRIMARY KEY, - timestamp INTEGER, - deleted INTEGER, - level INTEGER, - region INTEGER, - locationX INTEGER, - locationY INTEGER, - locationZ INTEGER, - initiator BLOB, - initiatorName TEXT, - messageType INTEGER, - message TEXT, - imageUrl TEXT - ); - CREATE INDEX IF NOT EXISTS regionIndex ON comments ( - level, - region - ); - CREATE INDEX IF NOT EXISTS timestampIndex ON comments ( - timestamp - ); - """); - } + public void load() throws IOException { + Files.createDirectories(db.basePath.resolve("regions")); + for (Level level : db.server.getAllLevels()) { + ResourceLocation dimension = level.dimension().location(); + Path levelPath = getLevelPath(dimension); + Files.createDirectory(levelPath); + regionIndex.put(dimension, new Long2ObjectOpenHashMap<>()); + try (Stream files = Files.list(levelPath)) { + for (Path file : files.toList()) { + long region = Long.parseUnsignedLong(file.getFileName().toString().substring(1, 9), 16); + List regionEntries = new ArrayList<>(); + regionIndex.get(dimension).put(region, regionEntries); - public List queryInRegion(ResourceLocation level, ChunkPos region) throws SQLException { - ArrayList entries = new ArrayList<>(); - try (ResultSet result = db.executeQuery( - "SELECT * FROM comments WHERE level = ? AND region = ?", params -> { - params.setInt(1, db.dimensions.getDimensionId(level)); - params.setLong(2, region.toLong()); - } - )) { - while (result.next()) { - entries.add(new CommentEntry(this, result)); + byte[] fileContent = Files.readAllBytes(file); + FriendlyByteBuf src = new FriendlyByteBuf(Unpooled.wrappedBuffer(fileContent)); + while (src.readerIndex() < fileContent.length - 1) { + CommentEntry entry = new CommentEntry(dimension, src); + regionEntries.add(entry); + playerIndex.computeIfAbsent(entry.initiator, ignored -> new ArrayList<>()) + .add(entry); + } + } } } - return entries; } - public List queryInTime(long from) throws SQLException { - ArrayList entries = new ArrayList<>(); - try (ResultSet result = db.executeQuery( - "SELECT * FROM comments WHERE timestamp > ?", params -> { - params.setLong(1, from); - } - )) { - while (result.next()) { - entries.add(new CommentEntry(this, result)); - } + private Path getLevelPath(ResourceLocation dimension) { + return db.basePath.resolve("regions") + .resolve(dimension.getNamespace() + "+" + dimension.getPath()); + } + + private Path getLevelRegionPath(ResourceLocation dimension, ChunkPos region) { + return db.basePath.resolve("regions") + .resolve(dimension.getNamespace() + "+" + dimension.getPath()) + .resolve("r" + Long.toHexString(region.toLong()) + ".bin"); + } + + public List queryInRegion(ResourceLocation level, ChunkPos region) throws SQLException { + return regionIndex.get(level).get(region.toLong()); + } + + public void insert(CommentEntry newEntry) throws IOException { + Path targetFile = getLevelRegionPath(newEntry.level, newEntry.region); + try (FileOutputStream oStream = new FileOutputStream(targetFile.toFile(), true)) { + newEntry.writeFileStream(oStream); } - return entries; + regionIndex.get(newEntry.level) + .computeIfAbsent(newEntry.region.toLong(), ignored -> new ArrayList<>()) + .add(newEntry); + playerIndex.computeIfAbsent(newEntry.initiator, ignored -> new ArrayList<>()) + .add(newEntry); } } diff --git a/common/src/main/java/cn/zbx1425/worldcomment/data/Database.java b/common/src/main/java/cn/zbx1425/worldcomment/data/Database.java index 38c4dec..b879afe 100644 --- a/common/src/main/java/cn/zbx1425/worldcomment/data/Database.java +++ b/common/src/main/java/cn/zbx1425/worldcomment/data/Database.java @@ -3,76 +3,25 @@ import net.minecraft.server.MinecraftServer; import net.minecraft.world.level.storage.LevelResource; +import java.io.IOException; import java.nio.file.Path; -import java.sql.*; -import java.util.HashMap; -import java.util.function.Consumer; public class Database { - public static Database INSTANCE; - public static final Snowflake SNOWFLAKE = new Snowflake(); - private final Connection dbConn; + public final MinecraftServer server; + public final Path basePath; public final CommentTable comments; - public final DimensionTable dimensions; - public Database(Path dbPath) throws SQLException { - dbConn = DriverManager.getConnection("jdbc:sqlite:" + dbPath); + public Database(MinecraftServer server) { + this.server = server; + this.basePath = Path.of(server.getWorldPath(LevelResource.ROOT).toString(), "world-comment"); comments = new CommentTable(this); - dimensions = new DimensionTable(this); - init(); - } - - public static void loadDatabase(MinecraftServer server) throws SQLException { - INSTANCE = new Database( - Path.of(server.getWorldPath(LevelResource.ROOT).toString(), "world-comment.db") - ); - } - - public void close() throws SQLException { - for (PreparedStatement stmt : preparedStatements.values()) { - stmt.close(); - } - preparedStatements.clear(); - dbConn.close(); - } - - private void init() throws SQLException { - comments.init(); - dimensions.init(); - } - - private final HashMap preparedStatements = new HashMap<>(); - - public void execute(String command) throws SQLException { - prepareStatement(command).execute(); - } - - public void execute(String command, StatementConsumer params) throws SQLException { - PreparedStatement stmt = prepareStatement(command); - params.accept(stmt); - stmt.execute(); - } - - public ResultSet executeQuery(String command) throws SQLException { - return prepareStatement(command).executeQuery(); - } - - public ResultSet executeQuery(String command, StatementConsumer params) throws SQLException { - PreparedStatement stmt = prepareStatement(command); - params.accept(stmt); - return stmt.executeQuery(); } - private PreparedStatement prepareStatement(String command) throws SQLException { - PreparedStatement stmt = preparedStatements.get(command); - if (stmt == null) { - stmt = dbConn.prepareStatement(command); - preparedStatements.put(command, stmt); - } - return stmt; + public void load() throws IOException { + comments.load(); } } diff --git a/common/src/main/java/cn/zbx1425/worldcomment/data/DimensionTable.java b/common/src/main/java/cn/zbx1425/worldcomment/data/DimensionTable.java deleted file mode 100644 index 73f438a..0000000 --- a/common/src/main/java/cn/zbx1425/worldcomment/data/DimensionTable.java +++ /dev/null @@ -1,70 +0,0 @@ -package cn.zbx1425.worldcomment.data; - -import it.unimi.dsi.fastutil.objects.Object2IntMap; -import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; -import it.unimi.dsi.fastutil.objects.ObjectArrayList; -import it.unimi.dsi.fastutil.objects.ObjectList; -import net.minecraft.resources.ResourceLocation; - -import java.sql.ResultSet; -import java.sql.SQLException; - -public class DimensionTable { - - public Database db; - - public Object2IntMap dimensionMap = new Object2IntOpenHashMap<>(); - public ObjectList idMap = new ObjectArrayList<>(); - - public DimensionTable(Database db) { - this.db = db; - } - - public void init() throws SQLException { - db.execute(""" - CREATE TABLE IF NOT EXISTS dimensions ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - resourceLocation TEXT - ); - """); - dimensionMap.clear(); - try (ResultSet result = db.executeQuery("SELECT * FROM dimensions;")) { - while (result.next()) { - ResourceLocation name = new ResourceLocation(result.getString(2)); - int id = result.getInt(1); - dimensionMap.put(name, id); - idMap.size(Math.max(idMap.size(), id)); - idMap.set(id - 1, name); - } - } - } - - public int getMaxId() throws SQLException { - try (ResultSet result = db.executeQuery("SELECT id FROM dimensions ORDER BY id DESC LIMIT 1;")) { - if (!result.next()) { - return 0; - } else { - return result.getInt(1); - } - } - } - - public ResourceLocation getDimensionById(int id) { - return idMap.get(id - 1); - } - - public int getDimensionId(ResourceLocation dimension) throws SQLException { - if (!dimensionMap.containsKey(dimension)) { - db.execute("INSERT INTO dimensions (resourceLocation) VALUES (?);", params -> { - params.setString(1, dimension.toString()); - }); - int newId = getMaxId(); - dimensionMap.put(dimension, newId); - idMap.size(Math.max(idMap.size(), newId)); - idMap.set(newId - 1, dimension); - return newId; - } else { - return dimensionMap.getInt(dimension); - } - } -}