From bcfbcdc29559b5312d3dc1a7d404af8cdf64d2df Mon Sep 17 00:00:00 2001 From: hamdan Date: Sat, 30 May 2026 21:59:19 +0800 Subject: [PATCH] Add PostgreSQL storage support --- API/build.gradle.kts | 2 +- .../essentials/api/storage/StorageType.java | 1 + DiscordBot/build.gradle.kts | 2 ++ .../config/DiscordDatabaseConfiguration.java | 15 +++++++-- DiscordBot/src/main/resources/config.yml | 6 ++-- build.gradle.kts | 2 +- .../essentials/MainConfiguration.java | 13 ++++++-- .../essentials/storage/ZStorageManager.java | 2 +- .../database/repositeries/UserRepository.java | 32 ++++++++++++++++++- .../storage/storages/SqlStorage.java | 9 +++++- src/main/resources/config.yml | 12 ++++--- src/main/resources/plugin.yml | 1 + 12 files changed, 81 insertions(+), 16 deletions(-) diff --git a/API/build.gradle.kts b/API/build.gradle.kts index d317b1ab..203e2534 100644 --- a/API/build.gradle.kts +++ b/API/build.gradle.kts @@ -10,7 +10,7 @@ dependencies { compileOnly("io.papermc.paper:paper-api:1.21.5-R0.1-SNAPSHOT") compileOnly(files("../libs/zMenu-1.1.1.2.jar")) - implementation("fr.maxlego08.sarah:sarah:1.23") + implementation("fr.maxlego08.sarah:sarah:1.24") implementation("com.tcoded:FoliaLib:0.5.1") implementation("fr.mrmicky:fastboard:2.1.5") } diff --git a/API/src/main/java/fr/maxlego08/essentials/api/storage/StorageType.java b/API/src/main/java/fr/maxlego08/essentials/api/storage/StorageType.java index d0b7951a..7539f02e 100644 --- a/API/src/main/java/fr/maxlego08/essentials/api/storage/StorageType.java +++ b/API/src/main/java/fr/maxlego08/essentials/api/storage/StorageType.java @@ -5,6 +5,7 @@ public enum StorageType { JSON, MYSQL, MARIADB, + POSTGRESQL, SQLITE, HIKARICP, diff --git a/DiscordBot/build.gradle.kts b/DiscordBot/build.gradle.kts index 2a11a75b..e90293c9 100644 --- a/DiscordBot/build.gradle.kts +++ b/DiscordBot/build.gradle.kts @@ -22,6 +22,8 @@ dependencies { implementation("org.yaml:snakeyaml:2.3") implementation("net.dv8tion:JDA:5.2.0") implementation("mysql:mysql-connector-java:8.0.33") + implementation("org.mariadb.jdbc:mariadb-java-client:3.5.6") + implementation("org.postgresql:postgresql:42.7.3") implementation(project(":API")) } diff --git a/DiscordBot/src/main/java/fr/maxlego08/essentials/bot/config/DiscordDatabaseConfiguration.java b/DiscordBot/src/main/java/fr/maxlego08/essentials/bot/config/DiscordDatabaseConfiguration.java index f1f682d5..d917eb04 100644 --- a/DiscordBot/src/main/java/fr/maxlego08/essentials/bot/config/DiscordDatabaseConfiguration.java +++ b/DiscordBot/src/main/java/fr/maxlego08/essentials/bot/config/DiscordDatabaseConfiguration.java @@ -3,10 +3,21 @@ import fr.maxlego08.sarah.DatabaseConfiguration; import fr.maxlego08.sarah.database.DatabaseType; +import java.util.Locale; + public record DiscordDatabaseConfiguration(String tablePrefix, String user, String password, int port, String host, - String database, boolean debug) { + String database, boolean debug, String type) { public DatabaseConfiguration toDatabaseConfiguration() { - return new DatabaseConfiguration(tablePrefix, user, password, port, host, database, debug, DatabaseType.MYSQL); + return new DatabaseConfiguration(tablePrefix, user, password, port, host, database, debug, getDatabaseType()); + } + + private DatabaseType getDatabaseType() { + String value = type == null || type.isBlank() ? "MYSQL" : type; + return switch (value.toUpperCase(Locale.ROOT)) { + case "MARIADB" -> DatabaseType.MARIADB; + case "POSTGRESQL" -> DatabaseType.POSTGRESQL; + default -> DatabaseType.MYSQL; + }; } } diff --git a/DiscordBot/src/main/resources/config.yml b/DiscordBot/src/main/resources/config.yml index 018f02a2..c72efc71 100644 --- a/DiscordBot/src/main/resources/config.yml +++ b/DiscordBot/src/main/resources/config.yml @@ -7,12 +7,14 @@ bot-token: "DISCORD-TOKEN" guild-id: 511516467615760405 database-configuration: + # Database type. Valid values: MYSQL, MARIADB, POSTGRESQL + type: MYSQL # The prefix that will be applied to all tables, if you have several plugins with the same database you must have one. # It is advisable not to change this value tablePrefix: "zessentials_" # IP Address of the machine the database is hosted on host: 192.168.10.10 - # Port of the database, by default, MYSQL's port is 3306 + # Port of the database, by default, MYSQL and MARIADB use 3306, POSTGRESQL uses 5432 port: 3306 # Database username user: homestead @@ -64,4 +66,4 @@ link: # author: # name: "Author Name" # url: "https://authorwebsite.net" # Optional - # iconUrl: "https://groupez.dev/storage/images/users/0/0/0/1.png" \ No newline at end of file + # iconUrl: "https://groupez.dev/storage/images/users/0/0/0/1.png" diff --git a/build.gradle.kts b/build.gradle.kts index 6d6ebaba..983cf15c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -69,7 +69,7 @@ allprojects { // compileOnly("fr.maxlego08.menu:zmenu-api:1.1.0.0") compileOnly(files("libs/zMenu-1.1.1.2.jar")) - compileOnly("fr.maxlego08.sarah:sarah:1.23") + compileOnly("fr.maxlego08.sarah:sarah:1.24") compileOnly("com.tcoded:FoliaLib:0.5.1") compileOnly("fr.mrmicky:fastboard:2.1.5") } diff --git a/src/main/java/fr/maxlego08/essentials/MainConfiguration.java b/src/main/java/fr/maxlego08/essentials/MainConfiguration.java index f290ff38..8f0f41c2 100644 --- a/src/main/java/fr/maxlego08/essentials/MainConfiguration.java +++ b/src/main/java/fr/maxlego08/essentials/MainConfiguration.java @@ -152,11 +152,11 @@ public void load() { configuration.getString("database-configuration.tablePrefix", configuration.getString("database-configuration.table-prefix")), configuration.getString("database-configuration.user"), configuration.getString("database-configuration.password"), - configuration.getInt("database-configuration.port", 3306), + configuration.getInt("database-configuration.port", this.storageType == StorageType.POSTGRESQL ? 5432 : 3306), configuration.getString("database-configuration.host"), configuration.getString("database-configuration.database"), configuration.getBoolean("database-configuration.debug"), - DatabaseType.MYSQL + getDatabaseType() ); final ConfigurationSection nearDirSection = configuration.getConfigurationSection("near-direction-replacements"); @@ -184,6 +184,15 @@ public void load() { } } + private DatabaseType getDatabaseType() { + return switch (this.storageType) { + case SQLITE -> DatabaseType.SQLITE; + case MARIADB -> DatabaseType.MARIADB; + case POSTGRESQL -> DatabaseType.POSTGRESQL; + default -> DatabaseType.MYSQL; + }; + } + private void loadReplacePlaceholders() { YamlConfiguration configuration = (YamlConfiguration) this.plugin.getConfig(); diff --git a/src/main/java/fr/maxlego08/essentials/storage/ZStorageManager.java b/src/main/java/fr/maxlego08/essentials/storage/ZStorageManager.java index 63155876..fb8e8047 100644 --- a/src/main/java/fr/maxlego08/essentials/storage/ZStorageManager.java +++ b/src/main/java/fr/maxlego08/essentials/storage/ZStorageManager.java @@ -32,7 +32,7 @@ public ZStorageManager(EssentialsPlugin plugin) { this.plugin = plugin; StorageType storageType = plugin.getConfiguration().getStorageType(); this.iStorage = switch (storageType) { - case HIKARICP, SQLITE, MYSQL, MARIADB -> new SqlStorage(plugin, storageType); + case HIKARICP, SQLITE, MYSQL, MARIADB, POSTGRESQL -> new SqlStorage(plugin, storageType); default -> new JsonStorage(plugin); }; } diff --git a/src/main/java/fr/maxlego08/essentials/storage/database/repositeries/UserRepository.java b/src/main/java/fr/maxlego08/essentials/storage/database/repositeries/UserRepository.java index ea09cbef..73f63609 100644 --- a/src/main/java/fr/maxlego08/essentials/storage/database/repositeries/UserRepository.java +++ b/src/main/java/fr/maxlego08/essentials/storage/database/repositeries/UserRepository.java @@ -12,7 +12,11 @@ import fr.maxlego08.sarah.DatabaseConnection; import fr.maxlego08.sarah.conditions.JoinCondition; import fr.maxlego08.sarah.database.DatabaseType; +import fr.maxlego08.sarah.dialect.SqlDialect; +import fr.maxlego08.sarah.dialect.SqlDialects; +import java.sql.PreparedStatement; +import java.sql.SQLException; import java.util.Collection; import java.util.Date; import java.util.List; @@ -55,7 +59,8 @@ public void upsert(User user) { * We will execute an UPDATE query with a LEFT JOIN */ public void clearExpiredSanctions() { - if (this.connection.getDatabaseConfiguration().getDatabaseType() == DatabaseType.SQLITE) { + DatabaseType databaseType = this.connection.getDatabaseConfiguration().getDatabaseType(); + if (databaseType == DatabaseType.SQLITE) { // TODO - Update Sarah SQLITE for left join plugin.getLogger().warning("Attention, SQLITE does not allow to execute all sql queries, the query that allows to delete inactive sanctions is currently not working."); @@ -73,6 +78,9 @@ public void clearExpiredSanctions() { }); });*/ + } else if (databaseType == DatabaseType.POSTGRESQL) { + clearExpiredSanctionsWithSubQuery("ban_sanction_id"); + clearExpiredSanctionsWithSubQuery("mute_sanction_id"); } else { // Removes ban sanctions update(table -> { @@ -89,6 +97,28 @@ public void clearExpiredSanctions() { } } + private void clearExpiredSanctionsWithSubQuery(String sanctionColumn) { + SqlDialect dialect = SqlDialects.from(this.connection.getDatabaseConfiguration().getDatabaseType()); + String usersTable = dialect.quoteTableReference(getTableName()); + String sanctionsTable = dialect.quoteTableReference(this.connection.getDatabaseConfiguration().getTablePrefix() + "sanctions"); + String column = dialect.quoteIdentifier(sanctionColumn); + String idColumn = dialect.quoteIdentifier("id"); + String expiredAtColumn = dialect.quoteIdentifier("expired_at"); + + String sql = "UPDATE " + usersTable + + " SET " + column + " = NULL" + + " WHERE " + column + " IN (" + + "SELECT " + idColumn + " FROM " + sanctionsTable + " WHERE " + expiredAtColumn + " < ?" + + ")"; + + try (var connection = getConnection(); PreparedStatement statement = connection.prepareStatement(sql)) { + statement.setTimestamp(1, new java.sql.Timestamp(System.currentTimeMillis())); + statement.executeUpdate(); + } catch (SQLException exception) { + exception.printStackTrace(); + } + } + /** * Updates the ban sanction ID for a specified user. * diff --git a/src/main/java/fr/maxlego08/essentials/storage/storages/SqlStorage.java b/src/main/java/fr/maxlego08/essentials/storage/storages/SqlStorage.java index 47b0afc6..f6b97994 100644 --- a/src/main/java/fr/maxlego08/essentials/storage/storages/SqlStorage.java +++ b/src/main/java/fr/maxlego08/essentials/storage/storages/SqlStorage.java @@ -181,7 +181,14 @@ public void processBatchs() { String database = globalDatabaseConfiguration.getDatabase(); boolean debug = globalDatabaseConfiguration.isDebug(); - return new DatabaseConfiguration(tablePrefix, user, password, port, host, database, debug, storageType == StorageType.SQLITE ? DatabaseType.SQLITE : storageType == StorageType.MARIADB ? DatabaseType.MARIADB : DatabaseType.MYSQL); + DatabaseType databaseType = switch (storageType) { + case SQLITE -> DatabaseType.SQLITE; + case MARIADB -> DatabaseType.MARIADB; + case POSTGRESQL -> DatabaseType.POSTGRESQL; + default -> DatabaseType.MYSQL; + }; + + return new DatabaseConfiguration(tablePrefix, user, password, port, host, database, debug, databaseType); } @Override diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index f19b91db..bdd771be 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -16,10 +16,12 @@ enable-debug: false # Storage: # SQLITE - For the launch of the plugin only. -# MYSQL - Allows creating a simple connection to the database, only for small servers. -# HIKARICP - HikariCP is a fast and lightweight JDBC connection pool. It optimizes database connections, ensuring quick acquisition and low latency. This improves performance and reliability, making it ideal for high-demand applications. +# MYSQL - RECOMMENDED +# MARIADB - RECOMMENDED +# POSTGRESQL - RECOMMENDED +# HIKARICP - Legacy MySQL HikariCP storage type, kept for backward compatibility. # -# We advise you to use MYSQL or HIKARICP, the SQLITE storage is only there to install the plugin and do some tests, not all features are available with SQLITE yet. +# We advise you to use MYSQL, MARIADB, or POSTGRESQL, the SQLITE storage is only there to install the plugin and do some tests, not all features are available with SQLITE yet. # The plugin will work, but some features like sanctions update when launching the plugin will not work. # This will be fixed in future plugin updates storage-type: SQLITE @@ -38,7 +40,7 @@ database-configuration: table-prefix: "zessentials_" # IP Address of the machine the database is hosted on host: 192.168.10.10 - # Port of the database, by default, MYSQL's port is 3306 + # Port of the database, by default, MYSQL and MARIADB use 3306, POSTGRESQL uses 5432 port: 3306 # Database username user: homestead @@ -299,4 +301,4 @@ blacklist-uuids: # - TELEPORT_REQUEST_DISABLE: Disables receiving teleport requests from other players default-options: - option: WORLDEDIT_INVENTORY - value: true \ No newline at end of file + value: true diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 5e4a6a36..7e32db16 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -17,3 +17,4 @@ softdepend: api-version: '1.20' libraries: - 'org.mariadb.jdbc:mariadb-java-client:3.5.6' + - 'org.postgresql:postgresql:42.7.3'