Skip to content

Commit 7f6b2a8

Browse files
committed
Add PostgresqlLoader
Took 57 minutes
1 parent 6c03ab0 commit 7f6b2a8

File tree

8 files changed

+347
-1
lines changed

8 files changed

+347
-1
lines changed

loaders/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ dependencies {
1111
api(project(":loaders:mongo-loader"))
1212
api(project(":loaders:mysql-loader"))
1313
api(project(":loaders:redis-loader"))
14+
api(project(":loaders:postgresql-loader"))
1415

1516
compileOnly(paperApi())
1617
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
plugins {
2+
id("asp.base-conventions")
3+
id("asp.publishing-conventions")
4+
}
5+
6+
dependencies {
7+
compileOnly(project(":api"))
8+
9+
api(libs.hikari)
10+
api("org.postgresql:postgresql:42.7.8")
11+
}
12+
13+
publishConfiguration {
14+
name = "Advanced Slime Paper PostgresSQL Loader"
15+
description = "PostgresSQL loader for Advanced Slime Paper"
16+
}
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
package com.infernalsuite.asp.loaders.postgresql;
2+
3+
4+
import com.infernalsuite.asp.api.exceptions.UnknownWorldException;
5+
import com.infernalsuite.asp.api.loaders.UpdatableLoader;
6+
import com.zaxxer.hikari.HikariConfig;
7+
import com.zaxxer.hikari.HikariDataSource;
8+
import org.jetbrains.annotations.ApiStatus;
9+
import org.slf4j.Logger;
10+
import org.slf4j.LoggerFactory;
11+
12+
import java.io.IOException;
13+
import java.sql.Connection;
14+
import java.sql.PreparedStatement;
15+
import java.sql.ResultSet;
16+
import java.sql.SQLException;
17+
import java.util.ArrayList;
18+
import java.util.List;
19+
20+
public class PostgresqlLoader extends UpdatableLoader {
21+
22+
private static final Logger LOGGER = LoggerFactory.getLogger(PostgresqlLoader.class);
23+
private static final int CURRENT_DB_VERSION = 1;
24+
25+
// Database version handling queries
26+
private static final String CREATE_VERSIONING_TABLE_QUERY =
27+
"CREATE TABLE IF NOT EXISTS database_version (" +
28+
"id SERIAL PRIMARY KEY, " +
29+
"version INT" +
30+
");";
31+
32+
private static final String INSERT_VERSION_QUERY =
33+
"INSERT INTO database_version (id, version) " +
34+
"VALUES (1, ?) " +
35+
"ON CONFLICT (id) DO UPDATE SET version = EXCLUDED.version;";
36+
37+
private static final String GET_VERSION_QUERY =
38+
"SELECT version FROM database_version WHERE id = 1;";
39+
40+
// World handling queries
41+
private static final String CREATE_WORLDS_TABLE_QUERY =
42+
"CREATE TABLE IF NOT EXISTS worlds (" +
43+
"id SERIAL PRIMARY KEY, " +
44+
"name VARCHAR(255) UNIQUE, " +
45+
"locked BIGINT DEFAULT 0 NOT NULL, " +
46+
"world BYTEA" +
47+
");";
48+
49+
private static final String SELECT_WORLD_QUERY =
50+
"SELECT world FROM worlds WHERE name = ?;";
51+
52+
private static final String UPDATE_WORLD_QUERY =
53+
"INSERT INTO worlds (name, world) VALUES (?, ?) " +
54+
"ON CONFLICT (name) DO UPDATE SET world = EXCLUDED.world;";
55+
56+
private static final String DELETE_WORLD_QUERY =
57+
"DELETE FROM worlds WHERE name = ?;";
58+
59+
private static final String LIST_WORLDS_QUERY =
60+
"SELECT name FROM worlds;";
61+
62+
private final HikariDataSource source;
63+
64+
public PostgresqlLoader(String sqlURL, String host, int port, String database, boolean useSSL, String username, String password) throws SQLException {
65+
HikariConfig hikariConfig = new HikariConfig();
66+
67+
sqlURL = sqlURL.replace("{host}", host)
68+
.replace("{port}", String.valueOf(port))
69+
.replace("{database}", database)
70+
.replace("{usessl}", String.valueOf(useSSL));
71+
72+
hikariConfig.setJdbcUrl(sqlURL);
73+
hikariConfig.setUsername(username);
74+
hikariConfig.setPassword(password);
75+
hikariConfig.setDriverClassName("org.postgresql.Driver");
76+
77+
hikariConfig.addDataSourceProperty("cachePrepStmts", "true");
78+
hikariConfig.addDataSourceProperty("prepStmtCacheSize", "250");
79+
hikariConfig.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
80+
81+
source = new HikariDataSource(hikariConfig);
82+
init();
83+
}
84+
85+
@ApiStatus.Experimental
86+
public PostgresqlLoader(HikariDataSource hikariDataSource) throws SQLException {
87+
source = hikariDataSource;
88+
init();
89+
}
90+
91+
@Override
92+
public void update() throws IOException, NewerStorageException {
93+
try (Connection con = source.getConnection()) {
94+
int version;
95+
try (PreparedStatement statement = con.prepareStatement(GET_VERSION_QUERY);
96+
ResultSet set = statement.executeQuery()) {
97+
version = set.next() ? set.getInt(1) : -1;
98+
}
99+
100+
if (version > CURRENT_DB_VERSION) {
101+
throw new NewerStorageException(CURRENT_DB_VERSION, version);
102+
}
103+
104+
if (version < CURRENT_DB_VERSION) {
105+
LOGGER.warn("Your PostgreSQL database is outdated. Updating now...");
106+
try {
107+
Thread.sleep(5000L);
108+
} catch (InterruptedException ignored) {
109+
}
110+
111+
// Insert/update database version
112+
try (PreparedStatement statement = con.prepareStatement(INSERT_VERSION_QUERY)) {
113+
statement.setInt(1, CURRENT_DB_VERSION);
114+
statement.executeUpdate();
115+
}
116+
}
117+
} catch (SQLException ex) {
118+
throw new IOException(ex);
119+
}
120+
}
121+
122+
@Override
123+
public byte[] readWorld(String worldName) throws UnknownWorldException, IOException {
124+
try (Connection con = source.getConnection();
125+
PreparedStatement statement = con.prepareStatement(SELECT_WORLD_QUERY)) {
126+
statement.setString(1, worldName);
127+
ResultSet set = statement.executeQuery();
128+
129+
if (!set.next()) {
130+
throw new UnknownWorldException(worldName);
131+
}
132+
133+
return set.getBytes("world");
134+
} catch (SQLException ex) {
135+
throw new IOException(ex);
136+
}
137+
}
138+
139+
@Override
140+
public boolean worldExists(String worldName) throws IOException {
141+
try (Connection con = source.getConnection();
142+
PreparedStatement statement = con.prepareStatement(SELECT_WORLD_QUERY)) {
143+
statement.setString(1, worldName);
144+
ResultSet set = statement.executeQuery();
145+
return set.next();
146+
} catch (SQLException ex) {
147+
throw new IOException(ex);
148+
}
149+
}
150+
151+
@Override
152+
public List<String> listWorlds() throws IOException {
153+
List<String> worldList = new ArrayList<>();
154+
try (Connection con = source.getConnection();
155+
PreparedStatement statement = con.prepareStatement(LIST_WORLDS_QUERY)) {
156+
ResultSet set = statement.executeQuery();
157+
while (set.next()) {
158+
worldList.add(set.getString("name"));
159+
}
160+
} catch (SQLException ex) {
161+
throw new IOException(ex);
162+
}
163+
return worldList;
164+
}
165+
166+
@Override
167+
public void saveWorld(String worldName, byte[] serializedWorld) throws IOException {
168+
try (Connection con = source.getConnection();
169+
PreparedStatement statement = con.prepareStatement(UPDATE_WORLD_QUERY)) {
170+
statement.setString(1, worldName);
171+
statement.setBytes(2, serializedWorld);
172+
statement.executeUpdate();
173+
} catch (SQLException ex) {
174+
throw new IOException(ex);
175+
}
176+
}
177+
178+
@Override
179+
public void deleteWorld(String worldName) throws IOException, UnknownWorldException {
180+
try (Connection con = source.getConnection();
181+
PreparedStatement statement = con.prepareStatement(DELETE_WORLD_QUERY)) {
182+
statement.setString(1, worldName);
183+
if (statement.executeUpdate() == 0) {
184+
throw new UnknownWorldException(worldName);
185+
}
186+
} catch (SQLException ex) {
187+
throw new IOException(ex);
188+
}
189+
}
190+
191+
private void init() throws SQLException {
192+
try (Connection con = source.getConnection()) {
193+
try (PreparedStatement statement = con.prepareStatement(CREATE_WORLDS_TABLE_QUERY)) {
194+
statement.execute();
195+
}
196+
try (PreparedStatement statement = con.prepareStatement(CREATE_VERSIONING_TABLE_QUERY)) {
197+
statement.execute();
198+
}
199+
}
200+
}
201+
}

plugin/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ tasks {
3131
relocate("com.zaxxer.hikari", "com.infernalsuite.asp.libs.hikari")
3232
relocate("com.mongodb", "com.infernalsuite.asp.libs.mongo")
3333
relocate("io.lettuce", "com.infernalsuite.asp.libs.lettuce")
34+
relocate("org.postgresql", "com.infernalsuite.asp.libs.postgresql")
3435
relocate("org.bson", "com.infernalsuite.asp.libs.bson")
3536
}
3637

plugin/src/main/java/com/infernalsuite/asp/plugin/config/DatasourcesConfig.java

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ public class DatasourcesConfig {
1010
private FileConfig fileConfig = new FileConfig();
1111
@Setting("mysql")
1212
private MysqlConfig mysqlConfig = new MysqlConfig();
13+
@Setting("postgresql")
14+
private PostgresqlConfig postgresqlConfig = new PostgresqlConfig();
1315
@Setting("mongodb")
1416
private MongoDBConfig mongoDbConfig = new MongoDBConfig();
1517
@Setting("redis")
@@ -108,6 +110,97 @@ public void setSqlUrl(String sqlUrl) {
108110
}
109111
}
110112

113+
@ConfigSerializable
114+
public static class PostgresqlConfig {
115+
116+
@Setting("enabled")
117+
private boolean enabled = false;
118+
119+
@Setting("host")
120+
private String host = "127.0.0.1";
121+
@Setting("port")
122+
private int port = 5432;
123+
124+
@Setting("username")
125+
private String username = "slimeworldmanager";
126+
@Setting("password")
127+
private String password = "";
128+
129+
@Setting("database")
130+
private String database = "slimeworldmanager";
131+
132+
@Setting("usessl")
133+
private boolean usessl = false;
134+
135+
@Setting("sqlUrl")
136+
private String sqlUrl = "jdbc:postgresql://{host}:{port}/{database}?ssl={usessl}";;
137+
138+
public boolean isEnabled() {
139+
return enabled;
140+
}
141+
142+
public void setEnabled(boolean enabled) {
143+
this.enabled = enabled;
144+
}
145+
146+
public String getHost() {
147+
return host;
148+
}
149+
150+
public void setHost(String host) {
151+
this.host = host;
152+
}
153+
154+
public int getPort() {
155+
return port;
156+
}
157+
158+
public void setPort(int port) {
159+
this.port = port;
160+
}
161+
162+
public String getUsername() {
163+
return username;
164+
}
165+
166+
public void setUsername(String username) {
167+
this.username = username;
168+
}
169+
170+
public String getPassword() {
171+
return password;
172+
}
173+
174+
public void setPassword(String password) {
175+
this.password = password;
176+
}
177+
178+
public String getDatabase() {
179+
return database;
180+
}
181+
182+
public void setDatabase(String database) {
183+
this.database = database;
184+
}
185+
186+
public boolean isUsessl() {
187+
return usessl;
188+
}
189+
190+
public void setUsessl(boolean usessl) {
191+
this.usessl = usessl;
192+
}
193+
194+
public String getSqlUrl() {
195+
return sqlUrl;
196+
}
197+
198+
public void setSqlUrl(String sqlUrl) {
199+
this.sqlUrl = sqlUrl;
200+
}
201+
202+
}
203+
111204
@ConfigSerializable
112205
public static class MongoDBConfig {
113206

@@ -288,6 +381,14 @@ public void setMysqlConfig(MysqlConfig mysqlConfig) {
288381
this.mysqlConfig = mysqlConfig;
289382
}
290383

384+
public PostgresqlConfig getPostgresqlConfig() {
385+
return postgresqlConfig;
386+
}
387+
388+
public void setPostgresqlConfig(PostgresqlConfig postgresqlConfig) {
389+
this.postgresqlConfig = postgresqlConfig;
390+
}
391+
291392
public MongoDBConfig getMongoDbConfig() {
292393
return mongoDbConfig;
293394
}

plugin/src/main/java/com/infernalsuite/asp/plugin/loader/LoaderManager.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import com.infernalsuite.asp.loaders.file.FileLoader;
77
import com.infernalsuite.asp.loaders.mongo.MongoLoader;
88
import com.infernalsuite.asp.loaders.mysql.MysqlLoader;
9+
import com.infernalsuite.asp.loaders.postgresql.PostgresqlLoader;
910
import com.infernalsuite.asp.loaders.redis.RedisLoader;
1011
import com.mongodb.MongoException;
1112
import io.lettuce.core.RedisException;
@@ -46,6 +47,21 @@ public LoaderManager() {
4647
}
4748
}
4849

50+
// Postgresql loader
51+
com.infernalsuite.asp.plugin.config.DatasourcesConfig.PostgresqlConfig postgresqlConfig = config.getPostgresqlConfig();
52+
if (postgresqlConfig.isEnabled()) {
53+
try {
54+
registerLoader("postgresql", new PostgresqlLoader(
55+
postgresqlConfig.getSqlUrl(),
56+
postgresqlConfig.getHost(), postgresqlConfig.getPort(),
57+
postgresqlConfig.getDatabase(), postgresqlConfig.isUsessl(),
58+
postgresqlConfig.getUsername(), postgresqlConfig.getPassword()
59+
));
60+
} catch (final SQLException ex) {
61+
LOGGER.error("Failed to establish connection to the PostgresSQL server:", ex);
62+
}
63+
}
64+
4965
// MongoDB loader
5066
com.infernalsuite.asp.plugin.config.DatasourcesConfig.MongoDBConfig mongoConfig = config.getMongoDbConfig();
5167

0 commit comments

Comments
 (0)