Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions loaders/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ dependencies {
api(project(":loaders:mongo-loader"))
api(project(":loaders:mysql-loader"))
api(project(":loaders:redis-loader"))
api(project(":loaders:postgresql-loader"))

compileOnly(paperApi())
}
Expand Down
16 changes: 16 additions & 0 deletions loaders/postgresql-loader/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
plugins {
id("asp.base-conventions")
id("asp.publishing-conventions")
}

dependencies {
compileOnly(project(":api"))

api(libs.hikari)
api("org.postgresql:postgresql:42.7.8")
}

publishConfiguration {
name = "Advanced Slime Paper PostgresSQL Loader"
description = "PostgresSQL loader for Advanced Slime Paper"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
package com.infernalsuite.asp.loaders.postgresql;


import com.infernalsuite.asp.api.exceptions.UnknownWorldException;
import com.infernalsuite.asp.api.loaders.UpdatableLoader;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import org.jetbrains.annotations.ApiStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

public class PostgresqlLoader extends UpdatableLoader {

private static final Logger LOGGER = LoggerFactory.getLogger(PostgresqlLoader.class);
private static final int CURRENT_DB_VERSION = 1;

// Database version handling queries
private static final String CREATE_VERSIONING_TABLE_QUERY =
"CREATE TABLE IF NOT EXISTS database_version (" +
"id SERIAL PRIMARY KEY, " +
"version INT" +
");";

private static final String INSERT_VERSION_QUERY =
"INSERT INTO database_version (id, version) " +
"VALUES (1, ?) " +
"ON CONFLICT (id) DO UPDATE SET version = EXCLUDED.version;";

private static final String GET_VERSION_QUERY =
"SELECT version FROM database_version WHERE id = 1;";

// World handling queries
private static final String CREATE_WORLDS_TABLE_QUERY =
"CREATE TABLE IF NOT EXISTS worlds (" +
"id SERIAL PRIMARY KEY, " +
"name VARCHAR(255) UNIQUE, " +
"locked BIGINT DEFAULT 0 NOT NULL, " +
"world BYTEA" +
");";

private static final String SELECT_WORLD_QUERY =
"SELECT world FROM worlds WHERE name = ?;";

private static final String UPDATE_WORLD_QUERY =
"INSERT INTO worlds (name, world) VALUES (?, ?) " +
"ON CONFLICT (name) DO UPDATE SET world = EXCLUDED.world;";

private static final String DELETE_WORLD_QUERY =
"DELETE FROM worlds WHERE name = ?;";

private static final String LIST_WORLDS_QUERY =
"SELECT name FROM worlds;";

private final HikariDataSource source;

public PostgresqlLoader(String sqlURL, String host, int port, String database, boolean useSSL, String username, String password) throws SQLException {
HikariConfig hikariConfig = new HikariConfig();

sqlURL = sqlURL.replace("{host}", host)
.replace("{port}", String.valueOf(port))
.replace("{database}", database)
.replace("{usessl}", String.valueOf(useSSL));

hikariConfig.setJdbcUrl(sqlURL);
hikariConfig.setUsername(username);
hikariConfig.setPassword(password);
hikariConfig.setDriverClassName("org.postgresql.Driver");

hikariConfig.addDataSourceProperty("cachePrepStmts", "true");
hikariConfig.addDataSourceProperty("prepStmtCacheSize", "250");
hikariConfig.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");

source = new HikariDataSource(hikariConfig);
init();
}

@ApiStatus.Experimental
public PostgresqlLoader(HikariDataSource hikariDataSource) throws SQLException {
source = hikariDataSource;
init();
}

@Override
public void update() throws IOException, NewerStorageException {
try (Connection con = source.getConnection()) {
int version;
try (PreparedStatement statement = con.prepareStatement(GET_VERSION_QUERY);
ResultSet set = statement.executeQuery()) {
version = set.next() ? set.getInt(1) : -1;
}

if (version > CURRENT_DB_VERSION) {
throw new NewerStorageException(CURRENT_DB_VERSION, version);
}

if (version < CURRENT_DB_VERSION) {
LOGGER.warn("Your PostgreSQL database is outdated. Updating now...");
try {
Thread.sleep(5000L);
} catch (InterruptedException ignored) {
}

// Insert/update database version
try (PreparedStatement statement = con.prepareStatement(INSERT_VERSION_QUERY)) {
statement.setInt(1, CURRENT_DB_VERSION);
statement.executeUpdate();
}
}
} catch (SQLException ex) {
throw new IOException(ex);
}
}

@Override
public byte[] readWorld(String worldName) throws UnknownWorldException, IOException {
try (Connection con = source.getConnection();
PreparedStatement statement = con.prepareStatement(SELECT_WORLD_QUERY)) {
statement.setString(1, worldName);
ResultSet set = statement.executeQuery();

if (!set.next()) {
throw new UnknownWorldException(worldName);
}

return set.getBytes("world");
} catch (SQLException ex) {
throw new IOException(ex);
}
}

@Override
public boolean worldExists(String worldName) throws IOException {
try (Connection con = source.getConnection();
PreparedStatement statement = con.prepareStatement(SELECT_WORLD_QUERY)) {
statement.setString(1, worldName);
ResultSet set = statement.executeQuery();
return set.next();
} catch (SQLException ex) {
throw new IOException(ex);
}
}

@Override
public List<String> listWorlds() throws IOException {
List<String> worldList = new ArrayList<>();
try (Connection con = source.getConnection();
PreparedStatement statement = con.prepareStatement(LIST_WORLDS_QUERY)) {
ResultSet set = statement.executeQuery();
while (set.next()) {
worldList.add(set.getString("name"));
}
} catch (SQLException ex) {
throw new IOException(ex);
}
return worldList;
}

@Override
public void saveWorld(String worldName, byte[] serializedWorld) throws IOException {
try (Connection con = source.getConnection();
PreparedStatement statement = con.prepareStatement(UPDATE_WORLD_QUERY)) {
statement.setString(1, worldName);
statement.setBytes(2, serializedWorld);
statement.executeUpdate();
} catch (SQLException ex) {
throw new IOException(ex);
}
}

@Override
public void deleteWorld(String worldName) throws IOException, UnknownWorldException {
try (Connection con = source.getConnection();
PreparedStatement statement = con.prepareStatement(DELETE_WORLD_QUERY)) {
statement.setString(1, worldName);
if (statement.executeUpdate() == 0) {
throw new UnknownWorldException(worldName);
}
} catch (SQLException ex) {
throw new IOException(ex);
}
}

private void init() throws SQLException {
try (Connection con = source.getConnection()) {
try (PreparedStatement statement = con.prepareStatement(CREATE_WORLDS_TABLE_QUERY)) {
statement.execute();
}
try (PreparedStatement statement = con.prepareStatement(CREATE_VERSIONING_TABLE_QUERY)) {
statement.execute();
}
}
}
}
1 change: 1 addition & 0 deletions plugin/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ tasks {
relocate("com.zaxxer.hikari", "com.infernalsuite.asp.libs.hikari")
relocate("com.mongodb", "com.infernalsuite.asp.libs.mongo")
relocate("io.lettuce", "com.infernalsuite.asp.libs.lettuce")
relocate("org.postgresql", "com.infernalsuite.asp.libs.postgresql")
relocate("org.bson", "com.infernalsuite.asp.libs.bson")
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ public class DatasourcesConfig {
private FileConfig fileConfig = new FileConfig();
@Setting("mysql")
private MysqlConfig mysqlConfig = new MysqlConfig();
@Setting("postgresql")
private PostgresqlConfig postgresqlConfig = new PostgresqlConfig();
@Setting("mongodb")
private MongoDBConfig mongoDbConfig = new MongoDBConfig();
@Setting("redis")
Expand Down Expand Up @@ -108,6 +110,97 @@ public void setSqlUrl(String sqlUrl) {
}
}

@ConfigSerializable
public static class PostgresqlConfig {

@Setting("enabled")
private boolean enabled = false;

@Setting("host")
private String host = "127.0.0.1";
@Setting("port")
private int port = 5432;

@Setting("username")
private String username = "slimeworldmanager";
@Setting("password")
private String password = "";

@Setting("database")
private String database = "slimeworldmanager";

@Setting("usessl")
private boolean usessl = false;

@Setting("sqlUrl")
private String sqlUrl = "jdbc:postgresql://{host}:{port}/{database}?ssl={usessl}";;

public boolean isEnabled() {
return enabled;
}

public void setEnabled(boolean enabled) {
this.enabled = enabled;
}

public String getHost() {
return host;
}

public void setHost(String host) {
this.host = host;
}

public int getPort() {
return port;
}

public void setPort(int port) {
this.port = port;
}

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}

public String getDatabase() {
return database;
}

public void setDatabase(String database) {
this.database = database;
}

public boolean isUsessl() {
return usessl;
}

public void setUsessl(boolean usessl) {
this.usessl = usessl;
}

public String getSqlUrl() {
return sqlUrl;
}

public void setSqlUrl(String sqlUrl) {
this.sqlUrl = sqlUrl;
}

}

@ConfigSerializable
public static class MongoDBConfig {

Expand Down Expand Up @@ -288,6 +381,14 @@ public void setMysqlConfig(MysqlConfig mysqlConfig) {
this.mysqlConfig = mysqlConfig;
}

public PostgresqlConfig getPostgresqlConfig() {
return postgresqlConfig;
}

public void setPostgresqlConfig(PostgresqlConfig postgresqlConfig) {
this.postgresqlConfig = postgresqlConfig;
}

public MongoDBConfig getMongoDbConfig() {
return mongoDbConfig;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import com.infernalsuite.asp.loaders.file.FileLoader;
import com.infernalsuite.asp.loaders.mongo.MongoLoader;
import com.infernalsuite.asp.loaders.mysql.MysqlLoader;
import com.infernalsuite.asp.loaders.postgresql.PostgresqlLoader;
import com.infernalsuite.asp.loaders.redis.RedisLoader;
import com.mongodb.MongoException;
import io.lettuce.core.RedisException;
Expand Down Expand Up @@ -46,6 +47,21 @@ public LoaderManager() {
}
}

// Postgresql loader
com.infernalsuite.asp.plugin.config.DatasourcesConfig.PostgresqlConfig postgresqlConfig = config.getPostgresqlConfig();
if (postgresqlConfig.isEnabled()) {
try {
registerLoader("postgresql", new PostgresqlLoader(
postgresqlConfig.getSqlUrl(),
postgresqlConfig.getHost(), postgresqlConfig.getPort(),
postgresqlConfig.getDatabase(), postgresqlConfig.isUsessl(),
postgresqlConfig.getUsername(), postgresqlConfig.getPassword()
));
} catch (final SQLException ex) {
LOGGER.error("Failed to establish connection to the PostgresSQL server:", ex);
}
}

// MongoDB loader
com.infernalsuite.asp.plugin.config.DatasourcesConfig.MongoDBConfig mongoConfig = config.getMongoDbConfig();

Expand Down
Loading